元数据与落盘

Session SQLite、canonical Message 与 Composer 派生上下文

SDK Session 保存在 City 提供的 Agent 存储作用域中;没有 City 时默认使用内存存储。应用应通过 Session API 读写,不要把这些文件当作自己的数据库。

~/.downcity/agents/<agent_id>/sessions/<origin_type>/<session_id>/
├── session.db
└── attachments/

一个 Session 对应一个 session.db。核心表为:

  • session_state:Session 身份、当前目录、配置、标题、预览和 system snapshot。
  • messages:User/Agent Message 的 envelope。
  • message_parts:每个 Message 的独立 Part;一个 content 对应一个 Part。

读取 API 返回聚合后的 SessionMessage,数据库表结构不会泄漏到 UI 或模型 Provider。Message 是 canonical history,永远不会因为上下文压缩而删除或改写。

session_state.workspace_id 记录的是该会话的当前目录,不是身份:它可以在会话中改变, 改变不影响 session_id、历史与附件归属。Agent 自己的那段对话位于固定的 main 分区, 路径为 sessions/main/main/。

Composer 与派生上下文

SessionComposer 负责生成最终送给模型的 system、messages 和 tools。调用方把 canonical history 与带 命名空间的派生存储传入每次调用,Composer 不会自己去打开 Session 存储。

默认 DefaultSessionComposer 以 Part 为上下文处理边界,并在自己的 composer_adaptive_part_checkpoints 派生表中保存累计摘要边界。它的 compose() 返回:

显式 <session-context-summary> system block
+ 边界之后的 canonical Parts

摘要与其他投影出的上下文都是可重建的派生数据,不是 Message。模型返回上下文超限,或真实 usage 超过 压力阈值时,Session 通过 advance_context() 请求 Composer 推进派生状态;不会写入 Active、Segment 或第二份消息文件。

中断恢复与事务

Message envelope 与 Parts 在同一个 SQLite 事务中提交,只有提交成功后才发布 Mutation。启动时 SessionMessages 会把 Storage 查询到的 streaming Agent Message 和 running Action 收口为可恢复状态;Storage 不解释业务终态。附件仍放在 attachments/,Message Part 只保存附件引用。

模型流遵循“事件负责实时展示、内存负责流式组装、数据库只保存稳定语义检查点”:普通 chunk 不写 SQLite;完整模型 Step、Tool 执行前后、Interaction、Action/Error 和 Message 收口才提交事务。

旧版本 JSONL 数据不由 Desktop 或 Runtime 自动迁移。需要迁移时,在应用外执行仓库提供的一次性脚本 scripts/migrate-session-storage-to-sqlite.mjs。已经生成旧 SQLite 的开发期数据使用 scripts/migrate-session-sqlite-v3.mjs 升级;Runtime 只检查版本,不会隐式修改磁盘。Interaction 并入 Tool Part 之前生成的数据库仍保留独立的 Interaction 行,读取时会被拒绝;执行 scripts/migrate-session-interaction-into-tool.mjs 把它们移回所属 Tool Part。