agentscope-ai / agentscope-ai/agentscope-java

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

Abierto
#2,789 0 comentarios 0 reacciones 0 asignados Ver en GitHub
bug
Lenguaje dominante
Java
Estrellas
5.6k
Forks
1.3k
Merge medio
4 d 12 h
PR fusionados (30 d)
77

Descripción

## 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

Guía de contribución

Abrir la guía de contribución

Evaluación

Este issue todavía no se ha evaluado.

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.