| 1 | # 向 DSH 运行机制对齐 |
| 2 | |
| 3 | 本文件固定新版会话的可执行契约;旧字段只用于历史展示。英文版 |
| 4 | [DSH_RUNTIME_ALIGNMENT.md](DSH_RUNTIME_ALIGNMENT.md) 给出同一契约及回归矩阵。 |
| 5 | |
| 6 | ## Todo |
| 7 | |
| 8 | `todo_write` 只接收必填的 `todos` 数组。每项只能有 `content` 和 `status`; |
| 9 | 内容 trim 后非空且列表内唯一,状态只能为 `pending`、`in_progress`、 |
| 10 | `completed`。每次提交整表替换,空数组合法;允许多个进行中项、仅 pending、 |
| 11 | 乱序完成、删除、重排和重新规划。成功结果是含规范化完整列表和计数的 JSON, |
| 12 | 失败不改变旧状态,展示压缩和结果去重不得替换这份状态事实。 |
| 13 | |
| 14 | todo 的生命周期是一个宿主真实回合。顶层 `turn/start` 成功提交后清空;模型 |
| 15 | 工具轮次、压缩、补充消息、批准和 Ask 回答不清空。回合完成、失败或取消后保留 |
| 16 | 最后一次成功列表用于展示,下一回合开始时再清空。Goal 续跑属于新回合。 |
| 17 | |
| 18 | 新版执行路径退役层级、`activeForm`、`step_id`、`complete_step`、宿主自动推进、 |
| 19 | Plan 自动播种、Goal 恢复 todo、完成前缀保护及完成门禁。隐藏的 |
| 20 | `complete_step` 墓碑只返回指向 `todo_write` 的退役错误;历史记录仍可查看。 |
| 21 | |
| 22 | ## 运行与交互所有权 |
| 23 | |
| 24 | 会话级 turn-loop 是执行权威:唯一持有前台准入、取消、当前回合身份、FIFO |
| 25 | 待处理输入和 level-triggered wake。`session.Runtime` 是持久化权威:会话身份、 |
| 26 | 单写者租约、内存接收和后台落盘。持久日志只记录已发生事实,进程重启后不得从 |
| 27 | 旧 running 记录恢复出并不存在的执行器。运行阶段为 `idle`、`executing`、 |
| 28 | `cancelling`、`finishing`、`recovery_required`、`closed`,兼容布尔字段由这些 |
| 29 | 状态和当前所有者派生。不再保留 Activity 许可,也不再在 Runtime 上设置第二套 |
| 30 | 准入门。 |
| 31 | |
| 32 | Stop 以 session 为目标,UI turn id 只能作为诊断信息,不能成为取消前提。取消 |
| 33 | 信号先到达已绑定的 turn-loop,再做磁盘、通知和回调清理;每一轮使用全新取消 |
| 34 | 上下文,Stop 不得污染下一轮。已有 15 秒宽限统一覆盖所属工作;无法收敛时保持 |
| 35 | `recovery_required`,迟到结果不得恢复旧回合或写入新代际。Runtime 以精确 |
| 36 | generation 绑定 turn-loop,旧 Controller 解绑不得清除新代际的控制权。 |
| 37 | |
| 38 | Ask、批准、Plan、恢复和 MCP 决策共用 `PendingPromptOwner`。身份绑定 request、 |
| 39 | 类型、turn 和 runtime epoch;回答只有一个胜者,过期回答明确拒绝;回调不在 |
| 40 | 注册表锁内执行。运行快照从该注册表派生 pending 状态,不再只看批准弹窗。 |
| 41 | 回答事件记录 `answered`、`rejected` 或 `cancelled`;Plan 回答与对应状态在同一 |
| 42 | 逻辑批次提交。回合终结会把仍未终结的请求一并关闭,重启后不会重新出现可回答授权。 |
| 43 | |
| 44 | ## 能力目录 |
| 45 | |
| 46 | `skill.Store.Snapshot` 是发现边界。每个代际提供不可变稳定顺序候选和 O(1) 名称 |
| 47 | 索引;并发冷请求共享一次扫描,单个等待者取消不影响其他等待者。失效时保留上 |
| 48 | 一个完整快照,新快照只在完整发现后发布,最多跨两个代际重试。创建、编辑、 |
| 49 | 删除及根目录变化会使快照失效。 |
| 50 | |
| 51 | `use_capability search` 每次固定使用一份目录和 MCP schema 快照;skill 参数契约 |
| 52 | 经索引读取,不会按结果逐项重扫目录。list 默认 50、最大 100,游标绑定目录 |
| 53 | 指纹,失效后要求从头开始。发现不得连接 MCP 或调用 `tools/list`。 |
| 54 | Windows 使用可取消的有界轮询代际,避免 `ReadDirectoryChangesW` 注册阻塞关闭; |
| 55 | 其他平台继续使用 fsnotify。两条路径都覆盖外部修改和缺失根目录的后续创建。 |
| 56 | |
| 57 | ## 兼容与缓存 |
| 58 | |
| 59 | todo schema 和能力分页形成一次明确的稳定前缀升级。同版本保持工具顺序、schema |
| 60 | 字节及 delivery marker 稳定;运行状态、时间戳、目录代际和 todo 内容不得进入 |
| 61 | 系统提示。旧 todo、签收、Goal todo 和 dismiss 记录只读保留,继续工作时不激活; |
| 62 | 新版 Goal 不保存 todo,Plan 不生成 todo,前端 dismiss 仅为当前挂载期展示偏好。 |
| 63 | |
| 64 | ## v3 会话边界 |
| 65 | |
| 66 | `internal/session` 定义所有权切换后的 codec `reasonix.session.linear/v3.1`。 |
| 67 | 旧原型 `reasonix.session.events/v3` 和预览格式 `reasonix.session.linear/v3` 不能直接用于 |
| 68 | 执行,只能通过受限导入器转换; |
| 69 | 未知必需事件、完整损坏记录或无法解释的历史替换都会保留原件并拒绝继续执行。 |
| 70 | 一个会话只有一个活动写句柄;分叉和编辑重发创建独立子会话,不在同一日志维护多 |
| 71 | head。一个物理 JSONL 记录保存一个完整逻辑批次,事件获得连续序号。未换行的尾 |
| 72 | 记录不会被冷读部分重放;写句柄取得独占租约后先逐字节保留该尾部,再截断回最后 |
| 73 | 一个完整批次并继续恢复。完整损坏记录、序号缺口、未知 codec 和未知必需事件都按 |
| 74 | 失败关闭处理。 |
| 75 | |
| 76 | 三层各自拥有明确事实:内存 `Session` 拥有类型化事件日志、序号分配、操作幂等表和投影; |
| 77 | `PersistenceBinding` 拥有待写队列、durable 水位和唯一排空链;物理 `Store` 只拥有 JSONL |
| 78 | 字节、写者租约和可重建偏移索引。句柄不再保存投影、操作表或已接受提交列表,因此无法 |
| 79 | 从磁盘结构反推业务状态。`Session.PrepareBatch` 在取得提交锁之前完成载荷复制、 |
| 80 | schema 校验和操作摘要;摘要只覆盖调用方提供的字段,因此重试同一逻辑批次保持幂等。 |
| 81 | `Session.CommitPrepared` 随后在一把短内存锁内完成幂等检查、序号分配、整批追加和投影 |
| 82 | 替换。批次在提交锁释放后才进入绑定队列,入队过程不做任何文件 I/O。 |
| 83 | |
| 84 | 新建 API 只在内存 Session、写句柄和不可变 session ID 已经发布后返回。标题与模型 |
| 85 | 选择(包括连接 revision)分别由 `session/title`、`session/config` 事件维护;Agent |
| 86 | 重建只替换模型上下文并追加配置事件,不创建第二个 Controller 与同一写者竞争。目录 |
| 87 | 索引和旧 model sidecar 都不是新版模型选择的事实来源。 |
| 88 | |
| 89 | 每个已发布 Runtime 由宿主持有,且只有 `RuntimeOwner` 能终止准确实例。`Service.Open` |
| 90 | 以 `ClientBinding` 附着客户端,不交出关闭权限,因此附着失败只能撤销调用方自己的绑定。 |
| 91 | Controller 只取得可发送和观察的 `ClientBinding`; |
| 92 | 关闭标签页或连接只解除自身绑定,不能关闭共享写者或取消活动回合。最后一个绑定离开 |
| 93 | 后,空闲 Runtime 由宿主回收;仍在活动的 Runtime 继续收敛,结束后再回收。准备失败 |
| 94 | 和迟到清理回调只能丢弃自己持有的准确候选或实例。取消直接触达已绑定的 |
| 95 | turn-loop,不先等待 Runtime、持久化或 UI 锁,因此回执不会被提交或磁盘操作阻塞。 |
| 96 | 会话事件只要求当前 Controller 仍持有写租约;Stop 不会撤销 `history/replace`、 |
| 97 | `turn/end`、交互收尾或诊断写入。Session 先接受,兼容 ledger 和前端投影只在 |
| 98 | Session 接收成功后推进。 |
| 99 | |
| 100 | `Append` 表示事实已被实时会话接受:先校验完整批次,再分配序号、保存不可变副本、 |
| 101 | 更新内存投影并通知观察者。它不表示已经落盘。第一份待写事件启动固定 200ms 批处理 |
| 102 | 窗口,后续追加不延长窗口;同一写句柄只有一条排空链。后台写入失败保留原批次并 |
| 103 | 暂停自动重试,下一次显式 `Flush` 才安全重试。写入或 fsync 结果无法确认时进入明确 |
| 104 | 的 uncertain 状态,不能重跑工具。 |
| 105 | |
| 106 | 模型适配器调用前和顶层工具 body 前必须执行语义检查点 `Flush`;失败时下游调用次数 |
| 107 | 必须为零。Todo、批准、助手消息和 `turn/end` 只做内存提交,交给批处理和下一检查点。 |
| 108 | idle 不代表 durable;导出、冷盘校验、写者交接和正常关闭必须显式等待 flush。实时 |
| 109 | 快照同时返回 event sequence 和 durable sequence,前者可以更大。 |
| 110 | |
| 111 | 新版根目录是 `sessions-v4`。继续旧会话时把 transcript 与配对的预览事件目录作为一次 |
| 112 | 导入决策,并且**先分类、后发布**:先在 transcript 写租约下冻结源文件,再在目录锁和 |
| 113 | writer 锁下冻结预览事件,两边都只解析冻结副本。预览中的结构化消息等于或严格包含 |
| 114 | legacy 消息时采用预览,legacy 严格包含预览时采用 transcript;两边存在无法解释的新 |
| 115 | 工作时保持只读且不产生任何目标。只有来源判定最终确定后,才在同一文件系统的临时目录 |
| 116 | 构造并校验唯一 v3 会话,最后原子 rename 发布。迁移 ID 由源规范路径、head、摘要和目标 codec 确定;相同输入幂等复用,源变化生成 |
| 117 | 另一个目标。原文件逐字节复制到目标的 `legacy/`,未知内容不经结构体重编码;Goal |
| 118 | 只导入目标、状态、预算等字段,不导入 todo 或自动续跑。旧未结束运行只作为历史, |
| 119 | 不能恢复批准或活动执行器。 |
| 120 | |
| 121 | 旧 schema-2 日志的每个可达 head 分别迁移。`legacyHeadId` 进入目标 ID 和迁移映射 |
| 122 | 键,因此两个旧 head 不会共享后续写入。目录列表只读取 manifest 头部、日志 revision(stat 加有界首尾采样)和可重建的标量 |
| 123 | 元数据,从不构建偏移索引。缓存条目与精确日志 revision 绑定,日志追加一个字节即使其 |
| 124 | 失效,无需重放。缺失元数据明确显示 pending,并由最多两个并发的流式归约任务重建。 |
| 125 | 暖列表不读取事件正文。冷历史分页采用流式校验读取,到达页上限即停止,不先把整份日志载入内存。 |
| 126 | |
| 127 | 最终整体切换时 Desktop host RPC 提升到协议版本 5;Electron 壳直接使用嵌入 command |
| 128 | contract 的版本发起并校验握手,避免壳与服务各维护一份易漂移常量。Serve 同时声明 |
| 129 | `execution-v2`、`session-history-v1`、`session-identity-v1` 和 `session-ownership-v1`。新版 Desktop 拒绝把运行 |
| 130 | 和取消命令发给缺少任一能力的远端,避免用路径身份或旧 RPC 模拟新版状态机。 |
| 131 |