[Extension Proposal]: Agent Steering — interruptible & steerable tasks
- 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
Evaluación
Este issue todavía no se ha evaluado.