agentscope-ai / agentscope-ai/agentscope-java
[Bug]:[2.0.0] HarnessAgent 并发调用时 WorkspaceSkillRepository 可能串用其他会话的 RuntimeContext
- Lenguaje dominante
- Java
- Estrellas
- 5.6k
- Forks
- 1.3k
- Merge medio
- 4 d 12 h
- PR fusionados (30 d)
- 77
Descripción
问题描述
在同一个 HarnessAgent 实例上并发执行不同 (userId, sessionId) 的请求时,WorkspaceSkillRepository 可能读取到其他请求的 RuntimeContext。
该问题会导致 workspace skill 扫描、文件系统访问和沙箱路由使用错误的 userId/sessionId。如果并发请求属于不同用户,还可能带来跨用户资源隔离风险。
call() 和 streamEvents() 均可能受影响。
环境
agentscope-core: 2.0.0
agentscope-harness: 2.0.0
单个 HarnessAgent 实例服务多个 session
启用 Harness workspace skills
使用需要根据 RuntimeContext 路由的 AbstractFilesystem
实际现象
两个请求并发执行:
Request A: userId=user-1, sessionId=session-A
Request B: userId=user-1, sessionId=session-B
日志中观察到:
trace-A -> filesystem received sessionId=session-B, ctxIdentity=123456
trace-B -> filesystem received sessionId=session-B, ctxIdentity=123456
即 Request A 的 trace 在访问文件系统时使用了 Request B 的 RuntimeContext,两个 trace 甚至拿到了完全相同的 Context 实例。
下游如果按照 Context 分配沙箱,会进一步表现为:
trace-A -> acquire sandbox for session-B
trace-B -> acquire sandbox for session-B
预期行为
每次 Agent 调用的 RuntimeContext 必须严格绑定当前 call scope:
trace-A 只能使用 session-A
trace-B 只能使用 session-B
不同请求并发时,不能通过 Agent 实例上的共享字段获取请求级 Context。
根因定位
1. ReActAgent 使用共享 activeRc
ReActAgent 中存在实例级字段:
private volatile RuntimeContext activeRc;
调用开始时,beforeAgentExecution() 会执行类似逻辑:
activeRc = currentRequestContext;
调用结束时,afterAgentExecution() 又会清空:
activeRc = null;
当不同 session 并发调用同一个 Agent 时,activeRc 会被后启动的请求覆盖;任意一个请求完成时,也可能把另一个仍在执行的请求对应的 activeRc 清空。
2. HarnessAgent 为 WorkspaceSkillRepository 注入共享 Context Supplier
HarnessAgent.Builder 构建 workspace skill repository 时,Context Supplier 最终调用:
reactAgent.getRuntimeContext()
而该方法返回上述共享的 activeRc。
3. 正确的 call-local RuntimeContext 在中间链路中丢失
HarnessSkillMiddleware.onSystemPrompt() 已经收到了当前调用正确的参数:
onSystemPrompt(Agent agent, RuntimeContext rc, String prompt)
但是 mergeRepositories(rc) 加载技能时调用的是:
repository.getAllSkills()
RuntimeContext rc 没有继续传递给 repository。
随后 WorkspaceSkillRepository.getAllSkills() 又调用:
currentContext()
-> contextSupplier.get()
-> reactAgent.getRuntimeContext()
-> activeRc
最终从正确的 call-local Context 退化成了 Agent 实例级共享 Context。
为什么认为这是并发实现问题
ReActAgent.callSerializationKey(RuntimeContext) 当前按类似下面的 key 做调用串行化:
(userId, sessionId)
这意味着框架允许同一个 Agent 上的不同 session 并发执行。
但 Harness 的 workspace repository 又依赖全局 activeRc,两者的并发语义不一致:
Core:不同 session 可以并发;
Harness workspace:通过 Agent 全局字段获取当前 Context。
因此只要不同 session 并发,就存在 Context 被覆盖的窗口。
建议修复方案
建议不要让任何请求级文件系统或 workspace 操作依赖:
agent.getRuntimeContext()
可以增加上下文感知的 repository 接口:
public interface ContextualSkillRepository extends AgentSkillRepository {
List getAllSkills(RuntimeContext context);
}
WorkspaceSkillRepository 显式接收 Context:
@Override
public List getAllSkills(RuntimeContext context) {
return loadSkills(filesystem, context, skillsRelativeDir);
}
HarnessSkillMiddleware 将本次调用的 Context 继续向下传递:
private LinkedHashMap mergeRepositories(RuntimeContext context) {
for (AgentSkillRepository repository : repositories) {
List skills;
if (repository instanceof ContextualSkillRepository contextual) {
skills = contextual.getAllSkills(context);
} else {
skills = repository.getAllSkills();
}
// merge skills
}
}
长期建议:
Harness 内部所有请求级行为都显式传递 RuntimeContext。
不再通过 AtomicReference 和 getRuntimeContext() 为 workspace repository 提供 Context。
将 activeRc/getRuntimeContext() 标记为 legacy/non-concurrent,或者完全使用 Reactor call scope 保存请求 Context。
检查其他依赖 getRuntimeContext() 的 middleware、hook、skill repository 和 tool,确认没有同类问题。
建议回归测试
不同 session 并发
并发调用同一个 Agent:
RuntimeContext ctxA = RuntimeContext.builder()
.userId("user-1")
.sessionId("session-A")
.build();
RuntimeContext ctxB = RuntimeContext.builder()
.userId("user-1")
.sessionId("session-B")
.build();
Mono.when(
agent.call(messageA, ctxA).subscribeOn(Schedulers.parallel()),
agent.call(messageB, ctxB).subscribeOn(Schedulers.parallel())
).block();
使用 recording filesystem 记录每次 glob/read/write 收到的 Context,断言:
Request A 的所有文件系统操作只能收到 ctxA
Request B 的所有文件系统操作只能收到 ctxB
不同用户并发
增加:
user-1/session-A
user-2/session-B
确认不存在跨用户 Context 串用。
call 与 streamEvents 混合并发
同时执行:
call(ctxA)
streamEvents(ctxB)
确认两条调用链均保持 Context 隔离。
影响等级
建议按高优先级处理。
该问题不仅会造成并发调用失败,还可能导致:
workspace skill 从错误会话加载;
文件系统访问错误用户或错误 session;
沙箱分配和释放错乱;
会话状态污染;
跨用户资源隔离风险。
Guía de contribución
Evaluación
Este issue todavía no se ha evaluado.