diff --git a/benchmark/runtime/EXECUTION_OBSERVATIONS.md b/benchmark/runtime/EXECUTION_OBSERVATIONS.md index 06edc94773..ccf8e9cfae 100644 --- a/benchmark/runtime/EXECUTION_OBSERVATIONS.md +++ b/benchmark/runtime/EXECUTION_OBSERVATIONS.md @@ -39,6 +39,25 @@ merely to produce an attachment. Configuration and readback invoke neither a probe nor a model. Runtime receipts record the selected producer, which does not attest execution, adoption or improvement. +Choose the settlement plan's ordinary `command_template` for in-flight progress +without new decision evidence. Use `semantic_closeout_command_template` for new +evidence from this admitted work, a route change or Todo completion; bind the +evaluated artifact/input and actual decision in a fresh Vision/path_delta, +including `continue`/`no_change` when retaining the route. Keep the original +Goal/Todo/Turn, follow the remaining writeback/spend steps and yield for freshly +guarded Host continuation; an unfinished Todo stays open. Prior-feedback +readback, bookkeeping and deterministic rechecks without new information are +context, not new progress: use them to advance the task first. Unchanged polls +alone need no checkpoint; neither feedback counts nor this command enables Stride. + +For explicitly continuing optimization, candidate usability alone is not completion: before +stopping, weigh acceptance gaps, plausible headroom and feasible next work's full marginal +cost against accepted stops, recording evidence in path_delta. Continue/replan +useful authorized work; preserve Stop, budget/permission limits and genuine waits, +and never infer completion or invent work, pivots or successors from submission +or unused budget alone. This is Agent guidance, not automatic tool-result classification; +native-model adoption, long-Turn supervision and outcome benefit remain unqualified. + ## Retiring the bundled research treatment `research_loop` and `LOOPX_RESEARCH_LOOP` have been removed. Any presence of @@ -170,6 +189,18 @@ Stop、原预算和真实阻塞继续有效,不为填满时长制造任务。 变化拒绝归因;文件清单哈希不是原生归档哈希,只覆盖明确选择的文件及探针 边界,不覆盖传递依赖或独立写入者,结果仍须证明对应实际评测快照和输入版本。 +当前授权工作实际产生或取得新的决策证据后,才进入相应工作段的检查点。旧反馈读回、 +记账和没有新增信息的确定性重复验证只是上下文,应先用于推进原任务。使用当前 +settlement plan 的 `durable_writeback.semantic_closeout_command_template`, +保留原 Turn、Todo、Goal 身份,填写实际结果和 Vision,再按同一 plan 的剩余 +步骤结算并让宿主接续;未完成的 Todo 保持 open。即使路线不变,也在既有 +Vision/path delta 中记录被评估版本、输入和决策。下一次 guard 继续同一 Goal/session +或执行已有到期复核;反馈数量与这个命令都不启用 Stride。未变化轮询、记账本身 +无需新 checkpoint。明确持续优化的目标不能因已有可用候选就自动收尾;结束前 +评估剩余验收、改进空间、可行实验、完整边际成本和原停止条件。合理时可停止, +不强制换路或用满时间。这是 Agent 执行指导,不是自动分类工具结果或模型已采用的证明。 +原生模型采用、自动长轮次监督和结果收益仍未取得资格,脚本路径通过不能替代这些证据。 + 观测通过既有 Explore 写回、replan 和研究历程保留,输入版本与引用必须属于 该 finding,计划/实际方法不同须记录理由。finding 引用及观测的 `evidence_refs`、 `result_ref` 共用既有语法,可保留 `validation:run/1`;方法 ID 仍使用独立规则。 diff --git a/docs/reference/protocols/goal-vision-replan-contract-v0.md b/docs/reference/protocols/goal-vision-replan-contract-v0.md index 40fc1cf60f..fa0b1970ce 100644 --- a/docs/reference/protocols/goal-vision-replan-contract-v0.md +++ b/docs/reference/protocols/goal-vision-replan-contract-v0.md @@ -917,14 +917,57 @@ receipts retain their checkpoint semantics: } ``` -CLI in-flight settlement omits the material-closeout Vision authoring schema. -Use the projected continuation command and its original boundary/identity flags; -this does not waive validation, writeback or debit rules. Material CLI closeout -and MCP `complete_task` retain their evidence-linked Vision authoring contract. - -CLI 进行中结算不附带实质收尾的 Vision 编写结构。沿用投影命令及原边界、身份 -参数,仍须验证、写回与原 debit;实质 CLI 收尾和 MCP `complete_task` 保留 -证据关联的 Vision 编写契约。 +Ordinary CLI Todo settlement includes the existing Vision authoring schema even +when its default boundary is in-flight. The durable-writeback step also exposes +`semantic_closeout_command_template`: the same admitted Goal, Todo and Turn, +with `--delivery-boundary semantic_closeout --agent-vision-json ` selected. +It is an alternative for that step, not another plan, claim, debit or admission. +Material CLI closeout and MCP `complete_task` retain their existing evidence gates. + +Choose `command_template` for ordinary in-flight progress without new decision +evidence; use `semantic_closeout_command_template` for new evidence from this +admitted work, a route change or Todo completion. Bind the evaluated artifact/input +and actual decision in fresh Vision/path_delta, including `continue`/`no_change` +when retaining the route. Keep the original Goal/Todo/Turn, follow the remaining +writeback/spend steps and yield for freshly guarded Host continuation; an unfinished +Todo stays open. Prior-feedback readback, bookkeeping and deterministic rechecks +without new information are context, not progress: advance the task first. +Unchanged polls alone need no checkpoint. This is Agent guidance, not automatic +tool-text classification, a Stride trigger or proof of adoption/benefit. + +An open Todo with controller-declared completion validation can use this +semantic checkpoint for `outcome_progress` with a fresh, open Vision and an +evidence-linked `continue` or `no_change` path. The shared settlement owner +qualifies the admitted writeback and its exact receipted spend through the same +rule. This closes only the original Turn: the Todo remains open and its final +completion validator remains pending. Primary-outcome, closed-Vision and +terminal declarations retain that validator gate. + +For explicitly continuing/optimization objectives, candidate usability alone is not completion: +before stop/no_followup, weigh acceptance gaps, plausible headroom and feasible next work's +full marginal cost against accepted stops in an evidence-linked path_delta. +Continue/replan useful authorized work; preserve Stop, budget/permission limits +and genuine waits. Neither submission nor unused budget alone establishes completion +or requires invented work, pivots or successors. + +普通 CLI Todo 的默认 in-flight 结算也提供 Vision 编写结构。无新决策证据的普通 +推进选 `command_template`;本次准入工作产生或取得新证据、改变路线或完成 Todo, +选 `semantic_closeout_command_template`,以 fresh Vision/path_delta 关联评估产物、 +输入版本和实际决策,保留路线也可用 `continue` / `no_change`。沿用原 Goal、Todo、 +Turn,按原计划继续写回和扣额,再由宿主以新 guard 接续;未完成 Todo 保持 open, +不另建 plan、认领、扣额或准入。旧反馈读回、记账、无新信息的确定性重验只是上下文, +应先用于推进任务;未变化轮询不要求新 checkpoint。它是 Agent 指导,不自动分类 +工具文本、不触发 Stride,也不证明模型采用或收益。 + +持续优化目标不能仅因候选可用就完成:停止前在 path_delta 中用证据对照剩余验收、 +改进空间、可行后续工作的完整边际成本和原停止条件;继续或重规划有用且获授权的工作, +保留 Stop、预算/权限限制与真实等待。提交或剩余额度均不证明完成,也不要求制造工作、 +换路或后继 Todo。 + +声明完成验证器的开放 Todo,可以用 fresh、open 的 Vision 和有证据关联的 +`continue` / `no_change` 路径记录 `outcome_progress`;准入写回与精确回执的扣额 +复用同一 TS 资格规则。它只结算原 Turn,不完成 Todo,也不满足最终完成验证器。 +primary outcome、已闭合 Vision 与终态声明仍须通过原完成验证门。 One explicit exception exists for in-flight delivery. When quota admits an open advancement Todo for normal delivery, its settlement guidance may add the @@ -954,7 +997,7 @@ decision merely because the scheduler woke up. Agents must start from projected boundary and identity flags; reconstructing a generic `semantic_closeout` command discards that continuity contract. -If the work actually replanned or completed the Todo, explicitly choose +If this admitted work obtains new decision evidence, actually replans or completes the Todo, explicitly choose `semantic_closeout` and provide its required vision checkpoint. Do not simply remove the replan ACK to make a real replan pass as in-flight work. Conversely, ordinary within-Todo progress must not add `--autonomous-replan-recorded`. diff --git a/loopx/control_plane/effect_program.py b/loopx/control_plane/effect_program.py index f283d6a9c1..d870c94d7d 100644 --- a/loopx/control_plane/effect_program.py +++ b/loopx/control_plane/effect_program.py @@ -636,6 +636,7 @@ class SettlementStep: conditional: bool = False command_condition: str | None = None vision_authoring: Mapping[str, Any] | None = None + semantic_closeout_command_template: str | None = None def as_dict(self) -> dict[str, Any]: step: dict[str, Any] = { @@ -653,6 +654,8 @@ def as_dict(self) -> dict[str, Any]: step["command_condition"] = self.command_condition if self.vision_authoring is not None: step["vision_authoring"] = dict(self.vision_authoring) + if self.semantic_closeout_command_template is not None: + step["semantic_closeout_command_template"] = self.semantic_closeout_command_template return step diff --git a/loopx/control_plane/effect_program.ts b/loopx/control_plane/effect_program.ts index 7e468d3642..b66576161c 100644 --- a/loopx/control_plane/effect_program.ts +++ b/loopx/control_plane/effect_program.ts @@ -213,6 +213,8 @@ export interface SettlementStep { idempotency_key_ref: string; expected_receipt: string; command_template?: string; + /** Same admitted writeback, with the existing semantic boundary selected. */ + semantic_closeout_command_template?: string; command_condition?: "todo_deliverable_complete"; vision_authoring?: JsonObject; conditional?: true; @@ -658,6 +660,7 @@ export function settlementStepPayload(step: SettlementStep): JsonObject { expected_receipt: step.expected_receipt, }; if (step.command_template) payload.command_template = step.command_template; + if (step.semantic_closeout_command_template) payload.semantic_closeout_command_template = step.semantic_closeout_command_template; if (step.command_condition) payload.command_condition = step.command_condition; if (step.conditional) payload.conditional = true; if (step.vision_authoring) payload.vision_authoring = step.vision_authoring; diff --git a/loopx/control_plane/effect_runtime_handlers.ts b/loopx/control_plane/effect_runtime_handlers.ts index 258d0972ac..7787d45865 100644 --- a/loopx/control_plane/effect_runtime_handlers.ts +++ b/loopx/control_plane/effect_runtime_handlers.ts @@ -711,6 +711,11 @@ export function createEffectRuntimeHandlers( requiredString(params.expected_effect_id, "expected_effect_id"), ), ], + [ + "settlement.open_todo_semantic_checkpoint", + lazyHandler(() => import("./goals/vision_checkpoint.ts"), ({qualifiesOpenTodoSemanticCheckpoint}) => (params) => + qualifiesOpenTodoSemanticCheckpoint(params.writeback, settlementIdentity(settlementIdentityInput(params.identity)))), + ], [ "settlement.receipt_bound_monitor_phase", lazyHandler(() => import("./quota/settlement_phase.ts"), ({receiptBoundMonitorPhase}) => (params) => receiptBoundMonitorPhase({ diff --git a/loopx/control_plane/goal_vision_lifecycle.json b/loopx/control_plane/goal_vision_lifecycle.json new file mode 100644 index 0000000000..a2c3f8980f --- /dev/null +++ b/loopx/control_plane/goal_vision_lifecycle.json @@ -0,0 +1,3 @@ +{ + "closed_states": ["vision_closed", "retired", "retired_or_superseded", "superseded", "no_followup"] +} diff --git a/loopx/control_plane/goals/goal_vision_state.py b/loopx/control_plane/goals/goal_vision_state.py index ae8306dc7d..238b94c120 100644 --- a/loopx/control_plane/goals/goal_vision_state.py +++ b/loopx/control_plane/goals/goal_vision_state.py @@ -1,19 +1,15 @@ from __future__ import annotations +from importlib.resources import files +import json import re from typing import Any GOAL_VISION_DEFAULT_STATE = "vision_patch_proposed" -GOAL_VISION_CLOSED_STATES = frozenset( - { - "vision_closed", - "retired", - "retired_or_superseded", - "superseded", - "no_followup", - } -) +GOAL_VISION_CLOSED_STATES = frozenset(json.loads( + files("loopx.control_plane").joinpath("goal_vision_lifecycle.json").read_text(encoding="utf-8") +)["closed_states"]) GOAL_VISION_SUCCESSOR_REQUIRED_STATES = frozenset({"vision_closed"}) GOAL_VISION_STATE_ALIASES = { "closed": "vision_closed", diff --git a/loopx/control_plane/goals/vision_checkpoint.ts b/loopx/control_plane/goals/vision_checkpoint.ts index 303d406e18..cc1e029561 100644 --- a/loopx/control_plane/goals/vision_checkpoint.ts +++ b/loopx/control_plane/goals/vision_checkpoint.ts @@ -1,6 +1,7 @@ -import type { JsonObject } from "../effect_program.ts"; +import {settlementIdentityPayload, type JsonObject, type SettlementIdentity} from "../effect_program.ts"; import { EffectRuntimeRequestError } from "../effect_runtime_errors.ts"; -import { isBoundedBlockedRetry } from "../quota/settlement_phase.ts"; +import {jsonObject} from "../runtime_decode.ts"; +import { isAcceptedProgressWriteback, isBoundedBlockedRetry } from "../quota/settlement_phase.ts"; import { DELIVERY_BOUNDARIES, type DeliveryBoundary, @@ -12,6 +13,7 @@ import { } from "../work_items/delivery_outcome.ts"; import {normalizeStrideDecision, STRIDE_DECISION_SCHEMA, RECORDED_STRIDE_SCHEMA, STRIDE_PROBE_LIMITS, STRIDE_AXIS_NAMES, STRIDE_PLAN_LIMIT, STRIDE_REASON_LIMIT, STRIDE_REVIEW_LIMIT} from "../work_items/stride_policy.ts"; +import lifecycle from "../goal_vision_lifecycle.json" with {type: "json"}; export const VISION_REFRESH_REQUEST_SCHEMA = "loopx_vision_refresh_request_v0"; export const VISION_REFRESH_PREPARED_SCHEMA_VERSION = @@ -126,6 +128,14 @@ export function visionAuthoringContract(autonomousReplan = false): JsonObject { ? `Autonomous review: changing ${GOAL_VISION_DURABLE_FIELDS.join(", ")} requires outcome=replan, even when the Goal objective stays unchanged. Retaining these fields permits continue/no_change. ` : "Changed mainline: outcome=replan. ") + "vision_closed closes a stage, not Goal; no_followup: no scoped work.", + // Autonomous planning/Stride and settlement-only recovery carry their own + // decision guidance. Keep this reminder at the ordinary checkpoint entry. + ...(autonomousReplan ? {} : { + continuation_review: "For explicitly continuing/optimization objectives, candidate usability alone is not completion: before stop/no_followup, " + + "weigh acceptance gaps, plausible headroom and feasible next work's full marginal cost against accepted stops in an evidence-linked path_delta; " + + "continue/replan useful authorized work, preserve Stop, budget/permission limits and genuine waits, " + + "and never infer completion or invent work/pivots/successors from submission or unused budget alone.", + }), }; } // Bounded typed fallback declarations survive prepare unchanged so the @@ -408,6 +418,37 @@ function normalizeGoalVisionState(value: unknown): string { return GOAL_VISION_STATE_ALIASES[state] ?? state; } +/** The existing lifecycle vocabulary is shared with the Python read model. */ +export function goalVisionStateIsOpen(value: unknown): boolean { + try { return !lifecycle.closed_states.includes(normalizeGoalVisionState(value)); } + catch { return false; } +} + +/** Qualify an open Todo's evidence checkpoint without accepting its deliverable. + * Write-time callers supply the admitted identity and prepared Vision; spend + * callers first verify the exact durable receipt. Terminal validation remains + * with Todo completion. An unchanged reason alone is not new decision evidence. */ +export function qualifiesOpenTodoSemanticCheckpoint(value: unknown, identity: SettlementIdentity): boolean { + const run = jsonObject(value), checkpoint = jsonObject(run?.vision_checkpoint); + const persisted = jsonObject(run?.settlement_identity), vision = jsonObject(run?.agent_vision); + if (!isAcceptedProgressWriteback(run, identity) || run?.completion_todo_id != null || + checkpoint?.delivery_boundary !== "semantic_closeout" || checkpoint.required !== true || + checkpoint.decision !== "patched" || !persisted || + Object.entries(settlementIdentityPayload(identity)).some(([key, value]) => persisted[key] !== value) || + vision?.schema_version !== "goal_vision_replan_contract_v0" || + // Compact run history omits this redundant Goal field; the exact run + // and settlement identity above still bind it. Present conflicts fail. + (vision.goal_id != null && vision.goal_id !== identity.goal_id) || + vision.agent_id !== identity.agent_id || typeof vision.state !== "string" || !vision.state.trim() || + !goalVisionStateIsOpen(vision.state)) return false; + const acceptance = jsonObject(vision.vision_patch)?.acceptance_summary; + let path; + try { [path] = normalizeGoalPathDelta(vision.path_delta); } catch { return false; } + return typeof acceptance === "string" && !!acceptance.trim() && !!path && + (path.outcome === "continue" || path.outcome === "no_change") && + Array.isArray(path.evidence_refs) && path.evidence_refs.length > 0; +} + function normalizeAdvancementPolicy(value: unknown): string { const candidate = compactText(value).toLowerCase().replaceAll("-", "_"); if (!GOAL_VISION_ADVANCEMENT_POLICIES.some(policy => policy === candidate)) { diff --git a/loopx/control_plane/quota/effect_program.py b/loopx/control_plane/quota/effect_program.py index c47571a36c..19cb7f8e3c 100644 --- a/loopx/control_plane/quota/effect_program.py +++ b/loopx/control_plane/quota/effect_program.py @@ -234,11 +234,19 @@ def build_turn_scoped_cli_settlement_plan( f"{lifecycle_actor_args}{turn_arg}{goal_ref_arg} --evidence ''" ) terminal_closeout = ordinary_completion + " --no-follow-up" - writeback = ( - f"{cli_prefix} refresh-state --goal-id {shlex.quote(goal_id)} " - "--classification --delivery-batch-scale " - f"--delivery-outcome {boundary_arg}{binding_arg}{turn_arg}{goal_ref_arg}" - f"{scoped_cli_args}{writeback_path_args}" + + def writeback_command(boundary: str) -> str: + return ( + f"{cli_prefix} refresh-state --goal-id {shlex.quote(goal_id)} " + "--classification --delivery-batch-scale " + f"--delivery-outcome {boundary}{binding_arg}{turn_arg}{goal_ref_arg}" + f"{scoped_cli_args}{writeback_path_args}" + ) + + writeback = writeback_command(boundary_arg) + semantic_writeback = ( + writeback_command(" --delivery-boundary semantic_closeout") + + " --agent-vision-json " ) spend = ( f"{cli_prefix} quota spend-slot --goal-id {shlex.quote(goal_id)} --slots 1 " @@ -255,6 +263,7 @@ def build_turn_scoped_cli_settlement_plan( "command_templates": { **({"todo_claim": task_claim} if todo_id else {}), "todo_completion": ordinary_completion, "durable_writeback": writeback, + "semantic_closeout": semantic_writeback, "quota_spend": spend, "terminal_closeout": terminal_closeout, }, }) @@ -267,6 +276,7 @@ def build_turn_scoped_cli_settlement_plan( expected_receipt=row["expected_receipt"], command_template=row.get("command_template"), conditional=row.get("conditional", False), command_condition=row.get("command_condition"), vision_authoring=row.get("vision_authoring"), + semantic_closeout_command_template=row.get("semantic_closeout_command_template"), ) for row in payload["ordered_steps"] ), _runtime_payload=payload) diff --git a/loopx/control_plane/quota/settlement_plan.ts b/loopx/control_plane/quota/settlement_plan.ts index e7e86b82ba..3b8a637bcf 100644 --- a/loopx/control_plane/quota/settlement_plan.ts +++ b/loopx/control_plane/quota/settlement_plan.ts @@ -19,6 +19,8 @@ export function turnScopedCliSettlementPlan(params: JsonObject): SettlementPlan const commands = requireJsonObject(params.command_templates, "command_templates"); const writeback = requireNonEmptyString(commands.durable_writeback, "durable_writeback"); const spend = requireNonEmptyString(commands.quota_spend, "quota_spend"); + const semanticWriteback = commands.semantic_closeout === undefined ? null + : requireNonEmptyString(commands.semantic_closeout, "semantic_closeout"); // Scale remains CLI-owned. Project its actual accepted inputs rather than // copying another enum or asking callers to load the entire command help. const scales = requireStringArray(params.delivery_batch_scale_choices, "delivery_batch_scale_choices"); @@ -81,21 +83,27 @@ export function turnScopedCliSettlementPlan(params: JsonObject): SettlementPlan "an artifact pointer; --result-file requires approved Goal acceptance criteria bound to this Todo, " + "not merely a Todo validator. No invented successor " + "or --no-follow-up is required. Todo done does not settle the Turn. " + - (inFlight ? "For unfinished bounded progress, keep the Todo open with its original declaration. " + - "The projected in-flight boundary is not an instruction to retain completed work: " + - "when complete or changing route, use --delivery-boundary semantic_closeout with an " + - "evidence-linked vision decision on this same Turn; no new guard or binding is needed." : ""); + "Keep an unfinished Todo open with its original declaration."; } const steps: SettlementStep[] = [ validation, { kind: "durable_writeback", owner: "agent", - precondition: (inFlight || identity.binding_kind !== "todo" ? "validation succeeded" - : "validation succeeded; " + VISION_MATERIAL_CLOSEOUT_HINT + - " Pass a new packet with --agent-vision-json.") + - " Route elimination needs evidence; failure alone is not progress. " + - (autonomousReplan ? "Autonomous replan uses --delivery-boundary semantic_closeout even while its Todo remains open; " - : "outcome_progress may use in_flight_continuation; ") + "outcome_gap requires " + + precondition: "validation succeeded; " + + (autonomousReplan ? "Autonomous replan uses --delivery-boundary semantic_closeout even while its Todo remains open. " + + "Bind new decision evidence from this admitted work to its artifact/input; follow the remaining plan, " + + "then yield for freshly guarded continuation. " + : (inFlight ? "Choose one: command_template for ordinary in_flight_continuation without new decision evidence; " + : "Use command_template for semantic_closeout; ") + + (semanticWriteback !== null ? "semantic_closeout_command_template" : "--delivery-boundary semantic_closeout") + + " for new decision evidence, route change or Todo completion. " + + "Bind new evidence produced or obtained by this admitted work to its artifact/input in fresh Vision/path_delta " + + "with --agent-vision-json, including continue/no_change when retaining the route. " + + "Use this same Turn; no new guard or binding. Follow the remaining plan, then yield for freshly guarded continuation. ") + + "Prior-feedback readback, bookkeeping and deterministic repetition without new information are context, " + + "not this Turn's progress; use them to advance the task first. " + + (!inFlight && identity.binding_kind === "todo" ? VISION_MATERIAL_CLOSEOUT_HINT + " " : "") + + "Route elimination needs evidence; failure alone is not progress. outcome_gap requires " + "semantic_closeout plus blocked + blocker/evidence IDs and continuation checks. " + "For an admitted open continuous Monitor whose observation is unavailable, " + "use this original identity for typed blocked outcome_gap; it settles without " + @@ -103,15 +111,19 @@ export function turnScopedCliSettlementPlan(params: JsonObject): SettlementPlan "An already committed Monitor poll needs neither refresh nor spend." + inputHint, idempotency_key_ref: "$.identity.effect_id", expected_receipt: "durable_writeback_receipt", command_template: writeback, - // In-flight continuation uses its projected flags without a material vision packet. + ...(identity.binding_kind === "todo" && !autonomousReplan && semanticWriteback !== null + ? {semantic_closeout_command_template: semanticWriteback} : {}), + // The same open Todo can reach a semantic feedback checkpoint. Deliver + // its existing authoring contract with the executable command variant. // Autonomous replan already carries this owner in its writeback contract. - ...(identity.binding_kind === "todo" && !inFlight + ...(identity.binding_kind === "todo" && (!inFlight || semanticWriteback !== null) ? {vision_authoring: visionAuthoringContract(autonomousReplan)} : {}), }, { kind: "quota_spend", owner: "agent", - precondition: "matching durable writeback exists and any declared completion validation " + - "has completed the Todo, or the same Turn has qualified in-flight/replan progress; " + + precondition: "exact original-identity durable writeback receipt exists; any declared completion validation " + + "has completed the Todo, or that receipt qualifies same-Turn in-flight/replan progress " + + "or a fresh open-Todo semantic checkpoint. Progress does not waive terminal completion validation; " + "skip this step after typed_blocked_writeback_no_spend", idempotency_key_ref: "$.identity.effect_id", expected_receipt: "quota_spend_receipt", command_template: spend, diff --git a/loopx/control_plane/quota/settlement_validation.py b/loopx/control_plane/quota/settlement_validation.py index 8b4e80f90c..199c597420 100644 --- a/loopx/control_plane/quota/settlement_validation.py +++ b/loopx/control_plane/quota/settlement_validation.py @@ -4,12 +4,33 @@ from typing import Any +from ..effect_runtime import effect_runtime_result from ..todos.completion_validation_projection import ( pending_completion_validation_todo, ) from ..todos.contract import normalize_todo_id +def qualified_open_todo_semantic_checkpoint( + delivery_run: dict[str, Any] | None, *, goal_id: str, + todo_id: str, agent_id: str | None, +) -> bool: + """Transport prepared or exact receipted facts to the shared TS qualifier.""" + if not isinstance(delivery_run, dict) or agent_id is None: + return False + identity = delivery_run.get("settlement_identity") + if not isinstance(identity, dict): + return False + turn_id = identity.get("turn_instance_id") + if not isinstance(turn_id, str) or not turn_id.strip(): + return False + return effect_runtime_result("settlement.open_todo_semantic_checkpoint", { + "identity": {"goal_id": goal_id, "agent_id": agent_id, "todo_id": todo_id, + "turn_instance_id": turn_id}, + "writeback": delivery_run, + }) is True + + def _is_qualified_semantic_replan_writeback( delivery_run: dict[str, Any] | None, *, @@ -158,6 +179,10 @@ def completion_validation_spend_error( is not None for summary in summaries ): + if qualified_open_todo_semantic_checkpoint( + delivery_run, goal_id=goal_id, todo_id=normalized_todo_id, agent_id=agent_id, + ): + return None return ( "quota spend is blocked until controller-declared completion " f"validation durably completes todo {normalized_todo_id}" diff --git a/loopx/control_plane/todos/completion_validation_accountability.py b/loopx/control_plane/todos/completion_validation_accountability.py index d08fb5ade9..d073f9bdfb 100644 --- a/loopx/control_plane/todos/completion_validation_accountability.py +++ b/loopx/control_plane/todos/completion_validation_accountability.py @@ -4,6 +4,7 @@ from typing import Any +from ..quota.settlement_validation import qualified_open_todo_semantic_checkpoint from ..turn_driver.delivery_continuity import DELIVERY_BOUNDARY_IN_FLIGHT from ..work_items.delivery_outcome import DeliveryOutcome from .active_state_todo_parser import parse_active_state_todos @@ -19,8 +20,10 @@ def require_accountable_completion_validation( delivery_boundary: str | None = None, delivery_outcome: str | None = None, semantic_replan_recorded: bool = False, + goal_id: str | None = None, + progress_writeback: dict[str, Any] | None = None, ) -> None: - """Reject accountable evidence while its exact validation Todo is open.""" + """Keep terminal validation pending while qualifying scoped open-Todo progress.""" # A qualified semantic replan updates the path for an open Todo; it does # not claim that Todo completed. Its write-time qualification already @@ -42,6 +45,10 @@ def require_accountable_completion_validation( agent_id=agent_id, ) if pending is not None: + if goal_id is not None and qualified_open_todo_semantic_checkpoint( + progress_writeback, goal_id=goal_id, todo_id=str(pending.get("todo_id")), agent_id=agent_id, + ): + return raise ValueError( "accountable refresh is blocked until controller-declared completion " f"validation durably completes todo {pending.get('todo_id')}" diff --git a/loopx/control_plane/todos/markdown.py b/loopx/control_plane/todos/markdown.py index 24ad7428e8..701adee69d 100644 --- a/loopx/control_plane/todos/markdown.py +++ b/loopx/control_plane/todos/markdown.py @@ -53,6 +53,8 @@ def _render_settlement_plan(plan: object) -> list[str]: lines.append(f"- {step.get('kind')}: {step.get('precondition')}") if step.get("command_template"): lines.append(f" - command{f' ({condition})' if condition else ''}: `{step['command_template']}`") + if step.get("semantic_closeout_command_template"): + lines.append(f" - semantic closeout: `{step['semantic_closeout_command_template']}`") return lines diff --git a/loopx/state_refresh.py b/loopx/state_refresh.py index 0d1b8d4b2b..09291c0214 100644 --- a/loopx/state_refresh.py +++ b/loopx/state_refresh.py @@ -1213,21 +1213,6 @@ def read_explore_context(read_goal_id: str, read_agent_id: str | None): # mutations and conflicting vision decisions before this fence. The # supplement repairs only the missing vision decision, so terminal # validation remains attached to the original Todo completion. - if ( - normalized_delivery_outcome in ACCOUNTABLE_DELIVERY_OUTCOMES - and not checkpoint_supplement - ): - require_accountable_completion_validation( - state_text, - todo_fields=todo_fields, - todo_id=(settlement_identity.todo_id if settlement_identity else None), - agent_id=normalized_agent_id or None, - delivery_boundary=normalized_delivery_boundary, - delivery_outcome=normalized_delivery_outcome, - semantic_replan_recorded=( - effective_autonomous_replan_recorded - ), - ) vision_checkpoint = build_vision_checkpoint( agent_id=normalized_agent_id or None, agent_vision=agent_vision, @@ -1241,6 +1226,31 @@ def read_explore_context(read_goal_id: str, read_agent_id: str | None): autonomous_replan_recorded=effective_autonomous_replan_recorded, blocked_retry=blocked_retry, ) + if ( + normalized_delivery_outcome in ACCOUNTABLE_DELIVERY_OUTCOMES + and not checkpoint_supplement + ): + require_accountable_completion_validation( + state_text, + todo_fields=todo_fields, + todo_id=(settlement_identity.todo_id if settlement_identity else None), + agent_id=normalized_agent_id or None, + delivery_boundary=normalized_delivery_boundary, + delivery_outcome=normalized_delivery_outcome, + semantic_replan_recorded=effective_autonomous_replan_recorded, + goal_id=safe_goal_id, + progress_writeback=({ + "goal_id": safe_goal_id, + "agent_id": normalized_agent_id, + "todo_id": settlement_identity.todo_id, + "turn_instance_id": settlement_identity.turn_instance_id, + "settlement_identity": settlement_identity.as_dict(), + "delivery_outcome": normalized_delivery_outcome, + "vision_checkpoint": vision_checkpoint, + "agent_vision": agent_vision, + "completion_todo_id": completion_todo_id, + } if settlement_identity is not None else None), + ) if checkpoint_supplement and not vision_checkpoint.get("satisfied"): raise ValueError( "checkpoint supplement did not satisfy the missing decision; " diff --git a/tests/control_plane/test_material_feedback_checkpoint.py b/tests/control_plane/test_material_feedback_checkpoint.py new file mode 100644 index 0000000000..867ca24d86 --- /dev/null +++ b/tests/control_plane/test_material_feedback_checkpoint.py @@ -0,0 +1,227 @@ +"""Released feedback closes a segment without closing its continuing Todo. + +The actor is deterministic. Real CLI, File/SQLite authority, settlement, +Explore, Stride and local evaluations run; model adoption is a separate check. +""" +from __future__ import annotations + +import hashlib +from itertools import product +import json +from pathlib import Path +import shlex +import subprocess +import sys + +import pytest + +from benchmark.runtime.research_probe import run_probe +from canonical_authority_fixture import isolate_sqlite_runtime +import test_quota_settlement_cli as fixture + + +@pytest.mark.parametrize(("provider", "strategy", "handoff_mode", "preexisting_feedback"), [ + (*case, False) for case in product(["file", "sqlite"], ["off", "evidence_conditioned_v1"], ["soft_claim", "hard_lease"]) +] + [pytest.param("sqlite", "evidence_conditioned_v1", "hard_lease", True, id="historical-feedback-is-context")]) +def test_feedback_checkpoint_reaches_next_actual_experiment( + tmp_path, monkeypatch, provider, strategy, handoff_mode, preexisting_feedback, +): + isolate_sqlite_runtime(tmp_path, monkeypatch) + project, runtime, registry = fixture._write_fixture(tmp_path) + configuration = json.loads(registry.read_text()) + configuration["goals"][0]["quota"]["allowed_slots"] = 8 + registry.write_text(json.dumps(configuration)) + state = project / f".codex/goals/{fixture.GOAL_ID}/ACTIVE_GOAL_STATE.md" + state.write_text(state.read_text().replace("Settle one standard Codex App delivery.", + "Minimize route cost on the supplied graph while retaining a valid route. " + "A feasible candidate is a checkpoint; stop when no useful authorized comparison remains.")) + fixture._configure_completion_validation_todo(project) + fixture._initialize_fixture_authority(project, runtime, provider=provider, handoff_mode=handoff_mode) + monkeypatch.chdir(project) + binding = ("--goal-id", fixture.GOAL_ID, "--agent-id", fixture.AGENT_ID) + + def cli(*args): + code, packet = fixture._run_cli(registry, runtime, *args, cwd=project) + assert code == 0, packet + return packet + + def execute(command, **values): + for name, value in values.items(): + command = command.replace("<" + name + ">", shlex.quote(str(value))) + assert "<" not in command, command + code, packet = fixture._run_generated_cli(command, registry_path=registry, cwd=project) + assert code == 0, packet + return packet + + cli("configure-goal", "--goal-id", fixture.GOAL_ID, "--execution-replan-after-turns", "1", + "--execution-stride-strategy", strategy, "--explore-graph-enabled", "--execute") + if handoff_mode == "hard_lease": + claimed = cli("todo", "claim", *binding, "--todo-id", fixture.TODO_ID, + "--claimed-by", fixture.AGENT_ID, "--claim-operation-id", "route-original-execution") + assert claimed["execution_managed"] and claimed["claim_operation_id"] == "route-original-execution" + lease_before = cli("task-lease", "inspect", "--goal-id", fixture.GOAL_ID, "--todo-id", fixture.TODO_ID) + assert lease_before["active"] + route = project / "route.json" + graph = {"start": {"detour": 1, "middle": 1, "finish": 1}, + "detour": {"middle": 1}, "middle": {"finish": 1}, "finish": {}} + (project / "graph.json").write_text(json.dumps(graph)) + validator = project / "evaluate.py" + validator.write_text( + "import json\nfrom pathlib import Path\n" + "g=json.loads(Path('graph.json').read_text()); r=json.loads(Path('route.json').read_text())\n" + "assert r[0]=='start' and r[-1]=='finish' and len(r)==len(set(r))\n" + "cost=sum(g[a][b] for a,b in zip(r,r[1:]))\n" + "print(json.dumps({'valid':True,'cost':cost}))\n") + route.write_text(json.dumps(["start", "detour", "middle", "finish"] if preexisting_feedback + else ["start", "middle", "finish"])) + if preexisting_feedback: + previous = subprocess.run([sys.executable, str(validator)], capture_output=True, text=True, check=True) + prior = project / "released-before-admission.json" + prior.write_text(previous.stdout) + assert json.loads(prior.read_text()) == {"valid": True, "cost": 3} + guard = cli("quota", "should-run", *binding, "--runtime-profile", "generic_cli", + "--todo-id", fixture.TODO_ID, "--turn-instance-id", "route-feedback-1") + assert guard["should_run"] and guard["selected_todo"]["todo_id"] == fixture.TODO_ID + plan = guard["interaction_contract"]["cli_channel"]["settlement_plan"] + spend_precondition = plan["ordered_steps"][2]["precondition"] + assert "exact original-identity durable writeback receipt" in spend_precondition + assert "fresh open-Todo semantic checkpoint" in spend_precondition + writeback = plan["ordered_steps"][1] + checkpoint = writeback["semantic_closeout_command_template"] + assert "--delivery-boundary semantic_closeout" in checkpoint and "" not in checkpoint + assert writeback["vision_authoring"]["continuation_review"] + # Merely obtaining the new executable projection performs no work or writeback. + assert not (runtime / "goals" / fixture.GOAL_ID / "runs/index.jsonl").exists() + if preexisting_feedback: + guidance = writeback["precondition"] + assert "not this Turn's progress; use them to advance the task first" in guidance + repeated = run_probe(command=[sys.executable, str(validator)], candidate_files=[route], + output=project / "prior-recheck.json", input_revision="graph-v1", planned_method_id="retained-route", + executed_method_id="retained-route", evidence_ref="probe:prior-recheck", limit_seconds=10) + assert json.loads((project / "prior-recheck.json.stdout").read_text()) == json.loads(prior.read_text()) + # Reading and deterministically reproducing the known result adds no + # decision evidence here. Advance the actual route before checkpointing. + assert not (runtime / "goals" / fixture.GOAL_ID / "runs/index.jsonl").exists() + assert fixture._spend_run_count(runtime) == 0 + route.write_text(json.dumps(["start", "middle", "finish"])) + observed = run_probe(command=[sys.executable, str(validator)], candidate_files=[route], + output=project / "probe-1.json", input_revision="graph-v1", planned_method_id="retained-route", + executed_method_id="retained-route", evidence_ref="probe:route-1", limit_seconds=10) + first = json.loads((project / "probe-1.json.stdout").read_text()) + assert first == {"valid": True, "cost": 2} + if preexisting_feedback: + assert observed["candidate_sha256"] != repeated["candidate_sha256"] + assert json.loads(prior.read_text())["cost"] > first["cost"] + evidence = "result:" + hashlib.sha256((project / "probe-1.json.stdout").read_bytes()).hexdigest() + observed.update(result_ref=evidence, result_candidate_sha256=observed["candidate_sha256"]) + observed["evidence_refs"].append(evidence) + result = project / "released-result.json" + result.write_text(json.dumps({"schema_version": "explore_result_attachment_v0", + "question": "Can an allowed route reduce the original cost?", "input_revision": "graph-v1", + "applicability": "Only the supplied public synthetic graph.", + "observation": "The independently evaluated feasible route costs two.", + "interpretation": "The allowed direct edge is an informative next comparison; feasibility alone is not closeout.", + "status": "tentative", "evidence_refs": observed["evidence_refs"], "execution_observation": observed})) + patch = {"vision_summary": "Minimize route cost under the original graph and authority.", + "acceptance_summary": "Retain a valid route and compare useful allowed alternatives before closeout.", + "advancement_policy": "repeat_until_closed"} + vision = project / "vision.json" + vision.write_text(json.dumps({"schema_version": "goal_vision_replan_contract_v0", "state": "active", + "vision_patch": patch, "todo_delta": ["retain:" + fixture.TODO_ID], + "path_delta": {"outcome": "continue", "prior_assumption": "The retained route is feasible.", + "observed_reality": "Released validation confirms cost two; the permitted direct edge is untested.", + "retained": ["Original optimization objective and current feasible route."], "evidence_refs": [evidence]}})) + result_command = (checkpoint + " --explore-result-json " + shlex.quote(str(result)) + + " --no-global-sync --suppress-external-sinks") + rejected_command = result_command + for key, value in dict(validated_progress="bookkeeping", scale="single_surface", + outcome="surface_only", file=vision).items(): + rejected_command = rejected_command.replace("<" + key + ">", shlex.quote(str(value))) + rejected_code, rejected = fixture._run_generated_cli(rejected_command, registry_path=registry, cwd=project) + assert rejected_code == 1 and not rejected.get("appended") + assert fixture._spend_run_count(runtime) == 0 + refreshed = execute(result_command, validated_progress="route_feedback_verified", + scale="implementation", outcome="outcome_progress", file=vision) + execute(result_command, validated_progress="route_feedback_verified", + scale="implementation", outcome="outcome_progress", file=vision) + assert fixture._classification_count(runtime, "route_feedback_verified") == 1 + persisted = json.loads(Path(refreshed["json_path"]).read_text()) + assert persisted["vision_checkpoint"]["delivery_boundary"] == "semantic_closeout" + assert persisted["agent_vision"]["path_delta"]["evidence_refs"] == [evidence] + execute(plan["ordered_steps"][2]["command_template"]) + execute(plan["ordered_steps"][2]["command_template"]) + assert fixture._spend_run_count(runtime) == 1 + retained_todo = cli("todo", "list", "--goal-id", fixture.GOAL_ID, "--todo-id", fixture.TODO_ID)["todo"] + assert retained_todo["status"] == "open" and retained_todo["completion_validation_required"] + if handoff_mode == "hard_lease": + lease_after = cli("task-lease", "inspect", "--goal-id", fixture.GOAL_ID, "--todo-id", fixture.TODO_ID) + assert lease_after["lease"]["idempotency_key"] == lease_before["lease"]["idempotency_key"] + assert lease_after["lease"]["lease_epoch"] == lease_before["lease"]["lease_epoch"] + if preexisting_feedback: + code, refused_completion = fixture._run_cli(registry, runtime, "todo", "complete", *binding, + "--todo-id", fixture.TODO_ID, "--turn-instance-id", "route-feedback-1", + "--claim-operation-id", "route-original-execution", "--evidence", "probe:route-1", cwd=project) + assert code == 1 and refused_completion.get("validation_blocked_completion"), refused_completion + assert cli("todo", "list", "--goal-id", fixture.GOAL_ID, "--todo-id", fixture.TODO_ID)["todo"]["status"] == "open" + # A new Host wake has a new Turn and the same Goal/Agent/Todo lane. The + # ordinary cadence reads the material result; raw experiment counts do not. + review = cli("quota", "should-run", *binding, "--runtime-profile", "generic_cli", + "--turn-instance-id", "route-review") + assert any(t["kind"] == "periodic_review_due" for t in review["autonomous_replan_obligation"]["triggers"]) + selection = review["interaction_contract"]["cli_channel"]["selection_command"] + review = execute(selection["route_prefix"] + " " + + selection["command_args_template"].format(todo_id=fixture.TODO_ID)) + control = review["replan_action_packet"].get("stride_control") + packet = {"schema_version": "goal_vision_replan_contract_v0", "state": "active", "vision_patch": patch, + "todo_delta": ["retain:" + fixture.TODO_ID], "path_delta": {"outcome": "continue", + "prior_assumption": "A feasible candidate may still leave useful optimization work.", + "observed_reality": "The scoped validation retains a cost-two route; one allowed direct route remains untested.", + "retained": ["Original objective and feasible incumbent."], + "changed": ["Compare the allowed direct route on the same graph."], "evidence_refs": [evidence]}} + if strategy == "off": + assert control is None + else: + assert control + finding = next(row for row in control["explore_basis"]["finding_refs"] + if row.get("execution_observation", {}).get("candidate_sha256") == observed["candidate_sha256"]) + decision = control["minimal_example"] + decision.update(action="switch", next_probe={"scope": "Compare the permitted direct route.", + "prediction": "The independently checked route cost falls from two to one.", + "adoption_criterion": "Same graph, valid endpoints and a lower actual route cost.", + "cost_bound": "One local process and existing settlement; no new evaluation authority.", + "minimum_evidence": "Exact route bytes and evaluator output."}) + decision["explore_review"] = {"revision": control["explore_basis"]["revision"], "disposition": "used", + "considered_refs": [finding["finding_id"]], "reason": "The measured feasible candidate leaves the direct edge untested."} + packet["stride_decision"] = decision + vision.write_text(json.dumps(packet)) + review_plan = review["interaction_contract"]["cli_channel"]["settlement_plan"] + acknowledged = execute(review_plan["ordered_steps"][1]["command_template"] + + " --agent-vision-json " + shlex.quote(str(vision)) + " --no-global-sync --suppress-external-sinks", + validated_progress="route_comparison_decided", scale="implementation", outcome="outcome_progress") + assert acknowledged["autonomous_replan_ack"]["semantic_delta"]["accepted"] + execute(review_plan["ordered_steps"][2]["command_template"]) + next_guard = cli("quota", "should-run", *binding, "--runtime-profile", "generic_cli", + "--todo-id", fixture.TODO_ID, "--turn-instance-id", "route-feedback-2") + assert next_guard["should_run"] and next_guard["selected_todo"]["todo_id"] == fixture.TODO_ID + route.write_text(json.dumps(["start", "finish"])) + changed = run_probe(command=[sys.executable, str(validator)], candidate_files=[route], + output=project / "probe-2.json", input_revision="graph-v1", planned_method_id="direct-route", + executed_method_id="direct-route", evidence_ref="probe:route-2", limit_seconds=10) + assert changed["candidate_sha256"] != observed["candidate_sha256"] + assert json.loads((project / "probe-2.json.stdout").read_text()) == {"valid": True, "cost": 1} + assert first["cost"] == 2 # Retain the original result; no hindsight rewrite. + terminal = dict(packet, state="no_followup", path_delta={"outcome": "stop", + "prior_assumption": "The retained route could still be improved.", + "observed_reality": "The direct route now has cost one.", "stopped": ["This comparison is complete."], + "evidence_refs": ["probe:route-2"]}) + terminal.pop("stride_decision", None) + vision.write_text(json.dumps(terminal)) + refused_command = next_guard["interaction_contract"]["cli_channel"]["settlement_plan"]["ordered_steps"][1][ + "semantic_closeout_command_template"] + " --no-global-sync --suppress-external-sinks" + for key, value in dict(validated_progress="unvalidated_completion", scale="implementation", + outcome="primary_goal_outcome", file=vision).items(): + refused_command = refused_command.replace("<" + key + ">", shlex.quote(str(value))) + code, refused = fixture._run_generated_cli(refused_command, registry_path=registry, cwd=project) + assert code == 1 and "completion validation" in refused["error"], refused + assert cli("todo", "list", "--goal-id", fixture.GOAL_ID, "--todo-id", fixture.TODO_ID)["todo"]["status"] == "open" + assert fixture._classification_count(runtime, "unvalidated_completion") == 0 diff --git a/tests/control_plane/test_quota_settlement_cli.py b/tests/control_plane/test_quota_settlement_cli.py index e07c20fea2..f726c10b1b 100644 --- a/tests/control_plane/test_quota_settlement_cli.py +++ b/tests/control_plane/test_quota_settlement_cli.py @@ -2528,7 +2528,10 @@ def test_in_flight_progress_preserves_todo_across_heartbeat_settlements( ] assert "--delivery-boundary " in first_writeback writeback_step = guard["interaction_contract"]["cli_channel"]["settlement_plan"]["ordered_steps"][1] - assert "vision_authoring" not in writeback_step + assert writeback_step["vision_authoring"]["schema_version"] == "goal_vision_replan_contract_v0" + checkpoint = writeback_step["semantic_closeout_command_template"] + assert "--delivery-boundary semantic_closeout" in checkpoint and "" not in checkpoint + assert f"--turn-instance-id {first_turn_id}" in checkpoint assert "outcome_gap requires semantic_closeout" in writeback_step["precondition"] offered = writeback_step["precondition"].split("; outcome=", 1)[1].split(".", 1)[0].split("|") blocked = { diff --git a/tests/control_plane/test_todo_settlement_markdown.py b/tests/control_plane/test_todo_settlement_markdown.py index 098d2fbdf6..147db0ec55 100644 --- a/tests/control_plane/test_todo_settlement_markdown.py +++ b/tests/control_plane/test_todo_settlement_markdown.py @@ -1,8 +1,14 @@ """Settlement guidance renders the typed plan without changing its semantics.""" from __future__ import annotations +import os +import subprocess +import sys + import pytest +from canonical_authority_fixture import isolate_sqlite_runtime +import test_quota_settlement_cli as fixture from loopx.control_plane.todos.markdown import render_todo_markdown @@ -24,6 +30,7 @@ def test_settlement_recovery_preserves_order_conditions_and_commands() -> None: "precondition": "write back the validated result", "conditional": True, "command_template": "loopx refresh-state --todo-id todo_fixture", + "semantic_closeout_command_template": "loopx refresh-state --todo-id todo_fixture --delivery-boundary semantic_closeout", }, {"kind": "quota_spend", "precondition": "writeback receipt exists"}, { @@ -41,6 +48,7 @@ def test_settlement_recovery_preserves_order_conditions_and_commands() -> None: " - command (todo_deliverable_complete): `loopx todo complete --todo-id todo_fixture`\n" "- durable_writeback: write back the validated result\n" " - command (conditional): `loopx refresh-state --todo-id todo_fixture`\n" + " - semantic closeout: `loopx refresh-state --todo-id todo_fixture --delivery-boundary semantic_closeout`\n" "- quota_spend: writeback receipt exists\n" "- terminal_closeout: all settlement receipts exist\n" " - command: `loopx todo complete --no-follow-up`" @@ -64,3 +72,34 @@ def test_empty_typed_settlement_plan_keeps_its_heading() -> None: assert render_todo_markdown( {"error": "incomplete", "settlement_plan": {"schema_version": "quota_settlement_plan_v1"}} ).endswith("\n\n## Same-Turn settlement plan\n") + + +@pytest.mark.parametrize("handoff_mode", ["soft_claim", "hard_lease"]) +def test_real_cli_recovery_markdown_exposes_the_same_semantic_command(tmp_path, monkeypatch, handoff_mode): + isolate_sqlite_runtime(tmp_path, monkeypatch) + project, runtime, registry = fixture._write_fixture(tmp_path) + fixture._initialize_fixture_authority(project, runtime, provider="sqlite", handoff_mode=handoff_mode) + binding = ["--goal-id", fixture.GOAL_ID, "--agent-id", fixture.AGENT_ID, + "--todo-id", fixture.TODO_ID, "--turn-instance-id", "markdown-feedback"] + if handoff_mode == "hard_lease": + code, claimed = fixture._run_cli(registry, runtime, "todo", "claim", "--goal-id", fixture.GOAL_ID, + "--todo-id", fixture.TODO_ID, "--agent-id", fixture.AGENT_ID, "--claimed-by", fixture.AGENT_ID, + "--claim-operation-id", "markdown-execution", cwd=project) + assert code == 0 and claimed["execution_managed"], claimed + code, guard = fixture._run_cli(registry, runtime, "quota", "should-run", *binding, + "--runtime-profile", "generic_cli", cwd=project) + assert code == 0 and guard["should_run"], guard + complete = ["todo", "complete", *binding, "--no-follow-up", "--evidence", "evidence:route-feedback"] + if handoff_mode == "hard_lease": + complete += ["--claim-operation-id", "markdown-execution"] + code, recovery = fixture._run_cli(registry, runtime, *complete, cwd=project) + assert code == 1, recovery + expected = recovery["settlement_plan"]["ordered_steps"][1]["semantic_closeout_command_template"] + markdown = subprocess.run([sys.executable, "-m", "loopx.entrypoint", "--registry", str(registry), + "--runtime-root", str(runtime), "--format", "markdown", *complete], cwd=project, + env={**os.environ, "PYTHONPATH": str(fixture.REPO_ROOT)}, capture_output=True, text=True) + assert markdown.returncode == 1, markdown.stdout + assert "semantic closeout: `" + expected + "`" in markdown.stdout + spend_precondition = recovery["settlement_plan"]["ordered_steps"][2]["precondition"] + assert "fresh open-Todo semantic checkpoint" in spend_precondition + assert "- quota_spend: " + spend_precondition in markdown.stdout diff --git a/tests/control_plane_ts/open_todo_semantic_checkpoint.test.ts b/tests/control_plane_ts/open_todo_semantic_checkpoint.test.ts new file mode 100644 index 0000000000..9c59d01d22 --- /dev/null +++ b/tests/control_plane_ts/open_todo_semantic_checkpoint.test.ts @@ -0,0 +1,92 @@ +import assert from "node:assert/strict"; +import {spawnSync} from "node:child_process"; +import test from "node:test"; +import {fileURLToPath} from "node:url"; +import {resolveTestPython} from "../../scripts/test-python.mjs"; +import {settlementIdentity, settlementIdentityPayload, type JsonObject} from "../../loopx/control_plane/effect_program.ts"; +import {createEffectRuntimeHandlers, dispatchEffectRuntimeMethod} from "../../loopx/control_plane/effect_runtime_handlers.ts"; +import {goalVisionStateIsOpen, qualifiesOpenTodoSemanticCheckpoint} from "../../loopx/control_plane/goals/vision_checkpoint.ts"; + +const identity = settlementIdentity({goal_id: "goal", agent_id: "agent", todo_id: "todo_checkpoint", turn_instance_id: "turn"}); +const writeback: JsonObject = { + goal_id: identity.goal_id, agent_id: identity.agent_id, todo_id: identity.todo_id, + turn_instance_id: identity.turn_instance_id, settlement_identity: settlementIdentityPayload(identity), + delivery_outcome: "outcome_progress", + agent_vision: {schema_version: "goal_vision_replan_contract_v0", goal_id: "goal", agent_id: "agent", state: "active", + vision_patch: {acceptance_summary: "The current result advances the open comparison."}, + path_delta: {outcome: "continue", prior_assumption: "The incumbent may be sufficient.", + observed_reality: "A new comparison leaves further work open.", retained: ["Original acceptance and incumbent."], + evidence_refs: ["evidence:new-comparison"]}}, + vision_checkpoint: {schema_version: "vision_checkpoint_v0", agent_id: "agent", required: true, satisfied: true, + decision: "patched", delivery_boundary: "semantic_closeout", + triggers: [{kind: "material_delivery_outcome", delivery_outcome: "outcome_progress"}]}, +}; +const changed = (path: string[], value: unknown): JsonObject => { + const copy = structuredClone(writeback); + let owner = copy; + for (const key of path.slice(0, -1)) owner = owner[key] as JsonObject; + owner[path.at(-1)!] = value; + return copy; +}; + +test("open Todo semantic evidence has one read-only registered qualification path", async () => { + const handlers = createEffectRuntimeHandlers({fingerprint: "checkpoint-test", requestShutdown() {}}); + const before = structuredClone(writeback); + assert.equal(qualifiesOpenTodoSemanticCheckpoint(writeback, identity), true); + assert.equal(await dispatchEffectRuntimeMethod(handlers, "settlement.open_todo_semantic_checkpoint", + {identity, writeback}), true); + assert.deepEqual(writeback, before); + assert.equal(qualifiesOpenTodoSemanticCheckpoint(changed(["agent_vision", "path_delta", "outcome"], "no_change"), identity), true); + const compact = structuredClone(writeback); + delete (compact.agent_vision as JsonObject).goal_id; + assert.equal(qualifiesOpenTodoSemanticCheckpoint(compact, identity), true); + await assert.rejects(dispatchEffectRuntimeMethod(handlers, "settlement.open_todo_semantic_checkpoint", + {identity: {goal_id: "goal", todo_id: "todo_checkpoint", turn_instance_id: "turn"}, writeback}), /identity.agent_id/); +}); + +test("checkpoint qualification cannot borrow another binding, reuse old context or accept completion", async () => { + const handlers = createEffectRuntimeHandlers({fingerprint: "checkpoint-negatives", requestShutdown() {}}); + const rejected: Array<[string[], unknown]> = [ + ...["goal_id", "agent_id", "todo_id", "turn_instance_id"].flatMap(field => + [[[field], "foreign"], [["settlement_identity", field], "foreign"]] as Array<[string[], unknown]>), + [["settlement_identity", "effect_id"], "foreign-effect"], + [["settlement_identity", "schema_version"], "unknown"], + [["delivery_outcome"], "surface_only"], [["delivery_outcome"], "primary_goal_outcome"], + [["completion_todo_id"], identity.todo_id], [["agent_vision"], null], + [["agent_vision", "goal_id"], "foreign"], [["agent_vision", "agent_id"], "foreign"], + [["agent_vision", "vision_patch", "acceptance_summary"], ""], + [["agent_vision", "path_delta"], null], [["agent_vision", "path_delta", "evidence_refs"], []], + [["agent_vision", "path_delta", "outcome"], "stop"], [["agent_vision", "path_delta", "outcome"], "replan"], + [["agent_vision", "state"], null], [["agent_vision", "state"], undefined], [["agent_vision", "state"], ""], + [["vision_checkpoint", "agent_id"], "foreign"], [["vision_checkpoint", "satisfied"], false], + [["vision_checkpoint", "required"], false], [["vision_checkpoint", "decision"], "unchanged_with_reason"], + [["vision_checkpoint", "triggers"], []], [["vision_checkpoint", "delivery_boundary"], "in_flight_continuation"], + ...["vision_closed", "retired", "retired_or_superseded", "superseded", "no_followup", + "closed", "satisfied", "vision_satisfied", "vision_retired", "vision_superseded", + "vision_no_followup", "closed_no_followup", "no_follow_up", "invalid state"].map(state => + [["agent_vision", "state"], state] as [string[], unknown]), + ]; + for (const [path, value] of rejected) { + const candidate = changed(path, value), before = structuredClone(candidate); + assert.equal(qualifiesOpenTodoSemanticCheckpoint(candidate, identity), false, path.join(".") + "=" + value); + assert.equal(await dispatchEffectRuntimeMethod(handlers, "settlement.open_todo_semantic_checkpoint", + {identity, writeback: candidate}), false, path.join(".")); + assert.deepEqual(candidate, before); + } +}); + +test("the shared closed vocabulary preserves Python aliases and custom open states", () => { + const closed = ["vision_closed", "retired", "retired_or_superseded", "superseded", "no_followup", + "closed", "satisfied", "vision_satisfied", "vision_retired", "vision_superseded", + "vision_no_followup", "closed_no_followup", "no_follow_up"]; + const states = [...closed, "active", "vision_patch_proposed", "custom_open_state", "invalid state", null]; + const expected = [...closed.map(() => false), true, true, true, false, true]; + assert.deepEqual(states.map(goalVisionStateIsOpen), expected); + const python = spawnSync(resolveTestPython(), ["-c", + "import json,sys; from loopx.control_plane.goals.goal_vision_state import normalize_goal_vision_state,goal_vision_state_is_closed\n" + + "def opened(value):\n try: return not goal_vision_state_is_closed(normalize_goal_vision_state(value))\n except ValueError: return False\n" + + "print(json.dumps([opened(value) for value in json.load(sys.stdin)]))"], + {cwd: fileURLToPath(new URL("../../", import.meta.url)), input: JSON.stringify(states), encoding: "utf8"}); + assert.equal(python.status, 0, python.stderr); + assert.deepEqual(JSON.parse(python.stdout), expected); +}); diff --git a/tests/control_plane_ts/quota_settlement_plan.test.ts b/tests/control_plane_ts/quota_settlement_plan.test.ts index ddeaba33d7..9b13a9763d 100644 --- a/tests/control_plane_ts/quota_settlement_plan.test.ts +++ b/tests/control_plane_ts/quota_settlement_plan.test.ts @@ -53,6 +53,13 @@ test("ordinary completion is conditional on deliverable acceptance, not Turn acc assert.match(plan.steps[0].precondition, /--result-file requires approved Goal acceptance criteria bound to this Todo, not merely a Todo validator/); assert.match(plan.steps[0].precondition, /Todo done does not settle the Turn/); + assert.match(plan.steps[0].precondition, /unfinished Todo open with its original declaration/); + assert.doesNotMatch(plan.steps[0].precondition, /Prior-feedback|semantic_closeout/); + assert.match(plan.steps[1].precondition, /new evidence produced or obtained by this admitted work.*artifact\/input/); + assert.match(plan.steps[1].precondition, /Prior-feedback readback.*deterministic repetition without new information are context/); + assert.match(plan.steps[1].precondition, /not this Turn's progress; use them to advance the task first/); + assert.match(plan.steps[1].precondition, /Follow the remaining plan.*yield for freshly guarded continuation/); + assert.doesNotMatch(plan.steps[0].precondition, /spend before the next decision/); assert.match(plan.steps[1].precondition, /--agent-vision-json/); assert.match(plan.steps[1].precondition, /path_delta.*evidence_refs/); assert.match(plan.steps[1].precondition, /acceptance_summary/); @@ -93,11 +100,13 @@ test("in-flight validation offers completion only after original deliverable acc assert.equal(plan.steps[0].command_template, "ordinary completion"); assert.equal(plan.steps[0].command_condition, "todo_deliverable_complete"); assert.equal(plan.steps[0].conditional, undefined); - assert.match(plan.steps[0].precondition, /keep the Todo open with its original declaration/); - assert.match(plan.steps[0].precondition, /semantic_closeout.*same Turn; no new guard or binding/); + assert.match(plan.steps[0].precondition, /unfinished Todo open with its original declaration/); + assert.match(plan.steps[1].precondition, /same Turn; no new guard or binding/); assert.match(plan.steps[1].precondition, /^validation succeeded/); assert.match(plan.steps[1].precondition, /Route elimination needs evidence/); assert.match(plan.steps[1].precondition, /failure alone is not progress/); + assert.match(plan.steps[1].precondition, /semantic_closeout.*new decision evidence.*fresh Vision\/path_delta/i); + assert.match(plan.steps[1].precondition, /including continue\/no_change when retaining the route/); assert.match( plan.steps[1].precondition, /outcome_gap requires semantic_closeout.*blocker\/evidence IDs.*continuation checks/, @@ -108,6 +117,37 @@ test("in-flight validation offers completion only after original deliverable acc assert.equal(plan.steps[3].conditional, true); }); +test("semantic feedback checkpoint is an exact variant of the current writeback, not another plan", () => { + for (const delivery_boundary of [undefined, "in_flight_continuation"]) { + const before = turnScopedCliSettlementPlan({...input, delivery_boundary}); + const checkpoint = "same bound writeback --delivery-boundary semantic_closeout --agent-vision-json "; + const plan = turnScopedCliSettlementPlan({...input, delivery_boundary, + command_templates: {...input.command_templates, semantic_closeout: checkpoint}}); + assert.deepEqual(plan.identity, before.identity); + assert.equal(plan.steps.length, before.steps.length); + for (const i of [0, 2, 3]) assert.deepEqual(plan.steps[i], before.steps[i]); + assert.equal(plan.steps[1].command_template, before.steps[1].command_template); + assert.equal(plan.steps[1].semantic_closeout_command_template, checkpoint); + if (delivery_boundary === "in_flight_continuation") { + assert.match(plan.steps[1].precondition, + /^validation succeeded; Choose one: command_template for ordinary in_flight_continuation without new decision evidence; semantic_closeout_command_template for new decision evidence, route change or Todo completion/); + } + assert.deepEqual(plan.steps[1].vision_authoring, visionAuthoringContract()); + const serialized = settlementPlanPayload(plan).ordered_steps as Record[]; + assert.equal(serialized[1].semantic_closeout_command_template, checkpoint); + assert.match(String(serialized[2].precondition), /exact original-identity durable writeback receipt/); + assert.match(String(serialized[2].precondition), /that receipt qualifies.*fresh open-Todo semantic checkpoint/); + assert.match(String(serialized[2].precondition), /does not waive terminal completion validation/); + assert.match(String(serialized[2].precondition), /skip this step after typed_blocked_writeback_no_spend/); + assert.match(visionAuthoringContract().continuation_review as string, + /candidate usability alone is not completion.*full marginal cost/); + assert.match(visionAuthoringContract().continuation_review as string, + /useful authorized work.*preserve Stop, budget\/permission limits and genuine waits/); + } + assert.throws(() => turnScopedCliSettlementPlan({...input, + command_templates: {...input.command_templates, semantic_closeout: ""}}), /must be a non-empty string/); +}); + test("Todo-bound semantic planning shares its exact autonomous authoring contract", () => { const ordinary = turnScopedCliSettlementPlan(input); assert.deepEqual(turnScopedCliSettlementPlan({...input, autonomous_replan: false}), ordinary); diff --git a/tests/control_plane_ts/replan_semantics.test.ts b/tests/control_plane_ts/replan_semantics.test.ts index 93e3a4a55d..dc3f5b4369 100644 --- a/tests/control_plane_ts/replan_semantics.test.ts +++ b/tests/control_plane_ts/replan_semantics.test.ts @@ -39,6 +39,35 @@ test("open-work review guidance does not require configured checks or outcome de } }); +test("autonomous checkpoints reuse owner review while ordinary checkpoints keep continuation guidance", () => { + assert.equal(typeof visionAuthoringContract().continuation_review, "string"); + assert.equal(Object.hasOwn(visionAuthoringContract(true), "continuation_review"), false); + for (const kind of ["vision_acceptance_gap", "periodic_review_due", "external_progress_review_drift"]) { + for (const stride_strategy of ["off", "evidence_conditioned_v1"]) { + const projection = projectReplanSemantics({operation: "requirements", obligation: { + obligation_id: "replan-1111111111111111", triggers: [{kind}], replan_context: {stride_strategy}, + }}); + const authoring = (projection.writeback_contract as JsonObject).vision_authoring as JsonObject; + assert.deepEqual(authoring, visionAuthoringContract(true)); + const guidance = (projection.planning_guidance as string[]).join(" "); + for (const clause of ["original intent", "every requirement", "full marginal cost", + "Before no_followup, review unmet acceptance", "reasonable in-scope next step remains, continue or replan", + "evidence and judgment in existing Vision/path_delta", "budget or stops", "planning grant no authority", + "never forces a pivot", "do not invent work or consume budget merely to stay active"]) { + assert.ok(guidance.includes(clause), clause); + } + if (stride_strategy === "evidence_conditioned_v1") { + const review = (projection.stride_control as JsonObject).decision_value_review as JsonObject; + const advice = Object.values(review).join(" "); + for (const clause of ["feasible alternative", "complete experiment", "headroom", + "artifact version, input conditions and original budget", "budget, feedback and permission limits"]) { + assert.ok(advice.includes(clause), clause); + } + } + } + } +}); + test("a revalidated successor receipt settles only its exact original Turn guard", () => { const id = "replan-1111111111111111"; const delta = {schema_version: "replan_semantic_delta_v0", accepted: true, @@ -579,6 +608,10 @@ test(`${recovery} restores only its exact admitted review and still owes Vision` const packet = result.settlement_action_packet as JsonObject; assert.equal(packet.settlement_only, true); assert.equal(packet.semantic_delta, null); + const writeback = packet.writeback_contract as JsonObject; + assert.match(String(writeback.rule), /Reassess the original acceptance with evidence.*do not repeat the original work/); + assert.deepEqual(writeback.vision_authoring, visionAuthoringContract(true)); + assert.equal(Object.hasOwn(writeback.vision_authoring as JsonObject, "continuation_review"), false); assert.equal(projectReplanSemantics({operation: "qualify", obligation: result}).accepted, false); assert.equal(projectReplanSemantics({operation: "qualify", obligation: result, agent_vision: vision}).accepted, true); const historical = {...row, todo_id: "todo_historical", completion_turn_key: "historical-completion",