Checklist
🐞 问题详细描述
Feature Request:面向开发者的统一 Redis 接入入口
提出日期:2026-07-29
提出角度:agent 应用开发者(使用 core-java + runtime-java 构建业务 agent 的一方)
上下文:FEAT-003 v2 spec § 5.1.6 "统一可配置 TTL"、demo 侧接入 checkpointer / Todolist / A2A TaskStore 的实际体验
我们(开发者)想要什么
在 agent 应用里,出于业务需要,我们可能要把很多东西保存到 Redis:
- Checkpointer(agent state / graph / workflow)
- Todolist(DeepAgent 的 task-scoped 计划)
- A2A Task 快照
- 未来还会有更多:会话缓存、模型响应缓存、rate limiter、幂等键、审计事件……
我们希望的开发体验:应用启动时,配置好一次 Redis 连接(host、集群、密码、TTL 默认值等等),拿到一个 Redis 接入句柄,然后所有需要写 Redis 的能力(现在的 + 将来的)都通过这个句柄读写。
至于底层是不是同一个物理 Redis 实例、要不要分 database、要不要 key 前缀分区——这些是我们做部署时才需要考虑的运维决策,不应该在应用代码或者 SDK API 层暴露成"你要给 checkpointer 一套配置、给 Todolist 另一套配置"。
我们目前遇到的事
按当前 core-java 730 + runtime-java develop 的实现,我们(demo 侧)经历了下面这些:
1. 同一份 Redis,配置要在两个地方各自装配
application-redis-checkpointer.yml 里:
openjiuwen.service.middleware.checkpointer.type=redis + redis-ref=default → runtime 会造一个 RuntimeRedisClient bean 给 checkpointer 用
- 但是 Todolist 走的是
DeepAgent.setKvStore(BaseKVStore),这条路 runtime 没自动接线;我们要在应用代码里手动:
BaseKVStore kvStore = KVStoreFactory.create("redis", Map.of("redis_client", runtimeRedisClient));
deepAgent.setKvStore(kvStore);
- 结果:同一个 Redis 实例、同一个 client bean,但是装配路径两条、配置消费点两个、在应用代码里显式做 bridge。
我们的疑问:为什么要我们感知到 checkpointer 和 Todolist 的接入方式不一样?在开发者视角,两者都是"写 Redis",不应该有区别。
2. 增加新的"要写 Redis 的能力"时,我们不知道应该怎么接
假设明天我们想加一个 session-cache(比如缓存 A2A 子 agent 的一些中间态),或者一个 rate limiter,或者别的什么。
我们不知道:
- 应该走
deepAgent.setKvStore(...) 那条路?—— 但这是给 Todolist 设计的,绑在 DeepAgent 生命周期上
- 还是自己去 runtime 层拿
RuntimeRedisClient bean,然后手动 wrap KVStoreFactory.create("redis", ...)?—— 每个开发者都要写一遍相同的 glue
- 还是复用 checkpointer 那套
Map<String, Object> conf?—— 那个格式 undocumented,是 core 内部约定
我们的疑问:文档里也好、SDK 契约里也好,我们没找到"新的 Redis 消费者应该沿着哪条 SPI 接入"这个答案。每次都是读代码猜。
3. TTL 是每个消费者各自实现的
- Checkpointer 有
checkpointer.ttl-seconds 配置,装配处 AgentCoreCheckpointerConfigAssembler 把它塞进 BaseRedisStorage 的 ttlSeconds 字段;BaseRedisStorage.save 里手动 pipeline.set(k, v, ttlSeconds)
- A2A TaskStore 有独立的 TTL 处理(
WriteThrottlingTaskStore 那套)
- Todolist 目前没有 TTL(
KvTodoStorage.save 直接调 BaseKVStore.set(k, v),无 TTL 参数、也不后续 EXPIRE)—— key 会永久驻留
开发者视角看到的事:
- Checkpointer 的 key 会过期,Todolist 的 key 不会
- 但 spec §5.1.6 明明写"简单统一的可配置 TTL"
- 我们没有一个统一的地方能配"我的应用里所有 Redis 写入都用同一个默认 TTL"
- 如果我们想给 session-cache 加 TTL,得自己再 patch 一遍 —— 因为
BaseKVStore.set(k, v) 这个签名根本没 TTL 参数,得绕开它
我们的疑问:TTL 是"写 Redis 的一等属性",应该在 SDK 提供给开发者的那个接入入口上就是 first-class 参数,而不是每个消费者自己想办法。
4. Redis 连接细节在两个仓库里各出现一次
从我们(应用侧)视角能看到的:
- runtime 侧有一整套
MiddlewareProperties.RedisEndpoint + RedisConnectionAssembler + RedisMiddlewareAutoConfiguration + RuntimeRedisClient bean(JedisPooled / JedisCluster 分支),管密码解密、集群、诊断、生命周期
- core 侧
com.openjiuwen.extensions.store.kv 下也有一套 RedisStore / RedisKVStoreProvider,甚至能自己用反射 new Jedis(host, port)(RedisKVStoreProvider.createClientByReflection)
开发者视角看到的困惑:
- 我们感受到的是"runtime 提供中间件接入服务,core 提供业务能力" —— 但 core 侧居然有能自己造 Redis 连接的能力
- 而且现在两套是靠
Map<String, Object> conf + 反射 duck-typing 粘合的(core 的 RedisStore 反射调 runtime 的 RuntimeRedisClient 方法名)—— 这条通路很脆,出问题时排错要跨两个仓
- 我们不知道应该往哪套 API 写,也不知道哪套是官方推荐的开发者入口
我们的疑问:从开发者视角,能不能只有一个"官方入口",不需要我们理解 core 里为什么还有一套 Redis 实现?
5. 具体现场:三个已经吃过的亏
以下三条不是本 feature request 的范围,但都是"没有统一入口"的直接后果,附在这里作为佐证:
| 现场 |
现象 |
根因(跟统一入口的关系) |
| gitcode issue #62 |
Redis checkpointer 因 InteractiveInput 不可序列化,checkpoint 100% 保存失败但静默返回假成功 |
Checkpointer 走的是 core 侧自建的 Redis path,异常兜底是内部实现细节,开发者感知不到 |
| MUST2 gap #1(跟开发组沟通中) |
Todolist 保存到 Redis 无 TTL |
BaseKVStore.set 无 TTL 参数,消费者想加 TTL 得绕开 SPI |
| MUST2 gap #2 |
Todolist 配置错误时静默降级到进程内 in_memory,无日志 |
Provider 在 core 侧能自己造 KV 实现,才有兜底的余地;也没有统一契约要求 fail-fast |
我们想请开发组回答 / 讨论的事
我们不给方案。作为消费方,我们希望开发组从架构定位出发,把下面这些讲清楚:
-
统一入口是不是 SDK 应该提供的能力?
即:应用启动配置一次 Redis → 任意消费者(checkpointer / Todolist / 未来的 X)都从这一个入口取 —— 这个体验目标,开发组认不认可?
-
如果认可,Redis 中间件接入是不是应该完全归属 runtime 层?
core 侧目前的 RedisStore / RedisKVStoreProvider / checkpointer 里的 Redis 装配代码,是不是应该收敛到 runtime?core 只保留 BaseKVStore 之类的抽象 SPI?
(我们理解 core 需要"能独立跑"的历史约束,但从开发者体验看,"能独立跑"跟"要在 core 里塞 Redis 客户端实现"不是同一件事)
-
TTL 作为 Redis 写入的一等属性,应该在哪一层?
现在是每个消费者自己 patch。是不是应该在开发者拿到的那个入口签名上(如 set(key, value, ttlSeconds))就是必选/可选参数?
-
未来新增"要写 Redis 的能力"(session-cache / rate-limiter / …),开发组希望我们(消费方)沿着哪条路接?
希望能有一份明确的官方接入指南,或者能明确说"就走 xxx 这个入口,其他历史 API 不要用"。
-
配置层的 middleware.redis.<name> map 结构(现在有 default,未来可能 todo / cache / …)
我们理解这是运维层选择"用几套物理 Redis"的机制。这个跟"SDK 只暴露一个开发者入口"能不能同时成立?(我们的直觉是能:应用配置层写 name→endpoint 映射,SDK 提供的入口方法可以带一个 redisRef 参数或者干脆按消费者名字自动选,但一个消费者永远只对一个 endpoint,不需要开发者手动 juggle 多个 client)
我们不主张的事(避免误会)
- 不主张"给 checkpointer / Todolist / 未来 X 各自维护一套 Redis 配置"—— 这是我们明确反对的方向
- 不主张"应用代码自己在两个仓的 API 之间做 bridge"—— 这是我们现在被迫做的事,也是我们希望消除的
- 不主张开发组一定要按上面 (1)~(5) 的问法回答—— 上面只是我们试图表达清楚的思路,实际怎么落地由架构组判断
详细的环境信息描述
core java 730分支
runtime Java develop分支
其他辅助信息
版本信息
感谢您的贡献 🎉!
Checklist
🐞 问题详细描述
Feature Request:面向开发者的统一 Redis 接入入口
提出日期:2026-07-29
提出角度:agent 应用开发者(使用 core-java + runtime-java 构建业务 agent 的一方)
上下文:FEAT-003 v2 spec § 5.1.6 "统一可配置 TTL"、demo 侧接入 checkpointer / Todolist / A2A TaskStore 的实际体验
我们(开发者)想要什么
在 agent 应用里,出于业务需要,我们可能要把很多东西保存到 Redis:
我们希望的开发体验:应用启动时,配置好一次 Redis 连接(host、集群、密码、TTL 默认值等等),拿到一个 Redis 接入句柄,然后所有需要写 Redis 的能力(现在的 + 将来的)都通过这个句柄读写。
至于底层是不是同一个物理 Redis 实例、要不要分 database、要不要 key 前缀分区——这些是我们做部署时才需要考虑的运维决策,不应该在应用代码或者 SDK API 层暴露成"你要给 checkpointer 一套配置、给 Todolist 另一套配置"。
我们目前遇到的事
按当前 core-java
730+ runtime-javadevelop的实现,我们(demo 侧)经历了下面这些:1. 同一份 Redis,配置要在两个地方各自装配
application-redis-checkpointer.yml里:openjiuwen.service.middleware.checkpointer.type=redis+redis-ref=default→ runtime 会造一个RuntimeRedisClientbean 给 checkpointer 用DeepAgent.setKvStore(BaseKVStore),这条路 runtime 没自动接线;我们要在应用代码里手动:我们的疑问:为什么要我们感知到 checkpointer 和 Todolist 的接入方式不一样?在开发者视角,两者都是"写 Redis",不应该有区别。
2. 增加新的"要写 Redis 的能力"时,我们不知道应该怎么接
假设明天我们想加一个 session-cache(比如缓存 A2A 子 agent 的一些中间态),或者一个 rate limiter,或者别的什么。
我们不知道:
deepAgent.setKvStore(...)那条路?—— 但这是给 Todolist 设计的,绑在 DeepAgent 生命周期上RuntimeRedisClientbean,然后手动 wrapKVStoreFactory.create("redis", ...)?—— 每个开发者都要写一遍相同的 glueMap<String, Object>conf?—— 那个格式 undocumented,是 core 内部约定我们的疑问:文档里也好、SDK 契约里也好,我们没找到"新的 Redis 消费者应该沿着哪条 SPI 接入"这个答案。每次都是读代码猜。
3. TTL 是每个消费者各自实现的
checkpointer.ttl-seconds配置,装配处AgentCoreCheckpointerConfigAssembler把它塞进BaseRedisStorage的ttlSeconds字段;BaseRedisStorage.save里手动pipeline.set(k, v, ttlSeconds)WriteThrottlingTaskStore那套)KvTodoStorage.save直接调BaseKVStore.set(k, v),无 TTL 参数、也不后续EXPIRE)—— key 会永久驻留开发者视角看到的事:
BaseKVStore.set(k, v)这个签名根本没 TTL 参数,得绕开它我们的疑问:TTL 是"写 Redis 的一等属性",应该在 SDK 提供给开发者的那个接入入口上就是 first-class 参数,而不是每个消费者自己想办法。
4. Redis 连接细节在两个仓库里各出现一次
从我们(应用侧)视角能看到的:
MiddlewareProperties.RedisEndpoint+RedisConnectionAssembler+RedisMiddlewareAutoConfiguration+RuntimeRedisClientbean(JedisPooled/JedisCluster分支),管密码解密、集群、诊断、生命周期com.openjiuwen.extensions.store.kv下也有一套RedisStore/RedisKVStoreProvider,甚至能自己用反射 new Jedis(host, port)(RedisKVStoreProvider.createClientByReflection)开发者视角看到的困惑:
Map<String, Object>conf + 反射 duck-typing 粘合的(core 的RedisStore反射调 runtime 的RuntimeRedisClient方法名)—— 这条通路很脆,出问题时排错要跨两个仓我们的疑问:从开发者视角,能不能只有一个"官方入口",不需要我们理解 core 里为什么还有一套 Redis 实现?
5. 具体现场:三个已经吃过的亏
以下三条不是本 feature request 的范围,但都是"没有统一入口"的直接后果,附在这里作为佐证:
InteractiveInput不可序列化,checkpoint 100% 保存失败但静默返回假成功BaseKVStore.set无 TTL 参数,消费者想加 TTL 得绕开 SPI我们想请开发组回答 / 讨论的事
我们不给方案。作为消费方,我们希望开发组从架构定位出发,把下面这些讲清楚:
统一入口是不是 SDK 应该提供的能力?
即:
应用启动配置一次 Redis→任意消费者(checkpointer / Todolist / 未来的 X)都从这一个入口取—— 这个体验目标,开发组认不认可?如果认可,Redis 中间件接入是不是应该完全归属 runtime 层?
core 侧目前的
RedisStore/RedisKVStoreProvider/ checkpointer 里的 Redis 装配代码,是不是应该收敛到 runtime?core 只保留BaseKVStore之类的抽象 SPI?(我们理解 core 需要"能独立跑"的历史约束,但从开发者体验看,"能独立跑"跟"要在 core 里塞 Redis 客户端实现"不是同一件事)
TTL 作为 Redis 写入的一等属性,应该在哪一层?
现在是每个消费者自己 patch。是不是应该在开发者拿到的那个入口签名上(如
set(key, value, ttlSeconds))就是必选/可选参数?未来新增"要写 Redis 的能力"(session-cache / rate-limiter / …),开发组希望我们(消费方)沿着哪条路接?
希望能有一份明确的官方接入指南,或者能明确说"就走
xxx这个入口,其他历史 API 不要用"。配置层的
middleware.redis.<name>map 结构(现在有default,未来可能todo/cache/ …)我们理解这是运维层选择"用几套物理 Redis"的机制。这个跟"SDK 只暴露一个开发者入口"能不能同时成立?(我们的直觉是能:应用配置层写 name→endpoint 映射,SDK 提供的入口方法可以带一个
redisRef参数或者干脆按消费者名字自动选,但一个消费者永远只对一个 endpoint,不需要开发者手动 juggle 多个 client)我们不主张的事(避免误会)
详细的环境信息描述
core java 730分支
runtime Java develop分支
其他辅助信息
版本信息
感谢您的贡献 🎉!