Skip to content

feat(runtimes): strands subagent delegation demo with standard SUBAGENT_* emission - #956

Merged
blove merged 10 commits into
mainfrom
blove/strands-subagent-demo
Sep 2, 2026
Merged

feat(runtimes): strands subagent delegation demo with standard SUBAGENT_* emission#956
blove merged 10 commits into
mainfrom
blove/strands-subagent-demo

Conversation

@blove

@blove blove commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Summary

Second PR of the runtime-subagents arc (adapter landed in #955).

  • The Strands meeting-scheduler delegates availability research to an availability_researcher specialist via an async-generator agents-as-tools @tool that re-yields the specialist's stream.
  • src/subagent_emitter.py rides the bridge's ToolBehavior.tool_stream_event_handler seam: SUBAGENT_STARTED (parentToolCallId = the delegation tool-use id) → real streamed child deltas as subagentRunId-attributed TEXT_MESSAGE_*SUBAGENT_FINISHED; SUBAGENT_ERROR on failure/force-stop. Pydantic ag_ui.core event classes throughout (the Python SDK already ships them). Hardened for provider tool-use-id reuse (reset-on-init_event_loop with generation-suffixed message ids) and capped session bookkeeping.
  • Evidence in cockpit/runtimes/aws-strands/python/docs/wire-capture-subagents.md: pre-emitter capture (bridge drops child text today), post-emitter capture (89 streamed child deltas across two delegations), and a live browser verification — the card streams mid-run (innerText 144→643 chars while running) with a committed screenshot.
  • Cross-lib fix caught by the new e2e: chat-tool-calls anchored subagent cards by the subagents() map key; the neutral contract carries the spawning id in the Subagent.toolCallId field (AG-UI keys the map by subagentRunId). Fixed with an O(n) re-index + a mutation-verified regression test in libs/chat; LangGraph behavior byte-equivalent (field == key there).
  • deployments/ag-ui-dev regenerated via the generator.

Testing

  • Python: 14/14 (delegation registry + emitter sequence field-for-field incl. error, reuse-reset, str-terminal, cap eviction).
  • e2e replay: 4/4 (3 existing + the new subagent-card spec, hand-written fixtures with the hasToolResult-first ordering).
  • libs/chat 1155/1155 (incl. the new anchoring pin, red without the fix); libs/ag-ui 241/241.
  • Live: full wire capture + streaming browser verification, servers torn down after.

🤖 Generated with Claude Code

blove and others added 10 commits September 2, 2026 09:14
…sion

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…arch to a specialist

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… at the bridge boundary

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…l fallback

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ter map key

chat-tool-calls looked up subagents via subs.has(tc.id) — the adapter's
map KEY. Adapters key subagents() differently (LangGraph by toolCallId,
AG-UI activities by messageId, AG-UI native SUBAGENT_* by subagentRunId,
e.g. `<toolCallId>-sub`), so a Strands delegation streamed correct
SUBAGENT_* wire events yet rendered as a plain tool-call chip. Re-index
by the contract field Subagent.toolCallId ("Tool call ID that spawned
this subagent"), which is the only guaranteed anchor.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…play

Three hand-written aimock fixtures cover the delegation turn's LLM calls:
orchestrator tool-call (research_availability), the specialist (matched on
its distinctive "availability researcher" system prompt), and the
orchestrator continuation (hasToolResult discriminator ordered before the
looser same-message entry). The spec asserts the inline
<chat-subagent-card> renders with the specialist's name and the final
bubble proposes a slot.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…t card

Live-model run (real key) driven headlessly: the delegation prompt renders
an inline chat-subagent-card (availability_researcher, wire toolCallId,
status badge) whose text streams mid-run — innerText polled at 150ms grew
144→643 chars while status stayed `running`, then settled to `complete` —
before the orchestrator's own summary bubble. Screenshot committed under
e2e/manual/. Matrix cell evidence: subagent streaming = yes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The two existing anchoring tests key the subagents map identically to the
toolCallId, so they pass with or without the field-based lookup. This one
keys the map by an AG-UI-style subagentRunId ('call_t-sub') while the
wrapper's toolCallId is 'call_t', and asserts the card still anchors to
the spawning call (mutation-checked: red on reverting the component fix).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…demo

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@blove
blove enabled auto-merge (squash) September 2, 2026 16:30

@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.

@vercel

vercel Bot commented Sep 2, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated
threadplane Ready Ready Preview Sep 2, 2026 4:32pm UTC

Request Review

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

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


I'll analyze this and get back to you.

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