diff --git a/docs/architecture/rfcs/monorepo-distribution-split-v0.md b/docs/architecture/rfcs/monorepo-distribution-split-v0.md index 3d0b327f7b..1f253100e4 100644 --- a/docs/architecture/rfcs/monorepo-distribution-split-v0.md +++ b/docs/architecture/rfcs/monorepo-distribution-split-v0.md @@ -824,6 +824,91 @@ 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 version loading and matched costs + +The independent proposal `8a79d8602e699e51452ca1f43dc9b9527c81aa1b` at baseline +`5a58635028fbef1316dc66531daf5fdb655e9b13` selects the existing version registrar +and handler through `cli_runtime`. It reuses the canonical global grammar, +format precedence, usage hooks and controller guard. Rejected parsing retains +full-CLI fallback and diagnostics; explicitly bound package or full-CLI exports +retain their registrar/handler patch points without triggering lazy exports +on ordinary queries. Public imports, identity and types remain +unchanged. No capability, provider, package, permission, preset or TypeScript +decision owner is added. The separate DSH/helper and negative cost proposal is +[#8185](https://github.com/loopx-project/loopx/pull/8185); its E7/E8 observations +are distinct from this version-path comparison and are not a dependency. + +The initial `55e3ef45a` comparison retains 120 ordinary-query observations +(source median 1.546 → 0.059 seconds; wheel 1.554 → 0.072). A later independent +case finds that it ignores a patched package-facade handler, returning 0 instead +of 37. Its passing premerge did not cover that branch. The failure and original +inputs/data remain retained; they do not qualify facade compatibility. The +proposal repairs both package registrar and handler adoption, then freezes the +new exact-head comparison below. + +- **Real caller:** source and freshly installed monolithic wheels at both + immutable revisions exercise the actual `loopx` console. All 27 cases per + distribution have identical stdout, stderr and exit status, including + global/local/repeated formats, explicit paths, help, unknown/abbreviated + options, missing/empty values and fallback. Independent unknown-command/ + option cases reject with exit 2 and no result. Version works with Node absent + in all four variants; outer-controller read behavior and full-CLI patch points + remain compatible. +- **Loading census:** separate parent instrumentation records 1,157 → 10 + LoopX modules in both distributions, with the candidate contained in the + baseline set. Source read-opens are 1,286 → 28 and wheel 1,289 → 29. + Each version capture has zero write-opens, subprocesses, observed local IPC + connections and covered socket bytes. Stdout remains 81 bytes; stderr is empty. +- **Frozen comparison:** all four environments have verified provenance, + CPython 3.13.5 and identical 57 dependency versions. Stdlib, dependencies and + all 1,421 LoopX sources have valid timestamp bytecode in owned cache prefixes; + post-measurement/profile source/header inventories have no missing/invalid + caches and match the preflight. Every sample has fresh Python and disables + bytecode writes; disk cache is uncontrolled. No broker/warm-broker comparison + is made. Order is source then wheel, AB on even pairs and BA on odd pairs. + Both corrected groups complete 30 pairs: 120 accepted observations in 62.246 + seconds. Including the first study, aggregate sampling is 166.191 seconds, + below the unchanged 1,200-second cap. All observations are retained. + +| Distribution | Baseline seconds, median / p95 | Proposal seconds, median / p95 | CPU seconds, median (base → proposal) | RSS MiB, median (base → proposal) | Median paired proposal minus baseline, seconds | +| --- | --- | --- | --- | --- | --- | +| Source | 0.980 / 1.056 | 0.055 / 0.075 | 0.520 → 0.037 | 90.17 → 16.81 | -0.9242 | +| Monolithic wheel | 0.948 / 1.147 | 0.056 / 0.077 | 0.511 → 0.038 | 89.89 → 16.60 | -0.8993 | + +p95 uses nearest rank; paired differences and differences of arm medians are +distinct statistics. Median latency falls about 94% in both source and wheel +on this macOS arm64 environment; CPU and target RSS also fall. One-minute host +load ranges from 8.25 to 9.06. This qualifies a material version-query +improvement under frozen conditions, without adopting portable D6 budgets or +claiming real managed-Turn/long-horizon savings. Usage covers the target and +waited descendants; no child is launched in the census. Native detail and +complete authoring/model/maintenance costs remain unknown. + +The heartbeat-to-final-freeze interval is about 2,404 seconds, including +overlapping recovery, initial study, correction, dependency/build/install, +test, cache and parity work; overlapping phases cannot be summed. Cache +preparation is recorded separately. Corrected console/facade parity takes +82.759 seconds, instrumentation 2.539 and four parent profiles 4.155. Earlier +parity/instrumentation/profile costs of 136.239 / 3.830 / 7.089 seconds remain +retained. Cross-study timing levels are not treated as a code comparison. +Existing `performance-diagnosis` recipes show export loading and full parser +construction in baseline sampled stacks; weights remain separate from repeated +latency. Monolithic packaging is verified, without split-install or installed +host-upgrade qualification. + +Seven selected-loading cases fail on the baseline and pass on the proposal. +The package registrar/handler case passes on the baseline, fails on the initial +candidate and passes on the correction; canonical diagnostic and full-CLI +patch-point cases pass in both baseline and proposal. All 161 entrypoint cases and +focused lint pass. Owner-scoped mypy with `--follow-imports=skip` fails with +33 identical diagnostics at base/proposal after line normalization; this is +neither a pass nor the full configured type check. Adoption requires final-head +validation, independent review and merge readiness. I3's minimum one minor +release window remains in force; no D3/expiry decision is made. R0's wider census, +R1/minimal, D2/D5/D6, hard-lease/context/model and split-install acceptance remain +open. The bounded refactor reuses its existing parser/handler; larger runtime +and package changes retain their accepted owners. + ### 2026-11-06 — Discovery integration and the next executable boundary - **Integrated:** [#8012](https://github.com/loopx-project/loopx/pull/8012) @@ -1288,6 +1373,7 @@ open; independent exact-head review and required validation govern adoption. | 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 | +| E9 | Selected version loading and matched cost | `5a5863502` / `8a79d8602`; macOS arm64 / CPython 3.13.5 | Version checkpoint above; existing console/version and performance-diagnosis owners | Corrected two groups × 30 pairs; 120 observations; public-facade/parity/cache readback pass; material query improvement | Initial 120 observations and facade failure retained; public-safe aggregates; full Turn/minimal, split install, host upgrade and complete costs unqualified; E7/E8 remain with #8185 | | 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 | 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 9c9ea152a0..6a3f6a75f4 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,69 @@ R0 可以先于目录移动交付,但不代表内核合格。R3/R4 可选, 不关闭完整 R0/V10、M1、拆包安装、R1 managed minimal 或 D1–D7。不改变 preset、 前端操作、权限或持久化状态;受保护执行仍由既有 owner 所有。 +### 2026-11-06 — Version 按需加载及匹配成本 + +独立候选 `8a79d8602e699e51452ca1f43dc9b9527c81aa1b` 对比基线 +`5a58635028fbef1316dc66531daf5fdb655e9b13`,由 `cli_runtime` 选择既有 +version registrar 与 handler,复用规范全局语法、format 优先级、usage hook +及 controller guard。解析拒绝保留完整 CLI fallback 与诊断;已显式加载完整 +CLI 或绑定 package 导出的调用保留其 registrar/handler patch 点;普通查询不会 +触发 lazy exports。公开 import、身份及类型不变。 +不新增 capability、provider、package、权限、preset 或 TypeScript 决策 owner。 +独立 DSH/helper 及负面成本提案仍在 [#8185](https://github.com/loopx-project/loopx/pull/8185); +其 E7/E8 与本次 version 对照分开,也不构成依赖。 + +初始 `55e3ef45a` 对照的 120 个普通查询观察继续保留:source 中位数 +1.546 → 0.059 秒、wheel 1.554 → 0.072 秒。随后独立用例发现它忽略已替换的 +package-facade handler,返回 0 而非 37;此前通过的 premerge 未覆盖该分支。 +原输入、数据和失败保留,不将其视为 facade 兼容资格。提案补回 package +registrar 与 handler 的采纳后,另行冻结下列精确 head 对照。 + +- **真实调用:** 两个不可变版本的源码及新安装单体 wheel 均经实际 `loopx` + console 执行。每种发行形态的 27 个用例,stdout、stderr、退出状态完全一致, + 覆盖全局/局部/重复 format、显式路径、help、未知/缩写参数、缺失/空值及 + fallback。独立未知命令/参数 oracle 以 exit 2 拒绝且无结果。四个组合在缺少 + Node 时均可查询 version;outer-controller 读行为及完整 CLI patch 点兼容。 +- **加载清点:** 独立 parent 插桩记录,两种发行形态均为 1,157 → 10 个 LoopX + 模块,候选是基线的子集。Source read-open 1,286 → 28,wheel 1,289 → 29。 + 每个 version 捕获的 write-open、子进程、观测到的本地 IPC 连接及覆盖 socket + 字节均为零。Stdout 保持 81 字节,stderr 为空。 +- **冻结对照:** 四个环境均核验来源,使用 CPython 3.13.5 及相同 57 个依赖 + 版本。标准库、依赖及全部 1,421 个 LoopX 源文件,在独占 cache prefix 具有 + 有效时间戳字节码;测量/profile 后的源码及 header 清点与预检一致,无缺失/ + 无效缓存。每次采样是新 Python 进程,禁止字节码写入;磁盘缓存不受控,不比较 + broker/warm broker。顺序为源码先于 wheel,偶数对 AB、奇数对 BA。两组各 + 完成 30 对,共 120 个有效观察,采样 62.246 秒。计入首轮后合计采样 + 166.191 秒,低于不变的 1,200 秒上限;全部观察保留。 + +| 发行形态 | 基线秒数,中位数 / p95 | 候选秒数,中位数 / p95 | CPU 秒数,中位数(基线 → 候选) | RSS MiB,中位数(基线 → 候选) | 配对候选减基线的中位数,秒 | +| --- | --- | --- | --- | --- | --- | +| Source | 0.980 / 1.056 | 0.055 / 0.075 | 0.520 → 0.037 | 90.17 → 16.81 | -0.9242 | +| 单体 wheel | 0.948 / 1.147 | 0.056 / 0.077 | 0.511 → 0.038 | 89.89 → 16.60 | -0.8993 | + +p95 使用 nearest rank;配对差中位数与两 arm 中位数之差不同。本次 macOS +arm64 条件下,source 与 wheel 中位延迟均约下降 94%,CPU 与 target RSS +也下降。一分钟宿主负载为 8.25–9.06。本片证明冻结条件下的明显版本查询收益, +不采纳可移植 D6 预算,不声明真实 managed Turn 或长程节省。用量覆盖 target +及等待子进程,清点中未启动子进程;native 细节及完整编写/模型/维护成本未知。 + +从本次唤醒时间戳到最终冻结约 2,404 秒,包含有重叠的入口恢复、首轮实验、修正、 +依赖/构建/安装、测试、缓存及一致性验证;重叠阶段不可求和。缓存准备单独记录。 +修正后的 console/facade 一致性为 82.759 秒、插桩 2.539 秒、四个 parent +profile 4.155 秒;首轮 136.239 / 3.830 / 7.089 秒成本继续保留。 +不同轮的耗时水平不作为代码对照。既有 +`performance-diagnosis` recipe 在基线采样栈中显示 export loading 及完整 parser +构建;栈权重与重复延迟分开。已验证单体打包,不代表拆包安装或已安装宿主升级。 + +七项 selected-loading 用例在基线失败、候选通过。Package registrar/handler +用例在基线通过、初始候选失败、修正后通过;规范诊断及完整 CLI patch 点在基线/ +候选均通过。完整入口 161 项及聚焦 lint 通过。Owner-scoped mypy 使用 +`--follow-imports=skip`,基线与候选在行号归一后均有 33 项相同错误,不是通过或 +完整配置的类型检查。采纳仍需最终 head 验证、独立评审及 merge readiness。 +I3 至少一个 minor release 的窗口继续有效,不作 D3/到期决策。R0 更广清点、 +R1/minimal、D2/D5/D6、hard-lease/context/model 及拆包安装验收仍开放。 +有界重构复用既有 parser/handler;更大 runtime/package 改动保留在其已接受 owner。 + ### 2026-11-06 — 发现路径集成与下一条可执行边界 - **已集成:** [#8012](https://github.com/loopx-project/loopx/pull/8012) @@ -969,6 +1032,7 @@ hard-lease/context/model 和拆包安装验收仍开放;采纳仍受独立 | 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 或性能声明 | +| E9 | Version 按需加载及匹配成本 | `5a5863502` / `8a79d8602`;macOS arm64 / CPython 3.13.5 | 上述 version checkpoint;既有 console/version 及 performance-diagnosis owner | 修正后两组各 30 对,共 120 个观察;公开 facade/一致性/缓存读回通过;版本查询明显改善 | 首轮 120 个观察及 facade 失败保留;仅公开安全汇总;完整 Turn/minimal、拆包安装、宿主升级及完整成本未取得资格;E7/E8 保留在 #8185 | | E7 | 有界成对成本实验 | `a9485073f` / `86d9c542d`;上述具名环境 | 上述成对成本 checkpoint;既有入口/SQLite 夹具及 performance-diagnosis owner | 499 个观察;源码组完整,wheel 不完整;缓存归因未取得资格 | 仅公开安全汇总;不证明提速、完整 V10、拆包安装或模型成本 | | E8 | 字节码匹配的代表性对照 | `546c08c0d` / `0df5ad792`;上述具名环境 | 上述匹配字节码 checkpoint;相同 CLI/SQLite 及 performance-diagnosis owner | 六组各 30 对,共 360 个观察;语义/拒绝及缓存读回通过;未证明明显收益 | 仅公开安全汇总;后续集成、完整 V10、minimal、拆包安装及模型成本未取得资格 | diff --git a/loopx/cli_runtime.py b/loopx/cli_runtime.py index fee6385e67..0a84f75b3f 100644 --- a/loopx/cli_runtime.py +++ b/loopx/cli_runtime.py @@ -74,7 +74,7 @@ # Existing CLI vocabulary; this group selects loading, not registry authority. _REGISTRY_INSPECTION_COMMANDS = frozenset({"registry", "registry-boundary"}) _SELECTED_COMMANDS = _STATUS_COMMANDS | GLOBAL_READ_COMMANDS | PROJECT_LIFECYCLE_COMMANDS | _REGISTRY_INSPECTION_COMMANDS | { - "todo", "quota", "task-lease", "change-window", "delegation", "turn", "doctor", "commands", + "todo", "quota", "task-lease", "change-window", "delegation", "turn", "doctor", "commands", "version", "authority-archive", "coordination-shadow", "extension", "slash-commands", "manager-inbox", "agent-context", "agent-directory", "agent-capabilities", @@ -475,6 +475,10 @@ def _build_selected_parser(command: str) -> LoopXArgumentParser: from .help_surface import register_command_reference register_command_reference(subparsers) + elif command == "version": + from .cli_commands.version import register_version_command + + register_version_command(subparsers, add_subcommand_format) elif command == "performance-diagnosis": from .capabilities.performance_diagnosis.cli import register_performance_diagnosis_commands @@ -801,6 +805,12 @@ def _dispatch_selected(args: argparse.Namespace, raw_argv: list[str]) -> int: inspect_registry_boundary=inspect_registry_boundary, render_registry_boundary_markdown=render_registry_boundary_markdown, ) + elif args.command == "version": + from .cli_commands.version import handle_version_command + + result = handle_version_command( + args, output_format=output_format, print_payload=print_payload, + ) elif args.command == "capability": from .cli_commands.capability import handle_capability_command @@ -865,6 +875,16 @@ def main(argv: list[str] | None = None) -> int: def _run_command(command: str | None, raw_argv: list[str]) -> int: + if command == "version": + version_exports = sys.modules.get("loopx.cli_commands") + # Inspect bound exports without triggering the lazy facade. Explicit + # package/full-CLI imports retain their registrar and handler patch points. + if "loopx.cli" in sys.modules or ( + version_exports is not None + and ("handle_version_command" in vars(version_exports) + or "register_version_command" in vars(version_exports)) + ): + return _run_full_cli(raw_argv) if command in _SELECTED_COMMANDS: try: args = _build_selected_parser(command).parse_args(raw_argv) diff --git a/tests/test_cli_entrypoint.py b/tests/test_cli_entrypoint.py index 7e33ef3070..e3849eacb5 100644 --- a/tests/test_cli_entrypoint.py +++ b/tests/test_cli_entrypoint.py @@ -331,17 +331,89 @@ def test_version_flag_skips_full_cli_import() -> None: assert completed.returncode == 0, completed.stderr -def test_other_arguments_delegate_to_full_cli() -> None: +@pytest.mark.parametrize("arguments", [ + ["version"], + ["--format", "json", "version"], + ["version", "--format", "json"], + ["--format=markdown", "version", "--format=json"], + ["--format=json", "version", "--format=markdown"], + ["--registry", "missing-registry.json", "--runtime-root", "missing-runtime", + "--cli-bin", "fixture-loopx", "version", "--format", "json"], + ["--format", "markdown", "--format", "json", "version"], +]) +def test_version_command_uses_its_selected_owner(arguments: list[str]) -> None: + completed = run_cli_main( + "loopx.entrypoint", arguments, + forbidden_modules=("loopx.cli", "loopx.chat_manager", "loopx.control_plane.effect_runtime"), + environment={"LOOPX_USAGE_PING": "0"}, + ) + assert completed.returncode == 0, completed.stderr + from loopx import __version__ + + if arguments[-1] == "--format=markdown" or arguments == ["version"]: + assert completed.stdout == f"loopx {__version__}\n\n" + else: + assert json.loads(completed.stdout) == { + "ok": True, "schema_version": "loopx_version_v0", + "name": "loopx", "version": __version__, + } + + +def test_version_command_retains_full_cli_diagnostics() -> None: + arguments = [ + ["version", "--help"], ["version", "-h"], + ["version", "--unknown-option"], ["version", "--format", "yaml"], + ["version", "--format"], ["version", "--format="], + ["--format", "yaml", "version"], ["--format", "", "version"], + ["--format", "version"], ["--format", "json", "version", "extra"], + ["--cli-bin", "", "version"], ["--registry", "version"], + ["--for", "json", "version"], ["version", "--for", "json"], + ["version", "--registry", "missing.json"], ["version", "--version"], + ["--version", "version"], ["--", "version"], + ] + assert run_cli_batch("loopx.entrypoint", arguments) == run_cli_batch("loopx.cli", arguments) + + +def test_version_command_retains_explicit_full_cli_patch_points() -> None: script = """ +from loopx import cli from loopx.entrypoint import main +from loopx.cli_commands.version import register_version_command -raise SystemExit(main(["--format", "json", "version"])) +assert cli.register_version_command is register_version_command +seen = [] +def patched(args, **kwargs): + seen.append(args.command) + return 37 +cli.handle_version_command = patched +assert main(["version"]) == 37 +assert seen == ["version"] """ - completed = run_isolated_script(script) + completed = run_isolated_script(script, environment={"LOOPX_USAGE_PING": "0"}) + assert completed.returncode == 0, completed.stderr + + +def test_version_command_retains_explicit_package_export_patch_points() -> None: + script = """ +from loopx import cli_commands +from loopx.cli_commands import handle_version_command, register_version_command +from loopx.entrypoint import main +assert cli_commands.handle_version_command is handle_version_command +seen = [] +def register(*args, **kwargs): + seen.append("register") + return register_version_command(*args, **kwargs) +def patched(args, **kwargs): + seen.append(args.command) + return 37 +cli_commands.register_version_command = register +cli_commands.handle_version_command = patched +assert main(["version"]) == 37 +assert seen == ["register", "version"] +""" + completed = run_isolated_script(script, environment={"LOOPX_USAGE_PING": "0"}) assert completed.returncode == 0, completed.stderr - payload = json.loads(completed.stdout) - assert payload["schema_version"] == "loopx_version_v0" def test_top_level_help_skips_full_cli_import() -> None: