Skip to content

Add missing AG-UI features to A2AAgent bridge (shared state, HITL, Task/Artifact updates) #762

@0xTomDaniel

Description

@0xTomDaniel

Summary

Build out the A2A bridge so AG-UI can project artifacts/status/activity into shared state and activity events from A2A tasks/status updates, handle the AG-UI config/control lane for shared-state updates, and support input-required/interrupt flows while preserving existing text-only agent behavior.

Status

Work is already underway even though the guidelines ask contributors to reach out before starting; these features are required for my own usage whether or not they merge upstream.

Missing AG-UI <> A2A bridge features

integrations/a2a/typescript/

  • Config/control lane: support structured AG-UI shared-state/config updates via a dedicated extension, replace the unused extension header, and keep conversational messaging unchanged.
  • Task-aware reconnect/resubscribe: use task snapshots/historyLength, subscribe-only mode, and resume payloads without reopening closed runs.
  • Shared-state/activity projection: emit task/status/context snapshots and deltas; project artifacts with default /view/artifacts/<id> paths, append/lastChunk handling, and metadata-driven paths; include activity for surfaces and input requests.
  • Input-required interrupts/resume: emit RUN_FINISHED outcome interrupt with pending interrupt state/activity; surface A2A input_required task states through the AG-UI Human in the Loop (HITL) experience so reviewers see the request context; resume via a2a.input.response, update activity/state, and clear pending interrupts when resolved.
  • Context/config forwarding (no full transcript): send the current message plus opt-in system/developer/config cues and AG-UI tool results when needed; rely on A2A task history for the full log; preserve existing text-only agents unchanged.
  • Metadata hygiene: avoid leaking threadId/runId to A2A; prefer contextId/taskId in payloads and metadata.

Fix A2A Compliance

Currently the AG-UI threadId is used as the A2A contextId which is non-compliant with the A2A Protocol. The A2A contextId is meant to be generated by the A2A server when not supplied by the A2A client. The A2A client should never send a contextId that wasn't obtained by the A2A server.

  • AG-UI gives every agent a threadId up front: if the caller supplies one it’s used;
    otherwise the AbstractAgent constructor auto-generates a UUID and stores it on the
    instance (sdks/typescript/packages/client/src/agent/agent.ts:50-66).
  • Each run reuses that agent threadId untouched; it’s copied straight into RunAgentInput
    (sdks/typescript/packages/client/src/agent/agent.ts:287-299).
  • In the A2A bridge, that same threadId is forwarded as the A2A contextId when converting
    history and when sending the message (integrations/a2a/typescript/src/agent.ts:126-142 and
    integrations/a2a/typescript/src/utils.ts:190-238). There is no additional ID generation or
    remapping inside the bridge.
  • The bridge never reads a server-generated contextId from A2A responses, so AG-UI’s
    threadId remains the source of truth; A2A events are just streamed back as AG-UI events
    and don’t mutate it (integrations/a2a/typescript/src/agent.ts:163-188).

Proposed Solution

Keep threadId optional and late-bind threadId to the first server contextId for A2A agents; no bridge cache; non-A2A agents unchanged.

  • Event semantics:
    • For deferred A2A agents, RUN_STARTED is delayed until contextId is received; after that, event order remains RUN_STARTED → snapshot/deltas/messages.
    • Non-A2A agents: no change.
    • Caller contract:
    • ThreadId and contextId are the same for A2A agents after the first binding; callers just keep using threadId. No extra contextId parameter is required in the public API.

Scope / Acceptance

  • Add run options (send/stream, taskId) plus:
    • subscribe-only to reconnect without sending a new message (fetch snapshot, then resubscribe).
    • resume to send input responses to the existing task without reopening the prior run.
  • Use A2A server task snapshots (getTask) plus resubscribe for reconnects (no new message send).
  • Send the current payload with opt-in system/developer/config cues (no full transcript); use a dedicated config/control extension for AG-UI shared-state updates and gate the legacy extension header; rely on A2A task storage for full history.
  • Project A2A messages/status/artifacts into AG-UI text/tool/activity and shared-state events (snapshots + deltas), with default artifact base paths and append/lastChunk semantics.
  • Handle input_required by emitting RUN_FINISHED with outcome interrupt, keeping pending interrupts in shared state/activity, surfacing them via the AG-UI Human in the Loop (HITL) flow for context, and resuming via a2a.input.response without reviving closed runs.
  • Keep metadata layering so threadId/runId stay internal; prefer contextId/taskId externally.

Testing plan

Add or update unit, integration, and e2e coverage as needed; run pnpm --filter @ag-ui/a2a test (A2A agent/util e2e + integration Jest suites), pnpm --filter @ag-ui/client test (client agent/subscriber Jest suites), plus pnpm lint and pnpm build; follow docs/testing-strategy.md.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions