Skip to content

feat(langgraph): server-announced subagent identity - #869

Merged
blove merged 1 commit into
mainfrom
blove/subagent-binding-events
Aug 30, 2026
Merged

feat(langgraph): server-announced subagent identity#869
blove merged 1 commit into
mainfrom
blove/subagent-binding-events

Conversation

@blove

@blove blove commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

Closes the gap #864 deliberately left open. That PR established there's no wire-level link between a child's tools:<uuid> namespace and its call_* tool-call id, and chose to refuse guessing under ambiguity — correct, but it means parallel fan-out shows empty cards forever.

The server knows both halves

Probed live before designing anything: inside the @tool body, config.metadata.checkpoint_ns is the exact namespace the child will stream under (verified matching on the wire, three for three), and InjectedToolCallId provides the call_* id. The client can't derive the link; the server can announce it.

The mechanism

threadplane-middleware 0.0.2 adds announce_subagent(config, tool_call_id) — emits one custom event:

{"type": "threadplane.subagent_binding", "namespace": "tools:<uuid>", "tool_call_id": "call_…"}

Safely no-ops outside a run (no writer, no namespace, no id) so callers never guard it.

@threadplane/langgraph recognizes the event: bindChildStream() maps the namespace authoritatively, replays any chunks buffered before the binding arrived (the #847 buffer doing double duty), and never overrides an established mapping. The event is consumed as protocol chatter — it doesn't leak into customEvents(). The description ladder and single-candidate fallback are untouched, so graphs that don't announce keep exactly today's behavior.

cockpit/chat/subagents adopts it via a ~20-line inline emitter per the cockpit standalone rule, with a comment pointing at the canonical middleware helper.

Verification

  • Wire: 3 binding events per run; every stream namespace bound; every bound id is a real tool call from the parent's messages.
  • Live model, Chrome: all three cards populate, each on its own call_* id, run settled.
  • Lib 346/346, including the decisive test: two children outstanding, streams arriving in reverse dispatch order — the exact case fix(langgraph): don't guess subagent attribution when it's a coin flip #864's guard refuses — now attributing both exactly via bindings, including a chunk that arrived before its binding (buffered then replayed). With two candidates outstanding the fallback can't fire, so only the binding path explains that result.
  • Middleware 42/42 under the CI-exact uv flow (venv → pip install -e '.[test]' → pytest). One false start: I added langgraph to the test extra before noticing it's already a runtime dep — reverted, so the uv.lock diff is exactly the version bump.
  • e2e cockpit-chat-subagents 1/1; lint 0 errors.

After merge

  1. Publish threadplane-middleware 0.0.2 — the publish workflow is deliberately manual (workflow_dispatch, dry-run default). Until then the demo's inline emitter keeps everything working; nothing depends on the unpublished version.
  2. @threadplane/langgraph patch release (0.0.62) to ship the client half.
  3. Adopt announce_subagent in examples/chat once 0.0.2 is on PyPI.

🤖 Generated with Claude Code

@vercel

vercel Bot commented Aug 30, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
threadplane Ready Ready Preview Aug 30, 2026 4:23pm

Request Review

@github-actions

github-actions Bot commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

Claude finished @blove's task in 0s —— View job


I'll analyze this and get back to you.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated approval: this PR received an intelligent (AI) code review. See the review comments on this PR.

The tools:<uuid> namespace a child graph streams under is a checkpoint id
assigned independently of the parent's call_* tool-call id — nothing on the
wire links them (#864 established this and chose to refuse guessing under
ambiguity, at the cost of empty cards for parallel fan-out).

But the server KNOWS both halves. Probed live: inside the @tool body,
config metadata carries checkpoint_ns (= the exact stream namespace,
verified matching on the wire) and InjectedToolCallId provides the call id.

threadplane-middleware 0.0.2 adds announce_subagent(config, tool_call_id):
one custom event {type: 'threadplane.subagent_binding', namespace,
tool_call_id}, emitted via get_stream_writer, safely no-op outside a run.

@threadplane/langgraph recognizes it: bindChildStream() maps the namespace
authoritatively, replays any chunks buffered before the binding arrived,
and never overrides an established mapping. The event is consumed as
protocol chatter, not forwarded to customEvents(). The ladder and the
single-candidate fallback remain for graphs that don't announce.

cockpit/chat/subagents adopts it with a ~20-line inline emitter (cockpit
standalone rule; comment points at the canonical middleware helper).

Verified:
- wire: 3 binding events, every stream namespace bound to a real call id
- live model in Chrome: all three cards populate on their own call ids
- lib 346/346 — incl. the case #864 left unattributed (two children,
  reverse arrival order) now resolving exactly via bindings, with the
  pre-binding chunk replayed from the buffer
- middleware 42/42 under the CI-exact uv flow; e2e 1/1; lint 0

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@blove
blove force-pushed the blove/subagent-binding-events branch from 6af8c1e to 6c583da Compare August 30, 2026 16:19
@github-actions

github-actions Bot commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

Claude finished @blove's task in 0s —— View job


I'll analyze this and get back to you.

@blove
blove merged commit 2414766 into main Aug 30, 2026
22 checks passed
blove added a commit that referenced this pull request Aug 30, 2026
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant