Skip to content

Feat/session auto commit v2 - #3736

Merged
qin-ctx merged 3 commits into
mainfrom
feat/session-auto-commit-v2
Aug 5, 2026
Merged

Feat/session auto commit v2#3736
qin-ctx merged 3 commits into
mainfrom
feat/session-auto-commit-v2

Conversation

@zhoujh01

@zhoujh01 zhoujh01 commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

feat(session): 会话自动 commit(best-effort 简化实现)

背景与动机

会话(session)在使用过程中会不断累积 live message。只有触发 commit 才会把这些消息归档(archive)并抽取记忆(memory extraction)。此前完全依赖调用方显式调 commit:调用方不 commit,消息就一直堆在 live 区,既拿不到记忆,也让 session 越来越大。

本 PR 让服务端能在满足条件时自动触发 commit,调用方无需自己管理 commit 时机。

本 PR 是对早期方案(#2772)的重写。早期方案引入了较重的并发状态机、重试与轮询逻辑,多轮 review 难以收敛。这一版按 best-effort(尽力而为) 模型重新实现:满足条件就触发一次,触发不了就跳过,由后续的消息写入或 idle 扫描自然重新触发,不做重试、不做轮询、不做补偿

设计概览

自动 commit 由两条路径触发,共用同一套 policy 与去重逻辑:

  1. 内联阈值触发(inline):每次 add_message / batch_add_messages 写入后,检查该 session 是否越过 token 或消息数阈值,越过则尝试触发一次后台 commit。触发发生在消息已写入之后,不阻塞写入返回。
  2. idle 超时触发(scheduler):一个服务端后台调度器周期性扫描所有 session 的 .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_threshold 10000 50000 未提交 pending token 严格大于该值时,写入后触发 commit
message_count_threshold 50 500 未提交 live message 数严格大于该值时,写入后触发 commit
idle_timeout_seconds 86400 604800 有未提交内容的 session 空闲这么多秒后,进入 idle 调度处理范围
keep_recent_count 2 500 阈值触发的 commit 后保留(不归档)的最近消息数;idle 触发忽略此值,全部归档
min_commit_interval_seconds 0 604800 两次自动 commit 之间的最小间隔(节流)

clamp 与默认值填充统一收敛在 AutoCommitPolicy.from_dict() 一处,HTTP / SDK / CLI 各入口不重复实现校验。未知字段一律以 InvalidArgumentError 拒绝。

best-effort 去重模型(关键取舍)

阈值场景下,突发并发写入会在短时间内多次命中触发条件。为避免为同一个 session 同时 spawn 多个 commit 任务,去重分两层:

  • 进程内 in-flight 集合 _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_policy

auto_commit_policy 作为顶层字段,与 memory_policy 平级(没有 config 包装层)。

POST /api/v1/sessions
Content-Type: application/json

{
  "auto_commit_policy": {
    "pending_token_threshold": 8000,
    "message_count_threshold": 40,
    "idle_timeout_seconds": 600,
    "keep_recent_count": 10,
    "min_commit_interval_seconds": 0
  }
}
  • 不传 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_atlast_auto_commit_atauto_commit_last_errorauto_commit_last_error_at

SDK / CLI

Python / Go / TypeScript SDK 的 create_session 均新增 auto_commit_policy 参数(Go 为 AutoCommitPolicy,TS 为 autoCommitPolicy)。请求体统一使用顶层 auto_commit_policy 键。

result = await client.create_session(
    auto_commit_policy={"pending_token_threshold": 8000, "keep_recent_count": 10}
)
print(result["auto_commit_policy"])

服务端配置

新增 memory.session_auto_commitSessionAutoCommitConfig),是服务端全局开关,不是单 session 业务 policy:

参数 类型 默认 说明
default_enabled bool false 对未显式传 policy 的新 session 是否默认开启自动 commit
idle_enabled bool false 是否启动 idle 超时调度器;关闭则不启动 scheduler,但 token/消息数内联触发仍生效
check_interval_seconds float 60.0 idle 调度器扫描周期,必须 > 0
scan_batch_size int 16 每批并发读取的 session meta 数,必须 > 0
scan_batch_pause_seconds float 0.0 批次之间的可选暂停,降低大规模扫描时的存储压力
  • token / 消息数内联触发不依赖 scheduler,也不受 idle_enabled 影响。
  • idle_enabled=true 时才启动 SessionAutoCommitScheduler,按周期扫描 AGFS /local/{account}/user/{user}/sessions 下的 .meta.json;不做单独的启动恢复扫描。

分布式与性能

  • 分布式安全:跨 worker 去重依赖 task_tracker.has_running() 而非仅进程内集合;idle 扫描与内联触发都通过 has_running 让重复触发退化为跳过。commit 本身仍走既有 path lock 保证串行正确性。
  • 无额外热路径开销:内联触发只在 add_message 之后做一次内存态阈值比较;不引入轮询线程(idle scheduler 仅在显式开启时才存在)。policy 不可变,因此热路径没有动态探测/协商逻辑。
  • best-effort 不补偿:任何一次触发失败/被跳过都不重试,由下一次写入或下一轮 idle 扫描自然重新触发。

主要文件

文件 说明
openviking/session/auto_commit_policy.py AutoCommitPolicy:默认值、上限、clamp、校验(单一收敛点)
openviking/service/session_auto_commit.py idle 调度器 SessionAutoCommitScheduler + policy 解析/idle 判定辅助函数
openviking/service/session_service.py 编排:create 落 policy、maybe_schedule_auto_commit / run_auto_commit 去重节流、effective_auto_commit_policy
openviking/session/session.py SessionMeta 新增 policy 与记账字段;commit_async 透传 persist_keep_recent_count / record_auto_commit_success
openviking/server/routers/sessions.py 顶层 auto_commit_policy 请求/响应;add_message 后内联触发
openviking/service/core.py memory.session_auto_commit 启停 idle scheduler
openviking_cli/utils/config/memory_config.py SessionAutoCommitConfig 全局配置
SDK:sdk/pythonsdk/gosdk/typescript;client:openviking/{async,sync}_client.pyopenviking_cli/client/base.py 各层 auto_commit_policy 参数透传

测试

  • 单元测试:tests/unit/session/test_auto_commit_policy.py(policy 校验/clamp)、tests/unit/service/test_session_auto_commit.py(调度器、idle 判定、去重、节流、记账)。
  • HTTP 接口测试:tests/server/test_api_sessions.py(创建/读取顶层 auto_commit_policy、默认值填充、越界 clamp、未知字段拒绝、default_enabled 行为、不可变)。
  • 端到端:本地向量库 + 真实模型的 shell E2E(不入库),覆盖 token/消息数/idle 触发、节流、default_enabled、空 policy、越界 clamp、未知字段拒绝、account 隔离、多轮 commit_count 递增,以及越阈值并发写入去重(10 并发 → 1 个 commit 任务)。最近一轮 28/28 通过

兼容性

  • 纯增量:老 session 的 .meta.json 没有这些字段时按默认(policy 为空 = 关闭),读写不受影响。
  • 不新增 commit 流程,复用既有 commit_async;不修改既有 commit 的对外语义。
  • 默认全部关闭(default_enabled=falseidle_enabled=false),不改变现有部署的默认行为。

zhoujh01 and others added 2 commits August 4, 2026 20:44
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>
@zhoujh01
zhoujh01 marked this pull request as ready for review August 4, 2026 12:49
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
qin-ctx merged commit d2056e9 into main Aug 5, 2026
7 of 8 checks passed
@qin-ctx
qin-ctx deleted the feat/session-auto-commit-v2 branch August 5, 2026 08:18
@github-project-automation github-project-automation Bot moved this from Backlog to Done in OpenViking project Aug 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants