> ## Documentation Index
> Fetch the complete documentation index at: https://docs.agentscope.io/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> For AgentScope Python, use https://docs.agentscope.io/stable/en/index for new projects. For existing projects, check the installed agentscope version and use matching versioned documentation.
> The /latest/ alias points to the newest documentation and may move ahead of /stable/ when a development version exists. Do not mix AgentScope 1.x and 2.x APIs.
> State the AgentScope version when providing installation commands or code examples. ReMe uses its own continuously updated /reme/latest/ documentation.

# Human-in-the-Loop

> Pause for user confirmation or external execution, then resume with result events

The agent pauses execution and emits special events when it encounters two situations: a tool call that requires **user confirmation** (permission system returns ASK), or a tool marked as **external execution** (the result must come from outside the agent). In both cases, you resume the agent by passing a result event back via `reply`.

While paused, `reply` returns a waiting notice message whose `finished_reason` is `None`: the reply is parked, not finished, and no remaining tool calls are executed until the outside result arrives.

## User Confirmation

When the permission system determines a tool call needs user approval, the agent emits a `RequireUserConfirmEvent` and pauses.

**1. Receive the RequireUserConfirmEvent**

Use `reply_stream` to detect the pause. The event has the following structure:

**reply\_id** `str` *required*

ID of the current reply, used to resume the agent.

**tool\_calls** `list[ToolCallBlock]` *required*

Tool calls pending user confirmation. Each `ToolCallBlock` contains:

**id** `str`

Unique identifier for this tool call.

**name** `str`

The tool name (e.g. `"Bash"`, `"Write"`).

**input** `str`

JSON-encoded input parameters.

**suggested\_rules** `list[PermissionRule]`

Auto-generated permission rules the user can accept to allow similar future calls.

```python theme={null}
from agentscope.event import RequireUserConfirmEvent

async for event in agent.reply_stream(msg):
    if isinstance(event, RequireUserConfirmEvent):
        for tc in event.tool_calls:
            print(f"Tool: {tc.name}, Input: {tc.input}")
            print(f"Suggested rules: {tc.suggested_rules}")
```

**2. Build the confirmation result**

For each pending tool call, create a `ConfirmResult` indicating whether to allow or deny it. You can also modify the tool call input or accept suggested permission rules:

```python theme={null}
from agentscope.event import ConfirmResult, UserConfirmResultEvent

confirm_results = []
for tc in event.tool_calls:
    confirm_results.append(ConfirmResult(
        confirmed=True,           # or False to deny
        tool_call=tc,             # pass back (optionally modified)
        rules=tc.suggested_rules, # accept rules for future auto-allow
    ))
```

**3. Resume the agent**

Pass the `UserConfirmResultEvent` back to `reply` or `reply_stream`:

```python theme={null}
confirm_event = UserConfirmResultEvent(
    reply_id=event.reply_id,
    confirm_results=confirm_results,
)
result = await agent.reply(confirm_event)
```

* **Confirmed** tool calls execute immediately and the agent continues reasoning
* **Denied** tool calls produce an error result visible to the LLM, which may retry with a different approach
* **Accepted rules** are persisted to the permission engine, so matching future calls are auto-allowed without prompting again

## External Tool Execution

When the agent calls a tool with `is_external_tool = True`, it emits a `RequireExternalExecutionEvent` and pauses. The tool's logic runs outside the agent, typically by a human operator or an external system.

**1. Receive the RequireExternalExecutionEvent**

The event has the following structure:

**reply\_id** `str` *required*

ID of the current reply, used to resume the agent.

**tool\_calls** `list[ToolCallBlock]` *required*

Tool calls to be executed externally. Each `ToolCallBlock` contains:

**id** `str`

Unique identifier for this tool call.

**name** `str`

The external tool name.

**input** `str`

JSON-encoded input parameters.

```python theme={null}
from agentscope.event import RequireExternalExecutionEvent

async for event in agent.reply_stream(msg):
    if isinstance(event, RequireExternalExecutionEvent):
        for tc in event.tool_calls:
            print(f"Execute externally: {tc.name}({tc.input})")
```

**2. Execute externally and build results**

Run the operation outside the agent and wrap the results as `ToolResultBlock` objects:

```python theme={null}
from agentscope.message import ToolResultBlock, TextBlock, ToolResultState
from agentscope.event import ExternalExecutionResultEvent

execution_results = []
for tc in event.tool_calls:
    # Perform the actual operation (API call, human action, etc.)
    output = await run_external_operation(tc.name, tc.input)

    execution_results.append(ToolResultBlock(
        id=tc.id,
        name=tc.name,
        output=[TextBlock(text=output)],
        state=ToolResultState.SUCCESS,
    ))
```

**3. Resume the agent**

Pass the `ExternalExecutionResultEvent` back to resume:

```python theme={null}
external_event = ExternalExecutionResultEvent(
    reply_id=event.reply_id,
    execution_results=execution_results,
)
result = await agent.reply(external_event)
```

The results are injected into the agent's context and reasoning continues from where it left off.

### The Built-in AskUser

`AskUser` is the framework's own external execution tool, there for the moments an agent needs a decision from the person it is working for. One call carries up to four questions, each with two to four options — see [Ask the User](https://docs.agentscope.io/en/versions/2.0.12dev/building-blocks/tool/python-tool.md#ask-the-user) for how to register it.

`tc.input` in the `RequireExternalExecutionEvent` has the shape a renderer works from:

| Field | Meaning |
| - | - |
| `questions[].question` | The question itself |
| `questions[].header` | A label of at most 12 characters, usually rendered as a chip |
| `questions[].context` | What the user must look at while answering — a draft, an error, two designs being compared — shown above the options |
| `questions[].multi_select` | Whether more than one option may be chosen |
| `questions[].options[].label` / `description` | The option's text, and what choosing it means |
| `questions[].options[].preview` | Optional side-by-side content for a single-select question, such as a code snippet or a mockup |

A renderer is expected to offer an "Other" free-text answer beside the options, because the tool has already promised the user one.

On the way back, `output` is written for the model to read, while the same answers go into `metadata` shaped by `AskUserMetadata`, so a program can branch on the choice:

```python Returning an AskUser answer theme={null}
from agentscope.message import ToolResultBlock, TextBlock, ToolResultState
from agentscope.tool import AskUserAnswer, AskUserMetadata

result = ToolResultBlock(
    id=tc.id,
    name=tc.name,
    output=[TextBlock(text="Auth method: OAuth 2.0 (Recommended)")],
    metadata=AskUserMetadata(
        answers=[
            AskUserAnswer(
                question="Which auth method should we integrate?",
                selected=["OAuth 2.0 (Recommended)"],  # labels the user chose
                other=None,                            # free text, when they typed instead
            ),
        ],
    ).model_dump(),
    state=ToolResultState.SUCCESS,
)
```

> [!NOTE]
>
> If multiple tool calls require outside interaction and only partial confirmation or execution results are passed back, the agent won't re-send the requiring events for the unconfirmed or unexecuted tool calls.

> [!TIP]
>
> Confirmations are de-duplicated inside one concurrent batch: when several tool calls run in parallel and a later call is already covered by the rule suggested by an earlier confirmation in the same batch, it is not surfaced a second time. Safety ASKs are never de-duplicated this way.

> [!TIP]
>
> Use `reply_stream` when building interactive UIs: it lets you detect pause events in real time and prompt the user immediately. Use `reply` when you have pre-built automation that handles events programmatically.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.