← Back to articlesTRACK 02 · State and identity in multi-agent systems · PART 3 OF 4
Architecture & agents

LangGraph + Agno in the same application

After dealing with an architecture where LangGraph and Agno shared responsibilities without a sufficiently clear boundary, the decision in my case was ultimately to migrate the entire solution to LangGraph.

The choice made sense for that case because the workflow already depended heavily on state, branching, checkpoints, resumption, and explicit execution control, so maintaining two runtimes added complexity without proportional benefit. Consolidating orchestration and agents in LangGraph simplified continuity, observability, and troubleshooting.

That experience, however, did not lead me to conclude that LangGraph and Agno should not be used together. What became clearer was under which conditions that combination begins to make sense and, above all, which responsibilities need to be defined before integration.

If I had a concrete reason today to keep both frameworks in the same application, I would use one as the authority over the global process and treat the other as a bounded capability accessed through a contract. That is the design I present here.

There are scenarios where the combination is justified: while LangGraph provides a very convenient model for state machines, checkpoints, branching, interrupts, and resumption, Agno facilitates building specialized agents with tools, knowledge, sessions, and Teams, alongside the components available around AgentOS. A product may have a strongly stateful business workflow while already possessing an established Agno specialist or a capability worth keeping in that ecosystem.

In that situation, my first decision would be to define who is responsible for global execution state.


A design I would use#

For a workflow involving data collection, analysis, compliance, human approval, and execution, I would keep LangGraph as the authoritative runtime while an Agno specialist receives a bounded task and returns a structured result. Its internal state would serve that capability without simultaneously representing the global status of the process.

Enlarge ↗

In this design, LangGraph maintains workflow phase, branching, interrupts, human approval, and checkpoint progression, while Agno maintains only the state required for specialist execution, such as local context, tools, knowledge, and memory scoped to that capability.

The application layer creates identifiers crossing runtimes, such as 𝐜𝐨𝐧𝐯𝐞𝐫𝐬𝐚𝐭𝐢𝐨𝐧_𝐢𝐝, 𝐞𝐱𝐞𝐜𝐮𝐭𝐢𝐨𝐧_𝐢𝐝 and 𝐝𝐞𝐥𝐞𝐠𝐚𝐭𝐢𝐨𝐧_𝐢𝐝, as well as the idempotency key. An independent ledger records already-completed remote results or effects. The model gateway is responsible for credentials, provider and model policy, fallback, and provenance.

One difficulty I encountered in the architecture that motivated this series was precisely the amount of implicit knowledge between runtimes. When one framework's internal state starts becoming necessary for the other to continue execution, troubleshooting and recovery become harder, which is why I now prefer placing an application contract at that boundary.


The contract must survive replacing the specialist#

A request to a specialist can be relatively simple:

Enlarge ↗

And the response:

Enlarge ↗

The graph knows these structures and an interface such as SpecialistPort, while the adapter knows Agno. This separation has an important practical consequence: if the specialist is later reimplemented in CrewAI, a custom service, or another runtime, the global state model does not need to be redesigned along with it.

The same applies to identity: an explicit mapping can use conversation_id as LangGraph's thread_id and Agno's session_id while delegation_id identifies that specialist's remote execution. The exact convention depends on the application; what matters is that the application creates it and it remains stable across retries, restarts, and failovers.


A2A as the boundary between runtimes#

When the specialist runs as an independent service, A2A is an interesting option for transporting delegation and necessary context between runtimes.

Enlarge ↗

I would keep the protocol as a communication boundary. Business state continues to be maintained by the runtime controlling the workflow. This separation also helps distinguish A2A from MCP: an agent may receive a task from another runtime through A2A and, during execution, consume tools or resources exposed through MCP. These are different responsibilities and can coexist without the communication protocol becoming the workflow's persistence mechanism.


Recovery after the remote call#

One of the most delicate windows occurs when a specialist completes its work and the main process fails before recording the next checkpoint.

Imagine the following sequence:

Enlarge ↗

Without idempotency, resumption may simply execute the specialist again.

I would handle the remote call as follows:

Enlarge ↗

If failure occurs after step 6, the node may be rerun during resumption. The application consults the ledger, finds the previous result, and continues without repeating remote processing. This test is particularly useful because it reproduces a class of problems that often appears only after retry, persistence, and remote calls start operating together.

For critical effects, such as financial transactions or changes in external systems, I would add a transactional outbox or equivalent strategy. Workflow checkpoints and confirmation of an external effect are related problems, but they are not the same thing.


Persistence and observability#

For canonical workflow state, Postgres fits this design well. LangGraph has a Postgres checkpointer, and the combination works well for processes requiring durability, resumption, and auditability. Redis may remain useful for caching, rate limiting, or ephemeral coordination. I would be more cautious about using it as the sole source of truth for a workflow whose execution history needs to be reconstructed after a failure. Event sourcing may make sense where detailed replay and causal trails are important requirements, but it adds another set of architectural and operational decisions. I would adopt it when that need already exists in the domain.

With more than one runtime, distributed tracing also takes on a larger role. I would propagate W3C Trace Context across the entire chain and add identifiers to spans such as conversation_id, execution_id, delegation_id, agent_id and tool_call_id.

Remote boundaries also deserve validations similar to any internal API: schema version, allowed specialist, IDs, payload size, idempotency key, and response types. The specialist receives only the authority necessary to perform its function.


The architecture can also be reversed#

LangGraph does not necessarily have to be the main runtime. If an application already uses AgentOS as its central runtime and a specific capability is implemented with LangGraph, the relationship can be reversed. In that case, AgentOS retains global authority and the remote graph performs a bounded function. The precaution remains the same: avoid having both components maintain independent, authoritative versions of the same process.

Before adding a second framework, I now try to answer a very concrete question: which additional capability justifies introducing another lifecycle, persistence model, retry semantics, and operational boundary? In the project that originated this series, the answer ultimately favored simplification and migration to LangGraph. In another system, an existing Agno specialist, an established Team, or specific AgentOS requirements could justify keeping both.

It was precisely to explore this second situation that I built the example repository accompanying the series. In it, LangGraph controls the global workflow and Agno appears as a remote specialist accessed through a contract. I later added CrewAI as an alternative implementation of the same specialist.

The experiment's objective is to verify whether the rest of the application continues working when the specialist's framework changes. If that replacement requires changing the global state machine, business IDs, or recovery strategy, the boundary probably still carries too many responsibilities.

Repository: https://github.com/brunovicco/agent-runtime-boundaries-lab

Originally published on LinkedIn. View publication ↗