a2aproject / a2aproject/A2A

[Extension Proposal]: Agent Steering — interruptible & steerable tasks

Abierto
#2,125 0 comentarios 0 reacciones 0 asignados Ver en GitHub
enhancement v1.1-candidate
Lenguaje dominante
Shell
Estrellas
25.7k
Forks
2.6k
Merge medio
3 d 6 h
PR fusionados (30 d)
16

Descripción

### Summary

An opt-in A2A extension that lets an agent advertise, and a client safely use, two "steering" behaviors on a non-terminal task:

- **Interruptible**: the client can interrupt an actively `WORKING` task; the agent ceases the current work and accepts new input.
- **Steerable**: the client can send a message that the agent incorporates *while it keeps working*, without a state transition.

The extension is declared on the Agent Card and activated per-request via the `A2A-Extensions` header. It adds no new core types or enum values; steering signals ride on `Message.metadata` and interrupted status is expressed as a new `TaskState`.

### Motivation

Sending a message to a running task is currently undefined for interop (see #1992 Gap 1; #1022 vs #1027). The TSC deliberately kept interrupt semantics out of core because mandating them is onerous for all implementors. An **opt-in extension** resolves exactly that: agents that can support steering advertise it and get well-defined, discoverable semantics; agents that can't are unaffected. This also gives the front-end use case in #1022 ("guidable") a concrete, negotiable contract, and revisits the pause/resume need from #1276/#1277 (closed as core RPCs) as an extension instead. This extension remains experimental while coalescing on semantics that can land in the main spec.

### Extension URI

`https://a2a-protocol.org/extensions/steering/v1`. During incubation it would live in the sponsor-created `experimental-ext-steering` repo.

### Modes

**In scope**
- `interruptible`: interrupt an active task; agent stops and awaits new input.
- `steerable`: steer an active task in place; agent incorporates new input without leaving `WORKING`.

**Out of scope (deferred)**
- Agent continuing background work *while* concurrently consuming a stream of client events
- Agent-*initiated* pause (the second use case in #1276) that is not INPUT_REQUIRED. (To me this is a SUSPENDED state or perhaps operation).

### Agent Declaration (Agent Card)

Declared as an `AgentExtension` under `AgentCapabilities.extensions`, with the two modes as params:

```json
{
"capabilities": {
"extensions": [
{
"uri": "https://a2a-protocol.org/extensions/steering/v1",
"description": "Supports interrupting and/or steering an actively working task.",
"required": false,
"params": { "interruptible": true, "steerable": true }
}
]
}
}
```

An agent MAY advertise either mode independently (`steerable` is the stronger capability — steer while working — and does not imply `interruptible`, and vice-versa).

### Activation

The client activates the extension per-request with the header:

```
A2A-Extensions: https://a2a-protocol.org/extensions/steering/v1
```

The agent SHOULD echo the activated extension URI on the response. A steering signal sent **without** activation, or for a mode the agent did not advertise, SHOULD be rejected with `UnsupportedOperationError` (-32004).

### Semantics

**Steer (steerable).** The client sends a normal `message/send` targeting the task's `taskId` while it is `WORKING`, with steering metadata on the message:

```json
{ "metadata": { "https://a2a-protocol.org/extensions/steering/v1/control": "steer" } }
```

The agent incorporates the message into the in-flight work and remains `WORKING`. The agent MUST respond with a `Task` object that includes the user message into `history` to acknowledge.

**Interrupt (interruptible).** The client sends a message with:

```json
{ "metadata": { "https://a2a-protocol.org/extensions/steering/v1/control": "interrupt" } }
```

The agent ceases the current work and transitions the task to an **interrupted substate**: `TaskState = INPUT_REQUIRED` with `Task` metadata `{"https://a2a-protocol.org/extensions/steering/v1/interrupted": true}` (distinguishing a *client-initiated* interrupt from an agent-initiated elicitation).

**State modeling note.** Extensions cannot add `TaskState` enum values, so "interrupted" is modeled as a metadata substate of the existing `INPUT_REQUIRED` "interrupted state." If this proves broadly useful, a dedicated non-terminal `TASK_STATE_INTERRUPTED` could be proposed as a *core* change later — out of scope for the extension.

**Errors.** `UnsupportedOperationError` (-32004) when: a steering signal is sent without activation; for an unadvertised mode; or (for `interrupt`) when the target task is not in a state the agent can interrupt.

### Relationship to prior work

- #1992 Gap 1 (#1022 guidable / #1027 restrict) — this is the opt-in realization of #1022's intent.
- #1276 / #1277 — core PauseTask/ResumeTask RPCs, closed/unmerged; re-cast here as an opt-in extension (metadata + existing `message/send`, no new core RPC required).
- #1377 — capability-declaration contract; steering's card declaration should follow whatever that lands on.
- #2061 — adjacent multi-turn proposal; note it treats `INPUT_REQUIRED` as terminal, which conflicts with the proto (it's a non-terminal "interrupted state") and with this extension's use of it.

### Open questions

- Should `interrupt` and `steer` be one extension with two param flags (as above) or two separate extension URIs?
- Control signal via `Message.metadata` (as above) vs a new RPC method (extensions may add RPCs, gated by the same auth). Metadata keeps it on the existing `message/send` path; an RPC is more explicit. (Leaning: metadata rather than a core interrupt operation but it does mean that only one PART should be sent for that particular item.)
- Does `steer` need an ack event for clients that can't otherwise tell the message landed? I think yes.
- `referenceMessageId` (message-level reference) would help tie a steer/interrupt to the message it reacts to — currently only `reference_task_ids` exists (raised separately on #1992).

---

## References
- #1992 (multi-turn epic); #1022 (guidable, closed); #1027 (restrict to INPUT_REQUIRED, closed, rebutted by pstephengoogle); #1276/#1277 (PauseTask/Resume, closed); #1377 (capability declaration, open); #2061 (multi-turn, open).
- `docs/topics/extensions.md`, `docs/topics/extension-and-binding-governance.md` (proposal → sponsor → experimental-ext-* repo → TSC vote to graduate).
- `specification/a2a.proto`: `AgentExtension {uri, description, required, params}`; `TaskState` (9 values, no PAUSED/INTERRUPTED); `Message.reference_task_ids` (no message-level ref).
- `docs/specification.md`: `A2A-Extensions` header (§14.2.2); `contextId` "SHOULD NOT unless" (line ~594); `UnsupportedOperationError` = -32004.

Guía de contribución

Abrir la guía de contribución

Evaluación

Este issue todavía no se ha evaluado.

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.