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

> Learn how GenericAgent's ask_user tool integrates human-in-the-loop confirmation. It pauses execution for explicit validation, ensuring task accuracy before proceeding.

- Repository: [LJQ/GenericAgent](https://github.com/lsdefine/GenericAgent)
- Tags: how-to-guide
- Published: 2026-04-16

---

**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`](https://github.com/lsdefine/GenericAgent/blob/main/assets/tools_schema.json), the definition exposes two parameters: a required **question** string and an optional **candidates** array for quick-select options.

```json
{
  "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`](https://github.com/lsdefine/GenericAgent/blob/main/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

```python
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`](https://github.com/lsdefine/GenericAgent/blob/main/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`**

```python
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`](https://github.com/lsdefine/GenericAgent/blob/main/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`](https://github.com/lsdefine/GenericAgent/blob/main/assets/tools_schema.json) with parameters for questions and candidate answers, enabling LLM discovery.
- **Interrupt Payload**: The `ask_user` helper in [`ga.py`](https://github.com/lsdefine/GenericAgent/blob/main/ga.py) generates a structured dictionary with `status: "INTERRUPT"` and `intent: "HUMAN_INTERVENTION"`.
- **Execution Suspension**: The `do_ask_user` handler in [`ga.py`](https://github.com/lsdefine/GenericAgent/blob/main/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`](https://github.com/lsdefine/GenericAgent/blob/main/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`](https://github.com/lsdefine/GenericAgent/blob/main/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`](https://github.com/lsdefine/GenericAgent/blob/main/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`](https://github.com/lsdefine/GenericAgent/blob/main/assets/tools_schema.json) maps to the `do_ask_user` Python method for execution when the LLM generates a corresponding tool call.