Show HN: ProtoLink – A Python runtime for distributed A2A agents ProtoLink, a new Python framework for building distributed, LLM-powered autonomous agents that communicate via Google's Agent-to-Agent (A2A) protocol, has been released. The framework enables developers to create multi-agent systems with minimal boilerplate, supporting tool integration, context management, and structured workflows. Its 0.5.0 update introduces Structured Flows for deterministic agent orchestration. 📌 The framework is currently in betaand is subject to change. ProtoLink is a lightweight Python framework that allows you to build autonomous, LLM-powered agents that communicate directly, manage context, and integrate tools seamlessly . Build distributed multi-agent systems with minimal boilerplate and production-oriented architecture. Each ProtoLink agent is a self-contained runtime that can embed an LLM , manage execution context, expose and consume tools native or via MCP https://modelcontextprotocol.io/docs/getting-started/intro , and coordinate with other agents over a unified transport layer . ProtoLink implements and extends Google’s Agent-to-Agent A2A https://a2a-protocol.org/v0.3.0/specification/?utm source=chatgpt.com specification for agent identity, capability declaration, and discovery , while going beyond A2A by enabling LLM & tool integration . ProtoLink is an A2A-native agent runtimefor building distributed, typed, observable agent systems. LangChain composes model calls; ProtoLink runs agents. Find more in the Protolink whitepaper https://nmaroulis.github.io/protolink/whitepaper . The framework emphasizes minimal boilerplate , explicit control , and production-readiness , making it suitable for both research and real-world systems. Tool calling , agent delegation , LLM invocation , and task execution logic are provided by Protolink. You describe the agent, its tools, and its capabilities; ProtoLink handles the infer loop, native tool calling where available, JSON fallback for smaller models, action validation, and deterministic execution. Protolink uses dynamic semantic context injection , automatically enriching agent prompts with downstream agent capabilities, tools, and communication contracts so agents remain fully decoupled while adapting their behavior at runtime without hardcoded integrations . Focus on your agent logic- ProtoLink handles communication, authentication, LLM integration, and tool management for you. Follow the API documentation here 📚 documentation https://nmaroulis.github.io/protolink/ . The following articles published on Level Up Coding on give a hands-on guide and overview of Protolink: Medium - 📝 Building My Own Local “Claude Code”: What I Learned Demystifying Agentic Coding under the Hood https://levelup.gitconnected.com/building-my-own-local-claude-code-what-i-learned-demystifying-agentic-coding-under-the-hood-8772874b91b8 - 📝 Your First Autonomous Agent Mesh - Easier Than You Think https://levelup.gitconnected.com/your-first-autonomous-agent-mesh-easier-than-you-think-ce697b3dd87a - 📝 Build Easily Your Own “Claude Code” with Three Agents: Brain, Hands, and Coordinator https://medium.com/gitconnected/build-easily-your-own-claude-code-with-three-agents-brain-hands-and-coordinator-5236b392ddf0 Update 0.5.0 introduces Structured Flows , a new feature that allows you to define structured workflows for your agents. Building on A2A transport layer, flows allow you to define complex workflows for your agents that can be executed in a structured & deterministic manner . Technically, each flow is a deterministic Flow.execute Task - Task state machine over A2A Task , Message , Artifact , and AgentCard primitives, keeping orchestration protocol-native instead of creating a separate graph runtime. Again the Semantic Context Injection is handled by Protolink, meaning that the agents will automatically have the necessary context about the flow they are executing and what to pass down to next agents in the pipeline. See more here 🔀 flows https://nmaroulis.github.io/protolink/flows/ . In Protolink the agent is the central component that handles all the logic and incorporates the LLM , tools , transport layer through AgentClient and AgentServer , the Storage and OpenTelemetry for logging. Each of these components is a separate module that can be used independently or in combination with other modules. Each component is pluggable to the agent and can be replaced with your own implementation. Here's a simple example on how easy it is to create an agent , define the neccessary modules , provided by Protolink and plug 🔌 them to the agent. The agent is then ready to discover other agents and send & receive Tasks . python from protolink.agents import Agent 1. Initialize & start the Registry for A2A Discovery optional from protolink.discovery import Registry registry = Registry url="http://127.0.0.1:9000", transport="http" registry.start background=True 2. Initialize OpenAI API LLM optional from protolink.llms.api import OpenAILLM llm = OpenAILLM model="gpt-5.2" for local model use protolink.llms.server.OllamaLLM and more... 3. Initialize Storage optional from protolink.storage import SQLiteStorage storage = SQLiteStorage db path="agent.db" 4. Initialize Telemetry optional from protolink.telemetry import LangfuseTelemetry telemetry = LangfuseTelemetry 5a. Define the agent card agent card = { "url": "http://127.0.0.1:8020", "name": "example agent", "description": "A dummy agent", } 5b. Initialize the agent http transport will be created based on the agent card url Plug the modules to the agent agent = Agent card=agent card, transport="http", llm=llm, registry=registry, storage=storage, telemetry=telemetry 6. Add Native tool Tools from MCP can also be added easily using the MCPToolAdapter @agent.tool name="add", description="Add two numbers" async def add numbers a: int, b: int : return a + b Start the agent. agent.start The agent is now fully initialized and prepared to discover & be discovered by peers , send & receive tasks across your system. Running tasks can be canceled by task ID across local and remote transports, with final canceled state propagated through RunContext and streamed runtime events. Note : agent.start automatically adapts to the current environment under the hood, working seamlessly in standard Python scripts, async applications, and Jupyter notebooks with both blocking and background execution modes. When background=True , it runs the agent non-blocking using an event loop task or a dedicated background thread depending on the runtime context. See how Protolink handles agent execution lifecycle https://nmaroulis.github.io/protolink/agent/ lifecycle-methods . A2A Protocol Implementation : Fully compatible with Google's A2A specification Extended Capabilities : Unified Client/Server Agent Model : Single agent instance handles both client and server responsibilities, reducing complexity. Transport Layer Flexibility : Swap between HTTP , WebSocket , gRPC or in-memory transports with minimal code changes. Simplified Agent Creation and Registration : Create and register autonomous AI agents with just a few lines of code. LLM-Ready Architecture: Native support for integrating LLMs to agents APIs & local directly as agent modules, allowing agents to expose LLM calls, reasoning functions, and chain-of-thought utilities with zero friction. Tooling : Native support for integrating tools to agents APIs & local directly as agent modules. Native Adapter for MCP tooling . Runtime Transport Layer : In-process agent communication using a shared memory space. Agents can easily communicate with each other within the same process, making it easier to build and test agent systems. Enhanced Security : Native TLS/mTLS , OAuth 2.0 , bearer tokens, Basic auth, and API key support . Comprehensive Logging : Built-in support for console colored , file-based logging including structured JSON output , and a no-op quiet logger.- Built-in support for streaming and async operations. Planned Integrations : Advanced Orchestration Patterns - Multi-step workflows, supervisory agents, role routing, and hierarchical control systems. Runtime Control Plane : Typed client/server requests for cancellation, state inspection/reset/compaction, and LLM history compaction without exposing maintenance operations to the model prompt. Observable & Replayable Runs : RunContext , RunEvent , RunReport , budgets, policy decisions, approvals, and redaction for production-facing UIs and tests. Developer Tools : protolink doctor , registry inspection, run replay, and a local dashboard with agent ping, HTTP chat, registry health, and a disabled Studio preview for future topology work. Protolink exposes a dashboard UI through the protolink dashboard CLI command, projecting run-store and registry state into a local browser view. Run the dashboard against an existing run store and a live registry both optional : protolink dashboard --store runs.db --registry-url http://127.0.0.1:9010 --open The command serves a dependency-free local HTML dashboard over Protolink's public devtool collectors. It reads persisted task snapshots and RunReport records from SQLiteRunStore , pulls AgentCard entries from the registry, and exposes live dashboard actions through local endpoints for snapshot refresh, run replay, HTTP agent /status probes, and standard agent chat. The page does not create agents; it projects the runtime state your agents already emit through AgentCard , RunContext , RunEvent , and RunReport . ProtoLink implements Google’s A2A protocol at the wire level , while providing a higher-level agent runtime that unifies client, server, transport, tools, and LLMs into a single composable abstraction the Agent . | Concept | Google A2A | ProtoLink | |---|---|---| | Agent | Protocol-level concept | Runtime object | | Transport | External server concern | Agent-owned | | Client | Separate | Built-in | | LLM | Out of scope | First-class | | Tools | Out of scope | Native + MCP | | UX | Enterprise infra | Developer-first | Protolink takes a centralized agent approach compared to Google's A2A protocol, which separates client and server concerns. Here's how it differs: | Feature | Google's A2A | Protolink | |---|---|---| Architecture | Decoupled client/server | Unified agent with built-in client/server | Transport | Factory-based with provider pattern | Direct interface implementation | Deployment | Requires managing separate services | Single process by default, scales to distributed | Complexity | Higher needs orchestration | Lower simpler to reason about | Flexibility | Runtime configuration via providers | Code-based implementation | Use Case | Large-scale, distributed systems | Both simple and complex agent systems | Simplified Development : Manage a single agent runtime without separate client/server codebases. Reduced Boilerplate : Common functionality is handled by the base Agent /nMaroulis/protolink/blob/main class, letting you focus on agent logic. Flexible Deployment : Start with a single process, scale to distributed when needed Unified State Management : Shared context between client and server operations Maintainability :- Direct code paths for easier debugging - Clear control flow with fewer abstraction layers - Type-safe interfaces for better IDE support Extensibility :- Easily add new transport implementations - Simple interface-based design - No complex configuration needed for common use cases Real Multi-Agent Systems : Build autonomous agents with embedded LLMs, tools, and memory that communicate directly. Simple API : Built from the ground-up for minimal boilerplate , letting you focus on agent logic rather than infrastructure. Developer Friendly : Clean abstractions and direct code paths make debugging and maintenance a breeze. Production Oriented : Designed for performance, reliability, and scalability in real-world deployments. Extensible & Interoperable : Add new agents, transports, or protocols easily; compatible with A2A and MCP standards. Community Focused : Designed for the open-source community with clear contribution guidelines. This will install the base package without any optional dependencies. Using uv recommended uv add protolink Using pip pip install protolink Protolink supports optional features through extras. Install them using square brackets: Note: uv add can be replace with pip install if preferred. Install with all optional dependencies uv add "protolink all " Install with HTTP support for web-based agents uv add "protolink http " Install with gRPC support for service-to-service agents uv add "protolink grpc " Install all the supported LLM libraries uv add "protolink llms " For development includes all optional dependencies and testing tools uv add "protolink dev " To install from source and all optional dependencies: git clone https://github.com/nmaroulis/protolink.git cd protolink uv pip install -e ". dev " 👉 The example found in the jupyter notebooks here: Hello World Example https://github.com/nMaroulis/protolink/tree/main/examples/notebooks/basic example python from protolink.agents import Agent from protolink.models import AgentCard from protolink.tools.adapters import MCPToolAdapter from protolink.llms.api import OpenAILLM from protolink.discovery import Registry Initialize Registry for A2A Discovery registry = Registry url="http://127.0.0.1:9000", transport="http" registry.start background=True Define the agent card agent card = AgentCard name="example agent", description="A dummy agent", url="http://127.0.0.1:8020", OpenAI API LLM llm = OpenAILLM model="gpt-5.2" Initialize the agent agent = Agent agent card, transport="http", llm=llm, registry=registry Add Native tool @agent.tool name="add", description="Add two numbers" async def add numbers a: int, b: int : return a + b Add MCP tools and return them as protolink native tools mcp adapter = MCPToolAdapter transport="sse", url="https://api.example.com/mcp/sse" mcp tools = mcp adapter.get tools for mcp tool in mcp tools: agent.add tool mcp tool Start the agent agent.start When using an HTTP-compatible transport http , sse , json-rpc , or sse-json-rpc , the Agent and Registry expose lightweight browser pages: registry status at /status , agent status at /status , and agent chat at /chat for LLM-backed agents. WebSocket, gRPC, and runtime transports keep the same logical endpoints for clients, but they do not serve browser HTML directly. | | ProtoLink's core philosophy is a simple API with progressive control : the shortest path should be enough for prototyping, while production features remain available through explicit, composable objects instead of adding every advanced option to Agent . For a fast prototype, pass a transport name. ProtoLink builds the transport with safe defaults: python from protolink import Agent, AgentCard card = AgentCard name="assistant", description="General-purpose assistant", url="http://127.0.0.1:8000", agent = Agent card=card, transport="http" For production, configure the transport directly and pass the completed object to the same Agent API: python from protolink import Agent, AgentCard, RetryPolicy, TLSConfig, TransportConfig, TransportLimits from protolink.transport import HTTPTransport card = AgentCard name="assistant", description="Production assistant", url="https://agent.internal:8443", transport = HTTPTransport url=card.url, tls=TLSConfig certfile="certs/agent.pem", keyfile="certs/agent-key.pem", cafile="certs/ca.pem", , config=TransportConfig limits=TransportLimits max concurrent requests=200 , retry=RetryPolicy max attempts=3 , , agent = Agent card=card, transport=transport This keeps Agent concerns such as reasoning, tools, state, and policy separate from transport concerns such as TLS, payload limits, retries, keepalive, and connection management. The same rule applies to AgentClient and Registry : pass a string for defaults or a configured transport object for advanced behavior. Every service boundary can therefore use its own certificates and capacity policy without expanding or coupling the high-level constructors. Follow the API documentation here: Documentation https://nmaroulis.github.io/protolink/ For Agent-to-Agent & Agent-to-Registry communication: http · HTTPTransport https://github.com/nMaroulis/protolink/blob/main/protolink/transport/http transport.py : Uses HTTP/HTTPS for synchronous requests. Two ASGI implementations are available.- Lightweight: starlette , httpx & uvicorn - Advanced | Schema Validation: fastapi , pydantic & uvicorn - Lightweight: sse , json-rpc , sse-json-rpc · SSEJSONRPCTransport https://github.com/nMaroulis/protolink/blob/main/protolink/transport/sse jsonrpc transport.py : Uses normal HTTP routes plus Server-Sent Events for streamed task updates. websocket · WebSocketTransport https://github.com/nMaroulis/protolink/blob/main/protolink/transport/websocket transport.py : Uses WebSocket for streaming requests. websockets grpc · GRPCTransport https://github.com/nMaroulis/protolink/blob/main/protolink/transport/grpc transport.py : Uses gRPC unary calls and unary-stream task events over compact JSON envelopes. grpcio runtime · RuntimeTransport https://github.com/nMaroulis/protolink/blob/main/protolink/transport/runtime transport.py : Simple in-process, in-memory transport . All network transports share one native TLS API. Use https:// , wss:// , or grpcs:// and configure TLSConfig on the transport: python from protolink import Agent, AgentCard, TLSConfig from protolink.transport import HTTPTransport tls = TLSConfig certfile="certs/agent.pem", keyfile="certs/agent-key.pem", cafile="certs/ca.pem", require client cert=True, enable mutual TLS card = AgentCard name="secure-agent", description="Agent served over HTTPS", url="https://agent.internal:8443", transport = HTTPTransport url=card.url, tls=tls, agent = Agent card=card, transport=transport TLSConfig encrypts the connection and verifies certificates; Authenticator implementations handle application credentials and authorization. They are independent and can be combined. See the TLS and mutual TLS guide https://nmaroulis.github.io/protolink/docs/transport/ tls-and-mutual-tls and examples/tls agent.py https://github.com/nMaroulis/protolink/blob/main/examples/tls agent.py . All transports also share one production configuration surface: python from protolink import Agent, AgentCard, RetryPolicy, TransportConfig, TransportLimits from protolink.transport import HTTPTransport agent card = AgentCard name="production-agent", description="Production task worker", url="http://127.0.0.1:8000", transport config = TransportConfig limits=TransportLimits max concurrent requests=200, max concurrent streams=50 , retry=RetryPolicy max attempts=3 , transport = HTTPTransport url=agent card.url, config=transport config agent = Agent card=agent card, transport=transport Retries remain off by default and run only for explicitly idempotent request specifications. Correlation IDs, server-side idempotency replay, bounded payloads/concurrency, loop-safe pooled-resource shutdown, WebSocket keepalive, typed transport errors, local metric snapshots, and /healthz and /readyz probes use the same contract across HTTP, SSE JSON-RPC, WebSocket, gRPC, and RuntimeTransport. The grpc extra also installs standard gRPC health checking and reflection. See the production transport guide https://nmaroulis.github.io/protolink/docs/transport/ production-configuration and examples/transport production.py https://github.com/nMaroulis/protolink/blob/main/examples/transport production.py . These controls exist so changing protocols does not change the Agent's production safety model. Limits prevent one payload or traffic burst from exhausting the process; retries recover explicitly safe operations from temporary connection failures; idempotency prevents those retries from executing the same operation twice; and metrics and health probes make the behavior visible. The defaults are suitable for getting started and do not enable retries automatically. The application-facing transport types are exported directly from protolink : | API | Purpose | |---|---| TransportConfig | One immutable configuration for limits, retries, keepalive, shutdown, idempotency replay, and metrics. | TransportLimits | Request, response, stream-event, request-concurrency, and stream-concurrency bounds. | RetryPolicy | Explicit attempt count, exponential backoff, jitter, and retryable method set. | TransportMetricsSnapshot | Dependency-free per-instance counters, byte totals, active work, retries, and cumulative latency. | TransportError and typed subclasses | Stable connection, timeout, protocol, remote, and payload-limit failures with request metadata. | Transport authors can additionally import Transport , TransportCapabilities , and TransportRequestContext from protolink.transport . The shared API reference https://nmaroulis.github.io/protolink/docs/transport/ shared-transport-api-reference lists every field, default, base-class hook, health payload, and protocol mapping. Protolink separates LLMs into three types: api , local , and server . The following are the Protolink wrappers for each type. If you want to use another model, you can use it directly without going through Protolink’s LLM class. API Server Local API , calls the API, requires an API key: OpenAILLM https://github.com/nMaroulis/protolink/blob/main/protolink/llms/api/openai client.py : Uses OpenAI API for sync & async requests. AnthropicLLM https://github.com/nMaroulis/protolink/blob/main/protolink/llms/api/anthropic client.py : Uses Anthropic API for sync & async requests. GeminiLLM https://github.com/nMaroulis/protolink/blob/main/protolink/llms/api/gemini client.py : Uses Gemini API for sync & async requests. GrokLLM https://github.com/nMaroulis/protolink/blob/main/protolink/llms/api/grok client.py : Uses Grok API for sync & async requests. DeepSeekLLM https://github.com/nMaroulis/protolink/blob/main/protolink/llms/api/deepseek client.py : Uses DeepSeek API for sync & async requests. Local , runs the model in runtime: LlamaCPPLocalLLM /nMaroulis/protolink/blob/main : Uses local runtime llama-cpp-python for sync & async requests. Server , connects to an LLM Server, deployed locally or remotely: OllamaLLM https://github.com/nMaroulis/protolink/blob/main/protolink/llms/server/ollama client.py : Uses Ollama for sync & async requests. LlamaCPPServerLLM /nMaroulis/protolink/blob/main : Connects to llama-server for sync & async requests. Native Tool https://github.com/nMaroulis/protolink/blob/main/protolink/tools/tool.py : Uses native tools. MCPToolAdapter https://github.com/nMaroulis/protolink/blob/main/protolink/tools/adapters/mcp adapter.py : Connects to MCP Server and registers MCP tools as native tools. The MCPToolAdapter connects to Model Context Protocol MCP https://modelcontextprotocol.io/ servers and exposes their tools as Protolink-native callables, wrapping remote MCP tools as native Tool instances. This enables seamless integration with the growing MCP ecosystem. Supported transports: stdio - Local subprocess communication for MCP servers as scripts/executables sse - Remote HTTP Server-Sent Events for MCP servers as web services python from protolink.tools.adapters import MCPToolAdapter Connect to a local MCP server adapter = MCPToolAdapter transport="stdio", command="python", args= "my mcp server.py" Or connect to a remote MCP server adapter = MCPToolAdapter transport="sse", url="https://api.example.com/mcp/sse" Get tools and add them to your agent for tool in adapter.get tools : agent.add tool tool Or call tools directly add tool = adapter.get callable "add" result = add tool a=5, b=7 Protolink treats agentic systems as distributed programs , not probabilistic workflows. Every interaction between models, tools, and agents becomes an explicit action that the runtime can validate, execute, observe, and replay. Runs can also carry a typed RunContext and emit versioned RunEvent s, giving applications stable session, trace, budget, permission, and progress metadata without custom glue. RunAction , capability policy, and typed approval checkpoints gate side effects while keeping approval previews and user experience application-owned. At the heart of Protolink is the infer loop : - The LLM proposes exactly one next action. - Protolink validates that action. - The runtime executes the tool call, agent delegation, or final response. - The result is fed back into the conversation. - The loop repeats until the agent returns a final answer. This is where Protolink removes the boilerplate most agent frameworks push onto you: provider-specific tool prompts, JSON parsing, schema validation, retries, routing, delegation, and error recovery. When a provider supports native tool calling, Protolink uses it. When a local or smaller model works better with simple JSON instructions, Protolink uses that instead. The user-facing contract stays the same. Protolink removes that complexity by standardizing all interactions through a small set of explicit primitives: Task - a shared unit of work Message - communication within a task Part - an atomic, machine-interpretable action or result Agents never infer behavior implicitly. Instead, they declare intent explicitly using structured Parts such as: tool call - execute a local tool agent call - delegate work to another agent infer - invoke LLM reasoning text - return user-facing output and more... From there, the runtime handles everything deterministically. You do not need to: - Write provider-specific tool-calling prompts - Parse raw LLM text or JSON - Convert native tool calls into runtime actions - Write routing or delegation logic - Rebuild retry, validation, and self-correction loops The runtime automatically: - Selects the right prompt/tool-calling mode for the model - Injects tool schemas and discovered agent capabilities - Validates every LLM action before execution - Executes tools and agent calls deterministically - Emits structured events for tracing and debugging Tool calls, agent calls, and LLM invocations only happen when explicitly declared. All results are returned as structured Parts - no hidden side effects, no magic. From the user’s perspective: task = Task.create Message.user "What's the weather in Geneva?" That’s it. This is not a black-box agent framework. - No hidden reasoning - No implicit planning - No speculative execution Every action is explicit, inspectable, and replayable . If a tool runs, you see a tool call . If an agent is contacted, you see an agent call . If an LLM is invoked, you see an infer . This makes the system predictable, debuggable, composable, and production-ready. The runtime is fully LLM-agnostic . Any model, API-based, self-hosted, or local , can be swapped in without changing behavior or results. OpenAI, Anthropic, local servers, or custom backends all operate through the same unified execution model. The orchestration stays the same. The contracts stay the same. Only the model changes. This lets you evolve providers, costs, latency, or deployment strategy without rewriting your agents. This project uses a structured, Agent-to-Agent A2A style communication model. Understanding how Tasks , Messages , Artifacts , and Parts interact is key to using the agent effectively. A Task represents a unit of work or a conversation thread between agents. - It contains Messages and Artifacts . - Tracks lifecycle through Task.state submitted , working , completed , failed , etc. . - Records successful state transitions in metadata "state history" . - Tasks are sent between agents; each agent executes what is explicitly defined in the task. A Message is a communicative unit in a task. - Can be sent by a user or an agent. - Contains Parts representing atomic content. - Example roles: "user" - input from a human or another agent "agent" - output from an agent An Artifact is a container for outputs generated by the agent. - Stores Parts that result from executing a tool tool call or an LLM inference infer . - Can include tool results, reasoning traces, or structured outputs. - Artifacts allow agents to append results without modifying the original message. A Part is the atomic content of a Message or Artifact. - Defines what to do or what was produced . - Example Part types PartType : "text" : plain text content "json" : structured data "tool call" : request to execute a registered tool "tool output" : result outputfrom executing a tool "infer" : input to invoke the agent's LLM "status" , "error" , "image" , "audio" , etc. - Task Creation A user or agent creates a Task with a Message containing one or more Parts . - Task Execution - The receiving agent inspects the last message or artifact in the task. - Executes each Part sequentially: tool call → executes a registered tool → produces tool output Part in an Artifact. infer → invokes the agent's LLM → produces infer output Part in an Artifact. - The receiving agent inspects the - Appending Outputs - Results are appended to the Task as new Artifacts . - Lifecycle state transitions are applied to Task.state and recorded in metadata "state history" . - Results are appended to the Task as new - Sequential Processing - Tasks are processed sequentially at the message/artifact level. - Parallel execution is possible within the parts of a single message/artifact , but not across multiple messages/artifacts in the same task. python from protolink.models import Message, Part, Task 1️⃣ User creates a Task with a message containing a Part task = Task.create Message.user "What's the weather in Athens?" 2️⃣ Add a tool call Part tool part = Part.tool call tool name="weather api", args={"city": "Athens"} task.add message Message.agent parts= tool part 3️⃣ Agent executes the task result task = await agent.execute task task 4️⃣ Outputs are appended as artifacts for artifact in result task.artifacts: for part in artifact.parts: if part.type == "tool output": print "Tool Output:", part.content 5️⃣ If needed, a infer Part can trigger the agent's LLM infer part = Part.infer prompt="Summarize today's weather in Athens" task.add message Message.agent parts= infer part result task = await agent.execute task task Key Notes: - Each Task maintains the full history of messages and artifacts. - Agents execute the last message or artifact to determine the next action. - Parts inside a message or artifact can be executed in parallel if needed. - Agents do not guess intent ; they execute exactly what is defined in the Parts. This structured approach ensures predictable, deterministic agent behavior while still supporting multi-step interactions and LLM/tool execution. While frameworks like LangChain or LangGraph rely on complex implicit state machines or LLM-driven routing, Protolink's Structured Flows provide explicit, deterministic execution paths Pipeline , Parallel , Router , Graph without the heavy overhead. Flows allow you to define highly composable, rigid, and predictable multi-step workflows. Pipeline , Parallel , and Graph define deterministic topology, while Router branches on structured Part.route ... decisions that are serializable, trace-visible, and testable. Legacy ROUTE: key tags are still accepted for older prompts. Even though flows are programmatically structured, agents executing inside them must remain fully decoupled from the overall flow topology. Protolink achieves this through Dynamic Semantic Context Injection : Topological Analysis : Before dispatching a step, the Flow orchestrator looks at the subsequent step in the topology e.g., a single downstream agent, a parallel committee, or conditional routes . Context-Aware Prompt Generation : It automatically builds descriptive system instructions outlining the next target's capabilities, description, or routing choices retrieved dynamically via their AgentCard from the Registry . Flow State Injection : It populates the prompt into task.flow state "prompt" . Execution : The executing agent's LLM automatically merges these instructions into its system prompt during inference. The agent adapts its behavior at runtime to optimize its output format for the downstream receiver without any hardcoded integration In simple terms, the Flow tells the agents what to do and what format to use for their output based on the downstream agents' capabilities. In the Pipeline example below, the orchestrator tells the researcher that its output will be used by the summarizer, so it should format its output in a way that the summarizer can understand. This is done automatically and transparently to the developer You can define and run structured flows programmatically using both asynchronous and synchronous APIs. A Pipeline runs a predefined sequence of agents, passing the output of one agent as the input to the next: python from protolink.flows import Pipeline from protolink.models import Task 1. Define a deterministic sequence of steps can be Agent names, instances, or other sub-flows pipeline = Pipeline steps= "researcher", "summarizer" , registry=registry Optional: allows discovering string-based agents by name 2. Run the flow asynchronously task = Task.create infer prompt="Research the future of Agentic computing." result = await pipeline.execute task Pipelines support a fluid builder pattern for dynamic step configuration: pipeline = Pipeline registry=registry .add step "researcher" .add step "summarizer" result = await pipeline.execute task For simple scripts, Jupyter Notebooks, or background workers where async syntax is not preferred, use the .sync blocking wrapper: Executes the flow synchronously in a clean event loop result = pipeline.sync.execute task print result.get last part content Since every flow primitive inherits from the base Flow class, they are fully polymorphic. You can nest complex flow structures inside one another seamlessly: python from protolink.flows import Pipeline, Parallel A parallel execution block containing multiple concurrent agents review committee = Parallel branches= "editor", "fact checker" , registry=registry Embed the parallel block as a single step inside a pipeline orchestrated flow = Pipeline registry=registry \ .add step "researcher" \ .add step review committee \ .add step "summarizer" result = await orchestrated flow.execute task Protolink provides non-invasive, out-of-the-box observability for agent task execution, LLM inferences, and tool calls. It utilizes Python's contextvars to automatically track execution states asynchronously without cluttering core method signatures. Currently supported telemetry options: LocalTraceTelemetry - built-in local traces for replay/debugging, with optional JSONL output Langfuse https://langfuse.com/ LangSmith https://www.langchain.com/langsmith For local debugging, use LocalTraceTelemetry without any external service: python from protolink import Agent, AgentCard, LocalTraceTelemetry, Task, create llm telemetry = LocalTraceTelemetry path="traces.jsonl" agent = Agent card=AgentCard name="debug agent", description="Local debug agent", url="runtime://debug" , llm=create llm "mock", sequential responses= {"type": "final", "content": "done"} , , telemetry=telemetry, result = await agent.handle task Task.create infer prompt="Run a traced task" trace = telemetry.recorder.replay -1 print result.get last part content done print trace "trace id" print span "kind" for span in trace "spans" task, llm, ... Local traces capture task spans, LLM events, tool calls, retry counts, redacted payloads, and model metadata. This makes the infer loop easy to inspect before sending anything to Langfuse, LangSmith, or another observability backend. For hosted telemetry, install the optional dependency uv add "protolink telemetry " and inject the tracker to your agent. You can also broadcast events to multiple trackers using MultiTelemetry : python from protolink.telemetry import LangfuseTelemetry, LangSmithTelemetry, MultiTelemetry from protolink.agents import Agent Traces, spans and generations will be automatically recorded to both Langfuse and LangSmith agent = Agent card={"name": "ObserverAgent", "url": "http://127.0.0.1:8000"}, telemetry=MultiTelemetry LangfuseTelemetry , LangSmithTelemetry See the Telemetry Documentation https://nmaroulis.github.io/protolink/docs/telemetry/ for full setup instructions and custom integrations. Protolink provides a flexible logging system to track agent activity across different outputs. ConsoleLogger https://github.com/nMaroulis/protolink/blob/main/protolink/logging/console.py : Provides colored, human-readable logs in the terminal. FileLogger https://github.com/nMaroulis/protolink/blob/main/protolink/logging/file.py : Persists logs to disk, with optional JSON formatting for easy ingestion into log management tools. QuietLogger https://github.com/nMaroulis/protolink/blob/main/protolink/logging/quiet.py : Implements the logger interface while intentionally dropping every log message. BaseLogger https://github.com/nMaroulis/protolink/blob/main/protolink/logging/base.py : Abstract base class for creating custom logging implementations. MIT All contributions are more than welcome Please see CONTRIBUTING.md https://github.com/nMaroulis/protolink/blob/main/CONTRIBUTING.md for more information.