From 2668acd369906597a5b9f5b7f38699bba65f86ef Mon Sep 17 00:00:00 2001 From: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> Date: Fri, 30 Oct 2026 00:31:54 +0800 Subject: [PATCH 1/8] perf(context): reuse full recovery for native presentation Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> --- ...controller-executor-unified-contract-v0.md | 8 ++++++ ...ller-executor-unified-contract-v0.zh-CN.md | 6 ++++ loopx/cli_commands/quota_capture.py | 14 ++++++---- .../test_progressive_native_context.py | 23 +++++++++++++++ .../test_saved_context_project.py | 28 +++++++++++-------- 5 files changed, 63 insertions(+), 16 deletions(-) diff --git a/docs/architecture/rfcs/controller-executor-unified-contract-v0.md b/docs/architecture/rfcs/controller-executor-unified-contract-v0.md index 958b5b175f..46bfd94a35 100644 --- a/docs/architecture/rfcs/controller-executor-unified-contract-v0.md +++ b/docs/architecture/rfcs/controller-executor-unified-contract-v0.md @@ -130,6 +130,14 @@ the existing context-delivery owner. C1 must converge the remaining composition and consumer assumptions, rather than introduce another capture service or rewrite already shared delta/native-resume logic. +A separate native-presentation slice reuses the committed full recovery as its +reader target, avoiding an unused second wire publication. Delta and unwrapped +inputs retain their own immutable files and the same reader/lifecycle owners. +On a matched 107,289-byte synthetic source, capture writes fall from 216,754 to +107,289 bytes; full reader bytes and the 3,937-byte warm delta are unchanged. +This qualifies only duplicate capture IO removal, not model-input reduction, +complete cost, accepted outcomes or C1 writer/consumer cutover. + | Current source / obligation | Present envelope treatment | Required C1 disposition | | --- | --- | --- | | `interaction_contract.agent_channel.work_context` and pending required reads | `work_context` plus `required_reads`; identical inline material can be referenced | One complete requirement/evidence source with resolvable references; preserve pending reads and their completion conditions | diff --git a/docs/architecture/rfcs/controller-executor-unified-contract-v0.zh-CN.md b/docs/architecture/rfcs/controller-executor-unified-contract-v0.zh-CN.md index 7c5c9d20ae..25ed1bc25a 100644 --- a/docs/architecture/rfcs/controller-executor-unified-contract-v0.zh-CN.md +++ b/docs/architecture/rfcs/controller-executor-unified-contract-v0.zh-CN.md @@ -104,6 +104,12 @@ Quota capture 与 Turn journal capture 都调用 context-delivery owner。C1 应收敛剩余的组合逻辑与消费者假设,不另建 capture service, 也不重写已经共享的差分与 native-resume 逻辑。 +独立的原生展示切片复用已提交的完整恢复原件作为 reader 目标,避免再次发布未使用的 +wire 副本。Delta 与未包装输入仍保留各自的不可变文件,reader/lifecycle owner 不变。 +同一 107,289 字节合成来源的 capture 写入从 216,754 降为 107,289 字节;完整 reader +字节及 3,937 字节的 warm delta 均不变。这只验证重复 capture IO 的消除,不证明模型 +输入减少、完整成本收益、成果验收或 C1 writer/consumer 切换。 + | 当前来源 / 义务 | 当前 envelope 处理 | C1 必须保留的归属与语义 | | --- | --- | --- | | `interaction_contract.agent_channel.work_context` 与未完成必读 | `work_context` 加 `required_reads`;相同内联材料可改用引用 | 一份完整要求/证据来源与可解析引用;保留未完成必读及其完成条件 | diff --git a/loopx/cli_commands/quota_capture.py b/loopx/cli_commands/quota_capture.py index d82a5896e8..71ea9602d8 100644 --- a/loopx/cli_commands/quota_capture.py +++ b/loopx/cli_commands/quota_capture.py @@ -209,21 +209,25 @@ def native_context_capture_view(projected: dict[str, Any], directory: Path, *, max_page_output_bytes: int | None = None) -> dict[str, Any]: """Publish file references for an already-decided native capture. - Persist the complete wire and expose navigation without eagerly importing - content into model history. Reading never collects or admits a decision. + Reuse full recovery; persist delta or unwrapped wire before exposing it. + Reading never collects or admits a decision. """ from ..control_plane.turn_driver.decision_capture import write_decision_capture if max_page_output_bytes is not None and max_page_output_bytes <= 0: raise QuotaDecisionTransportError("Native context output limit must be positive.") - wire = directory / "delivery.json" - write_decision_capture(wire, projected) - reference = {"path": str(wire), "sha256": hashlib.sha256(wire.read_bytes()).hexdigest()} + reference = None mode, target = "full", None if projected.get("schema_version") == "context_delivery_v1": mode, target = projected["mode"], projected["target"] if mode == "full": reference = {key: projected["recovery"][key] for key in ("path", "sha256")} + if reference is None: + # Full delivery already reads its committed recovery. Delta and + # unwrapped inputs still need their own immutable reader target. + wire = directory / "delivery.json" + write_decision_capture(wire, projected) + reference = {"path": str(wire), "sha256": hashlib.sha256(wire.read_bytes()).hexdigest()} page_bytes, output_bytes = native_context_page_budget() if max_page_output_bytes is not None: output_bytes = min(output_bytes, max_page_output_bytes) diff --git a/tests/control_plane/test_progressive_native_context.py b/tests/control_plane/test_progressive_native_context.py index a605e10323..67ddd16f87 100644 --- a/tests/control_plane/test_progressive_native_context.py +++ b/tests/control_plane/test_progressive_native_context.py @@ -33,6 +33,29 @@ def outcome(document): return document["planning_horizon"]["core_goal"]["acceptance_contract"]["outcome_requirements"]["outcomes"][0] +def test_full_native_reference_reuses_the_committed_recovery(tmp_path, monkeypatch): + from loopx.cli_commands.quota_capture import native_context_capture_view + from loopx.control_plane.turn_driver import decision_capture + + setup(tmp_path, monkeypatch) + original, full = project(tmp_path, payload=payload()) + recovery = full["recovery"] + saved = Path(recovery["path"]).read_bytes() + + def unexpected_publication(*args, **kwargs): + pytest.fail("Full presentation must reuse its already committed recovery") + + monkeypatch.setattr(decision_capture, "write_decision_capture", unexpected_publication) + view = native_context_capture_view(full, Path(recovery["path"]).parent, + context_directory=str(tmp_path)) + assert (view["path"], view["sha256"]) == (recovery["path"], recovery["sha256"]) + assert Path(view["path"]).read_bytes() == saved + pages = read_pages(tmp_path, full) + assert json.loads("".join(page["content"] for page in pages)) == full["context"] + details = full["context"]["context_details"] + assert json.loads(Path(details["path"]).read_bytes()) == original + + def test_cold_warm_and_repeated_compaction_keep_requirements_without_replaying_material(tmp_path, monkeypatch): event = setup(tmp_path, monkeypatch) original = payload() diff --git a/tests/control_plane/test_saved_context_project.py b/tests/control_plane/test_saved_context_project.py index 8fc3358b22..1a5c1d35e3 100644 --- a/tests/control_plane/test_saved_context_project.py +++ b/tests/control_plane/test_saved_context_project.py @@ -575,10 +575,12 @@ def response(request, number, message): assert descriptor["ok"] and descriptor["mode"] == ("full" if name == "cold" else "delta") if benchmark: assert len(json.dumps(descriptor, ensure_ascii=False, separators=(",", ":")).encode()) + 1 <= 32000 - wire = json.loads((Path(descriptor["path"]).parent / "delivery.json").read_text()) - assert descriptor["target"] == wire["target"] - observed[name] = (wire, descriptor) data = Path(descriptor["path"]).read_bytes() + assert hashlib.sha256(data).hexdigest() == descriptor["sha256"] + published = json.loads(data) + if descriptor["mode"] == "delta": + assert descriptor["target"] == published["target"] + observed[name] = (published, descriptor) # The existing page protocol also admits the projector's first # page. Old installed projectors still start with command. offset = descriptor["next_offset"] if "content" in descriptor else 0 @@ -618,7 +620,7 @@ def response(request, number, message): if benchmark and name in ("cold", "warm"): # Consume the command actually delivered to the native # worker, rather than manually supplying missing flags. - presented = json.loads(Path(observed[name][0]["recovery"]["path"]).read_text()) + presented = json.loads(Path(descriptor["recovery"]["path"]).read_text()) selection = presented["interaction_contract"]["cli_channel"]["selection_command"] command = selection["route_prefix"] + " " + selection["command_args_template"].replace("{todo_id}", TODO_ID) argv = shlex.split(command) @@ -705,12 +707,16 @@ def response(request, number, message): assert _heartbeat_receipt_count(runtime, "saved-native-turn") == receipts and _spend_run_count(runtime) == 0 cold = observed["cold"][0] baseline = None - for wire, _ in observed.values(): - projected = json.loads(Path(wire["recovery"]["path"]).read_text()) + for published, descriptor in observed.values(): + projected = json.loads(Path(descriptor["recovery"]["path"]).read_text()) current = prepare_context_delivery(projected, - scope={key: wire["target"][key] for key in - ("goal_id", "agent_id", "session_id", "context_epoch", "continuity_generation") if key in wire["target"]}, - capture_id=wire["target"]["capture_id"], surface="quota_packet") + scope={key: descriptor["target"][key] for key in + ("goal_id", "agent_id", "session_id", "context_epoch", "continuity_generation") if key in descriptor["target"]}, + capture_id=descriptor["target"]["capture_id"], surface="quota_packet") + # Full presentation publishes the original document; only deltas have + # a separate wire target. Both retain the same typed recovery check. + wire = current if descriptor["mode"] == "full" else published + assert current["target"] == descriptor["target"] restored = restore_context_delivery(wire, baseline=baseline, current=current) assert restored["context"] == projected if wire["mode"] == "delta": @@ -720,7 +726,7 @@ def response(request, number, message): baseline = {**current, "ancestry": 0 if wire.get("continuity_basis") == "captured_file" else wire["ancestry"]} if benchmark: for name, (_, raw) in zip(("cold", "warm"), sources): - presentation = json.loads(Path(observed[name][0]["recovery"]["path"]).read_text()) + presentation = json.loads(Path(observed[name][1]["recovery"]["path"]).read_text()) original = json.loads(raw) selected = presentation["interaction_contract"]["cli_channel"]["selection_command"] original_command = original["interaction_contract"]["cli_channel"]["selection_command"]["command_args_template"] @@ -749,7 +755,7 @@ def response(request, number, message): presentation.pop("context_reference_instruction", None) assert presentation == original # only presentation transport differs else: - assert cold["context"] == json.loads(sources[0][1]) and projected == json.loads(sources[1][1]) + assert cold == json.loads(sources[0][1]) and projected == json.loads(sources[1][1]) assert all(path.read_bytes() == raw for path, raw in sources) From 77765d79e74ba5ecde078d5123eb578e00513c67 Mon Sep 17 00:00:00 2001 From: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> Date: Fri, 30 Oct 2026 01:11:16 +0800 Subject: [PATCH 2/8] fix(context): keep small native references discoverable Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> --- .../rfcs/controller-executor-unified-contract-v0.md | 5 +++++ .../controller-executor-unified-contract-v0.zh-CN.md | 4 ++++ docs/reference/required-work-context.md | 7 ++++++- loopx/cli_commands/quota_capture.py | 5 ++++- tests/control_plane/test_progressive_native_context.py | 10 ++++++++++ 5 files changed, 29 insertions(+), 2 deletions(-) diff --git a/docs/architecture/rfcs/controller-executor-unified-contract-v0.md b/docs/architecture/rfcs/controller-executor-unified-contract-v0.md index 3cb44e6c6b..65c53a3c29 100644 --- a/docs/architecture/rfcs/controller-executor-unified-contract-v0.md +++ b/docs/architecture/rfcs/controller-executor-unified-contract-v0.md @@ -137,6 +137,11 @@ On a matched 107,289-byte synthetic source, capture writes fall from 216,754 to 107,289 bytes; full reader bytes and the 3,937-byte warm delta are unchanged. This qualifies only duplicate capture IO removal, not model-input reduction, complete cost, accepted outcomes or C1 writer/consumer cutover. +For a small explicit output ceiling, native presentation can also omit repeated +directory/selection commands while retaining the complete recovery command: +append `--list` or `--select` to discover/read current facts without a baseline. +The original 4,000-byte cold/warm/compaction cases retain their requirements, +Stop, original identity and full recovery; this is transport qualification. | Current source / obligation | Present envelope treatment | Required C1 disposition | | --- | --- | --- | diff --git a/docs/architecture/rfcs/controller-executor-unified-contract-v0.zh-CN.md b/docs/architecture/rfcs/controller-executor-unified-contract-v0.zh-CN.md index f3481fdc58..90cbeed2e8 100644 --- a/docs/architecture/rfcs/controller-executor-unified-contract-v0.zh-CN.md +++ b/docs/architecture/rfcs/controller-executor-unified-contract-v0.zh-CN.md @@ -109,6 +109,10 @@ wire 副本。Delta 与未包装输入仍保留各自的不可变文件,reader 同一 107,289 字节合成来源的 capture 写入从 216,754 降为 107,289 字节;完整 reader 字节及 3,937 字节的 warm delta 均不变。这只验证重复 capture IO 的消除,不证明模型 输入减少、完整成本收益、成果验收或 C1 writer/consumer 切换。 +显式小预算下,原生展示还可省略重复的目录/选读快捷命令,保留完整恢复命令; +在其后追加 `--list` 或 `--select`,无须基线即可发现或读取当前事实。 +原有 4,000 字节冷态、温态及压缩恢复用例保留完整要求、Stop、原身份和完整恢复; +这属于传输验证。 | 当前来源 / 义务 | 当前 envelope 处理 | C1 必须保留的归属与语义 | | --- | --- | --- | diff --git a/docs/reference/required-work-context.md b/docs/reference/required-work-context.md index ffc05aaaea..1544a0c430 100644 --- a/docs/reference/required-work-context.md +++ b/docs/reference/required-work-context.md @@ -438,6 +438,9 @@ capture and return a small file reference, not an eager first page. The response names the delta/full `path`, digest and reader `command`; `recovery` names the current complete file. A file delta additionally carries `baseline_reference`. `index_command` lists current fields and `select_command` batches exact pointers. +Under a smaller explicit ceiling, these shortcut commands may be omitted; +append `--list` or `--select ` to `recovery.command` instead. Both +operations address the current complete capture, including during delta delivery. The reference stays below 7900 serialized bytes (or the explicit smaller output ceiling); if metadata cannot fit, recovery retains the current capture. Reader pages remain independently bounded. A complete raw tool log is not proof that @@ -724,7 +727,9 @@ SSH Goal 使用 `--runtime-profile codex_app_ssh_goal` 生成 context-config 和 不内联第一页。`path`、摘要与 `command` 指向当前差分或全量;`recovery` 指向当前 完整文件;文件差分另带 `baseline_reference`。`index_command` 展示当前字段目录, `select_command` 批量选读精确路径。引用输出不超过 7900 序列化字节或调用者显式更小 -的上限;元数据放不下时保留当前捕获恢复入口。读取页另受输出预算约束。 +的上限;较小预算下可省略这两个快捷命令,改在 `recovery.command` 后追加 +`--list` 或 `--select `,差分投递时也直接读取当前完整捕获。 +元数据仍放不下时保留当前捕获恢复入口。读取页另受输出预算约束。 原始工具日志完整,不证明宿主保留给模型的字节也完整。 原生 `captured_file` 基线来自校验后的前一完整文件,可差分复用所有未变字段,包括 diff --git a/loopx/cli_commands/quota_capture.py b/loopx/cli_commands/quota_capture.py index 71ea9602d8..57a6880f8b 100644 --- a/loopx/cli_commands/quota_capture.py +++ b/loopx/cli_commands/quota_capture.py @@ -269,7 +269,7 @@ def native_context_capture_view(projected: dict[str, Any], directory: Path, *, result["instruction"] = CONTEXT_CAPTURE_NAVIGATION_GUIDANCE if mode != "full": result["instruction"] += (" command reads the delta; baseline_reference identifies its exact saved base when file-backed. " - "index_command/select_command read current facts directly without that baseline.") + "recovery.command reads current facts directly without that baseline.") # The reference must fit even under an explicit small caller ceiling. def fits(): return len(json.dumps(result, ensure_ascii=False, separators=(",", ":")).encode()) + 1 <= min(output_bytes, 7900) @@ -279,6 +279,9 @@ def fits(): result.pop("select_command") # --select also works on the current recovery command. if not fits() and "baseline_reference" in result: result["baseline_reference"].pop("command", None) # Exact path/hash remain usable. + if not fits(): + result.pop("index_command") + result["instruction"] += " Append --list or --select to recovery.command to discover or read current fields." if not fits(): raise QuotaDecisionTransportError("Native context reference exceeds the output limit; " "read the saved original decision and receipt without rerunning admission.") diff --git a/tests/control_plane/test_progressive_native_context.py b/tests/control_plane/test_progressive_native_context.py index 67ddd16f87..0112c05932 100644 --- a/tests/control_plane/test_progressive_native_context.py +++ b/tests/control_plane/test_progressive_native_context.py @@ -147,7 +147,17 @@ def check_recovery(view): key: journal[key] for key in ("path", "sha256")} argv = shlex.split(view["recovery"]["command"]) if output_limit is not None: + assert len((json.dumps(view, ensure_ascii=False, separators=(",", ":")) + "\n").encode()) <= output_limit assert int(argv[argv.index("--max-page-output-bytes") + 1]) <= output_limit + # Discovery and selection remain usable from the complete current + # recovery, independently of optional shortcut command fields. + code, index = _run_cli(registry, runtime, *argv[argv.index("quota"):], "--list", cwd=tmp_path) + assert code == 0 and index["ok"], index + fields = json.loads(index["content"])["selections"][0]["fields"] + assert {"/stop", "/heartbeat_receipt"} <= {field["pointer"] for field in fields} + code, selected = _run_cli(registry, runtime, *argv[argv.index("quota"):], "--select", "/stop", cwd=tmp_path) + assert code == 0 and selected["ok"], selected + assert json.loads(selected["content"])["selections"][0]["value"] == original["stop"] pages, combined = [], b"" while True: code, recovered = _run_cli(registry, runtime, *argv[argv.index("quota"):], cwd=tmp_path) From f327d9d799080ce9f76c2097a8fd359334bd8ce1 Mon Sep 17 00:00:00 2001 From: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> Date: Fri, 30 Oct 2026 01:20:42 +0800 Subject: [PATCH 3/8] test(cli): follow the quota presentation owner Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> --- examples/cli-control-plane-command-modularization-smoke.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/examples/cli-control-plane-command-modularization-smoke.py b/examples/cli-control-plane-command-modularization-smoke.py index c750d30bde..31cd0cb91a 100644 --- a/examples/cli-control-plane-command-modularization-smoke.py +++ b/examples/cli-control-plane-command-modularization-smoke.py @@ -16,6 +16,7 @@ INIT_PATH = REPO_ROOT / "loopx" / "cli_commands" / "__init__.py" TODO_MODULE = REPO_ROOT / "loopx" / "cli_commands" / "todo.py" QUOTA_MODULE = REPO_ROOT / "loopx" / "cli_commands" / "quota.py" +QUOTA_PRESENTATION_MODULE = REPO_ROOT / "loopx" / "cli_commands" / "quota_presentation.py" def run_cli(*args: str) -> subprocess.CompletedProcess[str]: @@ -41,6 +42,7 @@ def assert_source_shape() -> None: init_text = INIT_PATH.read_text(encoding="utf-8") todo_text = TODO_MODULE.read_text(encoding="utf-8") quota_text = QUOTA_MODULE.read_text(encoding="utf-8") + quota_presentation_text = QUOTA_PRESENTATION_MODULE.read_text(encoding="utf-8") assert_contains(init_text, "register_todo_command", "__init__") assert_contains(init_text, "handle_todo_command", "__init__") @@ -61,7 +63,8 @@ def assert_source_shape() -> None: assert_contains(runtime_text, "handle_quota_command(", "cli_runtime.py") assert_contains(todo_text, "render_todo_markdown", "todo module") assert_contains(todo_text, "append_cli_rollout_event", "todo module") - assert_contains(quota_text, "render_quota_should_run_markdown", "quota module") + assert_contains(quota_text, "from .quota_presentation import", "quota module") + assert_contains(quota_presentation_text, "render_quota_should_run_markdown", "quota presentation") assert_contains(quota_text, "append_cli_rollout_event", "quota module") From 463074478d50fd5968d5a701fb4079b63b84bc69 Mon Sep 17 00:00:00 2001 From: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> Date: Fri, 30 Oct 2026 03:46:53 +0800 Subject: [PATCH 4/8] perf(semantics): reuse parsed field presence for production scans Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> --- loopx/semantics/python_production.py | 40 +++++++++++++------ .../architecture/test_semantic_production.py | 35 ++++++++++++++++ 2 files changed, 63 insertions(+), 12 deletions(-) diff --git a/loopx/semantics/python_production.py b/loopx/semantics/python_production.py index 7ddeb26b5d..9f5dab3466 100644 --- a/loopx/semantics/python_production.py +++ b/loopx/semantics/python_production.py @@ -9,6 +9,7 @@ import ast from collections import Counter from dataclasses import dataclass +from functools import lru_cache from types import SimpleNamespace from typing import Mapping, TypeVar @@ -137,20 +138,29 @@ def enum_members(source: SourceFile, symbol: str, *, strict: bool = False) -> di return result +@lru_cache(maxsize=4096) +def _field_mentions(source: SourceFile) -> frozenset[str]: + """Reuse parsed names, not another retained AST or a source-text filter.""" + names: set[str] = set() + for node in ast.walk(_parsed(source)): + if isinstance(node, ast.Name): + names.add(node.id) + elif isinstance(node, ast.Attribute): + names.add(node.attr) + elif isinstance(node, ast.Constant) and isinstance(node.value, str): + names.add(node.value) + elif isinstance(node, ast.keyword) and node.arg is not None: + names.add(node.arg) + return frozenset(names) + + def python_literal_uses(source: SourceFile, field: str) -> set[str]: """Literal writes and direct dispatch operands; not alias/data-flow proof.""" - tree = _parsed(source) - nodes = list(ast.walk(tree)) - # Inspect parsed names/keys, including escaped strings and normalized - # identifiers. Parse errors must still fail even when the field is absent. - if not any( - isinstance(node, ast.Name) and node.id == field - or isinstance(node, ast.Attribute) and node.attr == field - or isinstance(node, ast.Constant) and node.value == field - or isinstance(node, ast.keyword) and node.arg == field - for node in nodes - ): + # Parsing precedes absence: escaped keys and normalized identifiers count, + # and invalid syntax must fail even without a textual field mention. + if field not in _field_mentions(source): return set() + tree = _parsed(source) def reads(node: ast.AST) -> bool: if isinstance(node, ast.Name): @@ -188,7 +198,7 @@ def literals(node: ast.AST) -> set[str]: # conditions and does not inspect strings containing sample source text. rows = scan_python_production(source, field=field, enums={}) result = set().union(*(row.values for row in rows)) - for node in nodes: + for node in ast.walk(tree): if isinstance(node, ast.Compare): for left, operator, right in zip( [node.left, *node.comparators[:-1]], node.ops, node.comparators, strict=True, @@ -407,6 +417,12 @@ def scan_python_production( call_arguments = call_arguments or {} calls = _qualified_bindings(source, tree, call_arguments, modules) return_paths = return_paths or {} + # Every output form needs a field, registered return or qualified owner. + # Keep enum results and builder arguments even when the field is absent. + if not bindings and not calls and not return_functions and ( + not field or field not in _field_mentions(source) + ): + return [] module_functions = _module_functions(source, tree) call_memo: dict[tuple[str, str], tuple[frozenset[str], tuple[str, ...]]] = {} environments: dict[tuple[int, str], SimpleNamespace] = {} diff --git a/tests/architecture/test_semantic_production.py b/tests/architecture/test_semantic_production.py index 6cda493d43..6d9e11dedd 100644 --- a/tests/architecture/test_semantic_production.py +++ b/tests/architecture/test_semantic_production.py @@ -28,6 +28,41 @@ def test_literal_scan_rejects_invalid_syntax_without_a_field_mention(): source = SourceFile('loopx/broken_probe.py', '.py', 'def broken(') with pytest.raises(SyntaxError): python_production.python_literal_uses(source, 'action') + with pytest.raises(SyntaxError): + python_production.scan_python_production(source, field='action', enums={}) + + +@pytest.mark.parametrize('text,options,expected', [ + ("def choose():\n return 'run'", {'return_functions': frozenset({'choose'})}, + {('return', frozenset({'run'}), False)}), + ("class Action:\n RUN='run'\ndef choose():\n return Action.RUN", + {'enums': {'loopx/field_absence.py::Action': {'RUN': 'run'}}}, + {('enum_result', frozenset({'run'}), False)}), + ("def pack(result):\n return result\npack('run')", + {'call_arguments': {'loopx/field_absence.py::pack': {'result': 0}}}, + {('call_argument', frozenset({'run'}), False)}), + ("def choose(value):\n return value", {'return_functions': frozenset({'choose'})}, + {('return', frozenset(), True)}), +]) +def test_field_absence_preserves_registered_and_enum_evidence(text, options, expected): + source = SourceFile('loopx/field_absence.py', '.py', text) + assert 'action' not in text + rows = python_production.scan_python_production(source, **{'field': 'action', 'enums': {}, **options}) + assert {(row.form, row.values, row.unresolved) for row in rows} == expected + + +def test_field_presence_tracks_source_changes_despite_hash_collisions(): + class CollidingText(str): + def __hash__(self): + return 0 + + for text, expected in [ + ("other = 'wait'", set()), + (r"packet['\x61ction'] = 'run'", {'run'}), + ("other = 'run'", set()), + ]: + source = SourceFile('loopx/field_presence.py', '.py', CollidingText(text)) + assert python_production.python_literal_uses(source, 'action') == expected @pytest.mark.parametrize('first_text,second_text', [ From 70d206cabefc2a0a985ce6ea01ba874b11dbbfc0 Mon Sep 17 00:00:00 2001 From: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> Date: Fri, 30 Oct 2026 03:46:54 +0800 Subject: [PATCH 5/8] docs(self-repair): record repeated static scan diagnosis Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> --- skills/loopx-self-repair/references/repair-patterns.md | 1 + 1 file changed, 1 insertion(+) diff --git a/skills/loopx-self-repair/references/repair-patterns.md b/skills/loopx-self-repair/references/repair-patterns.md index 88cc509b8f..e6dc0d70a6 100644 --- a/skills/loopx-self-repair/references/repair-patterns.md +++ b/skills/loopx-self-repair/references/repair-patterns.md @@ -70,6 +70,7 @@ teaches a reusable control-plane lesson. | `admission_timer_reentry_gap` | A positively admitted Todo with an unchanged, already-due timer immediately becomes writeback-only on same-Turn reentry before producing a result. | The original positive admission receipt, its retained resume/readiness facts and timestamp, current canonical Todo, exact binding, and File/SQLite replay. | Recovery classified every external wait as introduced after admission; today's due flag alone cannot establish when a wait was authored. | Freeze scalar resume/readiness facts at the first positive admission and compare them in the existing typed wait owner with microsecond timestamps. Continue only the unchanged runnable timer; later waits and missing/malformed legacy facts retain original closeout. Preserve identity, clocks, gates and one debit. Never clear a legitimate wait, rewrite receipts, fabricate progress or reset a retry clock to escape recovery. | | `blocked_turn_retry_history_gap` | An exact blocked Turn can close without spending, but its open Todo is immediately selected again or loses its wait when other runs crowd the status display; a peer hard lease prevents the blocked agent from updating the Todo. | Exact Goal/Agent/Todo/Turn receipt, peer lease, canonical Todo validator, persisted retry due time, full run index versus compact recent runs and cached index digest, next-Turn selection and spend count. | The settlement had no bounded retry authority, or quota selection reconstructed it from truncated or stale status projection and tried to mutate a peer-gated Todo. | Persist one bounded retry on the blocked Turn receipt, retain it in the full-index semantic projection, and fence cached scheduler status against the current Run index before projecting it for the same agent. Keep the canonical Todo and terminal validator intact; prove replay, no spend, crowded history, stale cache, and real File/SQLite hard-lease readback. | | `host_closeout_history_scan_amplification` | Quota entry approaches or exceeds its runtime timeout only on a long-lived Goal, especially when many prior Turns already have valid closeouts. | Turn and receipt counts, rollout-log and runs-index sizes, candidate count, per-stage elapsed time, declared runtime budget, and the same synthetic settled history before and after repair. | Preflight rebuilt append-order recency with repeated list searches, then each candidate reread, reparsed, and rescanned the complete event and run history. A larger timeout hid the multiplicative work without bounding it. | Parse each persisted source once, retain strict-versus-tolerant read semantics in the shared snapshot, build append-order and exact Turn/effect indexes, and reduce every candidate from that snapshot. Restore the ordinary runtime budget and cover a sufficiently large all-settled history that must traverse every candidate; never truncate history, skip identity validation, or raise the timeout as the repair. | +| `static_scan_repeats_module_analysis` | Required vocabulary or static checks approach their timeout as tracked modules and vocabulary queries grow. | Exact source/runtime, unchanged tracked-source population, complete production and unknown observations, phase CPU/elapsed time, native budget and memory footprint; escaped/normalized-name, invalid-syntax and source-change counterexamples. | Each field query rebuilds module/scope analysis even without a parsed field, registered return or qualified enum/builder. A source-substring shortcut would lose escaped strings and normalized identifiers. | Reuse bounded immutable parsed-name facts keyed by the complete source beside the existing analyzer. Parse before proving absence; retain enum, return, builder and unknown evidence. Compare complete observations and CPU, preserve hash-collision/source-change behavior, and rerun the full native check under its original budget. Retain failed or contended runs and disclose memory costs. | | `turn_replay_recovery_semantic_gap` | A real Turn resumes from a safe Journal prefix, but `inspect-journal` presents `replay_legal=false` as if recovery were forbidden; scheduler-only or saved-Host-result recovery is especially misleading. | Journal integrity fields, replay decision, executor-adopted recovery decision, completed phase prefix, complete typed settlement identity, authoritative selected-Todo lineage, conditional Host Session Binding check, prepared-effect presence, and bounded recovery outcome. | Effect-free terminal replay and effectful executor recovery were collapsed into one user-facing decision even though the executor used separate status/phase rules; initial repairs also omitted either the settlement identity or its binding-to-Turn cross-check, allowing a drifted goal, agent, or canonical Todo to reach a later effect. | Keep replay legality and Journal consistency distinct. Make one typed recovery decision the source used by both executor and inspection; validate and cross-bind the canonical settlement goal, agent, Turn instance, binding, and effect id before authorization, including the selected Todo or adaptive primary Todo; project continue/resume phase/Host reinvocation/reason plus only participating checks, and persist a bounded planned-versus-actual audit. Cover identity and binding drift with zero-provider-call regressions. Preserve the existing prepared-effect readback owner and do not claim general exactly-once semantics. | | `managed_chat_resume_turn_identity_gap` | A managed Chat Session resumes after its adapter becomes unhealthy, returns to `ready`, and clears `active_turn_id`, but the interrupted Turn remains nonterminal and unowned. | Pre-resume Session snapshot, post-prepare persisted Session, adapter health, interrupted Turn status and error code, final Session state. | Resume preparation cleared the persisted active Turn reference before adapter recovery, then recovery re-read only the mutated Session and lost the pre-resume Turn identity. | Carry the pre-mutation Turn id through the fresh closed-state check, terminalize that Turn as `failed/server_restarted` before restoring the Session, and cover unhealthy-adapter resume with a focused regression while retaining the per-Session lifecycle lock. | | `dashboard_chat_stdio_encoding_gap` | Dashboard Chat completes ASCII turns but Codex app-server closes on non-ASCII input on Windows; direct stderr reports invalid UTF-8. | Failing multilingual request, app-server stderr, Python locale encoding, app-server `Popen` text-pipe options, and the same request under an explicit UTF-8 control. | The text-mode stdio pipe inherited the Windows system code page even though the app-server JSON-RPC transport requires UTF-8. | Set `encoding="utf-8"` explicitly on the app-server subprocess pipes and retain a focused launch-contract regression so host locale cannot change the protocol encoding. | From 64a60ba185895d022c5d1b9c933c67cee6cb70c1 Mon Sep 17 00:00:00 2001 From: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> Date: Fri, 30 Oct 2026 04:01:37 +0800 Subject: [PATCH 6/8] refactor(semantics): clarify scanner expression types Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> --- loopx/semantics/python_production.py | 30 ++++++++++++++-------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/loopx/semantics/python_production.py b/loopx/semantics/python_production.py index 9f5dab3466..fef8df7267 100644 --- a/loopx/semantics/python_production.py +++ b/loopx/semantics/python_production.py @@ -11,7 +11,7 @@ from dataclasses import dataclass from functools import lru_cache from types import SimpleNamespace -from typing import Mapping, TypeVar +from typing import Mapping, TypeVar, cast from .inventory import SourceFile @@ -232,9 +232,9 @@ def _qualified_bindings(source: SourceFile, tree: ast.Module, owners: Mapping[st if value is None and modules is not None and alias.name in symbols: # One unrenamed re-export hop through a tracked module binds the # same owner; only names that are owner symbols are followed. - target = _tracked_module(module, modules) - if target is not None and target.path != source.path: - value = _reexported(target, alias.name, imports) + reexport_source = _tracked_module(module, modules) + if reexport_source is not None and reexport_source.path != source.path: + value = _reexported(reexport_source, alias.name, imports) if value is not None: bindings[name] = value else: @@ -333,11 +333,11 @@ def _module_functions(source: SourceFile, tree: ast.Module) -> dict[str, ast.Fun # name some scope declares ``global`` and then stores is no longer reliably # the ``def`` above; binding a call to that ``def`` would report the original # body's returns for a function the module can swap at runtime. - for node in ast.walk(tree): - if not isinstance(node, ast.Global): + for global_node in ast.walk(tree): + if not isinstance(global_node, ast.Global): continue - scope = _enclosing_scope(tree, node) - for name in node.names: + scope = _enclosing_scope(tree, global_node) + for name in global_node.names: if name in defined and _stores_name(scope, name): bound[name] += 1 functions = {name: node for name, node in defined.items() @@ -348,14 +348,14 @@ def _module_functions(source: SourceFile, tree: ast.Module) -> dict[str, ast.Fun def _index_value(node: ast.AST) -> str | int | None: if isinstance(node, ast.Constant) and type(node.value) in (str, int): - return node.value + return cast(str | int, node.value) if (isinstance(node, ast.UnaryOp) and isinstance(node.op, ast.USub) and isinstance(node.operand, ast.Constant) and type(node.operand.value) is int): return -node.operand.value return None -def _union(values: list[ast.AST]) -> ast.AST: +def _union(values: list[ast.expr]) -> ast.expr: """Fold several definitions of one local into a finite selection node. The scan reports syntactic result possibilities, so a name written more than @@ -497,7 +497,7 @@ def collect(node: ast.AST, loops: frozenset[int] = frozenset()) -> None: if scope == '': nested_names -= local_owner_names | {owner.split('::')[1] for owner in call_arguments if owner.split('::')[0] == source.path} - import_bound = set() + import_bound: set[str] = set() for node in nodes: if isinstance(node, (ast.Import, ast.ImportFrom)): import_bound.update(alias.asname or alias.name.split('.')[0] for alias in node.names) @@ -526,7 +526,7 @@ def collect(node: ast.AST, loops: frozenset[int] = frozenset()) -> None: local_bindings = {k: v for k, v in bindings.items() if k not in shadows} local_calls = {k: v for k, v in calls.items() if k not in shadows} - plain: dict[str, list[ast.AST]] = {} + plain: dict[str, list[ast.expr]] = {} for node in nodes: target = value = None if (isinstance(node, ast.Assign) and len(node.targets) == 1 @@ -536,7 +536,7 @@ def collect(node: ast.AST, loops: frozenset[int] = frozenset()) -> None: and node.value is not None): target, value = node.target.id, node.value if target is not None: - plain.setdefault(target, []).append(value) + plain.setdefault(target, []).append(cast(ast.expr, value)) declared = {name for node in nodes if isinstance(node, (ast.Global, ast.Nonlocal)) for name in node.names} # Every store of the name must be one of those plain writes, so a value @@ -788,7 +788,7 @@ def same_module_call(node: ast.Call, mode: str) -> tuple[set[str], bool] | None: blocked(reason) return set(values), bool(reasons) - def enum_object_value(node: ast.AST, seen: frozenset[str]) -> tuple[set[str], bool]: + def enum_object_value(node: ast.AST | None, seen: frozenset[str]) -> tuple[set[str], bool]: node, seen = bound(node, seen) if isinstance(node, ast.IfExp): parts = [enum_object_value(value, seen) for value in (node.body, node.orelse)] @@ -871,7 +871,7 @@ def scan_scope(body: list[ast.stmt], scope: str, parameters: set[str], env = env if env is not None else environment(body, scope, parameters, call_shadows, own) blockers = env.blockers - def record(node: ast.AST | None, form: str, location: ast.AST) -> None: + def record(node: ast.AST | None, form: str, location: ast.expr | ast.stmt) -> None: # Reasons are read back by position: one scope's environment is # shared with same-module call resolution, which may nest inside. start = len(blockers) From 1dc6cb85d1203eeea1236d70c3127782fd375379 Mon Sep 17 00:00:00 2001 From: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> Date: Fri, 30 Oct 2026 04:33:23 +0800 Subject: [PATCH 7/8] fix(semantics): retain parsed module result type Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> --- loopx/semantics/inventory.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/loopx/semantics/inventory.py b/loopx/semantics/inventory.py index 008bf836a0..c341a3e056 100644 --- a/loopx/semantics/inventory.py +++ b/loopx/semantics/inventory.py @@ -116,7 +116,7 @@ def _assigned_name(node: ast.AST) -> tuple[str, ast.AST] | None: return None -def parse_python(source: SourceFile) -> ast.AST: +def parse_python(source: SourceFile) -> ast.Module: """Parse one tracked Python source, rejecting an unparseable one by path. Deliberately not cached. Caching the trees would hold roughly two million From 0f961c047637df70a17a836ea549d7b712d29b98 Mon Sep 17 00:00:00 2001 From: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> Date: Fri, 30 Oct 2026 12:34:50 +0800 Subject: [PATCH 8/8] test(context): follow native context page cursors Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> --- .../test_saved_context_project.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/tests/control_plane/test_saved_context_project.py b/tests/control_plane/test_saved_context_project.py index 1a5c1d35e3..870ee6aa00 100644 --- a/tests/control_plane/test_saved_context_project.py +++ b/tests/control_plane/test_saved_context_project.py @@ -490,6 +490,7 @@ def test_native_saved_preflight_uses_lifecycle_without_guard_replay(tmp_path, mo from loopx.control_plane.quota.turn_envelope import prepare_context_delivery, restore_context_delivery from tests.control_plane.codex_checkpoint_fixture import local_inference_provider from tests.control_plane.test_codex_context_boundary import _app_server, _configure_provider, _native_fixture, _input_text + from tests.control_plane.codex_checkpoint_fixture import last_context_page as _last_page from tests.control_plane.test_codex_context_capture import _delivered from tests.control_plane.test_quota_settlement_cli import ( AGENT_ID, GOAL_ID, TODO_ID, _configure_selectable_alternative, @@ -581,14 +582,10 @@ def response(request, number, message): if descriptor["mode"] == "delta": assert descriptor["target"] == published["target"] observed[name] = (published, descriptor) - # The existing page protocol also admits the projector's first - # page. Old installed projectors still start with command. - offset = descriptor["next_offset"] if "content" in descriptor else 0 - for offset in range(len(data) if offset is None else offset, len(data), 4096): - # Synthetic captures are ASCII JSON; public reader owns every byte. - queue.append(shlex.join([*cli, "--format", "json", "quota", "context-read", - "--capture-file", descriptor["path"], "--capture-sha256", descriptor["sha256"], - "--offset", str(offset)])) + # Follow the public reader's actual page cursor rather than + # guessing offsets from the capture size. This keeps each + # request's real page budget and avoids overlapping reads. + queue.append(descriptor["command"]) if progressive: # Exercise the actual small reference and selective public # reader in native model input; never consume either file in full. @@ -616,6 +613,12 @@ def response(request, number, message): native_context = home / "loopx-context" if benchmark else context assert "candidate" not in json.loads((native_context / "session.json").read_bytes()) else: + page = _last_page(request) + assert page is not None and page["sha256"] == descriptor["sha256"] + if not page["eof"]: + assert page["next_offset"] > page["offset"] and page["next_command"] + queue.append(page["next_command"]) + return response(request, number, message) assert _delivered(request, descriptor["sha256"]) == Path(descriptor["path"]).read_text().rstrip("\n") if benchmark and name in ("cold", "warm"): # Consume the command actually delivered to the native