You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
docs(langgraph): how child streams get matched to tool calls (#879)
The subagent-binding mechanism shipped across three packages (#869,
middleware 0.0.2, @threadplane/langgraph 0.0.62) and two demos, but the
subgraphs guide still described only the heuristic story. New section
documents the three attribution tiers in preference order — server-announced
binding, description ladder, single-candidate positional fallback — with the
three-line `announce_subagent` tool change and its no-guarding-needed
contract.
Placed between "Subagent stream details" and "Orchestrator pattern", where
the reader has just met SubagentStreamRef and is about to wire dispatch.
One renderer correction along the way: `<Tip>` is a cockpit-guide component,
not a website-docs one — prerender failed until it became `<Callout>`.
Verified: website build green, suite 347/347.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
A child graph invoked inside a `@tool` body streams under a `tools:<uuid>` namespace — and that uuid is a **checkpoint id**, assigned independently of the tool-call id. Nothing on the wire links the two. `injectAgent()` bridges the gap in three tiers, in order of preference:
232
+
233
+
1.**A server-announced binding** (exact, works under any concurrency — see below)
234
+
2.**The description ladder**: the child's first human message is compared against each pending tool call's `description` argument — exact match, then substring in either direction
235
+
3.**A positional fallback** that fires only when *exactly one* tool child is outstanding — with several in flight, arrival order is not dispatch order, and guessing would cross-wire the cards, so ambiguous streams stay buffered instead
236
+
237
+
Tier 3 covers the common sequential shape (one dispatch per assistant turn). For parallel fan-out, or a delegation tool whose argument isn't named `description`, announce the binding from the server — the tool body is the one place both halves are known:
238
+
239
+
```python
240
+
from typing import Annotated
241
+
242
+
from langchain_core.runnables import RunnableConfig
243
+
from langchain_core.tools import InjectedToolCallId, tool
244
+
from threadplane.middleware.langgraph import announce_subagent
announce_subagent(config, tool_call_id) # one line — before invoking the child
253
+
result =await child_graph.ainvoke({...})
254
+
...
255
+
```
256
+
257
+
`announce_subagent` (threadplane-middleware ≥ 0.0.2) emits one custom event pairing the config's `checkpoint_ns` with the injected tool-call id. `injectAgent()` consumes it, attributes the stream exactly — replaying any chunks that arrived before the announcement — and never lets it override an established mapping. It returns `False` instead of raising when anything it needs is unavailable (outside a run, no namespace, no id), so it needs no guarding.
258
+
259
+
<Callouttitle="Announce even in sequential graphs">
260
+
It costs one line, upgrades attribution from heuristic to exact, and your graph keeps working unchanged if you later parallelize dispatch.
261
+
</Callout>
262
+
229
263
## Orchestrator pattern
230
264
231
265
The orchestrator pattern delegates specialised work to subagents and merges their results. Each subagent runs its own graph independently while the parent coordinates the whole.
0 commit comments