Files
nanoclaw/docs/zh/db-session.md
2026-05-12 13:14:17 +00:00

8.4 KiB
Raw Blame History

NanoClaw — 每个 Session 的 DB Schema

每个 session会话拥有的两个 SQLite 文件的参考:inbound.db(宿主机写入,容器读取)和 outbound.db(容器写入,宿主机读取)。请先阅读 db.md 了解三库概览、单一写入者规则以及跨挂载可见性约束。

Schema 位于 src/db/schema.ts 中,作为 INBOUND_SCHEMAOUTBOUND_SCHEMA 常量。当新的 session 目录被配置时,两个文件都由 src/session-manager.ts 中的 ensureSchema() 创建。


1. Session 目录布局

data/v2-sessions/<agent_group_id>/<session_id>/
  inbound.db              ← 宿主机写入,容器读取(只读挂载)
  outbound.db             ← 容器写入,宿主机读取(只读打开)
  .heartbeat              ← 容器触碰的 mtime非 DB 写入)
  inbox/<message_id>/     ← 用户附件,从入站消息内容解码
  outbox/<message_id>/    ← agent 生成的附件

一个 session = 一个目录 = 一对 DB。agent_group_id 父目录还存放每个 agent group 共享的状态(.claude-shared/agent-runner-src/),这些状态在该 agent group 的所有 session 间共享。

src/session-manager.ts 中的路径辅助函数:sessionDir()inboundDbPath()outboundDbPath()heartbeatPath()


2. 入站库 (inbound.db)

宿主机拥有容器只读。Schema 常量:src/db/schema.ts 中的 INBOUND_SCHEMA

2.1 messages_in

抵达 session 的每条消息:用户聊天、计划任务、重复任务、问题回复、内部系统消息。

CREATE TABLE messages_in (
  id             TEXT PRIMARY KEY,
  seq            INTEGER UNIQUE,           -- 仅偶数(宿主机分配)——见 §3
  kind           TEXT NOT NULL,
  timestamp      TEXT NOT NULL,
  status         TEXT DEFAULT 'pending',   -- pending|completed|failed|paused
  process_after  TEXT,
  recurrence     TEXT,                     -- 重复任务的 cron 表达式
  series_id      TEXT,                     -- 将重复任务的发生次数分组
  tries          INTEGER DEFAULT 0,
  trigger        INTEGER NOT NULL DEFAULT 1, -- 0 = 仅上下文不唤醒1 = 唤醒 agent
  platform_id    TEXT,
  channel_type   TEXT,
  thread_id      TEXT,
  content        TEXT NOT NULL,            -- JSON格式取决于 kind
  source_session_id TEXT,                  -- agent 到 agent 的返回路径
  on_wake        INTEGER NOT NULL DEFAULT 0 -- 1 = 仅在容器的首次轮询时投递
);
CREATE INDEX idx_messages_in_series ON messages_in(series_id);

内容格式:见 api-details.md §Session DB Schema Details

写入者(宿主机): insertMessage()insertTask()insertRecurrence()——均在 src/db/session-db.ts 中。每个都调用 nextEvenSeq()读取者(容器): container/agent-runner/src/db/messages-in.ts——轮询 status='pending' AND (process_after IS NULL OR process_after <= now)

2.2 delivered

宿主机在将 messages_out 行交给频道适配器后写入此处。容器读取 platform_message_id 以定位编辑和反应。

CREATE TABLE delivered (
  message_out_id      TEXT PRIMARY KEY,
  platform_message_id TEXT,
  status              TEXT NOT NULL DEFAULT 'delivered',  -- delivered|failed
  delivered_at        TEXT NOT NULL
);

写入者:src/db/session-db.ts 中的 markDelivered() / markDeliveryFailed()。较旧的 session DB 由 migrateDeliveredTable() 惰性地升级 schema。

2.3 destinations

本 session 的 agent 对应的中央 agent_destinations 表(见 db-central.md §1.10)的投影。容器根据此表解析 to="name";如果行不存在,则拒绝发送并报 unknown destination

CREATE TABLE destinations (
  name           TEXT PRIMARY KEY,
  display_name   TEXT,
  type           TEXT NOT NULL,   -- 'channel' | 'agent'
  channel_type   TEXT,            -- 用于 type='channel'
  platform_id    TEXT,            -- 用于 type='channel'
  agent_group_id TEXT             -- 用于 type='agent'
);

在每次容器唤醒时以及连接配置在 session 中途变更时,由 writeDestinations() 整体重写(事务中的 DELETE + INSERTsrc/db/schema.ts 中该表的注释是刷新语义的规范陈述。

2.4 session_routing

单行(id=1)默认路由:当 agent 未指定目的地时,出站消息的走向。

CREATE TABLE session_routing (
  id           INTEGER PRIMARY KEY CHECK (id = 1),
  channel_type TEXT,
  platform_id  TEXT,
  thread_id    TEXT
);

writeSessionRouting() 在每次容器唤醒时写入,数据来源于 sessions.messaging_group_id + sessions.thread_id


3. 序列号奇偶规则

每条消息(入站或出站)获得一个单调递增的整数 seq,在 session 内跨两张表唯一。

  • 宿主机写入偶数 seq2、4、6、…messages_in——src/db/session-db.ts:75 中的 nextEvenSeq()
  • 容器写入奇数 seq1、3、5、…messages_out——逻辑在 container/agent-runner/src/db/messages-out.ts:54max % 2 === 0 ? max + 1 : max + 2),跨两张表读取 MAX(seq) 以保持全局顺序。

为什么不相交?seq 是 agent 视角的消息 ID。当 agent 调用 edit_message(seq=5)add_reaction(seq=6) 时,getMessageIdBySeq() 利用奇偶来路由查找:奇数 → messages_out,偶数 → messages_in。单凭奇偶就能消除歧义,无需连接。冲突会破坏编辑功能。

如果你添加了一条写入任一张表的代码路径,请保持奇偶规则——该规则不是通过约束强制执行的,只有两个辅助函数在维护。


4. 出站库 (outbound.db)

容器拥有宿主机只读。Schema 常量:src/db/schema.ts 中的 OUTBOUND_SCHEMA

4.1 messages_out

agent 生成的所有内容聊天回复、编辑、反应、卡片、问题发送、agent 到 agent 消息、系统动作。

CREATE TABLE messages_out (
  id            TEXT PRIMARY KEY,
  seq           INTEGER UNIQUE,   -- 仅奇数(容器分配)——见 §3
  in_reply_to   TEXT,
  timestamp     TEXT NOT NULL,
  deliver_after TEXT,
  recurrence    TEXT,
  kind          TEXT NOT NULL,    -- chat|chat-sdk|system|…
  platform_id   TEXT,
  channel_type  TEXT,
  thread_id     TEXT,
  content       TEXT NOT NULL     -- JSON操作类型包含在其中edit/reaction/card/…)
);

内容格式:见 api-details.md §Session DB Schema Details

写入者(容器): container/agent-runner/src/db/messages-out.ts 中的 writeMessageOut()读取者(宿主机): src/delivery.ts(轮询投递),getMessageIdBySeq() / getRoutingBySeq() 用于编辑/反应定位。

4.2 processing_ack

容器侧对它所接触的每个 messages_in.id 的状态记录。宿主机轮询此表并将状态同步回 messages_in——这避免了容器向 inbound.db 写入。

CREATE TABLE processing_ack (
  message_id     TEXT PRIMARY KEY,
  status         TEXT NOT NULL,      -- processing|completed|failed
  status_changed TEXT NOT NULL
);

崩溃恢复:容器启动时,陈旧的 processing 条目被清除。宿主机侧同步:src/host-sweep.ts 中的 syncProcessingAcks()

4.3 session_state

持久化的容器拥有的 KV 存储。主要消费者是 Chat SDK session ID——将其存储在这里可以让 agent 的对话在容器重启后恢复。可通过 /clear 清除。

CREATE TABLE session_state (
  key        TEXT PRIMARY KEY,
  value      TEXT NOT NULL,
  updated_at TEXT NOT NULL
);

访问方式:container/agent-runner/src/db/session-state.ts


5. Schema 演化

与中央库不同session DB 经过编号迁移。INBOUND_SCHEMAOUTBOUND_SCHEMA 都使用 CREATE TABLE IF NOT EXISTS,因此新的 session 总是获得最新的 schema。对于在较旧版本下创建的 session 目录,列级别的差异在打开时惰性修补——例如 src/db/session-db.ts 中的 migrateDeliveredTable() 如果缺少 platform_message_idstatus 列,则会将其添加到 delivered 表。

如果你向任一 schema 添加了一个列,请为现有的 session 目录添加匹配的惰性迁移,并优先使用可空的列或带默认值的列,这样就不需要回填数据。