After examining state and continuity in the first part of this series, one of the most frequent causes of strange behavior deserves separate attention: identity. In a stateful system, a request continues the previous story only if the components involved can associate it with the same conversation, the same logical execution, and, when delegation occurs, the same remote work.
When this fails, the effect for the application's user looks very much like memory loss. The agent asks again about something already answered, ignores a previous approval, repeats an analysis, or begins a new reasoning sequence. Before increasing context, changing models, or adding a memory layer, it is worth checking whether the runtime received the correct identifiers.
Conversation, execution, and delegation have different lifetimes#
I distinguish three basic levels:
conversation_idrepresents the logical continuity of an interaction or process.execution_ididentifies a concrete attempt to process something within that conversation.delegation_ididentifies work handed to a specific capability, agent, or service.
Enlarge ↗This separation becomes especially useful when retries are involved. A transport timeout should not accidentally turn a repeat of the same delegation into new business work. At the same time, a new user request may belong to the same conversation and generate a different execution. Without this vocabulary, IDs end up being reused or recreated arbitrarily.
How this appears in LangGraph and Agno#
In LangGraph, thread_id is part of the persisted identity used by the checkpointer. If the next call arrives with a different value, the runtime starts consulting another thread. The application may show the same chat window to the user, but the graph is already reading a different checkpoint history.
A simplified usage would be:
Enlarge ↗In Agno, session_id and run_id represent different things. The session groups continuity, while the run identifies an individual component execution. In an integration between the two frameworks, it is easy to end up with something like this:
Enlarge ↗Each side remains internally consistent, but they are no longer reconstructing the same story. When the intention is to share conversation identity, I prefer to map it explicitly:
Enlarge ↗This mapping belongs to the application. It should not depend on an adapter figuring out on its own which ID from another framework to reuse.
Automatic IDs are great at the start and dangerous when they become an implicit contract#
During a prototype, letting each library create identifiers reduces code and speeds up the first version. The problem appears when that implementation starts receiving retries, different workers, queues, or remote calls. One service creates the session, another creates the run, the gateway adds a request ID, and telemetry generates a trace. After a few months, nobody can say confidently which identifiers represent the same unit of work.
I therefore suggest creating canonical IDs in the application layer and propagating them across boundaries:
Enlarge ↗Runtimes can continue generating internal identifiers for their own structures. What matters is having a correlation key that survives framework replacement, resumption, and troubleshooting.
Retry requires an explicit decision about idempotency#
Consider a delegation that reached the specialist, completed, and had its response lost to a timeout. The orchestrator calls again. If that retry receives a new delegation_id, the specialist has no way to know it is seeing a repeat of earlier work.
A stable key can be derived from business context:
Enlarge ↗Before executing again, the service consults its ledger or durable storage. If a complete result already exists for that key, it returns the persisted result. This does not replace LangGraph's checkpointer; these are guarantees at different levels. The checkpointer helps reconstruct graph state, while idempotency helps prevent a transport repeat from becoming a new business effect.
Long-running processes make the importance of these IDs much more visible#
In a short request, execution seems simple: an input arrives, the agent responds, and it ends. In longer processes, the same story may span deployments, failover, human approval, worker changes, and manual resumption:
Enlarge ↗During that interval, several traces may start and end. What needs to remain stable is business identity. One option is keeping trace_id as observability information and conversation_id, execution_id and delegation_id as process information. The two sets meet in span and log attributes.
The same principle applies to A2A. The message transports delegation; it does not replace the application's identity model. A payload can carry or allow reconstruction of something like:
Enlarge ↗The receiver may maintain local state and internal IDs, provided it does not silently redefine which conversation or execution it is processing.
A recovery test worth more than many happy-path tests#
One simple way to validate this architecture is to trigger failure at the most uncomfortable point: after the specialist completes the task and before the orchestrator advances the next checkpoint.
Enlarge ↗If the implementation handles that delegation idempotently, the specialist call count remains one. When it rises to two, the test has revealed that some boundary lost the identity or result of the previous operation.
This is one reason why, on hearing that an agent “forgot” a conversation, I now start with the request path: which conversation_id arrived, which thread_id was used, which session_id was reopened, which execution was underway, and which delegation was resumed. In many cases, the model never lost the context; the application simply stopped pointing to the same persisted state.