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
8 changes: 8 additions & 0 deletions docs/reference/canonical-terminal-review.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,14 @@ a changed route uses `semantic_closeout` with an evidence-linked vision decision
on the same Turn; no fresh guard or binding is needed. Projecting a command does
not execute it or establish acceptance.

Successful Turn-bound ordinary completion returns verified settlement progress
and, while writeback remains due, the existing same-identity settlement plan and
next writeback command. That material-closeout plan includes the shared vision
authoring schema; callers need not reconstruct identities or guess a JSON
format after completion. CLI Markdown shows the verified progress and next
writeback command; JSON carries the full plan and authoring schema. This grants
no new execution or terminal authority.

The shared Todo summary keeps missing-successor diagnostics and counts, but its
warning is review guidance, not an obligation to create a successor or terminate
a completed stage. Review remaining authorized Goal acceptance and the runnable
Expand Down
5 changes: 5 additions & 0 deletions docs/reference/canonical-terminal-review.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,11 @@ Goal、Agent、Todo 和 Turn。只有当前契约允许最终范围收尾时,
`todo_deliverable_complete`,必需的验证不会随条件省略。符合准入的
`in_flight_continuation` 保留未交付 Todo 为 open,不会伪造通过验证的完成。

带原 Turn 身份的普通完成成功后,返回已验证的结算进度;仍待写回时,一并返回
既有同身份结算计划和下一条写回命令。该语义收尾计划包含共享的 vision authoring
格式,调用者无需在完成后重建身份或猜测 JSON 结构。CLI Markdown 展示已验证进度和
下一条写回命令,JSON 保留完整计划与 authoring 格式;它不授予新的执行或终态权限。

共享 Todo 摘要仍保留缺少后继的诊断和计数,但告警是复核引导,不要求制造后继或
终结已完成阶段。先复核剩余的授权 Goal 验收与可执行任务;范围内仍有工作时继续
已有任务或重规划。队列为空本身不能证明最终范围可以收尾。图校验、空前沿重规划
Expand Down
34 changes: 34 additions & 0 deletions docs/reference/protocols/model-behavior-qualification-v0.md
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,40 @@ target. The terminal-settlement scenario requires the model to validate a real
fixture artifact, then follow the quota-projected `durable_writeback ->
quota_spend -> terminal_closeout` sequence under one stable effect identity; a
premature no-follow-up or spend-before-writeback fails.
The plan's conditional ordinary Todo completion may run during validation,
before writeback and spend. The actor requires the original Goal/Agent/Todo/Turn
arguments and its committed validation receipt; ordinary completion does not
qualify terminal closeout. `--no-follow-up` still requires both matching
writeback and spend receipts. Later receipts advance the original Turn's plan;
they do not discard its identity-bound command templates. This follows the shared
[quota-bound completion contract](../canonical-terminal-review.md#quota-bound-cli-completion)
without changing the portfolio, repeats, contrasts or settlement order.
The refresh CLI's pure progress-input decoder identifies invalid argument
combinations before dispatch or authority writes. Its structured rejection is
returned to the same model, which may correct its input within the original
ten-call budget. Rejected calls remain counted and commit no settlement step;
the actor never retries a command or restarts a scenario automatically. Other
non-zero CLI results and fixture identity, route or ordering violations still
fail immediately; non-zero execution alone does not prove rollback.
The shared CLI transport retains the process exit code and transient stdout;
even a non-zero process that emitted valid committed-receipt JSON fails unless
it is that exact pre-dispatch input rejection. Raw stdout is never copied into
durable qualification receipts.
After the proof is read, a terminal actor may author a project draft in
`.scratch/` using the existing OS-isolated shell. Original fixture inputs and
authority stores remain protected; private host data and external networking
are unavailable. LoopX mutations still use one direct, identity-bound CLI call
at a time; the draft shell cannot invoke LoopX. The original ten-call limit and
all committed receipt checks remain. Explicit foreign routes fail rather than
being repaired. After the initial quota guard, completion, writeback and spend
must retain the fixture Turn. The initial guard's host Turn reference is rebound
by the existing CLI bridge; it is not a foreign-Turn refusal test. Failure
receipts retain fixture-owned codes rather than echoing CLI errors or
model-authored JSON.
The shared plan also names the existing scope-input requirements for typed
`no_followup` and `exploration_exhausted` observations. A Todo's completion is
not itself evidence that a coverage scope is exhausted; the CLI still rejects
missing coverage inputs before writeback.

For selected-Todo and terminal qualification,
a standalone `cat` may read up to eight workspace-confined files, optionally
Expand Down
12 changes: 9 additions & 3 deletions loopx/cli_commands/project_lifecycle_inputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@
}


class ProgressObservationArgumentError(ValueError):
"""A pure argument rejection, before refresh dispatch or authority writes."""

code = "progress_observation_invalid_arguments"


def split_explore_result_input(vision_packet, result_path, *, scope_context=None):
"""Transport explicit attachments; the capability owns validation/conflicts.
Expand Down Expand Up @@ -100,20 +106,20 @@ def inline_progress_observation(
if not any(value is not None for value in fields.values()) and not evidence_ids:
return None
if not fields["result_class"]:
raise ValueError("typed progress observation requires --progress-result-class")
raise ProgressObservationArgumentError("typed progress observation requires --progress-result-class")
if fields["result_class"] in {
ProgressResultClass.EXPLORATION_EXHAUSTED.value,
ProgressResultClass.NO_FOLLOWUP.value,
} and not fields["coverage_scope_id"]:
raise ValueError(
raise ProgressObservationArgumentError(
f"--progress-result-class {fields['result_class']} requires "
"--progress-coverage-scope-id"
)
if (
fields["result_class"] == ProgressResultClass.EXPLORATION_EXHAUSTED.value
and fields["coverage_complete"] is not True
):
raise ValueError(
raise ProgressObservationArgumentError(
"--progress-result-class exploration_exhausted requires "
"--progress-coverage-complete"
)
Expand Down
28 changes: 27 additions & 1 deletion loopx/cli_commands/todo.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,12 @@
from ..control_plane.quota.settlement import (
QuotaSettlementReadback,
SettlementIdentity,
SettlementStepKind,
build_turn_scoped_cli_settlement_plan,
attach_settlement_progress,
read_heartbeat_settlement,
settlement_result_payload,
settlement_step_command,
)
from ..control_plane.runtime.time import chronology_key
from ..control_plane.todos.markdown import render_todo_markdown
Expand Down Expand Up @@ -134,6 +137,7 @@ def _todo_settlement_plan(
identity: SettlementIdentity, *, args: argparse.Namespace,
registry_path: Path, runtime_root: Path,
goal_ref: dict[str, str] | None,
quota_spend_source: str = "heartbeat",
) -> dict[str, object]:
"""Render the native plan with the original route and supplied lease facts."""
actor_args = ""
Expand All @@ -156,7 +160,7 @@ def _todo_settlement_plan(
replan_obligation_id=identity.replan_obligation_id,
turn_instance_id=identity.turn_instance_id, command_prefix=prefix,
scoped_cli_args="", lifecycle_actor_args=actor_args, writeback_path_args=path_args,
goal_ref=goal_ref,
goal_ref=goal_ref, quota_spend_source=quota_spend_source,
).as_dict()

from ..capabilities.explore.turn_context import project_settlement_attachment
Expand Down Expand Up @@ -633,6 +637,8 @@ def handle_todo_command(
no_follow_up=bool(args.no_follow_up),
)
if completion_error is not None:
progress = settlement_readback.progress
assert progress is not None
settlement_result = settlement_readback.settlement
payload = {
"ok": False,
Expand All @@ -652,6 +658,7 @@ def handle_todo_command(
registry_path=registry_path,
runtime_root=runtime_root,
goal_ref=goal_ref,
quota_spend_source=progress["quota_spend_source"],
),
"error": completion_error,
}
Expand Down Expand Up @@ -825,6 +832,25 @@ def handle_todo_command(
payload["ok"] = False
payload["receipt_repair_required"] = True
payload["error"] = settlement_result.failure.reason
elif not args.no_follow_up:
attach_settlement_progress(
payload, settlement_readback, registry_path=registry_path,
runtime_root=runtime_root, goal_ref=goal_ref,
)
progress = settlement_readback.progress
assert progress is not None
if progress.get("next_step") == "durable_writeback":
identity = settlement_readback.identity.value
assert identity is not None
plan = _todo_settlement_plan(
identity, args=args, registry_path=registry_path,
runtime_root=runtime_root, goal_ref=goal_ref,
quota_spend_source=progress["quota_spend_source"],
)
payload["settlement_plan"] = plan
payload["next_cli_actions"] = [settlement_step_command(
plan, SettlementStepKind.DURABLE_WRITEBACK,
)]
if (
args.todo_command == "complete"
and payload.get("ok")
Expand Down
3 changes: 3 additions & 0 deletions loopx/control_plane/quota/settlement_plan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ export function turnScopedCliSettlementPlan(params: JsonObject): SettlementPlan
"continue authorized work within existing limits or report a genuine typed blocker. " +
"Set --delivery-boundary " + DELIVERY_BOUNDARIES.join("|") + " for the applicable case above. " +
"Typed evidence: --progress-result-class " + PROGRESS_RESULT_CLASSES.join("|") +
". For no_followup or exploration_exhausted, also pass --progress-coverage-scope-id; " +
"exploration_exhausted also needs --progress-coverage-complete. " +
"Todo completion alone does not establish scope coverage" +
". For blocked outcome_gap, --progress-work-item-id must match this plan's Todo/obligation id " +
"and --progress-blocker-id names the blocker. Repeat --progress-evidence-id for evidence. " +
"IDs are 1-128 characters, start with an ASCII letter/digit, then letters/digits or ._:/- " +
Expand Down
9 changes: 7 additions & 2 deletions loopx/control_plane/testing/model_tool_behavior.py
Original file line number Diff line number Diff line change
Expand Up @@ -554,13 +554,18 @@ def execute_loopx_cli(
bounded_detail = (
detail if len(detail) <= 1_000 else detail[:500] + "\n...\n" + detail[-500:]
)
raise LoopxCliExecutionError(completed.returncode, bounded_detail)
raise LoopxCliExecutionError(
completed.returncode, bounded_detail, stdout=completed.stdout,
)
return completed.stdout


class LoopxCliExecutionError(RuntimeError):
"""An executed CLI returned nonzero; this does not imply state rollback."""

def __init__(self, returncode: int, detail: str) -> None:
def __init__(self, returncode: int, detail: str, *, stdout: str = "") -> None:
super().__init__(f"LoopX CLI command failed with exit={returncode}: {detail}")
self.returncode = returncode
# Transient tool feedback only. Durable receipts must never serialize
# this raw output or infer rollback from the presence of valid JSON.
self.stdout = stdout
Loading
Loading