Skip to content

Workstream: A2A/AGUI Delegation Tools as Visual Subagents #5

Description

@cbuthim-art

Issue Proposal

Agent Adaptor already provides a useful foundation for orchestrating work
across different local agent CLIs. For example, a host can run Claude Code to
implement a change and then run Codex to review it. However, this orchestration
still happens at the host level: Claude Code cannot autonomously call Codex as
a subagent during its own run, and none of the supported CLIs can currently
call an arbitrary remote agent as a subagent-like collaborator.

This issue proposes a delegation mechanism that lets any host-curated remote
agent that speaks A2A or AG-UI appear inside a supported local agent CLI
(Claude Code, Codex, Cursor, and future adapters) as a visual subagent. The
remote agent remains remote: it keeps its own workspace, permissions, runtime,
tools, identity, and protocol semantics. The local agent only receives a final
structured summary/result through a tool call, while the UI receives the remote
agent's live progress through a side-channel stream.

The minimum product contract is:

  1. When a local agent calls a remote agent, the remote agent's progress, text,
    artifacts, and terminal state are streamed in real time over the SSE /
    AG-UI layer as a nested subagent. These live events must not be injected
    into or otherwise pollute the parent agent's model context as ordinary tool
    output.
  2. When the remote agent finishes, the parent agent receives a concise,
    structured tool result containing the remote task summary, status, artifact
    references, and protocol identifiers needed for audit and replay.

The implementation shape is an MCP delegation tool plus a host-side stream
overlay:

  1. The local agent calls an MCP tool such as delegate_to_agent.
  2. The tool delegates a bounded task to a remote A2A or AG-UI agent.
  3. While the tool call is active, the host streams remote task events through a
    delegation event bus into the same SSE / AG-UI response as the parent run.
  4. The parent model receives only the final structured tool result and then
    continues its own turn.

With this mechanism, Team Agent mode is no longer constrained to subagents
implemented inside a single agent CLI. A host can compose heterogeneous agent
technologies, for example Claude Code as the parent, Codex as a review
subagent, Cursor as an implementation subagent, and remote A2A/AG-UI agents as
specialized collaborators.

1. Target Product Effect

The target UI should look like this:

Parent agent is working...
  Tool: delegate_to_agent(research)

  Remote subagent: research
    status: starting
    text: Searching official A2A SDK examples...
    text: Comparing task streaming and artifact behavior...
    artifact: a2a-sdk-notes.md
    status: completed

Parent agent resumes with remote result...

The browser or IDE receives one continuous SSE / AG-UI stream. The user should
not need to open a second socket, watch a second task screen, or wait for the
remote task to finish before seeing remote progress.

2. How Close This Gets to Native Subagents

Dimension Target fidelity Notes
Real-time visible output High A2A streaming or AG-UI remote events can be merged into the parent SSE stream.
Nested lifecycle High subagent.started, progress, artifacts, terminal event can be shown under the parent tool call.
Cancellation High Parent cancel must cascade to the active A2A task.
Auditability High Store delegation ID, remote task ID, remote context ID, agent card fingerprint, events, and final result.
Parent agent receives final result High The MCP tool returns a structured tool result after remote completion or failure.
Parent agent watches tokens while reasoning Medium-low Most local agent protocols block while a tool call is active. The UI can see remote tokens; the parent model usually sees only the final tool result.
Shared workspace Intentionally no Remote agent owns its workspace. Local files must be passed explicitly as artifacts or references.
Shared permissions Intentionally no Remote auth and permissions are owned by the remote agent / host policy.
Shared tool namespace Intentionally no Remote tool calls may be opaque unless the remote agent exposes them as A2A / AG-UI events.

Conclusion: the design can closely match native subagent visualization and
lifecycle, but it must not claim same-process execution equivalence.

3. References

Assumption after issue #4 merges: this workstream consumes the A2A client and
bridge primitives from PR #6 instead of re-implementing A2A wire handling. In
particular, issue #5 should use:

  • pkg/clients/a2a.GetTaskRequest for polling/recovery with tenant and
    HistoryLength preservation.
  • pkg/clients/a2a.CancelTaskRequest for cancellation cascade with tenant and
    structured cancellation metadata.
  • pkg/bridges/a2a.ArtifactAssistantOutput (assistant-output) when mapping
    remote bridge text artifacts to subagent.text.*.
  • pkg/bridges/a2a.ArtifactAgentAdaptorResult (agent-adaptor-result) when
    building the final structured tool result.

Relevant A2A surfaces to consume from issue #4 and map into delegation events:

  • Agent Card discovery and capability metadata.
  • Message send and streaming send.
  • Task status updates.
  • Task artifact updates.
  • Task cancellation.
  • Task lookup / subscription for reconnect and recovery.

Relevant local SDK surfaces to reuse:

  • RunHandle.StreamEvents() <-chan StreamPayload
  • pkg/bridges/agui translation from StreamPayload to AG-UI events.
  • pkg/bridges/sse single HTTP SSE stream.
  • WithMCP / WithDefaultMCP for tool exposure to local agents.
  • WithRuntimeServices / RuntimeServiceManager for per-run service setup.

4. Scope

In Scope

  • A host-managed A2A/AG-UI delegation tool exposed to local agents through MCP.
  • A run-scoped event bus that streams remote delegation events while the MCP
    tool call is still executing.
  • A stream mux that merges parent RunHandle.StreamEvents() with delegation
    events into one AG-UI / SSE stream.
  • Mapping remote A2A task status, messages, artifacts, errors, and cancellation
    into nested subagent events.
  • BDD scenarios, TDD boundaries, and robustness tests.

Out of Scope

  • Automatic agent routing by the SDK.
  • Treating arbitrary remote A2A agents as default SDK agents.
  • Adding a built-in HTTP/gRPC server to core SDK.
  • Making concrete adapters import or understand A2A.
  • Pretending remote stdout/stderr exists. Remote protocol traces are not
    RunResult.RawStreams.
  • Giving the model arbitrary A2A endpoint URLs. Hosts must curate remote
    agents.

5. Architecture

sequenceDiagram
    participant UI as Browser / IDE
    participant SSE as SSE/AG-UI mux
    participant SDK as agent-adaptor RunHandle
    participant Parent as Local Codex/Claude/Cursor
    participant MCP as A2A delegation MCP tool
    participant Bus as DelegationEventBus
    participant Remote as Remote A2A/AG-UI agent

    UI->>SSE: POST chat request
    SSE->>SDK: sdk.Start(... WithStreaming, WithMCP)
    SDK->>Parent: adapter.Run(...)
    Parent-->>SSE: parent StreamPayloads
    Parent->>MCP: tool call delegate_to_agent
    MCP->>Bus: delegation.started
    Bus-->>SSE: subagent.started
    MCP->>Remote: A2A sendStreaming / subscribe
    Remote-->>MCP: task status / message / artifact events
    MCP->>Bus: subagent text/status/artifact events
    Bus-->>SSE: nested subagent stream
    Remote-->>MCP: terminal task status
    MCP->>Bus: delegation.finished
    MCP-->>Parent: structured tool result
    Parent-->>SSE: parent resumes and finishes
    SSE-->>UI: one ordered SSE stream
Loading

The important detail: the remote stream does not pass through the parent model
protocol. It travels through a host-side event bus and is multiplexed into the
same UI stream.

6. Package Layout

Proposed packages:

pkg/
  clients/
    a2a/                     existing issue #4 A2A client substrate
  bridges/
    a2a/                     existing issue #4 Runner -> A2A bridge substrate
    subagentstream/
      mux.go                 parent StreamPayload + DelegationEvent mux
      agui.go                nested AG-UI/custom event mapping
      sse.go                 optional handler wrapper around pkg/bridges/sse
  hosttools/
    a2adelegation/
      registry.go            host-curated remote agent registry
      delegator.go           Delegate(ctx, request) orchestration
      events.go              DelegationEvent model and bus
      mcpserver.go           MCP tool server exposing delegate_to_agent
      correlation.go         run/tool/delegation correlation
      policy.go              allowlist, budget, auth, timeout

Naming can change during implementation, but the ownership should not:

7. Required New Capability: Run-Scoped MCP Context

This is the first hard boundary.

To render a remote task as a local subagent, the delegation MCP tool must know
which parent run it belongs to. Ideally it also knows which parent tool call it
belongs to.

Current facts:

  • RuntimeServiceManager.Ensure receives RuntimeServiceRequest.RunID.
  • RunHandle.RunID() is available after Start().
  • MCPServerSpec is resolved before runtime services, so a runtime-created
    URL/token cannot currently be appended to MCP config without a small SDK
    enhancement.
  • MCPServerSpec.Headers and MCPServerSpec.Env are static host-provided
    values today; they do not automatically receive run ID or a per-run token.

Therefore, a reliable implementation needs one of these mechanisms.

Option A: Runtime-Backed MCP Server Injection (Preferred)

Allow a runtime service to declare that it exposes an MCP server for the run.
The SDK would append this server to the effective MCP payload before adapter
profile materialization.

Sketch:

agentadaptor.RuntimeServiceRef{
    ID:   "a2a-delegation",
    Name: "a2a-delegation",
    URL:  "http://127.0.0.1:43127/mcp",
    Metadata: map[string]string{
        "agentadaptor.mcp.enabled":   "true",
        "agentadaptor.mcp.transport": "http",
        "agentadaptor.mcp.key":       "a2a-delegation",
        "agentadaptor.mcp.auth":      "bearer",
    },
}

The runtime manager can create a per-run bearer token and register:

runID -> delegation bus, remote agent registry, cancellation handle

The injected MCP endpoint receives the token on every tool request and resolves
the parent run context without asking the model to pass it.

Option B: Run-Scoped MCP Header / Env Expansion

Support placeholder expansion in MCP server specs:

agentadaptor.MCPServerSpec{
    Key:       "a2a-delegation",
    Transport: agentadaptor.MCPTransportHTTP,
    URL:       "http://127.0.0.1:43127/mcp",
    Headers: map[string]string{
        "Authorization": "Bearer ${AGENT_ADAPTOR_RUN_TOKEN}",
        "X-Run-ID":     "${AGENT_ADAPTOR_RUN_ID}",
    },
}

For stdio MCP servers:

Env: map[string]string{
    "AGENT_ADAPTOR_RUN_ID":    "${AGENT_ADAPTOR_RUN_ID}",
    "A2A_DELEGATION_BUS_URL":  "${A2A_DELEGATION_BUS_URL}",
    "A2A_DELEGATION_RUN_TOKEN": "${AGENT_ADAPTOR_RUN_TOKEN}",
}

This is smaller than runtime-backed injection but less expressive.

Option C: Weak Correlation (MVP Only)

Use a shared MCP server and correlate by temporal proximity:

  1. Parent stream emits tool_call.start with Name == "delegate_to_agent".
  2. MCP server receives a delegation request.
  3. The bridge associates the next unclaimed delegation with the active parent
    tool call.

This can work for demos but is not robust enough for production because
parallel tool calls and retries can misassociate events.

Design Decision

The production design should use Option A or Option B. Option C is acceptable
only for a spike and must be guarded by tests that demonstrate its known
failure modes.

8. Required New Capability: Delegation Stream Overlay

This is the second hard boundary.

The parent adapter cannot emit remote A2A task events because those events are
produced by the MCP tool process, not by the parent CLI process. Therefore
RunHandle.StreamEvents() alone is not enough.

Add a host-side event bus:

type DelegationEvent struct {
    RunID            string
    ParentToolCallID string
    DelegationID     string
    AgentKey         string
    AgentName        string
    Protocol         string // "a2a" or "agui"

    RemoteTaskID    string
    RemoteContextID string
    RemoteMessageID string
    RemoteArtifactID string

    Kind      DelegationEventKind
    Delta     string
    Text      string
    Artifact  *DelegationArtifact
    Status    string
    Error     *DelegationError
    Raw       map[string]any

    Time time.Time
}
type DelegationEventKind string

const (
    DelegationStarted          DelegationEventKind = "subagent.started"
    DelegationStatus           DelegationEventKind = "subagent.status"
    DelegationTextStart        DelegationEventKind = "subagent.text.start"
    DelegationTextDelta        DelegationEventKind = "subagent.text.delta"
    DelegationTextEnd          DelegationEventKind = "subagent.text.end"
    DelegationArtifactCreated  DelegationEventKind = "subagent.artifact"
    DelegationInputRequired    DelegationEventKind = "subagent.input_required"
    DelegationFinished         DelegationEventKind = "subagent.finished"
    DelegationFailed           DelegationEventKind = "subagent.failed"
    DelegationCancelled        DelegationEventKind = "subagent.cancelled"
)

The SSE / AG-UI mux subscribes to:

  • Parent handle.StreamEvents().
  • Delegation events for handle.RunID().

It emits one ordered stream to the UI.

9. Event Mapping

A2A to DelegationEvent

A2A signal DelegationEvent Notes
task created / accepted subagent.started Includes agent key, task ID, context ID, card fingerprint.
task status update subagent.status Preserve raw status and message.
status message text subagent.text.delta or subagent.status Use text delta only when the remote protocol is actually streaming assistant text.
task artifact update subagent.artifact Store artifact metadata and content reference.
input-required state subagent.input_required Host decides whether to surface UI action, return tool result, or fail.
completed state subagent.finished Terminal success.
failed / rejected state subagent.failed Terminal failure with remote status details.
cancel acknowledged subagent.cancelled Terminal cancellation.
stream disconnect non-terminal warning, then recovery Re-subscribe or poll before failing.

Remote AG-UI to DelegationEvent

AG-UI event DelegationEvent Notes
RUN_STARTED subagent.started Use remote run ID as remote task ID if no A2A task ID exists.
TEXT_MESSAGE_* subagent.text.* Preserve message lifecycle.
TOOL_CALL_* subagent.status or nested raw event Do not pretend remote inner tools are local parent tools unless UI supports second-level nesting.
CUSTOM subagent.status or raw custom Preserve raw event payload.
RUN_FINISHED subagent.finished Terminal success.
RUN_ERROR subagent.failed Terminal failure.

DelegationEvent to Local StreamPayload

For the first implementation, avoid adding new core StreamKind constants.
Use opaque custom stream payloads:

agentadaptor.StreamPayload{
    Kind: "",
    Name: "subagent.text.delta",
    Raw: map[string]any{
        "delegation_id":       ev.DelegationID,
        "agent_key":           ev.AgentKey,
        "parent_tool_call_id":  ev.ParentToolCallID,
        "remote_task_id":       ev.RemoteTaskID,
        "remote_context_id":    ev.RemoteContextID,
        "delta":                ev.Delta,
    },
}

pkg/bridges/agui already maps Kind == "" to AG-UI CUSTOM. The new mux can
either:

  1. Emit these as AG-UI CUSTOM events with names like subagent.text.delta.
  2. Optionally wrap them in AG-UI tool-call lifecycle events if the frontend
    wants all subagent activity to live under the parent tool call.

The default should be custom subagent events because they preserve remote
semantics without overloading local tool call lifecycles.

10. UI Wire Shape

Recommended AG-UI custom event payload:

{
  "type": "CUSTOM",
  "name": "subagent.text.delta",
  "value": {
    "runId": "run-...",
    "parentToolCallId": "toolu-...",
    "delegationId": "del-...",
    "agentKey": "research",
    "agentName": "Research Agent",
    "remoteTaskId": "task-...",
    "remoteContextId": "ctx-...",
    "messageId": "msg-...",
    "delta": "Searching official examples..."
  }
}

Recommended lifecycle names:

subagent.started
subagent.status
subagent.text.start
subagent.text.delta
subagent.text.end
subagent.artifact
subagent.input_required
subagent.finished
subagent.failed
subagent.cancelled

The UI renderer groups by:

runId + parentToolCallId + delegationId

If parentToolCallId is unknown, group under the run as an unclaimed remote
delegation and update the group once correlation is resolved.

11. Tool Contract

Expose one generic tool plus optional named shortcuts.

Generic Tool

delegate_to_agent

Input:

{
  "agent": "research",
  "objective": "Find the current A2A Go SDK streaming API shape.",
  "input": {
    "prompt": "Research official examples and summarize exact APIs.",
    "context": "We are designing agent-adaptor A2A delegation.",
    "artifacts": [
      {
        "name": "current-design.md",
        "uri": "host://artifact/current-design.md"
      }
    ]
  },
  "constraints": {
    "timeout_seconds": 180,
    "stream": true,
    "max_artifacts": 10
  }
}

Output:

{
  "delegation_id": "del-...",
  "agent": "research",
  "remote_protocol": "a2a",
  "remote_task_id": "task-...",
  "remote_context_id": "ctx-...",
  "status": "completed",
  "summary": "Official a2a-go exposes...",
  "artifacts": [
    {
      "id": "artifact-...",
      "name": "a2a-sdk-notes.md",
      "mime_type": "text/markdown",
      "uri": "host://delegations/del-.../artifacts/a2a-sdk-notes.md"
    }
  ],
  "messages": [
    {
      "role": "assistant",
      "text": "..."
    }
  ],
  "raw_task": {
    "provider": "a2a",
    "task_id": "task-..."
  }
}

Errors should return structured tool errors, not unstructured text:

{
  "delegation_id": "del-...",
  "status": "failed",
  "error": {
    "code": "remote_timeout",
    "message": "Remote task did not finish before timeout.",
    "retryable": true,
    "remote_status": "working"
  }
}

Named Shortcuts

Hosts may expose curated wrappers:

delegate_to_research_agent
delegate_to_code_review_agent
delegate_to_data_agent
delegate_to_legal_agent

These are aliases over the generic delegator with a fixed agent key and
stricter input schema.

12. Remote Agent Registry

The model must not pass arbitrary remote URLs.

Host-owned registry:

type RemoteAgentSpec struct {
    Key          string
    DisplayName  string
    Protocol     string // "a2a" or "agui"
    AgentCardURL string
    EndpointURL  string
    AuthRef      string
    Capabilities RemoteCapabilities
    Policy       DelegationPolicy
}

Policy examples:

type DelegationPolicy struct {
    AllowedTenants       []string
    MaxTimeout           time.Duration
    MaxArtifactBytes     int64
    MaxConcurrentPerRun  int
    MaxConcurrentGlobal  int
    AllowInputRequired   bool
    AllowRemoteArtifacts bool
    AuditLevel           string
}

Security rules:

  • Agent endpoints are configured by the host, not by the model.
  • Agent Cards are fetched by the host and cached with a fingerprint.
  • Auth tokens never appear in prompts, tool args, or StreamPayload.
  • Tool args contain agent keys, not URLs or bearer tokens.
  • Artifacts are stored in host-controlled storage and passed by URI/reference
    when possible.

13. Correlation Model

Identifiers:

ID Owner Purpose
RunID SDK Parent run identity.
ParentToolCallID Parent adapter Local tool-call lifecycle ID for delegate_to_agent.
DelegationID Delegation tool Stable host ID for one remote delegation.
RemoteTaskID A2A remote Remote task identity.
RemoteContextID A2A remote Remote multi-turn context identity.

Correlation levels:

  1. Strong: MCP request carries a run-scoped token and the parent adapter emits
    a matching tool call ID or request metadata.
  2. Medium: MCP request carries run ID; mux groups the delegation under the
    active delegate_to_agent tool call by call order.
  3. Weak: no run ID; host guesses by timestamp. This must not be production
    default.

The desired implementation is strong or medium. Weak correlation is only a
diagnostic fallback.

14. Session and Context Semantics

Default behavior:

  • Each delegation creates a new remote A2A task.
  • If the remote protocol supports a context ID, the delegator may create or
    reuse it according to host policy.
  • The parent SDK session is not the remote context ID.

Supported policies:

Policy Behavior Use case
ephemeral New remote context per delegation. Safest default.
sticky_per_parent_thread_agent Reuse remote context for (parent SessionKey, agentKey). Remote specialist remembers prior delegations within a chat.
sticky_per_run_agent Reuse remote context within one parent run only. Multiple delegations in one turn.

The tool result must always report the remote context ID actually used.

15. Cancellation Semantics

Cancellation cascade:

  1. UI disconnects or user clicks stop.
  2. pkg/bridges/sse cancels the parent request context.
  3. Parent RunHandle.Cancel(ctx) is called.
  4. The MCP tool request context is cancelled by the parent provider if the
    provider supports MCP cancellation.
  5. Delegator calls A2A cancel task for active remote task.
  6. Delegation bus emits subagent.cancelled.
  7. If remote cancellation cannot be confirmed before timeout, emit
    subagent.status with cancel_pending and mark the local tool result as
    cancelled or abandoned according to host policy.

Tests must cover providers that do not propagate MCP cancellation promptly.
The host should keep a cleanup goroutine keyed by DelegationID so remote
tasks are still cancelled when the parent process exits first.

16. Failure Semantics

Failure classes:

Code Meaning Tool result UI event
agent_not_found Unknown registry key. Error, no remote call. subagent.failed optional.
agent_unavailable Agent Card fetch/health failed. Retryable error. subagent.failed.
capability_unsupported Remote lacks required streaming/artifact capability. Error or degraded mode. subagent.status.
remote_rejected Remote rejects task. Non-retryable error. subagent.failed.
remote_failed Remote task failed. Error with remote status. subagent.failed.
remote_timeout Host timeout expired. Retryable or cancelled by policy. subagent.failed or subagent.cancelled.
stream_interrupted Stream dropped before terminal state. Recover with subscribe/get before failing. subagent.status.
artifact_too_large Artifact exceeds policy. Partial result with artifact error. subagent.artifact error.
input_required Remote needs user input. Configurable: surface, fail, or return needs_input. subagent.input_required.

The parent model should receive enough structured information to decide
whether to retry, continue without remote output, or ask the user.

17. Streaming and Backpressure

Delegation events are UI-facing and may be high-volume. Use QoS lanes:

Lane Events Policy
Critical started, finished, failed, cancelled, input_required Never drop; block or spill to disk.
Important artifacts, status transitions Prefer no drop; deduplicate repeated status.
Volatile text deltas May coalesce under pressure.

Coalescing rule:

  • Preserve text order per (DelegationID, MessageID).
  • Merge adjacent deltas when the UI consumer is slow.
  • Emit a subagent.status or stream.dropped marker if deltas are dropped.
  • Never drop terminal state.

The mux assigns its own global SSE cursor so parent events and delegation
events can be replayed in emitted order.

18. SSE / AG-UI Mux

The mux should be a wrapper, not a replacement for existing bridges.

Sketch:

type MuxOptions struct {
    Protocol          sse.Protocol
    DelegationBus     a2adelegation.EventBus
    IncludeRawPayloads bool
    SubagentMode      SubagentMode
}

type SubagentMode int

const (
    SubagentAsCustomEvents SubagentMode = iota
    SubagentAsToolNestedEvents
)

Flow:

  1. Start parent run with WithStreaming().
  2. Create runStream := agui.WrapWithContext(ctx, handle).
  3. Create subagentStream := bus.SubscribeRun(ctx, handle.RunID()).
  4. Merge both streams into one AG-UI event stream.
  5. Assign a monotonic SSE id at the mux layer.
  6. On context cancellation, cancel parent handle and active delegations.

Default rendering:

  • Parent events remain normal AG-UI events.
  • Subagent events are AG-UI CUSTOM events.
  • Frontends that know the custom names render nested subagent UI.
  • Frontends that do not know the custom names still receive valid AG-UI.

19. BDD Scenarios

Scenario 1: Successful Remote A2A Subagent Stream

Feature: A2A delegation appears as a local visual subagent

  Scenario: Remote A2A agent streams text and an artifact
    Given a parent SDK run is started with streaming enabled
    And the A2A delegation tool is available through MCP
    And the remote "research" agent supports streaming and artifacts
    When the parent agent calls delegate_to_agent with agent "research"
    Then the SSE stream emits a parent tool_call.start for delegate_to_agent
    And the SSE stream emits subagent.started for agent "research"
    And the SSE stream emits subagent.text.delta events before the tool result
    And the SSE stream emits subagent.artifact for the remote artifact
    And the SSE stream emits subagent.finished with the remote task ID
    And the parent agent receives a structured MCP tool result
    And the parent agent can continue and emit final assistant text

Scenario 2: Remote Agent Has No Streaming

Scenario: Non-streaming remote agent degrades gracefully
  Given the remote "legacy" agent does not advertise streaming
  When the parent agent delegates a task to "legacy"
  Then the SSE stream emits subagent.started
  And the SSE stream emits status updates from polling or task state
  And the SSE stream does not emit fake text deltas
  And the final tool result contains the remote task result

Scenario 3: Parent Cancellation Cancels Remote Task

Scenario: User stops the parent run while a remote task is active
  Given the parent run has an active delegation with remote task ID "task-1"
  When the user cancels the parent run
  Then the delegator calls A2A cancel task for "task-1"
  And the SSE stream emits subagent.cancelled or subagent.status cancel_pending
  And the parent run terminates without leaking the remote task watcher

Scenario 4: Remote Failure Is Visible and Structured

Scenario: Remote task fails
  Given the remote A2A agent accepts a delegated task
  When the remote task reaches a failed terminal status
  Then the SSE stream emits subagent.failed with the remote status
  And the MCP tool returns a structured error result
  And the parent agent can decide whether to retry or continue

Scenario 5: Input Required

Scenario: Remote task requires additional input
  Given the remote A2A task enters input-required state
  And host policy allows surfacing remote input requests
  When the delegator receives the input-required event
  Then the SSE stream emits subagent.input_required
  And the MCP tool blocks until the input is resolved or times out

Scenario 6: Multiple Concurrent Delegations

Scenario: Two remote delegations stream concurrently
  Given the parent run starts two delegate_to_agent tool calls
  When both remote agents stream events concurrently
  Then every subagent event includes a distinct delegation ID
  And events are grouped under the correct parent tool call when known
  And terminal events are emitted for both delegations

Scenario 7: SSE Reconnect

Scenario: UI reconnects while remote delegation is active
  Given a remote delegation is active and streaming
  When the browser disconnects and reconnects with Last-Event-ID
  Then the mux replays buffered events after the requested cursor
  And the delegator resubscribes or polls the remote A2A task if needed
  And the UI receives a consistent terminal state exactly once

Scenario 8: Unauthorized Agent Key

Scenario: Model requests an unregistered remote agent
  Given the remote registry only contains "research"
  When the model calls delegate_to_agent with agent "unknown"
  Then the tool returns agent_not_found
  And no outbound network request is made
  And no secret is exposed in the tool result or stream

20. TDD Boundaries

Tests should be written before implementation in these layers.

20.1 Pure Mapping Unit Tests

Package: pkg/hosttools/a2adelegation

  • Registry reads issue Add A2A protocol bridge and client primitives #4 pkg/clients/a2a.AgentCard capability metadata
    conservatively.
  • Registry rejects duplicate agent keys.
  • Registry rejects agent specs with direct model-provided URLs.
  • Policy clamps timeout to host maximum.
  • A2A task status update maps to DelegationStatus.
  • A2A artifact update maps to DelegationArtifactCreated.
  • a2a.ArtifactAssistantOutput maps to subagent.text.*.
  • a2a.ArtifactAgentAdaptorResult maps to final tool-result summary/artifacts.
  • A2A terminal completed maps to DelegationFinished.
  • A2A terminal failed maps to DelegationFailed.
  • A2A TASK_STATE_INPUT_REQUIRED maps to DelegationInputRequired.
  • Malformed protocol payload returns typed error, not panic.
  • Delegator emits subagent.started before remote stream events.
  • Delegator emits exactly one terminal event.
  • Tool result includes delegation ID, remote task ID, context ID, summary, and artifacts.
  • Remote auth reference never appears in event payloads.

Package: pkg/bridges/subagentstream

  • Mux preserves parent AG-UI events unchanged.
  • Mux converts delegation events to AG-UI custom events.
  • Mux assigns monotonic SSE ids across parent and delegation events.
  • Mux closes open subagent lifecycle on terminal parent run.
  • Unknown delegation event kinds are forwarded as custom events or ignored by policy, never panic.

20.2 Contract Tests

  • pkg/hosttools/a2adelegation must not import codex, claude, or cursor.
  • pkg/bridges/subagentstream must not import concrete adapters.
  • Core SDK must not grow A2A-specific types.
  • RunResult.RawStreams must not contain remote A2A wire dumps.
  • Remote artifacts must be referenced by structured artifact fields, not appended to Output.
  • Parent Run / Start execution path remains unchanged.

20.3 Integration Tests With Fake A2A Server

Fake server capabilities:

  • Agent Card endpoint.
  • Send streaming task.
  • Emit status, message, artifact, completed.
  • Emit failed.
  • Support cancellation.
  • Drop stream mid-task and allow subscribe/get recovery.

Test cases:

  • Happy path with streaming text and artifact.
  • Non-streaming fallback via polling.
  • Remote failure.
  • Remote timeout.
  • Parent cancellation.
  • Stream disconnect and recovery.
  • Duplicate terminal events.
  • Artifact too large.

20.4 MCP Tool Tests

  • Tool schema validates required agent and objective.
  • Tool rejects arbitrary endpoint_url fields.
  • Tool maps registry key to remote spec.
  • Tool receives run-scoped context from token/env/header.
  • Tool handles missing run context as configuration error.
  • Tool returns structured error on remote failure.
  • Tool emits bus events while call is still blocked.
  • Tool cleanup runs when context is cancelled.

20.5 UI / SSE Golden Tests

Use golden event sequences.

Expected order for happy path:

RUN_STARTED
TOOL_CALL_START(delegate_to_agent)
CUSTOM(subagent.started)
CUSTOM(subagent.text.start)
CUSTOM(subagent.text.delta)
CUSTOM(subagent.artifact)
CUSTOM(subagent.text.end)
CUSTOM(subagent.finished)
TOOL_CALL_RESULT(delegate_to_agent)
TEXT_MESSAGE_START
TEXT_MESSAGE_CONTENT
TEXT_MESSAGE_END
RUN_FINISHED

Verifier assertions:

  • AG-UI event stream is valid.
  • Parent run starts before any other AG-UI event.
  • Parent run has exactly one terminal event.
  • Subagent has exactly one terminal event.
  • Subagent terminal event happens before parent tool result in the happy path.
  • Terminal parent event closes dangling subagent groups if remote stream ends late.

21. Robustness Test Matrix

Risk Test
Remote stream drops before terminal state Force disconnect; verify subscribe/get recovery before failure.
Remote sends duplicate events Deduplicate by remote event ID or stable hash.
Remote sends terminal before started Synthesize subagent.started, then terminal.
Remote never sends terminal Timeout and cancel; emit remote_timeout.
Parent cancels but remote cancel hangs Cancel with bounded timeout; mark cancel_pending; cleanup watcher continues.
Slow browser consumer Coalesce text deltas; never drop terminal events.
Huge artifact Store out-of-band or reject by policy; do not inline into SSE.
Malformed Agent Card Reject before tool is exposed or before first call.
Auth failure Return typed agent_unavailable / remote_rejected; redact secrets.
Concurrent delegations Ensure grouping by delegation ID and no cross-talk.
Weak correlation ambiguity Test that weak mode refuses ambiguous concurrent calls.
Race on run shutdown go test -race for mux, bus, delegator cleanup.
Goroutine leak Leak test after cancellation, timeout, and remote disconnect.
Event replay buffer overflow Emit explicit replay gap marker; do not replay partial terminal states.
Remote input-required unsupported by policy Tool returns structured input_required_unsupported.

22. Property and Fuzz Tests

Property tests:

  • For every DelegationID, there is at most one terminal event.
  • For every terminal event, a started event is visible to the mux output
    either remote-originated or synthesized.
  • Event cursor is strictly increasing in mux output.
  • Per (DelegationID, MessageID), text deltas preserve input order.
  • Secret-like fields configured as auth refs never appear in serialized events.

Fuzz targets:

  • Agent Card registry ingestion.
  • A2A-to-DelegationEvent mapper.
  • Artifact metadata parser.
  • Delegation tool JSON input.
  • AG-UI custom event serialization.
  • SSE Last-Event-ID parser.

23. Phased Implementation

Prerequisite: Issue #4 A2A Substrate Merged

This workstream starts after PR #6 (issue #4) lands. The implementation must
reuse pkg/clients/a2a and pkg/bridges/a2a from that PR.

Do not re-implement:

  • Agent Card fetch/validation/cache.
  • A2A send/sendStreaming/subscribe/get/cancel wire handling.
  • A2A stream final-state detection and recovery.
  • Runner-to-A2A bridge artifact naming and exposure policy.
  • Official a2a-go dependency selection.

Exit criteria:

Phase 1: Run-Scoped MCP Context

  • Implement the chosen production correlation mechanism for the delegation MCP
    tool.
  • Prefer Option A: runtime-backed MCP server injection from
    RuntimeServiceRef metadata into the effective MCP payload before adapter
    profile materialization.
  • Keep Option B, run-scoped header/env expansion, as the smaller fallback if
    runtime-backed injection proves too invasive.
  • Weak temporal correlation may exist only as an explicit diagnostic/spike mode
    and must refuse ambiguous concurrent calls.

Exit criteria:

  • The MCP tool can resolve the parent RunID without asking the model to pass
    it.
  • A per-run token or equivalent authority gates access to the delegation MCP
    endpoint.
  • Tests cover missing run context, invalid token, and concurrent parent runs.

Phase 2: Delegation Core

  • Implement host registry and policy.
  • Implement in-memory DelegationEventBus.
  • Implement A2A event to DelegationEvent mapping using issue Add A2A protocol bridge and client primitives #4 DTOs and
    bridge artifact constants.
  • Implement streaming remote A2A execution.
  • Implement non-streaming fallback via Send(ReturnImmediately) plus
    GetTaskRequest polling.
  • Implement stream disconnect recovery via subscribe/get before failing.
  • Implement cancellation cascade via CancelTaskRequest.
  • Keep fake A2A server fixtures for deterministic tests.

Exit criteria:

  • BDD scenarios 1, 2, 3, and 4 pass against fake parent/fake remote.
  • Delegator emits subagent.started before remote progress and exactly one
    terminal event.
  • Cancellation test proves remote task cancel is called with tenant and
    structured metadata.
  • Non-streaming fallback does not emit fake text deltas.

Phase 3: MCP Tool Server

  • Implement delegate_to_agent MCP tool.
  • Implement tool schema validation and structured tool-result shape.
  • Wire tool requests to run-scoped context from Phase 1.
  • Tool emits bus events while blocked on remote completion.
  • Tool rejects arbitrary endpoint URLs and only accepts registry keys.

Exit criteria:

  • MCP tool tests pass.
  • No arbitrary URL can be passed by the model.
  • Remote failure and timeout return structured tool errors.
  • Tool cleanup runs when context is cancelled.

Phase 4: SSE / AG-UI Product Path

  • Add subagentstream mux or extend existing SSE bridge through options.
  • Render subagent custom events into one stream.
  • Add replay buffer for reconnect.
  • Add backpressure/coalescing behavior for high-volume text deltas.
  • Keep parent AG-UI events unchanged.

Exit criteria:

  • Golden AG-UI stream validates.
  • Browser reconnect test passes.
  • Backpressure/coalescing tests pass.
  • Parent and subagent terminal events are each emitted exactly once.

Phase 5: Product Example

  • Update examples/streaming-chat-copilotkit.
  • Use Claude Code as the parent local agent.
  • Expose Codex through the issue Add A2A protocol bridge and client primitives #4 A2A bridge.
  • Inject the delegation MCP tool into the Claude run.
  • Render Codex remote progress as nested subagent events in the same SSE /
    AG-UI stream.

Exit criteria:

  • The example demonstrates Claude calling Codex through delegate_to_agent.
  • Codex live progress is visible in the UI before the MCP tool result returns.
  • Claude receives only the concise structured tool result and then continues.
  • Setup and run instructions are documented.

Phase 6: A2A Interop Hardening

  • Exercise at least one live/manual A2A endpoint in addition to fake-server
    fixtures.
  • Pin protocol fixtures for remote message, artifact, terminal, failure,
    input-required, and cancellation cases.
  • Document known interop gaps and degradation behavior.

Exit criteria:

  • Fake server tests still pass.
  • At least one live/manual interop target is documented.
  • Protocol fixtures are pinned.

Phase 7: Remote AG-UI Input

  • Add optional remote AG-UI client ingestion.
  • Map remote AG-UI events to DelegationEvent.

Exit criteria:

  • Remote AG-UI and remote A2A produce the same subagent custom event family.

24. Acceptance Criteria

  • A local agent can call delegate_to_agent via MCP.
  • The remote A2A/AG-UI task appears in the same SSE stream as a nested
    subagent before the MCP tool result is returned.
  • Parent cancellation cascades to the remote task.
  • Remote artifacts are visible as subagent artifact events and as structured
    tool result references.
  • The parent model receives a final structured tool result.
  • No concrete adapter imports A2A delegation packages.
  • Core SDK does not gain a second execution entrypoint.
  • Remote auth secrets are not visible to the model or SSE client.
  • BDD happy path, cancellation, remote failure, concurrent delegation, and
    reconnect scenarios pass.
  • go test -race passes for delegation bus, mux, and tool server packages.

25. Open Design Questions

  1. Should run-scoped MCP context be implemented as runtime-backed MCP injection
    or placeholder expansion in MCPServerSpec?
  2. Do we want the frontend to render subagent events as AG-UI CUSTOM only, or
    also provide a "nested tool lifecycle" compatibility mode?
  3. Should sticky remote context reuse be enabled by default for named agents,
    or should the default be ephemeral only?
  4. How much remote inner tool-call visibility should the UI show? Second-level
    nesting is possible but may clutter the stream.
  5. Should remote input-required map into local HITL v2, or remain a
    subagent-specific UI event with host-owned resolution?

26. Architectural Guardrails

  • The MCP tool is a host-controlled delegation surface, not a router.
  • A2A client code is localized and does not enter core SDK.
  • The event overlay is UI-facing; it does not mutate parent RunResult.
  • The parent agent sees only the final structured tool result unless the host
    deliberately feeds remote progress back as tool output.
  • The UI may make remote execution look like a local subagent, but audit data
    must preserve the remote protocol, task ID, context ID, and agent identity.

27. Required Example

The implementation must include an end-to-end example based on the existing
Copilot/CopilotKit demo path, currently
examples/streaming-chat-copilotkit.

The example should demonstrate:

  1. A Claude Code instance is the parent local agent driving the main chat.
  2. A separate Codex instance is exposed as an A2A-compatible remote agent.
  3. Claude Code receives an MCP tool such as delegate_to_agent that targets
    the Codex A2A agent.
  4. When Claude Code calls the tool, the CopilotKit UI renders the Codex run as
    a nested visual subagent in the same SSE / AG-UI stream.
  5. Codex's live progress is visible to the user but is not injected into
    Claude Code's model context as ordinary tool output.
  6. When Codex finishes, Claude Code receives a concise structured result and
    continues the parent turn.

This example is the concrete product proof for the workstream: a Codex agent
running through A2A must be callable as a subagent-like collaborator from a
Claude Code parent while preserving the parent/remote context boundary.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions