跳转至

持久状态与只读投影

长程任务能跨 session 恢复,不是因为系统保存了更多聊天记录,而是因为关键事实有稳定归属, 并且能够被重新投影成当前决策。本章建立 LoopX 的状态底座:哪些表面保存事实,哪些表面只负责 阅读,以及为什么“看起来像当前状态”的页面或 Markdown 不能自动成为写入入口。

本章目标

读完后,你应该能:

  • 区分 registry、event ledger、active-state workbench、run history 与 status projection;
  • 判断一个字段应该属于 canonical state、外部事实还是只读 projection;
  • 解释 append-only event、replay、idempotency 与 freshness 的关系;
  • 说明为什么 dashboard、prompt 和 task graph 都不能成为第二套状态机;
  • 在协议变化时找到权威来源,而不是依赖某个 Python 函数名。

Goal identity 不属于聊天线程

LoopX 的持久身份是 Goal,不是某个 Host thread:

Goal
├── objective and boundary
├── todos, gates and evidence lineage
├── registered peer identities
└── runtime and projection routes

Session / thread
└── one temporary executor context

一个 Goal 可以先后由 Codex App、Codex CLI 或其他 Host 推进;一个 session 也可能读取多个 Goal。 读取 Goal 不会自动授予写权限,结束 session 也不会使 Goal 消失。

精确复用 Goal,不靠文本猜测

Goal 复用依赖 stable goal_id 和 registry 连接,不依赖 objective 的模糊相似度:

one registered goal
  -> reuse that exact goal boundary

multiple registered goals
  -> read-only goal_selection_gate
  -> choose one exact goal_id
  -> rerun before any mutation

如果项目注册了多个 Goal,start-goal --guided 应列出可选 id、状态和精确重跑命令。在选择完成前, Todo 写入、Agent 注册和 Host activation 都不应发生。目标文本相似、来自同一 repository,甚至 共享一部分 acceptance,都不是静默合并 Goal 的依据。

还要把 Goal reuse 与 Agent takeover 分开。新 Agent 可以读取同一 Goal 的公共 frontier 和历史, 但默认注册 fresh agent_id;复用已有 Agent identity 需要用户明确选择那个精确 id。这样历史 lineage 能连续,执行责任却不会被新 session 冒名继承。

因此,恢复模型不是:

restore = replay the old conversation

而是:

next decision =
  replay(durable project facts)
  + inspect(fresh workspace and external facts)

旧对话可以帮助理解,但不能比当前 Git、当前 Gate、当前 CI 和 LoopX canonical state 更权威。

五类状态表面

1. Registry:身份、连接与长期策略

Registry 回答“这个 Goal 是谁、连接到哪里、允许哪些运行路径”:

  • Goal id、repository 与 active-state 路由;
  • local/global runtime root;
  • registered Agent identities;
  • coordination、write scope 与 guard;
  • default-off feature 的配置。

Registry 不证明某个 Host 已经成功启动,也不保存每一轮 Agent 输出。它是连接与策略事实,不是 执行回执。

2. Event ledger:发生过什么

event_sourced_state_contract_v0 把 Todo、Gate、run、evidence、projection 和 quota 变化表达为 append-only events。

事件至少需要满足四个不变量:

不变量 作用
Append-only 新事实追加,不能重写历史来伪装旧动作没有发生
Ordered replay 能重建相同的生命周期顺序
Idempotent 同一 event_id 与相同 payload 重放不会重复生效
Privacy-partitioned public-safe 摘要与 local/private payload 不混在同一公开流中

例如“Todo 已完成”不是把 Markdown 复选框改成 [x] 就结束。合法转换应留下 Todo id、producer、 completion evidence、时间和 event lineage,使 status、review packet 与下一轮 quota 能复用同一 事实。

3. Active-state workbench:人可读工作台

ACTIVE_GOAL_STATE.md 让人和 Agent 能快速阅读 Objective、Next Action、User Todo、Agent Todo 与 Progress。它是重要的工作台,但不能笼统地理解为“所有真相都在 Markdown”。

在迁移或兼容阶段,Markdown 可能仍参与 Todo 读取;规范写入仍应通过 LoopX lifecycle commands 形成事件或受控 writeback。直接编辑一个被投影出来的段落,不等于完成状态转换。

active_state_structured_projection_v0 定义了如何从这个工作台生成 typed、read-only 的 Todo、Gate 与 Next Action 视图。协议明确:

  • projection 可以重算;
  • projection 不授予写权限;
  • generated compatibility id 不等于 migration-ready canonical id;
  • duplicate id、缺失 section 等问题应成为 diagnostics,而不是被静默忽略。

4. Run history:一轮发生了什么

Run history 保存一轮 bounded work 的紧凑索引,例如:

  • 哪个 Agent、Todo 与 Goal 参与了本轮;
  • 观察、交付或 blocker 的分类;
  • validation 与 evidence refs;
  • delivery scale 与 outcome;
  • successor、replan 或 no-follow-up;
  • 是否满足 spend 条件。

Run snapshot 不是完整 project memory。它回答“这一轮看到了什么、做了什么、证明了什么”,而 Goal lifecycle 仍由 Todo、Gate、events 与 acceptance 组合决定。

富日志、raw transcript 和 verifier tail 可以留在 local/private runtime artifact;公开 projection 只保留足以复核和恢复的 bounded references。

5. Status 与其他 projection:当前如何阅读

loopx statusquota should-run、dashboard、review packet 和 task graph 都是面向不同消费者的 读模型。

它们可以:

  • 聚合多个 source facts;
  • 压缩大 payload;
  • 按 user、agent、CLI 或 operator 视角重新组织;
  • 暴露 stale、gap、repair 与 attention signals。

它们不能:

  • 发明一个 source 中不存在的 Todo;
  • 用展示顺序替代 lifecycle priority;
  • 通过修改卡片或图节点绕过 write API;
  • 把 stale external observation 当成当前事实。

task_graph_projection_v0 尤其强调:图中的 blocksvalidatescontinueshands_off_to 是派生关系,不是新的调度 命令。

三种 Ledger:Turn Journal、Goal State、Run History

把“所有记录”都当成同一类状态,会导致“阶段已记录”冒充“业务 transition”。LoopX 区分三种 ledger:

Ledger 拥有什么 生命周期 典型用途
Turn journal 单次事务的恢复信息 单次事务 恢复一个中断的 bounded segment
Goal/event state 持久 lifecycle transition 跨 session、跨 Host 判断当前 frontier、Gate、acceptance
Run history/status 历史的证据索引与投影 只读,不可重写 复审、replan、handoff 时的上下文

Turn journal 回答“本轮发生了什么,如果中断如何恢复”。它记录的是单次事务内的临时状态,不是 持久业务事实。把 journal 当 goal state 的典型错误:agent 在 journal 中看到“已进入阶段三”,就认为 goal 已经 transition 到阶段三。但 journal 只记录 agent 有过的意图,只有 goal/event state 才记录 实际完成的 transition。

Goal/event state 回答”当前 frontier 是什么,谁可以做什么”。它通过 append-only event 记录 lifecycle transition(Todo 完成、Gate 解决、Vision 更新),并支持跨 session 重建。它是 durable lifecycle fact 的权威来源,quota 将其与 registry/boundary、Todo/Gate、 capability/workspace、run outcomes/history、scheduler context 以及 fresh external fact 一起编译。

Run history/status 回答“历史上发生了什么,有什么证据”。它是只读的,不能反向写入 goal state。 run 记录说“这轮测试通过”,不等于 goal state 中对应的 acceptance 已闭合——只有通过 lifecycle command 写入的 transition 才算。

区分这三者的实践意义:每次写回前,确认要写入的是 goal/event state(transition)而不是 turn journal(临时记录);每次读取 decision 前,确认读的是 goal/event state 而不是 run history 的旧 投影。完整三类 ledger 的源码路径和实验见 Control-Plane Course 第 8 讲

Canonical、Workbench、Projection 与外部事实

四个词必须分开:

典型内容 谁能改变 能否直接支持状态转换
Canonical state event、typed Todo、Gate resolution、quota spend LoopX lifecycle writer 可以
Workbench active-state Markdown、人工说明 受控 writeback 或兼容编辑 需要转成规范事实
Projection status、quota packet、dashboard、task graph projection builder 不可以,只供决策读取
External fact Git commit、PR、CI、cloud resource 对应外部系统 需要 fresh readback/evidence

“某个页面显示 PR 已合并”可能只是旧 projection;“某次 run 说测试通过”也可能绑定旧 commit。 只有重新读取外部事实并检查 revision、freshness 与 scope,才能把观察用于当前转换。

存储介质不是 authority contract

LoopX 当前是 本地优先 的控制面:项目 registry、active-state workbench、event/run history 和 runtime state 位于项目或用户本地。这个事实不意味着“Markdown 文件本身就是 authority”,也不 意味着把目录换成数据库就自动获得正确的并发与恢复语义。

event_sourced_state_contract_v0 明确允许 JSONL、SQLite 或其他 local-first append-only 实现,只要它们保持:

  • stable event id 与 ordered replay;
  • idempotent append;
  • projection head 与 event-store head 对齐;
  • public-safe、local-private 与 private-pointer 分区;
  • Markdown 继续作为 workbench/projection,而不是任意写入口。

local_state_write_correctness_v0 当前标记为 public-safe protocol draft。它把更强的写入正确性目标分成 prepare -> preview -> apply -> record -> project

  • 同一 idempotency_key 不应重复产生逻辑 effect;
  • expected_revision 不匹配时应 fail closed 或从新 revision 重算非重叠 patch;
  • foreign/expired lease 不应被静默清除;
  • lock 默认以 Goal 为目标边界,只有单 Todo 且不影响共享顺序时才可更窄;
  • 外部写、凭据、production 和 private read 仍需独立 Gate。

当前 Todo lifecycle 命令已经在 active-state file lock 下重读并写回,preview 也会暴露 write intent。协议文档同时明确:hard idempotency、统一 optimistic CAS 和 lease conflict enforcement 仍按 writer 分阶段 promotion,不能假设所有 writer 已完整执行上述 Draft。

因此,文件、SQLite 或未来 provider 回答的是“字节存在哪里”;event、revision、CAS、lease 与 authority 回答的是“哪次状态转换合法”。

当前已发布与仍在设计中的边界

当前公开架构把 CLI 作为 compatibility baseline,并把 local server/daemon 描述为 roadmap。 多 Host 协作、离线队列和 shared control plane 的详细方案存在于状态为 Draft 的 RFC 中,不能 写成当前安装后即可用的云端功能。

当前可以依赖:

  • 本地项目状态与 global registry projection;
  • Todo lifecycle writer 的 active-state file lock、preview/readback 和当前已实现的幂等行为;
  • registered peer、soft claim、可选 task lease 与独立 worktree guard;
  • 不同 Host 通过同一 registry/Goal 读取并受控写回。

当前不应承诺:

  • 多台设备自动共享一个在线 authority;
  • 离线设备可以新 claim、complete、续 lease 或执行 protected write;
  • 把项目目录放进同步盘就得到一致的分布式状态;
  • NoKV、数据库或 IM 自动替代 LoopX lifecycle owner。

如果要实现跨设备控制面,应保留一个 canonical LoopX authority,要求 revision-bound、幂等的受控 命令与 receipt,并把消息传递、上下文记忆和状态 authority 分开。直到该 Draft 经过发布验证, Dev Book 只教授这些协议边界,不提供“云端模式已可用”的操作步骤。

历史产物的三层完整性

LoopX 可以让研究、验证和决策产物不被静默改写,但这不等于旧结论永远适用于当前状态。判断一条 历史 evidence 能否进入当前决策,要分三层:

层次 需要回答 典型检查
Lineage integrity 这条产物来自谁、何时生成,是否被追加、纠正或 supersede? event_idrun_id、producer、recorded revision、append-only refs
Current applicability 它支持的输入、范围和外部事实与当前问题仍一致吗? commit、target key、source revision、time window、Gate scope、fresh readback
Supersession 后来的 evidence 或决定是否替代、收窄或撤销了它? supersedes/superseded_by、compensating event、newer decision、replan delta

因此,append-only lineage 解决的是防止历史被无声重写,不是自动证明旧结论仍然新鲜。 研究笔记、测试结果或 PR readback 要进入当前 frontier,至少应带稳定 join key,并在 material input 变化后重新验证 applicability。无法确认时,把它标为 historical observation 或 stale evidence, 不要删除历史,也不要继续把它当作 current authority。

agent_scoped_evidence_ledger_v0 提供 bounded、read-only 的 Agent chronology,适合 replan 和 handoff;它不替代 current status、 quota decision 或外部系统 readback。

Replay 不是复制旧结论

Replay 的目标是从有序事实重建当前状态,而不是永久保留旧判断。

假设事件流记录:

todo_added(T1)
todo_claimed(T1, agent-a)
gate_added(G1, scope=public_claim:action:homepage)
run_recorded(R1, tests_passed_at=commit-a)

随后 Git 前进到 commit-b,用户又改变首页方向。Replay 仍能说明 R1 和 G1 曾经存在,但不会自动 证明:

  • R1 对 commit-b 仍有效;
  • G1 已覆盖新的首页方案;
  • agent-a 仍在当前 workspace 执行;
  • 当前 frontier 可以继续发布。

恢复者必须把 replay 后的 project facts 与新鲜环境重新组合。

Projection gap 是控制面故障

当 source 与读模型不一致时,不能任选一个看起来方便的表面继续:

  • event 中有 open Todo,status 却没有;
  • Gate 已解决,quota 仍显示 operator wait;
  • active state 有 Next Action,但对应 Todo 不存在;
  • dashboard 显示 runnable,workspace guard 却指向另一个 worktree。

这些情况属于 projection gap。正确动作是:

  1. 找到 authoritative source;
  2. 判断是 source 写入失败、projection stale、migration drift 还是 external observation 过期;
  3. 通过原 lifecycle/writeback 路径修复;
  4. 重算 projection 并验证 source revision;
  5. 在修复前不运行依赖该状态的交付。

手工把多个展示面改成一致,只会隐藏问题。

如何决定一个新字段放在哪里

新增字段前按顺序问:

  1. 它描述长期配置、身份或路由吗?放 registry。
  2. 它描述一次 lifecycle transition 吗?放 event。
  3. 它描述一轮观察或交付吗?放 run snapshot/evidence。
  4. 它只服务某个读者视角吗?从现有事实生成 projection。
  5. 它属于 GitHub、CI 或其他系统吗?保留外部 authority,只存 bounded readback。
  6. 它是 Issue-Fix、Explore 等领域专属结果吗?放 Domain State,不要塞进通用 Todo/Quota。

如果一个字段同时想承担配置、事件、展示和权限四种责任,通常说明协议边界还没有拆清。

协议阅读入口

本章拥有概念顺序,不复制完整 schema。需要修改 LoopX 状态行为时,优先阅读:

如果你准备修改 registry、event、Domain State、replay 或 projection builder,继续阅读 Control-Plane Course 第 4 讲。 它从 Issue-Fix、Auto ML 与 Auto Research 的事实归属进入源码路径和实验;本章继续作为外部 开发者的概念入口。

下一章在这套状态底座上建立工作图:谁可以做什么、什么条件阻塞它,以及一项工作如何合法地 继续、交接或结束。