From 75f673d5589c481cbbb42fee16935565eff385d4 Mon Sep 17 00:00:00 2001 From: "lusendong.6789" Date: Sat, 7 Nov 2026 13:08:06 +0800 Subject: [PATCH 1/4] docs(self-repair): qualify generated Todo pointer reader compatibility Signed-off-by: lusendong.6789 Co-authored-by: TRAE CLI --- .../rfcs/monorepo-distribution-split-v0.md | 31 ++++++++++++++++++- .../monorepo-distribution-split-v0.zh-CN.md | 24 +++++++++++++- .../references/repair-patterns.md | 1 + 3 files changed, 54 insertions(+), 2 deletions(-) diff --git a/docs/architecture/rfcs/monorepo-distribution-split-v0.md b/docs/architecture/rfcs/monorepo-distribution-split-v0.md index c8e7ce79f9..e2964172e4 100644 --- a/docs/architecture/rfcs/monorepo-distribution-split-v0.md +++ b/docs/architecture/rfcs/monorepo-distribution-split-v0.md @@ -852,4 +852,33 @@ cross-package breaking change for three consecutive releases. ## Appendix E: Incident and review lessons -None yet. +### Generated Todo pointer read compatibility (non-normative) + +The generated archive and active-field omission pointers belong to the existing +Todo projection owner. An older strict `machine_region.py` reader rejects their +`loopx:todo-archive-pointer` line as foreign content. The reader repair is already +on `main` through [#7834](https://github.com/loopx-project/loopx/pull/7834); an +installed reader whose bytes predate that repair needs composition qualification, +rather than another source fix or removal of live projection metadata. + +Author qualification at `97cc1e0c4` used the same synthetic source and real +File/SQLite stores under Python 3.13 and 3.14 with Node 26. The prior reader's +grammar matches `8d314af49`. Across the prior snapshot, current source and its +built wheel, 36 cases produced 20 expected successes and 16 expected refusals: +four prior-reader pointer failures and twelve foreign-narrative failures. All +four prior-reader failures recovered and replayed through the current source +under the original Turn identity. Canonical work remained unchanged and quota +debits stayed zero. Eight additional source/wheel console cases read the native +capture back with synthetic Session identities; fixture preparation failures +remain failures, and a committed capture was recovered without rerunning +admission. The existing region, projection and SQLite archive regressions passed +(83 cases). + +The 36-case matrix took 135.6 seconds; this is observation cost, not a speed or +long-horizon benefit qualification. Total model, service CPU and human costs are +unmeasured. Actual installed-host upgrade and readback remain unqualified. +Preserve I3 for at least one minor release. This evidence does not close full +R0/V10, M1/#8011, R1, context/model or split installation, or promote D2/D5/D6/D3. +Use the existing self-repair lookup for `marked_todo_archive_pointer_reader_drift` +to distinguish a shipped repair from installed-reader drift without weakening +foreign-content rejection or rewriting active state. diff --git a/docs/architecture/rfcs/monorepo-distribution-split-v0.zh-CN.md b/docs/architecture/rfcs/monorepo-distribution-split-v0.zh-CN.md index 5136da5181..5ba56619cf 100644 --- a/docs/architecture/rfcs/monorepo-distribution-split-v0.zh-CN.md +++ b/docs/architecture/rfcs/monorepo-distribution-split-v0.zh-CN.md @@ -622,4 +622,26 @@ R0 可以先于目录移动交付,但不代表内核合格。R3/R4 可选, ## 附录 E:事故与评审经验 -暂无。 +### 生成式 Todo 指针的读取兼容性(非规范性) + +归档和活跃字段省略指针由既有 Todo 投影 owner 生成。较旧的严格 +`machine_region.py` reader 会把 `loopx:todo-archive-pointer` 行作为外来内容拒绝。 +reader 修复已通过 [#7834](https://github.com/loopx-project/loopx/pull/7834) 进入 +`main`;实际 reader 字节早于该修复的安装需要装配资格验证,不能据此重复源码修复 +或删除活跃投影元数据。 + +作者在 `97cc1e0c4` 上,以相同合成 source 和真实 File/SQLite store,在 Python +3.13、3.14 与 Node 26 下验证。旧 reader 的语法与 `8d314af49` 一致。旧快照、当前 +源码及其构建 wheel 共 36 个场景得到 20 个预期成功、16 个预期拒绝:4 个旧 reader +指针失败和 12 个外来叙述失败。4 个旧 reader 失败均通过当前源码按原 Turn 身份恢复 +并重放;canonical work 保持不变,quota 扣费为零。另外 8 个 source/wheel console +场景以合成 Session 身份完成 native capture 回读;fixture 准备失败仍保留为失败, +已提交 capture 的恢复未重跑准入。既有 region、projection 和 SQLite archive 回归 +通过(83 个场景)。 + +36 场景矩阵耗时 135.6 秒;这是观察成本,不是速度或长程收益资格。完整 model、 +service CPU 与人工成本未测量。真实安装宿主的升级及回读仍未验证。I3 至少保留一个 +minor release。这些证据不关闭完整 R0/V10、M1/#8011、R1、context/model 或拆分安装, +也不晋升 D2/D5/D6/D3。使用既有 self-repair lookup 中的 +`marked_todo_archive_pointer_reader_drift`,区分已交付修复和安装 reader 漂移;保留 +外来内容拒绝,不重写活跃状态。 diff --git a/skills/loopx-self-repair/references/repair-patterns.md b/skills/loopx-self-repair/references/repair-patterns.md index bc95573fe9..1432dc5a84 100644 --- a/skills/loopx-self-repair/references/repair-patterns.md +++ b/skills/loopx-self-repair/references/repair-patterns.md @@ -5,6 +5,7 @@ teaches a reusable control-plane lesson. | Pattern | Symptoms | Evidence To Read | Likely Root | Durable Repair | | --- | --- | --- | --- | --- | +| `marked_todo_archive_pointer_reader_drift` | An installed `quota should-run` reports `quota_unexpected_collection_error` and `ValueError`; a scoped read-only `quota status --verbose` identifies `non-generated content inside marked Todo region`, while current source reads succeed. | Exact launcher, Python/import route and per-component hashes; the generated `loopx:todo-archive-pointer` format and the installed `machine_region.py` reader; owner-authorized snapshot parser controls and a minimal synthetic File/SQLite source/wheel reproduction. Keep verbose diagnostics and snapshots private. | A valid generated archive or active-field omission pointer reaches an older reader whose strict machine-region grammar predates that format. A package version or declared commit alone cannot establish installed bytes, especially for a dirty source snapshot. | First verify the pointer is produced by the existing projection owner and preserve rejection of foreign narrative. Compare the same synthetic source and real backend under the same interpreter; verify original-Turn recovery/replay, unchanged canonical work and zero quota debit. If current main already includes the reader repair, as in #7834, do not duplicate it, strip active metadata or relax the grammar. Qualify a complete source/wheel composition, then follow existing authority for an upgrade and original-binding readback. A successful source recovery does not qualify the installed runtime or prove long-horizon benefit. | | `answer_question_replaced_by_delivery_status` | A user asks why a result occurred or whether it is expected, but receives measurements, tests or PR progress even though the reply was delivered completely. | The actual question and relevant corrections, current receiver brief, shared answer guidance, and the complete message on its original return route. | The author answered implementation progress instead of the causal question; successful transport or correct counters were mistaken for a useful answer. | Retain the correction in the existing Task and outcome, then repair the existing shared generation guidance: explain meaning, supported mechanism, expectations and the specific unknown before supporting data. Keep hypotheses separate from confirmed causes and preserve the qualified investigation owner. Verify guidance at native callers and read back the actual explanatory answer. Do not add keyword-based answer scoring, a second model rewrite, another display or a duplicate investigation; a prompt test or delivery receipt does not prove model compliance or user adoption. | | `queue_action_type_coercion` | A queued input action such as `["edit"]` is accepted but applies cancellation, or a stored operation accepts a non-string action. | Original JSON types, the typed planner and receipt decoder, real HTTP responses, canonical Turn/transcript/events/Session snapshots and native IO before/after refusal. | Membership checks stringify the action while effect selection and replay compare its original literal value. | Validate the original literal action at the existing owner before branching and when decoding pending or committed receipts. Cover arrays, nested arrays, objects, nulls, numbers, booleans and unknown strings; require rejection without state or native effects, then verify legal edit/cancel, original-operation replay and queue races. Preserve malformed historical evidence rather than coercing or rewriting receipts. | | `collaboration_return_format_projection_gap` | A complete answer that fits the result budget is rejected, or a published versioned return becomes unreadable or cannot replay through the real CLI. | Selected renderer output and Unicode count; persisted reply, compact publication/Inbox observations, original-route delivery and legacy replay at the same boundary. | Validation counted a different renderer before choosing the immutable format, or a compact codec omitted the persisted format marker and silently treated it as legacy. | Apply the unchanged budget to the actual selected format, carry its existing marker through every compact observation, and retain old bytes on replay. Validate BMP/non-BMP exact-limit and one-character-over cases, unsupported versions, real CLI publication/retry, original-audience return and File/SQLite packaged readback. Do not raise the budget, truncate the answer, rewrite historical receipts or add a second model. | From e6d23c4e117d0dc283ce59ebcebe58cae4dd5b09 Mon Sep 17 00:00:00 2001 From: Lusen Dong Date: Sat, 7 Nov 2026 13:45:04 +0800 Subject: [PATCH 2/4] refactor(turn): defer DSH builders and process helpers to selected paths (#8185) --- .../rfcs/monorepo-distribution-split-v0.md | 263 ++++++++++++++++++ .../monorepo-distribution-split-v0.zh-CN.md | 211 ++++++++++++++ loopx/cli_commands/turn_run_once.py | 33 ++- .../turn_driver/host_bridge_transport.py | 39 ++- loopx/doctor_git.py | 25 +- .../test_control_plane_import_boundaries.py | 73 +++++ tests/control_plane/test_host_process.py | 54 ++++ tests/test_doctor_git_probe_budget.py | 14 +- tests/test_loopx_turn_driver.py | 10 + 9 files changed, 711 insertions(+), 11 deletions(-) diff --git a/docs/architecture/rfcs/monorepo-distribution-split-v0.md b/docs/architecture/rfcs/monorepo-distribution-split-v0.md index c8e7ce79f9..85a999b95e 100644 --- a/docs/architecture/rfcs/monorepo-distribution-split-v0.md +++ b/docs/architecture/rfcs/monorepo-distribution-split-v0.md @@ -824,6 +824,267 @@ Approval needs a reviewed decision, not implementation progress or silence. installation, R1 managed minimal or D1–D7. No preset, frontend operation, permission or persisted state changes; protected execution keeps its owner. +### 2026-11-06 — Selected Turn host loading seam + +Following the generic-cli/SQLite candidate in +[#8180](https://github.com/loopx-project/loopx/pull/8180), the source proposal +`9b80cf7d6` defers DSH builders at `turn_run_once` until a caller selects DSH +or explicitly requests their established exports. Named imports, original +builder identity, static types, `dir()`, star imports and the CLI's builder +patch points remain available. The existing host/authority owners are reused; +this does not select a minimal preset or adopt D2/D5. + +- **Observed delta:** the real generic-cli/SQLite task path loads 533 LoopX + modules instead of 538 at `62e234c93`. The five removed modules are + `turn_dsh_host`, `turn_driver.host_candidate`, `dsh_goal_mode` and its + `host_failure_map`/`turn_host_adapter`. No module is added. The cold-import + oracle fails at the baseline and passes at the proposal; the existing DSH + CLI fixture also proves the public builder patch point is used. +- **Real-path parity:** the same private harness runs ordinary completion, + interruption after typed host-result persistence, original-journal resume + and committed replay on isolated SQLite fixtures at both revisions. Each + fixture ends with one host invocation/attempt, one writeback and one debit; + replay repeats none. Independent argv validators accept identical artifact + bytes. Each arm uses an isolated native broker whose shutdown and OS exit are + verified. This remains deterministic transport evidence, not model-host V4 + or normal hard-lease V2 qualification. +- **Observed costs:** one ordinary instrumented invocation per revision records + 2.962 → 3.169 seconds, parent CPU 0.737 → 0.775 seconds and parent peak RSS + 65.6 → 64.6 MiB; output remains 3,474 bytes. Waited-child CPU is + 0.567 → 0.599 seconds. Cache, run order and competing work are not controlled; + these observations establish neither a latency improvement nor a regression + threshold. Broker RSS observations and waited-child usage do not measure + complete tree peaks, full wire/context bytes or model cost. The loading delta + is small; long-horizon benefit and D6 qualification remain unproven. +- **Historical validation limits at `bc59a9ed4`:** the wider source selection records 596 passed and one + failed preview cold-import test; the baseline independently fails the same + test because `host_bridge_transport` eagerly imports + `extensions.process_runtime`. Owner-scoped mypy with `--follow-imports=skip` + reports the same 29 diagnostics at both revisions after line normalization; + it is not the full configured mypy suite. Neither failure is waived or + relabeled as passing. The process-helper seam below repairs that preview + failure in the same author proposal while retaining the type-check limitation. +- **Remaining:** independent review, required validation and exact-head merge + readiness still govern adoption. Opt-in minimal selection, D2/D5 decisions, + full R0/V10/R1, hard-lease/context/model parity and split installation remain + open; the separate #8011 status proposal retains its limits. No authority, + provider, default, installed-host or release change is introduced. + +### 2026-11-06 — Preview process helpers remain with execution + +The follow-on runtime proposal `a44e27d96` repairs the unchanged preview import +oracle. Deferring the bridge helpers first exposes a second eager import in +`doctor_git`; both adapters now resolve the existing `process_runtime` owner +at execution/cleanup or explicit import. Named/star imports, original function +identity, static aliases, `dir()` and public patch points remain compatible. +The TypeScript Host lifetime and process/permission decisions do not move. + +- **Observable result:** importing the preview command loads 337 LoopX modules + instead of 338 at `bc59a9ed4`, removing only `extensions.process_runtime`. + Successful real Git reads likewise leave cleanup unloaded. Actual generic-cli + execution still loads 533 modules in both arms because it needs those helpers. + This fixes a loading boundary; it establishes no speedup or long-horizon benefit. +- **Real paths:** the same isolated SQLite harness passes ordinary completion, + typed-result interruption, original-journal recovery and committed replay at + both immutable runtime revisions. Artifacts remain identical; each fixture + has one host invocation/attempt, writeback and debit, with no replayed effect. + Both owned brokers stop with verified OS exit. Real bridge send/cancel, + backpressure, deadline and unconfirmed-cleanup cases retain their behavior; + real Git timeout/terminal cancellation retire the query's helpers. A cleanup + error retains the unknown diagnostic through the existing patch point. +- **Limits:** seven Windows Job Object cases are skipped on macOS. One combined + validation attempt fails because the Git fixture's hook did not start within + its existing budget; the isolated Git selection subsequently passes without + changing that budget or oracle. Owner-scoped mypy checks all three touched + loading adapters and retains the same 29 diagnostics at base and proposal. + Full configured mypy, packaged/split installation, minimal selection, D2/D5, + complete R0/R1/V10, hard-lease/context/model parity and full-cost benefit remain + unqualified. Independent exact-head review and required validation still govern + adoption; the historical failures above remain historical failures. + +### 2026-11-06 — Paired cost campaign remains unqualified + +The bounded campaign at `a9485073f` versus `86d9c542d` produces useful negative +evidence, not an attributable speedup. It ends at the frozen 20-minute sampling +wall limit with 499 accepted observations: eight source groups have 30 pairs +each; wheel JSON-version has nine pairs and one unmatched candidate observation. +The other wheel groups were not sampled. The original criteria and every +observation remain retained; neither the cap nor the incomplete groups become +a passing qualification. Later integration of target `cd677182c` is outside +these immutable measurements. + +The experiment uses macOS arm64, CPython 3.13.5, Node 26.0.0 and the real +Node-owned SQLite 3.53.1 backend. Source and disposable monolithic `loopx` 2.2.0 +wheel environments have the same 57 dependency versions and verified import +provenance. The public entrypoint exercises JSON version, selected +`performance-diagnosis` help, status, quota admission and deterministic +generic-cli work on the existing `_write_live_fixture(provider="sqlite")` owner. +Order is fixed: source before wheel, that command order, cold before warm, +alternating baseline/candidate order by pair. Each task has fresh synthetic +state. Cold uses a fresh Python process and retired owned broker; warm uses a +fresh Python process and retained broker. Filesystem caches are uncontrolled +and bytecode writes are disabled; this is not a cold-disk claim. + +**Cache limitation:** existing bytecode was not normalized. A subsequent +inventory finds 7 versus 1,241 LoopX `.pyc` files and 2 versus 344 dependency +`.pyc` files in the source arms. JSON version loads the same 1,157 LoopX modules +at both revisions; its parent read-open census is 2,402 versus 1,253. This +invalidates attribution of the observed source reductions to the five-module +loading change. The numbers below describe these cache conditions only. + +| Source operation | Broker condition | Baseline seconds, median / p95 | Candidate seconds, median / p95 | +| --- | --- | --- | --- | +| JSON version | absent | 3.092 / 3.394 | 1.707 / 2.110 | +| Selected help | absent | 0.093 / 0.174 | 0.101 / 0.165 | +| Status | cold | 2.104 / 2.437 | 1.832 / 2.074 | +| Status | warm | 1.304 / 1.422 | 0.985 / 1.115 | +| Quota admission | cold | 2.481 / 2.720 | 2.029 / 2.331 | +| Quota admission | warm | 1.785 / 1.881 | 1.313 / 1.460 | +| Generic-cli task | cold | 3.910 / 4.158 | 3.446 / 3.803 | +| Generic-cli task | warm | 2.837 / 3.025 | 2.424 / 2.635 | + +- **Covered costs:** cold task CPU for the target and waited descendants is + 1.769 / 1.832 versus 1.283 / 1.388 seconds, median / p95; their maximum RSS + is 79.2 / 87.4 versus 75.5 / 78.7 MiB. Separate broker CPU is 0.720 / 0.780 + versus 0.735 / 0.800 seconds, with end RSS 126.6 / 129.7 versus + 128.3 / 133.5 MiB. These are not a concurrent tree peak or complete CPU ledger. + Outputs remain 81, 325, 21,829, 23,795 and 3,557 bytes for the five operations. + One-minute host load ranges from 5.00 to 14.42; this is a named-host observation. +- **Separate instrumentation:** task loading remains 538 → 533 in source and + wheel. Cold task parent read/write-open counts are 2,179/284 → 1,643/284 in + source and 2,184/284 → 2,173/284 in wheel. All four captures make 238 local + IPC connections; covered socket send/receive bytes are 895,335/391,794 and + 895,339/391,794 in source, 894,790/391,543 in both wheels. These cover the + observed Python sockets, not all context, transport or descendant I/O. + A separate source cold-task audit records parent/waited-child CPU + 1.234/0.560 → 0.802/0.582 seconds and maximum RSS + 88.7/75.3 → 63.4/75.4 MiB; these instrumented samples are not timing evidence. + The existing performance-diagnosis recipes inspect separate Pyinstrument + and actual Node-worker CPU profiles. Parent waits, workspace capture and + reads, and worker startup/retirement remain investigation leads; profile + elapsed/idle weights do not enter the timing table. Full JSON version uses + the full parser/export graph, while `--version` loads two LoopX modules. +- **Semantic readback:** all sampled tasks independently accept the same eight + squared integers. Source and wheel at both revisions pass ordinary committed + replay, interruption after typed-result persistence, original-journal resume + and replay. Each accepted fixture has one host attempt/invocation, writeback + and debit; replay repeats none. Dropping the last square makes all four + independent validators reject, with one dispatch and zero writebacks/debits. + Owned brokers finish with OS PID-exit readback. One seed shutdown initially + targets a different interpreter-alias fingerprint; its failure is retained + and recovery uses the verified owned locator, without touching host state. +- **Complete-cost limits and next work:** setup/orchestration is 888 seconds, + including 194 seconds of fixture preparation and a 98-second successful + preflight; unsuccessful bundle-build, invocation and preflight attempts remain + failures. Sampling target elapsed totals 1,001 seconds inside the 20-minute + wall cap. Separate semantic validation takes 39 seconds; artifact rejection + plus diagnosis/cleanup recovery takes 184 seconds; the instrumented census + takes 108 seconds, and task/version profiling 58/20 seconds. Subsequent + authoring/tool/model costs, complete native/thread work and full-tree peaks + remain incompletely covered. Both wheels contain 1,997 files and + approximately 8.9 MiB compressed; no split install is qualified. The next + cost experiment must match bytecode conditions and choose a feasible + representative source/wheel corpus from measured setup and invocation costs + before freezing it. Further loading edits await that comparison. R0/V10, + R1/minimal, D2/D5/D6, I3/D3, model/context/hard-lease and installation gaps + retain their existing owners and gates; this checkpoint closes none of them. + +### 2026-11-06 — Matched-bytecode representative cost checkpoint + +A new frozen comparison of `546c08c0d3f22358ef09b13e69f7edaa806121b8` +and `0df5ad7921faab3b9ab8ec809f2fd71060692cce` completes all six selected +source/wheel groups, each with 30 interleaved pairs: 360 accepted observations +in 948.573 seconds, within the unchanged 1,200-second sampling cap. E7 remains +an incomplete, cache-confounded experiment; these are separate observations. +The measured proposal includes target main through `546c08c0d`. Later target +integration is outside this immutable cost comparison. + +The environment remains macOS arm64, CPython 3.13.5, Node 26.0.0 and real +Node-owned SQLite 3.53.1. All four source/monolithic-wheel environments have +verified provenance and identical 57 dependency versions. Before freezing, +each arm compiles stdlib, dependencies and all 1,421 LoopX Python sources into +an owned `PYTHONPYCACHEPREFIX`; timestamp-header inventories find no missing or +invalid own/dependency caches. Relative source sets match within each +distribution. Inventories and frozen harness hashes remain identical after +sampling, recovery and profiles. Targets disable bytecode writes. Source runs +before wheel, selected help before task, cold before warm; even pairs use +baseline/candidate order and odd pairs reverse it. Every invocation has fresh +Python; cold retires the owned broker and warm retains it. Disk cache remains +uncontrolled. One-minute host load ranges from 8.35 to 88.73; retain every tail +observation, without attributing the source cold p95 difference to the code. + +| Selected operation | Broker | Baseline seconds, median / p95 | Proposal seconds, median / p95 | Median paired proposal minus baseline, seconds | +| --- | --- | --- | --- | --- | +| Source help | absent | 0.136 / 0.193 | 0.138 / 0.168 | +0.0010 | +| Source SQLite task | cold | 3.579 / 5.997 | 3.617 / 9.234 | -0.0195 | +| Source SQLite task | warm | 2.673 / 3.019 | 2.657 / 2.979 | -0.0602 | +| Wheel help | absent | 0.087 / 0.107 | 0.083 / 0.116 | -0.0033 | +| Wheel SQLite task | cold | 3.722 / 4.266 | 3.663 / 4.430 | -0.0847 | +| Wheel SQLite task | warm | 2.707 / 3.111 | 2.797 / 3.156 | -0.0457 | + +The p95 uses nearest rank. Median paired differences and differences of arm +medians are different statistics. No material latency or CPU benefit is +demonstrated: arm medians and tails have mixed directions, and task CPU remains +similar. These data qualify the representative comparison's completion and +semantic parity, not a speed threshold, full V10 or long-horizon benefit. + +| Task path | Target plus waited descendants CPU seconds, median (base → proposal) | Target/waited peak RSS MiB, median (base → proposal) | Owned broker CPU seconds, median (base → proposal) | Broker end RSS MiB, median / p95 (base → proposal) | +| --- | --- | --- | --- | --- | +| Source cold | 1.311 → 1.325 | 75.75 → 75.58 | 0.770 → 0.775 | 127.9 / 134.8 → 128.6 / 133.5 | +| Source warm | 1.235 → 1.226 | 76.15 → 76.16 | 0.180 → 0.185 | 146.5 / 148.4 → 129.8 / 148.7 | +| Wheel cold | 1.353 → 1.369 | 75.94 → 76.56 | 0.805 → 0.810 | 129.5 / 134.4 → 130.1 / 134.1 | +| Wheel warm | 1.273 → 1.260 | 76.05 → 76.69 | 0.190 → 0.180 | 100.4 / 136.3 → 101.4 / 140.2 | + +These RSS observations are not concurrent process-tree peaks; warm broker +end RSS also reflects its retained lifetime. Help target/waited median RSS is +17.56–17.85 MiB and median CPU is 0.046–0.056 seconds. Uninstrumented stdout is +325 bytes for help, 3,557 for source tasks and 3,550 for wheel tasks in both +arms; stderr is empty. Target/waited block-I/O counters do not measure all +broker/native I/O. + +Separate instrumentation confirms task loading 538 → 533 in both distributions, +with only the previously named five DSH modules removed; help stays at 12. +Parent read-open counts are source cold 1,680 → 1,675, source warm 1,269 → 1,264, +wheel cold 1,683 → 1,677 and wheel warm 1,272 → 1,267. Task write-opens stay at +284 cold and 279 warm, with 238 observed parent local socket connections. +Covered task send/receive bytes range from 893,698–894,864 / 392,608–392,859; +the source proposal removes five send bytes and wheel bytes are unchanged. +Help opens 31 files in source and 32 in wheel, with no writes or socket traffic. +Instrumented task parent CPU is 0.635–0.774 seconds and peak RSS 62.3–65.5 MiB; +waited-child CPU is 0.528–0.573 seconds and peak RSS 76.1–78.6 MiB. These single +captures are a census, separate from the repeated timing results. + +The same isolated real SQLite fixtures at all four variants pass ordinary +committed replay, interruption immediately after typed-result persistence, +original-journal recovery and committed replay. Independent squared-integer +artifact/transition oracles retain identical artifact bytes and one host +invocation/attempt, one writeback and one debit. Resume does not redispatch; +replay repeats no effect. Removing the final square produces exit 1 / failed +with one dispatch and zero writebacks/debits in every variant. All owned +brokers have shutdown/OS-exit readback; no experimental broker remains live. + +Setup/orchestration costs 1,018.786 seconds, including overlapping build, +installation, fixture and cache work; those phases must not be summed. +The separate rejection phase costs 16.056 seconds, instrumentation 39.116 and +eight parent/actual-Node profile captures 54.880. Recovery/replay target time +is recorded separately; its complete orchestration cost remains uncovered. +Existing `performance-diagnosis` recipes report workspace capture, journal +checkpoints, Git probes and quota preparation among Python observations; Node +profiles include startup, request dispatch, idle and retirement. Sampled stack +weights are neither benchmark latency nor proof of a root cause; inclusive +weights, warmup and retirement cannot be added as task savings. Full context +and process-tree/native/thread/model costs remain unknown. Monolithic wheels +remain approximately 8.9 MiB; this does not qualify split installation. + +**Evidence-conditioned next action:** retain the tested compatibility/loading +seams, and stop expanding these helpers for an unproven speedup. Prioritize a +bounded characterization of the existing full-parser/export closure or the +measured workspace/journal/bridge owner, then require a new matched real-path +comparison before adopting a performance change. This checkpoint creates no +new owner, budget, preset or default. R0's complete census, R1/minimal, +D2/D5/D6, I3/D3, hard-lease/context/model and split-install acceptance remain +open; independent exact-head review and required validation govern adoption. + ## Appendix B: Decision log | Date | Decision | Owner / approval | Alternatives | Normative sections changed | @@ -840,6 +1101,8 @@ Approval needs a reviewed decision, not implementation progress or silence. | E4 | Selected CLI loading seam | `9cae3b28939f72fa4aadab5cfe49e19bcbdab372` | Linked entrypoint/CLI in Section 4 | Source-audited | No new timing claim | | E5 | DSH composition example | Pinned upstream commit | [`minimal.patch.yml`](https://github.com/deepseek-ai/deepseek-harness/blob/f3803c96974ad4a94195fb97e5c637e9c2a3afe3/packages/bundle/web-app/presets/minimal.patch.yml), [`standard.patch.yml`](https://github.com/deepseek-ai/deepseek-harness/blob/f3803c96974ad4a94195fb97e5c637e9c2a3afe3/packages/bundle/web-app/presets/standard.patch.yml), [`sdk-minimal`](https://github.com/deepseek-ai/deepseek-harness/blob/f3803c96974ad4a94195fb97e5c637e9c2a3afe3/packages/bundle/sdk-minimal/README.md) | Source-audited | Shared preset composition and a self-contained SDK bundle; not a LoopX host qualification or permission recommendation | | E6 | Pi core/context example | Pinned upstream commit | [`agent` core](https://github.com/badlogic/pi-mono/blob/f14813e96510a7cf7562a120e809c6a134af073d/packages/agent/README.md) | Source-audited | Agent/tool/event core with `transformContext`, `convertToLlm`, `prepareRequest` and `finishTurn` seams; no LoopX authority or performance claim | +| E7 | Bounded paired cost campaign | `a9485073f` / `86d9c542d`; named environment above | Paired cost checkpoint above; existing entrypoint/SQLite fixture and performance-diagnosis owners | 499 observations; source groups complete, wheel incomplete; cache attribution unqualified | Aggregate public-safe evidence only; no speedup, full V10, split-install or model-cost qualification | +| E8 | Matched-bytecode representative comparison | `546c08c0d` / `0df5ad792`; named environment above | Matched-bytecode checkpoint above; same CLI/SQLite and performance-diagnosis owners | Six groups × 30 pairs; 360 observations; parity/rejection and cache readback pass; no demonstrated material benefit | Aggregate public-safe evidence only; later integration, full V10, minimal, split install and model costs unqualified | ## Appendix D: Rejected or superseded alternatives diff --git a/docs/architecture/rfcs/monorepo-distribution-split-v0.zh-CN.md b/docs/architecture/rfcs/monorepo-distribution-split-v0.zh-CN.md index 5136da5181..266ae73948 100644 --- a/docs/architecture/rfcs/monorepo-distribution-split-v0.zh-CN.md +++ b/docs/architecture/rfcs/monorepo-distribution-split-v0.zh-CN.md @@ -599,6 +599,215 @@ R0 可以先于目录移动交付,但不代表内核合格。R3/R4 可选, 不关闭完整 R0/V10、M1、拆包安装、R1 managed minimal 或 D1–D7。不改变 preset、 前端操作、权限或持久化状态;受保护执行仍由既有 owner 所有。 +### 2026-11-06 — 所选 Turn 宿主的加载接缝 + +承接 [#8180](https://github.com/loopx-project/loopx/pull/8180) 的 +generic-cli/SQLite 候选,源码提案 `9b80cf7d6` 在 `turn_run_once` 延迟加载 DSH +builder,直到调用方选择 DSH 或显式请求既有 export。命名 import、原 builder +身份、静态类型、`dir()`、star import 与 CLI builder patch 点均保留。复用既有 +宿主/authority owner;这不选择 minimal preset,也不代表 D2/D5 已采纳。 + +- **观察到的变化:** 真实 generic-cli/SQLite 任务路径加载 533 个 LoopX 模块, + 基线 `62e234c93` 为 538。移出的五个模块是 `turn_dsh_host`、 + `turn_driver.host_candidate`、`dsh_goal_mode` 及其 `host_failure_map`/ + `turn_host_adapter`,没有新增模块。冷 import oracle 在基线失败、提案通过; + 既有 DSH CLI 夹具还证明公开 builder patch 点被使用。 +- **真实路径一致性:** 同一个私有 harness 在两个版本的隔离 SQLite 夹具上运行 + 正常完成、typed host result 持久化后中断、原 journal 恢复及 committed 重放。 + 每个夹具最终仅一次宿主调用/attempt、一次 writeback 和一次扣额,重放不重复 + 效果。独立 argv 验收器接受字节相同的产物。每个版本使用隔离 native broker, + 已核验停止与 OS 退出。这仍是确定性 transport 证据,不是模型宿主 V4 或正常 + hard-lease V2 的资格。 +- **观察成本:** 每个版本一次正常插桩调用记录耗时 2.962 → 3.169 秒,父进程 + CPU 0.737 → 0.775 秒、峰值 RSS 65.6 → 64.6 MiB;输出均为 3,474 字节。 + 已 wait 子进程的 CPU 为 0.567 → 0.599 秒。缓存、执行顺序与竞争工作未受控, + 这些观察不证明延迟改善,也不建立回归阈值。broker RSS 观察及 wait 子进程的 + usage 不覆盖完整进程树峰值、完整 wire/context 字节或模型成本。加载变化较小, + 长程收益和 D6 资格仍未证明。 +- **`bc59a9ed4` 的历史验证限制:** 更广的源码选择为 596 个通过、一个 preview 冷 import 测试失败; + 基线独立出现同一失败,原因是 `host_bridge_transport` 提前导入 + `extensions.process_runtime`。owner 范围的 mypy 使用 `--follow-imports=skip`, + 两个版本在线号归一化后均报告相同 29 个诊断,不代表完整配置的 mypy 套件。 + 两项失败均不豁免、不改写为通过。下述 process helper 接缝在同一作者提案中 + 修复 preview 失败,并保留类型检查限制。 +- **剩余:** 采纳仍受独立评审、必需验证和精确 head 的 merge readiness 治理。 + opt-in minimal 选择、D2/D5 决策、完整 R0/V10/R1、hard-lease/上下文/模型 + 一致性和拆包安装仍未关闭;独立 status 提案 #8011 保留原限制。不改变权限、 + provider、默认值、已安装宿主或发布行为。 + +### 2026-11-06 — Preview process helper 保留在执行路径 + +后续 runtime 提案 `a44e27d96` 修复未改动的 preview import oracle。先延迟 +bridge helper 暴露了 `doctor_git` 的第二条 eager import;两个 adapter 现均在 +执行/清理或显式 import 时解析既有 `process_runtime` owner。命名/star import、 +原函数身份、静态 alias、`dir()` 和公开 patch 点均保留。TypeScript Host lifetime +及进程/权限判定不迁移。 + +- **可观察结果:** 导入 preview command 加载 337 个 LoopX 模块,`bc59a9ed4` + 为 338,仅移出 `extensions.process_runtime`。真实 Git 成功读取同样不加载清理 + helper。实际 generic-cli 执行仍需要这些 helper,两个版本均加载 533 个模块。 + 本片修复加载边界,不证明提速或长程收益。 +- **真实路径:** 同一个隔离 SQLite harness 在两个不可变 runtime 版本通过正常 + 完成、typed result 后中断、原 journal 恢复及 committed 重放。产物字节相同, + 每个夹具仅一次宿主调用/attempt、writeback 和扣额,重放无重复效果。两个自有 + broker 均停止并核验 OS 退出。真实 bridge send/cancel、背压、deadline 和清理 + 未确认分支保留行为;真实 Git 超时/终端取消退役查询自有 helper。清理错误通过 + 既有 patch 点保留 unknown 诊断。 +- **限制:** macOS 跳过七个 Windows Job Object 用例。一轮组合验证因 Git 夹具 + hook 未在既有预算内启动而失败;随后隔离 Git 选择通过,未修改预算或 oracle。 + owner 范围的 mypy 检查三个受影响加载 adapter,基线与提案仍有相同 29 个诊断。 + 完整配置 mypy、打包/拆包安装、minimal 选择、D2/D5、完整 R0/R1/V10、 + hard-lease/上下文/模型一致性及完整成本收益仍未取得资格。采纳仍受独立精确 + head 评审与必需验证治理;前述历史失败仍是历史失败。 + +### 2026-11-06 — 成对成本实验仍未取得资格 + +`a9485073f` 对比 `86d9c542d` 的有界实验产生了有用的负面证据,未证明可归因的 +提速。实验在冻结的 20 分钟采样墙钟上限结束,共有 499 个通过语义断言的观察: +八个源码组各完成 30 对;wheel 的 JSON version 完成九对,另有一个未配对的候选 +观察,其余 wheel 组未采样。原标准和全部观察均保留;上限触发及未完成组不改写为 +资格通过。随后集成的目标 `cd677182c` 不在这组不可变版本的测量中。 + +环境为 macOS arm64、CPython 3.13.5、Node 26.0.0,真实 backend 是 Node owner +的 SQLite 3.53.1。源码和隔离的单体 `loopx` 2.2.0 wheel 环境具有相同的 57 个 +依赖版本,并核验实际 import 来源。公开入口依次运行 JSON version、所选 +`performance-diagnosis` help、status、quota admission 和确定性 generic-cli +任务,复用既有 `_write_live_fixture(provider="sqlite")` owner。顺序冻结为源码 +先于 wheel、上述命令顺序、冷路径先于暖路径,每对交替基线/候选顺序。每个任务 +使用新的合成状态。冷路径为新 Python 进程加已退役的自有 broker;暖路径为新 +Python 进程加保留的 broker。文件系统缓存未受控、禁止写入字节码,不声明冷磁盘。 + +**缓存限制:** 既有字节码未归一化。后续盘点发现源码组 LoopX `.pyc` 数为 +7 对 1,241,依赖 `.pyc` 数为 2 对 344。两个版本的 JSON version 均加载 +1,157 个 LoopX 模块,父进程 read-open census 为 2,402 对 1,253。因此不能把 +源码观察到的下降归因于五个模块的加载变化;下表仅描述这些缓存条件。 + +| 源码操作 | Broker 条件 | 基线秒数,中位数 / p95 | 候选秒数,中位数 / p95 | +| --- | --- | --- | --- | +| JSON version | 无 | 3.092 / 3.394 | 1.707 / 2.110 | +| 所选 help | 无 | 0.093 / 0.174 | 0.101 / 0.165 | +| Status | 冷 | 2.104 / 2.437 | 1.832 / 2.074 | +| Status | 暖 | 1.304 / 1.422 | 0.985 / 1.115 | +| Quota admission | 冷 | 2.481 / 2.720 | 2.029 / 2.331 | +| Quota admission | 暖 | 1.785 / 1.881 | 1.313 / 1.460 | +| Generic-cli 任务 | 冷 | 3.910 / 4.158 | 3.446 / 3.803 | +| Generic-cli 任务 | 暖 | 2.837 / 3.025 | 2.424 / 2.635 | + +- **覆盖的成本:** 冷任务 target 和已 wait 后代的 CPU 中位数/p95 为 + 1.769 / 1.832 对 1.283 / 1.388 秒,其最大 RSS 为 79.2 / 87.4 对 + 75.5 / 78.7 MiB。独立 broker CPU 为 0.720 / 0.780 对 0.735 / 0.800 秒, + 结束时 RSS 为 126.6 / 129.7 对 128.3 / 133.5 MiB;不代表并发进程树峰值或 + 完整 CPU 账本。五个操作的输出分别保持 81、325、21,829、23,795、3,557 字节。 + 宿主一分钟 load 范围为 5.00–14.42;这是具名环境的观察。 +- **独立 instrumentation:** 源码和 wheel 的任务加载仍为 538 → 533。 + 冷任务父进程 read/write-open 为源码 2,179/284 → 1,643/284、wheel + 2,184/284 → 2,173/284。四个 capture 各有 238 次本地 IPC 连接;覆盖的 socket + send/receive 字节为源码 895,335/391,794 与 895,339/391,794,两个 wheel 均为 + 894,790/391,543,仅覆盖已观察的 Python socket,不覆盖全部 context、transport + 或后代 I/O。独立源码冷任务 audit 的父进程/已 wait 子进程 CPU 为 + 1.234/0.560 → 0.802/0.582 秒,最大 RSS 为 88.7/75.3 → 63.4/75.4 MiB; + 这些 instrumentation 样本不作为计时证据。既有 performance-diagnosis recipe 分别检查 Pyinstrument 和实际 + Node worker CPU profile。父进程等待、workspace capture 和读取,以及 worker + 启动/退役仍是调查线索;profile elapsed/idle 权重不进入计时表。完整 JSON + version 走 full parser/export graph,`--version` 仅加载两个 LoopX 模块。 +- **语义回读:** 所有已采样任务通过独立断言,产物为相同的八个整数平方。源码和 + wheel 在两个版本均通过正常 committed 重放、typed result 落盘后中断、原 + journal 恢复及重放。每个被接受的夹具仅一次宿主 attempt/调用、writeback 和 + 扣额,重放不重复效果。移除最后一个平方值使四个独立 validator 均拒绝,只有 + 一次 dispatch、零 writeback/扣额。自有 broker 结束后核验 OS PID 退出。 + 一个 seed shutdown 起初选到不同 interpreter alias 的 fingerprint;保留该失败, + 随后通过核验过的自有 locator 恢复,未触及宿主状态。 +- **完整成本限制及下一步:** setup/编排为 888 秒,包含 194 秒夹具准备和 + 98 秒成功 preflight;失败的 bundle build、调用及 preflight 尝试仍为失败。 + 20 分钟墙钟上限内,采样 target elapsed 总计 1,001 秒。独立语义验证耗时 + 39 秒;产物拒绝及诊断/清理恢复 184 秒;instrumented census 为 108 秒, + 任务/version profiling 为 58/20 秒。后续撰写/工具/模型成本、完整 + native/thread 工作及进程树峰值仍未完整覆盖。两个 wheel 均有 + 1,997 个文件、压缩体积约 8.9 MiB,不代表拆包安装资格。下一轮成本实验必须 + 对齐字节码条件,并在冻结前根据实际准备/调用成本选定可完成的代表性 + source/wheel corpus;进一步加载修改等待该对照。R0/V10、R1/minimal、D2/D5/D6、 + I3/D3、模型/上下文/hard-lease 和安装缺口保留既有 owner/gate,本 checkpoint + 不关闭其中任何一项。 + +### 2026-11-06 — 字节码匹配的代表性成本 checkpoint + +新冻结对照比较 `546c08c0d3f22358ef09b13e69f7edaa806121b8` 与 +`0df5ad7921faab3b9ab8ec809f2fd71060692cce`。所选 source/wheel 六组各完成 +30 对交错样本:360 个有效观察,采样耗时 948.573 秒,保持原定 1,200 秒上限。 +E7 仍是不完整且缓存混杂的实验;本轮数据单独保留。测量候选包含截至 +`546c08c0d` 的目标 main;后续目标分支集成不在本次不可变成本对照内。 + +环境仍为 macOS arm64、CPython 3.13.5、Node 26.0.0 及真实 Node-owned +SQLite 3.53.1。四个源码/单体 wheel 环境的来源已核验,57 个依赖版本相同。 +冻结前,各 arm 在独占 `PYTHONPYCACHEPREFIX` 编译标准库、依赖及全部 +1,421 个 LoopX Python 源文件;时间戳 header 清点没有缺失/无效的自身或依赖缓存。 +同一发行形态的相对源码集合一致。采样、恢复和 profile 后,缓存清点与冻结 harness +哈希仍相同;目标进程禁止字节码写入。顺序固定为源码先于 wheel、help 先于 task、 +cold 先于 warm;偶数对先 baseline,奇数对先 candidate。每次调用都是新 Python +进程;cold 退休独占 broker,warm 保留 broker。磁盘缓存不受控,不代表冷磁盘。 +一分钟宿主负载范围为 8.35–88.73;保留全部尾部观察,不将源码 cold p95 差异 +归因于代码。 + +| 所选操作 | Broker | 基线秒数,中位数 / p95 | 候选秒数,中位数 / p95 | 每对候选减基线的中位数,秒 | +| --- | --- | --- | --- | --- | +| Source help | 无 | 0.136 / 0.193 | 0.138 / 0.168 | +0.0010 | +| Source SQLite task | cold | 3.579 / 5.997 | 3.617 / 9.234 | -0.0195 | +| Source SQLite task | warm | 2.673 / 3.019 | 2.657 / 2.979 | -0.0602 | +| Wheel help | 无 | 0.087 / 0.107 | 0.083 / 0.116 | -0.0033 | +| Wheel SQLite task | cold | 3.722 / 4.266 | 3.663 / 4.430 | -0.0847 | +| Wheel SQLite task | warm | 2.707 / 3.111 | 2.797 / 3.156 | -0.0457 | + +p95 使用 nearest rank。配对差的中位数与两 arm 中位数之差是不同统计量。 +本轮未证明明显延迟或 CPU 收益:arm 中位数和尾部变化方向不一致,task CPU +相近。数据证明代表性对照完整执行及语义一致,不证明速度阈值、完整 V10 或 +长程收益。 + +| Task 路径 | Target 与等待子进程 CPU 秒数,中位数(基线 → 候选) | Target/等待子进程峰值 RSS MiB,中位数(基线 → 候选) | 独占 broker CPU 秒数,中位数(基线 → 候选) | Broker 结束 RSS MiB,中位数 / p95(基线 → 候选) | +| --- | --- | --- | --- | --- | +| Source cold | 1.311 → 1.325 | 75.75 → 75.58 | 0.770 → 0.775 | 127.9 / 134.8 → 128.6 / 133.5 | +| Source warm | 1.235 → 1.226 | 76.15 → 76.16 | 0.180 → 0.185 | 146.5 / 148.4 → 129.8 / 148.7 | +| Wheel cold | 1.353 → 1.369 | 75.94 → 76.56 | 0.805 → 0.810 | 129.5 / 134.4 → 130.1 / 134.1 | +| Wheel warm | 1.273 → 1.260 | 76.05 → 76.69 | 0.190 → 0.180 | 100.4 / 136.3 → 101.4 / 140.2 | + +上述 RSS 不是并发进程树峰值;warm broker 结束 RSS 还反映其保留生命周期。 +Help 的 target/等待子进程 RSS 中位数为 17.56–17.85 MiB,CPU 中位数为 +0.046–0.056 秒。未插桩 stdout 在两 arm 均为 help 325 字节、source task +3,557 字节、wheel task 3,550 字节,stderr 为空。Target/等待子进程块 I/O +计数不覆盖全部 broker/native I/O。 + +独立插桩确认两种发行形态的 task 加载均为 538 → 533,仅移除前述五个 DSH +模块;help 保持 12 个模块。Parent read-open 为 source cold 1,680 → 1,675、 +source warm 1,269 → 1,264、wheel cold 1,683 → 1,677、wheel warm +1,272 → 1,267。Task write-open 保持 cold 284、warm 279,观测到的 parent +本地 socket 连接均为 238。覆盖的 task 发送/接收字节范围为 +893,698–894,864 / 392,608–392,859;source 候选少发送五字节,wheel 不变。 +Help 在 source 打开 31 个文件、wheel 32 个,无写入或 socket 流量。 +插桩 task parent CPU 为 0.635–0.774 秒、峰值 RSS 62.3–65.5 MiB;等待子进程 +CPU 0.528–0.573 秒、峰值 RSS 76.1–78.6 MiB。单次捕获只作清点,与重复计时分开。 + +四个版本组合的同源隔离真实 SQLite 夹具均通过普通 committed replay、typed-result +持久化后中断、原 journal 恢复及 committed replay。独立平方整数产物/转移 oracle +确认产物字节相同,host invocation/attempt、writeback、扣额各一次。Resume +不重复 dispatch,replay 不重复 effect。移除最后一个平方值时,四组均以 exit 1 / +failed 拒绝,dispatch 一次而 writeback/扣额为零。所有独占 broker 均取得 +shutdown 与 OS 退出读回,实验 broker 已全部退出。 + +准备/编排耗时 1,018.786 秒,包含有重叠的构建、安装、夹具及缓存工作,各阶段 +不能求和。独立拒绝阶段为 16.056 秒、插桩 39.116 秒、八个 parent/实际 Node +profile 捕获 54.880 秒。恢复/重放 target 耗时单独记录,其完整编排成本尚未覆盖。 +既有 `performance-diagnosis` recipe 在 Python 观察中显示 workspace capture、 +journal checkpoint、Git probe 及 quota preparation;Node profile 包含启动、 +请求 dispatch、idle 与 retirement。采样栈权重不是 benchmark 延迟或根因证明; +inclusive 权重、warmup 与 retirement 不能相加作为 task 节省。完整 context、 +进程树/native/thread/模型成本仍未知。单体 wheel 仍约 8.9 MiB,不证明拆包安装。 + +**证据驱动的下一步:** 保留已验证的兼容/加载接缝,停止为未证明的提速扩展这些 +helper。优先有界刻画既有完整 parser/export 闭包或实测 workspace/journal/bridge +owner;采纳性能变更前必须另做匹配的真实路径对照。本 checkpoint 不新增 owner、 +预算、preset 或默认行为。R0 完整清点、R1/minimal、D2/D5/D6、I3/D3、 +hard-lease/context/model 和拆包安装验收仍开放;采纳仍受独立 exact-head +评审及必需验证约束。 + ## 附录 B:决策日志 | 日期 | 决策 | 负责人 / 批准 | 备选 | 变更的规范章节 | @@ -615,6 +824,8 @@ R0 可以先于目录移动交付,但不代表内核合格。R3/R4 可选, | E4 | 所选 CLI 加载接缝 | `9cae3b28939f72fa4aadab5cfe49e19bcbdab372` | 第 4 节链接的入口/CLI | 源码审计 | 无新性能测量 | | E5 | DSH 装配范例 | 固定上游 commit | [`minimal.patch.yml`](https://github.com/deepseek-ai/deepseek-harness/blob/f3803c96974ad4a94195fb97e5c637e9c2a3afe3/packages/bundle/web-app/presets/minimal.patch.yml)、[`standard.patch.yml`](https://github.com/deepseek-ai/deepseek-harness/blob/f3803c96974ad4a94195fb97e5c637e9c2a3afe3/packages/bundle/web-app/presets/standard.patch.yml)、[`sdk-minimal`](https://github.com/deepseek-ai/deepseek-harness/blob/f3803c96974ad4a94195fb97e5c637e9c2a3afe3/packages/bundle/sdk-minimal/README.md) | 源码审计 | 共享预设装配与自包含 SDK bundle;不代表 LoopX 宿主资格或权限建议 | | E6 | Pi core/上下文范例 | 固定上游 commit | [`agent` core](https://github.com/badlogic/pi-mono/blob/f14813e96510a7cf7562a120e809c6a134af073d/packages/agent/README.md) | 源码审计 | Agent/tool/event core,以及 `transformContext`、`convertToLlm`、`prepareRequest`、`finishTurn` 接缝;无 LoopX authority 或性能声明 | +| E7 | 有界成对成本实验 | `a9485073f` / `86d9c542d`;上述具名环境 | 上述成对成本 checkpoint;既有入口/SQLite 夹具及 performance-diagnosis owner | 499 个观察;源码组完整,wheel 不完整;缓存归因未取得资格 | 仅公开安全汇总;不证明提速、完整 V10、拆包安装或模型成本 | +| E8 | 字节码匹配的代表性对照 | `546c08c0d` / `0df5ad792`;上述具名环境 | 上述匹配字节码 checkpoint;相同 CLI/SQLite 及 performance-diagnosis owner | 六组各 30 对,共 360 个观察;语义/拒绝及缓存读回通过;未证明明显收益 | 仅公开安全汇总;后续集成、完整 V10、minimal、拆包安装及模型成本未取得资格 | ## 附录 D:被拒绝或被取代的备选 diff --git a/loopx/cli_commands/turn_run_once.py b/loopx/cli_commands/turn_run_once.py index 11db4e23cf..4e82d3f611 100644 --- a/loopx/cli_commands/turn_run_once.py +++ b/loopx/cli_commands/turn_run_once.py @@ -1,8 +1,10 @@ from __future__ import annotations import argparse +import sys as _sys from collections.abc import Callable, Mapping from pathlib import Path +from typing import TYPE_CHECKING as _TYPE_CHECKING from typing import Any from ..capabilities.explore.composition_frontier import ( @@ -42,15 +44,37 @@ from .turn_cadence import managed_cadence_start from .turn_host_request import build_codex_cli_host_runner, prepare_turn_host_request from .turn_decision import collect_turn_status_payload, current_turn_host_launch_decision -from .turn_dsh_host import build_dsh_host_runner, build_dsh_operation_host_runner from .turn_rendering import build_turn_error_payload from .turn_post_writeback import drain_committed_turn_post_writeback_hooks from .turn_todo_writeback import captured_todo_text_sha256, write_turn_validated_completion +if _TYPE_CHECKING: + from .turn_dsh_host import ( + build_dsh_host_runner as build_dsh_host_runner, + build_dsh_operation_host_runner as build_dsh_operation_host_runner, + ) + EXACT_SETTLEMENT_READBACK_NOT_FOUND = ( "exact settlement readback unexpectedly returned not-found" ) +# Keep the existing import and patch seams without loading an unselected host. +_DSH_HOST_EXPORTS = ("build_dsh_host_runner", "build_dsh_operation_host_runner") + + +def __getattr__(name: str) -> Any: + if name not in _DSH_HOST_EXPORTS: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + from . import turn_dsh_host + + value = getattr(turn_dsh_host, name) + globals()[name] = value + return value + + +def __dir__() -> list[str]: + return sorted(set(globals()) | set(_DSH_HOST_EXPORTS)) + def execute_turn_run_once( args: argparse.Namespace, @@ -816,7 +840,7 @@ def resolve_built_in_session_binding( session_binding_resolver = resolve_built_in_session_binding elif args.host == "dsh": if getattr(args, "dsh_operation_module", None): - host_runner = build_dsh_operation_host_runner( + host_runner = getattr(_sys.modules[__name__], "build_dsh_operation_host_runner")( args, runtime_root=runtime_root, registry_path=registry_path, workspace=project, environ=operator_environ, goal_admission=goal_admission, @@ -829,7 +853,7 @@ def resolve_dsh_operation_session(envelope: Mapping[str, Any]) -> dict[str, str] session_binding_resolver = resolve_dsh_operation_session else: - host_runner = build_dsh_host_runner( + host_runner = getattr(_sys.modules[__name__], "build_dsh_host_runner")( args, workspace=project, environ=operator_environ, ) @@ -937,3 +961,6 @@ def on_managed_start_admitted() -> None: execution_started=execution_started, journal_readback=journal_readback, ) + + +__all__ = [name for name in globals() if not name.startswith("_")] + list(_DSH_HOST_EXPORTS) diff --git a/loopx/control_plane/turn_driver/host_bridge_transport.py b/loopx/control_plane/turn_driver/host_bridge_transport.py index 1dbed9c89a..79f012086b 100644 --- a/loopx/control_plane/turn_driver/host_bridge_transport.py +++ b/loopx/control_plane/turn_driver/host_bridge_transport.py @@ -6,11 +6,35 @@ import queue import signal import subprocess +import sys as _sys import threading import time from typing import Any +from typing import TYPE_CHECKING as _TYPE_CHECKING -from ...extensions.process_runtime import force_terminate_process, write_process_stdin_before_deadline +if _TYPE_CHECKING: + from ...extensions.process_runtime import ( + force_terminate_process as force_terminate_process, + write_process_stdin_before_deadline as write_process_stdin_before_deadline, + ) + +# Preview needs the bridge type; process helpers belong to execution or an +# explicit caller import. Keep their existing identity and patch seams. +_PROCESS_EXPORTS = ("force_terminate_process", "write_process_stdin_before_deadline") + + +def __getattr__(name: str) -> Any: + if name not in _PROCESS_EXPORTS: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + from ...extensions import process_runtime + + value = getattr(process_runtime, name) + globals()[name] = value + return value + + +def __dir__() -> list[str]: + return sorted(set(globals()) | set(_PROCESS_EXPORTS)) class GuardedHostBridge: @@ -61,8 +85,8 @@ def send(self, frame: str) -> None: if self.next(deadline=self.deadline) != {"kind": "ready"}: raise RuntimeError("managed Host bridge did not become ready") self.requested = True - write_process_stdin_before_deadline(self.process, frame, deadline=self.deadline, - cancel_and_reap=self.cancel) + write_stdin = getattr(_sys.modules[__name__], "write_process_stdin_before_deadline") + write_stdin(self.process, frame, deadline=self.deadline, cancel_and_reap=self.cancel) def cancel(self) -> None: if self.cancelled: @@ -73,7 +97,8 @@ def cancel(self) -> None: if not self.requested: # No request bytes have been sent, so this bridge cannot own a # Host. This also handles suspension before JS handlers exist. - force_terminate_process(self.process, process_tree_owned=True) + getattr(_sys.modules[__name__], "force_terminate_process")( + self.process, process_tree_owned=True) self.not_launched = True elif self.process.poll() is None: self.process.terminate() @@ -98,7 +123,8 @@ def cancel(self) -> None: # Killing the bridge alone cannot certify an unobserved detached # Host. Retain the launching record and fail instead of claiming it. if self.process.poll() is None: - force_terminate_process(self.process, process_tree_owned=True) + getattr(_sys.modules[__name__], "force_terminate_process")( + self.process, process_tree_owned=True) raise RuntimeError("managed Host bridge cleanup unconfirmed") from error def close_output(self) -> None: @@ -112,3 +138,6 @@ def close_output(self) -> None: self.reader.join(timeout=self.cleanup_timeout) if not self.reader.is_alive(): self.output.close() + + +__all__ = [name for name in globals() if not name.startswith("_")] + list(_PROCESS_EXPORTS) diff --git a/loopx/doctor_git.py b/loopx/doctor_git.py index df09510587..0c71ef8341 100644 --- a/loopx/doctor_git.py +++ b/loopx/doctor_git.py @@ -5,13 +5,31 @@ import os import re import subprocess +import sys as _sys from dataclasses import dataclass from enum import Enum from pathlib import Path from typing import Any +from typing import TYPE_CHECKING as _TYPE_CHECKING from urllib.parse import urlsplit -from .extensions.process_runtime import terminate_process_tree +if _TYPE_CHECKING: + from .extensions.process_runtime import terminate_process_tree as terminate_process_tree + + +# Successful read-only Git probes do not need process cleanup. Keep the former +# helper export and patch point, resolving its owner only when requested. +def __getattr__(name: str) -> Any: + if name != "terminate_process_tree": + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + from .extensions.process_runtime import terminate_process_tree + + globals()[name] = terminate_process_tree + return terminate_process_tree + + +def __dir__() -> list[str]: + return sorted(set(globals()) | {"terminate_process_tree"}) # `doctor` inspects a checkout the operator may not control, so every Git read @@ -64,7 +82,7 @@ def _retire_owned_probe(process: subprocess.Popen[str]) -> None: """ try: - terminate_process_tree(process, grace_seconds=0.2) + getattr(_sys.modules[__name__], "terminate_process_tree")(process, grace_seconds=0.2) except (OSError, RuntimeError): # The answer is already unknown; an unconfirmed cleanup must not turn a # read-only diagnostic into a traceback. @@ -324,3 +342,6 @@ def trusted_release_ref_for_root( "git_ref": f"{remote}/{expected_ref}", } return None + + +__all__ = [name for name in globals() if not name.startswith("_")] + ["terminate_process_tree"] diff --git a/tests/architecture/test_control_plane_import_boundaries.py b/tests/architecture/test_control_plane_import_boundaries.py index 1bfbdd12f4..df1d7e0321 100644 --- a/tests/architecture/test_control_plane_import_boundaries.py +++ b/tests/architecture/test_control_plane_import_boundaries.py @@ -324,6 +324,31 @@ def test_read_adapters_do_not_load_writeback_facades_for_leaf_helpers() -> None: assert completed.returncode == 0, completed.stderr +def test_turn_run_once_defers_dsh_builders_and_preserves_exports() -> None: + completed = subprocess.run( + [sys.executable, "-c", """ +import sys +import loopx.cli_commands.turn_run_once as command +assert 'loopx.cli_commands.turn_dsh_host' not in sys.modules +assert 'loopx.dsh_goal_mode.turn_host_adapter' not in sys.modules +names = {'build_dsh_host_runner', 'build_dsh_operation_host_runner'} +assert names <= set(dir(command)) +assert not hasattr(command, 'unknown_host_builder') +from loopx.cli_commands.turn_run_once import build_dsh_host_runner, build_dsh_operation_host_runner +from loopx.cli_commands import turn_dsh_host +assert build_dsh_host_runner is turn_dsh_host.build_dsh_host_runner +assert build_dsh_operation_host_runner is turn_dsh_host.build_dsh_operation_host_runner +exports = {} +exec('from loopx.cli_commands.turn_run_once import *', exports) +assert exports['build_dsh_host_runner'] is build_dsh_host_runner +assert exports['build_dsh_operation_host_runner'] is build_dsh_operation_host_runner +assert exports['execute_turn_run_once'] is command.execute_turn_run_once +"""], + cwd=REPOSITORY_ROOT, capture_output=True, check=False, text=True, + ) + assert completed.returncode == 0, completed.stderr + + def test_settlement_plan_import_does_not_load_native_capture_io() -> None: # A command template needs the native output profile, not capture binding # or the complete quota/Goal routing graph. Keep the typed kernel usable @@ -373,6 +398,54 @@ def test_turn_preview_import_does_not_select_report_delivery_or_lark_hosts() -> assert completed.returncode == 0, completed.stderr +def test_host_bridge_defers_process_helpers_and_preserves_exports() -> None: + completed = subprocess.run( + [sys.executable, "-c", """ +import sys +import loopx.control_plane.turn_driver.host_bridge_transport as bridge +assert 'loopx.extensions.process_runtime' not in sys.modules +names = {'force_terminate_process', 'write_process_stdin_before_deadline'} +assert names <= set(dir(bridge)) +assert not hasattr(bridge, 'unknown_process_helper') +from loopx.control_plane.turn_driver.host_bridge_transport import force_terminate_process, write_process_stdin_before_deadline +from loopx.extensions import process_runtime +assert force_terminate_process is process_runtime.force_terminate_process +assert write_process_stdin_before_deadline is process_runtime.write_process_stdin_before_deadline +exports = {} +exec('from loopx.control_plane.turn_driver.host_bridge_transport import *', exports) +assert exports['force_terminate_process'] is force_terminate_process +assert exports['write_process_stdin_before_deadline'] is write_process_stdin_before_deadline +assert exports['GuardedHostBridge'] is bridge.GuardedHostBridge +"""], + cwd=REPOSITORY_ROOT, capture_output=True, check=False, text=True, + ) + assert completed.returncode == 0, completed.stderr + + +def test_doctor_git_defers_cleanup_and_preserves_exports() -> None: + completed = subprocess.run( + [sys.executable, "-c", """ +import sys +from pathlib import Path +from loopx import doctor_git +assert 'loopx.extensions.process_runtime' not in sys.modules +assert doctor_git._run_git(Path.cwd(), 'rev-parse', '--show-toplevel').returncode == 0 +assert 'loopx.extensions.process_runtime' not in sys.modules +assert 'terminate_process_tree' in dir(doctor_git) +assert not hasattr(doctor_git, 'unknown_process_helper') +from loopx.doctor_git import terminate_process_tree +from loopx.extensions import process_runtime +assert terminate_process_tree is process_runtime.terminate_process_tree +exports = {} +exec('from loopx.doctor_git import *', exports) +assert exports['terminate_process_tree'] is terminate_process_tree +assert exports['git_source_metadata'] is doctor_git.git_source_metadata +"""], + cwd=REPOSITORY_ROOT, capture_output=True, check=False, text=True, + ) + assert completed.returncode == 0, completed.stderr + + def test_lazy_report_and_lark_exports_preserve_original_owner_identity() -> None: # Compatibility includes from-imports, attribute access, dir(), star # exports for report facades, and the existing Lark command patch seam. diff --git a/tests/control_plane/test_host_process.py b/tests/control_plane/test_host_process.py index 5932d7e1d2..9130796b03 100644 --- a/tests/control_plane/test_host_process.py +++ b/tests/control_plane/test_host_process.py @@ -488,6 +488,60 @@ def test_default_turn_deadline_is_optional_and_real_transport_accepts_none(tmp_p assert result["returncode"] == 0 +@pytest.mark.parametrize("operation", ["send", "cancel"]) +def test_host_bridge_preserves_process_helper_patch_points( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch, operation: str, +) -> None: + from loopx.control_plane.turn_driver import host_bridge_transport as bridge_module + + calls: list[str] = [] + original_write = bridge_module.write_process_stdin_before_deadline + original_terminate = bridge_module.force_terminate_process + + def write(*args, **kwargs): + calls.append("send") + return original_write(*args, **kwargs) + + def terminate(*args, **kwargs): + assert kwargs["process_tree_owned"] is True + calls.append("cancel") + return original_terminate(*args, **kwargs) + + monkeypatch.setattr(bridge_module, "write_process_stdin_before_deadline", write) + monkeypatch.setattr(bridge_module, "force_terminate_process", terminate) + source = """ +import json, sys +print(json.dumps({"kind": "ready"}), flush=True) +assert json.loads(sys.stdin.readline()) == {"message": "bridge λ😀"} +print(json.dumps({"kind": "result", "outcome": "exited"}), flush=True) +""" + with subprocess.Popen( + [sys.executable, "-u", "-c", source], cwd=tmp_path, + stdin=subprocess.PIPE, stdout=subprocess.PIPE, text=True, + encoding="utf-8", start_new_session=True, + ) as process: + bridge = bridge_module.GuardedHostBridge( + process, deadline=time.monotonic() + 5, cleanup_timeout=2, + ) + try: + if operation == "send": + bridge.send(json.dumps({"message": "bridge λ😀"}, ensure_ascii=False) + "\n") + process.wait(timeout=5) + bridge.cancel() + bridge.cancel() + assert calls == [operation] + assert process.poll() is not None + assert bridge.not_launched is (operation == "cancel") + if operation == "send": + assert bridge.result == {"kind": "result", "outcome": "exited"} + finally: + if process.poll() is None: + process.kill() + process.wait(timeout=5) + bridge.close_output() + assert not bridge.reader.is_alive() and bridge.output.closed + + @pytest.mark.parametrize("broken_observation", ["not-json", "[]"]) def test_host_bridge_parser_failure_closes_reader_without_claiming_cleanup(broken_observation): from loopx.control_plane.turn_driver.host_bridge_transport import GuardedHostBridge diff --git a/tests/test_doctor_git_probe_budget.py b/tests/test_doctor_git_probe_budget.py index c9f5ed8618..1a15a065b2 100644 --- a/tests/test_doctor_git_probe_budget.py +++ b/tests/test_doctor_git_probe_budget.py @@ -102,8 +102,9 @@ def test_doctor_finishes_when_git_blocks( @pytest.mark.skipif(os.name != "posix", reason="core.fsmonitor needs a POSIX hook") +@pytest.mark.parametrize("cleanup_error", [False, True]) def test_a_blocked_query_takes_the_helpers_it_started( - tmp_path: Path, monkeypatch: pytest.MonkeyPatch + tmp_path: Path, monkeypatch: pytest.MonkeyPatch, cleanup_error: bool, ) -> None: """Git's own helpers are in its group, and the budget has to retire them. @@ -127,9 +128,20 @@ def test_a_blocked_query_takes_the_helpers_it_started( ) monkeypatch.setattr(doctor_git, "GIT_PROBE_TIMEOUT_SECONDS", 0.8) + cleanup_calls: list[float] = [] + original_terminate = doctor_git.terminate_process_tree + + def terminate(process, *, grace_seconds): + cleanup_calls.append(grace_seconds) + original_terminate(process, grace_seconds=grace_seconds) + if cleanup_error: + raise RuntimeError("fixture cleanup could not confirm retirement") + + monkeypatch.setattr(doctor_git, "terminate_process_tree", terminate) started = time.monotonic() assert doctor_git._run_git(repo, "status", "--porcelain") is None assert time.monotonic() - started < 6 + assert cleanup_calls == [0.2] retired = heartbeats.stat().st_size assert retired > 0, "the fixture hook never ran, so this proves nothing" diff --git a/tests/test_loopx_turn_driver.py b/tests/test_loopx_turn_driver.py index 3afb07ea43..e5c1047393 100644 --- a/tests/test_loopx_turn_driver.py +++ b/tests/test_loopx_turn_driver.py @@ -2432,7 +2432,16 @@ def run(extra=()): def test_turn_run_once_cli_dsh_fresh_sessions_and_iteration_failure( tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, ) -> None: + original_builder = turn_run_once_command.build_dsh_host_runner + builder_calls: list[Path] = [] + + def patched_builder(*args: Any, **kwargs: Any) -> Any: + builder_calls.append(kwargs["workspace"]) + return original_builder(*args, **kwargs) + + monkeypatch.setattr(turn_run_once_command, "build_dsh_host_runner", patched_builder) project, runtime, registry = _write_live_fixture(tmp_path) host_project = tmp_path / "isolated-dsh-workspace" host_project.mkdir() @@ -2507,6 +2516,7 @@ def run(turn_instance_id: str, context: str) -> dict[str, object]: run("fresh-fixture-2", "fresh") run("resume-fixture-1", "resume") run("resume-fixture-2", "resume") + assert builder_calls == [host_project] * 4 session_ids = (host_project / "dsh-session-ids.txt").read_text( encoding="utf-8" From d39fac28091668b319a6c1e3c4f987da27571b85 Mon Sep 17 00:00:00 2001 From: Jiaqiiii523 Date: Sat, 7 Nov 2026 00:46:09 -0500 Subject: [PATCH 3/4] refactor(work-items): read the execution handoff modes from the vocabulary owner (#8184) --- .../work_items/execution_preparation.ts | 4 ++-- .../execution_preparation.test.ts | 22 +++++++++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/loopx/control_plane/work_items/execution_preparation.ts b/loopx/control_plane/work_items/execution_preparation.ts index 25fd0996fc..8105338135 100644 --- a/loopx/control_plane/work_items/execution_preparation.ts +++ b/loopx/control_plane/work_items/execution_preparation.ts @@ -1,3 +1,4 @@ +import {EXECUTION_HANDOFF_MODES} from "../coordination/handoff_mode_vocabulary.ts"; import type {JsonObject} from "../effect_program.ts"; import {EffectRuntimeRequestError} from "../effect_runtime_errors.ts"; import { @@ -13,7 +14,6 @@ export const EXECUTION_PREPARATION_FACTS_SCHEMA_VERSION = export const EXECUTION_PREPARATION_SCHEMA_VERSION = "execution_preparation_v0"; -const HANDOFF_MODES = ["soft_claim", "hard_lease"] as const; const LEASE_STATUSES = ["active", "released", "unknown"] as const; function leaseFact(value: unknown, label: string): JsonObject { @@ -55,7 +55,7 @@ export function projectExecutionPreparation(value: unknown): JsonObject { } const goalId = requireNonEmptyString(request.goal_id, "goal_id"); const agentId = requireNonEmptyString(request.agent_id, "agent_id"); - const handoffMode = requireStringLiteral(request.handoff_mode, HANDOFF_MODES, "handoff_mode"); + const handoffMode = requireStringLiteral(request.handoff_mode, EXECUTION_HANDOFF_MODES, "handoff_mode"); const authorityAvailable = requireBoolean(request.authority_available, "authority_available"); const selected = requireJsonObject(request.selected_todo, "selected_todo"); const todoId = requireNonEmptyString(selected.todo_id, "selected_todo.todo_id"); diff --git a/tests/control_plane_ts/execution_preparation.test.ts b/tests/control_plane_ts/execution_preparation.test.ts index 9e1c3c7491..f3812e62b9 100644 --- a/tests/control_plane_ts/execution_preparation.test.ts +++ b/tests/control_plane_ts/execution_preparation.test.ts @@ -1,6 +1,10 @@ import assert from "node:assert/strict"; import test from "node:test"; +import { + EXECUTION_HANDOFF_MODES, + HANDOFF_MODES, +} from "../../loopx/control_plane/coordination/handoff_mode_vocabulary.ts"; import {EffectRuntimeRequestError} from "../../loopx/control_plane/effect_runtime_errors.ts"; import {projectExecutionPreparation} from "../../loopx/control_plane/work_items/execution_preparation.ts"; @@ -85,3 +89,21 @@ test("malformed facts are rejected", () => { /active requires status=active/, ); }); + +test("the execution handoff vocabulary stays narrower than the historical one", () => { + // `legacy` still decodes stored state and receipts, but it is not an accepted + // execution-preparation input. Borrowing the shared `HANDOFF_MODES` list here + // instead of the execution subset would admit it silently, so both the + // membership and the rejection are pinned against the vocabulary owner. + for (const mode of EXECUTION_HANDOFF_MODES) { + assert.doesNotThrow(() => projectExecutionPreparation(facts({handoff_mode: mode}))); + } + const historical: readonly string[] = HANDOFF_MODES; + const execution: readonly string[] = EXECUTION_HANDOFF_MODES; + assert.ok(historical.includes("legacy"), "legacy stays decodable for stored receipts"); + assert.ok(!execution.includes("legacy"), "legacy is not a new execution target"); + assert.throws( + () => projectExecutionPreparation(facts({handoff_mode: "legacy"})), + EffectRuntimeRequestError, + ); +}); From ebb39f4db088cc77927c4514d614476ad0a5c84e Mon Sep 17 00:00:00 2001 From: Jiaqiiii523 Date: Sat, 7 Nov 2026 00:46:29 -0500 Subject: [PATCH 4/4] test(control-plane): keep hot-path capture out of inline JSON smokes (#8182) --- examples/control_plane/status-projection-cache-smoke.py | 4 ++++ examples/control_plane/status-quota-perf-budget-smoke.py | 4 ++++ examples/control_plane/todo_lifecycle_fixtures.py | 8 +++++++- examples/custom-runtime-minimal-cli-turn-smoke.py | 7 ++++++- 4 files changed, 21 insertions(+), 2 deletions(-) diff --git a/examples/control_plane/status-projection-cache-smoke.py b/examples/control_plane/status-projection-cache-smoke.py index 18366b2f84..9283f05de1 100644 --- a/examples/control_plane/status-projection-cache-smoke.py +++ b/examples/control_plane/status-projection-cache-smoke.py @@ -94,6 +94,10 @@ def run_cli_result( ) -> tuple[dict[str, Any], int, str]: env = os.environ.copy() env["PYTHONPATH"] = str(REPO_ROOT) + # This smoke reads machine JSON and asserts the inline decision/cache fields; + # the model-facing capture transport replaces that body with a reader + # reference, so keep the same opt-out the canary harness uses in argv. + env["LOOPX_CONTEXT_DELIVERY"] = "off" result = subprocess.run( [ sys.executable, diff --git a/examples/control_plane/status-quota-perf-budget-smoke.py b/examples/control_plane/status-quota-perf-budget-smoke.py index 98840073cd..3fc0cd9eee 100644 --- a/examples/control_plane/status-quota-perf-budget-smoke.py +++ b/examples/control_plane/status-quota-perf-budget-smoke.py @@ -112,6 +112,10 @@ def run_cli_result( ) -> tuple[dict[str, Any], float, int, str]: env = os.environ.copy() env["PYTHONPATH"] = str(REPO_ROOT) + # This smoke measures status/quota decision latency and reads the inline + # `should_run` field; the model-facing capture transport replaces that body + # with a reader reference, so keep the same opt-out the canary harness uses. + env["LOOPX_CONTEXT_DELIVERY"] = "off" started = time.perf_counter() result = subprocess.run( [ diff --git a/examples/control_plane/todo_lifecycle_fixtures.py b/examples/control_plane/todo_lifecycle_fixtures.py index b1a813dbc4..7dc4cc838f 100644 --- a/examples/control_plane/todo_lifecycle_fixtures.py +++ b/examples/control_plane/todo_lifecycle_fixtures.py @@ -5,9 +5,11 @@ import contextlib import io import json +import os import sys import tempfile from pathlib import Path +from unittest.mock import patch REPO_ROOT = Path(__file__).resolve().parents[2] @@ -126,7 +128,11 @@ def run_fixture_cli( ] stdout = io.StringIO() stderr = io.StringIO() - with contextlib.redirect_stdout(stdout), contextlib.redirect_stderr(stderr): + # These fixtures consume machine JSON and read the inline decision fields; + # the model-facing capture transport replaces that body with a reader + # reference, so opt out in-process the way the canary harness does in argv. + with patch.dict(os.environ, {"LOOPX_CONTEXT_DELIVERY": "off"}), \ + contextlib.redirect_stdout(stdout), contextlib.redirect_stderr(stderr): try: returncode = loopx_cli_main(argv) except SystemExit as exc: diff --git a/examples/custom-runtime-minimal-cli-turn-smoke.py b/examples/custom-runtime-minimal-cli-turn-smoke.py index 8299bc7afb..bfa3f6b9f7 100755 --- a/examples/custom-runtime-minimal-cli-turn-smoke.py +++ b/examples/custom-runtime-minimal-cli-turn-smoke.py @@ -13,10 +13,12 @@ import contextlib import io import json +import os import sys import tempfile from pathlib import Path from typing import Any +from unittest.mock import patch REPO_ROOT = Path(__file__).resolve().parents[1] if str(REPO_ROOT) not in sys.path: @@ -62,7 +64,10 @@ def run_cli(registry_path: Path, *args: str) -> dict[str, Any]: *args, ] buf = io.StringIO() - with contextlib.redirect_stdout(buf): + # This smoke reads machine JSON and asserts the inline decision fields; the + # model-facing capture transport replaces that body with a reader reference, + # so opt out in-process the way the canary harness does in argv. + with patch.dict(os.environ, {"LOOPX_CONTEXT_DELIVERY": "off"}), contextlib.redirect_stdout(buf): code = loopx_cli_main(argv) raw = buf.getvalue().strip() payload: dict[str, Any] = json.loads(raw) if raw else {}