Skip to content
Feature

Human-in-the-Loop

When your agent needs a human decision, it pauses. The desktop app shows the task in an approval queue. You review, approve or reject, and the agent resumes.

Overview

The input_required state is the bridge between autonomous agents and human judgment. When an agent encounters a decision it cannot or should not make on its own, it returns input_required instead of completed. The task pauses, the human is notified, and the workflow waits until a person weighs in.

This is useful for high-stakes operations like deploying to production, approving financial transactions, or reviewing breaking changes. The agent does the analysis, surfaces what matters, and lets a human make the final call.

|

How It Works

The approval flow follows the standard A2A task lifecycle, with input_required acting as a pause point between the agent and the human operator.

1

Caller sends a task

An external client sends a task to your agent via A2A SendMessage. The task enters the submitted state.

2

Desktop app dispatches to your agent

The desktop app picks up the task, runs your agent's configured command, and pipes the task JSON to stdin. See the Building Agents guide for the full input format.

3

Agent returns input_required

Instead of completing the task, the agent returns a response with state input_required and an explanation of what it needs.

stdout
{
  "state": "input_required",
  "text": "Found 3 breaking API changes. Should I proceed with the migration?"
}
4

Task appears in the approval queue

The desktop app updates the task status and shows a badge on the approvals tab. A macOS notification alerts the operator that a task is waiting for review.

5

Human reviews

The operator opens the task and sees the full conversation history, including any artifacts the agent produced during its analysis.

6

Human responds

The operator has three options:

  • Approve - the task moves to working and the agent is re-invoked to continue
  • Send Response - the operator adds context or asks a follow-up question, and the agent is re-invoked with the new message
  • Reject - the task moves to rejected and the caller is notified
7

Caller is notified

The caller learns the outcome through GetTask polling or push notifications. The task includes the final status and any artifacts or messages produced during the approval flow.


Writing an Approval Agent

Any agent can request human approval by returning input_required instead of completed. The key is deciding when to pause and what to show the human.

Bash example

A simple script that reads the task, performs analysis, and requests approval before proceeding.

agents/reviewer.sh
#!/bin/bash
# Read the task from stdin
INPUT=$(cat)
TASK_TEXT=$(echo "$INPUT" | jq -r '.message.parts[0].text')

# Do some analysis...
RESULT="Found 3 breaking API changes affecting 12 consumers."

# Request human approval
cat <<EOF
{
  "state": "input_required",
  "text": "$RESULT\n\nShould I proceed with the migration?"
}
EOF

Claude-powered example

Use Claude Code as your agent runtime. The prompt instructs it to conditionally return input_required for dangerous operations and completed for safe ones.

claude -p "Analyze this request. If it involves breaking changes or destructive operations, respond with JSON: {\"state\": \"input_required\", \"text\": \"your explanation\"}. Otherwise respond with JSON: {\"state\": \"completed\", \"text\": \"your analysis\"}." --input-stdin

What the Human Sees

When a task enters input_required, the desktop app shows the operator everything they need to make a decision:

  • Message history - the full conversation rendered as chat bubbles, including the original request and the agent's analysis
  • Artifacts - any files, diffs, or structured data the agent produced, rendered inline
  • Response form - three action buttons (Approve, Send Response, Reject) with an optional text field for adding context

Multi-Turn Conversations

When the operator chooses Send Response, the agent is re-invoked with the same contextId. The new message from the human is appended to the conversation, and the agent receives the full context on stdin.

The agent can return input_required again if it needs further clarification. This creates a back-and-forth conversation between the agent and the human, with each turn adding to the shared context. The cycle continues until the agent returns completed, failed, or the human chooses to reject.