fix(coordination): continue leased Todos across explicit Agent handoff - #4994
Conversation
huangruiteng
left a comment
There was a problem hiding this comment.
动机
审阅 2d3cd1d26218df6e460b91317539913846a1cc65,对照 #4574、shared Goal authority 的 L2/L3/L5 和 TypeScript 控制面迁移。此前显式 handoff 遇到 hard-lease Todo 一律拒绝,即使来源 Agent 持有当前租约、现有 task-lease transfer --transfer-claim 已能原子转移认领和租约。这让已登记 Agent 之间无法沿这条 CLI 路径携带工作上下文继续执行。本 PR 给出一个可独立验收的显式 CLI 交接闭环;它没有宣称已完成自动调度、外部副作用隔离或整个 Goal 的迁移。
改动思路
执行权仍由现有 task-lease proof 和验收约束判断。来源用当前 proof 写入有界 continuation note,现有 lease lifecycle 在同一事务中转移认领和租约,只在原 note 有效且其他工作事实未变时重绑 note 指纹。接收者重新 inspect,凭自己的当前 proof 做 adopt;adopt 只提交上下文接收收条,不新增认领或租约。即使重放历史收条,最终也要重读当前 Todo 和租约。Python 负责 CLI 参数、上下文文件和投影交付,语义判定留在 TypeScript。
具体改动
todo_continuation.ts将 leased prepare/inspect/adopt 接到共享执行 proof;保留 lease-free 的原有 claim 分支,并在结果返回前复核当前权威状态。continuation_adoption.ts新增 receipt-only CAS,绑定 operation、Todo、Agent、会话、revision 和 note 指纹;不通过收条授予工作权。continuation_note.ts统一上下文允许键与封闭校验;lease_claim_transfer.ts在授权转移中重绑有效 note。Python 删除重复的字段白名单,增加成对 proof 参数,成功提交后用现有 canonical Todo projection 做交付及恢复。- 文档覆盖中英文 RFC、交接操作合同及后续阶段;测试覆盖 File/SQLite 进程路径、真实 PostgreSQL、旧格式、租约失效、过期 note、CAS/收条重放和投影恢复。无新增前端、Lark 或自动加载的指导面。
对主干的风险
主要风险是“有旧收条就误判仍可执行”或“转移时把早已过期的 note 刷新”。实现中的转移前有效性检查、工作事实比较和 adopt 后重新读取当前权威状态分别挡住这两条路径;故意放松 stale-note 检查时,回归测试会失败。真实 File、SQLite 和隔离的 PostgreSQL 16 三臂演练均完成 prepare → transfer → adopt → replay,且无关 Todo/lease 事实保持不变;未改写活动 Goal。旧的 lease-free 路径继续通过。尚未覆盖自动接收者启动和独立结果回传;全量 PostgreSQL 293 用例初跑为 291 通过、2 个旧 stale-note 预期失败,更新预期后重跑了变更相关用例,没有把这说成全套重跑。
我的整体评价
对这个明确的 CLI 缺口,改动比例合理:新增的收条合同很窄,重复的 Python schema 决策被移除,既有 proof、转移和投影 owner 被复用。90 个聚焦 TypeScript 测试、16 个 Python 投影/认领用例、File claim-transfer conformance、SQLite/PostgreSQL 变更用例、类型/静态检查与风险选择的 premerge canary(19/19)通过。依照当前审查合同逐项核对了复用、状态规则、默认行为、权限命名、领域中立性及交付比例;本次未发现需阻止此 head 的问题。它只完成明确交接这一个阶段,File/SQLite 默认切换仍须后续独立验收。运行时控制面 PR 留给维护者合并。
English verdict: APPROVE - exact head 2d3cd1d26218df6e460b91317539913846a1cc65; no blocking finding, focused real-provider checks and 19/19 premerge canary passed; default cutover remains separate.
huangruiteng
left a comment
There was a problem hiding this comment.
Request changes conclusion (author-owned PR; GitHub blocks formal self-review)
动机
#4574 G1/G2 的显式 Agent 交接需要跨越已有的硬租约 Todo,而原 handoff 对这类 Todo 一律拒绝。本 PR 交付的是可独立调用的同机 CLI 路径,不是自动派工、跨主机身份认证或整个管家协作目标的完成;这个边界是合理的。
改动思路
来源 Agent 凭当前 lease proof 准备 note;认领和租约仍由原 task-lease transfer --transfer-claim 在同一 CAS 转移;接收者凭自己的 proof 读取并封存一个只表示“接收上下文”的 receipt,之后再读当前权威状态。没有把 receipt 当成新的执行授权,也没有在 Python 复制租约决策。原 lease-free 路径保留。
具体改动
关键代码讲解
todo_continuation.ts:122:将原硬租约 blanket rejection 改为当前证明约束下的 prepare/inspect/adopt;adopt 不再偷偷转移认领。continuation_adoption.ts:17,31:复用现有 lease/验收判断,写入 receipt-only 的接收决策;历史回执还要经当前状态回读验证。continuation_note.ts:303与lease_claim_transfer.ts:13:只有原 note 对旧工作事实仍有效、且除认领外的工作事实未变时,才在原子转移中重绑指纹;过期 note 不被“洗新”。- Python CLI 只做 host JSON 输入与 Markdown 投影交付;新增 File/SQLite/PostgreSQL 和反例测试。
对主干的风险
[P1] 新增 CLI 测试绕过本机 Python 发现机制,当前 main 的防回归单测也漏掉这种调用形态。 tests/control_plane_ts/leased_continuation.test.ts:196,224 两次写成 promisify(execFile)("python3", ...)。在此机直接执行
node --no-warnings --experimental-sqlite --experimental-strip-types --test tests/control_plane_ts/leased_continuation.test.ts
得到 8 通过、2 失败、1 跳过;两个失败均启动系统 Python 3.9,在 @dataclass(slots=True) 报错。通过 uv run --extra test node ... 则是 10 通过、1 跳过,说明该通过依赖了临时 PATH 注入,并未修复测试自身的解释器选择。当前 main 已合并 #4980 的 scripts/test-python.mjs,但 test_python_runtime.test.ts 的 direct-call 正则对 promisify(execFile)("python3") 返回 false,因此 guard 也会放行这次回归。
最小修复:把分支更新到当前 main,两个子进程统一使用 resolveTestPython();再给 Python 启动 guard 增加这种 curried/promisified 调用的负例。用系统 Python 3.9 在 PATH 的直接 Node 命令及 guard 测试复跑,不能仅用 uv run 代替。
其余核心路径已独立检查:todo_continuation.test.ts 在所选 Python 下 37/37 通过;新增套件 File/SQLite 10 通过、1 个 PostgreSQL 环境跳过,随后我在一次性隔离的真实 PostgreSQL 实例上单独跑 PostgreSQL 用例 1/1 通过;TS typecheck 与 diff check 通过。失效证明、旧 owner、旧回执、并发编辑、过期租约和验收 hold 均有反例覆盖。自动 host 续跑和外部副作用 fencing 仍是明确非目标。
我的整体评价
REQUEST_CHANGES,针对精确 head 2d3cd1d26218df6e460b91317539913846a1cc65。交接状态机和现有权威边界总体成立,范围也与真实 CLI 缺口相称;阻断点是新增测试重新引入用户已明确要求消除的 Python 3.9 误选,而且绕过了刚合入 main 的约束。先修这一处并在当前 main 上复跑,再重新审阅精确 head。相关的未来向重构判断:TS note/lease/receipt 所有权已足够集中,无需为此扩展新框架;测试解释器应复用已落地主干的单一 selector。
English verdict: REQUEST_CHANGES — exact head 2d3cd1d; the new CLI test launches bare Python 3.9 and evades the current-main interpreter guard.
2d3cd1d to
553397c
Compare
huangruiteng
left a comment
There was a problem hiding this comment.
Approval conclusion (author-owned PR; GitHub blocks formal self-approval). Approved exact head 553397c8a55be91ee0cb482a3add2460f3d8975c, rebased onto origin/main 46280da6e0cf1dd5a044cc26dab7a9d4bf53dbc5. This head resolves the blocking finding from my review on 2d3cd1d26 and re-runs the whole evidence pass on the new base.
[P1 resolved] The new CLI test no longer launches a bare interpreter, and the guard that missed it now catches the shape. tests/control_plane_ts/leased_continuation.test.ts takes its interpreter from scripts/test-python.mjs for both subprocesses, and tests/control_plane_ts/test_python_runtime.test.ts now sees through promisify(execFile)("python3", …) and aliased-launcher bindings while still ignoring a resolved interpreter path or an unrelated helper argument. Verified without any PATH injection: a bare node --experimental-strip-types --test run of the new suite reports 10 passed, 1 skipped (with the isolated PostgreSQL arm: 11 passed, 0 skipped), and restoring the literal python3 launch makes the guard fail and name the file.
动机
#4574 G1/G2 的显式 Agent 交接需要跨越已有的硬租约 Todo,而原 handoff 对这类 Todo 一律拒绝:来源 Agent 即使持有当前租约、现有 task-lease transfer --transfer-claim 也能原子转移认领与租约,这条 CLI 路径仍然不可用。本 PR 交付的是可独立调用的同机 CLI 交接闭环,不是自动派工、跨主机身份认证或整个管家协作目标的完成;这个边界在 RFC checkpoint 里写清楚了。
改动思路
执行权仍由现有 task-lease proof 与验收约束判断,本 PR 不新增第二套规则。来源用当前 proof 写入有界 continuation note;认领和租约仍由原 transfer planner 在同一 CAS 转移,且只把「转移前仍然有效、且只有认领变化」的 note 重绑指纹;接收者重新 inspect,再凭自己的当前 proof 封存一个只表示「接收上下文」的 receipt,之后调用方再读一次当前权威状态。即使重放历史 receipt,也要通过当前 Todo/租约重新判定。Python 只做参数传输、上下文文件读取与投影交付,语义判定留在 TypeScript。
具体改动
关键代码讲解
todo_continuation.ts:146:用leased取代原来的 blanket rejection,leased 时把 prepare/inspect/adopt 接到共享执行权判定;非当前持有者被明确要求先走task-lease transfer --transfer-claim;adopt 返回独立的adoptionreceipt 而不是claim,并在提交与重放之后再次读当前权威。continuation_adoption.ts:17,31:continuationExecutionAuthority复用evaluateCanonicalTaskLeaseProof与acceptanceWorkGuard;sealLeasedContinuationAdoption用CoordinationCommandReceipt做 receipt-only CAS,校验 operation/goal/request 摘要/Todo/Agent/note facts,并在 replay 时解码比对,历史回执不能授予当前执行权。continuation_note.ts:303:只有原 note 对旧工作事实仍有效、且除认领外的工作事实未变时才重绑todo_facts,因此过期 note 不会被「洗新」;:63把CONTEXT_ROOT_KEYS导出为生产者与校验器共享的唯一封闭 schema。loopx/cli_commands/todo_continuation.py:158,164:CLI 以--task-lease-idempotency-key/--task-lease-expected-version成对传递当前 proof,删除 Python 侧的封闭字段白名单副本,并在提交成功后用settle_canonical_todo_projection交付(重试只投影当前 head,不重放写入)。tests/control_plane_ts/test_python_runtime.test.ts:84与leased_continuation.test.ts:26:本次修复点——测试用统一 selector 选择解释器,guard 覆盖 promisified/aliased 启动形态。
对主干的风险
主要风险是「有旧 receipt 就误判仍可执行」和「转移时把早已过期的 note 刷新」。前者由 receipt 身份解码加调用后的当前权威重读挡住,后者由转移前的有效性与工作事实比较挡住;两条都有反例测试(历史回执在租约释放或验收 hold 后失效、并发 canonical 编辑无法被 receipt 掩盖、过期 note 不被刷新)。真实 provider 证据:file/sqlite 裸 node --test 10 通过 1 跳过;一次性隔离的真实 PostgreSQL 16 上 11 通过 0 跳过,另外 postgresql_authority_store.integration.test.ts 293 项全通过;control-plane 全量 3056 项(3027 通过、0 失败、29 跳过,其中一次满载运行出现 1 个 90 秒子进程超时,隔离复跑 3.2 秒通过、重复全量也通过);聚焦 Python 36 项、typecheck、ruff 与 goal 作用域 premerge(19/19、0 失败、0 人工挂起)均通过,质量回执 cqr_262ae289e6d10c4c6c36 有效。未验证项据实记录:本 goal 不等待远端 CI(上一 head 的红灯在干净 merge base 上同样复现:canonical snapshot 请求预算、runtime-shadow e2e、仓库级 mypy,以及一个与本 diff 无关的 dashboard 场景);仓库级 mypy 在 merge base 上就失败且涉及文件不在其配置清单内。边界未扩大:adopt 不新增认领或租约,不启动接收者,不外发结果,也不改变默认 provider。
我的整体评价
阻断点已按最小方式修好并有反向验证(恢复裸解释器启动即让 guard 指名该文件),其余核心路径在 file/sqlite/真实 PostgreSQL 三臂上复跑通过。改动比例合适:新增的 receipt 合同很窄,重复的 Python schema 判定被删除,既有 proof、transfer 与投影 owner 被复用;相邻的未来向重构也做了(统一上下文键集合、统一解释器选择器)。剩余工作(其余 CLI/Turn/Chat 调用方、外部副作用隔离、消费者集成、SQLite D2、默认切换)都在 checkpoint 里明确列为后续独立验收,不影响本 head 的合并判断。可交维护者合并。
English verdict: APPROVE - exact head 553397c8a55be91ee0cb482a3add2460f3d8975c; the requested-changes item (bare python3 launch plus the guard gap) is fixed and mutation-verified, and the leased handoff path passes on file, sqlite and an isolated real PostgreSQL 16 with the full control-plane suite and goal-scoped premerge green.
Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
…ogram Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
Review follow-up. The new CLI test started `promisify(execFile)("python3", …)`,
so the bare system interpreter (Python 3.9 on this host) ran `loopx.cli` and
failed on `@dataclass(slots=True)`; only a PATH-injected `uv run` hid it. The
merged checkout guard in `test_python_runtime.test.ts` also missed the curried
call shape, so it let the regression through.
Both subprocesses now take their interpreter from
`scripts/test-python.mjs`, and the guard sees through promisified and aliased
launchers while still ignoring a resolved interpreter path or an unrelated
helper argument.
Verified with the system Python 3.9 in PATH and no `uv` wrapper: the bare
`node --experimental-strip-types --test` run of the new suite reports 10
passed, 1 skipped (PostgreSQL), and restoring the literal `python3` launch
makes the guard name the file.
Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
553397c to
f4e6eeb
Compare
huangruiteng
left a comment
There was a problem hiding this comment.
Approval conclusion (author-owned PR; GitHub blocks formal self-approval). Approved exact head f4e6eeb2a4a0b3ea2f3ff8eb8b084eeff252d544, rebased onto origin/main 6d298821e8960ff71d2fac9dcbdffb3deb5ac6d3. This head resolves the blocking finding from my review on 2d3cd1d26; the branch-owned file content is byte-identical to the earlier rebased head, and the evidence pass was re-run on this base (full control-plane suite 3056 tests / 0 failed, focused continuation and guard suites, goal-scoped premerge 19/19, receipt cqr_21f6f48828cad45457f7).
[P1 resolved] The new CLI test no longer launches a bare interpreter, and the guard that missed it now catches the shape. tests/control_plane_ts/leased_continuation.test.ts takes its interpreter from scripts/test-python.mjs for both subprocesses, and tests/control_plane_ts/test_python_runtime.test.ts now sees through promisify(execFile)("python3", …) and aliased-launcher bindings while still ignoring a resolved interpreter path or an unrelated helper argument. Verified without any PATH injection: a bare node --experimental-strip-types --test run of the new suite reports 10 passed, 1 skipped (with the isolated PostgreSQL arm: 11 passed, 0 skipped), and restoring the literal python3 launch makes the guard fail and name the file.
动机
#4574 G1/G2 的显式 Agent 交接需要跨越已有的硬租约 Todo,而原 handoff 对这类 Todo 一律拒绝:来源 Agent 即使持有当前租约、现有 task-lease transfer --transfer-claim 也能原子转移认领与租约,这条 CLI 路径仍然不可用。本 PR 交付的是可独立调用的同机 CLI 交接闭环,不是自动派工、跨主机身份认证或整个管家协作目标的完成;这个边界在 RFC checkpoint 里写清楚了。
改动思路
执行权仍由现有 task-lease proof 与验收约束判断,本 PR 不新增第二套规则。来源用当前 proof 写入有界 continuation note;认领和租约仍由原 transfer planner 在同一 CAS 转移,且只把「转移前仍然有效、且只有认领变化」的 note 重绑指纹;接收者重新 inspect,再凭自己的当前 proof 封存一个只表示「接收上下文」的 receipt,之后调用方再读一次当前权威状态。即使重放历史 receipt,也要通过当前 Todo/租约重新判定。Python 只做参数传输、上下文文件读取与投影交付,语义判定留在 TypeScript。
具体改动
关键代码讲解
todo_continuation.ts:146:用leased取代原来的 blanket rejection,leased 时把 prepare/inspect/adopt 接到共享执行权判定;非当前持有者被明确要求先走task-lease transfer --transfer-claim;adopt 返回独立的adoptionreceipt 而不是claim,并在提交与重放之后再次读当前权威。continuation_adoption.ts:17,31:continuationExecutionAuthority复用evaluateCanonicalTaskLeaseProof与acceptanceWorkGuard;sealLeasedContinuationAdoption用CoordinationCommandReceipt做 receipt-only CAS,校验 operation/goal/request 摘要/Todo/Agent/note facts,并在 replay 时解码比对,历史回执不能授予当前执行权。continuation_note.ts:303:只有原 note 对旧工作事实仍有效、且除认领外的工作事实未变时才重绑todo_facts,因此过期 note 不会被「洗新」;:63把CONTEXT_ROOT_KEYS导出为生产者与校验器共享的唯一封闭 schema。loopx/cli_commands/todo_continuation.py:158,164:CLI 以--task-lease-idempotency-key/--task-lease-expected-version成对传递当前 proof,删除 Python 侧的封闭字段白名单副本,并在提交成功后用settle_canonical_todo_projection交付(重试只投影当前 head,不重放写入)。tests/control_plane_ts/test_python_runtime.test.ts:84与leased_continuation.test.ts:26:本次修复点——测试用统一 selector 选择解释器,guard 覆盖 promisified/aliased 启动形态。
对主干的风险
主要风险是「有旧 receipt 就误判仍可执行」和「转移时把早已过期的 note 刷新」。前者由 receipt 身份解码加调用后的当前权威重读挡住,后者由转移前的有效性与工作事实比较挡住;两条都有反例测试(历史回执在租约释放或验收 hold 后失效、并发 canonical 编辑无法被 receipt 掩盖、过期 note 不被刷新)。真实 provider 证据:file/sqlite 裸 node --test 10 通过 1 跳过;一次性隔离的真实 PostgreSQL 16 上 11 通过 0 跳过,另外 postgresql_authority_store.integration.test.ts 293 项全通过;control-plane 全量 3056 项(3027 通过、0 失败、29 跳过,其中一次满载运行出现 1 个 90 秒子进程超时,隔离复跑 3.2 秒通过、重复全量也通过);聚焦 Python 36 项、typecheck、ruff 与 goal 作用域 premerge(19/19、0 失败、0 人工挂起)均通过,质量回执 cqr_262ae289e6d10c4c6c36 有效。未验证项据实记录:本 goal 不等待远端 CI(上一 head 的红灯在干净 merge base 上同样复现:canonical snapshot 请求预算、runtime-shadow e2e、仓库级 mypy,以及一个与本 diff 无关的 dashboard 场景);仓库级 mypy 在 merge base 上就失败且涉及文件不在其配置清单内。边界未扩大:adopt 不新增认领或租约,不启动接收者,不外发结果,也不改变默认 provider。
我的整体评价
阻断点已按最小方式修好并有反向验证(恢复裸解释器启动即让 guard 指名该文件),其余核心路径在 file/sqlite/真实 PostgreSQL 三臂上复跑通过。改动比例合适:新增的 receipt 合同很窄,重复的 Python schema 判定被删除,既有 proof、transfer 与投影 owner 被复用;相邻的未来向重构也做了(统一上下文键集合、统一解释器选择器)。剩余工作(其余 CLI/Turn/Chat 调用方、外部副作用隔离、消费者集成、SQLite D2、默认切换)都在 checkpoint 里明确列为后续独立验收,不影响本 head 的合并判断。可交维护者合并。
English verdict: APPROVE - exact head f4e6eeb2a4a0b3ea2f3ff8eb8b084eeff252d544; the requested-changes item (bare python3 launch plus the guard gap) is fixed and mutation-verified, and the leased handoff path passes on file, sqlite and an isolated real PostgreSQL 16 with the full control-plane suite and goal-scoped premerge green.
|
Merged as Changed surfaces: the canonical continuation command and its TypeScript decision path ( Checks run on the reviewed head: bare Mutation/counterfactual evidence: restoring Skips and holds: remote CI lanes for this head were not awaited under this goal's review policy; the previous head's lanes were red for failures reproduced on a clean merge base (canonical snapshot request budget, runtime-shadow e2e, repository mypy) and one dashboard scenario this diff does not touch. No manual holds. Coverage is adequate because the CLI path was exercised on three real providers, the negative authority cases have dedicated reproductions, and the tree that Residual risk: automatic host-driven continuation, external-effect fencing, the remaining CLI/Turn/Chat callers, SQLite D2 evidence and the provider default switch remain separate packages recorded in the RFC checkpoint; the adoption receipt is additive and grants no execution authority. |
Goal And Delivered Outcome
task-lease transfer --transfer-claimmoves claim and lease in one CAS; the receiver inspects and seals adoption with its own current proof. A current note is rebound in the transfer transaction. Stale notes remain stale. Adoption does not acquire, renew or transfer execution authority. See validation rows below.46280da6efromorigin/main. This does not close the local default program.Scope And Continuation
adoptionreceipt and rechecks current authority after replay.Review Follow-Up
Review on
2d3cd1d26requested changes: the new CLI suite launchedpromisify(execFile)("python3", …), so the bare system interpreter (Python 3.9) ranloopx.cli, and the merged checkout guard missed that curried call shape. Both subprocesses now take their interpreter fromscripts/test-python.mjs, and the guard intests/control_plane_ts/test_python_runtime.test.tssees through promisified and aliased launchers while still ignoring a resolved interpreter path or an unrelated helper argument. The branch is rebased onto currentorigin/main46280da6e.Validation
553397c8a.staticnpm run typecheck:control-plane; Python Ruff and mypy on the changed CLI adapter;git diff --check.integrationreal_backendregression_parityintegrationstaticintegrationloopx canary premerge --from-git-diff: 19 selected and executed checks, no failures or warnings; exact-scope quality receipt valid.node --no-warnings --experimental-sqlite --experimental-strip-types --test tests/control_plane_ts/leased_continuation.test.tswith the system Python 3.9 inPATHreports 10 passed / 1 skipped, and 11 passed / 0 skipped against a disposable PostgreSQL 16 cluster;postgresql_authority_store.integration.test.tsreports 293 passed / 0 failed on the same server;npm run test:control-planereports 3056 tests (3027 passed, 0 failed, 29 skipped); the focused Python selection is 36 passed;typecheck:control-plane, Ruff and the goal-scoped premerge (19 checks, 0 failures) pass. Restoring the literal bare-python launch makes the interpreter guard fail and name the file.Frontend / Visual Evidence
Type of Change
LoopX Area
Technical Direction
Shared-authority RFC fixture impact
loopx_coordination_production_scale_fixture_v0; the existing native/imported complete work graph is reused, with no speculative fixture field.Boundary Checklist
none.Signed-off-bytrailer (git commit -s).