feat: subagent support (protocol + LangGraph + demo)#2137
Draft
mme wants to merge 56 commits into
Draft
Conversation
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>
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>
Contributor
Python Preview PackagesVersion
Install with uvAdd the TestPyPI index to your [[tool.uv.index]]
name = "testpypi"
url = "https://test.pypi.org/simple/"
explicit = trueThen 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 testpypiInstall with pippip install \
--index-url https://test.pypi.org/simple/ \
--extra-index-url https://pypi.org/simple/ \
ag-ui-protocol==0.0.0.dev1783529130
Commit: 25c2a80 |
@ag-ui/a2a-middleware
@ag-ui/a2ui-middleware
@ag-ui/event-throttle-middleware
@ag-ui/mcp-apps-middleware
@ag-ui/mcp-middleware
@ag-ui/a2a
@ag-ui/adk
@ag-ui/ag2
@ag-ui/agno
@ag-ui/aws-strands
@ag-ui/claude-agent-sdk
@ag-ui/crewai
@ag-ui/langchain
@ag-ui/langgraph
@ag-ui/llamaindex
@ag-ui/mastra
@ag-ui/pydantic-ai
@ag-ui/vercel-ai-sdk
@ag-ui/watsonx
@ag-ui/a2ui-toolkit
create-ag-ui-app
@ag-ui/client
@ag-ui/core
@ag-ui/encoder
@ag-ui/proto
commit: |
…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>
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.
Draft. Adds AG-UI subagent support:
subagentIdon messages/creation events +SUBAGENT_STARTED/FINISHED/ERRORlifecycle (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