agentscope-ai / agentscope-ai/agentscope-java
[Feature]: Built-in ask_user tool - model-initiated questions to the user (HITL ask direction)
- Lingua principale
- Java
- Stelle
- 5.6k
- Fork
- 1.3k
- Merge medio
- 4g 12h
- PR unite (30g)
- 77
Descrizione
## Feature: Built-in `ask_user` tool — let the model proactively ask the user structured questions (HITL "ask" direction)
### Problem
AgentScope's HITL is currently **one-directional**: the *user* decides whether a *tool* may run (permission ALLOW/DENY/ASK, `RequireUserConfirmEvent`, `GenerateReason.PERMISSION_ASKING`). There is no way for the **model** to proactively request *information* from the user during a run.
This is a standard capability in current coding-agent products:
| Product | Mechanism |
|---|---|
| OpenAI Codex CLI | `request_user_input` (id/header/question/isOther/isSecret/options, isBlocking + autoResolutionMs) |
| Claude Code / Agent SDK | `AskUserQuestion` tool (questions + choices, "type your own answer if none fit") |
| Cline | AskUserQuestion TUI (options + trailing free-text input row) |
| DeepSeek Harness | `ask_user_question` (questions/options/multi_select + custom free text) |
Open issues already asking for the "give new information to the agent" direction:
- agentscope-ai/agentscope#1120 — "[human in loop] how to interrupt the agent flow and give a confirm/**new information** to the agent to continue the flow"
- agentscope-ai/agentscope#926 — "[Feature]: human in loop wanted"
- agentscope-ai/agentscope-java#2168 — "[Bug]: Fail to continue Human-in-the-Loop"
### Proposed design
A framework-first-class, opt-in capability:
1. **New permission decision signal** — `PermissionBehavior.ASK_USER` + `PermissionDecision.askUser(...)`. It is produced only by a tool's own `checkPermissions()` (i.e. the bypass-immune tool-check phase), so it interrupts in **every** `PermissionMode` (incl. `BYPASS`), and is not registerable as a rule.
2. **Built-in `AskUserTool`** (registered by `HarnessAgent.Builder.enableAskUser()`) exposing a JSON-schema'd `ask_user` tool: `questions[] {id, header, question, type: single|multiple|free|secret, options[], required}`, batch questions in one tool call, `required` skip support.
3. **Distinct interrupt semantics** — `GenerateReason.ASK_USER_ASKING` (parallel to `PERMISSION_ASKING`), new `RequireUserAskEvent` (parallel to `RequireUserConfirmEvent`, carries the pending `ToolUseBlock`s with the questions). The tool is **never executed**; the framework pauses, the caller renders the questions, then resumes with answers.
4. **First-class resume** — new `AskUserResult` + `Msg.METADATA_ASK_USER_RESULTS` (parallel to `ConfirmResult` / `METADATA_CONFIRM_RESULTS`); the framework formats the answers into the `ask_user` tool result itself, so the next reasoning iteration reads the user's answers without re-executing the tool. Streaming gets `UserAskResultEvent` for correlation.
### Scope
- `agentscope-core`: decision signal, gate routing, new event(s), `GenerateReason`, resume payload handling (parallel to the existing `PERMISSION_ASKING` machinery).
- `agentscope-harness`: `AskUserTool` + `enableAskUser()` builder switch (opt-in, default off).
- Documentation + runnable example + tests (incl. BYPASS-mode interrupt, multi-question batch, skip semantics, resume-does-not-execute).
### Non-goals (for this PR)
- No AG-UI / protocol bridging (that's app-layer work).
- No secret masking policy, prompt-segment engineering, or question frequency limits — these are host-application concerns.
### API sketch
```java
HarnessAgent agent = HarnessAgent.builder()
.model(model)
.enableAskUser() // registers ask_user into the toolkit
.build();
```
```java
// call returns Msg with GenerateReason.ASK_USER_ASKING;
// subsequent agent.call(List.of(resumeMsg)) with
// Msg.METADATA_ASK_USER_RESULTS = List.of(new AskUserResult(toolCallId, answers))
// continues the run; the ask_user call is not executed.
```
Guida per i contributori
Apri la guida per i contributori
Valutazione
Questa issue non è ancora stata valutata.