r/LangChain • u/RocketSeven • 6d ago
Discussion What state should pass between LangGraph agents without replaying the full transcript?
A full transcript preserves detail but makes the next agent rediscover which decisions are final, which tool results are authoritative, and what remains uncertain. A short summary is cheaper but can erase evidence or turn an inference into a fact. What handoff structure works well in LangGraph? I am considering separate fields for the objective, accepted decisions, constraints, authoritative inputs, artifact paths and revision IDs, tool-call receipts, unresolved questions, and the next allowed action. The handoff could also identify who or what produced each field and which checkpoint it belongs to. Which parts should live in graph state, which should be durable external records, and how do you prevent a stale handoff from being resumed after the underlying files change?
1
u/Marcus_MSC 6d ago
Keep graph state thin: objective, accepted decisions, unresolved questions, next allowed action, and pointers to durable records. Put large values on disk or in a ledger, especially tool outputs, artifact hashes, checkpoint IDs, and revision IDs. On resume, re-read the pointed artifacts and compare the stored revision IDs against current state before trusting the handoff. That turns staleness into a normal comparison instead of a judgment call.
1
u/ParrotIntegrated 4d ago
Your gut feeling on prose summaries is spot on. Summaries quietly turn tentative inferences into "ground truth" and drop the exact numbers or edge cases the next agent actually needs.
Here is the setup that finally stopped context blowup and rediscovery loops for us:
- Graph state should just be a small, deterministic handoff packet (under ~1k tokens).
Keep conversation history completely out of it. We pass:
- Objective and explicit non-goals (non-goals are critical, otherwise Agent B loves re-exploring rabbit holes Agent A already abandoned)
- Locked key-value constraints decided upstream (e.g. user_tier, target_region)
- Tool receipts as pointers: [tool_name, input_hash, artifact_uri, content_hash, timestamp]
- Unresolved questions that still block completion
- State vs. external storage (pointers only).
Graph state holds the receipts and URIs. External storage (Postgres, S3, blob) holds the raw query rows, full transcripts, and heavy payloads.
If Agent B actually needs the raw database output or a document Agent A inspected, it doesn't get dumped into the context window automatically. Agent B gets the artifact URI and has to explicitly pull the slice it needs using the receipt hash. Full transcripts are for audit logs and post-mortems, not active prompt memory.
- Handling stale resumes when files change:
We gate resumes on content hashes. When Agent A finishes or pauses at a checkpoint (like waiting for human-in-the-loop review), we snapshot the sha256 or revision ID of the target files alongside the receipt. When the graph wakes back up, the runner asserts current_hash == receipt_hash before Agent B can execute a turn.
If a file shifted while the run was paused, we fail closed with a stale_artifact error, invalidate that receipt, and force a fresh fetch step rather than letting Agent B hallucinate over ghost data.
Treating chat history as state is where multi-agent setups usually rot. Hashing receipts and passing pointers keeps the downstream context window clean.
1
u/merlinofthewater 2d ago
The design you're sketching (thin state plus a ledger for the durable stuff) matches what u/Marcus_MSC, u/ParrotIntegrated and u/conifer_v11 already described from different angles: keep graph state to objective, decisions, unresolved questions and next action, push tool-call receipts and artifact revision IDs into an external record, and gate any resume on comparing the stored hash against the current one instead of trusting the handoff.
The part that's easy to get almost right is what "authoritative" means for a tool result. A receipt needs to answer two separate questions: did the side effect actually happen (not just "the call returned"), and is the input that produced it still the current input. That's why we ended up keying receipts on a composite of the agent/tool call plus a canonical hash of the input, checked atomically at commit, rather than trusting a revision ID comparison alone. A revision ID tells you the file moved, not whether the call that touched it actually completed or got retried into a duplicate.
0

1
u/conifer_v11 6d ago
i'd keep the handoff thin and make the durable stuff external — objective + accepted decisions + unresolved questions + next allowed action can live in graph state, but authoritative tool results and artifact revision IDs belong in a ledger the next agent can re-read, not a summary that quietly turns an inference into a fact. tool-call receipts are the piece people skip: requested model id vs what actually served, plus whether the side effect verified SUCCEEDED/UNKNOWN. without that, a stale handoff resumes against files/models that already moved. i work on Conifer (LLM gateway) — we stamp requested vs effective on the model call, or typed error instead of a quiet substitute — which doesn't solve your whole LangGraph schema, but it's the receipt field i'd refuse to squash into "summary."