How the ask_user Tool Implements Human-In-The-Loop Confirmation in GenericAgent

The ask_user tool pauses GenericAgent's autonomous execution by returning an interrupt payload with status: "INTERRUPT" and should_exit=True, enabling explicit human validation before task continuation.

The ask_user tool serves as the critical bridge between autonomous agent operation and human oversight in the GenericAgent framework. When the Large Language Model (LLM) encounters ambiguous decisions or requires explicit approval, this mechanism triggers a structured pause that requests operator input before proceeding. According to the GenericAgent source code, the implementation spans schema definitions, interrupt payload generation, and execution flow control across three core files.

Tool Schema Definition in assets/tools_schema.json

The LLM discovers the ask_user capability through a formal function declaration in the tool schema. Located at lines 62-66 in assets/tools_schema.json, the definition exposes two parameters: a required question string and an optional candidates array for quick-select options.

{
  "name": "ask_user",
  "description": "Interrupt task to ask user when needing decisions, extra info, or facing unresolvable blockers",
  "parameters": {
    "type": "object",
    "properties": {
      "question": {"type": "string", "description": "Question for the user"},
      "candidates": {
        "type": "array",
        "items": {"type": "string"},
        "description": "Optional quick-select choices for the user"
      }
    }
  }
}

This schema registration allows the model to invoke human intervention as a first-class operation rather than an error state.

Interrupt Payload Generation in ga.py

The helper function ask_user in ga.py (lines 93-97) constructs the standardized interrupt object that orchestration layers recognize. The function returns a dictionary containing three critical fields:

  • status: Set to "INTERRUPT" to signal execution suspension
  • intent: Set to "HUMAN_INTERVENTION" to categorize the interruption type
  • data: Contains the question text and candidate options
def ask_user(question: str, candidates: list = None):
    """question: 向用户提出的问题。candidates: 可选的候选项列表。需要保证 should_exit 为 True"""
    return {
        "status": "INTERRUPT",
        "intent": "HUMAN_INTERVENTION",
        "data": {"question": question, "candidates": candidates or []}
    }

This payload structure ensures that downstream safety interceptors and plan-mode checks can distinguish legitimate confirmation requests from unexpected errors.

Execution Pause via do_ask_user Handler

Within the BaseHandler subclass in ga.py, the do_ask_user method (lines 320-327) implements the actual suspension logic. When the LLM invokes the ask_user tool, the handler executes the following sequence:

  1. Extracts the question and candidates from the tool arguments
  2. Calls the ask_user helper to generate the interrupt payload
  3. Yields a status message ("Waiting for your answer ...") to the interface
  4. Returns a StepOutcome with should_exit=True
def do_ask_user(self, args, response):
    question   = args.get("question", "请提供输入:")
    candidates = args.get("candidates", [])
    result = ask_user(question, candidates)   # ← creates interrupt payload

    yield f"Waiting for your answer ...\n"
    return StepOutcome(result, next_prompt="", should_exit=True)

The should_exit=True flag is the critical mechanism that halts the main agent loop, preventing any further autonomous steps until the human responds.

Workflow Integration and Resume Logic

The main execution loop in agent_loop.py monitors the StepOutcome returned by each tool handler. Upon detecting should_exit=True, the loop terminates the current task session and propagates the interrupt payload to the UI layer—whether Streamlit, WeChat, or DingTalk front-ends.

The front-end renders the question and optional candidates as an interactive prompt. Once the user provides input, the response injects back into the workflow as a standard message, and the agent resumes execution using the human's answer as context for subsequent tool calls.

Summary

  • Schema Declaration: The ask_user tool is defined in assets/tools_schema.json with parameters for questions and candidate answers, enabling LLM discovery.
  • Interrupt Payload: The ask_user helper in ga.py generates a structured dictionary with status: "INTERRUPT" and intent: "HUMAN_INTERVENTION".
  • Execution Suspension: The do_ask_user handler in ga.py returns StepOutcome with should_exit=True, forcing the agent loop to pause.
  • Human Validation: UI front-ends display the pending question and resume the workflow only after receiving explicit user input, completing the human-in-the-loop validation cycle.

Frequently Asked Questions

How does GenericAgent distinguish between an error and a legitimate human confirmation request?

GenericAgent uses the intent field within the interrupt payload to differentiate purposeful human-in-the-loop confirmation from failures. The ask_user tool explicitly sets intent: "HUMAN_INTERVENTION" and status: "INTERRUPT", allowing safety interceptors and the main execution loop in agent_loop.py to recognize this as a controlled pause rather than an exception.

What happens if the user never responds to an ask_user prompt?

The current implementation in ga.py sets should_exit=True indefinitely until the front-end injects a user response back into the conversation. The agent remains in a suspended state, with no timeout or automatic fallback implemented in the core BaseHandler logic. The specific behavior for orphaned prompts depends on the UI implementation (Streamlit, WeChat, etc.) handling the session persistence.

Can the LLM specify predefined options for the user to select?

Yes. The candidates parameter in the tool schema accepts an array of strings that the do_ask_user handler passes through to the interrupt payload. When present, front-end interfaces can render these as quick-select buttons or dropdown menus rather than free-text input fields, streamlining the confirmation process for binary or limited-choice decisions.

Where is the ask_user tool handler actually registered within the codebase?

The do_ask_user method is implemented within the BaseHandler subclass located in ga.py. This handler connects to the main agent loop through the standard tool dispatch mechanism, where the ask_user function name declared in assets/tools_schema.json maps to the do_ask_user Python method for execution when the LLM generates a corresponding tool call.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →