feat(harness): built-in ask_user tool — model-initiated questions to the user (HITL ask direction) - #2865
feat(harness): built-in ask_user tool — model-initiated questions to the user (HITL ask direction)#2865Letter2025 wants to merge 7 commits into
Conversation
Adds the HITL ask direction: a tool whose checkPermissions() returns PermissionDecision.askUser(...) pauses the run with a new GenerateReason.ASK_USER_ASKING (in every permission mode, including BYPASS) and is never executed; callers render the questions and resume with AskUserResult(s) under Msg.METADATA_ASK_USER_RESULTS. Core: - PermissionBehavior.ASK_USER + PermissionDecision.askUser() - PermissionEngine: ASK_USER short-circuits the bypass-immune tool self-check - ReActAgent: gate routes ASK_USER to a dedicated pending list; actingStream emits RequireUserAskEvent + RequestStopEvent(ASK_USER_ASKING); acting() returns the pausing assistant Msg unchanged; doCallInner accepts and formats AskUserResults into the ask_user tool result on resume - New events: RequireUserAskEvent / UserAskResultEvent (replyId-correlated) - GenerateReason.ASK_USER_ASKING Harness: - Built-in AskUserTool (ask_user, JSON-schema'd questions[]) - HarnessAgent.Builder.enableAskUser() (opt-in, off by default) Docs + examples + tests: - v1 hitl.md / v2 permission-system.md (en+zh) document the ask direction - AskUserHITLExample runnable example - ReActAgentAskUserTest (core) + HarnessAgentAskUserTest (harness) cover pause under BYPASS, batch questions, skip semantics, resume without executing the tool Closes agentscope-ai#2860
… new events - AskUserResult gains @JsonCreator/@JsonProperty so UserAskResultEvent decodes through the RemoteEventCodec AGENT_EVENT passthrough - RemoteEventCodecPassthroughTest covers REQUIRE_USER_ASK / USER_ASK_RESULT - PermissionBehaviorTest expects five behaviors (ASK_USER added)
…ests Default memory hooks schedule background flush/maintenance after each call; disabling them keeps the AskUser harness tests deterministic and free of post-call background threads in the shared surefire JVM.
…7153) HarnessAgentSubagentStreamTest.call_localSubagent_returnsReplyWithoutStreaming fails on the CI runner with 'Failed to close extension context' at @tempdir teardown. The same failure occurred on main's guava-bump CI (no relation to this PR); the test passes locally. No code change in this commit.
|
Non-blocking note on CI: the remaining build failures are a pre-existing harness test flake, not caused by this PR. The failing jobs report
The same failure pattern hit main's own CI on an unrelated commit (guava bump, run 32981277153 — The deterministic failures caused by this PR were fixed and verified:
Happy to rebase/adapt if maintainers prefer a different approach. |
|
Hi @mikemikimike @helloworldtang @wzq-xzwj — could someone rerun the failed CI jobs on this PR? The failure is a pre-existing harness
This branch already verified locally: full core + harness suites pass (only known local-only errors: |
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
- AskUserEventsTest: full-arg constructors, getters, Jackson round-trips and null-safe payloads for RequireUserAskEvent / UserAskResultEvent; @JsonCreator round-trip + edge branches of AskUserResult (empty list, selected-only, text-only, skipped=false, scalar values, multi-entry) - ReActAgentAskUserTest: resume validation error branches (duplicate answer id, unknown/stale tool call id) - HarnessAgentAskUserTest: AskUserTool.callAsync fallback covers the interactive placeholder; AskUserTool fallback text now carries the real metadata key value
|
This PR has conflicts with the git fetch origin
git checkout feat/ask-user-tool
git rebase origin/main
# resolve conflicts, then:
git push --force-with-leaseThis is a one-time reminder. Feel free to @mention me for a re-review after conflicts are resolved. Automated notification by github-manager-bot |
Summary
Adds the HITL "ask" direction to AgentScope: the model can proactively ask the user structured questions during a run, complementing the existing user-confirms-tool flow.
A tool whose
checkPermissions()returnsPermissionDecision.askUser(...)pauses the run with the newGenerateReason.ASK_USER_ASKING— in everyPermissionMode(includingBYPASS, since it rides the bypass-immune tool self-check) — and is never executed. Callers render the questions and resume withAskUserResult(s) underMsg.METADATA_ASK_USER_RESULTS; the framework formats the answers into theask_usertool result so the model reads them on the next reasoning iteration.The harness ships a built-in
ask_usertool (JSON-schema'dquestions[]: id/header/question/type single|multiple|free|secret/options/required), enabled viaHarnessAgent.Builder.enableAskUser()(opt-in, off by default — no behavior change for existing builders).Motivation
HITL today only answers "may this tool run?" (
PERMISSION_ASKING+RequireUserConfirmEvent). There is no way for the model to request information from the user. This is a standard capability in current coding agents (OpenAI Codexrequest_user_input, Claude Code / Agent SDKAskUserQuestion, Cline, DeepSeek Harnessask_user_question) and has been requested in:DeepSeekFormatterconvertsystemmessages tousermessages? #2168 — "Fail to continue Human-in-the-Loop"Core changes (
agentscope-core)PermissionBehaviorASK_USERconstant (not registerable as a rule; produced only by a tool'scheckPermissions())PermissionDecisionaskUser(String)factoryPermissionEngineASK_USERshort-circuits immediately in the bypass-immune tool-sef-check phase (not swallowed by rule/BYPASS handling)ReActAgentASK_USERto a dedicated pending list;actingStreamemitsRequireUserAskEvent+RequestStopEvent(ASK_USER_ASKING);acting()returns the pausing assistantMsg; resumes with answers viavalidateAndAcceptAskUserResultswhich formats them into the tool result without executing itRequireUserAskEvent/UserAskResultEvent(replyId-correlated, parallel to the confirm flow; registered inAgentEventsubtypes)GenerateReasonASK_USER_ASKINGHarness changes (
agentscope-harness)AskUserToolinio.agentscope.harness.agent.tool(nameask_user,checkPermissions()→askUser(...))HarnessAgent.Builder.enableAskUser()(no-arg + boolean overload), registers the tool atbuild()Docs & examples
docs/v1/{en,zh}/docs/task/hitl.md— new "Ask the user for input" sectiondocs/v2/{en,zh}/docs/building-blocks/permission-system.md— new "Ask the user (model-initiated questions)" section (incl.DONT_ASKnote)agentscope-examples/.../hitl/AskUserHITLExample.java— runnable interactive exampleTests
ReActAgentAskUserTest(core): pause withASK_USER_ASKING, tool not executed, pause underBYPASS, resume → answers visible in next reasoning, resume-without-answers guidance error, streamingRequireUserAskEvent/UserAskResultEventcorrelation, answer formatting (scalar/list/selected+text/skipped)HarnessAgentAskUserTest(harness):enableAskUser()registers the tool and pauses; BYPASS still pauses; full resume round-trip (answer reaches the model,ask_usernot executed); tool self-descriptionCompatibility
Opt-in feature: existing
HarnessAgent/ReActAgentbuilders are unaffected (tool not registered, no new gate entries,GenerateReasongains one value).ASK_USERis not a rule behavior, so no permission-config surface changes.Closes #2860