Todo continuation and closure readback¶
Todo list, status and quota distinguish a completed record from a closed work
slice. A completed tracked advancement Todo still needs an existing successor
or an explicit no_followup=true. This read policy lives in
control_plane/todos/succession.ts; Python normalizes legacy input and renders
its decisions. It belongs to the existing Todo control plane, uses the selected
AuthorityStore, and adds no capability or extension provider.
PR dependency observation / PR 依赖观察¶
pr_merged:owner/repo#42 identifies a dependency; absence of a merge event
does not prove that GitHub is still pending. Delivery-history supervision no
longer exempts that unobserved wait. Causal blocked closeout continues to require
a registered monitor_changed or todo_done target with current readback.
The registered issue-fix TurnStart hook observes exact
unfinished PR dependencies from the complete Todo head, including deferred work.
It polls at most four distinct repository-qualified dependencies per invocation,
with a durable 30-minute cadence per dependency for successful and failed attempts.
Executing callers share one Goal-local single flight; a concurrent caller reports
observation in progress without another provider request. A missing
repository binding is reported for repair; it never guesses from PR numbers.
An exact merged observation appends the existing native pr_merge fact so the
original condition becomes satisfied. Resume readback uses the same complete
retained Goal log as observation, before any recent-event display limit. The
typed resume owner keeps one original merge proof per exact dependency; more
than 500 unrelated events or 256 other dependencies cannot evict that proof.
List, status, quota and archived reads keep their bounded presentation, and
restarts need neither repeated remote reads nor rewritten merge timestamps.
Queue disappearance, closed-without-merge
and provider failures cannot satisfy it. This observation changes no Todo status,
requirements, claims, leases or Goal acceptance. Deferred ready work still needs
its normal lifecycle transition.
The existing typed capability owner resolves external_evidence_poll from the
current Goal declaration, remembered host observations and invocation facts;
remembered revocation defeats inherited permission. No per-command flag is
required when the owner already supplies permission. Shell/filesystem access,
Todo requirements and explicit prequota invocation do not grant network access.
Unavailable permission sources return a failure without contacting GitHub.
Remove all inherited grants or record the capability as unavailable to stop
future automatic reads; previously verified merge facts remain usable.
Fresh executing quota should-run and turn run-once --execute compose the
same registered hook before collecting the governing decision. Compatibility
heartbeat-prequota --goal-id GOAL --agent-id AGENT uses that hook too. Generated
full/thin heartbeat prompts retain their optional prequota command when the
host explicitly declares polling; their executing guard also resolves current
owner permission. Observation can continue while business compute is paused or
business execution is stopped, if observation remains authorized. The ordinary
stop, quota, claim, lease and launch checks still govern subsequent work.
Status, list, page/context reads, prompt generation, plans, dry runs,
managed-step and --resume-turn-key recovery perform no fresh observation.
JSON carries capability-owned readback under
turn_start_capability_hook_dispatch.results[].readback, with hook id
issue_fix.pr_wait_observation: remote read count, duration in milliseconds,
degraded/failure readback, target/cadence diagnostics and observation
state_written. Compatibility prequota retains checks.pr_dependencies.
Markdown consumes the hook's bounded summary without PR-specific core logic.
Diagnostic lists include at most 16 rows and 16 Todo ids per row, with explicit
omission counts; selection and satisfaction still use complete canonical state.
The 64 KiB hook budget remains unchanged. An uncertain provider/log response
reports null reads/effects; rejected post-invocation results also preserve
uncertain effects and invalidate the quota projection cache. Neither case may
certify zero effects. Provider failures do not gate unrelated eligible work.
Observation has no separate model call or business quota debit. It is polling;
network latency and total host costs remain workload-dependent.
File/SQLite source qualification covers full/thin generated heartbeat commands, executing guard/native Turn, cross-entrypoint cadence, concurrent invocation, revocation, actual validated protocol-host work and original-journal replay. The App/Lark shared context backend reads the same canonical readiness without polling. This does not qualify an installed App/heartbeat/Lark executor or automatic recovery of an existing owner's live Goal. Those owners must retain their actual identity, permission and scheduling readback. Observe, condition satisfaction, admission and accepted subsequent work are separate evidence.
Full decisions retain the selected Task's existing resume_when, resume_ready
and complete resume_condition; TurnEnvelope carries the same fields in
action.selected_todo and signs them with the action. Native Host inputs retain
that proof, including its repository, event identity and original event time,
alongside the complete Task requirements. This is observation transport;
execution still needs the ordinary current admission. The projection does not
recompute satisfaction or substitute another Task's proof. Tasks without these
fields keep their prior shape, and saved journal envelopes keep their original
signed contents. managed-step remains a read-only controller and does not poll.
pr_merged:owner/repo#42 只标识依赖;缺少合并事件不证明 GitHub 仍在等待。
历史交付监督不再豁免这种未观察等待,因果阻塞结算仍要求已注册的
monitor_changed 或 todo_done 目标及当前回读。
注册的 issue-fix TurnStart hook 从完整 Todo 源选择
精确的未完成 PR 依赖(含 deferred),每次最多核验四个不同仓库限定的依赖。
成功和失败均按每项依赖保留 30 分钟核验节奏,重启后仍有效;执行入口共用 Goal 内的
single-flight,并发调用只读回观察进行中,不重复请求。仓库缺失需要修复,不能按编号猜测。
真实合并观察写入既有 pr_merge,原条件自然满足。回读与观察共用完整保留的 Goal
日志,在最近事件展示裁剪前由 TS resume owner 保留每项精确依赖的原始合并证明;
超过 500 条无关事件或 256 项其他依赖不能挤走它。list、status、quota 与归档读取
仍保留原展示边界,重启不需要重复远端读取或改写合并时间。离开 open 列表、关闭未合并或
provider 失败不能解除等待。观察不修改 Todo 状态、要求、认领、lease 或 Goal 验收;
已满足条件的 deferred Todo 仍走正常生命周期转换。
既有 typed capability owner 从当前 Goal 声明、已记住的宿主观察和本次参数解析
external_evidence_poll;宿主记录的撤销优先于继承授权。owner 已提供权限时,不必给
每条命令手工补 flag。filesystem/shell、Todo 要求与显式调用 prequota 都不授予网络
权限。权限来源不可用时只返回失败,不联系 GitHub。移除所有继承授权或记录能力不可用,
即可停止后续自动读取;已验证合并事实保留。
执行性的 quota should-run 与新的 turn run-once --execute 在 governing decision
前组合相同注册 hook;兼容 heartbeat-prequota 也调用它。full/thin heartbeat 在宿主
显式声明轮询时保留原 prequota 命令,执行 guard 仍从当前 owner 解析权限。业务额度暂停
或业务停止时,仍获授权的观察可以继续;随后工作仍受 stop、quota、claim、lease 与
启动准入约束。status、list、页面/上下文读取、prompt 生成、plan、dry run、
managed-step 与原 journal 恢复均不执行新观察。
JSON 在 turn_start_capability_hook_dispatch.results[].readback 返回能力自有读回,
hook id 为 issue_fix.pr_wait_observation,包含读取次数、耗时、退化/失败、目标/
节奏诊断与观察 state_written;兼容 prequota 保留 checks.pr_dependencies。
Markdown 消费 hook 的有界摘要,核心 renderer 不持有 PR 业务规则。诊断列表最多
16 行、每行最多 16 个 Todo id,并声明省略数量;选择和满足判断仍使用完整 canonical
源,64 KiB hook 预算不变。provider/日志响应丢失时,读取/效果为 null;执行后
被契约拒绝的结果也保留效果不确定性并使 quota 投影缓存失效,不能宣称零效果。
provider 失败不阻挡无关的合法工作。观察不单独调用模型或扣业务额度;它是轮询,
网络延迟与完整宿主成本仍依赖实际负载。
File/SQLite 源码验证覆盖 full/thin 生成命令、执行 guard/原生 Turn、跨入口节奏、 并发、撤销、实际协议宿主的独立校验工作与原 journal 重放。App/Lark 共用上下文后端 无需轮询即可读取相同 canonical 就绪状态。这不代表已安装 App/heartbeat/Lark 执行器或既有 owner 的真实 Goal 已恢复;这些 owner 仍需以真实身份读回权限和调度。 观察、条件满足、执行准入与后续验收工作是分别成立的证据。
完整决策保留所选 Task 既有的 resume_when、resume_ready 与完整
resume_condition;TurnEnvelope 将相同字段置于 action.selected_todo,纳入
动作签名。原生 Host 输入同时保留该证明的仓库、事件身份、原事件时间与完整任务要求。
这些是观察事实的传输,执行仍需正常当前准入;投影不重判满足,也不借用另一 Task 的
证明。缺少这些字段的任务保持原形态,已保存 journal 保留原签名内容。
managed-step 仍是只读控制器,不执行观察轮询。
Completion dependency cycles / 完成依赖环¶
An unfinished todo_done chain that reaches a cycle now projects
resume_condition_invalid with invalid_state=dependency_cycle, consistently
with canonical execution admission. dependency_cycle_todo_ids identifies up
to eight members in dependency order; dependency_cycle_size retains the full
count. A task leading into the cycle is also invalid, even when another Agent
owns the cyclic tasks. Actual done records, including retained archived
completion, terminate the chain. Missing records in a partial source do not
prove a cycle; other resume kinds keep their own rules.
When no independent work is executable, the current/unclaimed, non-excluded
Agent lane requests successor replan instead of quiet waiting. Read the exact
Todo with todo list --goal-id GOAL --todo-id TODO --format json, inspect the
reported members and their owners, and repair only a reviewed obsolete
dependency through the existing Todo lifecycle and owner/lease/CAS checks.
Cycle detection grants no authority to delete dependencies or complete work.
An already-bound Turn retains its identity and exposes repair=dependency
unless its earlier verified writeback still requires settlement. In that case,
finish only the original settlement through its exact receipt-bound spend
command; a later cycle cannot replace the committed outcome or debit it twice.
Settlement leaves the current dependency graph unchanged. Otherwise, re-enter
the same Turn after authorized dependency repair. New causal wait closeout cannot
qualify a cycle, and current delivery supervision cannot use it as an ordinary
wait exemption. Frozen v0 causal receipts keep their historical pair-only
readback semantics; they never certify today's graph. No receipt migration or
automatic dependency rewrite is performed.
未完成的 todo_done 依赖链进入环时,读回与执行检查统一返回无效依赖;
dependency_cycle_todo_ids 最多展示八个环内任务,完整数量保存在
dependency_cycle_size。实际完成(含归档完成)截断追踪;局部快照缺少任务不能
作为有环的证据。没有独立可执行工作时,当前 Agent 可处理的任务进入明确重规划,
不能静默等待。通过精确 Todo 读回查看环及 owner,只能按既有权限、租约和 CAS
规则修复经核实已经过时的依赖;诊断不授权删边或虚报完成。已绑定 Turn 保留
原身份,修复后重新进入同一 Turn。若该 Turn 先前已有经验证的写回、只差结算,
优先执行原回执对应的结算命令;后来形成的环不能改写已提交结果或重复扣费,
结算也不会解除当前依赖。新等待结算和当前交付监督不再将环视为正常
因果等待;旧 v0 回执仍按冻结的双任务事实读回,不重新裁决过去,也不证明当前图合法。
Synthetic packaged-UI readback: before, cycle diagnosed, mobile, and after owner-reviewed repair.
Relationship evidence¶
The policy evaluates the complete available Todo graph before role, status,
Agent, ID or display-limit selection. It recognizes explicit
successor_todo_ids, superseded_by, and advancement records pointing back
through unblocks_todo_id or resume_when=todo_done:<source>.
- A declared successor must exist and differ from the source. A dangling or self reference does not close work. A retained archived record remains relationship evidence; it does not become active work.
- Explicit links retain their existing role-neutral meaning. Inferred
successors require an advancement task.
monitor_changedis a resume condition, not an inferred work successor. - An existing successor records continuation lineage. It does not prove that the successor has executed, been accepted or acquired a lease. This is not a transitive Goal acceptance proof or a cycle-freedom certificate.
- Basic historical checkboxes without structured execution context keep their compatibility behavior. Explicit no-follow-up remains an independent closeout choice. Deferred work is never classified as a completed advancement gap.
Both completed-work warnings and handoff gates use that same graph. Previously handoff ignored explicit successor lists, while completed-work warnings accepted nonexistent/self links. Filtering or archiving a valid inferred successor could also manufacture a warning that was absent on the full source.
| Handoff facts, in precedence order | State |
|---|---|
| Existing, non-self supersession target | superseded |
| Deferred source | deferred |
| Source has not completed | blocking |
| Completed with explicit no-follow-up | cleared_no_followup |
| Completed with a resolved successor | cleared_with_successor |
| Completed without either | cleared_without_successor |
Only active dependency-linked executor exclusions are handoff gates. These states describe the gate; none changes claims, grants, leases or stored Todos. The existing legacy stale-closeout prose hint remains a compatibility adapter until route-closeout writers supply the explicit replan flag. Its substring matching can overmatch narrative and is not used for successor resolution, handoff state or permission. An explicit boolean replan flag takes precedence.
Selection, proofs and transport¶
A fresh full-source evaluation accompanies each internal summary row as an ephemeral Python attribute, outside dictionary fields and JSON serialization. Its fact digest prevents reuse after relevant item edits; it is a consistency check, not authentication. Fresh parsing/canonical reads always recompute it rather than trusting stored evaluations. Shadow capture discards this derived field; canonical records and durable source digests do not gain a second authority. Public parser rows keep their existing dictionary schema. Final list/status responses copy plain dictionaries, retaining decision fields without exposing the internal evaluation or expanding the hot-path payload.
When choosing between Agent and User frontier waits, compare UTC instants, not the original ISO spelling retained by a projected deadline. Equal instants retain source order; due/expired exclusion and pre-compaction evidence are unchanged. An earlier deadline cannot be postponed by a different UTC offset.
The existing Python deadline rule remains until it can join an already-needed whole-consumer typed batch. A separate projection RPC adds hot-path latency; retiring a small rule alone does not justify that cost. This correction grants no Todo, lease, settlement or host-schedule authority and does not qualify provider defaults or actual host backoff adoption.
Legacy archive/recreate can retain one archived and one active record with the same logical Todo ID. The active record owns that ID's inferred edges regardless of source order; archived metadata cannot supply stale edges for the replacement. Two active or two archived records with the same ID remain ambiguous and reject. This read precedence does not relax canonical capture's unique-identity contract.
A status/ID/Agent-filtered list describes that selection but emits no Goal-source or terminal-closure proof. A display limit alone does not change the source: counts, warning decisions and proof eligibility are computed first. Handoff state, successor count and executor exclusions survive the bounded list view.
Terminal closure additionally requires no deferred/convergent work, unresolved handoff, successor gap or route-replan obligation. Watch-only monitors retain the existing convergent-work exception. It remains separate from Goal acceptance.
Field-presence sets are interned inside a succession RPC request so long archive histories do not repeat identical metadata shapes. The full graph is retained; no record sampling, per-page rule evaluation or RPC limit increase is used.
Migration and operation¶
The shared archive-capture owner retains the reachable continuation graph as
well as resume dependencies and standing decisions. It preserves real record
status, including deferred history: capturing a deferred record does not
satisfy todo_done. Duplicate identities, invalid archive state and incompatible
role/authority combinations still reject capture. Unrelated archive records
remain outside the bounded canonical capture.
For a historical record with an explicit role=agent but no task_class,
capture retains the same class as the legacy active read. The Python Markdown
codec sends that compatibility classification as legacy_task_class, separate
from recorded metadata; the TS selector accepts it only for a recorded Agent
role and an Agent-compatible class. Explicit classes take precedence. Missing
roles still require an explicit Agent-only class; user authority, contradictory
scope and unknown classes cannot be reconstructed from text. The selected class
also drives inferred successor closure, so dependency traversal and materialized
records agree. The existing legacy text classifier remains a compatibility
codec, not a second authority-admission rule or a new classification heuristic.
New legacy Agent archive moves persist the read classification when the source omitted it, alongside the source role. Unknown metadata and original receipt lines remain intact. Existing archives are not rewritten by capture.
The internal request is todo_archive_dependency_capture_request_v2, and the
result is todo_archive_dependency_capture_result_v1 with the selected class.
Python and the bundled TS runtime must be upgraded together; an older runtime
rejects the new request instead of silently losing classification or edges. Existing
historical capture/promotion receipts are not rewritten or upgraded in place.
Requalify capture on this runtime before a future promotion.
Todo mutation adapters take the existing archive limit and headroom from
completed_archive, avoiding a reverse import of the status collector. Both
canonical transport and the retained unpromoted writer keep the command's
default of ten completed records; status compatibility exports remain intact.
Handoff display is another live compatibility boundary. Status/index, summary and selected-work consumers still use the Python handoff adapter; its stable identity, nested-field precedence, bounded source references and credential rejection must survive retirement. Credential assignments are rejected before display truncation; ordinary discussion of token budgets remains readable. Canonical exact-Todo reads return the source record, while status adds derived handoff context. Neither context grants execution authority or proves completion. The handoff characterization tests exercise these distinctions and real File/SQLite CLI readback with legacy mutation modules absent. They enable a later whole-consumer typed migration; the same fixture also takes a normally built wheel, installs it into an isolated target and runs its installed console entrypoint against both real providers, with the old writer modules and four shadow capture producers physically removed. Module, distribution and packaged TypeScript provenance are checked before those CLI journeys. Creation recovery preserves later writes and the original provider; provider loss refuses admission, while leased settlement and monitor retries retain their original receipts without repeating effects. This is a disposable retirement oracle, not adoption on an active Goal, old-backup import/restore qualification or final writer retirement. Preserve the existing adapter until that migration replaces its real callers, rather than adding a projection RPC for every displayed Todo.
Ordinary pytest runs keep the source arm. To require the installed retirement arm, first prepare the qualified Chat bundle and build the wheel through the normal distribution build, then supply that artifact explicitly. A supplied missing/invalid wheel fails the run; it is never converted to a source-only pass.
LOOPX_TODO_RETIREMENT_WHEEL=path/to/built.whl uv run --extra test python -m pytest tests/control_plane/test_canonical_todo_writer_isolation.py tests/control_plane/test_todo_handoff_retirement_contract.py -q
This variable selects test coverage only; it grants no production access or feature authority. No setting is installed or persisted. Omit the variable to run the ordinary source arm. Installed test results qualify only the supplied artifact and removed paths.
Use existing read commands; no activation or new option is needed:
loopx --registry registry.json todo list --goal-id example-goal --format json
loopx --registry registry.json todo list --goal-id example-goal --todo-id todo_source --limit 1 --format json
Completion retries also close a receipt/head read race: if the first receipt lookup misses a peer commit but the head already shows completion, recheck the matching operation receipt before interpreting a supplied validation receipt. This returns the committed result without repeating effects; no matching receipt still follows the existing validation and identity guards.
Reads do not repair Markdown, mutate Todo/lease state or replay a business operation. Missing promoted Markdown is acceptable; an unavailable provider is not an empty Goal. No frontend configuration changes are needed: CLI, manager Chat details and existing status/quota consumers retain their current entry points. To reverse a business decision, use its ordinary mutation, not a read model or restored Markdown. Code rollback retains provider state and fences; old read policies can again misclassify these cases.
中文¶
“这个 Todo 已完成”与“这一段工作已闭环”不同。结构化推进任务完成后,要有真实
存在的后继,或明确声明 no_followup=true。TS 现在统一解析显式后继、替代关系和
反向交接关系;Python 保留旧输入规范化与展示。不存在的 ID、自指 ID 不再遮住
未闭环工作,归档与筛选也不再凭空制造后继缺口。
关系在完整可用源上判定,然后才筛选、分页。按状态、ID 或 Agent 筛出的列表不能 为整个源出具闭环证明;仅限制显示条数不会改变完整源上的计数与判断。handoff 的 状态与排除执行者信息不会在压缩展示时丢失。派生判断带相关事实摘要以防陈旧复用, 但不是授权凭据,也不写回 provider。旧 prose replan 提示仍仅用于兼容;显式 布尔标记优先,不能靠标题里的几个词推导后继存在或授予权限。
归档捕获现在保留与当前工作有关的后继图和原有依赖、standing decision。延后历史
可以被保留,但状态仍是 deferred,绝不会因此满足 todo_done。新的内部 v1 请求
要求 Python 与 TS 配套升级;旧回执不被重新解释。长历史重复字段集合采用无损共享,
没有放宽 RPC 上限或丢弃历史节点。
本阶段关闭一组 T3/L5 读语义及其 L7 捕获依赖,不代表 D1 投影投递、D2 耐久性、 D3 整 Goal 切换完成,也不修改默认 provider。PostgreSQL 使用相同规则,服务部署与 资格仍独立。复杂 fixture 和只读快照演练不是长期 soak 或生产晋升许可。
handoff 展示仍有真实 Python 调用方,退役时必须保留稳定身份、嵌套字段优先级、 有界源引用及截断前的凭据拒绝;普通 token budget 讨论仍可展示。canonical 精确 Todo 读取返回源记录,status 才加入派生交接上下文,两者都不授予执行权限或证明 完成。上述测试覆盖旧 mutation 模块缺席时的真实 File/SQLite CLI 读回,只为后续 整组消费者迁入 TS 建立兼容基线。同一 fixture 还将正常构建的 wheel 安装到隔离目录, 用其实际 console 入口在两种真实 provider 上运行;旧 writer 模块与四个 shadow capture producer 物理移除,先核对模块、distribution 和打包 TS 来源。创建重试 保留后来写入与原 provider,provider 缺失拒绝准入,lease 结算和 monitor 重试 保持原回执且不重复效果。这是一次性隔离退役验收,不是活跃 Goal 的安装采用、 旧备份导入恢复或最终 writer 退役。替代真实调用方前保留既有适配器,不为每个 展示 Todo 新增一次投影 RPC。
普通 pytest 保留源码验证。完成 Chat bundle 的正常构建并生成 wheel 后,用上文
LOOPX_TODO_RETIREMENT_WHEEL 指定产物,才运行安装态退役验证;指定的 wheel
缺失或无效会失败,不降级为源码通过。此变量只选择测试覆盖,无生产或功能授权,
不持久化配置;省略变量恢复源码验证。结论仅覆盖指定产物与实际移除路径。
Todo mutation 适配器直接复用 completed_archive 的既有阈值与余量,消除对 status
collector 的反向导入;canonical transport 与保留的旧 writer 仍默认保留十个完成
记录,status 的兼容导出不变。