The box runner¶
python -m locus.runner (also installed as locus-runner) is one agent's loop,
running inside an NVIDIA OpenShell sandbox for one run. A Locus gateway starts
it as the sandbox's command. The gateway stays the control plane: it decides
every call, performs the tools a box must not, keeps the run's checkpoints and
writes every record. The runner holds no policy, no model key and no audit
chain of its own.
Environment¶
| Variable | What it is |
|---|---|
LOCUS_ADMIT_URL |
The gateway's base URL, reachable from the box. |
LOCUS_ADMIT_TOKEN |
The box's workload token, sent as a bearer token. Never logged. |
LOCUS_RUN_ID |
The run this box serves. |
<model.api_key_env> |
The OpenShell placeholder of the model's key, named by the manifest. |
<mcp[].credential_env>, <api[].credential_env> |
Placeholders of MCP and API connector credentials, named by the manifest. |
SHIV_ROOT |
Where locus-runner.pyz unpacks itself (the image sets it). |
A placeholder is not a credential: the sandbox's proxy swaps it for the real value on its bound host and nowhere else.
Exit codes¶
| Code | Meaning |
|---|---|
| 0 | The run finished, parked on a person, or the gateway said stop. |
| 2 | Refused: the manifest asks for a tool, a provider or a credential this runner does not have. |
| 1 | Error: no gateway configured or reachable, or the run failed. |
What it does¶
GET /internal/v1/runner/next→start,resumeorstop.GET /internal/v1/runner/manifest→ theRunManifest; one for another run is refused.- Builds the agent (
locus.runner.build_runtime). Tools, byruns: box: the harness tools over/sandbox;ask_userpauses the run for an answer;taskstarts a subagent in the same box, as a child run (POST /).internal/ v1/ runs/ {id}/ children gateway:POST /, admitted and performed by the gateway.internal/ v1/ runs/ {id}/ tools/ {name} mcp:<id>:tools/callon that mount, from the box.api:<id>: one operation of that API connector, from the box. The request islocus.: the gateway computes the same request when it admits the call.runner. api_ request(connector, operation, arguments) - Every call but the gateway's own is admitted first
(
POST /). An MCP or API call carries the admission's one-shot decision token asv1/ admit {run_ id, call_ id, tool, arguments} x-locus-decision; the box guard checks it against the request on the wire. - Progress goes to
POST /internal/v1/runs/{id}/events(token,think,tool_start,tool_complete,harness.exec, …), spooled to/sandbox/.locus/events.jsonlthrough outages. -
It ends with
POST /internal/v1/runs/{id}/result:{"status": "done", "final_message": "...", "stop_reason": "complete", "usage_reported": {"prompt_tokens": 20, "completion_tokens": 40}} {"status": "parked", "waiting": {"kind": "approval", "call_id": "c5", "tool": "bash", "approval_id": "ap-1"}} {"status": "parked", "waiting": {"kind": "question", "call_id": "q1", "question": "..."}} {"status": "refused", "error": "..."} {"status": "error", "error": "..."}
usage_reported is the runner's own count; the box guard's metered count is
the one that bills and budgets.
Holds and parking¶
A call the gateway holds for a person is waited on up to
budgets.park_ttl_s (default 600 s). Approved in time, it is asked about again
with its approval_id and runs. Still undecided, the run pauses there: the
agent's state is checkpointed with the gateway
(PUT /), what it waits for is written to
/sandbox/.locus/parked.json and reported, and the process exits 0.
When the decision comes the gateway starts the box again and next answers
resume with {approval_id, decision} (or {answer} for a question). The
runner restores the checkpoint and re-asks about the held call naming its
approval; the gateway admits only the exact call it approved.
What a command cannot use¶
bash is admitted as a command, not as a model or connector call, so it must
not be able to make one:
- commands inherit the runner's environment without
LOCUS_ADMIT_TOKENand without any credential placeholder; - once its runtime is built, the runner wipes the token and every placeholder
out of its initial environment block (
/proc/<pid>/environ) andos.environ, so a command running as the same user finds only empty values there;LOCUS_RUNNER_HARDEN=non-dumpableaddsprctl(PR_SET_DUMPABLE, 0)for a sandbox that does not identify processes through/proc(see below).
The image runs as uid 10001 with no capabilities. The box guard still meters every model call per run, whoever sends it.
Getting it¶
locus-runner.pyz, built withscripts/, runs on any CPython 3.12 on linux x86_64:build_ runner_ pyz. sh <wheel> <out> python3 locus-runner.pyz. It is not attached to GitHub releases; a gateway builds its own from the locus-sdk version it pins and hands it to the box.ghcr.:io/ oracle/ locus- runner:<version> python:3.12-slimwith git, ripgrep and the.pyz; workspace/sandbox.
Keeping the token and placeholders from commands¶
The runner stays dumpable, because NVIDIA OpenShell identifies the process behind every
DNS lookup and connection through /proc/<pid> and refuses one it cannot identify. Once
its runtime is built, before any command runs, it wipes its token and every placeholder
out of its initial environment block (/proc/<pid>/environ) and os.environ; commands
never inherit them. LOCUS_RUNNER_HARDEN=non-dumpable adds prctl(PR_SET_DUMPABLE, 0)
for a sandbox that does not identify processes that way. The runner also writes its log
to /sandbox/.locus/runner.log (no values), which its gateway reads when a box ends
without a report.