agentscope-ai / agentscope-ai/agentscope-java

[Bug]:TaskRepository.cancelTask 无法停止正在执行的异步 subagent —— cancelRequested 在 agent 执行循环内没有检查点

未关闭
#2,789 0 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看
bug
主要语言
Java
星标
5.6k
派生
1.3k
平均合并
4 天 12 小时
30 天内合并 PR
77

描述

## AgentScope-Java Version

2.0.0(agentscope-harness 2.0.0)

## Description

主 Agent 被取消后,由 `SubagentsMiddleware` 派生的异步 subagent(background task)不会停止,
会继续调用工具、继续消耗 token,直到自然执行完毕,并把结果推回 inbox 唤醒主 Agent。

`TaskRepository.cancelTask(ctx, sessionId, taskId)` 看起来是为此设计的 API,但对**已经开始执行**
的本地任务实际无效:它只能让任务在「任务边界」被识别为已取消,无法中断正在运行的 agent 循环。

## To Reproduce

1. 构建一个带 `subagentFactory` 的 supervisor agent。
2. 让 supervisor 派生一个异步 subagent(background task 模式),该 subagent 内部会连续调用多轮工具。
3. 等 subagent 真正开始执行(`ws-task-*` 线程上已有工具调用)。
4. 调用 `taskRepository.cancelTask(ctx, sessionId, taskId)`。
5. 观察 subagent 的行为。

## Expected Behavior

`cancelTask` 之后,subagent 应在下一个可中断点(例如下一轮 reasoning 或 tool call 之前)停止执行,
不再发起新的 LLM 请求和工具调用。

## Actual Behavior

subagent 完全不受影响,继续跑完剩余所有轮次,最后正常打出:
[ws-task-307] INFO SubagentsMiddleware - Subagent task task_xxx completed, pushed to inbox and enqueued wakeup: session=xxx

生产环境实测:主 run 于 `11:24:08` 取消,4 个 subagent 在 `11:24:09`、`11:24:10` 继续
`POST_ACTING ... state=SUCCESS`,直到 `11:24:40` 才自然 completed —— 取消后又跑了 32 秒。

## Root Cause

分两处,都可由字节码验证(`javap -c`,agentscope-harness-2.0.0.jar)。

**1)`BackgroundTask.cancel(boolean)` 不中断执行线程**
public boolean cancel(boolean); 0: aload_0 1: iconst_1 2: putfield cancelled:Z 5: aload_0 6: getfield future:Ljava/util/concurrent/CompletableFuture; 9: iload_1 10: invokevirtual java/util/concurrent/CompletableFuture.cancel:(Z)Z 13: ireturn

方法体只有「置 `cancelled` 标志」+「`CompletableFuture.cancel`」两件事。而
`CompletableFuture.cancel(mayInterruptIfRunning)` 按 JDK 契约会忽略 `mayInterruptIfRunning`
(该实现不使用中断来控制处理),因此在 `ExecutorService`(`ws-task-*`)上已经跑起来的
supplier 不会被打断。

**2)`TaskRecord.cancelRequested` 在 agent 执行循环里没有任何检查点**

`cancelTask` 会 `setCancelRequested(true)` 并 `persistRecord` 落盘,这部分是有效的。
但对整个 jar 做全类扫描(对每个 class 执行 `javap -c` 并检索 `isCancelRequested` 调用)后,
读取该标志的只有两个类:

- `io/agentscope/harness/agent/subagent/task/WorkspaceTaskRepository.class`
- `io/agentscope/harness/agent/subagent/task/TaskRecord.class`

`WorkspaceTaskRepository` 内部的检查点位置为:

| 位置 | 时机 |
|---|---|
| `runLocalSupplier` 入口 | supplier 开始**之前** |
| `runLocalSupplier` supplier 返回后 | supplier 结束**之后** |
| `runRemoteTask` 入口 | 远程调用之前 |
| `pollRemoteUntilDone` 轮询循环内 | 远程任务,每次轮询 |
| `updateStatus` | 防止把 CANCELLED 覆盖回去 |

也就是说,本地任务(`LocalTaskRunSpec`)的检查点全在 supplier 的**外面**。supplier 内部是完整的
agent 执行循环(多轮 LLM + 工具调用),一旦进入就再无检查机会。

净效果:
- 排队未启动的任务 —— 入口检查生效,能真正跳过 ✅
- 正在执行的任务 —— 完全无法中断,跑完为止 ❌
- 远程任务(`RemoteTaskRunSpec`)—— 有轮询检查点,情况好于本地 ✅

## 相关 issue

#1577 讨论了同步 subagent(`stream()` 模式)的 cancel 传播断裂,其影响范围表格中把
「异步子 Agent(timeout=0)」标注为「本来就不在链上,不受影响(需 `task_cancel`)」。
本 issue 描述的正是那个被指向 `task_cancel` 的缺口:`cancelTask` 这个 API 存在,
但对已执行的本地任务达不到「停止执行」的语义。

## Suggested Fix

SDK 内部其实已经具备所需的全部机制,缺的只是接线。

`ReActAgent` 已有 per-session 的中断能力:

```java
public void interrupt(String userId, String sessionId, Msg msg)
// → getAgentState(userId, sessionId).interruptControl().trigger(InterruptSource.USER, msg)
而 ReActAgent$CallExecution 内部已有 private Mono checkInterrupted(), 并且在推理/行动循环中有 4 个调用点 —— 这正是「执行循环内的可中断点」。

建议方案:让 cancelTask 触达正在执行的 subagent 实例的 InterruptControl。

具体做法可以是:WorkspaceTaskRepository.putTask 时把该任务对应的 subagent 实例 (或其 AgentState / InterruptControl)与 taskId 一并登记;cancelTask 除现有逻辑外, 额外调用一次 interruptControl().trigger(...)。这样中断粒度就与 checkInterrupted 的检查点密度一致,无需新增检查逻辑。

替代方案:在 runLocalSupplier 内部,把 supplier 的响应式链与 cancelRequested 组合(例如 takeUntilOther / 定期检查后 Mono.error),使标志在 supplier 执行期间也能生效。

关于外部自行绕过的可行性:应用侧无法自己解决。以我们的场景为例,subagent 实例由 subagentFactory 每次 spawn 新建且不进缓存,外部拿不到正在执行的实例引用,因此无法自行调用 interrupt;GracefulShutdownManager 是全局单例、无 session 维度,会波及同进程所有会话。 目前只能在 subagent 自己的 middleware 链上挂 onActing 钩子查外部取消标志再抛异常来兜底 —— 这需要每个接入方重复实现,且中断粒度受限于工具调用边界。

Environment
AgentScope-Java: 2.0.0
JDK: 17 (Amazon Corretto 17.0.17)
受影响模块: agentscope-harness
受影响类:
io.agentscope.harness.agent.subagent.task.WorkspaceTaskRepository
io.agentscope.harness.agent.subagent.task.BackgroundTask
io.agentscope.harness.agent.middleware.SubagentsMiddleware

贡献指南

打开贡献指南

评估

这个 Issue 还没有评估数据。

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。