Skip to content

RFC:Agent Loop Effect Interpreter(v0)

字段
状态 已接受
日期 2026-08-08
作者 LoopX maintainers
范围 公开控制面文档、packet 合同、重构方向、测试策略

本文与英文版互为语义镜像;两者不一致属于缺陷。

摘要

LoopX harness 应该被解释、设计和测试为一个 agent loop 外围的 effectful program,而不是一组彼此无关的状态机集合。

标准形状是:

model -> effect request -> harness interprets effect -> observation -> model

agent loop 是循环本身;harness 是解释每个 effect request 并向下一个模型步骤返回 observation 的 effectful program。

该框架建立在齐梦星空的公开讲座系列之上: 主线一:Agent Loop 是 effectful program(1)主线一:Tool Calling 是 Kleisli arrow(2)主线一:Agent Loop 里的小魔法:函数的组合(3)

LoopX 的职责是中间两步:它接收来自 agent 或 host 的 effect request,决定是否以及如何解释它,写回 observation,并把控制权交还给下一个循环迭代。

本 RFC 建立这套心智模型,定义 canonical packet 语义,并给出一个里程碑计划,让文档、代码和测试逐步与该模型对齐。

里程碑状态

里程碑 状态
M0 RFC 与 Lecture 0 已合并(#2905、#2906、#2908)
M1 Canonical packet 示例 已合并(#2907、#2910)
M1.5 组合视角 已合并(#2911)
M2 Bounded context 对齐 已合并/完成(#2912-#2915、#2919、#2926、#2933、#2963-#2982)
M3 聚焦测试族 已合并/完成(#2916-#2918、#2925、#2929、#2984)
M4 架构文档 已合并/完成(#2921、#2923、#2924、#2985)
M5 稳态评审 已合并/完成(#2922、#2931、#2984、#2985)
M6 通用 effect-program 抽象 Narrow gate 已完成(#2963-#2987);定性提升需要 M7
M7 Effect Program Runtime 已重新规划:先做 outcome contract 和一个纵向 runtime slice,再泛化

为什么这很重要

现在 LoopX 有很多正确但难以解释的部件:

  • todo lifecycle 与 handoff 状态;
  • quota decision 与 spend 状态;
  • scheduler 与 heartbeat 状态;
  • capability gate 与 user gate;
  • vision、monitor 与 replan 状态;
  • evidence 与 run history。

每个部件都有自己的状态机。难点不是这些状态机存在,而是读者无法立即看到每个状态机解释的是什么 effect、产生什么 observation,以及该 observation 如何回到下一轮循环。

agent-loop-as-effectful-program 视角通过在每个地方问同一个问题来解决这一点:

谁解释这个 effect request,返回什么 observation?

核心心智模型

Agent Loop

底层循环是:

model -> effect request -> harness interprets effect -> observation -> model

模型提出下一个动作。harness 决定该动作是否被允许、如何执行、如何处理失败,以及如何把结果编码成下一个模型步骤可用的输入。

Effectful Program

纯计算是:

A => B

effectful 计算是:

A => F[B]

F 捕获外部世界:持久化、权限、预算、时序、通知、调度、evidence 和失败。

最好把 LoopX harness 理解为长程 agent loop 外层的那个 F

GoalState => F[QuotaDecision]

把 LoopX 概念映射到本框架

讲座概念 LoopX 对应物
Agent loop 每个自动化 heartbeat、PR monitor 和持续重构 turn
Effect request todo addquota spendrefresh-statenotifymonitor pollbind-agent-thread
Harness 解释 effect quota should-run + interaction_contract + capability_gate + work_lane_contract + scheduler_hint
Observation Quota packet、run history、evidence log、state writeback
Middleware mount points User gate、capability bridge、scheduler ACK、cooldown、external evidence poll
A => B 理想化的 GoalState => GoalState
A => F[B] 真实的 GoalState => F[QuotaDecision]

Canonical Packet 语义

每个重要的控制面 packet 都应该能用四个语义槽解释:

  1. effect_request
  2. interpretation
  3. observation
  4. next_effect

quota should-run 为例:

{
  "effect_request": "agent proposes next bounded turn",
  "interpretation": {
    "route": "advancement_task",
    "capability_gate": "repair_bridge",
    "scheduler_hint": "active_work"
  },
  "observation": {
    "decision": "run",
    "recommended_action": "...",
    "state_writeback": "validated_progress"
  },
  "next_effect": "execute bounded turn, then refresh-state"
}

这些槽不是第二套 schema。它们是对现有 packet 字段的文档与命名纪律。只有当真实调用方需要一个统一位置读取全部四个槽时,新 packet 才可以增加 effect_interpretation envelope。

组合与 Around 语义

标准循环是一个 effectful 步骤:

GoalState => F[QuotaDecision]

公开讲座系列区分三层组合:

组合 形状 LoopX 对应物
函数组合 A => BB => C Read model -> projection -> decision
Kleisli 组合 A => F[B]B => F[C] 一个有界 turn、host effect、经过验证的 writeback
Middleware 组合 (A => F[B]) => (A => F[B]) capability_gateinteraction_contractwork_lane_contractscheduler_hint 中的 around decision

LoopX 不暴露通用 Python middleware registry。它的 around 语义是声明式的、packet 形状的。

有界 Kleisli Runtime Decision

M7 把 Kleisli 组合当作执行要求,而不是装饰性术语。选中的 turn-closeout slice 应该能解释为一组 typed 步骤:

A => F[B]
B => F[C]
A => F[C]

对该 slice 而言,F 必须保留带 receipt 的结果,并显式表达 cancellation、permission-denial、budget-rejection 和 settlement outcome。组合可以用 closeout 局部的 bindflat_mapand_then seam 实现,但 M7.2 必须证明语义,而不是标准化某一个方法名。聚焦测试必须覆盖:

  • identity:增加 typed no-op 步骤不改变 receipt 或 effect;
  • associativity:对同一组有序步骤重新分组,不改变 receipt、短路点或外部可见 effect 顺序;
  • ordered short-circuit:typed failure 阻止后续 effect 执行,同时不丢失失败类型;
  • replay:durable receipt 跳过已经提交的 effect;以及
  • non-commutativity:writeback、spend 与 host handoff 不允许重排。

runtime 合同有两个一等调用方。默认 Codex App 路径通过跨 agent/host 边界的 data-encoded CLI effects 结算普通 LoopX turn。隔离 turn driver 通过 in-process callbacks 执行同一 settlement 形状。它们应共享 plan、receipt、effect identity 和 failure algebra,但不需要共享同一个 executor,因为它们的 authority boundary 不同。在共享执行所有权被证明之前,通用 Kleisli、middleware stack、executor registry 或通用 Effect monad 仍为时过早。

共享 settlement algebra 由核心 effect_program 模块拥有。Quota 只提供 Codex App/CLI plan builder 与兼容 re-export;各 runtime adapter 组合核心 algebra,而不是继承领域 program,也不会把自己的执行权上移到通用基类。

Handler 是数据,不是 Callable

Runtime middleware 接收一个 handler callable,并决定是否调用、调用一次、重试、fallback 或短路。LoopX 无法跨 context 和 session 边界接收 model 或 host callable。相反,interpreter 在 packet 中返回 next_effect:CLI actions、scheduler ACK 和 failure hint。host 或下一个自动化 turn 调用这个 data-encoded handler。

这保留了 around 风格的能力,同时让 handler 可持久、可重放:

  • short-circuit:decisioneffective_action 可以说 skipwaitmonitor_quiet_skiprepair_bridgeask_owner,而不假装原 effect 已执行;
  • rewrite:work_lane_contract 可以用到期 monitor 或 Lark inbox 抢占普通 advancement,capability_gate 可以把 next effect 重写为先物化缺失能力;
  • settle:scheduler_hint.ack_hintfailure_hint 告诉 host 如何提交成功或失败,unchanged_poll 限制重复尝试。

失败、取消、权限和预算保持在 typed packet 字段中可见,而不是被 catch-all wrapper 吞掉:

Around layer Packet 字段 短路示例 重写示例
Capability capability_gate ask_ownerrepair_bridgeunsupported 为缺失能力生成 repair todo 与 CLI actions
Interaction interaction_contract 用户通道 action_requiredmode Primary action、protocol action、next CLI actions
Work lane work_lane_contract Monitor 或 inbox 抢占、must_attempt_work=false Selected lane、obligation、next_lane
Scheduler scheduler_hint 暂停/删除 heartbeat、no-spend quiet RRULE、cadence class、stateful backoff

这些 around layer 的顺序是合同,不是实现细节。改变顺序会改变先观察到哪个 gate、哪个 monitor 可以抢占普通工作,以及 host update 失败后是否仍然期待 ACK。这类变更需要 parity fixtures 和聚焦测试。

用讲座审视 middleware stack 的同样问题来评审 LoopX around decision:

  1. 正在解释哪个 effect request?
  2. 哪个 around layer 拥有该 decision,它发出什么 observation?
  3. 它能否在不假装 effect 已执行的情况下短路?
  4. data-encoded handler(next_effect)在哪里?
  5. failure、cancellation、permission 和 budget 是结构化的还是被吞掉的?
  6. around-layer 顺序是否显式并被测试?
  7. evidence、trace 和 budget continuity 是否能穿过 host effect 到达 writeback、ACK 和 spend?

CLI 是高密度 Effect

单个 tool call 是 ToolInput => F[ToolOutput]。LoopX CLI packet 是高密度 effect:一条命令可以在同一个 request 中携带 permission、budget、参数校验、外部执行、失败语义、scheduler ACK 和 writeback。模型仍然只提出 effect request;harness 把它们解释为 CLI actions。

如果某个 vendor API 后续支持串行 tool calls 或交错推理,并不会改变 LoopX 的形状。它只是 interpreter 内部的一种 execution mode:

  • serial、parallel 和 interleaved 是执行策略,不是新状态机;
  • effect_request -> interpretation -> observation -> next_effect 保持稳定;
  • next_effect 从单条 CLI command 变成有序 effect program。

通用 Effect-Program 抽象

当前的 EffectTurn 视角刻意是 read-only 且 quota-specific 的。它为 LoopX 提供一个稳定词汇、一个 canonical read model,以及围绕一个真实 packet 的 around 语义。它还不是通用 effect-program 抽象。

仅靠重构不会产生该抽象。重构会创建共享抽象能够安全存在的 bounded contexts。两条轨道并行且同等重要:

  • refactor:让每个状态族留在自己的 bounded context;
  • generalize:只有在真实 runtime 调用方需要时,才抽取共享 effect 形状。

与 Goal Replan 的边界

Effect 执行与 goal replan 相邻但是不同的控制面问题:

Plane 问题 权威状态
Goal path 为什么继续、还缺什么 outcome、下一步该跑哪条路径? Vision、acceptance evidence、path delta、Todo frontier
Effect runtime 一条选中的路径应如何执行、失败、恢复和结算? Effect plan、host execution receipts、observation、writeback

effect runtime 不能决定某个 milestone 是否仍然服务于最终目标。反过来,goal replan 不能重复 effect runtime 的 permission、idempotency、failure 或 settlement 语义。更通用的 effect interpreter 本身不会改善 long-horizon goal alignment。

Product Outcome Contract

M7 只有在至少产生一个下列最终 effect 时才有理由存在:

  1. 从真实 host path 中移除一个竞争性的 transition 或 command truth 来源。
  2. 通过稳定 effect ids、显式 authority、idempotency 和 typed receipts,让部分执行可恢复。
  3. 让第二个 runtime caller 以更少的编排代码、且不丢失 domain invariants 的方式复用同一执行合同。

下面是支持性证据,而不是 product outcome 本身:

  • 存在一个 protocol 或 dataclass;
  • EffectTurn 在 packet builder 中更早构造;
  • 另一个 packet 可以映射到同样的四个名词;
  • module line budgets 和 parity tests 通过;或
  • 更多 Todo、monitor 或 gate 族放在同一个 interface 后面。

第一个 M7 vertical slice 必须满足所有这些验收检查:

  • 一条真实路径拥有 request -> plan -> host execution -> receipt -> reduce
  • 至少删除一个旧 command builder、settlement branch 或并行 runtime path;
  • fault injection 证明 retry/resume 不会重复 external effect、ACK、writeback 或 spend;
  • permission denial、cancellation、budget rejection 和 partial completion 仍然可区分;
  • public packets、CLI budgets 和现有 domain transition invariants 保持兼容;并且
  • 在抽取共享 interpreter protocol 前识别出第二个 caller。

出现任一 kill criterion 时停止或收窄 M7:

  • 新 layer 主要把 raw mappings 或 CLI strings 穿过另一个对象,而不拥有执行语义;
  • production code 增长,但没有移除任何既有 truth source;
  • 提议的 executor 跨越它无法自行结算的 model、user 或 host ownership boundary;
  • parity 无法把行为变化归因到新路径;或
  • 第二个真实 caller 并不需要提议的共享 protocol。

今天已存在什么

  • EffectRequestEffectInterpretationEffectObservationEffectNextEffectTurn 作为 canonical slots。
  • interpret_quota_should_run_packet 作为第一个真实 interpreter。
  • interpret_turn_result_packet 作为第二个真实 interpreter。
  • EffectNext.execution_mode 支持 serialparallelinterleaved 执行策略。
  • EffectProgrameffect_program_from_ordered_steps 作为现有 guided_transaction.ordered_steps 上的 read-only 形状。
  • R1 替换:bootstrap guided rendering 通过 EffectProgram 读取 ordered_steps(#2955)。
  • R2 替换:turn executor 通过 interpret_turn_result_packet 解析 result kind(#2956)。
  • R3 替换:Codex CLI 本地 scheduler commands 通过 EffectProgram 构建(#2957)。
  • R5 替换:quota should-run TurnEnvelope 通过 interpret_quota_should_run_packet 派生 canonical action、writeback 和 scheduler slots。
  • around 语义编码在 capability_gateinteraction_contractwork_lane_contractscheduler_hint 中。
  • 聚焦测试和文档固定该视角。

还缺什么

  • 只有在两个 runtime execution path 需要相同的 plan/receipt 语义后,才定义最小 interpreter 或 executor protocol。两个 packet reader 本身不足以证明该合同。
  • 一个真实 host 或 turn-driver caller,在执行有序 effect program 时保留 failure、cancellation、permission 和 budget 语义。

R4 继续保持 deferred,直到存在真实的多步 executor caller。

何时泛化

只有至少两个真实 runtime execution path 需要相同的 plan/receipt 语义时才泛化。Packet interpreters 可以建立共同 read model,但本身不足以证明共享 executor protocol 的合理性。

在此之前,把抽象保持为文档化视角,并增加证明每个 packet 无损映射的测试。这可以避免构建一个没有 runtime 使用的通用 Effect 框架。

替换状态

R1、R2、R3 和 R5 已完成:

  • R1 bootstrap guided rendering 通过 EffectProgram(#2955);
  • R2 turn executor result-kind resolution 通过 interpret_turn_result_packet(#2956);
  • R3 Codex CLI scheduler command set 通过 EffectProgram(#2957);
  • R5 quota should-run TurnEnvelope 通过 interpret_quota_should_run_packet

R4 仍 pending,并且在真实多步 host/turn-driver caller 执行有序 effect program 之前不得实现。

定性改进计划

当前 effect 抽象是 read lens 加三个小的 runtime replacement。只有以下全部为真时,M6 才能被称为 mostly complete:

  1. 热模块缩小到有界大小:
  2. loopx/quota.py 低于 2000 行(当前 1043);
  3. loopx/status.py 低于 2000 行;
  4. loopx/heartbeat_prompt.py 低于 1200 行。
  5. loopx quota should-run 通过有界的 should_run decision module 构建,loopx.quota.build_quota_should_run 成为 thin compatibility wrapper。
  6. EffectTurnEffectProgram 被 CLI quota、turn driver 和 bootstrap construction 消费,而不只是测试和 renderer。
  7. 没有 effect 抽象保持 test-only。
  8. Maintainability、import-graph、CLI output 和 hot-path interface ratchets 无新增 exception 通过。
  9. Doubao/model-behavior shadow qualification 覆盖变更后的 agent-facing packets。

阶段:

  • Q1:停止 milestone claims,保持 M6 in progress。
  • Q2:刻画热模块并为 quota.pystatus.pyheartbeat_prompt.py 捕获 parity fixtures。
  • Q3:把 quota should-run decision 和 packet builder 抽取到有界模块。已完成:should_run.py entry decision(#2963)、should_run_prepare.py preparation chain(#2964)、should_run_packet.py route/packet assembly(#2965)。
  • Q4:把 status read models、collection 和 presentation 抽取到有界模块。已完成:bounded status projections(#2967-#2978);status.py 1392。
  • Q5:把 heartbeat prompt builders 抽取到有界模块。已完成:bounded heartbeat task body/builder/support modules(#2979/#2980/#2982);heartbeat_prompt.py 159。
  • Q6:让 CLI quota、turn driver 和 bootstrap construction 消费 EffectTurn / EffectProgram。已完成:quota should-run TurnEnvelope 消费 interpret_quota_should_run_packet(#2983);turn driver 和 bootstrap 消费 interpret_turn_result_packet / effect_program_from_ordered_steps
  • Q7:为每个抽取增加 quality gates 和聚焦测试。已完成:RFC module budgets 在 module_metric_baseline.json 中 ratchet,聚焦 M6 quality-gate pytest 固定热模块上限和 runtime EffectTurn 消费(#2984)。
  • Q8:gate 通过后再重新评估 M6。已完成:见下方 audit evidence。

M6 完成证据

  • 热模块行数:loopx/quota.py 1049、loopx/status.py 1392、loopx/heartbeat_prompt.py 159。
  • Maintainability ratchet:ok=true,无 unreviewed findings,无 stale exceptions。
  • 聚焦 M6 audit suite:172 通过,覆盖 quota parity、status re-export、heartbeat support、effect interpreter/program/turn families、CLI output budget/differential、import boundaries、model-behavior/Doubao shadow 和 turn driver/executor。
  • loopx canary quality-auditready=truegap_count=0drift_count=0

M7:Effect Program Runtime

M6 让 effect lens 被 runtime 消费,但仍然偏描述性:packet builders 先计算 decision,再映射到 EffectTurn。M7 不能因此让每个状态族实现同一个 protocol。它必须首先证明 typed effect runtime 移除一个真实的编排 split-brain。

M7.0:盘点真实多步 runtime 候选。选中的核心是从稳定 quota decision 出发,经过验证 writeback 和 exactly-once spend 的 normal-turn settlement。它有两个真实 adapter:默认 Codex App interaction path 和隔离 turn driver。Scheduler apply 和 ACK 保持为 delegated host handoffs。Guided bootstrap 未被选中,因为部分 ordered steps 属于 model、user 或 host;quota-to-host scheduling 未被选中,因为 LoopX 无法自行结算外部自动化 mutation。

M7.1:在添加 protocol 前刻画选中的 vertical slice。为合法与非法 transition、部分执行、重试、取消、权限拒绝、预算拒绝和结算捕获 parity fixtures。durable transfer 必须包含 writeback 和 scheduler handoff 时的 cancellation、host execution 和 quota spend 时的 permission denial,以及 writeback 后的 spend-budget rejection。该阶段保留当前 runtime behavior,包括 M7.2 预期修复的任何 split projection。还必须刻画默认 Codex App selection-drift seam:选中 Todo 完成后,writeback 推进 frontier 时,spend 仍必须结算原始 effect identity,而不是绑定到新选中的 successor。

M7.2:用一个 typed plan/receipt algebra 替换核心 settlement truth。plan step 必须携带稳定 kind、owner、precondition、idempotency identity 和 expected receipt。首先让默认 Codex App path 把 completion、refresh 和 spend 绑定到原始 quota-turn effect identity,而不是新的 Todo selection。然后让隔离 turn driver 消费同一 algebra。每个 replacement PR 都必须删除对应的 manual command 或 settlement truth。Raw mappings 和 free-form CLI commands 可以保留为 compatibility payloads,但不是语义执行合同。组合必须满足上文定义的 identity、associativity、short-circuit、replay 和 ordering 性质,保持 cancellation、permission denial 和 budget rejection 可区分,并让 scheduler apply 或 ACK 留在 agent-owned settlement boundary 之外。

M7.3:在两个 M7.2 adapter 都消费经过验证的 plan/receipt 语义后,比较它们的执行所有权。只有在删除重复编排且不跨越 Codex App agent/host boundary 时,才抽取最小的共享 executor 或 Kleisli-like bind protocol。不要增加 registry 或通用组合框架。quota should-run 可以从同一个 canonical decision plan 派生 packet 和 effect projection,但更早构造 EffectTurn 本身不是验收条件。如果两个 caller 只共享 algebra 而不共享 executor boundary,用结构化 no-follow-up decision 关闭 M7.3,并保留各自的 local executor。

M7.4:只有在移除重复知识时,才一次扩展一个有界状态族。Todo、monitor、capability、scheduler 和 gate 状态机保留自己的 domain transition invariants。它们不能仅仅因为 packet 字段相似就移到共享 protocol 后面。

因此,之前的 R5-R9 列表不是实施队列:

  • 共享 EffectInterpreter protocol 推迟到 M7.3;
  • packet-before-view ordering 被一个 canonical decision-plan source 取代;
  • guided bootstrap 仍是候选,并受 host-boundary review 约束;
  • turn closeout 是另一个候选,可能是更好的第一个 vertical slice;并且
  • family-wide alignment 被 M7.4 的 duplicate-knowledge gate 取代。

M7 只有在真实 vertical slice 满足 Product Outcome Contract、旧路径被移除、且第二个 caller 为保留的抽象提供证据时才完成。

Replacement-First 规则

每个 M6 代码变更都必须替换现有真实 runtime call path,而不是增加并行的未使用抽象。

  • 替换前:为现有路径捕获 parity fixture 或 smoke。
  • 替换:让 runtime 读写通过 EffectTurn / EffectProgram
  • 替换后:删除旧路径;只有当真实外部 import 或持久化合同需要时,才保留 compatibility wrapper。
  • 仅测试性增加不算 M6 progress。

示例替换:

  • bootstrap_command_pack 应在 rendering 或 validation 前通过 effect_program_from_ordered_steps 读取 ordered_steps
  • turn_driver/executor 应在提交 receipt 前通过 interpret_turn_result_packet 派生 result status 和 next phase。

把状态机当作解释表

不要把状态机教成一串 enum values,而是把每个状态机教成一张解释表:

Input effect | Interpreter | Decision | Observation | Next effect

以 monitor scheduling 为例:

Monitor cadence or due horizon
  -> scheduler interpreter
  -> host RRULE / initial interval
  -> scheduler_hint packet
  -> next heartbeat or monitor poll

这保留了现有状态机,同时让它们的目的可见。

里程碑

M0:RFC 与 Lecture 0

目标:发布本 RFC,并在任何状态机细节之前增加一讲来讲述这个故事。

步骤:

  1. 合并本 RFC。
  2. docs/development/control-plane-course/ 增加 Lecture 0: Harness Is the Effectful Program
  3. 重写 docs/product/core-control-plane/state-machine.md,为每个状态族加入 interpretation-table section。
  4. 更新 docs/README.md 和课程导航,指向本 RFC。

验收标准:

  • 新贡献者可以用一段话和标准循环形状解释 LoopX。
  • 每份现有状态机文档都链接回 interpretation-table 模式。
  • 没有 runtime behavior 变更。

M1:Canonical Packet 示例

目标:选择 quota should-run 作为 canonical 示例,并让四个语义槽在文档和 smoke 中可见。

步骤:

  1. docs/reference/effect-interpreter-packet.md 增加描述 quota should-run 四个槽的 public-safe 文档 section。
  2. 增加聚焦 pytest 或 smoke,断言 raw inputs 到 canonical interpretation fields 的映射。
  3. 保持现有 payload 字段不变。

验收标准:

  • 读者能从一个真实 packet 的 effect request 追踪到 observation。
  • 没有 CLI output budget regression。
  • 没有真实 caller 时不引入新 runtime contract。

M1.5:组合视角

目标:让 around 语义在 canonical packet 视角中可见。

步骤:

  1. 在本 RFC 和 Lecture 1 中记录三层组合与 data-encoded handler。
  2. 扩展 EffectTurn 增加 next_effect,让四个语义槽不仅在 prose 中、也在代码中表示。
  3. 增加聚焦测试,证明 capability gate 是结构化 around decision:它会短路、重写 next effect,并保持 permission 语义可见。
  4. 在公开文档中引用公开 Tool Calling 与 Function Composition 来源。绝不引用内部讲座材料。

验收标准:

  • 读者能回答真实 packet 的 next_effect 编码在哪里。
  • 代码视角覆盖 effect_requestinterpretationobservationnext_effect
  • 没有 runtime behavior 变更。

M2:Bounded Context 对齐

目标:把现有重构与 effect-interpreter boundary 对齐。

步骤:

  1. 继续把 status.pyquota.pygoal_frontier.py 拆分为 read-model、projection 和 decision modules。
  2. 用 loop 术语命名边界:
  3. read model = 当前 A(state);
  4. projection = observation;
  5. decision = effect interpreter。
  6. 为现有 public imports 保持 re-export compatibility。
  7. 在至少两个真实 caller 需要同一 envelope 之前,不创建通用 effect abstraction。

验收标准:

  • Module names 和 docstrings 明确 effect-interpreter 角色。
  • Public import compatibility tests 保持绿色。
  • Maintainability 和 line-budget smokes 保持绿色。

M3:聚焦测试族

目标:把大型控制面 smoke 按 effect family 转换为聚焦 pytest modules。

步骤:

  1. 为以下内容创建聚焦 pytest modules:
  2. work-lane contract;
  3. quota decision;
  4. scheduler/monitor interpretation;
  5. state-machine interpretation tables。
  6. 保留证明 CLI 仍可工作的 thin end-to-end smokes。
  7. 为 failure、cancellation、gate 和 observation writeback 路径增加 regression tests。

验收标准:

  • 每个 effect family 都有聚焦 pytest module。
  • 在其聚焦替代通过前,不删除大型 smoke。
  • Full public smoke suite 保持绿色。

M4:架构文档

目标:让架构和产品文档使用同一个故事。

步骤:

  1. 围绕标准循环重构 docs/architecture.md
  2. 更新 control-plane course,让每讲引用同一个 effect_request -> interpretation -> observation 流程。
  3. 在 README 产品语言仍只说 "state machine" 而未解释 interpretation 角色处更新。

验收标准:

  • 公开文档不再把 LoopX 呈现为一堆无关状态机。
  • 技术读者能在每个文档化工作流中识别 loop boundary、effect request、interpreter 和 observation。

M5:稳态评审

目标:让 RFC 成为 living contract。

步骤:

  1. 增加 canary smoke 或 docs smoke,检查 canonical packet 文档存在。
  2. 对照四个语义槽评审新状态机和 packet fields。
  3. 当新 effect family 需要新 canonical slot 时更新本 RFC。

验收标准:

  • 维护者文档和课程材料引用本 RFC。
  • 新控制面功能说明它们解释哪个 effect。

M6:通用 Effect-Program 抽象

目标:在没有投机性框架构建的前提下,从 quota-only read lens 走向共享 effect-program 抽象。

步骤:

  1. 增加第二个真实 interpreter,例如 interpret_turn_result_packetinterpret_status_packet,并用聚焦测试证明 EffectTurn 对该 family 也无损。
  2. 把 packet interpretation 保持为 read-model seam。只有当两个执行路径需要相同 plan/receipt 语义时,才抽取共享 runtime interpreter 或 executor protocol。暂不增加 registry 或通用组合框架。
  3. EffectNext 增加 execution_mode,用聚焦测试文档化 serial / parallel / interleaved 语义。
  4. 当一个 owner 能执行并结算多个步骤时,引入 data-encoded ordered effect program shape 和真实 executor seam。在选中第一个 slice 前,qualify turn closeout、guided bootstrap 和 quota-to-host scheduling;现有 ordered list 本身不建立可执行 authority boundary。
  5. 在每一个 interpreter 中保持 failure、cancellation、permission 和 budget 语义结构化。不引入 catch-all wrapper。

验收标准:

  • 至少两个 packet family 产生 EffectTurn
  • Runtime code(不只是 tests)消费共享 shape。
  • next_effect 能用显式 execution mode 表达有序 effect program。
  • 在出现第二个 runtime caller 之前,不增加通用 Effect monad、registry 或 middleware framework。

测试策略

测试应按 effect family 组织,而不是按源文件大小:

effect_request -> interpretation -> observation -> next_effect

每个聚焦 pytest module 都应覆盖:

  • positive routing;
  • gate 与 capability decisions;
  • failure 与 cancellation;
  • observation writeback;
  • public imports 兼容性。

大型 smoke 只保留为 thin end-to-end checks。

Runtime Replacement 测试

对每个 runtime replacement:

  • focused pytest 覆盖新 seam 并与旧路径 parity;
  • thin public smoke 练习真实 CLI 或 host path;
  • CLI output budget regression 保持绿色;
  • model-behavior / Doubao shadow qualification 覆盖 agent-facing packet 变更;
  • canary premerge 包含 core-control-planecanary-runner profiles。

Non-Goals

  • 不把全部状态机合并成一个巨型 enum。
  • 在没有两个真实 caller 时,不创建通用 Effect 抽象。
  • 不把 test-only lenses 算作 M6 progress;每个 M6 变更必须替换真实 runtime call path。
  • quota.pystatus.pyheartbeat_prompt.py 仍然过大,或 effect 抽象仍 test-only 时,不把 M6 标记为 mostly complete。
  • 在出现第二个 interpreter 和真实 executor caller 前,不把当前 EffectTurn 视角当作通用 runtime 抽象。
  • 不为命名而重写 quota should-run
  • 不用 effect-runtime 泛化替代 final-goal acceptance、evidence 或 replan。
  • 不因为 guided bootstrap 的 ordered steps 可以渲染成 EffectProgram 就让它可执行;保留 model、user 和 host ownership boundary。
  • 在没有证明重复 transition knowledge 并删除它之前,不让 Todo、monitor 和 gate 族对齐到共享 protocol 后。
  • 没有迁移窗口时,不删除现有 public compatibility routes。

风险

  • 命名漂移:可能把 "effect" 当装饰而不改变语义。缓解:每个 RFC milestone 必须产生真实文档或测试变更。
  • 过度抽象:通用 effect envelope 可能变成未使用 scaffolding。缓解:只有第二个 caller 需要时才增加共享 envelope。
  • 装饰性命名:文档说 "effect program",runtime 仍只传 CLI strings。缓解:M6 要求在 RFC 声称通用抽象前出现第二个 interpreter 和真实 runtime replacement。
  • 测试 churn:过快转换大型 smoke 会降低 e2e confidence。缓解:在聚焦测试覆盖相同行为前保留 thin e2e。
  • Goal/effect 混淆:可靠 executor 可能持续执行错误 milestone。缓解:把 goal-path evidence 和 effect settlement 作为独立合同,并在 milestone closeout 时同时要求两者。
  • Executor boundary 越界:ordered steps 可能属于不同 actor。缓解:只有在 owner 和 receipt boundaries 明确后才选择第一个 vertical slice。

开放问题

  • effect_interpretation 应该成为 hot quota packet 的一等字段,还是只作为文档化视角?
  • 每个 capability 应该拥有自己的 interpretation table,还是 table 保留在中心文档?
  • 何时应把新状态机视为新 effect family?
  • 哪个 packet family 应该成为第二个真实 EffectTurn interpreter:turn result、status 还是 monitor poll?
  • 何时 next_effect 应从扁平 CLI tuple 变成带 execution_mode 的有序 effect program?
  • 哪个候选以最窄 authority boundary 移除最多重复编排:turn closeout、guided bootstrap 还是 quota-to-host scheduling?
  • 什么稳定 effect identity 和 receipt 能让该路径在部分执行后恢复,而不会重复 ACK、writeback、spend 或外部 action?
  • 哪个第二个 runtime caller 需要相同、经过验证的 plan/receipt 语义?
  • 何时 EffectProgram 应从 host-driven 变成 runtime-owned,哪些 steps 必须保持 model、user 或 host-owned?

成功指标

  • 新技术读者能用一段话解释 LoopX。
  • 每个主要控制面 packet 都能穿过四个语义槽追踪。
  • Focused pytest coverage 增长,大型 smoke files 缩小。
  • 公开文档和课程材料使用同一套 loop 词汇。
  • 现有 CLI output budgets 和 public compatibility contracts 保持绿色。
  • 至少一个 M7 vertical slice 删除旧 command/settlement source,并通过 retry、partial-failure、permission、cancellation 和 budget 测试。
  • 只有在两个真实 caller 使用它之后,共享 runtime protocol code 才存在。

结论

LoopX harness 不是 "一组状态机"。它是长程 agent loop 外围的 effectful program 和 effect interpreter。本 RFC 让这个故事显式化,并为重构和测试工作提供稳定目标。

参考