Decision 007: Conversations park in INPUT_REQUIRED with a metadata sub-state, a control envelope completes them, and commands are a hook-extensible registry
- Status: proposed (accepted when the issue-25 pull request is approved)
- Date: 2026-10-12
- Deciders: @MadaraUchiha-314 (approver), the-loop (proposer)
- Work item: issue #25
Context
Before issue #25, a reply without a tool call completed the task, and A2A forbids restarting a terminal task, so every exchange became a new task. The ticket asked for a task that stays open after a reply, explicit Complete and Cancel controls in both renderers, and in-conversation controls (/model, /effort, /sub-task, /status) that code and plugins can extend. A2A constrains the shape of the answer:
- Its
TaskStateenum is closed. Extensions "should use existing enum values and annotate additional semantic meaning in themetadatafield", and the protocol's docs give task sub-states as the example (Extensions). input-requiredis the interrupted state an agent uses when it needs the client.TaskStatushas no metadata field;Task.metadataand the status update's metadata do.- A2A has
CancelTaskbut no verb to complete a task.
Decision
- A reply parks the task. A task a participant's message started (lifecycle
conversation) ends each turn inTASK_STATE_INPUT_REQUIRED. Tasks the agent delegates (spawn_subtask, participant tasks) keep completing on their own, so a parent can go on. - The reason is a sub-state in metadata, declared by an extension. The status extension writes
sub_state(awaiting-next-messageorawaiting-answer) intoTask.metadataand the status update's metadata, whether or not a client activates it. The value is an open string; an unknown value means plainINPUT_REQUIRED. LeavingINPUT_REQUIREDremoves it. - Completion is a message, not a new verb. A participant completes a task with a message whose one data part is the existing event envelope with
{"kind": "control", "action": "complete"}. The workflow's update validator checks the sender's role before anything is interrupted; the control then cuts a running turn and runsdecide_completionwithrequested_by, so the unresolved-sub-task rule and its hook override still apply. Cancel stays A2A'sCancelTask. - Commands are a programmatic registry run through a hook operation. A message that is exactly
/<name> <arguments>for a registered name runs on the harness, never the model; anything else, an unregistered/nameincluded, is an ordinary message. Each command's body is aninexecutor oncommand.invoked, filtered to its own name. Code registers one withCommandRegistry.register, a plugin withcommands.jsonand a command-ownedhooks.jsonentry (local or remote), and the four built-ins come the same way at the default body priority, with a built-inprerole policy. The registry is frozen before the worker starts and listed on the card by the commands extension.
Consequences
- An A2A client that waited for
COMPLETEDto know a reply was done must now treat any final status update,INPUT_REQUIREDincluded, as the end of a turn. The demo, the e2e tests and both renderers were changed to match. - An extension-unaware client still works: it sees the standard state and can complete only by sending the envelope or cancel with
CancelTask. - Commands extend like every other operation: an operator can tighten the role policy, replace a built-in body, or serve a command from a remote executor without code in the harness. Policy hooks on
request.receiveddo not see commands or controls;command.invokedis their policy point. - A participant can now change the model and effort (cost) and create tasks. The selectable-models allowlist, the watcher refusal and the existing rate limit bound it.
- A parked conversation keeps its workflow open until a participant completes or cancels it.
Alternatives considered
- A new
TaskStatevalue (e.g.awaiting-next-message). Rejected: A2A extensions cannot add enum values, and every client would break on an unknown state. - Complete the task after each reply and continue in a new task with the same
contextId. Rejected: it is what the ticket complained about; the history, plan and sub-tasks would be split across tasks. - A custom JSON-RPC method or REST route for completion. Rejected: no A2A client can call it without custom transport code; a message works over every binding and the official SDKs as they are.
- Agent-initiated completion through an intrinsic tool. Left out of scope (open question 3 of the requirements); only a participant ends a conversation.
- Commands resolved in each renderer, or defined in the configuration file. Rejected: renderer-side commands would differ by client, and configuration-defined commands could not carry a body. Resolving on the harness gives every A2A client the same behaviour from the same text.
- A command registry separate from the hook system. Rejected: plugins, remote executors, priorities and overrides would have to be built a second time.