Feat/session auto commit v2 - #3736
Merged
Merged
Conversation
Re-implementation of the auto-commit feature (supersedes the overly complex PR #2772). Commits are triggered on message write (token/message thresholds) and by an optional background idle-timeout scan. The commit policy is set once at session creation and is immutable afterwards; all bounds are clamped in AutoCommitPolicy.from_dict. Server-wide switches live under memory.session_auto_commit and default to disabled, so hot paths short-circuit when no policy is stored. Scheduling is best-effort: a missed cycle is re-triggered by the next write or idle scan. maybe_schedule_auto_commit swallows scheduling errors so the task store being unavailable never fails the caller's message write or aborts an idle scan batch. Spawned commit tasks are kept in a strong-ref set so they are not GC'd mid-await. Config is threaded through the HTTP router, local/async/sync clients, the Python/TypeScript/Go SDKs, and the CLI. Session GET returns the resolved config. Co-authored-by: TRAE CLI <noreply@bytedance.com>
Replace the single-field `config` wrapper on session create with a top-level `auto_commit_policy` field, matching `memory_policy`. Requests now send `auto_commit_policy` directly, and create/get responses return it at the top level instead of under `config`. Updates the router, service accessor, all Python client layers, the Python/Go/TypeScript SDKs, server tests, and API/config docs. Co-authored-by: TRAE CLI <noreply@bytedance.com>
Fix four concurrency/contract issues in session auto-commit: - load(): always recompute message_count from live messages so a stale persisted value cannot drift (previously only recovered when .meta.json was missing). - run_auto_commit(): drop the lock-free failure accounting that could overwrite concurrent message-write meta; the removed auto_commit_last_error fields become dead, so remove them from SessionMeta and the commit success path. Failures are logged and self-heal on next load. - _should_run_auto_commit(): enforce min_commit_interval on the idle path, matching the documented throttle contract. - commit_async(): compute stored_keep_recent_count after the in-lock meta re-read so a concurrent manual commit's keep_recent_count is not reverted. Test infra: add no-op pathlock methods to MockLocalAGFS (tree/exact/release) so the append/commit path lock works under the mock, and make the counting_save_meta monkeypatch forward lease_ref. Co-authored-by: TRAE CLI <noreply@bytedance.com>
qin-ctx
approved these changes
Aug 5, 2026
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.
feat(session): 会话自动 commit(best-effort 简化实现)
背景与动机
会话(session)在使用过程中会不断累积 live message。只有触发 commit 才会把这些消息归档(archive)并抽取记忆(memory extraction)。此前完全依赖调用方显式调 commit:调用方不 commit,消息就一直堆在 live 区,既拿不到记忆,也让 session 越来越大。
本 PR 让服务端能在满足条件时自动触发 commit,调用方无需自己管理 commit 时机。
设计概览
自动 commit 由两条路径触发,共用同一套 policy 与去重逻辑:
add_message/batch_add_messages写入后,检查该 session 是否越过 token 或消息数阈值,越过则尝试触发一次后台 commit。触发发生在消息已写入之后,不阻塞写入返回。.meta.json,把「有未提交内容且空闲超过idle_timeout_seconds」的 session 挑出来触发 commit。idle 触发会归档全部积压消息。两条路径最终都走
SessionService.maybe_schedule_auto_commit()→run_auto_commit(),共用去重与节流。policy 是什么
每个 session 可携带一份
auto_commit_policy。没有 policy 的 session 永远不会自动 commit(除非服务端开启了default_enabled)。policy 只在创建 session 时设置一次,之后不可变(immutable),不支持运行期 PATCH。policy 字段(均可选,缺省回落到推荐默认值;所有值 clamp 到
[0, 上限]):pending_token_thresholdmessage_count_thresholdidle_timeout_secondskeep_recent_countmin_commit_interval_secondsclamp 与默认值填充统一收敛在
AutoCommitPolicy.from_dict()一处,HTTP / SDK / CLI 各入口不重复实现校验。未知字段一律以InvalidArgumentError拒绝。best-effort 去重模型(关键取舍)
阈值场景下,突发并发写入会在短时间内多次命中触发条件。为避免为同一个 session 同时 spawn 多个 commit 任务,去重分两层:
_auto_commit_inflight((account, user, session_id)三元组 +asyncio.Lock):拦掉同一进程内的重复触发。get_task_tracker().has_running("session_commit", ...):分布式部署下,其他 worker 已在跑 commit 时就跳过。两层都是「命中就跳过」,不排队、不重试。因此突发 N 次写入通常只产生 1 个 commit 任务(E2E 场景 P 验证 10 并发写入 → 1 个 commit 任务)。偶发的重复 no-op 任务是可接受代价,换来的是没有并发状态机。
调度决策做了两次校验,避免用陈旧内存态误触发:
maybe_schedule_auto_commit先用当前 session 判一次,run_auto_commit里 reload 后再判一次,commit_async内部还会在自己的 path lock 下再读一次。成功 / 失败记账
commit 成功后,在 Phase 1 与 Phase 2 的 meta 写入里(同一把 path lock 下)清空错误字段并打上
last_auto_commit_at。失败时只记录auto_commit_last_error/auto_commit_last_error_at,不抛给消息写入或 idle 扫描批次。这些字段通过GET /sessions/{id}暴露,便于排查。与手动 commit 的关系
自动 commit 完全复用既有的
Session.commit_async()(Phase 1 归档内联、Phase 2 记忆抽取走后台任务)。本 PR 没有另造一套 commit 流程,只是在合适时机替调用方调用它,并透传keep_recent_count/record_auto_commit_success等参数。API 变更
创建 session:新增顶层
auto_commit_policyauto_commit_policy作为顶层字段,与memory_policy平级(没有config包装层)。auto_commit_policy:自动 commit 关闭(除非服务端default_enabled=true),响应返回auto_commit_policy: null。{}或任意字段:为该 session 开启自动 commit,缺失字段用默认值补齐。创建与
GET /api/v1/sessions/{id}的响应都在顶层返回生效后的auto_commit_policy(或null):{ "status": "ok", "result": { "session_id": "a1b2c3d4", "uri": "viking://user/alice/sessions/a1b2c3d4", "user": { "account_id": "default", "user_id": "alice" }, "auto_commit_policy": { "pending_token_threshold": 8000, "message_count_threshold": 50, "idle_timeout_seconds": 86400, "keep_recent_count": 10, "min_commit_interval_seconds": 0 } } }GET响应还会带上记账字段:last_message_at、last_auto_commit_at、auto_commit_last_error、auto_commit_last_error_at。SDK / CLI
Python / Go / TypeScript SDK 的
create_session均新增auto_commit_policy参数(Go 为AutoCommitPolicy,TS 为autoCommitPolicy)。请求体统一使用顶层auto_commit_policy键。服务端配置
新增
memory.session_auto_commit(SessionAutoCommitConfig),是服务端全局开关,不是单 session 业务 policy:default_enabledfalseidle_enabledfalsecheck_interval_seconds60.0> 0scan_batch_size16> 0scan_batch_pause_seconds0.0idle_enabled影响。idle_enabled=true时才启动SessionAutoCommitScheduler,按周期扫描 AGFS/local/{account}/user/{user}/sessions下的.meta.json;不做单独的启动恢复扫描。分布式与性能
task_tracker.has_running()而非仅进程内集合;idle 扫描与内联触发都通过has_running让重复触发退化为跳过。commit 本身仍走既有 path lock 保证串行正确性。add_message之后做一次内存态阈值比较;不引入轮询线程(idle scheduler 仅在显式开启时才存在)。policy 不可变,因此热路径没有动态探测/协商逻辑。主要文件
openviking/session/auto_commit_policy.pyAutoCommitPolicy:默认值、上限、clamp、校验(单一收敛点)openviking/service/session_auto_commit.pySessionAutoCommitScheduler+ policy 解析/idle 判定辅助函数openviking/service/session_service.pycreate落 policy、maybe_schedule_auto_commit/run_auto_commit去重节流、effective_auto_commit_policyopenviking/session/session.pySessionMeta新增 policy 与记账字段;commit_async透传persist_keep_recent_count/record_auto_commit_successopenviking/server/routers/sessions.pyauto_commit_policy请求/响应;add_message 后内联触发openviking/service/core.pymemory.session_auto_commit启停 idle scheduleropenviking_cli/utils/config/memory_config.pySessionAutoCommitConfig全局配置sdk/python、sdk/go、sdk/typescript;client:openviking/{async,sync}_client.py、openviking_cli/client/base.pyauto_commit_policy参数透传测试
tests/unit/session/test_auto_commit_policy.py(policy 校验/clamp)、tests/unit/service/test_session_auto_commit.py(调度器、idle 判定、去重、节流、记账)。tests/server/test_api_sessions.py(创建/读取顶层auto_commit_policy、默认值填充、越界 clamp、未知字段拒绝、default_enabled行为、不可变)。default_enabled、空 policy、越界 clamp、未知字段拒绝、account 隔离、多轮commit_count递增,以及越阈值并发写入去重(10 并发 → 1 个 commit 任务)。最近一轮 28/28 通过。兼容性
.meta.json没有这些字段时按默认(policy 为空 = 关闭),读写不受影响。commit_async;不修改既有 commit 的对外语义。default_enabled=false、idle_enabled=false),不改变现有部署的默认行为。