agentscope-ai / agentscope-ai/agentscope-java
[Bug]:ASKING 状态会被持久化到状态存储中
- Linguagem predominante
- Java
- Estrelas
- 5.6k
- Forks
- 1.4k
- Merge médio
- 3d 23h
- PRs com merge (30d)
- 77
Descrição
[Bug]: plan_exit ASKING state persists across runs when enablePendingToolRecovery + enablePlanMode + PermissionMode.BYPASS are combined
**AgentScope-Java Version**: 2.0.1
**Describe the bug**
When `HarnessAgent` is configured with `enablePlanMode(true)` + `enablePendingToolRecovery(true)` + `PermissionMode.BYPASS` + a persistent `AgentStateStore` (MySQL), a `plan_exit` tool call can remain in `ASKING` state after a run is interrupted (timeout / cancellation / exception). The ASKING state is persisted to the state store. When a **new request** reuses the same `sessionId` **without** carrying `ConfirmResult` metadata, the framework throws a hard error and the new run fails immediately.
This is a follow-up edge case to #2534 / PR #2109. The 2.0.1 fix correctly prevents pending recovery from consuming HITL approvals **when the new input carries ConfirmResult**. However, when the new request is a **normal user message** (no ConfirmResult), the leftover ASKING state from a previous run is neither auto-cleared nor gracefully handled — it blocks the new run entirely.
Agent configuration:
```java
HarnessAgent.builder()
.name("testx-" + threadId)
.enablePendingToolRecovery(true)
.permissionContext(PermissionContextState.builder().mode(PermissionMode.BYPASS).build())
.enablePlanMode(true)
.stateStore(mysqlAgentStateStore) // custom AgentStateStore backed by MySQL
.defaultSessionId(threadId)
// ... other settings
.build();
```
Reproduction flow:
```
Run A (sessionId = T1):
1. Agent starts → enters Plan Mode
2. LLM generates plan → calls plan_exit to start execution
3. plan_exit enters ASKING state (awaiting HITL confirmation)
4. Request is interrupted (timeout / user cancel / exception)
5. AgentState (including ASKING plan_exit) persisted to MySQL
↓
Run B (sessionId = T1, NEW request, normal user message, no ConfirmResult):
1. Agent restores state from MySQL
2. Detects plan_exit in ASKING state
3. New request has NO ConfirmResult in metadata
4. Framework throws hard error — new request FAILS immediately
```
**To Reproduce**
Steps to reproduce the behavior:
1. Configure a `HarnessAgent` with `enablePlanMode(true)` + `enablePendingToolRecovery(true)` + `PermissionMode.BYPASS` + a persistent `AgentStateStore`
2. Send a request that triggers the agent to enter Plan Mode and call `plan_exit`
3. Interrupt the run (cancel / timeout) while `plan_exit` is in ASKING state
4. Send a new request with the same `sessionId` (normal user message, no `ConfirmResult`)
5. See error
**Expected behavior**
Two possible fixes:
1. `plan_exit` should not enter ASKING state under `PermissionMode.BYPASS`. Since BYPASS means "skip all permission checks", framework-internal plan tools (`plan_enter` / `plan_write` / `plan_exit`) should never require HITL confirmation.
2. When a new request arrives without `ConfirmResult` and the restored state contains ASKING tool calls from a previous run, the framework should auto-clear the orphaned ASKING state (or emit a warning event and skip it) instead of throwing a hard error.
**Error messages**
```
Agent is paused for human-in-the-loop confirmation: the following tool call(s) are in ASKING state and need your approval before the agent can continue: [plan_exit (id=call_f178664a2cf04251ae4afd19)]. This call supplied no confirmation, so it cannot proceed. To resume, send a follow-up message that carries a List under the metadata key "agentscope_confirm_results", e.g.: UserMessage.builder() .metadata(Map.of(Msg.METADATA_CONFIRM_RESULTS, List.of(new ConfirmResult(true, toolCall)))) .build(); Tip: capture the ToolUseBlocks from the RequireUserConfirmEvent emitted when the agent paused. If you did NOT expect a pending confirmation here, a previous run most likely paused on one of these tool calls and persisted that state under the same (agentId, sessionId); start a fresh session, clear the persisted state, or use an in-memory state store to begin clean.
```
**Environment**
- AgentScope-Java Version: 2.0.1
- Java Version: 17
- OS: Linux (production) / Windows (development)
**Additional context**
- Related: #2534 (人工介入与PendingToolRecovery不兼容), fixed by PR #2109 in 2.0.1. The 2.0.1 fix handles the case where the new input **carries** `ConfirmResult`. This issue covers the **different** scenario where a normal new request encounters leftover ASKING state with no `ConfirmResult` to provide.
- RC2 release notes mention "Plan Mode 优化 — 改进计划文件持久化与恢复机制,优化 plan_enter / plan_write / plan_exit 工具链的交互体验,增强 HITL 审批流程的稳定性" — this issue suggests there are still edge cases in that interaction.
- All our HarnessAgent-based agents are affected because they all use the same combination of `enablePlanMode(true)` + `enablePendingToolRecovery(true)` + `PermissionMode.BYPASS`.
- Current workaround: manually delete the persisted AgentScope state for the affected sessionId before each new run, or switch to `InMemoryAgentStateStore` (sacrificing session recovery).
Guia de contribuição
Direção de pesquisa
Start with the HarnessAgent configuration and the pending tool recovery path involving plan_exit, PermissionMode.BYPASS, AgentStateStore, and session restoration. Reproduce the interrupted Run A and normal Run B flow with the same sessionId and verify that the new request no longer fails when restored state contains an orphaned ASKING call.
Escrita pelo modelo de indexação a partir do texto da issue.
Avaliação
- Stack de tecnologia
- java, mysql
- Domínio
- ai, backend, database
- Tipo de issue
- Bug
- Dificuldade
- 4/5
- Tempo estimado
- 3-5 dias
- Status de atividade
- Ativa
- Clareza
- Razoavelmente clara
- Facilidade para iniciantes
- 45/100