Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
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
4 changes: 3 additions & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,9 @@ jobs:
cache-dependency-path: runner/requirements.txt
# httpx: the runner suite drives the app through FastAPI's TestClient, which needs it. The
# runner itself does not, so it stays out of requirements.txt and lives with pytest here.
- run: pip install -r runner/requirements.txt pytest pytest-asyncio httpx
# The System One Harness is on the runner's venv in the image (docker/entrypoint.sh pins the tag);
# the driver's tests import it, and skip without it, so the suite installs the same pin.
- run: pip install -r runner/requirements.txt pytest pytest-asyncio httpx "systemone-harness @ git+https://github.com/HarnessRouter/SystemOneHarness@v0.3.1"
- run: python -m pytest runner/tests -q

conformance:
Expand Down
65 changes: 64 additions & 1 deletion docker/entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ export HOSTNAME=0.0.0.0
TOOLS="$DATA_DIR/agent-tools"
export PATH="$TOOLS/bin:$PATH"
export NODE_PATH="$TOOLS/lib/node_modules"
export HR_BACKENDS="${HR_BACKENDS:-claude,codex,hermes,pi,dsh,opencode,qwen,gemini,cline,omp,goose,kimi,aider,openhands}"
export HR_BACKENDS="${HR_BACKENDS:-claude,codex,hermes,pi,dsh,opencode,qwen,gemini,cline,omp,goose,kimi,aider,openhands,systemone}"

wanted() { [[ ",$HR_BACKENDS," == *",$1,"* ]]; }
# The executable IS the definition of "installed" — an installer that exits 0 without producing
Expand All @@ -199,6 +199,7 @@ backend_bin() {
kimi) echo "$TOOLS/bin/kimi" ;;
aider) echo "$TOOLS/aider-venv/bin/aider" ;;
openhands) echo "$TOOLS/openhands-venv/bin/python" ;;
systemone) echo "$TOOLS/systemone-venv/bin/python" ;;
esac
}

Expand Down Expand Up @@ -341,6 +342,55 @@ KIMI_PIN="${HR_KIMI_VERSION:-2.0.0}"; KIMI_PIN="${KIMI_PIN#v}"
#
# Own venv, the dsh/hermes precedent: it pins litellm, fastmcp, pydantic and a browser stack, and
# must not share the runner's interpreter.
# The System One Harness (github.com/HarnessRouter/SystemOneHarness, Apache-2.0): a loop over a
# decision model, pinned by git tag. Its own venv on the data volume like openhands and aider: three
# small dependencies (httpx, pyyaml, the MCP SDK), installed once and rebuilt when the pin moves.
# The executable is the venv's python, which the runner hands runner/systemone_driver.py.
SYSTEMONE_PIN="${HR_SYSTEMONE_VERSION:-0.3.1}"; SYSTEMONE_PIN="${SYSTEMONE_PIN#v}"
# HR_SYSTEMONE_SPEC overrides where pip takes the package from (a mirror, a fork, a local tree
# copied into a derived image); the version proven below is the pin either way. The browser extra
# brings Browser Use, and with it the page and game environments; the Chromium they drive is
# installed beside the venv on the data volume (Playwright's, world-readable), because the image
# ships no browser and a session process cannot read root's cache.
SYSTEMONE_SPEC="${HR_SYSTEMONE_SPEC:-systemone-harness[browser] @ git+https://github.com/HarnessRouter/SystemOneHarness@v${SYSTEMONE_PIN}}"
export PLAYWRIGHT_BROWSERS_PATH="$TOOLS/ms-playwright"
# The libraries that Chromium links against live in the container, not on the volume the browser
# is on, so a container recreated over a volume that already holds the browser has the binary and
# none of its libraries (measured: libatk, libatspi, libXcomposite "not found", and every launch
# died before CDP came up). They are installed per container, keyed on a marker in the container's
# own filesystem: on the first install, and on every start that finds the venv already there.
CHROMIUM_LIBS_MARK=/var/lib/harnessrouter/chromium-libs
chromium_libs() {
[ -f "$CHROMIUM_LIBS_MARK" ] && return 0
echo "[harnessrouter] installing the system libraries Chromium links against (this container)…"
"$TOOLS/systemone-venv/bin/playwright" install-deps chromium >/dev/null 2>&1 || return 1
mkdir -p "$(dirname "$CHROMIUM_LIBS_MARK")" && : > "$CHROMIUM_LIBS_MARK"
}
install_systemone() {
# A failed install leaves NO venv behind: the executable is the definition of installed, and a
# venv whose pip step failed reported the base as available on a box where it could not run.
"${HR_SYSTEMONE_BASE_PYTHON:-python3}" -m venv "$TOOLS/systemone-venv" || { rm -rf "$TOOLS/systemone-venv"; return 1; }
"$TOOLS/systemone-venv/bin/pip" install -q --disable-pip-version-check "$SYSTEMONE_SPEC" playwright \
|| { rm -rf "$TOOLS/systemone-venv"; return 1; }
if ! ls "$PLAYWRIGHT_BROWSERS_PATH"/chromium-*/chrome-linux*/chrome >/dev/null 2>&1; then
echo "[harnessrouter] installing a Chromium for the System One base (Playwright's) under $PLAYWRIGHT_BROWSERS_PATH …"
"$TOOLS/systemone-venv/bin/playwright" install chromium \
|| { rm -rf "$TOOLS/systemone-venv"; return 1; }
chmod -R a+rX "$PLAYWRIGHT_BROWSERS_PATH" 2>/dev/null || true
fi
chromium_libs || { rm -rf "$TOOLS/systemone-venv"; return 1; }
# Prove the loop imports and the version is the pin, the way every other installer here proves
# its executable: an install that cannot import is a base that dies on its first turn.
"$TOOLS/systemone-venv/bin/python" -c '
import sys
import systemone_harness, systemone_harness.envs.mcp, systemone_harness.envs.browser # noqa: F401 - the loop, the MCP adapter, the browser
import browser_use # noqa: F401 - the browser extra
have = systemone_harness.__version__
if have != sys.argv[1]:
print(f"systemone-harness {have} installed, {sys.argv[1]} pinned", file=sys.stderr); sys.exit(1)
' "$SYSTEMONE_PIN" || { rm -rf "$TOOLS/systemone-venv"; return 1; }
}

install_openhands() {
oh_py="${HR_OPENHANDS_BASE_PYTHON:-python3}"
"$oh_py" -m venv "$TOOLS/openhands-venv" || return 1
Expand Down Expand Up @@ -592,6 +642,19 @@ install_backends() {
try_install "OpenHands" install_openhands || true
fi

s1_have=""
if [ -x "$(backend_bin systemone)" ]; then
s1_have="$("$(backend_bin systemone)" -c 'import systemone_harness as s; print(s.__version__)' 2>/dev/null || true)"
fi
if wanted systemone && [ "$s1_have" != "$SYSTEMONE_PIN" ]; then
rm -rf "$TOOLS/systemone-venv"
echo "[harnessrouter] installing System One Harness $SYSTEMONE_PIN (Apache-2.0)…"
try_install "System One Harness" install_systemone || true
fi
if wanted systemone && [ -x "$TOOLS/systemone-venv/bin/playwright" ]; then
chromium_libs || echo "[harnessrouter] WARNING: Chromium's system libraries could not be installed; the System One base's browser environments will not start until they are (retried on the next start)"
fi

if wanted kimi && [ "$("$(backend_bin kimi)" --version 2>/dev/null | head -n 1)" != "$KIMI_PIN" ]; then
echo "[harnessrouter] installing Kimi Code CLI $KIMI_PIN (MIT, version-pinned)…"
try_install "Kimi Code CLI" install_kimi || true
Expand Down
5 changes: 5 additions & 0 deletions docs/harness-verification.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ For every harness and every model its menu offers, one session runs five scenari
| Artifact | a file the task must produce exists in the turn record and is shown to the reader |
| Recycle | the sandbox is let go on purpose, then a follow-up still recalls the first message |

One base is not a coding agent. `systemone` runs a decision loop over a typed action space rather
than a CLI over a workspace, so the artifact scenario's prompt does not apply to it; it is verified by
its own loop (its package's tests, its UHP conformance and its benchmark) and by the first-turn,
follow-up, switch and recycle scenarios here. See docs/support-matrix-notes.md, "systemone".

## The rules that decide a row

A scenario that "completed" is not a pass on its own. Four rules turn a run into a verdict, and each
Expand Down
2 changes: 1 addition & 1 deletion docs/self-hosting-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -650,7 +650,7 @@ Backends are installed into your data volume rather than baked into the image, s
want is a run-time setting:

```bash
docker run -e HR_BACKENDS=claude,codex,hermes,pi,dsh,opencode,qwen,gemini,cline,omp,goose,kimi,aider,openhands ... # the default
docker run -e HR_BACKENDS=claude,codex,hermes,pi,dsh,opencode,qwen,gemini,cline,omp,goose,kimi,aider,openhands,systemone ... # the default
docker run -e HR_BACKENDS=opencode ... # lean
```

Expand Down
48 changes: 48 additions & 0 deletions docs/support-matrix-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -1266,3 +1266,51 @@ fourteen backends: the OpenHands venv installs in about a minute beside aider's.
of the turn survives the sweep. The agent doc reaches the model as context: asked, with no tool
allowed, for a secret word in the harness's instructions and the installed skills, it answered
both from the doc.

## systemone: the System One Harness as the fifteenth base (2026-09-19)

Not a coding CLI. The base runs the open-source System One Harness
(github.com/HarnessRouter/SystemOneHarness, Apache-2.0, pinned at v0.3.1 in `docker/entrypoint.sh`)
over TypeSafe's Jev, a decision model: it answers typed questions (a choice, a yes/no probability,
a score) with probabilities in one pass and writes no text. Every step is one request carrying
the next action as a choice over what the environment offers right now, every parameter of every
offered action, and a goal check; the harness gates the answer by the action's risk and executes.
The turn process is `runner/systemone_driver.py`; its events are claude's stream-json, so the
normaliser is the passthrough.

### Measured, 2026-09-19

- **Through the runner's own relay.** `_build_systemone` registers the route at the provider's API
ROOT (`https://openrouter.ai/api`), because OpenRouter serves decisions at `/api/alpha/decisions`
and a POST to `/api/v1/alpha/decisions` is a 404. The driver posts to `<relay>/v1/alpha/decisions`.
One live turn on the built-in order desk: six actions, `success`, 1.44 s wall; the relay's taps
read the served model `typesafe/jev-1.13-20260917` and usage 6304 in / 1410 out off the answer,
and the driver's own result event said the same. Rule 2 of harness-verification.md holds the
way it holds for cline and qwen: the base rides the relay.
- **The environment is the harness's MCP server** (the first one configured), its tools compiled to
actions at the start of each turn; a tool that needs free text is named in the trace as not
offered. With no server, the built-in order desk, so the base answers before anything is
configured. `disabledTools` withholds an action from the question itself (hard, by omission).
- **`incomplete` is a runner status now.** A loop that stops because the model asked for help or a
destructive action never cleared its confidence bar is neither a failure nor a step cap. The
driver's result carries `subtype: incomplete` and a `reason`; `_status_from_result` returns
`incomplete`, the poll body carries `reason`, and the gateway reports it as the task's
`incomplete_details.reason` without retrying another connection. Pinned by
`runner/tests/test_systemone_backend.py` and `gateway/tests/test_systemone_catalog.py`.
- **Continuation.** The desk's state and the loop's steps persist under
`.harness/systemone/<session>/` in the workspace; a resumed turn carries on from where the last
one stopped and the model sees the earlier steps as history (a two-step first turn followed by a
continuation redid none of its picks).
- **What the five matrix scenarios mean here.** First turn, follow-up, switch and recycle apply as
written; the artifact scenario's "create a file" prompt does not, because the desk produces one
artifact of its own (`manifest.json`, on ship). The base is verified by its own loop rather than
the coding prompts: the harness package's 35 tests, its UHP core conformance (40 of 40) and its
benchmark (15 of 15 goals on the live model) are the record, in that repository.

### The models

`jev-1.13` and `jev-latest`, OpenRouter only (`typesafe/jev-1.13`, `~typesafe/jev-latest`), added to
OpenRouter's vendor table after the shared copy so no other aggregator inherits an id it cannot
serve. No chat model is listed on this base: the loop asks typed questions a text model cannot
answer. TypeSafe's direct endpoint (`/v1/systemone`) is wired in the runner and not yet offered by
the gateway.
Loading
Loading