agentscope-ai / agentscope-ai/agentscope-java

[Bug]:[2.0.0] HarnessAgent 并发调用时 WorkspaceSkillRepository 可能串用其他会话的 RuntimeContext

Aperta
#2,314 2 commenti 0 reazioni 0 assegnatari Vedi su GitHub
area/core/agent area/core/memory area/core/tool area/extensions area/harness bug
Lingua principale
Java
Stelle
5.6k
Fork
1.3k
Merge medio
4g 12h
PR unite (30g)
77

Descrizione

问题描述
在同一个 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;
沙箱分配和释放错乱;
会话状态污染;
跨用户资源隔离风险。

Guida per i contributori

Apri la guida per i contributori

Valutazione

Questa issue non è ancora stata valutata.

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.