Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 51 additions & 7 deletions docs/reference/handoff-mode.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,32 @@ A read-only quota guard never claims or renews. Long-running delegated Hosts ret
their existing supervision; this CLI path does not supervise an idle external
process. The App's manual completion boundary described above is unchanged.

Goal-mode MCP uses the same task reference: retain `claim_operation_id` from
`claim_task` and pass it to `complete_task`, including retries. This is a breaking
tool-schema change: `complete_task` no longer accepts
`task_lease_idempotency_key` or `task_lease_expected_version`. Migrate callers
to the returned claim reference; do not synthesize one from an old lease key.
Ordinary CLI help also omits those host-only proof flags. Explicit Host supervision
and diagnostic lease commands retain their generation-fencing protocol.

For an eligible task edit without explicit proof, recovery now offers ordinary
`todo claim`, then retry with its returned reference. It does not direct the Agent
through inspect/acquire/release. Expired or replaced references remain rejected;
recovery advice itself never grants execution or changes state.

An expired hard-lease execution can be reclaimed with ordinary `todo claim`
by an eligible registered Agent. When the expired holder still matches the Todo
claim, the claim owner and a fresh execution generation change in one canonical
transaction. No response, release or transfer grant from the old process is
required. This intentionally changes the previous cross-agent expiry behavior.
Active executions remain protected. Missing, malformed, released or unrelated
lease history cannot authorize this takeover; `soft_claim` keeps its prior
handoff rules. Current task status, exclusions, acceptance, dependencies and
write-scope checks remain in force. Concurrent claimants cannot both win the same
provider revision. Old references cannot update, renew or release the replacement.
Expiry prevents further control-plane writes; it cannot undo external effects
already issued, and native Hosts still own process supervision.

## Choose the policy independently of storage

Use `soft_claim` for an explicitly single executor without parallel takeover.
Expand Down Expand Up @@ -252,9 +278,11 @@ under a fresh operation and current revision. Other execution fences remain:
| Observation | Recovery |
| --- | --- |
| Historical `legacy` policy, with or without a retained lease | Release any retained execution using its exact CAS, then review and back up a migration to `soft_claim` or `hard_lease`. Historical acceptance grants no current execution. |
| Active lease held by the current claim owner; missing/stale proof | Inspect the lease and retry using its current proof. Do not acquire a competing execution. |
| Released/expired lease; current owner is eligible and acquisition passes the current mode, acceptance and scope checks | Inspect the version, acquire a short lease with a fresh key, update using the returned proof, then release it. |
| Active foreign holder, divergent claim, or absent claim | Reconcile ownership through its lifecycle; never borrow another holder's proof. |
| Active execution held by the current claim owner; proof omitted | Use ordinary `todo claim` and retry with its returned reference; claim reuses eligible active execution. |
| Released/expired execution; proof omitted and current owner passes acquisition checks | Use ordinary `todo claim` for fresh execution, then retry with its returned reference. |
| Explicit Host proof is stale or partial | Inspect current execution and repair the Host's proof; do not silently fall back to a task reference. |
| Expired execution matches the retained claim; eligible registered replacement actor | Use ordinary `todo claim` for atomic takeover, then use its new reference. |
| Active foreign holder, divergent history, or absent claim | Reconcile ownership through its lifecycle; never borrow another holder's proof. |
| `soft_claim` with released same-owner history, ordinary owner copy edit | Omit lease proof; keep the history. No replacement lease is needed or allowed. |
| Non-open Todo, acceptance hold or conflicting write scopes | Resolve the reported acquisition blocker. No acquire action is offered. |
| Edit changes retained leased work requirements or status | Use the owning lifecycle transition; acquiring another lease cannot authorize the metadata edit. |
Expand All @@ -275,7 +303,8 @@ todo_done:<dependency-todo> --reason '<bounded cause>'` 暂停。此显式 wait
或附带执行 proof 仍被拒绝。用 `--status open --clear-resume-when --reason
'<resume basis>'` 恢复;两次转换都不授予执行权限,下一次执行仍需新 lease。

The recovery descriptor uses the standalone `loopx task-lease acquire` command.
For explicit Host-proof callers, the recovery descriptor uses the standalone
`loopx task-lease acquire` command when acquisition is eligible.
Combined `todo claim --task-lease-idempotency-key` is restricted to `hard_lease`
and is unavailable in historical `legacy` mode. After reviewed migration,
acquire uses `--owner`, a **fresh**
Expand Down Expand Up @@ -306,9 +335,10 @@ lease 即使自然过期,也不适用。显式旧 proof 或部分 proof 仍拒
`handoff_mode_requires_lease`
不表示 Goal 已自动切成 `hard_lease`;拒绝结果会返回真实 mode 与只读 `recovery`。

有效的本主租约应 inspect 后使用当前 proof 重试;已释放或过期的租约,只有当前
claim owner 满足相同的 acquire 准入规则时,才提示“inspect version → 用新 key
申请短 lease → 带新 proof 更新 → release”。legacy 下独立 acquire 与合并式
未提供显式 proof 的普通任务调用,只有当前 owner 满足执行准入时,才提示普通
`todo claim`,再用返回的 `claim_operation_id` 重试。有效执行复用,已结束执行须
重新通过准入;旧引用不能复活过期或换代执行。显式 Host proof 过期或不完整时,
仍须 inspect 并修复 Host 的执行证明,不静默切换协议。legacy 下独立 acquire 与合并式
claim+lease 均不可执行,必须先审核迁移;合并式 claim+lease 仍仅限 hard_lease。

异主有效 lease、claim 不一致、不允许执行的 Todo、验收阻塞或 scope
Expand All @@ -330,3 +360,17 @@ CLI 由明确的 `--agent-id` 认领给自己时,会在同一 canonical 事务
不自动借用或重新取得证明;显式证明不能与引用混用。原操作回执先于维护恢复,重试不
重复写入或续租。只读 quota guard 不做认领或续租;长期委派 Host 仍走已有监督,
本 CLI 路径不监督空闲外部进程,也不改变 App 手工完成的现有边界。

Goal-mode MCP 同样传递任务引用:保存 `claim_task` 返回的 `claim_operation_id`,
在 `complete_task` 及其重试中原样使用。本次为 breaking change,`complete_task`
移除 `task_lease_idempotency_key` 和 `task_lease_expected_version` 参数;调用方须
迁移到认领返回的引用,不能从旧 key 拼造。普通 CLI 帮助也不再展示这两个 Host
证明参数;显式 Host 监督和诊断命令保留换代隔离协议。恢复提示不授予执行权或写入状态。

过期的 hard-lease 执行可由符合当前条件的注册 Agent 用普通 `todo claim` 接管。
只有过期持有者仍与 Todo claim 一致时,才在同一 canonical 事务中变更认领人并取得
新一代执行,无需旧进程回复、释放或提供交接证明。本次明确改变原来的跨 Agent
过期恢复行为。有效执行不可抢占;缺失、损坏、已释放或不对应的历史记录不能授予
此接管,`soft_claim` 保持原交接规则。仍检查任务状态、排除规则、验收、依赖和写范围
冲突;同一 revision 的并发竞争只允许一个成功,旧引用不能写入、续租或释放新执行。
过期会阻止后续控制面写入,但无法撤回此前已发出的外部副作用;进程监督仍由 Host 负责。
2 changes: 1 addition & 1 deletion examples/explore-result-layer-smoke.py
Original file line number Diff line number Diff line change
Expand Up @@ -791,7 +791,7 @@ def check_todo_branch_prediction_contract() -> None:
rejected = {item["todo_id"]: item for item in plan["rejected_candidates"]}
assert rejected["todo_conflict"]["selection_status"] == "rejected_hazard", plan
assert rejected["todo_other"]["selection_status"] == "blocked_claimed_by_other", plan
assert any("task-lease acquire" in command for command in plan["selected_branches"][0]["suggested_commands"]), plan
assert not any("task-lease" in command for command in plan["selected_branches"][0]["suggested_commands"]), plan

worker_plan = build_explore_worker_branch_plan(
goal_id=goal_id,
Expand Down
4 changes: 2 additions & 2 deletions examples/explore-worker-plan-gate-smoke.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,10 +165,10 @@ def check_commands_and_width_cap() -> None:
assert plan["selected_worker_branch_count"] <= 3, plan
first = plan["selected_worker_branches"][0]
assert any("loopx todo claim" in command for command in first["suggested_commands"]), first
assert any("task-lease acquire" in command for command in first["suggested_commands"]), first
assert not any("task-lease" in command for command in first["suggested_commands"]), first
# Suggested is still only suggested: the packet remains dry-run.
assert plan["dry_run"] is True, plan
assert plan["harness_compatibility"]["claim_and_lease_are_suggested_only"] is True, plan
assert plan["harness_compatibility"]["claim_is_suggested_only"] is True, plan
_assert_read_only_boundary(plan)

wide = _plan(
Expand Down
8 changes: 4 additions & 4 deletions loopx/capabilities/explore/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -555,8 +555,8 @@ packet with:
enter the exploration scheduler or consume branch width;
- a dry-run A/B estimate comparing baseline serial execution with the
DSpark-style selected prefix (`ab_result.estimated_speedup_vs_baseline`);
- suggested `loopx todo claim` and `loopx task-lease acquire` commands for a
human operator or registered peer runner to execute explicitly;
- suggested ordinary `loopx todo claim` commands for a human operator or
registered peer runner; the claim manages execution and returns its task reference;
- the safety boundary that keeps the packet advisory rather than an
replacement for `quota should-run`.

Expand Down Expand Up @@ -682,12 +682,12 @@ the existing LoopX harness, not beside it and not instead of it:
2. The opt-in planner groups todos into worker-lane candidates and uses
DSpark-style confidence/prefix/load scoring to pick a worker branch prefix.
3. Execution must return to the normal LoopX path: `quota should-run`,
`todo claim`, `task-lease acquire`, worker execution, `explore node|edge|finding`,
`todo claim`, worker execution, `explore node|edge|finding`,
`refresh-state`, and `quota spend-slot`.

For goals that have opted in, the packet therefore contains
`harness_compatibility` and `boundary` fields: `replaces_loopx_runtime=false`,
`launches_workers=false`, and `claim_and_lease_are_suggested_only=true`; the
`launches_workers=false`, and `claim_is_suggested_only=true`; the
deny-by-default disabled packet carries the `boundary` block plus the opt-in
`required_contract` instead. The packet can be used by a controller or human
operator to decide which workers to start, but it cannot launch workers or
Expand Down
6 changes: 3 additions & 3 deletions loopx/capabilities/explore/README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ Todo 关联保留既有可恢复边界与 claim/lease 证明;恢复旧批次
- 选中的分支、置信度、hazards 与 reason codes;
- 被排除的 `continuous_monitor` 诊断——仍可见,但绝不进入探索调度器或消耗分支宽度;
- 基线串行执行与 DSpark 风格选中前缀的 dry-run A/B 估计(`ab_result.estimated_speedup_vs_baseline`);
- 供 operator 或注册 peer runner 显式执行的 `loopx todo claim` / `loopx task-lease acquire` 建议;
- 供 operator 或注册 peer runner 执行的普通 `loopx todo claim` 建议;认领管理执行并返回任务引用;
- 保持 packet 咨询性、而非替代 `quota should-run` 的安全边界。

advancement todo 可通过挂一个或多个显式 Explore node id 选择 typed 结果诊断:
Expand Down Expand Up @@ -326,9 +326,9 @@ loopx explore worker-branch-plan --goal-id <id> --worker-width 5 \

1. LoopX 提供 harness 输入:quota/status 上下文、open agent todo 投影、explore result 投影、所有权、能力、写作用域元数据。
2. opt-in planner 把 todos 分组为 worker-lane 候选,用 DSpark 风格 confidence/prefix/load 打分选择 worker branch 前缀。
3. 执行必须回到正常 LoopX 路径:`quota should-run`、`todo claim`、`task-lease acquire`、worker 执行、`explore node|edge|finding`、`refresh-state`、`quota spend-slot`。
3. 执行必须回到正常 LoopX 路径:`quota should-run`、`todo claim`、worker 执行、`explore node|edge|finding`、`refresh-state`、`quota spend-slot`。

因此 packet 对已 opt-in 的 goal 包含 `harness_compatibility` 与 `boundary` 字段:`replaces_loopx_runtime=false`、`launches_workers=false`、`claim_and_lease_are_suggested_only=true`;deny-by-default 的 disabled packet 携带 `boundary` 块和 opt-in `required_contract`。packet 可被控制器或 operator 用来决定启动哪些 worker,但不能自行 launch worker 或变更控制面。
因此 packet 对已 opt-in 的 goal 包含 `harness_compatibility` 与 `boundary` 字段:`replaces_loopx_runtime=false`、`launches_workers=false`、`claim_is_suggested_only=true`;deny-by-default 的 disabled packet 携带 `boundary` 块和 opt-in `required_contract`。packet 可被控制器或 operator 用来决定启动哪些 worker,但不能自行 launch worker 或变更控制面。

### 每 Goal 独立 Opt-In 门控

Expand Down
37 changes: 2 additions & 35 deletions loopx/capabilities/explore/todo_branch_plan.py
Original file line number Diff line number Diff line change
Expand Up @@ -239,28 +239,6 @@ def _lane_id(todo_id: str, *, index: int) -> str:
return f"branch_{index}_{todo_id.removeprefix('todo_')}"


def _lease_command(
*,
goal_id: str,
todo_id: str,
agent_id: str | None,
write_scopes: Sequence[str],
) -> str | None:
owner = normalize_todo_claimed_by(agent_id)
if not owner:
return None
parts = [
"loopx task-lease acquire",
f"--goal-id {goal_id}",
f"--todo-id {todo_id}",
f"--owner {owner}",
f"--idempotency-key branch-plan:{goal_id}:{todo_id}",
]
for scope in write_scopes:
parts.append(f"--write-scope {scope}")
return " ".join(parts)


def _claim_command(*, goal_id: str, todo_id: str, agent_id: str | None) -> str | None:
owner = normalize_todo_claimed_by(agent_id)
if not owner:
Expand Down Expand Up @@ -523,6 +501,7 @@ def build_explore_todo_branch_plan(
branch_index = len(selected)
role = "primary" if branch_index == 0 else "speculative"
selected_in_lane = selected_by_resource_lane.get(resource_lane, 0) + 1
claim_command = _claim_command(goal_id=goal_id, todo_id=todo_id, agent_id=normalized_agent)
selected_candidate = {
**candidate,
"selection_status": "selected",
Expand All @@ -533,19 +512,7 @@ def build_explore_todo_branch_plan(
"verify and write back before keeping this branch; discard or convert "
"to successor todo if evidence does not advance the exploration"
),
"suggested_commands": [
command
for command in (
_claim_command(goal_id=goal_id, todo_id=todo_id, agent_id=normalized_agent),
_lease_command(
goal_id=goal_id,
todo_id=todo_id,
agent_id=normalized_agent,
write_scopes=scopes,
),
)
if command
],
"suggested_commands": [claim_command] if claim_command else [],
}
assignment = resource_assignment(
resource_portfolio,
Expand Down
19 changes: 4 additions & 15 deletions loopx/capabilities/explore/worker_branch_plan.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@
_claim_command,
_compact_text,
_frontier_tokens,
_lease_command,
_execution_exclusion,
_required_capabilities,
_required_write_scopes,
Expand Down Expand Up @@ -492,19 +491,9 @@ def _branch_candidate(
commands: list[str] = []
for item in todo_bundle:
todo_id = str(item["todo_id"])
commands.extend(
command
for command in (
_claim_command(goal_id=goal_id, todo_id=todo_id, agent_id=agent_id),
_lease_command(
goal_id=goal_id,
todo_id=todo_id,
agent_id=agent_id,
write_scopes=item.get("required_write_scopes") or [],
),
)
if command
)
command = _claim_command(goal_id=goal_id, todo_id=todo_id, agent_id=agent_id)
if command:
commands.append(command)
aggregate_confidence = _aggregate_confidence(todo_bundle)
candidate = {
"branch_id": branch_id,
Expand Down Expand Up @@ -1177,7 +1166,7 @@ def build_explore_worker_branch_plan(
"replaces_loopx_runtime": False,
"launches_workers": False,
"mutates_quota_or_state": False,
"claim_and_lease_are_suggested_only": True,
"claim_is_suggested_only": True,
"duration_guard_controlled_by_planner": False,
"fixed_worker_count_controlled_by_planner": False,
"forces_full_branch_fill": False,
Expand Down
27 changes: 6 additions & 21 deletions loopx/cli_commands/todo_registration.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ def register_todo_command(
],
default=None,
help=(
"Use add to append a checkbox todo, claim to soft-claim by registered "
"Use add to append a checkbox todo, claim to prepare execution by registered "
"agent id, list to read projected todos, update/complete/supersede to transition by todo_id, or "
"archive-completed to move older completed todos into Completed Work Archive. "
"Use plan with --text and --agent-id for the existing Goal's model planning checkpoint; the caller owns subsequent execution."
Expand Down Expand Up @@ -285,32 +285,17 @@ def register_todo_command(
todo_parser.add_argument(
"--claimed-by",
help=(
"For agent todo add/claim/update, assign the soft execution owner to a "
"For agent todo add/claim/update, assign the task owner to a "
"registered public-safe agent id such as codex-main-control. This names "
"the assignment target, not the lifecycle actor; multi-agent lifecycle "
"commands still require --agent-id. User todos use --bound-agent or "
"--goal-bound instead."
),
)
todo_parser.add_argument(
"--task-lease-idempotency-key",
help=(
"For todo claim on promoted hard-lease authority, atomically acquire "
"the canonical lease and claim. Optional for administrative edits and bounded owner pause/resume; "
"when supplied, this proof and the current version are strictly checked. "
"Complete, supersede and other execution-fenced updates require the active execution proof."
),
)
todo_parser.add_argument(
"--task-lease-expected-version",
type=int,
help=(
"For promoted todo claim, optionally compare-and-set the canonical "
"lease version. For updates with an explicit lease key, supply its current version; "
"partial or stale proof is rejected. Complete, supersede and other execution-fenced "
"updates require the active lease version when it is effective."
),
)
# Host orchestration uses explicit generation fencing. Ordinary task callers
# use claim_operation_id; do not advertise the host transport as task work.
todo_parser.add_argument("--task-lease-idempotency-key", help=argparse.SUPPRESS)
todo_parser.add_argument("--task-lease-expected-version", type=int, help=argparse.SUPPRESS)
todo_parser.add_argument(
"--bound-agent",
help=(
Expand Down
Loading
Loading