Skip to content
Draft
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
1 change: 1 addition & 0 deletions .github/workflows/ark-turn.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ jobs:
- uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python }}
- run: npm ci --ignore-scripts --prefix loopx/hosts/oar
- run: python -m pip install -e ".[test]" -e packages/loopx-ark-turn
- name: Public SDK, real stdio MCP, negative cases and DSH parity
run: >-
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/memt-turn.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ jobs:
- uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python }}
- run: npm ci --ignore-scripts --prefix loopx/hosts/oar
- run: python -m pip install -e '.[test]' -e packages/loopx-memt-turn
# Explicit test-only installation; upstream retains its noncommercial license.
- run: python -m pip install 'memt-sglang[client] @ git+https://github.com/MemTensor/MemT-1.0.git@e75c13d090a6a9d34b29146d901bd84323185f24#subdirectory=inference'
Expand Down
94 changes: 94 additions & 0 deletions .github/workflows/oar-runtime.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
name: OAR Runtime Integration

on:
pull_request:
paths:
- "loopx/hosts/oar/**"
- "loopx/control_plane/turn_driver/**"
- "loopx/dsh_goal_mode/**"
- "loopx/chat_dsh.py"
- "tests/test_oar_*.py"
- "examples/loopx-steward-managed-chat-smoke.py"
- "examples/loopx-turn-codex-cli-e2e-smoke.py"
- "pyproject.toml"
- "MANIFEST.in"
- ".github/workflows/oar-runtime.yml"
push:
branches: [main]
paths:
- "loopx/hosts/oar/**"
- "loopx/control_plane/turn_driver/**"
- "loopx/dsh_goal_mode/**"
- "loopx/chat_dsh.py"
- "tests/test_oar_*.py"
- "examples/loopx-steward-managed-chat-smoke.py"
- "examples/loopx-turn-codex-cli-e2e-smoke.py"
- "pyproject.toml"
- "MANIFEST.in"
- ".github/workflows/oar-runtime.yml"
workflow_dispatch:

permissions:
contents: read

concurrency:
group: oar-runtime-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

jobs:
integration:
runs-on: ubuntu-latest
timeout-minutes: 20
env:
LOOPX_USAGE_PING: "0"
steps:
- uses: actions/checkout@v7
- name: Set OAR runtime cache
run: echo "LOOPX_OAR_HOME=$RUNNER_TEMP/loopx-oar" >> "$GITHUB_ENV"
- uses: actions/setup-python@v6
with:
python-version: "3.11"
cache: pip
- uses: actions/setup-node@v6
with:
node-version: "24"
cache: npm
cache-dependency-path: |
loopx/hosts/oar/package-lock.json
apps/presentation/dashboard/package-lock.json
- name: Install locked runtime and Python test dependencies
run: |
python -m pip install --disable-pip-version-check -e '.[test,deepseek-harness]' build
npm ci --ignore-scripts --prefix loopx/hosts/oar
- name: Check runtime contracts and real transport fixtures
run: |
npm --prefix loopx/hosts/oar run typecheck
npm --prefix loopx/hosts/oar test
python -m pytest -q --no-cov tests/test_oar_*.py
python examples/loopx-turn-codex-cli-e2e-smoke.py
python examples/loopx-steward-managed-chat-smoke.py
- name: Build the installable distribution
run: |
python scripts/chat_bundle.py build --install
python -m build --wheel
python - <<'PY'
from pathlib import Path
from zipfile import ZipFile
with ZipFile(next(Path("dist").glob("loopx-*.whl"))) as wheel:
names = wheel.namelist()
assert not any("/node_modules/" in name for name in names)
for name in ("runtime.ts", "stdio.ts", "dsh.ts", "dsh_worker.py", "package.json", "package-lock.json"):
assert "loopx/hosts/oar/" + name in names
PY
- name: Qualify the installed wheel outside the source checkout
run: |
python -m venv "$RUNNER_TEMP/oar-wheel-env"
for wheel in dist/loopx-*.whl; do
"$RUNNER_TEMP/oar-wheel-env/bin/python" -m pip install --disable-pip-version-check "$wheel[deepseek-harness]" pytest
done
mkdir -p "$RUNNER_TEMP/oar-wheel-check/tests"
cp tests/test_oar_pi_host.py tests/test_oar_dsh_chat.py "$RUNNER_TEMP/oar-wheel-check/tests/"
cd "$RUNNER_TEMP/oar-wheel-check"
"$RUNNER_TEMP/oar-wheel-env/bin/python" -m loopx.hosts.oar install
"$RUNNER_TEMP/oar-wheel-env/bin/python" -m loopx.hosts.oar status
"$RUNNER_TEMP/oar-wheel-env/bin/python" -m pytest -q tests -k 'real_pi or default_dsh_chat_interrupt'
1 change: 1 addition & 0 deletions .github/workflows/python-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -621,6 +621,7 @@ jobs:
run: |
python -m pip install --disable-pip-version-check -e ".[test]"
npm ci --ignore-scripts
npm ci --ignore-scripts --prefix loopx/hosts/oar
- name: Prepare native shell isolation
# Ubuntu 24.04 may need an executable-scoped userns profile for bwrap.
# Do not disable AppArmor or the system-wide unprivileged-userns guard.
Expand Down
1 change: 1 addition & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
graft loopx/web/chat
prune loopx/hosts/oar/node_modules
exclude loopx/semantics/project_registry_io_manifest_v1.json
26 changes: 26 additions & 0 deletions docs/architecture/rfcs/harness-selection-dsh-pi-v0.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,32 @@ Scope: the shared goals of [Reliability Diagnostics](./long-running-agent-reliab
and [Desktop Execution Frontends](./desktop-execution-frontends-v0.md).
[中文](./harness-selection-dsh-pi-v0.zh-CN.md)

## OAR integration checkpoint (2026-10-28)

LoopX-owned bounded Codex and DSH Turns, DSH Chat segments, and an explicitly
selected Pi generic-cli Turn now share `@botiverse/oar@0.75.0` session, event,
control and cleanup management. [Runtime setup](../../../loopx/hosts/oar/README.md)
documents the exact dependency, explicit installation and Node.js 24 requirement
for this optional runtime. Core's Node.js 22.22.3 requirement is unchanged.

Codex uses OAR's native app-server adapter with the requested sandbox and opaque
resume identity preserved. Pi uses OAR's matching bundled subprocess runtime.
Because upstream OAR has no DSH runtime, LoopX supplies a thin TypeScript runtime
over its existing pinned Python SDK worker. The shared Turn, validation, quota
and work-settlement owners remain authoritative. Missing output, rejected
controls, failed native outcomes or unconfirmed cleanup cannot become success.

This is a bounded-execution integration checkpoint. The existing Pi/DSH plugins
inside an already running host retain same-session behavior. Rich native Codex
Chat and Codex/DSH operation transports retain their adapters until OAR supports
their dynamic-tool, history-injection and background-control contracts. The
runtime adapter owner must qualify those contracts before extending this
boundary. This checkpoint does not promote the L1, Mode B, restart-recovery or
long-horizon effectiveness claims below; the dated historical tables retain
their original evidence scope. DSH Chat now reports real interrupt support for
the default supervised runtime, while an explicit custom runner may only
support abandonment.

## Decision

Keep **DSH as the first L1 event source**. Do not infer that DSH is already the
Expand Down
20 changes: 20 additions & 0 deletions docs/architecture/rfcs/harness-selection-dsh-pi-v0.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,26 @@
与 [Desktop Execution Frontends](./desktop-execution-frontends-v0.zh-CN.md) 的共同目标。
[English](./harness-selection-dsh-pi-v0.md)

## OAR 集成检查点(2026-10-28)

由 LoopX 启动的有界 Codex、DSH Turn、DSH Chat 分段,以及显式选择的 Pi
generic-cli Turn,现统一使用 `@botiverse/oar@0.75.0` 管理会话、事件、控制和清理。
[运行时安装说明](../../../loopx/hosts/oar/README.md)记录了精确依赖、显式安装命令及
该可选运行时的 Node.js 24 要求;Core 的 Node.js 22.22.3 最低要求不变。

Codex 使用 OAR 原生 app-server 适配器,保留请求的沙箱权限与不透明的恢复会话标识。
Pi 使用 OAR 配套的子进程运行时。上游 OAR 尚无 DSH 运行时,因此 LoopX 通过薄层
TypeScript 运行时连接原有固定版本的 Python SDK worker。Turn、独立验证、quota
和工作结算仍由现有 owner 决定;输出不完整、控制请求被拒、原生执行失败或未确认
清理完成,均不能成为成功结果。

本检查点的范围是有界执行集成。已经运行的 Pi/DSH 内部插件保留同一会话中的行为。
原生 Codex Chat 与 Codex/DSH operation transport 继续使用现有适配器,直到 OAR
具备对应的动态工具、历史注入与后台控制契约;后续扩展由运行时适配器 owner 先完成
这些契约的验证。本检查点不升级下文 L1、Mode B、重启恢复或长程效果的结论,带日期
的历史表格仍保留原证据范围。DSH Chat 默认运行时现在公开真实的中断能力,显式
自定义 runner 则仍可能只支持丢弃返回结果。

## 决策

保留 **DSH 作为 L1 首个事件源**,但不据此宣布它已成为生产 Mode B 的最终首选。
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,18 @@ or state is correct.
Check the local host once with `codex doctor`. If the default model is newer
than the installed Codex CLI, update Codex or pass `--codex-model`.

Bounded Codex Turns use the shared OAR runtime. Install its locked dependency
once in the LoopX service environment with Node.js 24+:

```bash
python -m loopx.hosts.oar install
python -m loopx.hosts.oar status
```

The existing Codex options and native session bindings remain available. See
[runtime setup and compatibility](../../../../loopx/hosts/oar/README.md) for
the installed-package path, Pi/DSH selection and native Chat boundaries.

## Run One Turn

For a write-capable coding todo:
Expand Down
29 changes: 26 additions & 3 deletions examples/loopx-steward-managed-chat-smoke.py
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,22 @@ def _run_turn(args: argparse.Namespace) -> int:

with tempfile.TemporaryDirectory(prefix="loopx-steward-managed-chat-") as directory:
root = Path(directory)
# The synthetic steward deliberately exercises the restricted DSH
# profile. The default trusted-owner profile requires native Codex.
configuration = root / "store" / "machine" / "configuration.json"
configuration.parent.mkdir(parents=True)
configuration.write_text(
json.dumps({
"schema_version": "loopx_machine_configuration_v0",
"namespaces": {
"manager_runtime": {
"schema_version": "manager_runtime_profile_v0",
"runtime_profile": "restricted",
},
},
}),
encoding="utf-8",
)
store = ChatSessionStore(root / "store")
# A real registry keeps this on the collected path the steward actually
# runs, instead of the registry-unavailable envelope.
Expand Down Expand Up @@ -382,8 +398,9 @@ def _assert_segment_is_read_only(

The channel pins ``DSH_PERMISSION_MODE=read-only`` for its own segments. This
asks the mock model to call the shell tool against a path outside the
segment workspace, then asserts the tool *ran* and was refused, so the
boundary is enforced rather than merely requested in the prompt.
segment workspace and verifies the tool's refusal. A host without a usable
sandbox must refuse execution entirely; report that separately from a
write denied by an available sandbox.
"""

target = _sandbox_probe_path()
Expand Down Expand Up @@ -422,8 +439,13 @@ def _assert_segment_is_read_only(
and message.get("role") == "tool"
and message.get("tool_call_id") == "call_sandbox_probe"
]
backend_unavailable = any(
"no sandbox backend is usable" in str(result)
and "refusing to run the command unconfined" in str(result)
for result in tool_results
)
_assert(
any(
backend_unavailable or any(
"denied" in str(result) or "not permitted" in str(result)
for result in tool_results
),
Expand All @@ -433,6 +455,7 @@ def _assert_segment_is_read_only(
"permission_mode": STEWARD_SEGMENT_ENV["DSH_PERMISSION_MODE"],
"write_attempted": True,
"write_denied": True,
"enforcement": "backend_unavailable" if backend_unavailable else "sandbox_denied",
}


Expand Down
4 changes: 2 additions & 2 deletions examples/loopx-turn-codex-cli-e2e-smoke.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
from tests.control_plane.canonical_authority_fixture import ( # noqa: E402
initialize_canonical_source_fixture,
)
from tests.control_plane.host_process_fixture import write_python_host_fixture # noqa: E402
from tests.control_plane.codex_app_server_fixture import write_codex_app_server_fixture # noqa: E402


GOAL_ID = "loopx-turn-real-cli-e2e"
Expand Down Expand Up @@ -127,7 +127,7 @@ def _write_fixture(root: Path, *, turn_count: int) -> tuple[Path, Path, Path, Pa

def _write_fake_codex(root: Path) -> Path:
executable = root / "fake-codex"
executable = write_python_host_fixture(
executable = write_codex_app_server_fixture(
executable,
f"""#!/usr/bin/env python3
import json
Expand Down
27 changes: 4 additions & 23 deletions loopx/chat_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@
VisibleResponseStreamFilter,
parse_agent_response,
)
from .chat_endpoint_remediation import (
AGENT_ENDPOINT_NEXT_ACTIONS as AGENT_ENDPOINT_NEXT_ACTIONS,
AGENT_ENDPOINT_UNAVAILABLE as AGENT_ENDPOINT_UNAVAILABLE,
)


class CodexChatAgentError(RuntimeError):
Expand Down Expand Up @@ -52,29 +56,6 @@ def _host_tool_gate(summary: str, next_action: str) -> dict[str, str]:
}


# One typed endpoint-unavailability error plus the next step for each reason the
# executor readback can publish. A surface that cannot serve a request names the
# blocking fact and what clears it, so an operator never has to infer why an
# endpoint that is listed as a capability refused the session.
AGENT_ENDPOINT_UNAVAILABLE = "agent_endpoint_unavailable"
AGENT_ENDPOINT_NEXT_ACTIONS = {
"dsh_runtime_unavailable": (
"The LoopX Chat service interpreter cannot import deepseek_harness. "
"Run `loopx doctor` in that service environment and check python.executable; "
"install `loopx[deepseek-harness]` in the same environment, then restart LoopX Chat."
),
"operator_credential_unconfigured": (
"Set the managed executor credential (DEEPSEEK_API_KEY, with "
"DEEPSEEK_BASE_URL when the endpoint is not the provider default) in the "
"LoopX Chat service environment, then restart it."
),
"invalid_reasoning_effort": (
"Fix the configured reasoning effort (LOOPX_MANAGER_REASONING_EFFORT or "
"LOOPX_TURN_REASONING_EFFORT) and restart LoopX Chat."
),
}


def agent_endpoint_error(agent_id: str, *, reason: str = "") -> ValueError:
"""Return the typed error for an Agent id this runtime cannot serve.

Expand Down
Loading
Loading