agentscope-ai / agentscope-ai/agentscope-java

[Bug]:ASKING 状态会被持久化到状态存储中

未關閉
#3,135 1 則留言 0 個 reaction 已指派 0 人 在 GitHub 檢視
area/core/agent area/harness bug
主要語言
Java
星號
5.6k
分支
1.4k
平均合併
3 天 23 小時
30 天內合併 PR
77

描述

[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).

貢獻指南

開啟貢獻指南

研究方向

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.

由索引模型根據 Issue 內容生成。

評估

技術堆疊
java, mysql
領域
ai, backend, database
Issue 類型
缺陷
難度
4/5
預估耗時
3-5 天
活躍度
活躍
描述清晰度
基本清楚
新手友好度
45/100

把新 issue 寄到你的電子郵件信箱

精選適合新手參與的 GitHub issue 摘要。