ag-ui-protocol / ag-ui-protocol/ag-ui

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

Open
#762 2 comments 2 reactions 0 assignees View on GitHub
enhancement Integration
Dominant language
Python
Stars
15.9k
Forks
1.4k
Avg merge
1d 17h
Merged PRs (30d)
163

Description

# 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/` 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`.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.