Skip to content

feat: subagent support (protocol + LangGraph + demo)#2137

Draft
mme wants to merge 56 commits into
mainfrom
mme/subagents
Draft

feat: subagent support (protocol + LangGraph + demo)#2137
mme wants to merge 56 commits into
mainfrom
mme/subagents

Conversation

@mme

@mme mme commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Draft. Adds AG-UI subagent support: subagentId on messages/creation events + SUBAGENT_STARTED/FINISHED/ERROR lifecycle (TS + Python SDKs), a backward-compat shim, and the LangGraph deepagents integration emitting attribution (incl. tool calls in MESSAGES_SNAPSHOT and name/description from the task tool). Includes an inline-attribution dojo demo.

🤖 Generated with Claude Code

mme and others added 30 commits July 7, 2026 12:43
Design for AG-UI subagent support: subagentId attribution on
creation/standalone events carried onto messages (Phase 1), plus
SUBAGENT_STARTED/FINISHED/ERROR lifecycle events (Phase 2). TS full +
Python schema mirror.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Carry subagentId from TEXT_MESSAGE_CHUNK/TOOL_CALL_CHUNK onto the
synthesized TEXT_MESSAGE_START/TOOL_CALL_START events so chunk-based
producers attribute messages identically to explicit-start producers.
Also passes SUBAGENT_STARTED/FINISHED/ERROR through the chunk
transform unchanged, fixing an exhaustiveness break introduced by
those new EventType members.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CR round 1 bucket-(a)/(b) fixes:
- subagent-verify.test.ts: move error-path assertions out of RxJS error
  callbacks (Vitest does not attribute throws there) and drop undefined
  fail(); assertions now genuinely enforce AGUIError + message regexes.
- subagent-apply.test.ts: add a test that exercises the positive
  wasCreated->subagentId-set branch and the non-overwrite-on-existing
  branch (the old test only proved wasCreated=false).
- subagent-lifecycle-hooks.test.ts: assert hook payloads, not just call counts.
- transform.ts: remove unused subagentId field from ReasoningMessageFields
  (ReasoningMessageChunk carries no subagentId by design).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Makes all *_CHUNK events symmetric: TEXT_MESSAGE_CHUNK and TOOL_CALL_CHUNK
already carried subagentId; add it to REASONING_MESSAGE_CHUNK (TS + Python)
and propagate it through transformChunks onto the synthesized
REASONING_MESSAGE_START. Also sets the required role: reasoning on that
synthesized event (previously omitted, making it schema-invalid).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The event/message base models are permissive (TS BaseEventSchema.passthrough(),
Python ConfiguredBaseModel extra='allow'), so read-back assertions passed whether
or not the field was declared. Add real guards: negative-type assertions in TS
(a declared z.string() rejects a number; a passthrough field would not) and
model_dump(by_alias=True) camelCase-alias checks in Python (an undeclared extra
dumps as snake_case, failing the lookup). These now guard against removal of the
subagentId/subagent_id declarations.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…as dump

StateDeltaEvent, TextMessageChunkEvent, and ToolCallChunkEvent assertions were
attribute-only, which pass under extra='allow' even if subagent_id were removed.
Add model_dump(by_alias=True)['subagentId'] guards so they genuinely detect
removal, matching the other assertions in the file.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
BackwardCompatibility_0_0_57 strips all subagent additions when talking to a
pre-subagent (<=0.0.57) remote agent: removes subagentId from input messages on
the way in, and drops SUBAGENT_STARTED/FINISHED/ERROR events + strips subagentId
from surviving events and MESSAGES_SNAPSHOT messages on the way out. Registered
behind compareVersions(maxVersion, '0.0.57') <= 0, and client bumped to 0.0.58 so
a default modern client keeps its own subagent data.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…eline

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The decode path validates every event against the closed EventSchemas
discriminated union and errors the stream on an unknown type, so new event
types are NOT silently ignored by older consumers (cross-version safety is a
version-negotiation concern). Document the new-client<->old-agent downgrade shim.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…0.57 shim

CR fixes for the subagent backward-compat shim:
- Also deep-strip subagentId from RUN_STARTED.input.messages on output (was only
  handled for MESSAGES_SNAPSHOT), so the output contract is fully met (confirmed by
  4 independent reviewers). Shared stripMessages helper.
- Emit a suppressible warning when dropping a SUBAGENT_* lifecycle event, mirroring
  BackwardCompatibility_0_0_45's convention (addresses silent SUBAGENT_ERROR loss).
- Correct the docstring to state actual scope (top-level + known nested message
  arrays; opaque RAW/CUSTOM payloads and subagent content-event flattening called out).
- Tests: RUN_STARTED.input echo stripping, drop-warning, broadened output-strip
  coverage (TOOL_CALL_START/REASONING_MESSAGE_START), positive e2e assertion.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CR confirmation-round fixes:
- Test: neutralize SUPPRESS_TRANSFORMATION_WARNINGS in beforeEach/afterEach so the
  warn-emitting test is deterministic regardless of ambient env (it previously
  failed under SUPPRESS_TRANSFORMATION_WARNINGS=true). Add a suppression test and an
  enum-constant-sync assertion (guards the string-literal filter against enum renames).
- Docs: correct the class docstring direction (the old party is the upstream agent;
  the output path is defensive normalization, not consumer-facing translation) and
  generalize the opaque-payload note beyond RAW/CUSTOM.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…s + dep-bump gate)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ersions

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Confirms subagents surface via nested langgraph_checkpoint_ns + lc_agent_name;
subagentId from the leading ns segment. Drives the emission tasks.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…point_ns

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ndaries

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…g_ui.core

SubagentStartedEvent/SubagentFinishedEvent/SubagentErrorEvent were defined
in ag_ui.core.events and included in the Event discriminated union, but
never re-exported from the ag_ui.core package __init__, so `from ag_ui.core
import SubagentStartedEvent` failed. The langgraph integration's new
subagent lifecycle reconciliation needs this top-level import.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
mme and others added 9 commits July 7, 2026 17:02
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…agent

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… concurrency

deepagents runs task subagents concurrently, so their events interleave
(A, B, A, B, ...). The old toggle-based reconcile_subagents treated every
id change as an exit+enter, thrashing STARTED/FINISHED pairs (151 pairs
for 3 subagents) and re-emitting STARTED for already-seen subagents.

reconcile_subagents now emits SUBAGENT_STARTED exactly once per distinct
subagent_id (dedup against active_subagents) and no longer emits FINISHED
on toggle; current_subagent_id is set per-event for _dispatch_event
stamping, decoupled from the active-set lifecycle. FINISHED is emitted
only by drain_subagents before RUN_FINISHED, closing every subagent that
was ever started.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds a minimal deepagents_subagents feature demo (LangGraph integration)
that renders the chat plus a compact panel reading agent.messages and
surfacing each message's subagentId with a small ⟐ marker, proving that
AG-UI subagent attribution reaches the frontend. Wires the id through
config.ts, agents.ts, menu.ts, and types/integration.ts alongside every
existing "subgraphs" entry.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rations

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…eaches the dojo

@copilotkit/react-core@1.61.2 pulls a published @ag-ui/client@0.0.57 that predates
subagentId; its useAgent/HttpAgent apply-loop would strip the field before the dojo
page sees it. Force the transitive @ag-ui/{client,core,encoder,proto} onto the
workspace versions via pnpm.overrides. Additive changes -> dojo typecheck stays green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…subagent for snapshot iteration

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ES_SNAPSHOT

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ne attribution demo

- Integration: include subagent tool calls and tool results (not just text) in
  MESSAGES_SNAPSHOT so a snapshot apply preserves full subagent attribution;
  source SUBAGENT_STARTED name/description from the deepagents task tool.
- Dojo demo: render subagentId inline via the CopilotKit slot system (tag on
  both text messages and tool calls) using CopilotKitProvider; center layout.
- Bump @ag-ui/core 0.0.57 -> 0.0.58 (aligns with @ag-ui/client) and link the
  workspace @Ag-ui packages so the demo surfaces subagentId through CopilotKit.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Python Preview Packages

Version 0.0.0.dev1783529130 published to TestPyPI.

Warning: These packages are built from contributor code that may not yet have been vetted for correctness or security. Install at your own risk and do not use in production.

Install with uv

Add the TestPyPI index to your pyproject.toml:

[[tool.uv.index]]
name = "testpypi"
url = "https://test.pypi.org/simple/"
explicit = true

Then install the packages you need:

# Core SDK
uv add 'ag-ui-protocol==0.0.0.dev1783529130' --index testpypi

# Integrations (each already depends on the matching ag-ui-protocol preview)
uv add 'ag-ui-langgraph==0.0.0.dev1783529130' --index testpypi
uv add 'ag-ui-crewai==0.0.0.dev1783529130' --index testpypi
# NOTE: ag-ui-agent-spec depends on pyagentspec (git-only, not on PyPI).
# You will need to install pyagentspec separately from its git repo.
uv add 'ag-ui-agent-spec==0.0.0.dev1783529130' --index testpypi
uv add 'ag_ui_adk==0.0.0.dev1783529130' --index testpypi
uv add 'ag_ui_strands==0.0.0.dev1783529130' --index testpypi

Install with pip

pip install \
  --index-url https://test.pypi.org/simple/ \
  --extra-index-url https://pypi.org/simple/ \
  ag-ui-protocol==0.0.0.dev1783529130

Use --extra-index-url https://pypi.org/simple/ so pip can resolve
transitive dependencies (pydantic, fastapi, etc.) from real PyPI.


Commit: 25c2a80

@pkg-pr-new

pkg-pr-new Bot commented Jul 8, 2026

Copy link
Copy Markdown

Open in StackBlitz

@ag-ui/a2a-middleware

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/a2a-middleware@2137

@ag-ui/a2ui-middleware

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/a2ui-middleware@2137

@ag-ui/event-throttle-middleware

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/event-throttle-middleware@2137

@ag-ui/mcp-apps-middleware

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/mcp-apps-middleware@2137

@ag-ui/mcp-middleware

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/mcp-middleware@2137

@ag-ui/a2a

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/a2a@2137

@ag-ui/adk

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/adk@2137

@ag-ui/ag2

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/ag2@2137

@ag-ui/agno

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/agno@2137

@ag-ui/aws-strands

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/aws-strands@2137

@ag-ui/claude-agent-sdk

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/claude-agent-sdk@2137

@ag-ui/crewai

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/crewai@2137

@ag-ui/langchain

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/langchain@2137

@ag-ui/langgraph

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/langgraph@2137

@ag-ui/llamaindex

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/llamaindex@2137

@ag-ui/mastra

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/mastra@2137

@ag-ui/pydantic-ai

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/pydantic-ai@2137

@ag-ui/vercel-ai-sdk

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/vercel-ai-sdk@2137

@ag-ui/watsonx

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/watsonx@2137

@ag-ui/a2ui-toolkit

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/a2ui-toolkit@2137

create-ag-ui-app

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/create-ag-ui-app@2137

@ag-ui/client

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/client@2137

@ag-ui/core

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/core@2137

@ag-ui/encoder

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/encoder@2137

@ag-ui/proto

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/proto@2137

commit: e503a7a

mme and others added 17 commits July 8, 2026 17:08
…ew preview

TEMPORARY (see BEFORE-MERGE.md): makes pkg.pr.new rewrite integration/middleware
@Ag-ui SDK peer/deps to preview URLs so the sibling CopilotKit PR can consume this
branch's preview while @Ag-ui 0.0.58 is unpublished. Revert before merge.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…solve workspace:* per-package)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…canary)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Revert root pnpm.overrides @ag-ui/* from link: paths back to workspace:*,
regenerate lockfile, and remove BEFORE-MERGE.md. The cross-repo pkg.pr.new
preview loop was abandoned (see CopilotKit PR); nothing temporary remains.
Subagent feature work is unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…l cards

Deepagents demo: show the declared subagent name (from useSubagent, backed by
CopilotKit core's subagent registry) next to the subagentId, and render subagent
tool calls via a wildcard tool-call renderer. Requires local CopilotKit
(useSubagent + registry) — see CopilotKit PR.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the per-message subagent tag with a single collapsible group per
distinct subagentId, styled with the exact reasoning chrome
(CopilotChatReasoningMessage.Header + .Toggle). The group shows the subagent
name (from useSubagent), auto-opens while running and collapses when finished.

- Hide the supervisor `task` delegation tool card (the group represents it).
- Derive group members from the LIVE agent.messages via useAgent, not the
  frozen slot props: MemoizedAssistantMessage only re-renders its anchor
  message when that message's own content changes, so deriving from
  props.messages stranded the group at first render (one tool call). The
  useAgent store subscription re-renders the group as each tool call streams
  in, so all of the subagent's tool calls now appear.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…sed group

Backend (langgraph): emit SUBAGENT_FINISHED when the deepagents `task`
delegation returns (its OnToolEnd), not at drain_subagents/RUN_FINISHED. The
task invocation's run_id is recorded at OnToolStart and matched at OnToolEnd so
only the delegation call — never the subagent's inner tools, which share its
checkpoint namespace — triggers the finish. Verified against a live run:
SUBAGENT_FINISHED fires ~5s before RUN_FINISHED and before the supervisor's
final text, so a subagent's reported status tracks its own lifecycle.

Frontend (dojo): make the subagent group behave like reasoning —
- collapsed by default (manual toggle wins),
- a subtle pulsing activity dot (the reasoning header's own indicator) shown
  while the subagent runs, now correctly stopping when the subagent finishes
  rather than when the parent run ends,
- header label is just the declared subagent name, falling back to the id.

Prune the now-dead tag/pill/meta CSS.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ti-subagent demo

Rebuild the subagent grouping on CopilotKit's purpose-built custom-message
mechanism instead of abusing the assistantMessage slot:

- renderCustomMessages injects one collapsible SubagentGroup per subagent,
  positioned "before" that subagent's first message in the thread — so each
  subagent gets its own reasoning-style header (name, collapsed by default,
  subtle activity dot tied to its own lifecycle). The group gathers all of the
  subagent's messages from live agent state, so interleaved subagents still
  group cleanly.
- The assistantMessage slot now only suppresses subagent messages from the
  default inline flow (returns null — the supported way to hide a message);
  they render inside their group instead. This drops the previous anchor/find
  hack.

Restore the demo agent to three research specialists (cognition, behavior,
neuroscience) so a single question fans out into multiple subagents, each
surfacing as its own group. The single-subagent reduction (used to isolate the
MESSAGES_SNAPSHOT fix) is no longer needed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ctivity

Two fixes to the demo agent:

- Parallel execution: the supervisor prompt now instructs the model to emit all
  three `task` calls in a SINGLE step (concurrent) instead of "in order", so the
  specialists run in parallel and their events interleave. Verified: three
  SUBAGENT_STARTED fire together and each finishes independently as its own task
  returns.
- Tool calls: the specialists' prompts stopped producing tool calls because they
  told the model to answer directly in 3-5 sentences. deepagents already gives
  each subagent the built-in todo + filesystem tools via middleware, so the fix
  is prompt-level: each specialist must call write_todos (plan) and write_file
  (notes) before answering. Verified: 17 tool calls across the three subagents
  (write_todos / write_file / read_file / grep).

Verified end-to-end in the dojo: three collapsible groups, each with its own
tool-call cards and an activity dot that stops when that subagent finishes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The subagent group header shows the pulsing activity dot while the subagent is
running; once it finishes (its own lifecycle, via useSubagent) the dot is
replaced by a subtle muted checkmark. Inline SVG with explicit 13x13 dimensions
(the cpk:size-3 utility isn't in CopilotKit's prebuilt stylesheet, and a bare
inline SVG collapses to 0x0).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CopilotChatReasoningMessage.Toggle animates grid-template-rows at 200ms in both
directions, so closing a group played a 200ms shrink before disappearing. Render
an equivalent collapse container that sets the transition duration to 0 while
closing (instant) and 200ms while opening (keeps the reveal animation).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Stop reusing CopilotChatReasoningMessage.Header. SubagentGroupHeader now
reproduces the same look (muted text, hover affordance, chevron that rotates
when open) as demo-owned markup: a plain button styled with the cpk: utility
classes from the already-imported CopilotKit stylesheet, an inline-SVG chevron
(no lucide dependency), and a tiny local cx() class-join helper instead of
twMerge. Drops the CopilotChatReasoningMessage import entirely — the group's
header, collapse container, and body are now all owned by the demo.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Subagent messages are streamed for display only and live in the subagent's own
subgraph checkpoint, never the main graph's. The client echoes the full history
back on every turn, so the main-graph merge (langgraph_default_merge_state)
treated them as new messages — their ids are never in the main checkpoint — and
appended them to the supervisor's state, polluting it with orphan subagent tool
calls that compound each turn.

Filter them out at the single run() entry point, keyed on the message's
subagent_id (a declared field on the AG-UI Python message types). The
supervisor's own messages carry no subagent_id and are kept; subagent messages
are re-streamed and re-attributed on each run.

Verified with a two-turn round-trip: turn 1 produced 35 subagent-attributed
messages; echoing all of them back on turn 2 left the supervisor state at 8
messages with only `task` tool calls — zero subagent-internal tool-call leakage.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Previously subagent messages were dropped from run input entirely, keeping the
graph state clean but making prior-turn subagent groups vanish from the display
on the next turn (the client's MESSAGES_SNAPSHOT apply drops any local message
absent from the snapshot).

Instead of discarding them, run() now splits the client's echoed-back subagent
messages aside (still never entering the graph) and stashes them on active_run.
_merge_subagent_messages re-emits them in the snapshot alongside the current
run's freshly-streamed subagent messages, deduped by id. Because the client's
snapshot apply is an edit-merge that keeps a message it already has in its
existing position, re-including them by id preserves their place — no ordering
reconstruction needed.

Net: subagent messages persist in the display across turns AND never pollute
graph state. Verified with a two-turn round-trip (all 16 turn-1 subagent
messages present in the turn-2 snapshot, relative order preserved, supervisor
state clean) and in the dojo (three groups stay pinned above the follow-up).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
These were working design/plan docs used during development; they don't belong
in the PR.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
spike_notes.md and snapshot_finding.md were development investigation notes;
their core findings are captured in the code and its comments, and their
specifics (single-subagent demo, market_researcher, deepagents 0.6.12 spikeenv)
are stale. The demo dir keeps only __init__.py and agent.py.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Findings from a code review of the subagent changes:

- Fix a failing test: test_subagent_emission's active_run helper had drifted
  from the real INITIAL_ACTIVE_RUN shape (missing subagent_tool_call_owner /
  inbound_subagent_messages), so _accumulate_subagent_message early-returned and
  the snapshot-merge assertion failed. Restore the full shape.
- LangGraph: a subagent left open when a run fails (hard exception, or an
  in-band "error" event) never received a terminal event — the client showed it
  running forever. Add error_open_subagents(): emit SUBAGENT_ERROR for every
  open subagent and clear them, on both the error-event branch and a new
  except-block for hard exceptions. Clearing also stops drain_subagents from
  emitting a contradictory SUBAGENT_FINISHED for a subagent that errored.
- LangGraph: reset current_subagent_id before end-of-run supervisor events so a
  lingering subagent id can't mis-stamp the final STATE_SNAPSHOT / STEP events
  when a subagent was still open at stream end.
- dojo: a subagent in "error" status rendered the green "finished" checkmark;
  show a distinct error mark instead.

Adds tests for error_open_subagents, _finish_subagent_on_task_end run_id
matching (incl. the inner-tool-shares-ns case), _capture_subagent_task_meta, and
cross-turn persistence — the load-bearing paths a coverage review flagged as
untested. Full suites green: integration 418, subagent-emission 27, py-sdk 6.

Co-Authored-By: Claude Opus 4.8 (1M context) <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