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
33 changes: 33 additions & 0 deletions .github/workflows/package-smoke.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ on:
pull_request:
paths:
- ".github/workflows/package-smoke.yml"
- "packages/loopx-core/**"
- "loopx/**"
- "tests/test_core_distribution.py"
- "packages/loopx-repo-health/**"
- "packages/loopx-obelisk/**"
- "packages/loopx-codex-provider-routing/**"
Expand All @@ -14,6 +17,9 @@ on:
- main
paths:
- ".github/workflows/package-smoke.yml"
- "packages/loopx-core/**"
- "loopx/**"
- "tests/test_core_distribution.py"
- "packages/loopx-repo-health/**"
- "packages/loopx-obelisk/**"
- "packages/loopx-codex-provider-routing/**"
Expand All @@ -28,6 +34,33 @@ concurrency:
cancel-in-progress: true

jobs:
core:
name: loopx-core
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v6
with:
python-version: "3.11"
- uses: actions/setup-node@v6
with:
node-version: "26.0.0"
- uses: astral-sh/setup-uv@v7
- name: Prepare source validation environment
run: uv sync --extra test
- name: Build and separately install the standalone core
run: |
uv run --extra test python packages/loopx-core/prepare.py --output "$RUNNER_TEMP/core-source"
uv build "$RUNNER_TEMP/core-source" --out-dir "$RUNNER_TEMP/core-dist"
uv venv "$RUNNER_TEMP/core-env" --python .venv/bin/python
uv pip install --python "$RUNNER_TEMP/core-env/bin/python" "$RUNNER_TEMP"/core-dist/loopx_core-*.whl
echo "LOOPX_CORE_TEST_PYTHON=$RUNNER_TEMP/core-env/bin/python" >> "$GITHUB_ENV"
- name: Qualify installed execution, recovery, replay and refusal
env:
LOOPX_USAGE_PING: "0"
run: uv run --extra test python -m pytest -q tests/test_core_distribution.py

codex-provider-routing:
name: loopx-codex-provider-routing
runs-on: ubuntu-latest
Expand Down
32 changes: 32 additions & 0 deletions docs/architecture/rfcs/monorepo-distribution-split-v0.md
Original file line number Diff line number Diff line change
Expand Up @@ -3405,3 +3405,35 @@ compatibility to older readers and does not upgrade an installed runtime.
Real File/SQLite RPC and CLI regressions, plus an isolated PostgreSQL read,
qualify the failure boundary without rewriting authority. The original
installed-host qualification and full-cost/adoption gaps remain open.


### 2026-11-12 — Standalone core artifact and installation diagnosis

The opt-in `packages/loopx-core` source builder reuses tracked runtime sources
and produces an independently installed wheel without Chat, web assets, the
canary fleet or the full CLI facade. It retains existing built-in capability
and host compatibility imports. This is the core-artifact prerequisite for
R1/M3; it does not qualify the complete minimal closure or publish a release.

The installed journey uses the existing generic CLI host, independent artifact
validation and canonical hard leases. The original admitted key/version travels
through the shared TypeScript preparation and checked Turn context to the
existing completion owner. Process loss resumes the confirmed result; replay
does not invoke the host or debit quota again. A renewed/released lease or an
invalid artifact refuses completion without state or quota effects.

Deep installation diagnosis now selects the actual distribution's RECORD and
required entrypoints. A healthy core passes; an unrecorded launcher or missing
runtime file remains a failure. Ordinary distributions still require their full
CLI. Core supports the advertised history action through its existing handler
and parses global version options without the omitted facade. The installed
qualification covers default SQLite and explicitly configured File hard leases;
help/import remain usable without Node, while governed work retains the existing
Node prerequisite. Ordinary-distribution CLI and installation parity have their
own matched checks. The package-smoke lane executes the installed qualification.

Preserve D1-D7 and all original milestones, historical failures and acceptance
boundaries. Workspace/meta-package publication, complete install/load/context
closure, independent Standard packages, other host/platform adoption, matched
model effectiveness and complete costs remain open. This synthetic task and
recovery evidence establishes neither model benefit nor an installed-host upgrade.
24 changes: 24 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 @@ -2658,3 +2658,27 @@ legacy fallback;已支持的历史字段清单仍可读取。这是既有源

真实 File/SQLite RPC 与 CLI 回归,以及隔离 PostgreSQL 读取,验证该失败边界
且不重写 authority。原 installed-host 资格、完整成本和 adoption 缺口继续开放。


### 2026-11-12 — 独立 core 产物与安装诊断

可选的 `packages/loopx-core` 源码构建器复用 tracked 运行时源码,生成可独立安装的
wheel,省略 Chat、web 资源、canary 集群及完整 CLI facade。它仍保留现有内建能力
和宿主兼容导入。这是 R1/M3 的 core 产物前提,不代表完整最小闭包已合格,也不
发布发行版本。

安装后任务沿用 generic CLI 宿主、独立产物验收及 canonical 硬租约。最初准入的
key/version 经共享 TypeScript preparation 和已校验的 Turn context 传入既有
完成 owner。进程丢失后恢复已确认结果;重放不再次调用宿主或扣额。租约更新/
释放或产物无效时拒绝完成,不产生状态写入和扣额。

深度安装诊断按实际发行物的 RECORD 和必要入口检查。健康 core 通过,未登记的
启动器或缺失运行文件仍失败;普通发行物仍要求完整 CLI。core 通过现有 handler
执行目录列出的 history action,并在缺少完整 facade 时解析全局版本选项。安装
资格覆盖默认 SQLite 和显式配置的 File 硬租约;无 Node 时可导入和查看帮助,
受治理任务保留既有 Node 前提。普通发行物的 CLI 和安装兼容有独立配对检查。
package-smoke 工作流执行这套安装后验证。

保留 D1-D7、原有里程碑、历史失败与验收边界。workspace/meta 发行、完整安装/
加载/context 闭包、独立 Standard 包、其他宿主/平台采用、配对模型效果和完整
成本仍开放。合成任务与恢复证据不证明模型收益,也不代表已升级当前宿主。
5 changes: 4 additions & 1 deletion loopx/cli_commands/turn_run_once.py
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,10 @@ def todo_completion(
agent_id=args.agent_id,
completion_delivery_workspace=completion_delivery_workspace,
completion_validation_workspace_path=delivery_workspace_path,
**(dict(native_completion_lease_resolver()) if native_completion_lease_resolver else {}),
**(dict(native_completion_lease_resolver()) if native_completion_lease_resolver else {
"task_lease_idempotency_key": execution_context.get("task_lease_proof", {}).get("idempotency_key"),
"task_lease_expected_version": execution_context.get("task_lease_proof", {}).get("expected_version"),
}),
)
if completion.get("ok") is not True:
return completion
Expand Down
65 changes: 59 additions & 6 deletions loopx/cli_runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
_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", "version",
"bootstrap", "connect", "configure-goal",
"authority-archive", "coordination-shadow", "extension", "slash-commands",
"manager-inbox",
"agent-context", "agent-directory", "agent-capabilities",
Expand Down Expand Up @@ -392,7 +393,15 @@ def _handle_registry_inspection_command(

def _build_selected_parser(command: str) -> LoopXArgumentParser:
parser, subparsers = build_cli_parser(parser_class=_SelectedArgumentParser)
if command in _REGISTRY_INSPECTION_COMMANDS:
if command in {"bootstrap", "connect"}:
from .cli_commands.bootstrap_connect import register_bootstrap_connect_command

register_bootstrap_connect_command(subparsers)
elif command == "configure-goal":
from .cli_commands.registry_admin_configure import register_configure_goal_command

register_configure_goal_command(subparsers)
elif command in _REGISTRY_INSPECTION_COMMANDS:
_register_registry_inspection_commands(subparsers)
elif command in _STATUS_COMMANDS:
from .cli_commands.status_registration import register_status_commands
Expand Down Expand Up @@ -517,6 +526,18 @@ def _dispatch_common_command(
registry_path: Path,
allow_missing_registry: bool,
) -> int | None:
if args.command in {"bootstrap", "connect"}:
from .cli_commands.bootstrap_connect import handle_bootstrap_connect_command

return handle_bootstrap_connect_command(
args, registry_path=registry_path, print_payload=print_payload,
)
if args.command == "configure-goal":
from .cli_commands.registry_admin import handle_registry_admin_command

return handle_registry_admin_command(
args, registry_path=registry_path, print_payload=print_payload,
)
if args.command == "host-mode-plan":
from .cli_commands.host_mode_plan import handle_host_mode_plan_command

Expand Down Expand Up @@ -637,7 +658,9 @@ def _dispatch_common_command(
)

print_payload(
build_command_reference_payload(), output_format(args),
build_command_reference_payload(
available_commands=_SELECTED_COMMANDS if _standalone_core() else None,
), output_format(args),
render_command_reference_markdown,
)
return 0
Expand Down Expand Up @@ -853,9 +876,32 @@ def _dispatch_selected(args: argparse.Namespace, raw_argv: list[str]) -> int:
return result


def _run_full_cli(raw_argv: list[str]) -> int:
from .cli import main as cli_main
def _standalone_core() -> bool:
from importlib.util import find_spec

if find_spec("loopx.cli") is not None:
return False
from importlib.metadata import PackageNotFoundError, distribution
from .python_install_owner import installed_loopx_distribution

try:
return installed_loopx_distribution(distribution).name == "loopx-core"
except PackageNotFoundError:
return False


def _run_full_cli(raw_argv: list[str]) -> int:
try:
from .cli import main as cli_main
except ModuleNotFoundError as exc:
if exc.name != "loopx.cli" or not _standalone_core():
raise
# Only the omitted full CLI is optional; never hide a broken dependency.
parser, _ = build_cli_parser()
parser.error(
"this command or argument is unavailable in the standalone core; "
"use its supported CLI options or the ordinary LoopX installation"
)
return cli_main(raw_argv)


Expand All @@ -868,9 +914,16 @@ def main(argv: list[str] | None = None) -> int:
from .help_surface import render_concise_help, top_level_help_requested

if top_level_help_requested(raw_argv):
print(render_concise_help(sys.argv[0] if argv is None else "loopx"), end="")
print(render_concise_help(sys.argv[0] if argv is None else "loopx",
standalone_core=_standalone_core()), end="")
return 0
command = _top_level_command(raw_argv)
if command is None and _standalone_core():
# The canonical global parser handles version and malformed/missing
# arguments without importing an intentionally omitted command facade.
parser, _ = build_cli_parser()
parser.parse_args(raw_argv)
return 0
from .usage_ping import begin, finish

ticket = begin(command or "")
Expand Down Expand Up @@ -908,7 +961,7 @@ 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 == "history" and args.history_action is not None:
if command == "history" and args.history_action is not None and not _standalone_core():
# Select ordinary reads; maintenance keeps its complete original route.
return _run_full_cli(raw_argv)
if command == "capability":
Expand Down
2 changes: 2 additions & 0 deletions loopx/control_plane/quota/execution_preparation.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ def project_selected_todo_execution_preparation(
"status": status if status in {"active", "released"} else "unknown",
"active": lease_is_active(dict(lease)),
**({"owner": lease["owner"]} if normalize_todo_claimed_by(lease.get("owner")) else {}),
"lease_proof": {"idempotency_key": lease.get("idempotency_key"),
"expected_version": lease.get("version")},
})
facts = {
"schema_version": "execution_preparation_facts_v0",
Expand Down
1 change: 1 addition & 0 deletions loopx/control_plane/quota/turn_envelope.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2571,6 +2571,7 @@ export function evaluateTurnEnvelope(value: unknown): JsonObject {
sha256: completeTaskTextSha256(decision, selectedWorkTodoId(work) ?? ""),
effective_action: decision.effective_action ?? null,
writeback: object(projection.writeback),
task_lease_proof: object(object(decision.execution_preparation).lease_proof),
replan_guard_scoped: Object.hasOwn(projection, "replan_action_packet"),
replan_action_packet: projection.replan_action_packet ?? null};
}
Expand Down
3 changes: 2 additions & 1 deletion loopx/control_plane/todos/read_transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,8 @@ export async function readExecutionPreparationSource(input: JsonObject, dependen
const todo = originalRecords(canonical.todos).find(row => row.todo_id === todoId);
if (!todo) return unavailable;
const leases = originalRecords(canonical.leases).filter(row => row.todo_id === todoId)
.map(({todo_id, schema_version, status, owner, expires_at}) => ({todo_id, schema_version, status, owner, expires_at}));
.map(({todo_id, schema_version, status, owner, expires_at, idempotency_key, version}) =>
({todo_id, schema_version, status, owner, expires_at, idempotency_key, version}));
return {schema_version: TODO_READ_RESULT, execution_preparation_source: {
goal_id: goalId, todo_id: todoId, handoff_mode: canonical.handoff_mode,
source_authority: canonical.source_authority, provider_revision: canonical.provider_revision,
Expand Down
19 changes: 17 additions & 2 deletions loopx/control_plane/work_items/execution_preparation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
optionalNonEmptyString,
requireBoolean,
requireJsonObject,
requireInteger,
requireNonEmptyString,
requireStringLiteral,
} from "../runtime_decode.ts";
Expand All @@ -25,7 +26,18 @@ function leaseFact(value: unknown, label: string): JsonObject {
if (active && status !== "active") {
throw new EffectRuntimeRequestError(`${label}.active requires status=active`);
}
return {status, active, ...(owner === null ? {} : {owner})};
let proof: JsonObject | undefined;
if (lease.lease_proof !== undefined) {
proof = requireJsonObject(lease.lease_proof, `${label}.lease_proof`);
const key = requireNonEmptyString(proof.idempotency_key, `${label}.lease_proof.idempotency_key`);
const version = requireInteger(proof.expected_version, `${label}.lease_proof.expected_version`);
if (key !== key.trim() || !Number.isSafeInteger(version) || version < 1) {
throw new EffectRuntimeRequestError(`${label}.lease_proof requires an exact key and positive safe-integer version`);
}
proof = {idempotency_key: key, expected_version: version};
}
return {status, active, ...(owner === null ? {} : {owner}),
...(proof === undefined ? {} : {lease_proof: proof})};
}

function claimAction(goalId: string, todoId: string, agentId: string): JsonObject {
Expand Down Expand Up @@ -113,7 +125,10 @@ export function projectExecutionPreparation(value: unknown): JsonObject {
reason: "The selected Todo has an active lease owned by another agent; do not take over its execution."};
}
return {...base, state: "ready", delivery_allowed: true,
reason_code: "matching_active_task_lease"};
reason_code: "matching_active_task_lease",
// Capture the admitted generation once. Recovery must not borrow a
// replacement lease after the original Host result was produced.
...(active[0].lease_proof === undefined ? {} : {lease_proof: active[0].lease_proof})};
}

const prior = leases[0];
Expand Down
6 changes: 5 additions & 1 deletion loopx/doctor.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
from .paths import configured_runtime_route, default_runtime_route, global_registry_path
from .python_install_owner import (
PythonInstallOwner,
installed_loopx_distribution,
python_distribution_upgrade_command,
resolve_python_install_owner,
)
Expand Down Expand Up @@ -142,7 +143,7 @@ def python_distribution_install(module_path: Path) -> dict[str, Any]:
"""Identify a wheel/sdist install that owns the imported LoopX module."""

try:
installed = distribution("loopx")
installed = installed_loopx_distribution(distribution)
except PackageNotFoundError:
return {"available": False}
installer = (installed.read_text("INSTALLER") or "").strip() or "unknown"
Expand All @@ -168,6 +169,8 @@ def python_distribution_install(module_path: Path) -> dict[str, Any]:
"installer": owner.manager,
"installer_environment": owner.environment,
"root": str(Path(cast(Path, installed.locate_file(""))).resolve()),
**({"distribution_name": "loopx-core"}
Comment thread
cocolord marked this conversation as resolved.
Resolved
if getattr(installed, "name", "loopx") == "loopx-core" else {}),
}


Expand Down Expand Up @@ -316,6 +319,7 @@ def build_install_freshness(
owner=distribution_owner,
python_executable=sys.executable,
doctor_command=f"loopx doctor{doctor_agent_arg}",
distribution_name=str((distribution_install or {}).get("distribution_name") or "loopx"),
) if distribution_owner else no_clone_command
manifest_source_git_commit = manifest_source.get("git_commit")
manifest_source_revision = (
Expand Down
Loading
Loading