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
35 changes: 35 additions & 0 deletions docs/architecture/rfcs/monorepo-distribution-split-v0.md
Original file line number Diff line number Diff line change
Expand Up @@ -3809,3 +3809,38 @@ minimal dependency closure, M3/Standard/workspace/meta packaging and complete
cost acceptance remain open. This does not upgrade an installed host, start a
model service or establish effectiveness from protocol fixtures. Preserve all
nine original requirements, the original-Goal regression and earlier failures.

### 2026-11-13 — Successful work records a fresh checkpoint on the same route

The generic Host result boundary previously required a fresh Vision to use
`material_replan` and `replan_required`. A successful task retaining its route
could only reuse an unchanged reason, even when it had new outcome evidence.
An explicit `path_delta_mode: unchanged` now accepts the existing Vision packet
with `path_delta.outcome: continue` or `no_change`, without an unchanged reason.
The existing TypeScript Vision, task, validation and settlement owners still
decide durable effects. Material replans, stop decisions, malformed packets and
conflicting declarations retain their refusal. Omitted modes retain legacy
inference; generic and Codex guidance describe the same boundary.

One existing isolated inventory task used the current model's tools through a
stdio adapter and a Python 3.11 standalone core artifact on SQLite. Revised
inputs yielded quantities 22/10/5 and value 185.25, versus 172.25 previously.
Independent checks verified the 13.00 reconciliation, all six excluded rows,
reusable generator and preservation of all five original files. After the
normalized result was journaled, SIGKILL interrupted the driver and validator.
The original Turn resumed at validation without invoking the Host, completed
the Todo and spent once; completed replay added no Host, writeback or spend.
The initial invalid completion/replan reply and three resume setup refusals
remain failed attempts. There were two Host dispatches and one model work pass;
the rejected reply was not treated as confirmed work.

Source executor/driver checks and Codex/dsh regression pass. Installed File and
SQLite checks retain legacy completion and qualify both fresh checkpoint
outcomes plus replay; the initial oversized inline test command failed its
existing bound, then the fixture used an ordinary script path. No command
budget, authority, default or historical receipt was changed. This qualifies
the touched success/checkpoint and recovery path, not full V1–V5/V10 or R1.
Complete context retention, minimal closure, progressive product control,
comparable complete costs, M3/Standard/workspace delivery and the original-Goal
regression remain open. All nine original requirements and frozen failures
remain intact; no installed host or model service was upgraded.
27 changes: 27 additions & 0 deletions docs/architecture/rfcs/monorepo-distribution-split-v0.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -2970,3 +2970,30 @@ Core module 诊断核验实际导入的 distribution、记录中的 module,以
完整 R1/V1–V5/V10、真实模型恢复/采用、渐进产品控制、最小依赖闭包、
M3/Standard/workspace/meta 拆包与完整成本验收仍开放。本轮不升级已安装 Host、
启动模型服务或从协议夹具推断效果。原九项要求、原 Goal 回归及此前失败全部保留。

### 2026-11-13 — 成功工作在同一路线上记录新检查点

Generic Host result 边界此前要求新 Vision 使用 `material_replan` 与
`replan_required`。即使已有新成果证据,保留路线的成功任务也只能复用 unchanged
reason。现在显式 `path_delta_mode: unchanged` 可携带既有 Vision packet,
其 `path_delta.outcome` 必须为 `continue` 或 `no_change`,且不能同时声明
unchanged reason。既有 TypeScript Vision、任务、验证与结算 owner 仍决定持久
效果;material replan、停止决定、非法 packet 与矛盾声明继续拒绝。省略 mode
保持旧推断,generic 与 Codex 指导说明同一边界。

一个既有隔离库存任务通过 stdio adapter 使用当前模型工具,以及 Python 3.11
standalone core artifact 与 SQLite。更正输入得到数量 22/10/5、价值 185.25,
此前为 172.25。独立检查核验了 13.00 差额、全部六条排除记录、可复用生成器与
五份原文件保持不变。Normalized result 写入 journal 后,SIGKILL 中断 driver
与 validator。原 Turn 从 validation 恢复,没有再次调用 Host,完成 Todo 并
只结算一次;完成后的 replay 没有增加 Host、writeback 或 spend。
首次非法 completion/replan reply 与三次 resume setup 拒绝保留为失败。
实际有两次 Host dispatch、一次模型工作;被拒绝的 reply 未冒充已确认工作。

源码 executor/driver 检查与 Codex/dsh 回归通过。安装态 File、SQLite 检查保留
legacy completion,并核验两种新 checkpoint outcome 与 replay;首次过长的
inline 测试命令触发既有上限,夹具随后采用普通 script path。未改命令预算、
权限、默认行为或历史 receipt。本轮只资格化所触及的成功/检查点与恢复路径,
不关闭完整 V1–V5/V10 或 R1。完整 context retention、最小闭包、渐进产品控制、
可比完整成本、M3/Standard/workspace 交付与原 Goal 回归仍开放。原九项要求和
冻结失败全部保留;没有升级已安装 Host 或模型服务。
9 changes: 7 additions & 2 deletions docs/reference/protocols/loopx-turn-v0.md
Original file line number Diff line number Diff line change
Expand Up @@ -778,8 +778,13 @@ result unchanged: `path_delta_mode`, `agent_vision_json`, and
`path_delta_mode: "material_replan"`, `result_kind: "replan_required"`, and a
JSON-encoded `goal_vision_replan_contract_v0` packet whose
`goal_path_delta_v0.outcome` is `replan`; it must not also claim an unchanged
reason. An unchanged path uses `path_delta_mode: "unchanged"`, no vision
packet, and a bounded `vision_unchanged_reason`. The Turn executor remains the
reason. An unchanged path uses `path_delta_mode: "unchanged"` with either a
bounded `vision_unchanged_reason` to reuse the current checkpoint, or a fresh
vision packet whose `goal_path_delta_v0.outcome` is `continue` or `no_change`.
The fresh packet carries evidence for progress or completion while retaining
the route; it must not also declare `vision_unchanged_reason`. A fresh packet
requires an explicit path mode: omitting it retains the legacy inference of
`material_replan`. The Turn executor remains the
owner of packet parsing, bounds, and semantic validation. These declarations do
not grant write or Goal authority, and legacy hosts that omit both path-delta
fields retain their prior compatibility behavior. For material results, an
Expand Down
4 changes: 2 additions & 2 deletions loopx/control_plane/turn_driver/codex_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -396,8 +396,8 @@ def _render_prompt(request: Mapping[str, Any], *, checked: Mapping[str, Any] | N
"Do not write LoopX state, spend quota, or apply scheduler changes; the adapter owns those effects.",
"Return only the schema-constrained result. For validated_progress, repair_required, or replan_required, fill every material field with public-safe evidence.",
"For those material results, preserve the actual delivery_batch_scale and delivery_outcome selected from the signed execution_profile's work and outcome requirements. Return work_evidence and validation_evidence as separate non-empty arrays of short public-safe strings; name the artifact or observed work in the first and the independent command or readback in the second. For legacy requests without execution_profile, use null when evidence is not supplied. For stop results, set both evidence fields to null.",
"For those material results, set path_delta_mode=material_replan only when this Turn changes a prior assumption, route, scope, acceptance rule, or stops prior work; then provide a complete bounded agent vision packet with goal_path_delta_v0 in agent_vision_json and leave vision_unchanged_reason empty.",
"For routine continuation, retry, successor creation, or no-change replanning, set path_delta_mode=unchanged, leave agent_vision_json empty, and provide vision_unchanged_reason.",
"For those material results, set path_delta_mode=material_replan and result_kind=replan_required only when this Turn changes a prior assumption, route, scope, acceptance rule, or stops prior work; then provide a complete bounded agent vision packet with goal_path_delta_v0 in agent_vision_json and leave vision_unchanged_reason empty.",
"For routine continuation, retry, successor creation, or no-change replanning, explicitly set path_delta_mode=unchanged. Reuse the current checkpoint with vision_unchanged_reason and empty agent_vision_json, or record new progress/completion evidence in a fresh agent_vision_json packet with path_delta.outcome continue or no_change and empty vision_unchanged_reason.",
"When replan_action_packet requires a semantic delta, return progress_observation_json as a JSON-encoded typed_progress_observation_v0 with result_class, the observed surface_id/hypothesis_id/probe_kind or blocker/coverage fields, and opaque evidence_ids. Report only independently checked evidence; a plan summary or a vision change alone cannot substitute for the required observation. Otherwise leave this field empty.",
"For user_action_required, wait, or iteration_failed, leave material-only fields empty and explain the stop in summary. iteration_failed ends only this iteration and never requests a retry or successor.",
'completed_phases must be exactly ["host_execute","typed_result"], and turn_key must match the request.',
Expand Down
12 changes: 10 additions & 2 deletions loopx/control_plane/turn_driver/executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -365,8 +365,16 @@ def _normalize_host_path_delta(
errors.append("material_replan cannot also declare vision_unchanged_reason")
elif path_delta_mode == "unchanged":
if agent_vision is not None:
errors.append("unchanged path_delta_mode cannot include agent_vision_json")
if not unchanged_reason:
path_delta = agent_vision.get("path_delta")
if not isinstance(path_delta, dict):
errors.append("unchanged agent_vision_json requires goal_path_delta_v0")
elif path_delta.get("outcome") not in {"continue", "no_change"}:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] 声明路径不变的完成结果可以改写已有验收条件。 先为同一 Agent 保存要求独立核对八个值的 Vision。让 Host 交付正确产物并返回 validated_completion,同时用 unchanged 与 continue 或 no_change 把验收改为只看第一个值。 候选通过独立产物检查,结束 Todo、结算一次并写入新的 role_scope 和 acceptance_summary。后续读取把缩小后的条件作为最新 Vision。File 和 SQLite 均复现。 让现有 TypeScript Vision owner 对照持久化的上一份 Vision 校验 fresh unchanged,并在 Todo completion、Vision 写回和 Spend 前拒绝 durable 字段变更。 在 File/SQLite 先写入完整旧 Vision,再跑合法 fresh checkpoint、仅旧理由、continue 和 no_change 各自改字段的对照。后两种应保留原 Todo 与 Vision、零 Spend,同时保留原次 Turn 的拒绝和恢复证据。

errors.append(
"unchanged goal_path_delta_v0 outcome must be continue or no_change"
)
if unchanged_reason:
errors.append("fresh agent_vision cannot also declare vision_unchanged_reason")
elif not unchanged_reason:
errors.append("unchanged path_delta_mode requires vision_unchanged_reason")

return path_delta_mode, agent_vision
Expand Down
7 changes: 5 additions & 2 deletions loopx/control_plane/turn_driver/host_candidate.py
Original file line number Diff line number Diff line change
Expand Up @@ -441,8 +441,11 @@ def render_prompt(authority: Mapping[str, Any], *, allow_completion: bool = Fals
"result_kind to replan_required, and agent_vision_json to a JSON-encoded "
"bounded goal_vision_replan_contract_v0 packet whose path_delta is "
"goal_path_delta_v0 with outcome replan; leave vision_unchanged_reason "
"empty. For unchanged paths, set path_delta_mode to unchanged, omit "
"agent_vision_json, and provide vision_unchanged_reason. These fields "
"empty. For unchanged paths, explicitly set path_delta_mode to unchanged. "
"Reuse the current checkpoint with vision_unchanged_reason and no "
"agent_vision_json, or record new progress/completion evidence in a fresh "
"agent_vision_json packet with path_delta.outcome continue or no_change "
"and no vision_unchanged_reason. These fields "
"describe the result only; they grant no authority. For stop results "
"(wait, user_action_required, iteration_failed), omit both "
"path_delta_mode and agent_vision_json.\n"
Expand Down
28 changes: 27 additions & 1 deletion tests/test_core_distribution.py
Original file line number Diff line number Diff line change
Expand Up @@ -460,8 +460,29 @@ def _assert_committed(core_python, workspace, runtime, result):
return result["resume_turn_key"]


def test_installed_core_executes_checks_and_replays_once(core_python, task):
@pytest.mark.parametrize("checkpoint", [None, "continue", "no_change"])
def test_installed_core_executes_checks_and_replays_once(core_python, task, checkpoint):
workspace, runtime, registry, arguments = task
if checkpoint is not None:
vision = {
"schema_version": "goal_vision_replan_contract_v0",
"state": "active",
"vision_patch": {"acceptance_summary": "Independently check squares and preserve exact replay."},
"path_delta": {
"schema_version": "goal_path_delta_v0", "outcome": checkpoint,
"prior_assumption": "The requested squares still needed a result check.",
"observed_reality": "The requested artifact is available for independent validation.",
"retained": ["The original squares request and acceptance."],
"evidence_refs": ["artifact:squares.json"],
},
}
host_index = arguments.index("--host-adapter-command-json") + 1
host_script = workspace / "checkpoint-host.py"
host_script.write_text(HOST_SOURCE.replace(
"'vision_unchanged_reason':'The requested squares remain the objective.'",
f"'path_delta_mode':'unchanged','agent_vision_json':{json.dumps(vision)!r}",
))
arguments[host_index] = json.dumps([core_python, "-I", str(host_script)])
history = json.loads(_run(core_python, ["--registry", str(registry), "--runtime-root", str(runtime),
"--format", "json", "history", "trajectory-hygiene", "--goal-id", GOAL], workspace).stdout)
assert history["ok"] is True
Expand All @@ -476,6 +497,11 @@ def test_installed_core_executes_checks_and_replays_once(core_python, task):
"--format", "json", "turn", "inspect-journal", "--goal-id", GOAL,
"--agent-id", AGENT, "--turn-key", key], workspace).stdout)
assert inspected["ok"] is True
if checkpoint is not None:
journal_path = runtime / "goals" / GOAL / "turns" / f"{key.removeprefix('sha256:')}.json"
journal = json.loads(journal_path.read_text())
assert journal["host_result"]["result_kind"] == "validated_completion"
assert journal["host_result"]["agent_vision"]["path_delta"]["outcome"] == checkpoint


@pytest.mark.skipif(os.name == "nt", reason="this qualification injects a POSIX SIGKILL")
Expand Down
109 changes: 107 additions & 2 deletions tests/test_loopx_turn_executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -741,6 +741,100 @@ def test_new_material_host_result_requires_qualifying_settlement(kind: str, outc
assert validation["result"]["delivery_outcome"] == outcome


def _continued_vision(*, outcome: str = "continue") -> dict[str, object]:
return {
"schema_version": "goal_vision_replan_contract_v0",
"state": "active",
"vision_patch": {
"acceptance_summary": "Independently validate the fixture result and recovery.",
},
"path_delta": {
"schema_version": "goal_path_delta_v0",
"outcome": outcome,
"prior_assumption": "The fixture result had not been independently checked.",
"observed_reality": "The fixture produced new checkable result evidence.",
"retained": ["Keep the original fixture acceptance and execution route."],
"evidence_refs": ["fixture:result-checkpoint"],
},
}


@pytest.mark.parametrize("kind", ["validated_progress", "validated_completion"])
@pytest.mark.parametrize("outcome", ["continue", "no_change"])
@pytest.mark.parametrize("from_journal", [False, True])
def test_unchanged_route_can_record_fresh_result_checkpoint(
kind: str, outcome: str, from_journal: bool,
) -> None:
plan = _plan()
result = _host_result(plan, kind=kind)
result.pop("vision_unchanged_reason")
result["path_delta_mode"] = "unchanged"
packet = _continued_vision(outcome=outcome)
result["agent_vision" if from_journal else "agent_vision_json"] = (
packet if from_journal else json.dumps(packet)
)

validation = validate_loopx_turn_host_result(
plan, result, from_journal=from_journal, completion_lifecycle_configured=True,
)

assert validation["ok"] is True, validation["errors"]
assert validation["result"]["result_kind"] == kind
assert validation["result"]["agent_vision"]["path_delta"] == packet["path_delta"]


@pytest.mark.parametrize("outcome", ["replan", "wait", "ask_human", "stop"])
@pytest.mark.parametrize("from_journal", [False, True])
def test_unchanged_checkpoint_cannot_hide_route_or_stop_decisions(
outcome: str, from_journal: bool,
) -> None:
plan = _plan()
result = _host_result(plan)
result.pop("vision_unchanged_reason")
packet = _continued_vision(outcome=outcome)
result.update(path_delta_mode="unchanged")
result["agent_vision" if from_journal else "agent_vision_json"] = (
packet if from_journal else json.dumps(packet)
)

validation = validate_loopx_turn_host_result(plan, result, from_journal=from_journal)

assert validation["ok"] is False
assert "outcome must be continue or no_change" in " ".join(validation["errors"])


@pytest.mark.parametrize("defect, expected_error", [
("unchanged_reason", "fresh agent_vision cannot also declare"),
("missing_path_delta", "unchanged agent_vision_json requires goal_path_delta_v0"),
("implicit_mode", "outcome must be replan"),
("completion_replan", "requires result_kind replan_required"),
])
def test_fresh_checkpoint_preserves_explicit_and_legacy_boundaries(
defect: str, expected_error: str,
) -> None:
plan = _plan()
result = _host_result(plan, kind="validated_completion")
packet = _continued_vision()
result["path_delta_mode"] = "unchanged"
if defect != "unchanged_reason":
result.pop("vision_unchanged_reason")
if defect == "missing_path_delta":
packet.pop("path_delta")
elif defect == "implicit_mode":
result.pop("path_delta_mode")
elif defect == "completion_replan":
result["path_delta_mode"] = "material_replan"
packet = _continued_vision(outcome="replan")
result["agent_vision_json"] = json.dumps(packet)

validation = validate_loopx_turn_host_result(
plan, result, completion_lifecycle_configured=True,
)

assert validation["ok"] is False
assert expected_error in " ".join(validation["errors"])


@pytest.mark.parametrize("packet, expected_error", [
(None, "cached agent_vision must be a JSON object"),
([], "cached agent_vision must be a JSON object"),
Expand Down Expand Up @@ -3458,16 +3552,21 @@ def invoke(*_args: object, **_kwargs: object) -> dict[str, object]:
assert all(count == 0 for count in calls.values())


@pytest.mark.parametrize("fresh_checkpoint", [False, True])
def test_run_once_recovers_saved_host_result_from_validation_without_host_retry(
tmp_path: Path,
tmp_path: Path, fresh_checkpoint: bool,
) -> None:
plan = _plan()
calls = {"host": 0, "validation": 0, "writeback": 0, "spend": 0, "scheduler": 0}
writeback, spend, scheduler = _callbacks(calls)

def host(_request: dict[str, object]) -> dict[str, object]:
calls["host"] += 1
return _host_result(plan)
result = _host_result(plan)
if fresh_checkpoint:
result.pop("vision_unchanged_reason")
result.update(path_delta_mode="unchanged", agent_vision_json=json.dumps(_continued_vision()))
return result

def interrupted_validation(
_plan: dict[str, object],
Expand Down Expand Up @@ -3506,6 +3605,12 @@ def interrupted_validation(
assert inspected["recovery_decision"]["can_continue"] is True
assert inspected["recovery_decision"]["resume_from"] == "validation"
assert inspected["recovery_decision"]["reinvoke_host"] is False
if fresh_checkpoint:
saved = json.loads(turn_journal_path(
tmp_path / "runtime", goal_id="fixture-goal",
turn_key=str(transaction["turn_key"]),
).read_text())
assert saved["host_result"]["agent_vision"]["path_delta"]["outcome"] == "continue"

recovered = run_loopx_turn_once(
plan,
Expand Down
Loading