Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions docs/architecture/rfcs/monorepo-distribution-split-v0.md
Original file line number Diff line number Diff line change
Expand Up @@ -786,6 +786,40 @@ Approval needs a reviewed decision, not implementation progress or silence.
exact expiry remains a release-owner decision. M1 is open. No module-count,
loading-performance, split-package or R1 managed-Turn benefit is claimed.

### 2026-11-04 — Capability discovery selected loading

- **Baseline:** `79b781865`. `capability list` / `show` and command help now
reuse the existing capability registrar and catalog handler without loading
the full CLI or Chat. `inspect`, `bind` and `invoke` execution, the explicit
full-CLI handler patch point, registry routing and canonical rejected-request
diagnostics retain their existing paths. The local discovery set reuses CLI
vocabulary; it creates no capability, provider, protocol or authority owner.
- **Evidence:** `tests/test_cli_entrypoint.py` covers output/help parity,
declared-but-absent and real installed-disabled providers, hidden helpers,
missing/invalid manifests and no process/network/write activation. The five
loading oracles fail on the baseline and pass on the candidate. All 23 focused
cases also pass against an independently installed wheel outside the checkout
with Node absent; this does not upgrade an existing host. Eight additional
immutable base/candidate rejection pairs preserve output and exit status.
- **Measured closure:** three fresh-process help/list/show workloads use 20
samples per revision, one discarded warm-up and alternating order on macOS
arm64/Python 3.13.5. Both source snapshots have warm filesystem caches and no
bytecode; Node and telemetry are absent. The real `main()` path loads 362
LoopX modules instead of 1,149, with 832 read-open observations instead of
2,494. Output bytes are identical; observed writes, process starts, network
connections and thread starts are zero. Timing and RSS evidence includes
equal instrumentation and interpreter startup; it is scoped PR evidence,
not a portable D6 threshold or installed-host/task-benefit claim.
- **Validation holds:** the broader source selection has 320 passing cases and
one failing `test_settlement_plan_import_does_not_load_native_capture_io`.
The immutable baseline fails identically. Configured mypy likewise reports
the same 789 errors in 126 files on both revisions. Neither failure is waived
or relabeled as a pass; focused parity does not certify all required checks.
- **Remaining:** this extends R0-style discovery qualification, independently
of the public-status proposal #8011. It does not close full R0/V10, M1, split
installation, R1 managed minimal or D1–D7. No preset, frontend operation,
permission or persisted state changes; protected execution keeps its owner.

## Appendix B: Decision log

| Date | Decision | Owner / approval | Alternatives | Normative sections changed |
Expand Down
26 changes: 26 additions & 0 deletions docs/architecture/rfcs/monorepo-distribution-split-v0.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -570,6 +570,32 @@ R0 可以先于目录移动交付,但不代表内核合格。R3/R4 可选,
版本,D3 精确退役时间仍由发布负责人决定。M1 未关闭,不声明模块数量、加载性能、
拆包或 R1 managed Turn 收益。

### 2026-11-04 — Capability 发现路径的所选加载

- **基线:** `79b781865`。`capability list` / `show` 及命令帮助复用既有
capability registrar 与 catalog handler,不再加载完整 CLI 或 Chat。
`inspect`、`bind`、`invoke` 的执行、显式完整 CLI 的 handler patch 点、registry
路由及非法请求的规范诊断仍保留原路径。本地发现集合复用 CLI 词汇,不新增
capability、provider、协议或权限 owner。
- **证据:** `tests/test_cli_entrypoint.py` 覆盖输出/帮助一致性、已声明但未安装
及真实已安装且禁用的 provider、隐藏 helper、缺失/非法 manifest,以及不启动
进程、不联网、不写文件。五个加载 oracle 在基线失败、候选通过;全部 23 个聚焦
用例也在 checkout 外独立安装的 wheel 上、无 Node 条件下通过,不升级既有宿主。
另有八组不可变基线/候选拒绝对照保留输出和退出码。
- **加载闭包测量:** macOS arm64/Python 3.13.5 上,三个新进程 help/list/show
负载各自每版本采样 20 次,丢弃一次 warm-up 并交替顺序。两份源码快照均为暖文件
缓存、无 bytecode,Node 与遥测关闭。真实 `main()` 路径加载的 LoopX 模块由
1,149 降到 362,read-open 观察由 2,494 降到 832。输出字节完全一致;观察到的
文件写入、进程启动、网络连接和线程启动均为零。耗时与 RSS 包含相同插桩及解释器
启动,是 PR 中的限定证据,不是跨机器 D6 阈值,也不声明已安装宿主或任务收益。
- **验证保留项:** 更广的源码选择为 320 个通过,
`test_settlement_plan_import_does_not_load_native_capture_io` 一个失败;不可变
基线有完全相同的失败。配置的 mypy 在两个版本上同样报告 126 个文件中的 789 个
错误。两项失败均不豁免、不改写为通过;聚焦一致性不证明所有必需检查通过。
- **剩余:** 本片独立于公开 status 提案 #8011,扩展 R0 类发现路径的资格证据,
不关闭完整 R0/V10、M1、拆包安装、R1 managed minimal 或 D1–D7。不改变 preset、
前端操作、权限或持久化状态;受保护执行仍由既有 owner 所有。

## 附录 B:决策日志

| 日期 | 决策 | 负责人 / 批准 | 备选 | 变更的规范章节 |
Expand Down
34 changes: 29 additions & 5 deletions loopx/cli_runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,10 @@
"heartbeat-prompt",
"goal-acceptance",
"host-mode-plan",
"capability",
}
# Existing CLI vocabulary; this set chooses loading, not operation authority.
_CAPABILITY_DISCOVERY_COMMANDS = frozenset({"list", "show"})


class LoopXArgumentParser(argparse.ArgumentParser):
Expand Down Expand Up @@ -409,6 +412,10 @@ def _build_selected_parser(command: str) -> LoopXArgumentParser:
from .cli_commands.host_mode_plan import register_host_mode_plan_command

register_host_mode_plan_command(subparsers, add_subcommand_format)
elif command == "capability":
from .cli_commands.capability import register_capability_commands

register_capability_commands(subparsers, add_subcommand_format)
else: # pragma: no cover - caller guards the private interface
raise ValueError(f"unsupported selected command: {command}")
return parser
Expand Down Expand Up @@ -703,11 +710,20 @@ def _dispatch_selected(args: argparse.Namespace, raw_argv: list[str]) -> int:
if guard_result is not None:
return guard_result
registry_path, _registry_was_configured = resolve_cli_registry(args, raw_argv)
result = dispatch_common_command(
args,
registry_path=registry_path,
allow_missing_registry=not user_supplied_registry(raw_argv),
)
if args.command == "capability":
from .cli_commands.capability import handle_capability_command

# Keep the explicit full CLI's handler and protected operations unchanged.
result = handle_capability_command(
args, registry_path=registry_path, runtime_root_arg=args.runtime_root,
output_format=output_format, print_payload=print_payload,
)
else:
result = dispatch_common_command(
args,
registry_path=registry_path,
allow_missing_registry=not user_supplied_registry(raw_argv),
)
if result is None: # pragma: no cover - caller guards the private interface
raise AssertionError(f"selected command was not dispatched: {args.command}")
return result
Expand Down Expand Up @@ -755,6 +771,14 @@ def _run_command(command: str | None, raw_argv: list[str]) -> int:
args = _build_selected_parser(command).parse_args(raw_argv)
except _SelectedParseRejected:
return _run_full_cli(raw_argv)
if command == "capability":
if args.capability_command not in _CAPABILITY_DISCOVERY_COMMANDS:
return _run_full_cli(raw_argv)
try:
return _dispatch_selected(args, raw_argv)
except _SelectedParseRejected:
# Catalog/manifest refusals use their canonical argparse diagnostic.
return _run_full_cli(raw_argv)
return _dispatch_selected(args, raw_argv)
return _run_full_cli(raw_argv)

Expand Down
136 changes: 136 additions & 0 deletions tests/test_cli_entrypoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -918,6 +918,142 @@ def reject_activation(event, args):
assert not list(tmp_path.iterdir())


@pytest.mark.parametrize("arguments", [
["list"], ["show", "performance-diagnosis"],
["--help"], ["list", "--help"], ["show", "--help"],
])
def test_capability_discovery_skips_unselected_layers(tmp_path, arguments):
result = run_cli_main("loopx.entrypoint",
["--registry", str(tmp_path / "absent.json"), "--runtime-root", str(tmp_path / "runtime"),
"capability", *arguments],
forbidden_modules=("loopx.cli", "loopx.chat_server", "loopx.chat_runtime",
"loopx.cli_commands.benchmark_dispatch", "loopx.capabilities.content_ops.cli"),
environment={"LOOPX_USAGE_PING": "0"})
assert result.returncode == 0, result.stderr
assert not list(tmp_path.iterdir())


@pytest.mark.parametrize("format_name", ["json", "markdown"])
@pytest.mark.parametrize("extension_mode", ["absent", "declared", "disabled"])
@pytest.mark.parametrize("command", ["list", "show"])
def test_capability_discovery_preserves_catalog_and_default_off(
tmp_path, format_name, extension_mode, command,
):
from tests.capabilities.test_capability_extension_registry import _write_manifest
from loopx.extensions.runtime import default_extension_state_file, disable_extension, install_extension

registry, runtime = write_command_fixture(tmp_path)
declared_extension = extension_mode != "absent"
arguments = [command]
if command == "show":
arguments.append("sample-report" if declared_extension else "performance-diagnosis")
if declared_extension:
provider = None
if extension_mode == "disabled":
provider = tmp_path / "provider"
provider.write_text(f"#!{sys.executable}\nraise SystemExit(0)\n", encoding="utf-8")
provider.chmod(0o755)
manifest = _write_manifest(tmp_path / "extension.toml", entrypoint=provider)
arguments += ["--extension-manifest", str(manifest)]
if extension_mode == "disabled":
state_file = default_extension_state_file(runtime)
install_extension(manifest, state_file=state_file, execute=True)
disable_extension("sample-extension", state_file=state_file, execute=True)
argv = ["--registry", str(registry), "--runtime-root", str(runtime),
"--format", "markdown", "capability", *arguments, "--format", format_name]
before = {str(path): path.read_bytes() for path in tmp_path.rglob("*") if path.is_file()}
selected = run_cli_main("loopx.entrypoint", argv, environment={"LOOPX_USAGE_PING": "0"})
full = run_cli_main("loopx.cli", argv, environment={"LOOPX_USAGE_PING": "0"})
assert selected.returncode == full.returncode == 0, (selected.stderr, full.stderr)
assert selected.stdout == full.stdout
assert selected.stderr == full.stderr == ""
assert {str(path): path.read_bytes() for path in tmp_path.rglob("*") if path.is_file()} == before
assert runtime.exists() == (extension_mode == "disabled")
if format_name == "json":
payload = json.loads(selected.stdout)
assert payload["schema_version"] == (
"loopx_capability_catalog_v0" if command == "list" else "loopx_capability_detail_v0")
entries = payload["capabilities"] if command == "list" else [payload["capability"]]
assert any(item["id"] == ("sample-report" if declared_extension else "performance-diagnosis")
for item in entries)
assert all(item["id"] != "sample-internal-helper" for item in entries)
if declared_extension:
entry = next(item for item in entries if item["id"] == "sample-report")
assert entry["origin"] == "extension"
assert entry["provider_state"] == {
"declared": True, "installed": extension_mode == "disabled", "enabled": False, "ready": False}


def test_capability_discovery_rejections_keep_canonical_diagnostics(tmp_path):
manifest = tmp_path / "invalid.toml"
manifest.write_text("not valid TOML [", encoding="utf-8")
prefix = ["--registry", str(tmp_path / "absent.json"),
"--runtime-root", str(tmp_path / "runtime"), "capability"]
cases = [["list", "--unknown"], ["show"], ["show", "absent-capability"],
["list", "--format", "yaml"], ["list", "--extension-manifest"],
["list", "--extension-manifest", str(tmp_path / "absent.toml")],
["list", "--extension-manifest", str(manifest)], ["list", "--ext"]]
assert run_cli_batch("loopx.entrypoint", [prefix + case for case in cases]) == (
run_cli_batch("loopx.cli", [prefix + case for case in cases]))
assert not (tmp_path / "runtime").exists()


@pytest.mark.parametrize("command", ["inspect", "bind", "invoke"])
def test_capability_non_discovery_retains_full_cli_patch_point(tmp_path, command):
arguments = ["inspect", "--goal-id", "fixture-goal"] if command == "inspect" else (
[command, "sample-report", "--goal-id", "fixture-goal", "--operation", "read", "--execute"])
if command == "invoke":
arguments += ["--input-json", str(tmp_path / "input.json")]
script = f"""
import os
os.environ["LOOPX_USAGE_PING"] = "0"
import loopx.cli as full
from loopx.entrypoint import main
seen = []
def original_path(args, **kwargs):
seen.append((args.capability_command, args.goal_id, getattr(args, "execute", False)))
return 73
full.handle_capability_command = original_path
assert main(["--registry", {str(tmp_path / "absent.json")!r}, "--runtime-root", {str(tmp_path / "runtime")!r},
"capability", *{arguments!r}]) == 73
assert seen == [({command!r}, "fixture-goal", {command != 'inspect'!r})]
"""
result = run_isolated_script(script)
assert result.returncode == 0, result.stderr
assert not list(tmp_path.iterdir())


@pytest.mark.parametrize("arguments", [["list"], ["show", "performance-diagnosis"]])
def test_capability_discovery_does_not_activate_providers(tmp_path, arguments):
from tests.capabilities.test_capability_extension_registry import _write_manifest

provider = tmp_path / "provider.py"
provider.write_text("raise AssertionError('discovery cannot run a provider')\n", encoding="utf-8")
manifest = _write_manifest(tmp_path / "extension.toml", entrypoint=provider)
script = f"""
import os
import sys
os.environ["LOOPX_USAGE_PING"] = "0"
sys.dont_write_bytecode = True
os.chdir({str(tmp_path)!r})
def reject_activation(event, args):
assert event not in ("subprocess.Popen", "os.system", "os.posix_spawn", "socket.connect"), event
if event == "open":
mode, flags = args[1:3]
assert not (isinstance(mode, str) and any(c in mode for c in "wax+")), args
assert not (isinstance(flags, int) and flags & (os.O_WRONLY | os.O_RDWR | os.O_CREAT)), args
sys.addaudithook(reject_activation)
from loopx.entrypoint import main
assert main(["--registry", {str(tmp_path / "absent.json")!r}, "--runtime-root", {str(tmp_path / "runtime")!r},
"--format", "json", "capability", *{arguments!r}, "--extension-manifest", {str(manifest)!r}]) == 0
"""
before = {str(path): path.read_bytes() for path in tmp_path.rglob("*") if path.is_file()}
result = run_isolated_script(script, environment={"PATH": str(tmp_path / "no-node")})
assert result.returncode == 0, result.stderr
assert {str(path): path.read_bytes() for path in tmp_path.rglob("*") if path.is_file()} == before
assert not (tmp_path / "runtime").exists()


@pytest.mark.parametrize("provider", ["file", "sqlite"])
def test_selected_refresh_preview_keeps_canonical_state_without_full_cli(tmp_path, monkeypatch, provider):
from tests.control_plane.canonical_authority_fixture import initialize_canonical_source_fixture, isolate_sqlite_runtime
Expand Down
Loading