feat(server): add session auto commit scheduling#2772
Open
zhoujh01 wants to merge 1 commit into
Open
Conversation
bd10b0e to
798b0be
Compare
feat(server): add session auto commit scheduling fix: harden session auto commit e2e coverage refactor: shorten session auto commit index path docs: sync session auto commit design and api docs feat: refine session auto commit idle indexing feat: expose session auto commit in sdk and cli fix: pin setuptools-scm for editable builds fix: harden session auto commit index sync
ba331c1 to
eca01a9
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Session Auto Commit 服务端自动触发详细方案
1. 背景
当前 session commit 的自动触发能力曾经主要分散在客户端或插件侧,不同接入方的行为模型并不一致。
两个典型例子:
openclaw-pluginopencode-memory-plugin这种现状带来几个问题:
因此,需要把 session auto commit 能力收回到服务端。
2. 目标
本方案的目标是:
auto_commit_policy持久化到 session meta3. 非目标
v1 明确不做下面这些事:
in_flight task状态update auto_commit_policy专用 API4. 设计原则
4.1 配置真相源和调度状态分离
auto_commit_policy保存在 session meta 中,因为它是业务配置真相源。索引文件不保存完整 policy,只保存“当前需要被 idle scheduler 跟踪的 session membership”。
也就是说:
4.2 token 和 idle 走不同路径
这两类触发机制本质不同:
因此不应强行走同一个调度模型。
4.3 索引只保存活跃待跟踪对象
索引不应该保存所有历史上开启过 auto commit 的 session。
它只应保存:
这样可以避免索引文件无限膨胀。
4.4 v1 优先简单可解释
在功能边界明确的前提下,优先做:
而不是一开始就追求复杂分片或强一致机制。
5. 现状梳理
5.1 openclaw-plugin 模式
openclaw-plugin当前更像:这个模型的特点是:
5.2 opencode-memory-plugin 模式
opencode-memory-plugin当前更像:这个模型的特点是:
5.3 收敛方向
服务端应统一支持:
这样既能保留两类行为的优势,又能统一治理。
6. Session Meta 设计
auto_commit_policy放入 session.meta.json。建议字段如下:
{ "auto_commit_policy": { "enabled": true, "token_threshold": 8000, "idle_timeout_seconds": 1800, "keep_recent_count": 10 }, "last_message_at": "2026-06-20T10:00:00+08:00", "auto_commit_last_error": "", "auto_commit_last_error_at": "" }说明:
auto_commit_policylast_message_atauto_commit_last_errorauto_commit_last_error_at6.1 为什么不持久化 runtime in-flight 状态
不建议在 meta 中写:
auto_commit_pendingauto_commit_in_flight_task_id原因是:
因此 v1 依赖:
Session.commit_async()自身的锁/no-op 语义TaskTracker.has_running(...)做最小去重即可。
7. 两类触发机制设计
7.1 token threshold 触发
这类触发不需要进索引。
原因:
处理方式:
add_message/batch_add_messagespending_tokens >= token_threshold7.2 idle timeout 触发
这类触发需要进索引。
原因:
处理方式:
last_message_atnext_check_at8. 服务端全局开关
给 idle 自动触发加一个服务端全局配置:
{ "server": { "session_auto_commit": { "idle_enabled": true, "check_interval_seconds": 60.0 } } }约束如下:
idle_enabledcheck_interval_seconds60.0这样做的好处是:
9. 索引文件设计
9.1 为什么需要索引
如果没有索引,要实现 idle 触发,理论上可以每轮扫描全部 session。
但这有几个问题:
因此需要一个“活跃 idle 候选集”的索引层。
9.2 为什么 token-only session 不进索引
因为 token-only session 不需要时间驱动扫描。
只要没有新消息写入,就不会触发 token threshold 判断。
把它放进索引会带来纯粹的无效扫描,没有收益。
9.3 单文件 vs 多文件
v1 先选单文件。
原因:
后续如果规模增长,再考虑分片。
10. 索引存储位置
索引文件不应放在
viking://resources/下。原因:
当前统一放在内部系统路径:
/local/_system/session_auto_commit/index.json这里不再保留额外的
tmp/bak轮转文件。11. 索引文件结构
当前结构如下:
{ "meta": { "updated_at": "2026-06-20T10:30:00+08:00" }, "data": { "acct_a": { "user_b": { "chat_123": {} } } } }说明:
meta和datadata采用account -> user -> session层级sessionvalue 目前为空对象{},只表示 membershipnext_check_at不持久化到文件中11.1 为什么不写扁平 key
比如这种:
{ "sessions": { "acct_a:user_b:chat_123": {} } }问题在于:
11.2 为什么不用
_meta当固定 key因为如果顶层同时承载业务层级,
_meta这种保留名会引入命名冲突风险。比如理论上有人账户名就可能叫
_meta。所以更安全的做法是:
metadata业务数据全部放在
data之下。11.3 为什么 value 里不重复写 account/user/session
因为这些信息已经由层级路径表达了。
value 不需要重复写:
account_iduser_idsession_id这样能减少冗余。
12.
next_check_at的设计next_check_at仍然存在,但只保存在进程内 runtime cache,不持久化到index.json。12.1 为什么不落盘
当前实现优先考虑:
因此:
session_key -> next_check_at12.2
next_check_at何时更新它在以下场景更新:
last_message_at变化idle_timeout_seconds变化12.3 为什么这样仍能减少无效扫描
当前 scheduler 每轮不是扫描全部 session,而是:
index.jsonnext_check_at这样比“扫描全部 session”轻很多。
13. 索引项写入条件
一个 session 只有同时满足以下条件,才进入索引:
auto_commit_policy.enabled == trueidle_timeout_seconds有效idle_enabled == true只有开启 idle 自动触发的 session 才会进入这个文件。
14. 索引项删除条件
索引文件里只保留当前需要后台调度器关注的 session。
因此以下情况应删除索引项:
idle_timeout_seconds被移除idle_enabled被关闭14.1 为什么自动 commit 成功后可以直接删
当自动 commit 成功后,如果当前 session 已无未提交内容,就没必要继续保留索引。
虽然 “policy 还开着” 这件事仍然存在,但这个信息保留在 session meta 里。
当未来再次
add_message时:这样做的好处是:
15. 持久化策略
15.1 何时持久化
v1 采用“membership 变更立即持久化”。
也就是说:
15.2 当前写法
当前实现简化成:
index.jsonindex.json这样做的原因是:
15.3 runtime 与持久化的边界
只有 membership 需要落盘。
以下状态只保存在进程内:
next_check_at16. 启动与重启恢复
16.1 启动正常流程
服务启动时:
这里不会在启动时全量扫描所有 session。
16.2 当前恢复思路
当前恢复机制不是“启动时全量重建”,而是“每轮 check 时懒恢复 runtime”。
具体做法:
index.jsonnext_check_at16.3 重启后会不会丢
分两类看:
index.json因此当前语义是:
17. scheduler 设计
17.1 扫描范围
scheduler 不扫描全部 session。
它只扫描索引文件里登记的 idle session membership。
也就是说:
17.2 单轮逻辑
每轮 scheduler:
index.jsonnext_check_atnext_check_at <= now的项17.3 为什么需要二次校验
因为索引不是配置真相源,runtime cache 也不是配置真相源。
例如:
idle_enabled已被服务端关闭所以到期后不能直接 commit,必须再读 session 状态做最终判断。
18. 去重与并发控制
自动 commit 存在重复触发风险,例如:
v1 使用三层最小去重:
Session.commit_async()自身锁与 no-op 语义TaskTracker.has_running(...)18.1 为什么不再额外持久化 task 状态
因为现有 commit 链路本身已经具备:
再单独持久化
auto_commit_pending或in_flight_task_id:v1 没必要。
19. 关键流程
19.1 add message
last_message_atauto_commit_policy,则持久化到 session metakeep_recent_count变化,则同步更新session.meta.keep_recent_countpending_tokensidle_enabled == truenext_check_at19.2 batch add messages
auto_commit_policy只允许在 batch 顶层传一次messages[*].auto_commit_policy不允许19.3 token 自动触发
pending_tokens19.4 idle 自动触发
19.5 自动 commit 成功
next_check_at19.6 自动 commit 失败
auto_commit_last_errorauto_commit_last_error_at20. API 语义
在消息写入接口上支持:
{ "auto_commit_policy": { "enabled": true, "token_threshold": 8000, "idle_timeout_seconds": 1800, "keep_recent_count": 10 } }这里的语义应明确:
如果未来需要单独更新 policy,也可以再补一个专用 API,但 v1 不强依赖。
21. 风险点与取舍
21.1 scheduler 每轮仍需读取 membership 并按需补 runtime
当前索引只存 membership,不存
next_check_at。这意味着 scheduler 每轮至少要:
index.json这个成本比“把全部调度状态都持久化到索引”更高,但换来:
21.2 单文件热点
单文件索引在高并发下会有写热点。
但 v1 的目标不是承载极端规模,而是先把语义跑通、恢复语义跑通,因此可以接受。
21.3 当前不考虑多进程强并发
目前实现以单进程语义为主,对
index.json的操作通过进程内锁串行化。暂不引入跨进程分布式锁或 lease。
21.4 故障窗口仍然存在
即便配置真相源在 meta、membership 在索引,也不能消灭所有故障窗口。
本方案的关键不是“绝对无窗口”,而是:
只要这三点成立,语义就是可解释、可补偿的。
22. v1 明确边界
v1 明确采用以下约束:
idle_enabled默认开启check_interval_seconds默认60.0resourcesnext_check_atindex.json,再按需补 runtime23. 当前实现对照
当前代码中已经落地的内容包括:
auto_commit_policylast_message_atauto_commit_last_errorauto_commit_last_error_atserver.session_auto_commit.idle_enabledserver.session_auto_commit.check_interval_secondsadd_message/batch_add_messages已支持接收并持久化auto_commit_policyauto_commit_policy.keep_recent_count已同步写入session.meta.keep_recent_countkeep_recent_count变化时,会重建pending_tokensresources/local/_system/session_auto_commit/index.jsonTaskTracker.has_running(...)Session.commit_async()自身锁/no-op 语义auto_commit_policymessages[*].auto_commit_policy已从 batch schema 中移除Session.load()会用 livemessages.jsonl回填meta.message_countSession.load()会重建pending_tokens24. 测试覆盖
当前已补的验证包括:
_system路径Session.load()可从 live messages 恢复message_countsession_auto_commit配置默认值与覆盖值校验idle_enabled == false时 scheduler 不启动25. 结论
这个方案的核心思路可以概括为:
auto_commit_policy放在 session meta,作为真相源_system,不放在resourcesindex.json只存 membership,不存next_check_atnext_check_at只保存在进程内 runtime cacheindex.json,再按需从 session meta 补齐 runtime这样可以在实现复杂度可控的前提下,把自动 commit 的核心能力从插件侧收回到服务端,并保证后续有继续增强的空间。