Skip to content

fix(todos): allow an agent-bound user gate's initial unblock link - #8618

Open
xiaobaifly7 wants to merge 2 commits into
loopx-project:mainfrom
xiaobaifly7:fix/user-gate-resume-path
Open

xiaobaifly7 wants to merge 2 commits into
loopx-project:mainfrom
xiaobaifly7:fix/user-gate-resume-path

Conversation

@xiaobaifly7

@xiaobaifly7 xiaobaifly7 commented Nov 11, 2026 •

Copy link
Copy Markdown
Contributor

A user gate created before its dependent Agent Todo must accept its first valid
link under hard_lease through the ordinary administrative provider-CAS edit.
Previously this path also accepted nonexistent targets, persisted the bad link,
and then refused an ordinary correction with handoff_mode_requires_lease,
leaving the real dependency blocked with its required decision scope intact.

Approach and scope

The shared TypeScript admission owner now looks up the proposed target in the
same revision-bound provider snapshot used for the edit. It requires an Agent
Todo in that Goal whose claimed_by matches the gate's bound/blocked agent.
Missing targets, User Todos, targets in another Goal, another lane, and unclaimed
work return user_gate_link_target_invalid in preview and write without creating
a receipt or changing the provider revision or Markdown. The same gate and operation ID
remain usable for a corrected, valid link.

The original registered-actor, first-link-only, lease/proof and CAS fences remain.
Linking keeps the gate open and preserves the dependent's decision scope; only
explicit completion with the original authorized decision/evidence consumes the
scope and resumes the dependent. Monitor poll closeout still ends its Turn.
The TS positive/negative fixture now includes a real, same-lane Agent target.
The public Todo contract documents the target requirement and recovery behavior.

Validation

Native Windows, Node 24.18.0 and checkout-local Python 3.14.6. Reused the existing
checkout .venv; no installed LoopX runtime or uv tools were changed. All
commands below use non-login PowerShell without the user's profile and begin
with . .local/test-env.ps1 to keep temporary files, caches, home and runtime
inside this worktree.

.venv/Scripts/python.exe -m pytest -q tests/control_plane/test_user_gate_resume_cli.py -k rejects_invalid --basetemp=.local/v0 --tb=short
.venv/Scripts/python.exe -m pytest -q tests/control_plane/test_user_gate_resume_cli.py tests/control_plane/test_settled_monitor_user_gate.py --basetemp=.local/v1 --tb=short
.venv/Scripts/python.exe .local/short-test-paths.py -q tests/control_plane/test_settled_monitor_user_gate.py -k sqlite --basetemp=.local/v2 --tb=short
node --no-warnings --experimental-sqlite --experimental-strip-types --test tests/control_plane_ts/todo_update.test.ts tests/control_plane_ts/todo_terminal_decision.test.ts tests/control_plane_ts/todo_resume_condition.test.ts tests/control_plane_ts/todo_execution_dependency.test.ts
npm.cmd run typecheck:control-plane
.venv/Scripts/python.exe -m ruff check tests/control_plane/test_user_gate_resume_cli.py tests/control_plane/test_settled_monitor_user_gate.py
.venv/Scripts/python.exe .local/check-capture-positives.py
git diff --check

Before the production edit: all 20 new provider/target/preview-write cases failed
because invalid links succeeded. Both saved missing-target write fixtures
contained the bad link; ordinary correction was rejected, and the actual
dependent remained blocked with its required scope.

After: all 20 new regression cases and both original File/SQLite gate journeys
pass. The combined Python gate/monitor command returned 35 passed and 11 failed;
all failures were SQLite monitor fixture initialization, before admission,
because their generated database paths reached 260 characters. A local pytest
fixture changes only the temporary directory basename; the resulting native
database path is 231 characters. Rerunning those 11 cases passes all 11 (13
unselected), with no storage/OS mocks or product changes. All 46 selected Python
cases therefore pass across the initial run and targeted rerun.

The four focused TS files pass all 96 tests; typecheck, Ruff and diff whitespace
checks pass.
The capture check passes with a real native Windows regular file, equal file
identity and unequal lstat/fstat ctimes. No OS/stat simulation was needed for
that positive case, and no capture implementation was modified.

Risks and delivery boundary

Unclaimed work has no matching lane and must first be claimed by the gate's
bound/blocked agent. Existing persisted links retain the ordinary retargeting
fence; this change prevents invalid first links rather than migrating old state.
No state schema, dependency, CLI option, UI, or scheduling policy is added.
Qualification is source-checkout CLI plus real disposable File/SQLite stores.
PostgreSQL, the full suites, packaged UI, installed runtime and real Harness
were not exercised; remote CI was not queried.

Fetched origin/main at 9ce3a46bfe; git merge-tree --write-tree --name-only HEAD origin/main exits 0. No rebase was needed; original commit 3d81ac59b1
is preserved. New signed-off fix commit: fb1f467.

🤖 Generated with Claude Code

Signed-off-by: xiaobaifly7 <196511160+xiaobaifly7@users.noreply.github.com>

@loopx-agent loopx-agent left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewer: model_agent · gpt-6.1-sol · OpenAI · runtime_reported · max

本次评审对应提交 3d81ac59b12303be5b3767c9a8a12f153fecef95。

[P2] 首次门禁链接会持久化不存在的目标,并阻止普通更正。

动机 / Motivation

管理 hard_lease Goal 中待用户文件或决策的操作者和受影响的 Agent。主干对已存在的 User gate 初次链接返回 handoff_mode_requires_lease;用户门禁不能作为 agent Todo 领取执行租约。File/SQLite 同一合成流程中,正确 ID 由旧版拒绝变为可预览、可写入和可重放;仅在独立明确的文件决策完成后才清除对应 scope 并允许正常领取。只在隔离 source CLI 的真实 File 和 SQLite 做测试,未安装到活动运行时,未查远端 CI,也未覆盖 PostgreSQL 或打包 UI。未授实际用户批准。首次链接只要求目标 ID 是非空字符串。File 和 SQLite 都会提交一个不存在的目标,原门禁随后因已有链接无法用普通更新修正,实际依赖任务仍处于 blocked,原 decision scope 也未消耗。

改动思路 / Approach

现有 TypeScript owner 适合承载一次只改 relation 的规划动作,复用原 CAS 能避免新的租约或状态。 修复目标是先有 gate 后有依赖的一次绑定;原决策、重放和 monitor 边界应保持。 评审按 docs/project-agent-todo-contract.md 的 Write Contract 和现有 monitor receipt 合同执行。boundUserGateLink 只应为合法的首个依赖链接放开行政写入,目标需在同一 provider snapshot 中验证。

具体改动 / Changes

关键代码讲解

基准 04958a89abbbf6b7d3f79327437e2d6deae97453 与评审头 3d81ac59b12303be5b3767c9a8a12f153fecef95 使用同一真实 File/SQLite CLI 夹具。FIRST-LINK-CAS:有效同 lane 目标由主干的 handoff_mode_requires_lease 变成可预览、提交和按 operation id 重放;错误 actor 与混合 status 编辑仍拒绝。DECISION-AND-TURN:写链接后 gate 仍 open,scope 与 leases 保留;明确完成后实际 agent Todo 恢复、可以领取。独立 primary monitor 的回归用例仍证明 poll 结束原 Turn,后续实现需要新 Turn。

对主干的风险 / Risks

FIRST-TARGET-VALIDATION:在 todo_update_admission.ts:227,一个拼写正确但从未存在的 Todo id 能通过预览并落盘。File 和 SQLite 都读回 applied。随后用真实依赖 id 更正,两路都拒绝;读回实际依赖仍为 blocked,decision scope 未变化。请在同一 CAS snapshot 校验有效 agent 目标和门禁范围。无效目标要明确拒绝并保留原门禁,再用正确 id 验证仍可提交。

语义与 CI 对齐

独立复现覆盖 File 和 SQLite 的正常、错误 actor、混合编辑和不存在目标。26 个 Python 和 96 个 TS 用例通过;typecheck、Ruff 和标准 premerge 的 5 个直接检查、19 个所选检查通过。现有用例不覆盖这次已证实的错误链接,远端 CI 与活动运行时未查。

我的整体评价 / Verdict

请求修改。继续保留现有 CAS 和原门禁完成流程;修复首次目标校验后复测两个 provider,确保错误输入不占用唯一的首次链接机会。改动在已有 CLI/typed Todo admission,产品视觉没有变化。未验安装态、打包 UI 或 PostgreSQL。

English verdict: REQUEST_CHANGES. The first-link path persists a nonexistent target in both File and SQLite, then the same gate cannot be corrected through the ordinary update. Validate the target before committing the administrative edit.

todo.status === "open" && todo.claimed_by == null && todo.unblocks_todo_id == null &&
input.actor_agent_id !== null && todo.bound_agent === input.actor_agent_id &&
todo.blocks_agent === input.actor_agent_id &&
typeof intent.unblocks_todo_id === "string" && intent.unblocks_todo_id.length > 0 &&

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] 请在已有 provider snapshot 中验证目标,再接受首次链接。两个实际 CLI/provider 夹具都把语法合法但不存在的 Todo ID 持久化为 applied。随后普通更新无法改成真实目标(已有 link 后重新进入租约门槛),实际依赖仍 blocked。无效目标应在 preview/write 阶段无写入拒绝,之后同一 gate 仍能链接到有效、范围匹配的 agent Todo。

Signed-off-by: xiaobaifly7 <196511160+xiaobaifly7@users.noreply.github.com>
@xiaobaifly7

Copy link
Copy Markdown
Contributor Author

Addressed the latest P2 in todoUpdateAdmissionRejection. Before granting the
first-link administrative exception, the existing provider snapshot must contain
the target as an Agent Todo in the same Goal, claimed by the gate's bound/blocked
agent. Otherwise preview and write return user_gate_link_target_invalid without
committing state or consuming the operation ID. Existing lease, actor and CAS
fences remain in place.

tests/control_plane/test_user_gate_resume_cli.py::test_gate_first_link_rejects_invalid_target_without_consuming_link
covers File and SQLite, preview and write, and missing, User, other-Goal,
other-lane and unclaimed targets. It verifies unchanged provider readback and
Markdown, then successfully previews, applies and replays the corrected link on
the same gate and operation ID while leaving approval/scope pending. All 20
cases failed before the fix and pass after it on native Windows. The existing
valid-link/completion/resume cases also pass for both providers; the TS fixture
now contains a real same-lane target, and all 96 focused TS tests, typecheck and
Ruff pass.

Validation used source-checkout CLI and disposable real File/SQLite stores. No
installed runtime, real Harness or remote CI qualification is claimed.

@loopx-agent loopx-agent left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewer: model_agent · gpt-6.1-sol · OpenAI · runtime_reported · reasoning_effort=max

复审提交:fb1f467107eaff496a4abd07b4dcb3641e62f34b。
[P2] 首次链接仍可指向已完成 Todo。 File 和 SQLite 都在预览返回 planned、正式写入返回 applied;改回实际依赖被拒绝。随后完成 Gate 返回 target_not_active,真正等待的工作仍 blocked,所需决策 scope 仍在。 首链路资格

动机 / Motivation

先创建用户决策 Gate、后创建等待该决定的工作的 Agent,以及随后提交决定的用户。 修订后,绑定 Agent 可以把先建的 Gate 连到真正等待的工作;不存在的目标会被拒绝。如果选中同一 Agent 已经完成的 Todo,当前仍会耗掉唯一的首次链接。 两种存储都能在正确链接时恢复工作,也都复现了链接已完成目标后 Gate 已关闭、真实依赖仍卡住。 独立验证源码 CLI、隔离的 File 和 SQLite 以及 TypeScript 测试;未验证安装态或其它 provider,也未查询远端 CI。

首次链接需要排除完成或已归档目标,避免完成 Gate 后仍要重新发起决策。 Todo 关系合同 要求链接被该 Gate 阻塞的具体工作;依赖完成条件并不能代替用户决定。 核验项:first_link_keeps_owner_and_provider_cas, first_link_targets_a_resumable_dependency。

改动思路 / Approach

首次链接和 CAS 留在现有 Todo 更新入口,使用同一快照;既有完成流程仍拥有用户决定和范围消除。 保留既有 actor、lease 和 provider revision 条件,只让首次链接接受确实能接续决定的工作。 保留现有的窄行政例外及 provider CAS。只查角色和归属,仍会把用户决定接到无法处理它的工作;同一个目标应在预览与提交时受到同样约束。

具体改动 / Changes

  • 首次链接判断:旧提交 3d81ac5 在 File、SQLite 的预览和写入四例都把缺失目标当成功;当前四例拒绝,能用同 operation ID 连到有效目标。 保留角色、绑定和租约限制。
  • 用户决定后续流程:已完成目标返回 target_not_active,实际依赖的 scope 仍在。正确链接后,两种 provider 都会消费对应 scope,状态变为 open,保持原 agent 归属;原 CLI 正向测试可继续认领。
  • 真实 CLI 恢复对照:精确提交 Python 80 项、TypeScript 74 项通过;基点和候选各 34 项原路径通过。旧提交四条缺失目标反例均失败。独立四组对照重现两次终态误接受。 建议把已完成目标加入预览、写入与重试用例。

对主干的风险 / Risks

绑定 Agent 的一次选错会占用唯一的免租约编辑;用户可以完成 Gate,但后续工作仍需重新处理同一个决策。 File 和 SQLite 得到同样结果。原 user-gate lane 与 completion followthrough 用例,在 PR 合并基点和候选各 34 项通过。 Ruff、签署、差异、RFC 和合入模拟通过。需要修复的行为限于新的首次链接资格。

我的整体评价 / Verdict

请求修改。Gate 能完成但实际依赖仍被阻塞,说明这条首次链接还不能保证用户的决定继续产生作用。 在同一 provider-CAS 写入前拒绝已完成或归档的目标,复用后续恢复对目标的生命周期判断。拒绝应保留原链接和操作号的重试机会。 在两个后端确认正常 Gate 完成后实际工作恢复,再复审。

English verdict: REQUEST_CHANGES. Head fb1f467. A completed same-lane Todo passes first-link admission on File and SQLite. The owner can complete the gate, but the real dependent stays blocked. Reject unusable targets before CAS and preserve same-operation retry.

if (lease !== undefined) canonicalTaskLease(lease, input.goal_id, input.todo_id);
if (boundUserGateLink) {
const target = indexCoordinationProjection(head, input.goal_id).todos.get(intent.unblocks_todo_id as string);
if (target?.role !== "agent" || target.claimed_by !== todo.blocks_agent) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] File 和 SQLite 都在预览返回 planned、正式写入返回 applied;改回实际依赖被拒绝。随后完成 Gate 返回 target_not_active,真正等待的工作仍 blocked,所需决策 scope 仍在。 在同一 provider-CAS 写入前拒绝已完成或归档的目标,复用后续恢复对目标的生命周期判断。拒绝应保留原链接和操作号的重试机会。 File/SQLite 先正常完成旧 Todo,预览和写入应拒绝该 ID 且不写;用同一操作号连到真正等待项,完成 Gate 后应清空 scope 并能继续。

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants