| 1 | # 会话恢复与并行工作 |
| 2 | |
| 3 | Reasonix 将会话记录持久化和工作区文件修改分成两条安全边界。只读任务 |
| 4 | 和互不重叠的文件声明可以并行执行;不透明写入(例如无限制 shell 或未知 |
| 5 | MCP 修改)继续使用工作区写租约。需要独立工作区的任务可以使用 Git |
| 6 | worktree 隔离。 |
| 7 | |
| 8 | ## 会话版本 |
| 9 | |
| 10 | 格式 2 的会话日志把同一会话的所有版本保存为其只追加 DAG 的 head |
| 11 | (见 [会话所有权](./SESSION_OWNERSHIP.zh-CN.md))。head 分为几类: |
| 12 | |
| 13 | - `main`:日志起始的那条线; |
| 14 | - `fork`、`rewind`:来自用户操作——从消息分叉、`/branch` 和对话回溯; |
| 15 | - `concurrent`:保存时发现另一写者位于同一条链上而分出的 head。 |
| 16 | |
| 17 | 其中一个 head 是*选中*的,打开会话即打开它。head 在日志内部列出、切换、 |
| 18 | 改名和退役,不会产生第二个会话文件;退役 head 的字节保留到单写者轮转日志 |
| 19 | 时才回收。 |
| 20 | |
| 21 | 格式 1 的会话(1.39.0 之前保存且尚未升级)仍在 branch metadata 中带有 |
| 22 | 版本身份: |
| 23 | |
| 24 | - `normal`:普通会话记录; |
| 25 | - `recovery`:保存冲突、文件锁超时或外部删除后保留的恢复记录; |
| 26 | - `subagent`:仅用于持久化的子代理会话。 |
| 27 | |
| 28 | 旧 sidecar 仍然可读。缺少显式版本字段但带有 `Recovered=true` 的记录会被 |
| 29 | 解释为 recovery 版本。恢复元数据记录父会话、父版本以及冲突时观察到的 |
| 30 | base/disk revision;恢复副本留在同一条逻辑会话谱系中,不会被当作普通会话 |
| 31 | 或子代理。 |
| 32 | |
| 33 | ## 恢复生命周期 |
| 34 | |
| 35 | 格式 2 的保存不会冲突。本地没有新增时直接跟随另一写者的追加;双方都有新增 |
| 36 | 时,保存分叉出 `concurrent` head,双方各收到一次提示。版本对话框同时列出两个 |
| 37 | head,由用户选择。不再有 `pending` 状态,也没有需要重试的租约移交:lease 只 |
| 38 | 决定谁写派生 transcript、索引和回合账本。 |
| 39 | |
| 40 | 格式 1 会话中,如果磁盘内容只是可采用的已保存前缀,系统直接采用磁盘版本, |
| 41 | 不创建新版本;如果两边有独立新增内容,系统通过现有 CAS 和 digest 校验保留 |
| 42 | recovery 版本。租约移交失败时,恢复版本会标记为 `pending`,占用者释放后桌面端 |
| 43 | 可以重试激活。恢复谱系收敛操作是幂等的:已被完整覆盖的副本可以移动到可恢复的 |
| 44 | 会话回收站,存在独立内容的分歧版本继续保留,等待用户明确选择。 |
| 45 | |
| 46 | 桌面桥接层提供:`GetRecoveryLineage` 与 `GetSessionVersionState`,列出格式 2 |
| 47 | 日志的 head(状态为 `heads`;所有成员共享日志路径,并带有 `headId`、 |
| 48 | `headKind`、`headName`、`selected`);`ChooseRecoveryBranch` 与 |
| 49 | `SetActiveSessionVersion` 接受 `headId`,把某个 head 设为当前(已打开的标签页 |
| 50 | 就地切换,未打开的会话写入 `select` 标记);`CleanRecoveryLineage` 退役已覆盖 |
| 51 | 的 head,并把最近一分钟内仍有活动的 head 报告为“正在使用”; |
| 52 | `RenameSessionHead` 给 head 命名;以及面向格式 1 的 `RetrySessionRecovery` |
| 53 | 和 `ReconcileRecoveryVersions`。若某个格式 1 根会话在已有恢复副本之后才被 |
| 54 | 升级,日志的 head 与这些副本会一起列出。worktree 状态查询和合并准备复用现有 |
| 55 | 后端检查及身份校验。 |
| 56 | |
| 57 | ## 遗留恢复副本 |
| 58 | |
| 59 | 升级前产生的 `-recovery-` 文件不会导入日志。它们仍是同一谱系中的格式 1 |
| 60 | 会话:在“查看版本”中列出、可以选择,已覆盖的副本继续由现有清扫和 |
| 61 | `reasonix sessions cleanup` 移入可恢复回收站。格式 2 日志不会形成恢复分组, |
| 62 | 因此 cleanup 对它的候选数恒为零;`reasonix sessions diagnose` 会在恢复副本 |
| 63 | 数字旁边给出会话日志数、head 数、已覆盖 head 数和已退役 head 数。 |
| 64 | |
| 65 | ## 兼容性 |
| 66 | |
| 67 | | 字段或格式 | 旧数据行为 | 新读者行为 | 旧读者行为 | 结论 | |
| 68 | | --- | --- | --- | --- | --- | |
| 69 | | `.events.jsonl` 格式 1 | 不变 | 重放;消息 id 确定性派生 | 不变 | 兼容 | |
| 70 | | `.events.jsonl` 格式 2 | 无 | 原生 | 拒绝打开,文件不改 | 明确边界(≥ 1.39.0) | |
| 71 | | `.jsonl` transcript | 格式不变 | 由选中 head 派生 | 可读但不权威 | 兼容 | |
| 72 | | `.jsonl.meta` head 字段 | 缺省 → 视为格式 1 | 使用 | 忽略 | 兼容 | |
| 73 | | `.event-index.json` schema 2 | 拒绝 schema 1 索引 → 重放 | 原生 | 拒绝 → 慢路径 | 兼容 | |
| 74 | | `-recovery-<hex>.jsonl` | 格式 1 谱系 | 不导入,照常列出 | 不变 | 兼容 | |
| 75 | | 会话目录 `v8.sqlite` | v7 文件隔离 | 原生 | 各用各的代际文件 | 兼容 | |
| 76 | |
| 77 | 格式 2 需要 Reasonix 1.39.0 及更新版本打开。混用新旧版本时应先升级旧的一方 |
| 78 | 再共享会话目录;旧版本会报告遇到了更新的格式,且不会修改文件。 |
| 79 |