Skip to content

feat(server): add database-backed stores for task and runtime state#305

Merged
liujuanjuan1984 merged 6 commits intomainfrom
eval/database-store
Mar 24, 2026
Merged

feat(server): add database-backed stores for task and runtime state#305
liujuanjuan1984 merged 6 commits intomainfrom
eval/database-store

Conversation

@liujuanjuan1984
Copy link
Collaborator

@liujuanjuan1984 liujuanjuan1984 commented Mar 23, 2026

概要

实现 #304:为服务引入可配置的 database-backed task store,并将跨重启仍需保留的 session / interrupt runtime state 收口到同一持久化后端。

改动模块

1. 配置与依赖

  • pyproject.toml
  • uv.lock
  • src/opencode_a2a/config.py

新增:

  • A2A_TASK_STORE_BACKEND
  • A2A_TASK_STORE_DATABASE_URL
  • A2A_TASK_STORE_TABLE_NAME
  • A2A_TASK_STORE_CREATE_TABLE
  • A2A_PENDING_SESSION_CLAIM_TTL_SECONDS

补充 sqlalchemy / aiosqlite 依赖,并在 A2A_TASK_STORE_BACKEND=database 时要求显式提供 database URL。

说明:

  • A2A_TASK_STORE_CREATE_TABLE 统一控制 task table 与 runtime state tables 的自动创建。
  • A2A_PENDING_SESSION_CLAIM_TTL_SECONDS 为 preferred session claim 提供显式租约时长,避免异常退出后的长期阻塞。

2. App 装配与 Task Store

  • src/opencode_a2a/server/task_store.py
  • src/opencode_a2a/server/application.py

新增 task store builder,按配置选择:

  • InMemoryTaskStore
  • DatabaseTaskStore

在 app lifespan 中统一初始化 task store / state repository,并在退出时释放 shared database engine。

3. Session / Interrupt 状态持久化

  • src/opencode_a2a/runtime_state.py
  • src/opencode_a2a/server/state_store.py
  • src/opencode_a2a/execution/session_manager.py
  • src/opencode_a2a/opencode_upstream_client.py
  • src/opencode_a2a/execution/stream_runtime.py
  • src/opencode_a2a/jsonrpc/application.py
  • src/opencode_a2a/execution/executor.py

新增 repository 抽象与 memory/database 双后端,接入:

  • task records
  • session binding
  • session ownership
  • pending preferred session claims
  • interrupt request bindings
  • interrupt tombstones

修正:

  • database backend 下已拥有 preferred session 的同一 identity 复用路径现在会通过 repository 正确回写 (identity, context_id) -> session_id 绑定,不再残留内存分支调用。
  • pending preferred session claims 现在在 memory / database backend 下都按 TTL 语义过期清理。
  • SessionManager 不再暴露 _sessions / _session_owners / _pending_session_claims 这组 memory alias,避免后续继续误把内存后端当成权威状态源。

4. Contract、文档与测试

  • src/opencode_a2a/contracts/extensions.py
  • docs/guide.md
  • tests/server/test_task_store_factory.py
  • tests/server/test_state_store.py
  • tests/server/test_database_app_persistence.py
  • tests/execution/test_session_ownership.py
  • 以及相关 transport / interrupt / upstream 测试更新

新增覆盖:

  • database-backed task store 的最小 persistence 验证
  • database-backed session / interrupt state persistence 验证
  • A2A_TASK_STORE_CREATE_TABLE=false 时不自动创建 state tables
  • app 重建后的端到端回归:task / session / interrupt 三类状态持续可用
  • owned preferred session 在 database backend 下的幂等复用
  • expired pending preferred session claim 不再跨重启长期阻塞其他 identity

对齐:

  • machine-readable session binding contract 不再把 (identity, contextId) -> session_id 固定描述成 memory-only。
  • interrupt callback 文档不再把 binding registry 固定描述成 in-memory cache,而是与当前 backend 语义保持一致。

当前边界

仍保持进程内状态,不进入数据库:

  • session in-flight create tasks
  • asyncio locks
  • outbound A2A client cache
  • stream-local aggregation state

验证

  • ./scripts/doctor.sh
  • 结果:361 passed

相关 commits

  • 10573d5 feat(server): add database-backed task store backend (#304)
  • e51bc97 feat(server): persist session and interrupt state in database backend (#304)
  • 3328523 test(server): cover app restart persistence for database backend (#304)
  • 8a5889d fix(server): align database state tables with create-table config (#304)
  • 9c8cb63 fix(server): harden preferred session claim persistence (#304)
  • 11203d7 fix(contracts): remove stale memory-store assumptions (#304)

Issue 关联

Closes #304
Relates to #264

说明:本 PR 直接落地 #304 的 durability 主诉求;对 #264 属于母单下的相关演进项,因此保持 Relates to 更准确。

@liujuanjuan1984
Copy link
Collaborator Author

本轮审查结论:实现方向合理,#304 的核心目标已经落地,且 task / session / interrupt 三类状态都补上了回归覆盖。

发现 1 个需要明确的风险和 1 个非阻塞清理点:

  1. 中:A2A_TASK_STORE_CREATE_TABLE 当前只作用于 task table,不作用于 runtime state tables。

    • 位置:src/opencode_a2a/server/task_store.py:65-70src/opencode_a2a/server/state_store.py:187-192src/opencode_a2a/server/state_store.py:523-528
    • 说明:PR 新增了 database-backed session / interrupt state,但这两类 repository 在初始化时始终执行 create_all。如果部署方把 A2A_TASK_STORE_CREATE_TABLE=false 理解成“所有数据库表都不自动建表”,当前行为会超出预期。
    • 判断:这不影响当前功能正确性,但会影响数据库接入边界,尤其是依赖外部 migration / 受限权限数据库的部署。
  2. 低:close_task_store() 目前只在测试里使用,主路径不再消费。

    • 位置:src/opencode_a2a/server/task_store.py:92-95
    • 说明:app lifespan 现在直接持有并释放共享 engine,这个 helper 已经不是主实现路径的一部分。
    • 判断:不阻塞合并,但后续可以继续收口,避免保留“看起来像生产路径、实际上只服务测试”的额外 helper。

除此之外,这个 PR 的 Closes #304 / Relates to #264 是准确的。

@liujuanjuan1984
Copy link
Collaborator Author

已基于当前分支修掉上一轮审查里的两个点:

  1. A2A_TASK_STORE_CREATE_TABLE 现在同时约束 runtime state tables 的自动创建。

    • DatabaseSessionStateRepository / DatabaseInterruptRequestRepository 新增 create_tables 开关。
    • build_session_state_repository() / build_interrupt_request_repository() 现在会传入 settings.a2a_task_store_create_table
    • 新增测试覆盖 A2A_TASK_STORE_CREATE_TABLE=false 时不自动创建 state tables。
  2. 删除了只服务测试的生产 helper close_task_store()

    • 测试改为直接释放 task store 持有的 engine。

补充验证:

  • uv run pre-commit run --all-files
  • uv run pytest
  • 结果:357 passed

新增 commit:

  • 8a5889d fix(server): align database state tables with create-table config (#304)

@liujuanjuan1984 liujuanjuan1984 changed the title feat(server): add database-backed persistence for task and runtime state feat(server): add database-backed stores for task and runtime state Mar 24, 2026
@liujuanjuan1984
Copy link
Collaborator Author

审查结论

本轮审查发现 2 个需要处理的问题:

  1. 高风险:database backend 下,已拥有 session 的同一 identity 再次通过 preferred_session_id 进入时会触发 AttributeError

    • 位置:src/opencode_a2a/execution/session_manager.py:44-51
    • 原因:claim_preferred_session() 返回 False 时,代码仍直接写 self._sessions.set(...);database repository 路径里 _sessions 被置为 None
    • 复现:先执行 finalize_session_claim(identity='user-1', session_id='session-X'),再调用 get_or_create_session(..., preferred_session_id='session-X'),实际抛出 'NoneType' object has no attribute 'set'
    • 影响:session control / preferred session 复用在 database backend 下不是幂等成功,而是直接失败。
  2. 中风险:pending preferred session claims 被持久化后没有 TTL / 回收语义,异常退出后可能长期阻塞其他 identity 认领同一 session。

    • 位置:src/opencode_a2a/server/state_store.py:50-55src/opencode_a2a/server/state_store.py:365-409
    • 现状:表里只有 updated_at,没有 expires_at,读取路径也没有清理逻辑。
    • 复现:一次 claim 未 finalize/release 后重启服务,其他 identity 再次 claim 同一 session 会持续收到 PermissionError
    • 风险:这改变了此前内存态“进程退出即释放”的语义,也会让临时失败升级为持久阻塞。

补充:

  • ./scripts/doctor.sh 当前通过,结果为 357 passed,说明现有测试没有覆盖上述场景。
  • Closes #304Relates to #264 的 issue 关联关系目前是准确的。

@liujuanjuan1984
Copy link
Collaborator Author

已修复上一轮审查提出的 2 个问题,并补齐回归覆盖:

  1. database backend 下 owned preferred session 的复用不再走残留的内存 _sessions.set(...) 分支。

    • 现在统一通过 repository 回写 (identity, context_id) -> session_id
    • 新增回归测试覆盖 database backend 下的幂等复用路径。
  2. pending preferred session claim 现在具备显式 TTL 语义,不再因异常退出而长期阻塞其他 identity。

    • 新增配置:A2A_PENDING_SESSION_CLAIM_TTL_SECONDS
    • memory / database backend 都按同一租约语义处理;database 实现复用现有 updated_at 推断过期,避免引入额外表结构迁移。
    • 新增回归测试覆盖 memory/database repository 的过期清理,以及 database backend 下“过期后其他 identity 可重新 claim”的 manager 行为。

补充验证:

  • ./scripts/doctor.sh
  • 结果:361 passed

新增 commit:

  • 9c8cb63 fix(server): harden preferred session claim persistence (#304)

@liujuanjuan1984
Copy link
Collaborator Author

补充收尾了上一轮审查里提到的 3 个“内存残留”问题:

  1. SessionManager 已移除 _sessions / _session_owners / _pending_session_claims 这组 memory alias。

    • 生产逻辑现在只通过 repository 读写状态,避免后续再误把 memory backend 当成权威来源。
    • 相关测试也已改为通过 repository 断言状态。
  2. machine-readable session binding contract 已更新。

    • 不再把 (identity, contextId) -> session_id 固定描述成 “in memory with TTL”。
    • 现在改为按 configured task/state store backend 与 TTL policy 表达。
  3. docs/guide.md 中 interrupt callback 的说明已更新。

    • 不再把 interrupt binding registry 固定描述成 “in-memory cache”。
    • 与前文 database backend 可持久化 interrupt bindings / tombstones 的语义保持一致。

补充验证:

  • ./scripts/doctor.sh
  • 结果:361 passed

新增 commit:

  • 11203d7 fix(contracts): remove stale memory-store assumptions (#304)

@liujuanjuan1984
Copy link
Collaborator Author

合并前补充审查结论:

  1. 代码层面目前没有发现新的阻塞问题。

    • #304 关注的 task durability、session binding / ownership、interrupt binding / tombstone 持久化路径都已落地。
    • 后续两轮修复也把 preferred session claim 的幂等复用、TTL 语义,以及 contract / docs 漂移一并收口了。
  2. PR 元数据存在一个实际问题,已修正。

    • 之前描述里的 Closes #304 放在代码格式里,GitHub 不会识别为 closing reference。
    • 现已改为普通文本,closing issue 关联恢复正常。
  3. issue 关系判断保持不变。

    • Closes #304:准确,本 PR 直接交付该 issue 的主需求。
    • Relates to #264:准确,#264 是更上层的演进母单,不应被本 PR 自动关闭。

@liujuanjuan1984 liujuanjuan1984 marked this pull request as ready for review March 24, 2026 03:26
@liujuanjuan1984 liujuanjuan1984 merged commit 0bc24d3 into main Mar 24, 2026
3 checks passed
@liujuanjuan1984 liujuanjuan1984 deleted the eval/database-store branch March 24, 2026 03:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Priority: High] [Architecture] 评估并推进从 InMemory Store 到 Database Store 的迁移

1 participant