agentscope-ai / agentscope-ai/agentscope-java

[Bug]:配置 AgentStateStore 时 stateCache 只写不读却无界驻留,建议提供失效策略或不填充

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

Descripción

**版本:** 2.0.0(`main` 分支同一份代码)
**模块:** `agentscope-core`
**环境:** 长驻服务进程,通过 `.stateStore(...)` 配置 `MysqlAgentStateStore`

## 概述

配置了 `AgentStateStore` 时,`ReActAgent#stateCache` 在调用路径上**只写不读**——每次调用都无条件回源并覆盖该条目,而调用期间的状态访问走的是 `CallExecution` 的直接引用。也就是说它没有承担任何缓存收益,却无界驻留、从不失效。

文档提到「缓存随会话数增长,除非单进程处理数百万会话否则通常可忽略」。本 issue 想说明的是:在配置了状态存储的部署形态下,这份驻留**没有对应的上行收益**,因此值得提供一个失效手段。

## 依据

`activateSlotForContext` 的两个分支使用了语义不同的方法:

```java
if (stateStore != null) {
loaded = loadOrCreateAgentStateForSlot(stateStore, uid, sid, ...); // 无条件回源
stateCache.put(slot, loaded); // 无条件覆盖 —— 非缓存语义
} else {
loaded = stateCache.computeIfAbsent(slot, k -> // 命中则复用 —— 缓存语义
loadOrCreateAgentStateForSlot(null, uid, sid, ...));
}
```

真正的「命中即复用」只存在于**没有**配置状态存储的分支。配置了存储时永远是 `put`,每次调用都查一次后端。

调用进行期间也不读该 map——`CallExecution` 持有的是对象硬引用,随 Reactor Context 传递:

```java
CallExecution scope = new CallExecution(loaded, loadedEngine, slot); // 同一实例的直接引用

private CallExecution scopeFrom(ContextView cv) {
Object scope = cv.getOrDefault(CALL_SCOPE_KEY, null); // 从 Reactor Context 取,不碰 stateCache
...
}

protected AgentState stateForCall(Object callScope) {
return callScope instanceof CallExecution ce ? ce.state : getAgentState();
} // ↑ 仅在调用之外才退回 map(且是 default 槽位)
```

因此在配置了状态存储时,`stateCache` 的实际读取者只有:

| 读取者 | 说明 |
|---|---|
| `getAgentState(userId, sessionId)` | admin API,javadoc 标注 "suitable for admin APIs and tests" |
| `saveAgentState(userId, sessionId)` | 同上 |
| 已废弃的 `interrupt(InterruptSource)` | 读取的是 `(null, defaultSessionId)` 默认槽位,非用户会话 |

而在 `agentscope-core` 与 `agentscope-harness` 中,对 `stateCache` 和 `permissionEngineCache` **没有任何** `remove` / `clear` / 失效 / TTL / 容量上限。`activateSlotForContext` 放进去的条目驻留至 JVM 结束。

## 影响

以我们的生产数据为例(内部只读答疑服务,流量很低):

| 指标 | 值 |
|---|---|
| 会话数 | 76 |
| 序列化总量 | 3.49 MB |
| 单会话均值 | 47 KB |
| 单会话峰值 | 315 KB |
| 新增速率 | 约 11 会话/天 |

堆内 `AgentState` 比序列化 JSON 更大(字符按 UTF-16 存储,另加 `Msg` / `ContentBlock` / 集合的对象开销),按 2~4 倍估算约 100~250 KB/会话。

当前量级完全无害,且滚动发布会周期性重置。但由于无上界,占用随「流量 × 进程存活时长」线性增长:例如 500 会话/天、30 天不重启时,单进程约 15000 个条目、1.5~3.7 GB。对于高流量或长驻不发布的部署,这会成为实际约束,而目前没有任何配置手段可以介入。

## 诉求(按侵入性从低到高)

1. **提供可配置的失效策略**,例如 `stateCacheMaxSize` / `stateCacheTtl`,或允许注入自定义 `Map` 实现。
2. **配置了 `AgentStateStore` 时不填充 `stateCache`。** 调用路径本就不读它;`getAgentState` / `saveAgentState` 这两个 admin API 在配置了存储时本就应当回源——这与 #_____(issue ① 编号)的修复方向一致,可一并处理。
3. **暴露公开的逐出 API**,例如 `evictSession(userId, sessionId)`,让使用方在会话结束时自行释放。

我们目前只能依赖发版重启兜底,并添加堆内存监控,没有代码层面的手段。

## 关联

`setPermissionMode` 读取该缓存导致的正确性问题另见 #_____(issue ① 编号)。两者共享同一根因:一个进程级、不回源、不失效的 `stateCache`。

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.