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
10 changes: 10 additions & 0 deletions docs/reference/protocols/turn-envelope-v0.md
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,12 @@ operation also runs the owed command there, using the originating CLI's
implementation. The normal workspace and settlement guards still apply; a
missing or incompatible workspace leaves the original owed command recoverable.

Callers that need a compact result can add `--settlement-receipt` to
`refresh-state --settle-turn`. A successful settled result provides its exact
identity and receipts inline and a `result_details.command` for the full saved
result. Inspect unresolved controls before continuing. If saving the view fails
after commitment, retry the same Turn and check its receipts.

An in-flight recommendation does not require keeping a completed Todo open.
Validate its original declaration, use ordinary completion when satisfied, and
write semantic closeout with an evidence-linked vision decision on the original
Expand All @@ -292,6 +298,10 @@ Todo 或 obligation/Turn 的 canonical 待结算命令并验证回执;不重
继续使用原调用方的 CLI 实现。原工作区和结算校验保持;目录缺失或不兼容时保留
原待结算命令以便恢复。

需要紧凑回执时,在 `refresh-state --settle-turn` 后加 `--settlement-receipt`。
结算成功后可直接核对本次身份和回执,用 `result_details.command` 读取完整结果。
先处理未解决的控制项。若结算已提交而视图保存失败,按原 Turn 重试并核对回执。

执行中续跑建议不要求把已完成的 Todo 保持开放。原声明验收满足后可正常完成,并
在原 Turn 上按语义收尾写入有证据的 vision;未完成的阶段进展仍可保留开放状态。
新 Turn 尊重上轮明确收尾或结构化 `path_delta.outcome=replan`,放开旧续跑偏好,
Expand Down
19 changes: 18 additions & 1 deletion loopx/cli_commands/project_lifecycle_refresh_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,10 @@ def register_refresh_state_command(
"--settle-turn", action="store_true",
help="After verified writeback, execute the owed quota step once for this exact Turn; replay never creates another debit.",
)
refresh_state_parser.add_argument(
"--settlement-receipt", action="store_true",
help="With --settle-turn, retain successful supporting snapshots in an immutable result and print controls plus exact receipts.",
)
refresh_state_parser.add_argument(
"--no-global-sync",
action="store_true",
Expand Down Expand Up @@ -484,6 +488,8 @@ def handle_refresh_state_command(
outcome_update_result = None
goal_acceptance_preview = None
try:
if getattr(args, "settlement_receipt", False) and (not args.settle_turn or fmt != "json"):
raise ValueError("--settlement-receipt requires --settle-turn and --format json")
if getattr(args, "settle_turn", False) and not (
args.agent_id and args.turn_instance_id and bool(args.todo_id) != bool(args.replan_obligation_id)
):
Expand Down Expand Up @@ -975,7 +981,7 @@ def handle_refresh_state_command(
def _settle_requested_refresh(payload, args, registry_path, goal_ref):
if not getattr(args, "settle_turn", False):
return
from .refresh_settlement import settle_refreshed_turn
from .refresh_settlement import present_settled_refresh, settle_refreshed_turn

settle_refreshed_turn(payload, registry_path=registry_path,
runtime_root=resolve_runtime_root(load_registry(registry_path), args.runtime_root),
Expand All @@ -986,3 +992,14 @@ def _settle_requested_refresh(payload, args, registry_path, goal_ref):
Path(args.delivery_workspace_path)
if args.delivery_workspace_path else None
))
if getattr(args, "settlement_receipt", False):
from ..control_plane.quota.error_codes import QuotaDecisionTransportError
try:
view = present_settled_refresh(payload, runtime_root=resolve_runtime_root(
load_registry(registry_path), args.runtime_root))
except QuotaDecisionTransportError as exc:
payload.update(ok=False, error=str(exc))
return # Exact committed receipts stay visible; never re-admit.
if view is not payload:
payload.clear()
payload.update(view)
17 changes: 10 additions & 7 deletions loopx/cli_commands/quota_capture.py
Original file line number Diff line number Diff line change
Expand Up @@ -851,6 +851,7 @@ def capture_decision(directory: Path | None, payload: dict[str, object]) -> None
def budgeted_settlement_result(
payload: dict[str, object], *, runtime_root: Path, output_bytes: int,
reader_argv: list[str] | None = None,
supporting_fields: tuple[str, ...] | None = None,
) -> dict[str, object]:
"""Present one evaluated result with its complete immutable diagnostic.

Expand All @@ -864,7 +865,7 @@ def fits(value: dict[str, object]) -> bool:
# Match the JSON CLI serializer, including print's newline.
return len(json.dumps(value, ensure_ascii=False, separators=(",", ":")).encode()) + 1 <= output_bytes

if fits(payload):
if supporting_fields is None and fits(payload):
return payload
try:
uid = private_capture_uid()
Expand All @@ -876,20 +877,21 @@ def fits(value: dict[str, object]) -> bool:
raise ValueError("result capture requires a private physical directory owned by this user")
directory = Path(tempfile.mkdtemp(prefix="settlement-", dir=root))
saved = directory / "result.json"
write_decision_capture(saved, payload)
raw = saved.read_bytes()
if len(raw) > MAX_REQUEST_BYTES:
digest = write_decision_capture(saved, payload)
if saved.stat().st_size > MAX_REQUEST_BYTES:
raise ValueError(f"complete result exceeds the existing reader limit: {saved}")
digest = hashlib.sha256(raw).hexdigest()
reference = write_context_read_reference(saved, digest)
details = {"reference_file": str(reference),
"command": context_capture_read_command(saved, digest, reference_file=reference,
page_bytes=min(NATIVE_CONTEXT_PAGE_BYTES, output_bytes),
max_page_output_bytes=output_bytes, reader_argv=reader_argv)}
deferred = [key for key in ("before", "after") if key in payload]
deferred = [key for key in (supporting_fields if supporting_fields is not None else ("before", "after")) if key in payload]
view = {key: value for key, value in payload.items() if key not in deferred}
view["result_details"] = {**details, "deferred_fields": deferred}
view["instruction"] = (
"The exact Turn is settled. Supporting snapshots are complete in result_details; read only needed details. "
"Keep every returned warning and unresolved control. No additional spend or admission is required for this result."
) if supporting_fields is not None else (
"The operation was already evaluated. before/after are complete in the immutable result; "
"read only needed details with context-read --select. Preserve the original Turn and effect "
"identity during recovery; do not repeat admission or infer authority from this presentation."
Expand All @@ -905,7 +907,8 @@ def fits(value: dict[str, object]) -> bool:
max_page_output_bytes=output_bytes, reader_argv=reader_argv),
"instruction": "Read the complete selected result controls before acting. The operation was "
"already evaluated; preserve its original Turn/effect identity. No execution authority "
"or context baseline is granted. before/after remain available in the same immutable result."}
"or context baseline is granted. " + ("Supporting snapshots remain available in the same immutable result."
if supporting_fields is not None else "before/after remain available in the same immutable result.")}
if fits(view):
return view
raise ValueError(f"result reader metadata exceeds the output budget; complete result: {saved}")
Expand Down
Loading
Loading