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
60 changes: 60 additions & 0 deletions docs/architecture/rfcs/monorepo-distribution-split-v0.md
Original file line number Diff line number Diff line change
Expand Up @@ -3677,3 +3677,63 @@ closure, paired complete costs, progressive product control, R1/M3 and original
Goal regression remain unqualified. Keep the nine original requirements,
previous failed attempts and frozen budget failures; no minimal preset,
provider default, package publication or paid operation is promoted here.

### 2026-11-13 — Full Turn defers unselected provider implementations

The installed core Turn previously loaded BM25 and OpenViking while resolving
Reward Memory configuration, and loaded the anti-entropy inspector even when
retirement governance was disabled. Context-provider exports and the existing
builtin builder registry now resolve implementations on explicit use. A pure
OpenViking scope owner is shared by binding normalization and the provider;
the original URI grammar, public aliases and Python serialization identity are
retained. Anti-entropy still uses the existing typed activation and applicable
basis; its feedback builder resolves only after those checks. No capability,
provider default, authority, claim, stop or settlement rule is added or relaxed.

The 12 fresh-process checks cover registry rejections/custom builders, real
local BM25 retrieval, binding classification, disabled/enabled retirement and
attribute/from/star exports. The original 11-check corpus fails five loading
oracles on immutable base `441c6077e7c5c8e896ea79562761fd483baf157e`, while its
six export checks pass. Sixteen URI success/rejection cases and protocol-4
pickle bytes match between installed base and candidate; cross-version reads
pass both ways. Definitions move to the pure scope source file: declaration
source introspection is not preserved by the original-module alias.

The final independent Python 3.11 core wheel contains 1886 runtime files that
match source bytes (SHA256
`bc36c92dd437b9418e21031e185fefd04984c91a30169cbd866a3c712d92b4fa`).
Source capability regression passes 628 cases. Real isolated File and SQLite
execution commits a checked artifact and one settlement; original-key replay
has no Host, state or quota effects. Instrumented full-Turn module counts,
including the package root, change from 525 to 516 on execution and 438 to 432
on replay; capability counts change from 111 to 102 and 56 to 50. Execution
RPC counts remain 221/220 and replay remains 11. The unselected OpenViking,
BM25 and anti-entropy inspection implementations disappear from those paths.
These counts describe the observed Python path, not the complete minimal closure.

Matched installed observations use macOS 27 arm64, Python 3.11.15, Node 26.0.0
and five
alternating base/candidate pairs per backend and phase. Each invocation starts
a new Python process. Before first execution, the isolated managed RPC server
is normally stopped and must restart; completed-Turn replay reuses that server.
Setup and independent readback are outside timing; OS, bytecode and compilation
caches are not reset. Nearest-rank p95 is the maximum of five samples.

| Backend / phase | Wall median / p95 ms, base → candidate | Median invocation RSS MiB, base → candidate |
| --- | --- | --- |
| SQLite / fresh execution | 2922.5 / 3561.9 → 3006.8 / 4139.9 | 81.81 → 81.25 |
| SQLite / completed-Turn replay | 809.6 / 948.6 → 889.0 / 1415.0 | 44.73 → 44.42 |
| File / fresh execution | 2858.7 / 3005.5 → 2862.5 / 2985.0 | 81.09 → 81.36 |
| File / completed-Turn replay | 645.4 / 679.7 → 692.5 / 773.9 | 44.73 → 44.28 |

All 40 invocations preserve the same independent result/effect fingerprint;
stdout sizes remain 4002 bytes for execution and 4548 for replay. The candidate
has slower SQLite medians and a higher tail: no latency or complete-cost
improvement is qualified. Invocation RSS excludes the persistent Node server;
zero block-I/O counters do not prove zero file access. Separate controlled
observers verify Python import/open/process/loopback events and Node module/
loopback/exit events, but do not provide OS-wide closure, complete IPC/I/O or
model-token, service-cost and human-attention accounting. The Host remains
synthetic. Full V1–V5/V10, real model adoption/recovery, progressive product
control, R1/M3, independent Standard packages and original-goal regression remain
open. Preserve all nine original requirements and earlier frozen failures.
49 changes: 49 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 @@ -2866,3 +2866,52 @@ journal 使用的 transaction-key 预约保留精确恢复兼容分支;已启
实际模型采用、完整 V1–V5/V10、默认闭包、配对完整成本、渐进产品控制、R1/M3 与
原始 Goal 回归仍未验收。保留原九项要求、此前失败尝试和冻结预算失败;本次没有
推广 minimal preset、provider 默认、包发布或付费操作。

### 2026-11-13 — 完整 Turn 延后加载未选择的 Provider 实现

此前独立 core 的已安装 Turn 在解析 Reward Memory 配置时加载 BM25 和
OpenViking,并在退休治理关闭时加载 anti-entropy 检查器。现在 context-provider
公开导出和原 builtin builder registry 在明确使用时解析实现。绑定归一化与实际
provider 共用纯 OpenViking scope owner,原 URI 语法、公开别名与 Python 序列化
身份保留。Anti-entropy 仍使用原 typed 激活规则和适用 basis,通过这些检查后才
解析 feedback builder。没有新增或放宽 capability、provider 默认值、authority、
claim、stop 或 settlement 规则。

12 项新进程检查覆盖 registry 拒绝/custom builder、真实本地 BM25 检索、绑定
分类、关闭/开启退休治理,以及 attribute/from/star 导出。原 11 项检查在不可变
基线 `441c6077e7c5c8e896ea79562761fd483baf157e` 上有五项加载 oracle 失败,
六项公开导出检查通过。两个安装版本的 16 类 URI 成功/拒绝结果与 protocol-4
pickle 字节一致,跨版本双向读取通过。定义移到纯 scope 源文件,旧模块别名
不能保留声明源码的 introspection 行为。

最终独立 Python 3.11 core wheel 的 1886 个 runtime 文件与源码字节一致,SHA256
为 `bc36c92dd437b9418e21031e185fefd04984c91a30169cbd866a3c712d92b4fa`。
源码 capability 回归通过 628 项。真实隔离 File 与 SQLite 执行提交已检查产物,
且只结算一次;原 key 重放没有 Host、state 或 quota 副作用。含 package root 的
受控完整 Turn 模块数在执行时由 525 降到 516,重放由 438 降到 432;capability
模块数分别由 111 降到 102、56 降到 50。执行 RPC 数仍为 221/220,重放仍为 11。
这些路径中不再出现未选择的 OpenViking、BM25 和 anti-entropy 检查实现。计数只
描述已观测的 Python 路径,不能代表完整最小闭包。

配对安装测量使用 macOS 27 arm64、Python 3.11.15、Node 26.0.0,每个 backend、
阶段各交替运行
五对基线/候选。每次调用启动新 Python 进程;首次执行前通过原 owner 正常停止
隔离的 managed RPC server,使其重新启动,已完成 Turn 重放则复用该 server。
准备与独立回读不计入耗时,OS、bytecode 和 compilation cache 不重置。
Nearest-rank p95 等于五个样本的最大值。

| Backend/阶段 | Wall 中位数/p95 ms,基线 → 候选 | 调用 RSS 中位数 MiB,基线 → 候选 |
| --- | --- | --- |
| SQLite/首次执行 | 2922.5 / 3561.9 → 3006.8 / 4139.9 | 81.81 → 81.25 |
| SQLite/已完成 Turn 重放 | 809.6 / 948.6 → 889.0 / 1415.0 | 44.73 → 44.42 |
| File/首次执行 | 2858.7 / 3005.5 → 2862.5 / 2985.0 | 81.09 → 81.36 |
| File/已完成 Turn 重放 | 645.4 / 679.7 → 692.5 / 773.9 | 44.73 → 44.28 |

40 次调用的独立结果/副作用指纹一致,stdout 大小保持首次执行 4002 bytes、
重放 4548 bytes。候选 SQLite 中位数较慢、尾延迟较高,本轮不取得延迟或完整成本
改善资格。调用 RSS 不包含持久 Node server,block-I/O 计数为零也不能证明没有
文件访问。独立受控 observer 验证 Python import/open/process/loopback 事件,以及
Node module/loopback/exit 事件,但不能提供 OS 级完整闭包、完整 IPC/I/O、model
token、service cost 或人工注意力核算。Host 仍为 synthetic。完整 V1–V5/V10、
真实模型采用/恢复、渐进产品控制、R1/M3、独立 Standard packages 与原目标回归
仍开放,保留原九项要求和此前冻结失败。
63 changes: 38 additions & 25 deletions loopx/capabilities/anti_entropy/__init__.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,43 @@
from __future__ import annotations

from .agent_tool import anti_entropy_inspect
from .catalog_entry import ANTI_ENTROPY_CATALOG_ENTRY
from .cli import run_anti_entropy_verify
from .contract import (
ANTI_ENTROPY_CAPABILITY_ID,
ANTI_ENTROPY_RECEIPT_SCHEMA_VERSION,
ANTI_ENTROPY_RETIREMENT_BASIS_SCHEMA_VERSION,
AntiEntropyFinding,
AntiEntropyReceipt,
RetirementBasis,
)
from .feedback import (
ANTI_ENTROPY_INSPECT_TOOL_DESCRIPTOR,
build_anti_entropy_turn_context,
run_anti_entropy_feedback_cycle,
)
from .oracles import (
audit_ghost_code,
evaluate_diff_hygiene,
verify_delete_first,
)
from .turn_context import (
anti_entropy_context_registration,
anti_entropy_context_result,
)
from importlib import import_module
from typing import Any

# Importing the Turn registration or contract is not a request to run the
# inspector. Explicit compatibility exports still resolve to their owners.
_EXPORTS = {
"anti_entropy_inspect": "agent_tool",
"ANTI_ENTROPY_CATALOG_ENTRY": "catalog_entry",
"run_anti_entropy_verify": "cli",
"ANTI_ENTROPY_CAPABILITY_ID": "contract",
"ANTI_ENTROPY_RECEIPT_SCHEMA_VERSION": "contract",
"ANTI_ENTROPY_RETIREMENT_BASIS_SCHEMA_VERSION": "contract",
"AntiEntropyFinding": "contract",
"AntiEntropyReceipt": "contract",
"RetirementBasis": "contract",
"ANTI_ENTROPY_INSPECT_TOOL_DESCRIPTOR": "feedback",
"build_anti_entropy_turn_context": "feedback",
"run_anti_entropy_feedback_cycle": "feedback",
"audit_ghost_code": "oracles",
"evaluate_diff_hygiene": "oracles",
"verify_delete_first": "oracles",
"anti_entropy_context_registration": "turn_context",
"anti_entropy_context_result": "turn_context",
}


def __getattr__(name: str) -> Any:
module = _EXPORTS.get(name)
if module is None:
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
value = getattr(import_module(f".{module}", __name__), name)
globals()[name] = value
return value


def __dir__() -> list[str]:
return sorted(set(globals()) | set(__all__))


__all__ = [
"ANTI_ENTROPY_CAPABILITY_ID",
Expand Down
17 changes: 15 additions & 2 deletions loopx/capabilities/anti_entropy/turn_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@
from __future__ import annotations

import json
import sys
from collections.abc import Mapping
from importlib import import_module
from pathlib import Path
from typing import Any

from ...control_plane.capability_hooks import TurnContextHookRegistration
from .contract import ANTI_ENTROPY_CAPABILITY_ID, RetirementBasis
from .feedback import build_anti_entropy_turn_context

ANTI_ENTROPY_CONTEXT_MAX_RESULT_BYTES = 64 * 1024
_BASIS_KEYS = ("retirement_basis", "anti_entropy_basis")
Expand All @@ -26,6 +27,18 @@
_UNSET = object()


def __getattr__(name: str) -> Any:
if name != "build_anti_entropy_turn_context":
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
value = getattr(import_module(".feedback", __package__), name)
globals()[name] = value
return value


def __dir__() -> list[str]:
return sorted(set(globals()) | {"build_anti_entropy_turn_context"})


def _embedded_json_objects(text: str):
"""Yield JSON objects embedded in free text, outermost first.

Expand Down Expand Up @@ -484,7 +497,7 @@ def anti_entropy_context_result(
)
is_recovery = bool(recovery_evidence)

context = build_anti_entropy_turn_context(
context = getattr(sys.modules[__name__], "build_anti_entropy_turn_context")(
basis=basis,
goal_id=goal_id,
workspace=workspace,
Expand Down
61 changes: 37 additions & 24 deletions loopx/capabilities/context_providers/__init__.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,40 @@
"""Reusable bounded context-provider integrations for LoopX capabilities."""

from .base import (
canonical_context_text,
canonical_context_lines,
canonical_context_matches,
ContextProviderItem,
ContextProviderRetrieval,
ContextProviderSync,
)
from .factory import build_context_provider, register_context_provider
from .bm25 import BM25ContextProvider, build_bm25_context_provider
from .openviking import (
OpenVikingContextProvider,
build_openviking_context_provider,
)
from .service_ownership import (
context_provider_service_restarted,
load_context_provider_service_ownership,
)


register_context_provider("openviking", build_openviking_context_provider)
register_context_provider("bm25", build_bm25_context_provider)
"""Context-provider exports resolve without selecting unrelated implementations."""

from __future__ import annotations

from importlib import import_module
from typing import Any

_EXPORTS = {
"canonical_context_text": "base",
"canonical_context_lines": "base",
"canonical_context_matches": "base",
"ContextProviderItem": "base",
"ContextProviderRetrieval": "base",
"ContextProviderSync": "base",
"build_context_provider": "factory",
"register_context_provider": "factory",
"BM25ContextProvider": "bm25",
"build_bm25_context_provider": "bm25",
"OpenVikingContextProvider": "openviking",
"build_openviking_context_provider": "openviking",
"context_provider_service_restarted": "service_ownership",
"load_context_provider_service_ownership": "service_ownership",
}


def __getattr__(name: str) -> Any:
module = _EXPORTS.get(name)
if module is None:
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
value = getattr(import_module(f".{module}", __name__), name)
globals()[name] = value
return value


def __dir__() -> list[str]:
return sorted(set(globals()) | set(_EXPORTS))


__all__ = [
"ContextProviderItem",
Expand Down
21 changes: 20 additions & 1 deletion loopx/capabilities/context_providers/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,26 @@


ContextProviderBuilder = Callable[[Mapping[str, Any]], ContextProvider]
_CONTEXT_PROVIDER_BUILDERS: dict[str, ContextProviderBuilder] = {}


def _build_openviking(config: Mapping[str, Any]) -> ContextProvider:
from .openviking import build_openviking_context_provider

return build_openviking_context_provider(config)


def _build_bm25(config: Mapping[str, Any]) -> ContextProvider:
from .bm25 import build_bm25_context_provider

return build_bm25_context_provider(config)


# Builtin ids are already registered, so extensions retain the same duplicate
# rejection. Resolving that registry does not select either provider's code.
_CONTEXT_PROVIDER_BUILDERS: dict[str, ContextProviderBuilder] = {
"openviking": _build_openviking,
"bm25": _build_bm25,
}


def register_context_provider(
Expand Down
Loading
Loading