agentscope-ai / agentscope-ai/agentscope-java
[Bug]: MEMORY.md 创建为default
- Linguagem predominante
- Java
- Estrelas
- 5.6k
- Forks
- 1.3k
- Merge médio
- 4d 12h
- PRs com merge (30d)
- 77
Descrição
# Bug: MemoryFlushMiddleware / MemoryMaintenanceMiddleware 使用 RuntimeContext.empty() 写入文件系统,导致所有用户共享同一 namespace(_default)
## 问题表现
使用 `RemoteFilesystemSpec`(`IsolationScope.USER`)+ MySQL 后端时,数据库中出现两类 namespace:
- **sessions**(`SessionTree` 写入):namespace 正确,含真实 userId
```
agents\x1F{agentName}\x1Fusers\x1F{userId}\x1Fsessions\x1F
```
- **memory 文件**(`MemoryFlushMiddleware` / `MemoryMaintenanceMiddleware` 写入):namespace 错误,userId 退化为 `_default`
```
agents\x1F{agentName}\x1Fusers\x1F_default\x1Froot\x1F → MEMORY.md
agents\x1F{agentName}\x1Fusers\x1F_default\x1Fmemory\x1F → YYYY-MM-DD.md, .consolidation_state
```
结果:所有用户的长期记忆(`MEMORY.md`、每日账本)被写入同一 namespace,用户间记忆互相覆盖,且 agent 在自己的 userId namespace 下找不到这些文件。
## 问题原因
`MemoryFlushMiddleware` 和 `MemoryMaintenanceMiddleware` 在 `onAgent()` 入口处立即捕获 `agent.getRuntimeContext()`,此时该值必然为 `null`。
```java
// MemoryFlushMiddleware.onAgent()(MemoryMaintenanceMiddleware 同理)
public Flux onAgent(Agent agent, AgentInput input, Function> next) {
final RuntimeContext rc =
agent instanceof AgentBase ab && ab.getRuntimeContext() != null
? ab.getRuntimeContext()
: RuntimeContext.empty(); // ← 始终走这里,rc 为 empty
return next.apply(input).doOnComplete(() -> doFlush(agent, rc).subscribe());
}
```
**根本原因**:`ReActAgent` 的 RuntimeContext 绑定发生在 `beforeAgentExecution()` 内部(即 Flux 被订阅、`call()` 真正执行时),而 `onAgent()` 是在 middleware chain 组装阶段被调用的,早于 `beforeAgentExecution()`:
```
streamEvents(msgs, rc)
└─ pendingRuntimeContext = rc // 暂存,未绑定
└─ MiddlewareChain.build().apply()
└─ MemoryFlushMiddleware.onAgent()
└─ agent.getRuntimeContext() == null // ← 此处捕获,必为 null
└─ next.apply(input)
└─ core(input) → call(msgs)
└─ beforeAgentExecution()
└─ bindRuntimeContextToHooks(rc) // rc 在这里才真正绑定
```
`SessionTree` 不受影响,因为它实现了 `RuntimeContextAware` 接口,在 `bindRuntimeContextToHooks()` 时通过回调拿到正确的 rc。
## 修复建议
将 rc 的读取从 `onAgent()` 入口移入 `doOnComplete()` 回调内部,在 agent 执行完成后(此时 `currentRuntimeContext` 已绑定)再读取:
```java
// MemoryFlushMiddleware
@Override
public Flux onAgent(Agent agent, AgentInput input, Function> next) {
return next.apply(input).doOnComplete(() -> {
RuntimeContext rc =
agent instanceof AgentBase ab && ab.getRuntimeContext() != null
? ab.getRuntimeContext()
: RuntimeContext.empty();
doFlush(agent, rc).subscribe();
});
}
```
```java
// MemoryMaintenanceMiddleware
@Override
public Flux onAgent(Agent agent, AgentInput input, Function> next) {
return next.apply(input).doOnComplete(() -> {
RuntimeContext rc =
agent instanceof AgentBase ab && ab.getRuntimeContext() != null
? ab.getRuntimeContext()
: RuntimeContext.empty();
maybeRunMaintenance(rc);
});
}
```
`Mono.using` 的 cleanup(`afterAgentExecution` / `unbindRuntimeContextFromHooks`)在 Reactor 中晚于下游 `doOnComplete` 触发,因此在 `doOnComplete` 内读取 `getRuntimeContext()` 时 rc 仍有效。
## 临时绕过方案
调用 `HarnessAgent.Builder.disableMemoryHooks()` 禁用上述两个 middleware,避免脏数据写入。Sessions 和 Compaction 不受影响,代价是长期记忆(MEMORY.md / 每日账本)不再持久化。
```java
HarnessAgent.builder()
...
.disableMemoryHooks()
.build();
```
Guia de contribuição
Avaliação
Esta issue ainda não foi avaliada.