主要状态机与状态流转¶
前面各章把机制拆开讲:02b 提出长程运行的四条要求,状态底座讲持久事实与只读投影, 工作图讲 Todo、claim 与权限,03 讲一轮内部的事务顺序。本章是它们汇合的地方。 那些机制在文档里分开读时各自成立;真正需要回答的问题在这里:它们怎样接回同一 条循环?
先给结论:LoopX 由多组状态机协作驱动;但最高一层不是 “九台机器互相发消息”,而是一条 effectful Agent Loop。 Harness 解释 Agent 或 Host 提出的 effect request,再由 Todo、Gate、Quota、Settlement、Scheduler 等领域状态机判断这一小段流程的 合法动作。它们通过持久事实、typed contract、guarded transition 和 receipt 衔接,而不是直接 覆盖彼此的状态。
LoopX 不靠一个巨型状态机推进 Goal。它把持久工作、单轮决策、证据结算、调度和界面投影 交给有明确 owner 的协作状态机。本章先建立名词和抽象层级,再解释为什么这样设计,随后看 协作总图,再逐组展开状态机,最后交代这套设计付出的代价。这样你可以先理解主线,再按需深入某一组规则。
从一个坏的结局开始¶
本章的场景需要跑完几轮才会出现,所以先看时间线:
周一 14:00 Goal 已是 active。Todo 图完整:实现、测试、文档各有归属。
周一 17:30 一轮工作完成。Agent 修改了 Vision:接受一条新的验收路径。
周二 09:00 进程被杀(部署、OOM 或用户按停止)。
周二 09:10 新实例接管。它读到 active 的 Goal、完整的 Todo 图、已 ACK 的调度记录。
周二 09:10 所有字段都合法。Todo status 在四个持久 status 之内;Vision checkpoint
格式正确;scheduler 的 last_checked_at 是最新的。
周二 09:10 但它无法判断一件事:Goal 的 acceptance 在周一 17:30 已被改过。
File/SQLite authority 路径上,checkpoint 的 read context 会覆盖 Goal 与
acceptance 基线的 digest,改过之后会判 stale 并要求重读;而在 acceptance
不由该路径携带的模式下,没有任何 owner 把 checkpoint 绑定到 acceptance 版本。
周三 10:00 没有人能说清重规划是否已经完成。
周三 10:00 控制面继续按旧路线推进。没有一条规则说这不允许。
这个结局里没有哪一台状态机坏了。Todo lifecycle 判对了 status,quota 按 current source facts 重新编译了 contract,settlement 的 receipt 齐全,scheduler ACK 也真的写了回执。每一台机器 单独看都给出正确答案,合起来却说不清"工作到了哪一步"。
这就是前面几章拆开讲时看不见的接缝:每台机器各自拥有的是一部分事实,循环由这些事实之间 的接力构成。 某台机器把结论写成持久事实,下一台读它,不会直接调用彼此的内部状态。
先读哪一层:一条 Loop、三层抽象、九组状态机¶
同一套实现可以从三个抽象层级理解。先确定自己正在读哪一层,不要把不同层的名词混在一起:
- 外层 — Agent Loop:问“一轮怎样接到下一轮?”最小模型是
effect request -> interpretation -> effect -> observation -> next effect。 - 中层 — 控制面协议:问“谁决定、谁执行、怎样证明?”关注 source facts、derived decision、 guarded writeback、receipt 和 projection。
- 内层 — 领域状态机:问“这个 bounded context 有哪些合法状态和边?”Todo、Gate、Quota、 Handoff 等各自维护一张 interpretation table。
所以“LoopX 是多状态机系统”在内层是对的;在外层,更准确的说法是:一条 Agent Loop 由 多组有边界的状态机共同解释和约束。 状态机是 Harness 内部的决策表,不是九个平级微服务, 也不是九个 Agent。
先认识名词:把“事实、判断、动作、证明”分开¶
下面这些词会反复出现。第一次阅读只需记住“它是什么”和“它不是什么”:
先把一轮读成一句白话:Goal 给出长期边界,frontier 给出当前工作,控制面派生本轮 decision, Agent / Host 执行 effect,observation 经验证后写回事实,receipt 与 projection 再把下一轮接起来。
- 目标与工作 — Goal、Todo、frontier、Vision / replan:Goal 是长期 identity 与边界;Todo 是工作项;frontier 是当前可继续的工作边界;Vision / replan 保存路线和改道依据。它们不是一个 可整体覆盖的大 Goal 对象,“一条 Todo 完成”也不等于“Goal 完成”。
- 事实与视图 — source / authoritative fact、derived decision、projection:source 是 owner 可持久写入的事实;decision 是规则算出的本轮判断;projection 是给 Agent、Host 或人看的视图。 页面显示的字段不一定可写,projection 也不是第二套事实源。
- 动作与证明 — effect request / proposal、observation、transition、evidence、receipt:proposal 请求动作;observation 报告外部事实;transition 接受合法变化;evidence 支撑判断;receipt 证明 有 identity 的提交已经发生。Agent 的请求不等于授权,工具返回也不自动等于完成。
- 约束与运行 — Gate、lease、quota、Scheduler / heartbeat、Host / Runtime:它们分别限定 决定范围、临时执行权、本轮计算资格、再次唤醒时间和 effect 执行;不是五种同义的全局锁。
先分清三种“状态”¶
控制面里显示为状态的字段,不一定都能直接写入。最重要的第一步,是按所有权分层:
- Source state 是可持久、可回放的事实,例如 Todo
status、claimed_by、Goalactivation_state、event 和 scheduler receipt;只有对应 write API 或 owner service 能改变。 - Derived decision 是 facts 经规则编译出的本轮判断,例如 quota
eligible、operator_gate、 handoffcleared_with_successor和interaction_contract;不能直接改,要改 source 后重算。 - Projection 是给人或 Host 读取的视图,例如 Workspace 卡片、status、review packet 和 scheduler hint;它可重建,用户动作仍须回到 write API。
最常见的错误,是把派生结果当成可写事实。例如 Todo 并没有持久的 running 状态;运行中由
当前 quota selection、lease 和 run history 共同推导。Workspace 中把卡片拖到“完成”,也不能绕过
Todo completion、evidence 和 receipt 的 owner。
为什么这样设计,而不是一个大状态机?¶
拆分不是为了增加名词,而是因为这些状态天然有不同的变化原因:
- 寿命不同:Todo 和 evidence 要跨会话保存;quota decision 只对当前 Turn 有效;projection 随时可以重建。
- 权限不同:Agent 可以提出 effect;Host 可以执行 effect;只有对应 transition owner 能接受事实变化。
- 失败恢复不同:网络调用需要 reconcile,Todo 冲突需要 rebase,显示漂移只需重新 projection。
- 并发边界不同:Goal identity、Todo snapshot、lease 和 provider revision 不能伪装成一个全局整数版本。
- 审计问题不同:系统必须分别回答“为什么选择它”“实际发生了什么”“谁接受了结果”“下一轮为何继续”。
如果把它们压进一个 Goal.status 或允许 Agent 整体覆盖 GoalState,一次写入就可能误删其他
Agent 的进展;一次网络超时也无法判断该重试、回读还是补偿;界面字段还会反向变成事实源。
协作不等于互相发消息。九组状态机主要通过四种稳定接口连接:
- Source facts:Todo、event、lease、Vision 和 receipt 供 reducer / interpreter 只读。
- Typed decision / effect:
interaction_contract、selected action 和 scheduler hint 告诉 Agent 或 Host 执行哪个 bounded action。 - Guarded transition:owner 校验 proposal、identity、revision basis 和 evidence 后,提交 event、 receipt 或 CAS。
- Readback / projection:committed facts 与 operation receipt 重建 Workspace/status,并派生下一轮 effect。
先看协作骨架:状态机通过事实与协议接力¶
这张图只画主循环,不展开任何一组内部状态。它回答“多组状态机怎样一起驱动”:左侧的状态机 读取共同事实并参与解释,本轮只产生一个有界 effect 或有序 effect program;外部 observation 必须经过验证与写回,才能 成为下一轮可读的事实。
flowchart TD
Facts["持久事实<br/>Goal / Todo / event / receipt"] --> Interpret["状态机共同解释<br/>Todo · Gate · Quota · Handoff"]
Interpret --> Decision["typed decision<br/>interaction contract"]
Decision --> Effect["Agent / Host<br/>执行 bounded effect / program"]
Effect --> Observation["observation<br/>结果或外部变化"]
Observation --> Validate["validate + guarded transition"]
Validate --> Facts
Facts -.-> Projection["projection<br/>Workspace / status"]
这不是“状态机 A 调用状态机 B”的时序图。真正接力的是 source facts、typed decision、effect、 observation 和 receipt;每个状态机只负责自己的 decision table 与合法 transition。
从 L0 到 L4:用这些名词回答五个架构问题¶
L0:为什么需要 LoopX?¶
长程 Agent 的模型上下文适合推理,却不适合独自承担 execution state。上下文会被压缩、切换或 重启;外部 effect、权限、多人协作、等待条件和验收证据的寿命通常比一次会话更长。若把这些事实 只留在对话里,下一轮就无法可靠回答“什么已经发生、谁获准继续、是否应当重试、Goal 是否完成”。
因此 LoopX 把控制面放在模型上下文之外:状态持久化、变化可回放、写入受 guard 约束,再把当前 需要的 bounded view 投影给 Agent。模型负责判断和提出动作;控制面负责保存事实、约束流转并在 下一轮重新派生合法动作。
L1:控制面有哪些组件?¶
| 组件 | 职责 | 在 LoopX 中的典型载体 |
|---|---|---|
| Goal | 提供要推进的目标语义与 identity;当前实现并未把全部 intent 收进一个 typed owner | registry identity、active state、Agent Vision 与 Todo frontier |
| Authoritative state | 保存可恢复的执行事实 | registry、Todo/event source、run history、receipt |
| Evidence | 证明结果、阻塞或外部 effect 的真实状态 | artifact ref、validation/readback、rollout/rollback event |
| Transition | 校验并提交一次合法状态变化 | Todo/Goal write API、settlement、scheduler ACK |
| Projection | 把 facts 编译成面向 Agent、Host 或人的视图 | quota/status、Workspace、review packet |
| Runtime | 解释本轮 decision 并执行有界 effect | Codex App/CLI、heartbeat、extension provider |
这些组件不是一条由 Runtime 随意覆盖的 JSON。Runtime 是 effect executor;authoritative state 与 transition owner 才决定事实是否真的改变。
L2:authoritative state 长什么样?¶
为了理解,可以先把理想的 Goal 控制面想象成下面的概念聚合视图。它描述希望一次读取能够 回答的问题,不是 LoopX 1.0 已经提供的统一 schema:
GoalControlSnapshot: # 概念视图;字段是待回答的问题,不是已实现字段名
identity:
goal_id: ...
activation_state: active | stopped
intent:
objective: ...
acceptance: ...
terminal_conditions: ...
frontier:
completed_requirements: [...]
pending_requirements: [...]
todos: [...]
gates_and_blockers: [...]
outcome:
artifacts: [...]
evidence_refs: [...]
basis:
revision_basis: state_event_log | markdown_active_state | canonical_todo_snapshot
state_event_basis_sequence: ...
source_basis_digest: ...
todo_basis: # canonical Todo snapshot 单独的 revision basis
source_authority: file_v0
provider_revision: ...
records_sha256: ...
LoopX 1.0 没有一个可以整体覆盖的 GoalState 大对象。更重要的是,objective、non-goals、
acceptance、permissions 和 terminal conditions 目前还没有统一的 typed canonical storage;上面的
intent 是目标模型,不应被描述成已经落地的 authoritative envelope。当前 shared Goal alignment 是
只读聚合:Todo basis 来自当前选定的 Markdown active state 或 canonical Todo snapshot,历史记录按其 owner 补充证据;旧 Todo event API 已退役。聚合使用
独立 todo_basis 标识 Todo/lease snapshot,再投影 drift 与 conflict:
| 常见抽象字段 | LoopX 的实际表达 |
|---|---|
goal_id |
registry 与所有 goal-scoped event 的稳定 identity |
phase |
Goal activation 只有 active | stopped;阶段路线属于 Agent Vision / Todo,而不是通用 Goal phase |
objective / acceptance / permissions / terminal conditions |
目前分散在项目材料、Vision、Todo 与运行约束中;没有统一 typed canonical intent revision |
| 已完成 / 待完成的要求 | 概念占位,不是当前字段名;alignment 输出中的对应项是 frontier_basis,其余由 Todo、Vision checkpoint、acceptance gap 与 frontier facts 聚合,且不是独立可写列表 |
artifacts / evidence / blockers |
保存在 Todo、run、event 和 receipt 中的引用与 typed facts |
version |
按 owner 使用 event append_sequence、source checksum 或 opaque provider revision;不存在全局 Goal version |
这就是前述 owner 分离在当前实现中的具体形状:读时可以聚合,写时仍回到各自的 authority。
L3:Agent 怎样修改 authoritative state?¶
Agent 提交的是 proposal 或 typed effect,不是“我认为新状态应该长这样”的全量覆盖:
flowchart TD
Read["读取 source + revision basis"] --> Propose["Agent 提交 bounded proposal / effect"]
Propose --> Guard["transition owner / Runtime boundary 校验 schema、identity、authority、lease、evidence 与 Gate"]
Guard --> Conflict{"source basis 仍匹配?"}
Conflict -->|否| Rebase["revision conflict / needs_rebase:重新读取"]
Conflict -->|是| Commit["owner-specific guarded commit"]
Commit --> Event["append event / receipt,或 CAS authoritative head"]
Event --> Readback["readback + rebuild projection"]
Readback --> Next["派生下一条合法动作"]
这里的“CAS”是并发控制原则,不是假装全仓只有一个整数 version:
- Todo completion 会在取得 mutation lock 后比对 admission 相关的字段快照(
status、completion_continuation、successor_todo_ids、validation_command_argv等),快照不一致即拒绝,确保针对某份声明的 validation 不能授权另一份已持久化的 Todo; - shared authority store 以 opaque
expected_provider_revision(file provider 中对应 generation)做真正的 compare-and-swap;authority_revision和lease_epoch不能代替它; - local-state correctness 模块会在 dry-run/shadow 中构造
expected_revision、per-Goal lock、lease 与 idempotency envelope;它明确不代表当前 apply path 已执行这些保证,实际 lock、write 与 event 仍由 caller 负责; - settlement 再以
goal_id + agent_id + turn_instance_id绑定 writeback、spend 与 scheduler receipt。
对应的源码锚点分别是
completion_transaction.py、
local_state_write_correctness.py、
authority_store.ts、
coordination/authority_core.py
和 settlement.py。
所以正确流程是“读 basis -> 提案 -> 验证/guard -> guarded commit -> 事件或 receipt -> 回读”,而不是 Agent 直接覆盖一份看似完整的状态。
L4:Agent 判断错了怎么办?¶
控制面把“判断错误”和“外部副作用已经发生”分开恢复:
| 错误位置 | 保护或恢复方式 | 不能做什么 |
|---|---|---|
| proposal 本身不合法 | schema、authority、Gate、evidence validation 拒绝写入 | 先写再补理由 |
| 读取后 source 已变化 | revision conflict / needs_rebase,重新读取并 replan |
静默覆盖新事实 |
外部 effect 仍为 running 或结果未知 |
用同一 invocation / idempotency identity reconcile |
重复启动同一个 effect |
| 受 settlement journal 管理的 provider step 已 prepared,但 writeback 中断 | 用同一 effect_ref 回读:committed 复用结果,absent 才执行,unknown fail closed |
未证明 absent 就重做 provider step |
| 已提交的 effect 后来证明错误 | 追加 rollback / compensation evidence,并生成 successor 或 replan | 删除旧 event、伪造从未发生 |
| projection 与 source 不一致 | 修 source 或 projection builder 后重新投影 | 直接改 dashboard 当作修复 |
这也是为什么 side effect 必须独立建模:模型可以改主意,外部世界却不能靠改一段上下文回滚。
LoopX 通过 validation、版本/identity guard、reconcile、补偿事件和 replan,把错误变成下一条可验证
transition,而不是让它成为一段失去来路的聊天结论。这里的幂等恢复保证有明确边界:它适用于带
持久 journal、effect_ref 和 provider readback resolver 的 settlement step;不能外推成“任意外部工具
调用都会自动去重”。resolver 缺失、报错或返回未知状态时,settlement 会 fail closed。
九组状态机怎样组成一条 Loop¶
现在把刚才那条时间线接回整条循环。维护者级地图把控制面拆成九组协作状态机;“九”是当前 核心规则的教学地图,不是一个要求所有 extension 都注册九台 runtime service 的协议常量。 本书按读者任务把它们组织为四层:
| 层 | 主要机器 | 回答的问题 |
|---|---|---|
| 持久工作与权限 | Todo lifecycle、Gate decision scope、Owner route / handoff | 做什么、谁能做、什么决定仍缺失? |
| 单轮执行与结算 | Quota / runtime、Evidence / rollout / rollback | 这一轮能否运行,什么结果可以写回并计费? |
| 长程连续性 | Scheduler / heartbeat、Agent Vision / replan | 何时再唤醒,何时必须改路线而不是重复? |
| 接入与呈现 | Projection sink、Agent onboarding / automation enablement | Goal 怎样进入运行面,状态怎样安全显示? |
flowchart TD
Sources["Registry + active state + Todos + events"] --> Decide["quota should-run 编译 interaction contract"]
Decide --> Run["执行一个 bounded segment"]
Decide --> Gate["询问 scoped decision"]
Decide --> Wait["等待 / monitor quiet"]
Decide --> Repair["repair / replan"]
Run --> Validate["独立验证"]
Repair --> Validate
Validate --> Writeback["durable writeback"]
Writeback --> Spend["quota spend once"]
Spend --> Schedule["scheduler apply + ACK"]
Gate --> Schedule
Wait --> Schedule
Schedule --> Sources
Sources --> Projection["Workspace / status / review packet"]
Projection --> UserAction["用户或 Host 发起动作"]
UserAction --> Sources
图中没有从 projection 直接连回 decision 的写入捷径。界面只能发起受治理动作;真正的 state transition 仍由 write API、validation 和 receipt 完成。箭头表示事实和 contract 的依赖关系, 不是状态机之间的进程消息。
1. 持久工作:Todo、Gate 与 Handoff¶
Todo lifecycle:只有四个持久 status¶
loopx/control_plane/todos/contract.py
定义 Todo 的持久状态:
claimed_by、resume_when、superseded_by、unblocks_todo_id 和 no_followup 是正交字段,
不是更多 status。它们共同决定下一条合法路径。
stateDiagram-v2
[*] --> Open
Open --> Blocked: blocker + reason
Blocked --> Open: blocker repaired / scope restored
Open --> Deferred: concrete resume_when
Open --> Done: validated completion
Blocked --> Done: blocker accepted as terminal outcome
Done --> [*]
done 与 deferred 都属于 terminal status,但“恢复条件满足”本身不会改写 status。它先产生一个
派生候选,再要求显式选择后续 lifecycle 动作:
flowchart LR
Deferred["persisted deferred Todo"] --> Condition{"resume condition satisfied?"}
Condition -->|否| Wait["保持 deferred"]
Condition -->|是| Candidate["deferred_resume_candidate<br/>status 仍是 deferred"]
Candidate --> Reopen["显式 reopen<br/>status=open + clear resume_when"]
Candidate --> Supersede["显式 supersede<br/>predecessor=done + linked successor"]
Candidate --> Close["记录 no-follow-up rationale / closeout"]
| Source / derived field | 所有权 | 正确理解 |
|---|---|---|
status |
Todo contract | 持久 lifecycle;不要发明第五个 status |
claimed_by |
Todo metadata | routing signal,不是分布式锁 |
| task lease | lease lifecycle | 有时限的执行权;过期后可恢复 |
Running |
quota + lease + run history 派生 | 当前有 bounded attempt,不应写回 Todo status |
superseded_by |
Todo relation | 保留历史并指向 replacement,不等于删除旧 Todo |
resume_when |
Todo relation | deferred 的可验证恢复条件,不是模糊的“以后再看” |
禁止的流转包括:没有 completion evidence 就把 open 改成 done;仅凭 claim 就宣称任务正在
执行;把条件满足当成自动 deferred -> open;删除被 supersede 的历史;把“等用户看看”写成没有
machine-readable 条件的 deferred。supersede 也不是简单的 deferred -> done 边:命令会把 predecessor
记为 done、写入 note=superseded,并创建和链接 successor。
Gate:约束 decision scope,不冻结整个 Goal¶
Gate 的 source 是 task_class=user_gate、decision_scope、required_decision_scopes、
global_gate 和 agent blocking fields。它先回答“这个决定覆盖哪条 action/lane”,再决定本轮是
询问用户还是运行独立 fallback。
flowchart LR
OpenGate["open Gate"] --> Scope{"覆盖 selected action?"}
Scope -->|是| Ask["询问具体 decision"]
Scope -->|否| Fallback["保留 Gate + 运行独立 fallback"]
Scope -->|字段矛盾| RepairProjection["repair projection"]
Ask -->|approve| Consume["只消费已覆盖 scope"]
Ask -->|reject / cancel| Replace["supersede 或 compensation"]
Ask -->|稍后| Defer["写入 resume_when"]
因此 user_channel.action_required=true 和 agent_channel.must_attempt=true 可以同时成立。前者要求
呈现 owner-held decision;后者只授权不依赖该 decision 的明确工作。
Handoff:clear 不等于路线完整¶
handoff_gate.py
从 Todo relation 派生六种 handoff state:
| 派生状态 | 含义 | 下一步 |
|---|---|---|
blocking |
当前 Agent 仍被 owner route 阻塞 | quiet wait 或呈现具体 Gate |
cleared_with_successor |
blocker 已完成,且 successor 已存在 | 路由到 successor |
cleared_without_successor |
blocker 已完成,但没有 successor / no-follow-up | 先做 successor replan |
cleared_no_followup |
owner 明确结束后续 | closeout |
superseded |
replacement Todo 已存在 | 跟随 replacement |
deferred |
resume condition 尚未满足 | 等待或观察条件 |
这些值是 projection,不应被手工写入。修复 cleared_without_successor 的方式是新增合法 successor、
重开工作或记录 no_followup,而不是把显示值改成 cleared_with_successor。
2. 单轮执行:Quota、Interaction Contract 与 Settlement¶
Quota runtime:先判定行为,再考虑 spend¶
loopx/control_plane/quota/states.py
固定了 quota runtime state 的判定优先级:
这是 reducer 在多个条件同时出现时使用的 precedence,不是 blocked_health 会依次流转到 paused
的状态图。每轮都从当前 source facts 重新判定。
| Runtime state | 本轮合法动作 | 常见恢复路径 |
|---|---|---|
blocked_health |
在 authority 允许时修 registry、projection、workspace 或 capability | 验证 repair 后重新计算 |
operator_gate |
呈现具体 owner decision | approve / reject / defer 后重新计算 |
focus_wait |
只恢复指定 outcome 或 fresh evidence | 写 recovery evidence 或 compact blocker |
eligible |
执行唯一 selected bounded action | 进入 settlement |
waiting |
观察明确 external handle,或 quiet wait | material observation / condition satisfied |
throttled |
不执行交付 | 等待 quota window 或 owner adjustment |
paused |
不执行自动 Turn | 显式 resume;active/stopped 由 Goal activation owner 管理 |
Quota state 不是单独的授权。interaction_contract 还会给出 user、agent、CLI channel、workspace
guard、capability gate、selected Todo、execution obligation 和 scheduler hint。Agent 必须执行的是
must_attempt_work 是布尔 obligation:为 true 时,本轮必须尝试 bounded work 并写回;它本身不选择
动作。需要选择 Todo 时先执行 selection_command,否则执行 capability packet 或
next_cli_actions[0] 指定的动作。Quota guard、选择、refresh、spend 和 scheduler settlement 必须复用
同一个 turn_instance_id;不能从 NOTIFY(只控制用户输出)或孤立的 should_run 自行推断动作。
Settlement:成功必须按事务顺序落地¶
turn_transaction_contract.json
定义完整 Turn 的阶段:
host_execute
-> typed_result
-> validation
-> durable_writeback
-> quota_spend
-> scheduler_apply
-> scheduler_ack
stateDiagram-v2
[*] --> HostExecute
HostExecute --> TypedResult
TypedResult --> Validation
Validation --> DurableWriteback: receipt committed
Validation --> Failed: invalid or missing receipt
DurableWriteback --> QuotaSpend: writeback accepted
DurableWriteback --> Failed: rejected / identity mismatch
QuotaSpend --> SchedulerApply
SchedulerApply --> SchedulerAck
SchedulerApply --> RetryHostUpdate: host update failed
RetryHostUpdate --> SchedulerApply
SchedulerAck --> [*]
Failed --> RepairOrRetry
Settlement identity 绑定 goal_id + agent_id + turn_instance_id,再且仅再绑定一个 Todo 或一个
autonomous replan obligation。它防止旧 Turn、其他 Agent 或其他工作项的 receipt 被误用。
必须保持三条不变量:
- validation receipt 缺失时不能 durable writeback;
- durable writeback 缺失或被拒绝时不能 spend;
- scheduler apply 没有 ACK 或 fail receipt 时不能假装 Host 已更新。
失败不是删掉 transaction。receipt_missing、identity_mismatch、writeback_rejected、
quota_spend_rejected 等失败种类会把控制权交给 repair/retry,并保留 effect identity 以实现幂等。
如果 journal 留下 prepared provider effect,恢复器必须先按相同 effect_ref 做 provider readback:
只有 absent 可以重新执行,committed 直接采用已提交 payload;resolver 不存在、抛错或返回未知
状态都会 fail closed。这项保证只覆盖由 settlement journal 与 resolver 管理的步骤。
Evidence / rollout / rollback:追加补偿事实,不改写历史¶
Evidence machine 决定“这次流转是否可信”,而不决定用户希望系统做什么。它把一项假设推进为 可接受事实,或保留为明确 blocker:
hypothesis
-> evidence bundle
-> validated snapshot | blocker evidence
-> rollout event + mutation anchor
-> optional rollback / compensation event
-> successor
artifact ref、test/build 结果、外部 readback、commit/PR/doc revision 都可以成为 mutation anchor。 Rollback 不删除原 evidence 或 rollout event;它追加一个补偿事实,并通常创建或解锁 successor。 因此“把旧状态改回去但不记录原因”不是合法恢复,也无法证明后续 projection 基于哪条事实。
3. 长程连续性:Scheduler、Monitor 与 Vision¶
Scheduler / heartbeat:决定何时再看,不决定能否做事¶
Scheduler hint 从已确定的 quota 和 interaction contract 派生。常见 action 包括立即运行、等待用户、
等待 reassignment、等待 material transition、等待 fresh evidence、等待任意 state change,以及停止或
return-to-owner。停止不只来自 Goal 闭环:Goal stopped、quota paused 和 peer coordination blocked
也会停止轮询或把控制权交回 owner;只有 terminal_no_followup 表达“因已验证 Goal closure 而停止”。
具体 cadence 由 runtime profile 和 scheduler owner 决定。
flowchart TD
Decision["resolved interaction contract"] --> Hint["scheduler hint"]
Hint --> Apply{"host cadence already matches?"}
Apply -->|否| HostUpdate["apply host update"]
Apply -->|是| Ack["record host-match ACK"]
HostUpdate -->|成功| Ack
HostUpdate -->|失败| Fail["record failure receipt"]
Ack --> NextTick["next tick re-runs quota"]
Fail --> Retry["bounded retry / backoff"]
reset_token 或 identity 改变时,cadence 回到初始档;同一 unchanged identity 才逐步 backoff。调度
变化本身不 spend,也不能把一个 paused/blocked Goal 变成 eligible。
Continuous Monitor:观察也是有界状态机¶
Monitor Todo 必须带 bounded stop/resume 信息,例如 expires_at、resume_when 或有界 no-change
策略。一次 poll 只写 compact observation:last_checked_at、result_hash、
consecutive_no_change 与是否 material change。外部网络 poll、quota settlement 和显示投影是独立
effect;外部结果必须先成为 typed observation,才能进入 Monitor 写事务。
- 未到
next_due_at:quiet no-op,不 poll、不 spend; - 到期但无变化:写 no-change receipt,按策略 backoff;
- material change:写 observation,并按显式 intent 生成独立 successor / Gate,再重新计算 quota;
- 到达 expiry 或 stop condition:完成 Monitor,并连接 successor 或
no_followup。
对于已经显式提升到 shared authority 的 Goal,
todo_monitor_poll.ts
会在同一个 canonical revision、同一次 CAS 和同一 durable operation receipt 中提交无 lease 的
observation 与它请求的独立 successor。事务先校验 actor 注册、claim/binding/exclusion、Monitor
仍处于 active 状态,以及 material-change generation 确实前进;任何校验失败都不会写入任一半。
相同 operation 的重试回放原 receipt 和原 successor,相同 evidence 不能借一个新的
material_change=true 再生成重复工作;no-change observation 只更新观察状态和下次 cadence,不创建
交付 Todo。
这里的“原子”有明确边界:它不包含外部网络 poll、quota spend 或 Markdown/dashboard delivery。
canonical commit 成功后,projection 仍可能处于 pending;outbox 只重试显示投影,
retry_business_mutation=false,不能再次执行已经提交的 observation/successor。2.0 中未迁移
Goal 返回 canonical_authority_required,必须先备份并迁移,之后才能执行新的 Monitor 写入。
把 future next_due_at 当作 advancement frontier 是错误的:它只说明何时观察,不能替代“下一步
怎样推进 Goal”。
Agent Vision / replan:路线改变必须成为 state delta¶
Vision 是 per-Agent 的有界可执行路线,不是 scratchpad。正常延续可以提交
vision_unchanged_reason;假设、scope、acceptance 或 route 发生 material change 时,必须提交
bounded Vision patch,并同步 Todo / acceptance delta。
stateDiagram-v2
[*] --> VisionPatchProposed
VisionPatchProposed --> ActiveRoute: validation + write
ActiveRoute --> ActiveRoute: unchanged + reason
ActiveRoute --> ReplanRequired: frontier exhausted / drift / missing checkpoint
ReplanRequired --> VisionPatchProposed: Todo + acceptance + route delta
ActiveRoute --> VisionClosed: stage accepted, successor required
ActiveRoute --> NoFollowup: terminal evidence
ActiveRoute --> Superseded: replacement route
vision_closed 关闭当前阶段但要求 successor;no_followup、retired 或 superseded 才表达对应
的收尾含义。只回复“已 replan”而没有 Vision、Todo、acceptance 或 no-follow-up delta,是
replan_noop,不能清除 obligation。
4. 接入与呈现:Activation、Onboarding 与 Projection¶
Goal activation 不等于 automation 已启用¶
Goal activation 的 typed source 只有 active 与 stopped。active 表示控制面允许继续评估,
不证明已经安装 Host heartbeat,也不证明当前有 eligible work。完整 onboarding 还要经过:
project registered
-> registry/global visibility
-> quota can resolve Goal + Agent
-> optional Host consent and installation
-> first real tick verified
相反,stopped 会把 automatic Turn 投影为 paused;删除 Goal 还要求单独的 lifecycle precondition,
不能把 stop、disable automation 和 delete 混成一个动作。
Projection sink 只读事实,不拥有事实¶
Workspace、status、frontstage、review packet 和外部 dashboard 都是 projection sink:
projection gap 的正确恢复是修 source 或 builder,然后 read back。直接改 dashboard row 会制造第二 事实源;让公共 sink 读取 private raw document、credentials、transcript 或本地路径,会破坏 public/private boundary。
LoopX 怎样体现“闭环”¶
LoopX 的闭环不是“Agent 做了一件事”,也不是 Todo=done。更准确的定义是:一次 effect 的结果
经过验证后写回负责该事实的 authoritative source,并且控制面能据此派生出下一条合法动作,或证明
应当停止。
如果结果只留在聊天、工作区文件、远端系统或 dashboard 文案里,没有 readback、durable writeback
和 frontier audit,这条链仍然是开环的。
LoopX 用四个嵌套层次完成这个反馈:
| 闭环层次 | 需要闭合的链 | 闭合证据 | 未闭合时的机器动作 |
|---|---|---|---|
| Effect 闭环 | request -> external effect -> observation / readback | exact revision、provider receipt、验证结果或明确失败 | reconcile、retry、rollback 或 blocker |
| Turn 闭环 | decision -> execute -> validate -> writeback -> spend -> scheduler ACK | 同一 settlement identity 下的有序 receipts;prepared effect 有 provider readback | 对受 journal 管理的 step 从失败 phase 恢复;未知 readback fail closed |
| Work-graph 闭环 | Todo -> outcome -> successor / Gate / monitor / no_followup |
completion evidence 与可运行、可等待或明确终止的下一节点 | 暴露 succession / handoff / replan gap |
| Goal 闭环 | Vision + acceptance -> 多轮证据 -> frontier audit -> terminal | acceptance、Todo source、monitor、successor、handoff、replan 和 readback 全部闭合 | 继续、等待、询问、replan 或 repair,不能假装完成 |
flowchart TD
Intent["Vision + Goal + acceptance"] --> Work["Todo / Gate / monitor frontier"]
Work --> Decide["quota + interaction contract"]
Decide --> Effect["bounded effect"]
Effect --> Observe["typed observation / external readback"]
Observe --> Validate["validation"]
Validate --> Writeback["durable writeback + receipts"]
Writeback --> Audit{"frontier + acceptance audit"}
Audit -->|"runnable successor"| Work
Audit -->|"explicit wait / Gate"| Schedule["scheduler apply + ACK"]
Schedule --> Work
Audit -->|"gap or drift"| Replan["repair / replan delta"]
Replan --> Work
Audit -->|"validated terminal no-followup"| Stop["stop recurring automation"]
闭环不等于一次成功:四个检查点¶
- 结果回来了没有? 外部 effect 必须有 typed observation 或 readback;“命令退出 0”不能替代 provider、PR、文件 revision 或部署状态的权威回读。
- 结果落盘了吗? validation 通过后,evidence、Todo outcome、Vision checkpoint 和 Next Action 必须通过 owner write API 持久化;聊天总结不是 writeback。
- 下一步有归宿吗? 完成一个 Todo 后必须存在 runnable successor、明确 Gate、带
resume_when/next_due_at的等待、repair/replan obligation,或者有证据的no_followup。 - Host 知道继续还是停止吗? scheduler apply 必须有 ACK 或 failure receipt;下一次唤醒重新读取
canonical source。
terminal_no_followup是因 Goal 完成而停止 recurring automation 的依据。Goalstopped、quota paused 或 peer coordination blocked 也可能产生 stop/return-to-owner,但这些不能 反证 Goal 已闭环。
Terminal 是严格合取,不是“队列看起来空了”¶
loopx/control_plane/goals/goal_frontier/terminal.py
中的 goal_frontier_is_terminal_no_followup 不接受一个手写的 terminal flag。它要求 Todo source
完整且闭合,并且 advancement、monitor、successor、handoff、replan、acceptance、autonomy blocker
等 frontier 均无未解决项;同时还需要结构化的 no_followup intent。
因此下列情况都没有闭环:
- PR 已创建,但没有 exact-head CI / review readback,也没有 monitor 或 successor;
- Todo 已标记 done,但 acceptance 仍未满足,或 Vision checkpoint 缺失;
- 外部操作成功,但 durable writeback 或 matching spend receipt 缺失;
- 所有可见 Todo 都为空,但仍有 due monitor、blocked successor、Gate 或 retryable sink;
- heartbeat 调整了 cadence,但 Host 没有 ACK,控制面却声称调度已生效。
真正的闭环也不要求结果一定是“成功”。经验证的 blocker、负向证据、rollback、retired 或
coverage-backed no_followup 都可以诚实收口;关键是结果可追溯、状态已回写,而且下一步或终止
条件由机器可验证。
一轮完整流转示例¶
假设一个 Agent 要补充公开文档,同时发布首页仍等待用户批准:
- 读取 source: 文档 Todo 为
open且 claimable;首页 Gate 只覆盖 publication scope。 - 编译 decision: quota 为
eligible;user channel 呈现首页 Gate,agent channel 选择独立的 文档 Todo。 - 绑定 identity: Turn 绑定当前 Goal、Agent、Todo 与唯一
turn_instance_id。 - 执行: Agent 在正确 worktree 完成一个 bounded doc change。
- 验证: 双语 smoke、strict build 和 boundary scan 产生 validation receipt。
- 写回: Todo evidence 记录 revision、验证和 next action;如果完成则创建 successor 或
no_followup。 - 计费: writeback 成功后只 spend 一次。
- 调度: 重新计算后,若只剩首页 Gate,则选择 human-gate backoff;Host apply 并 ACK。
- 投影: Workspace 显示文档已完成、首页仍待决定。界面没有吞掉或扩大 Gate scope。
如果第 5 步失败,流转停在 validation;如果第 6 步失败,不得进入 spend;如果第 8 步 Host 更新 失败,记录 failure receipt 并有界重试,而不是宣称 cadence 已生效。
从症状定位 owner¶
| 症状 | 先检查 | 不要做 | 恢复动作 |
|---|---|---|---|
| UI 显示 running,但没有实际执行 | quota selection、lease、run history | 给 Todo 发明 running status |
修 projection 或 stale lease |
| Gate 让整个 Goal 停住 | decision scope 与 selected fallback | 删除 Gate 或默认 approve | 修 scope,重新计算 contract |
| blocker 已清除但 Agent 仍空转 | handoff state 与 successor relation | 手改 gate_state |
建 successor、reopen 或 no_followup |
| 已 spend,但找不到产物 | settlement receipt 与 durable writeback | 补一段聊天说明 | repair/compensation,并修 spend path |
| heartbeat 越等越久 | reset token、identity、ACK/failure receipt | 无条件缩短 cadence | 修 stale scheduler state |
| Monitor 一直 poll | next_due_at、result hash、stop condition |
把每次 poll 当 delivery | 写 bounded no-change / closeout |
| Monitor 有 observation,但没有期望的 successor | authority mode、operation receipt、material-change generation、projection outbox | 重跑 business mutation 或手改投影 | 回放同一 operation;只重试 pending projection,或用新 evidence 新建一代 |
| replan 后路线没变 | Vision/Todo/acceptance delta | 用“已重新规划”清 obligation | 写 material patch 或 unchanged reason |
| dashboard 与 CLI 冲突 | canonical source 与 projection freshness | 把 dashboard 当 source | 修 builder/source 后 readback |
源码领读入口¶
| 机器 | 首要事实或语义 owner | 深入阅读 |
|---|---|---|
| Todo lifecycle | loopx/control_plane/todos/contract.py |
工作图、权限与 Peer 协作 |
| Gate / handoff | todos/contract.py、todos/handoff_gate.py |
Control-Plane Course 第 5 讲 |
| Quota / interaction | loopx/control_plane/quota/、loopx/quota.py facade |
一轮受治理的工作 |
| Settlement | effect_program.ts、turn_transaction_contract.json |
Control-Plane Course 第 6 讲 |
| Scheduler / heartbeat / Monitor | control_plane/scheduler/、coordination/todo_monitor_poll.ts |
第 7 讲 |
| Vision / replan | control_plane/goals/goal_vision_*、work_items/*replan* |
长程收敛专题 |
| Activation / onboarding | control_plane/goals/activation.py、project bootstrap/connect |
连接你的 Git 项目 |
| Projection | 对应 status/frontstage/Workspace builder | 持久状态与只读投影 |
完整维护者级九机状态表见 State Machines。修改规则时,不要从本章的教学图 反向复制实现;先确认当前 typed owner、协议 schema、characterization fixture 和 migration boundary。
代价与边界:九组状态机买到了什么¶
本章开头那条时间线说明:分开拥有事实换来的是每台机器可以独立演进、独立修复。代价是接力 本身成了需要维护的东西,而且它不会被任何一台机器单独发现。
代价一:接力点必须显式。 一条结论从"某台机器的判断"变成"下一台机器可读的事实",中间 要有 writeback 和 receipt。缺了这一步,两台机器各自都是对的,循环却断了。这也是本章开头 那条时间线的形状。
代价二:读到的可能已经过期。 每台机器按 current source facts 判定,而读取与提交之间
存在窗口。约束这个窗口的机制是 revision basis、checksum 和 CAS,它们让过期写入被拒绝,
代价是调用方必须处理 needs_rebase 并重新读取。
代价三:同一个问题要问多次。 九组机器各自维护自己的 interpretation table,同一个现象 (例如"这一轮该不该动")会在 quota、scheduler 和 frontier 三处分别被判定一次。重复的是 入口而非知识;好处是每条规则只有一个 owner,坏处是排障必须按 owner 逐个走。
代价四:跨组的性质无法由任何单组保证。 闭环是四层嵌套合取,终局判定要求整条 frontier 没有未解决项。这意味着"看起来快完成了"无法作为可判定的状态,必须逐项证明。
边界一:九组是教学地图,并非协议常量。 没有一个 runtime 要求注册九台 service;extension 不需要对齐这个数字。真正有约束力的是 typed owner、协议 schema 和 transition 规则。
边界二:这张地图不替代 migration boundary。 图中的 owner 与当前实现语言是两个维度。 某条语义已经由 TypeScript owner 承担时,从教学图反向在 Python 里重建一遍,就会制造第二个 事实源。
边界三:幂等恢复只覆盖 journal 管理的步骤。 带持久 journal、effect_ref 和 provider
readback resolver 的 settlement step 才享有这项保证。普通的任意外部调用不会因为本章讲了
reconcile 就自动去重;resolver 缺失或返回未知状态时,settlement 会 fail closed。
边界四:图不上前线。 本章的图是解释用的,并非实现规格。修改规则要先确认当前 typed owner、协议 schema、characterization fixture 和 migration boundary。
具名失败:接力断在哪里¶
上面四条代价各自对应一类可以观察到的失败。它们都能从状态读出来,不需要读 transcript。
失败一:结论没有变成事实。 某台机器算出本轮判定,但没有经过 writeback 就结束。下一轮 重新读取 source facts,得到的是同一个旧结论。症状是同一个 action 被反复选中,而 Todo 图上没有任何变化。
失败二:事实被过期写入覆盖。 读取 basis 之后 source 已变化,而提交没有走 CAS。症状是
两个 Agent 的进展互相抹掉,或者一个 lease 已经被换掉却继续写入。needs_rebase 就是为这条
准备的拒绝路径。
失败三:投影被当成事实源。 dashboard 上改一个 row 让显示正确,source 没变。症状是 UI 与 CLI 长期不一致,而每次刷新都回到旧值。恢复方式是修 source 或 builder,再 readback。
失败四:停止的理由并不等于闭环。 Goal stopped、quota paused 或 peer coordination blocked
都可能停止轮询,它们各自是合法停止,但不证明 Goal 闭环。只有 terminal_no_followup 表达
"因已验证 Goal closure 而停止"。把这几者混作一谈,会让一个被暂停的 Goal 看起来像已完成。
不变式¶
- 循环由事实接力构成,不由调用关系构成。 一台机器改变另一台的行为,方式是把结论写成 持久事实,不会直接改它的状态。
- 每台机器只对自己的 decision table 与合法 transition 负责。 单台给出正确答案,不保证 整条循环成立。
- 派生结果不可写。 显示层拿到的是 derivation;要改变它,改 source 再重算。
- 过期写入必须被拒绝。 读取与提交之间的窗口由 revision basis 与 CAS 约束,调用方负责 处理拒绝结果。
- 终局是合取。 完整且闭合的 Todo source、没有未解决 frontier、结构化
no_followupintent,三者同时成立才算闭环。 - 教学地图不等于实现规格。 改规则先确认 typed owner、协议 schema、characterization fixture 和 migration boundary。
这六条回答的是同一个问题:当机制被拆到九组机器、又只能通过持久事实互相说话时,凭什么 相信它们还在驱动同一条循环? 本章的答案是:能,但前提是接力点显式、派生不可写、过期写入 被拒,而终局必须逐项证明。