# Runner architecture

Millrace keeps the runner boundary intentionally narrow and stable.

## Core contract

The runtime-facing contract is:

- input: `StageRunRequest`
- output: `RunnerRawResult`

Compile freezes runner name, model name, entrypoint path, required skills, optional attached skills, and timeout into the stage plan before dispatch happens.

## Runner components

The runner system lives under `src/millrace_ai/runners/`.

Important modules include:

- `requests.py`: `StageRunRequest`, `RunnerRawResult`, prompt-context rendering
- `normalization.py`: terminal extraction, failure mapping, normalized envelopes
- `base.py`: adapter protocol (`name`, `run(request)`)
- `registry.py`: mapping from runner name to adapter
- `dispatcher.py`: runtime-facing adapter resolver
- `contracts.py`: invocation and completion artifact schemas
- `process.py`: subprocess helper with timeout and error mapping
- `adapters/_prompting.py`: shared Millrace-owned stage prompt construction
- `adapters/codex_cli.py`: built-in Codex CLI adapter
- `adapters/pi_rpc.py`: built-in Pi RPC adapter
- `adapters/pi_rpc_client.py`: focused JSONL RPC transport for Pi

The package also preserves `src/millrace_ai/runner.py` as a thin compatibility facade over the new runner package.

## Resolution order

Runner resolution happens in this order:

1. `StageRunRequest.runner_name`
2. `RuntimeConfig.runners.default_runner`
3. literal fallback `"codex_cli"`

Unknown names fail fast with `UnknownRunnerError`.

In practice there are two distinct decisions:

1. compile decides what runner name is frozen into the stage plan
2. dispatch resolves which adapter to execute from the request

## Shipped built-in adapters

The shipped canonical modes make the baseline posture explicit:

- `default_codex` binds every shipped stage to `codex_cli`
- `default_pi` binds every shipped stage to `pi_rpc`
- `standard_plain` remains accepted only as a compatibility alias for `default_codex`

Millrace is therefore a runtime around harness adapters, not a harness-free orchestration abstraction with no concrete shipped bindings.

## Runner artifacts

Each stage run writes runner artifacts into the run directory:

- `runner_prompt.<request_id>.md`
- `runner_invocation.<request_id>.json`
- `runner_stdout.<request_id>.txt`
- `runner_stderr.<request_id>.txt`
- `runner_completion.<request_id>.json`

The point of that artifact surface is diagnosability. Later operators can inspect what was asked, how the adapter was invoked, what came back, and how the runtime normalized it.

## Codex adapter behavior

The built-in Codex adapter:

- builds a deterministic stage prompt from `StageRunRequest`
- shells out to configured Codex command and args
- captures stdout and stderr
- maps subprocess outcomes to `RunnerRawResult.exit_kind`

The main shipped exit kinds are:

- `completed`
- `timeout`
- `runner_error`

The documented default config fields are:

```toml
[runners]
default_runner = "codex_cli"

[runners.codex]
command = "codex"
args = ["exec"]
permission_default = "maximum"
skip_git_repo_check = true
extra_config = []
```

The docs also describe permission precedence by stage, model, or default.

## Pi adapter behavior

The built-in Pi adapter:

- shells out to `pi --mode rpc --no-session`
- sends the same Millrace-owned stage prompt contract used by the Codex path
- persists streamed Pi events to `runner_events.<request_id>.jsonl`
- materializes final assistant text into `runner_stdout.<request_id>.txt`
- uses Millrace timeout governance, including RPC abort and bounded hard-kill fallback

The documented default Pi config fields are:

```toml
[runners.pi]
command = "pi"
args = []
disable_context_files = true
disable_skills = true
```

## Why the boundary matters

Millrace keeps `StageRunRequest -> RunnerRawResult` stable so that harness adapters can change without rewriting runtime orchestration. That is why Millrace should be described as a runtime or governance layer around raw harness execution, not as a coding model or interactive editor.

See also:

- `/ai/modes-and-loops.md`
- `/ai/architecture.md`
- `/ai/what-is-millrace.md`
