fix(langgraph): subagent cards rendered an empty transcript - #847
Merged
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
blove
force-pushed
the
blove/subagent-message-attribution-buffer
branch
from
August 29, 2026 19:38
0d3211c to
3ff15ba
Compare
Contributor
Every subagent card in cockpit/chat/subagents and examples/chat showed '0 message(s)' despite the child streaming a full response. Three defects stacked, each hiding the next. 1. Attribution never ran. matchSubgraphToSubagent was only called when a child's values.messages[0] was a HUMAN message. Wire capture shows this graph's child starts with the AI reply, so the namespace UUID was never mapped to its call_* id. Now any tools: child claims its tool call on first sight, falling through to the positional rung the ladder already relied on (the delegation tool here takes task_description, not description, so both text rungs were unreachable anyway). 2. Pre-attribution chunks were dropped. addMessageToSubagent silently returned when the namespace had no registered subagent. They are now buffered per namespace and replayed by establish(). 3. Delta chunks replaced instead of accumulating. Children stream AIMessageChunk deltas — ~14 chars each, ~500 per message — and the tracker merged by id with a plain overwrite, keeping only the last tiny delta. So even a correctly attributed card rendered an empty message. Now folds chunk content the way the parent transcript has since #751; snapshots still replace. Verified live against a real model: all three cards go 0 -> 1 message(s), each attributed to its own distinct call_* id, and expanding one renders the child's actual research text. 343/343 lib tests (3 new, each written from wire-captured shapes after an earlier hypothesis-shaped test passed while the UI stayed broken). e2e: chat-subagents, langgraph-subgraphs, deep-agents-subagents, examples/chat 54/54. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
blove
force-pushed
the
blove/subagent-message-attribution-buffer
branch
from
August 29, 2026 20:02
3ff15ba to
e8ec118
Compare
Contributor
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Every subagent card in
cockpit/chat/subagentsandexamples/chatshowed 0 message(s) while the child streamed a full response. I found this during #844 via a wire capture, confirmed it was pre-existing, and shipped that PR without it. This is the fix.Three defects were stacked — each one hid the next, so fixing only the first still left an empty card.
1. Attribution never ran
matchSubgraphToSubagentwas only invoked when a child'svalues.messages[0]was a human message:Wire capture of this graph shows the child's messages start with the AI reply — there is no human message anywhere in the child's state. So the
tools:<uuid>namespace was never mapped to itscall_*id, and nothing was ever claimed.The ladder's text rungs couldn't have helped either: the delegation tool takes
task_description, notdescription, so both the exact and substring rungs were structurally unreachable. Only the positional fallback could ever fire for this graph — and it was gated behind a call that never happened.Now any
tools:child claims its tool call on first sight. The precise description match still wins when the shape allows it.2. Pre-attribution chunks were dropped
addMessageToSubagentreturned silently when the namespace resolved to no registered subagent. Those chunks are now buffered per namespace and replayed byestablish()on attribution.3. Delta chunks replaced instead of accumulating
The one that made the other two invisible. Children stream
AIMessageChunkdeltas — measured on the wire at ~14 chars each, ~500 per message — but the tracker merged by id with a plain overwrite:So a correctly attributed card still rendered an empty message. The tracker now folds chunk content the way the parent transcript has since #751; snapshots still replace.
Verification
Live model, Chrome. All three cards go
0 message(s)→1 message(s), each attributed to its own distinctcall_*id (so the positional fallback isn't cross-wiring them), and expanding one renders the child's real output: "LAX (Los Angeles International — Los Angeles) Major terminals/airlines: Central Terminal Area with Terminals 1–8…"343/343 lib tests, 3 new. Worth noting how they were written: my first test encoded my hypothesis (messages arriving before attribution), passed after the buffer fix — and the live UI stayed broken. Every test here is now shaped from wire-captured payloads instead.
e2e: cockpit-chat-subagents 1/1, cockpit-langgraph-subgraphs 3/3, cockpit-deep-agents-subagents 1/1, examples/chat 54/54. Lint 0 errors.
Release
Ships behavior consumers can see, so it wants a patch release (0.0.60) after merge.
🤖 Generated with Claude Code