Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
7683373
feat(control-plane): protect first delivery result and direction stages
xyx2002OvO Oct 5, 2026
04cb722
test(control-plane): qualify staged delivery on File and SQLite
xyx2002OvO Oct 5, 2026
53bf736
docs(protocol): document first-delivery opt-in and recovery boundaries
xyx2002OvO Oct 5, 2026
b8efd1d
refactor(turn): separate direction Host IO and qualify bounded recovery
xyx2002OvO Oct 5, 2026
59b1585
docs(protocol): clarify exact-candidate Git recovery
xyx2002OvO Oct 5, 2026
43fd546
fix(runtime): preserve typed startup errors for directory locators
xyx2002OvO Oct 5, 2026
74a1f5b
test: repair delivery fixtures and portable contract checks
xyx2002OvO Oct 5, 2026
17dc9a8
refactor(control-plane): clear quota and Lark maintainability findings
xyx2002OvO Oct 5, 2026
70bf63f
fix(checkpoint): isolate unavailable historical observations
xyx2002OvO Oct 6, 2026
14ae106
fix(build): normalize Turn contract source newlines
xyx2002OvO Oct 6, 2026
ef285a9
fix(host): preserve controller guards for staged delivery
xyx2002OvO Oct 6, 2026
8743d79
fix(codex): preserve implementation binding on direction failure
xyx2002OvO Oct 6, 2026
5088408
test(dashboard): preserve authoritative packaged fixture readback
xyx2002OvO Oct 6, 2026
4bceb41
fix(todos): recover ordinary completion before optional enrollment er…
xyx2002OvO Oct 6, 2026
ba69d4e
docs: clarify recovery of unreadable optional enrollment
xyx2002OvO Oct 6, 2026
a10fe87
refactor(delivery): scope PR1 to caller-authored CLI and MCP recovery
xyx2002OvO Oct 6, 2026
8cbccc0
docs(delivery): define PR1 prerequisite and deferred terminal boundaries
xyx2002OvO Oct 6, 2026
8979f57
refactor(checkpoint): remove deferred terminal-only payload
xyx2002OvO Oct 6, 2026
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
14 changes: 14 additions & 0 deletions docs/architecture/rfcs/loopx-overall-roadmap-v0.md
Original file line number Diff line number Diff line change
Expand Up @@ -956,6 +956,20 @@ independent admission without weakening task-level validation. See the
[acceptance contract](../../reference/goal-acceptance-observations.md#owner-authorized-contract-v0).
This narrows a local recovery gap, not the full R1/R2 coordination acceptance.

**R1 first delivery checkpoint.** The opt-in local-registry File/SQLite
CLI/MCP prerequisite binds ordinary Todo result commits and subsequent
caller-authored direction decisions to separate read bases. Existing typed
owners retain original-Turn receipt recovery, same-head checks and provider CAS.
Relevant dependencies, frontier membership, acceptance and source identity are
checked at the owning commit. See the [operating protocol](../../reference/protocols/goal-vision-replan-contract-v0.md#opt-in-first-delivery-freshness).
Unindexed artifacts remain unknown and require original-identity reconciliation.
Protected no-followup is rejected before new effects; ordinary closeout remains
unchanged. Default owning-Agent continuation, committed-result repair and
protected terminal redirection are subsequent delivery; an optional independent
reviewer requires a separately registered contract. This prerequisite does not
close the frontend/Agent journey, distributed atomicity, independent Goal
acceptance or model-outcome acceptance.

**R1 transaction checkpoint.** Team-plan admission and whole-batch planning now live in `work_items/team_plan.ts`. Confirmation assigns all admitted lanes in one write with a durable operation receipt; identity is proposal + lane, never Todo text. File/SQLite authority uses the existing CAS and receipt owner; legacy Markdown writes the records and immutable receipt together under its existing fence and lock. Exact replay reads historical results even after a receiver changes, completes or deletes work. A precommit failure creates no lane prefix, and pending canonical display delivery requires recovery before Chat reports verified success. The card names partial assignments and gaps; quota/stop remain advisory and an explicit enforcement claim is rejected. Agent-originated settlement binds the same state basis at its journal's first write and re-reads it at settlement; a plan whose basis is missing or moved, or whose every lane is a gap, is a typed failed receipt that creates no Todo and replays unchanged.

This closes the local assignment/retry portion of F4, not R1's collaboration acceptance. Registered receivers are assigned without being impersonated as authors; agent-originated settlement cannot assign another peer without owner confirmation. Assignment does not attest receiver adoption, a lease, execution, dependency consumption or independent acceptance. Do not add a second confirmation to ordinary already-authorized work. Gap resolution requires new explicit intent; replay must not silently extend the confirmed subset. The fingerprint binds current local state and canonical revision, not a full shared Goal-intent transaction. R2/R3/R4 still own executor qualification, receiver adoption/result return and shared intent/authorization; the cross-host Turn lease is not a plan barrier.
Expand Down
134 changes: 133 additions & 1 deletion docs/reference/protocols/goal-vision-replan-contract-v0.md
Original file line number Diff line number Diff line change
Expand Up @@ -406,11 +406,143 @@ must satisfy the checkpoint before terminal closeout; it neither re-authors the
outcome nor spends a second time. Never invent an unchanged reason to clear a gap.
Typed in-flight continuations keep their existing exemption.

### Opt-in first delivery freshness

First delivery protection extends the built-in Todo, checkpoint and Turn owners;
it introduces no new provider or actor authority. It is off by default. Supported
profiles are local-registry Goals with File or SQLite canonical Todo authority
and an admitted, named Turn. Source-session GoalRef admission, other providers,
managed execution, protected no-followup closeout and compound repair/replan
effects are outside this protocol stage. Existing non-opted-in behavior and checkpoint-only
recovery retain their separate admission rules.

The stages are independent: prepare and validate the candidate, commit the
allowed result operation, read the resulting state, judge the direction outside
source/provider locks, commit that direction, then finish ordinary settlement.
This stage is a CLI/MCP backend prerequisite. Default owning-Agent continuation
and its product journey remain a subsequent stage; an independent reviewer
requires its own registered input, result and recovery contract.
Neither Todo completion nor a satisfied direction checkpoint certifies Goal
completion.

After the ordinary quota guard admits the Turn, direct CLI callers read a result
basis before preparing the candidate:

```sh
loopx --format json checkpoint-context --goal-id example --agent-id agent-a \
--todo-id todo_page --turn-instance-id turn-1 --purpose delivery_result
```

Validate against that basis. Pass its `read_context_id` to `todo complete` as
`--delivery-read-context`, retaining the Goal, Agent, Todo, Turn and original
candidate/validation options. The Todo owner checks historical receipts first,
compares relevant facts against its current authority head, and commits an exact
delta and receipt with revision CAS. Unrelated revisions may retry without
another model decision. A related change requires rechecking the candidate.

After the allowed result commit, read the direction:

```sh
loopx --format json checkpoint-context --goal-id example --agent-id agent-a \
--todo-id todo_page --turn-instance-id turn-1 --purpose first_delivery \
--decision-scope goal
```

Read the returned basis and produce a new Vision or unchanged reason. Submit it
using the existing delivery fields plus `refresh-state --first-delivery
--checkpoint-read-context ID --progress-scope goal`. Both `agent_lane` and `goal`
scopes bind complete membership and dependency closure. Reading more work grants no authority to write another Agent's work.
Final submission protects registry, Goal state, index and the real provider
through append. It rejects `--next-action` and Codex session usage booking as
compound effects; explicit usage observations can accompany the run. Lock order:
index, registry, maintenance, Todo projection, state, then provider. Model calls
and validation commands stay outside this section.

MCP callers use `complete_task(first_delivery=true)`. The v2 protocol first
returns `result_review_pending` with a result context. Validate, then call again
with `delivery_read_context_id`; it commits the ordinary completion and returns
`direction_pending`. Judge that context and call again with both
`delivery_read_context_id` and `read_context_id`, plus the new Vision/reason.
`review_task_vision(first_delivery=true)` can reread a rejected direction; it
never attaches a new token automatically to an old Vision. Protected
`no_follow_up=true` is rejected before any effects are admitted. Native protected
no-followup also rejects before a new commit, after historical receipt recovery.
Ordinary v0/v1 closeout and its refresh/spend/terminal order remain unchanged.
A future protected terminal stage must provide the legal transition for a changed
frontier after checkpoint or spend; retrying a refused terminal intent is not
that transition.

Status and quota readbacks expose `first_delivery_progress`: result and
direction commits, quota spend, pending stage and recovery action. Shared and Agent-scoped
status mirror recovery text into the existing next-action field used by the
dashboard and channel projections. These receipt observations are neither new
workflow authority nor a Goal completion signal.

Malformed or unreadable historical receipts are reported as scoped
`observation_errors`, never as proof that no recovery is pending. If the damaged
receipt cannot be attributed to a Turn, `observation_unavailable` retains the
Goal-scoped warning while leaving unrelated lanes' next actions intact. An
enrolled Turn whose original readback is uncertain stays `operation_unknown`;
recovery writes remain blocked until the original artifacts can be reconciled.
Observation reuses one complete canonical Todo snapshot across the discovered
identities; it does not truncate older pending work.

Ordinary Todo completion recovers an exact terminal receipt before checking an
unreadable optional enrollment source, without changing its request fingerprint.
For a new completion, the original Turn's verified ordinary writeback can prove
that a damaged shared supplement is optional. Missing or inconsistent history,
an enrolled writeback, or any result enrollment receipt remains
`checkpoint_commit_unknown`; unreadable JSON alone never proves non-enrollment.

After a lost response, retry the original request. `checkpoint-context` for its
first-delivery identity verifies an indexed direction and artifacts before
returning `committed`. A proved empty append can retry after freshness validation.
JSON/Markdown without a complete consistent index remains
`checkpoint_commit_unknown`: preserve the original identity and artifacts for
operator reconciliation; another Turn cannot bypass that unresolved append.
The files, provider, quota and Git are not one transaction.

To disable, omit the opt-in for **new** Turns. Reconcile enrolled pending Turns
with their original identity and a compatible runtime before downgrading. Preserve
receipts and successful artifacts. Existing integration receipts, exact candidate
SHA validation and ref CAS still own code publication. This adds no cross-host,
PostgreSQL or model-quality guarantee.

### 中文:本阶段边界与恢复

PR1 仅提供显式选择的本地 File/SQLite CLI/MCP 协议与后端前置路径。
先读取结果依据并验证候选,由 Todo owner 检查原回执、当前依据及 CAS;
结果提交后再读取方向依据,由调用方提交当前 Vision 或 unchanged reason,
最后完成原 Turn 的 refresh/spend。共享机制不改变各 owner 的失败策略,
也不改变 Post-Writeback optional hook 的 isolate 与 quota 结算定义。

相关依据变化时,未提交结果必须重新验证候选;结果已经提交时只重新读取和
判断方向。原 Turn、已提交结果和回执保持原身份,不能把旧判断贴上新 token。
索引不完整或关键提交未知时保留原 artifacts,先协调原回执,不能换 Turn 绕过。
明确的 `required=false/satisfied=true/decision=not_required` 沿用既有 checkpoint
豁免;缺少 checkpoint 记录不能被观察器猜成无需判断。

本阶段不接纳带保护的 no-followup;在新副作用前拒绝,历史精确回执仍优先恢复。
普通旧路径及 refresh→spend→terminal 顺序保留。默认 owning Agent 的原会话续接、
已提交结果要求修复及 protected terminal 改向出口由后续阶段闭合;独立 reviewer
另行登记完整契约。本阶段没有新模型调用、方向尝试预算或通用回滚协议。
status/quota 的回执投影可供既有前端及 Lark 读取,但不代表默认产品旅程已交付,
也不代表 Goal 已完成或模型效果已提升。

Code publication remains a separate operation. Qualify ref CAS against an
integration target that is not checked out; this protocol does not wrap direct
Git writes or promise concurrent working-directory isolation. If publication
responds ambiguously, read the integration status and exact current SHA, verify
that combined candidate, then use the existing `integration-branch sync
--candidate-ref SHA --execute` recovery. This can adopt the already published
candidate without moving the branch again. A clean merge is not task acceptance:
run the task verifier on that combined SHA before confirming its result.

### Read basis for checkpoint-only recovery

Missing-checkpoint supplementation now requires an explicit read receipt. This is
a default admission change for both legacy and newly committed Turn writebacks;
normal first writebacks and non-Turn vision authoring retain their existing rules.
non-opted-in first writebacks and non-Turn vision authoring retain their existing rules.
From the original working directory and with the original registry/runtime/project/
state-file options, read the basis for the exact settlement:

Expand Down
9 changes: 9 additions & 0 deletions loopx/cli_commands/project_lifecycle_refresh_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,11 @@ def register_refresh_state_command(
binding.add_argument("--todo-id")
binding.add_argument("--replan-obligation-id")
context_parser.add_argument("--goal-instance-id", help=argparse.SUPPRESS)
context_parser.add_argument("--purpose", choices=("supplement_checkpoint", "first_delivery", "delivery_result"),
default="supplement_checkpoint",
help="Explicitly enroll this original Turn in protected first delivery, or repair its missing checkpoint.")
context_parser.add_argument("--decision-scope", choices=("agent_lane", "goal"), default="agent_lane",
help="Complete work collection used by the first direction judgment; reading grants no write authority.")
context_parser.add_argument("--project")
context_parser.add_argument("--state-file")
context_parser.add_argument("--dependency-todo-id", action="append", default=[],
Expand All @@ -105,6 +110,8 @@ def register_refresh_state_command(
"refresh-state",
help="Append a read-only run from active goal state after state-only updates.",
)
refresh_state_parser.add_argument("--first-delivery", action="store_true",
help="Commit an explicitly enrolled first direction using its fresh checkpoint-context receipt.")
add_subcommand_format(refresh_state_parser)
refresh_state_parser.add_argument(
"--goal-id",
Expand Down Expand Up @@ -447,6 +454,7 @@ def handle_refresh_state_command(
project=Path(args.project).expanduser() if args.project else None,
state_file=Path(args.state_file).expanduser() if args.state_file else None,
dependency_todo_ids=args.dependency_todo_id,
purpose=args.purpose, decision_scope=args.decision_scope,
goal_ref=goal_ref,
)
except Exception as exc:
Expand Down Expand Up @@ -564,6 +572,7 @@ def handle_refresh_state_command(
merge_agent_vision_patch=merge_agent_vision_patch,
vision_unchanged_reason=args.vision_unchanged_reason,
checkpoint_read_context_id=getattr(args, "checkpoint_read_context", None),
first_delivery=bool(getattr(args, "first_delivery", False)),
progress_observation=progress_observation,
usage_measurement=usage_measurement,
usage_codex_session=(
Expand Down
87 changes: 51 additions & 36 deletions loopx/cli_commands/quota.py
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,47 @@ def _emit_quota_result(
return 0 if payload.get("ok") else 1


def _read_back_committed_heartbeat(
payload: dict[str, object],
args: argparse.Namespace,
*,
runtime_root: Path,
turn_instance_id: str,
stall_observation: str,
requested_todo_id: str | None,
goal_ref: dict[str, str] | None,
) -> None:
"""Expose selection only after the original heartbeat receipt is read back."""
receipt = find_heartbeat_receipt(
runtime_root,
goal_id=args.goal_id,
agent_id=args.agent_id,
turn_instance_id=turn_instance_id,
goal_ref=goal_ref,
)
if receipt:
rollout_event_value = payload.get("rollout_event")
rollout_event: Mapping[str, object] = (
rollout_event_value if isinstance(rollout_event_value, Mapping) else {}
)
payload["heartbeat_receipt"] = heartbeat_receipt_view(
receipt,
turn_instance_id=turn_instance_id,
status="committed" if rollout_event.get("appended") else "replayed",
)
commit_requested_action_selection(payload, requested_todo_id=requested_todo_id)
else:
fail_heartbeat_receipt(
payload,
turn_instance_id=turn_instance_id,
stall_observation=stall_observation,
reason=(
"heartbeat receipt append could not be read back; retry "
"quota should-run with the same --turn-instance-id"
),
)


def handle_quota_command(
args: argparse.Namespace,
*,
Expand Down Expand Up @@ -839,45 +880,19 @@ def handle_quota_command(
*(["goal_ref"] if goal_ref is not None else []),
],
)
receipt = find_heartbeat_receipt(
runtime_root,
goal_id=args.goal_id,
agent_id=args.agent_id,
_read_back_committed_heartbeat(
payload,
args,
runtime_root=runtime_root,
turn_instance_id=heartbeat_turn_id,
stall_observation=heartbeat_stall_observation,
requested_todo_id=(
action_selection.requested_todo_id
if action_selection is not None
else None
),
goal_ref=goal_ref,
)
if receipt:
rollout_event_value = payload.get("rollout_event")
rollout_event: Mapping[str, object] = (
rollout_event_value
if isinstance(rollout_event_value, Mapping)
else {}
)
payload["heartbeat_receipt"] = heartbeat_receipt_view(
receipt,
turn_instance_id=heartbeat_turn_id,
status="committed"
if rollout_event.get("appended")
else "replayed",
)
commit_requested_action_selection(
payload,
requested_todo_id=(
action_selection.requested_todo_id
if action_selection is not None
else None
),
)
else:
fail_heartbeat_receipt(
payload,
turn_instance_id=heartbeat_turn_id,
stall_observation=heartbeat_stall_observation,
reason=(
"heartbeat receipt append could not be read back; retry "
"quota should-run with the same --turn-instance-id"
),
)
else:
append_cli_rollout_event(
payload,
Expand Down
13 changes: 13 additions & 0 deletions loopx/cli_commands/status.py
Original file line number Diff line number Diff line change
Expand Up @@ -735,6 +735,19 @@ def attach_agent_lane_next_actions(
guard=guard,
)
latest_action = guard.get("latest_run_recommended_action")
delivery_progress = guard.get("first_delivery_progress")
if isinstance(delivery_progress, dict):
item["first_delivery_progress"] = delivery_progress
if (
isinstance(delivery_progress, dict)
and guard.get("recommended_action") == delivery_progress["next_action"]
):
item["recommended_action"] = delivery_progress["next_action"]
if isinstance(project_asset, dict):
project_asset["next_action"] = delivery_progress["next_action"]
goal_channel = item.get("goal_channel_projection")
if isinstance(goal_channel, dict):
goal_channel["next_action"] = delivery_progress["next_action"]
for target in (item, project_asset):
if not isinstance(target, dict):
continue
Expand Down
2 changes: 2 additions & 0 deletions loopx/cli_commands/todo.py
Original file line number Diff line number Diff line change
Expand Up @@ -631,6 +631,8 @@ def handle_todo_command(
evidence=args.evidence,
completion_result_file=Path(args.result_file).expanduser() if args.result_file else None,
completion_turn_key=completion_turn_key,
delivery_read_context_id=getattr(args, "delivery_read_context", None),
delivery_settlement_identity=settlement_identity.as_dict() if settlement_identity else None,
completion_identity_source=completion_identity_source,
completion_delivery_workspace=completion_delivery_workspace,
completion_validation_workspace_path=Path.cwd(),
Expand Down
Loading
Loading