This series grew out of two very concrete situations. In one professional experience, I inherited a POC for a multi-agent architecture in which a supervisor built with LangGraph delegated work to agents implemented with Agno. The separation looked reasonable in the diagram: one component handled orchestration and the others executed specialized capabilities. The problem emerged when we needed to understand continuity, recovery, and state. Each runtime had its own lifecycle, its own execution identifiers, and its own way of persisting context. When something went off the happy path, reconstructing what had happened required crossing two black boxes that did not share a single view of execution.
Almost a year later, in a conversation with another professional, a very similar account came up. It was another company, another implementation, and another team, but the symptoms were familiar: the supervisor on one side, agents on the other, and difficulty preserving continuity predictably. Those two experiences led me to organize a question that arises when we combine agentic frameworks: the problem is rarely only the model or the prompt. Often, it lies in how state, identity, and execution responsibility have been distributed.
This type of architecture is easy to justify: LangGraph offers a good foundation for stateful workflows, Agno accelerates the creation of specialized agents and teams, CrewAI models role- and task-oriented collaboration well, and other components can handle serving, memory, tools, and observability. The local benefit of each choice is real, but there is that damned trade-off: the cost appears when more than one layer simultaneously starts deciding what it means to continue a conversation, resume a run, repeat a task, or consider an effect complete.
Before discussing memory, distinguish the types of state#
In LLM applications, the word memory is often used for very different situations. In production, this simplification gets in the way because a conversation can have a textual history, a workflow can be paused awaiting approval, an agent can maintain long-term facts, and an external integration may already have executed even though the local checkpoint does not yet record it.
I have learned to distinguish at least five categories:
conversational history: what the user and system have already exchanged;
workflow state: the process stage the execution has reached;
long-term memory: facts that need to survive an execution or conversation;
operational state: which run, attempt, delegation, or agent is active;
external effects: what has already happened outside the runtime, such as a payment, ticket, or email delivery.
These categories have different lifecycles. A message history can be reconstructed from a session, while a payment requires much stronger evidence of whether it was sent. Storing everything under a generic structure called memory can work during prototyping, but becomes fragile when retries, crashes, concurrency, and long-running workflows enter the picture.
Two persistence mechanisms can produce two valid versions of the same story#
Consider an integration in which LangGraph controls the overall flow and a second framework persists its own session. A simplified path might be:
Enlarge ↗If the process crashes between steps 3 and 5, the specialist may have evidence of completion while the workflow still records that delegation as pending. Neither persistence mechanism is necessarily corrupted; they simply recorded different moments of the same operation without a shared transaction between runtimes.
This kind of failure is troublesome precisely because it does not need to generate an exception. After a restart, the application may resume and call the specialist again. The user sees a repeated answer, a duplicated analysis, or some context inconsistency, while the logs show apparently healthy components.
The same thing happens with conversational history. If LangGraph sends all accumulated messages to the specialist and, on receiving the same session, the specialist injects its locally maintained history again, the prompt may contain duplicate messages:
Enlarge ↗When output becomes repetitive or contradictory, it is tempting to attribute the behavior to the LLM. In this case, however, the context assembly had already gone wrong before the model received the request.
Workflow checkpoints and external effects need different evidence#
Durable execution helps a lot with resuming a state machine, but does not resolve on its own what happened in external systems. Imagine a node sends a payment request and the process crashes immediately after the provider's response, before the next checkpoint. On resumption, running the node again may generate a second payment attempt.
That is why I prefer a clear separation between the workflow checkpoint and a record of external effects. The checkpoint answers where execution was. The effects ledger answers what has already been done outside it. A minimal structure can store:
Enlarge ↗Before repeating a critical operation, the runtime checks this record. For some integrations, the external API itself also offers idempotency keys and can provide part of that guarantee. In financial processes, customer service, or any flow with relevant side effects, this distinction prevents workflow recovery from being confused with safe operation repetition.
Identity needs to exist above the framework#
Another problem I have seen in these designs is letting each layer create its own identifiers without an explicit relationship. LangGraph works with thread_id; Agno organizes sessions and runs; CrewAI, A2A services, queues, traces, and tools may add new IDs. If the application has no canonical identifiers, correlating everything later becomes investigative work.
A model I find useful is to maintain the following at the application layer:
Enlarge ↗From there, each runtime receives a mapping. The same conversation_id, for example, can be used as thread_id in LangGraph and session_id in an Agno specialist when that shared continuity makes sense. delegation_id can identify the remote call and contribute to the idempotency key. Frameworks remain free to create internal IDs, but there is a business identity capable of crossing all of them.
I also keep this identity separate from OpenTelemetry. trace_id and span_id are excellent for distributed troubleshooting, but a conversation can generate several traces, and an execution may resume hours later in a different trace. The best result comes when telemetry carries canonical identifiers as attributes, allowing navigation through both the operational view and the process history.
The purpose of this series#
The next articles build on this experience to explore some points that have become architectural criteria for me. The second addresses identity and continuity between runtimes. The third shows a way to combine LangGraph and Agno while maintaining a single owner of the overall workflow, with explicit contracts, idempotency, and an effects ledger. The fourth expands the comparison to CrewAI and discusses when each abstraction tends to fit the problem better.
The goal is not to turn a framework choice into a universal rule. LangGraph, Agno, and CrewAI solve real problems and can coexist in the same solution. The care lies in defining which responsibilities cross the boundary between them, which remain local, and which component has the authority to assert global execution state. It was precisely the absence of those answers in the situations that motivated this series that made diagnosis so difficult.