> ## Documentation Index
> Fetch the complete documentation index at: https://docs.atollhq.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Local runner

> Set up one local runner, bind checkouts, and inspect Codex, jobs, and recovery in the optional localhost UI.

`atoll-runner` runs one claimed issue at a time with Codex. Atoll controls
assignment, project access, repository authorization, and lease ownership.
The runner keeps machine-local checkout bindings and recovery evidence.

## Install and activate a runner

You do not need an Atoll source checkout. Install the released CLI package and
confirm that it contains both commands:

```bash theme={null}
npm install -g @atollhq/cli@latest
atoll --version
atoll-runner --help
```

On macOS, confirm that `git --version` succeeds. Apple Git can remain blocked
until you review and accept the Xcode Command Line Tools license. That legal and
administrator action must be completed by the machine operator.

Create a named profile for the agent and organization, then find the opaque
repository reference authorized for the project:

```bash theme={null}
atoll auth setup
atoll auth profiles --json
atoll --profile agent-a repository list --project project-slug --json
```

Bind the authorized reference to the repository's absolute local checkout:

```bash theme={null}
atoll-runner --profile agent-a repositories bind repo-ref /absolute/path/to/checkout --issue issue-uuid
atoll-runner --profile agent-a repositories validate repo-ref
```

Before the first start, there is no hosted runner row to pause. Remove every
eligible assignment from this agent, or use a dedicated setup agent with no
assignments. Then run one bounded real cycle to initialize local state and
register the row:

```bash theme={null}
atoll-runner --profile agent-a run --once
```

This cycle is safe only under that no-work precondition. Binding creates local
identity but does not create runner state or server presence; `doctor` cannot
fully pass yet, and `--dry-run` does not register the runner. When the runner
appears, immediately pause it in **Workspace Settings → Runners** and verify
that the fleet row shows **Paused**. Now complete the read-only checks:

```bash theme={null}
atoll-runner --profile agent-a doctor
atoll-runner --profile agent-a run --once --dry-run
```

The paused dry run should return no candidate with
`blocked_reason: intake_paused`.

For unattended macOS operation, install and verify the user service:

```bash theme={null}
atoll-runner --profile agent-a service install --ui --ui-port 4735
atoll-runner --profile agent-a service status
atoll-runner --profile agent-a status
```

The setup is ready when `doctor` passes, required bindings validate, Codex is
authenticated, the service is loaded, the optional loopback page responds, and
there is no unexpected current or retained job. Resume new work in hosted Atoll
only after these checks. Then confirm that the fleet row shows **Connected** and
**Accepting**, and that local status reports connected with active intake.
Resuming can immediately claim eligible assigned work.

One machine can run several named profiles, including profiles for different
organizations. Each process and macOS service is fixed to one profile, agent
identity, and organization. Give each enabled diagnostics UI a unique port:

```bash theme={null}
atoll-runner --profile agent-a service install --ui --ui-port 4735
atoll-runner --profile agent-b service install
```

Enable the dashboard on one service only. It shows machine-local process,
presence, intake, and current-job status for every configured runner profile.
Detailed diagnostics and repository actions remain scoped to the profile that
hosts it. Additional dashboards are optional and require different ports.

Do not run competing profiles for the same Atoll agent identity. Atoll permits
one current local runner installation per authenticated agent. One profile can
bind several repositories to different absolute checkouts. The runner creates
its owned worktrees under private runner state; the released CLI does not accept
a custom worktree root or per-issue working directory.

## Prerequisites

Use a CLI build that includes the real Codex worker. Configure a
[named Atoll profile](/cli/auth-profiles) with an agent key and organization.
The package pins both `@openai/codex-sdk` and `@openai/codex` to `0.153.4`.
The pinned runtime must have valid Codex authentication. An unrelated global
Codex installation is not a fallback. Use saved Codex login credentials.
The worker does not inherit API keys, runner tokens, proxy credentials, or
arbitrary environment variables from its parent process.

Your issue's project must have exactly one currently verified repository.
You need a local non-bare Git checkout whose `origin` matches that repository.
Credential-bearing HTTPS origins are rejected. Use a credential-free origin
and a Git credential helper for repository access.
Zero or multiple verified repositories block execution. A local binding does
not select a preferred repository or grant project access.

Configured external Git `clean`, `smudge`, or `process` filters block worktree
preparation. This includes global Git LFS filter configuration, even when the
selected repository does not use LFS. Use a checkout and Git configuration
without these external filters. The runner does not disable required filters
or change your Git configuration. Runner Git commands disable hooks and
filesystem-monitor commands to protect the primary checkout.

## Bind a local checkout

Use the opaque `repo_ref` from the project's authorized repository mapping.
Pass the issue UUID to verify that project's mapping during binding.

```bash theme={null}
atoll-runner --profile agent-a repositories bind repo-ref /path/to/checkout --issue issue-uuid
atoll-runner --profile agent-a repositories list
atoll-runner --profile agent-a repositories validate repo-ref
```

The binding stores the canonical checkout path on this machine. The runner
checks repository authorization again before dispatch. A saved binding cannot
override a revoked mapping or changed origin.

To remove an unused binding:

```bash theme={null}
atoll-runner --profile agent-a repositories remove repo-ref
```

Removal does not delete a checkout or change the project's server mapping.
A binding used by the current job cannot be removed.

## Check and run

```bash theme={null}
atoll-runner --profile agent-a doctor
atoll-runner --profile agent-a status
atoll-runner --profile agent-a run --once --dry-run
atoll-runner --profile agent-a run
```

For unattended macOS operation, install an explicit user LaunchAgent:

```bash theme={null}
atoll-runner --profile agent-a service install
atoll-runner --profile agent-a service install --ui --ui-port 4735
atoll-runner --profile agent-a service status
atoll-runner --profile agent-a service uninstall
```

Service installation is macOS-only. It writes one user-owned `0600` plist in
`~/Library/LaunchAgents/` with canonical Node and runner entry paths, the
selected profile, and optional loopback UI arguments. The plist contains no
API key, Codex token, lease token, environment variables, checkout path, or
shell command. An unknown or changed plist is rejected instead of overwritten.
Package installation is passive and never installs or starts this service.
The documented upgrade path is `service uninstall`, package upgrade, `doctor`,
then `service install`; pause hosted intake first when a no-work maintenance
window is required.

`service status` is read-only and reports only bounded installation, load,
plist, executable, PID, and last-exit fields. `service uninstall` bootstraps
down the exact label and removes only the exact owned plist. It leaves runner
configuration, journals, tokens, logs, branches, and worktrees in place.
Status and uninstall validate local ownership and remain available after profile
or key removal; install remains authenticated.

`doctor` and `status` report bounded Codex availability, version, and
authentication health without account output or credentials. These checks do
not submit a model turn. `--dry-run` is a read-only dispatch check, not proof
that a model turn or worktree preparation has succeeded.

Before turn intent, the runner resolves the server's default ref to an exact
commit and prepares an owned branch and worktree under its local storage.
It preserves the primary checkout, including unrelated changes, and verifies
that snapshot again before terminal reporting and lease finalization. Codex runs
with `workspace-write`, approval policy `never`, no added writable directories,
and sandbox network access disabled. The task can change files in its owned
worktree. It does not automatically create a PR, merge, deploy, or publish.

Manage pause and resume in hosted **Workspace Settings → Runners**. Intake is
read-only in localhost. Legacy local `pause` and `resume` commands return
`RUNNER_INTAKE_HOSTED_ONLY` and do not change server state.

Pause does not cancel a held lease. The current worker can finish or reconcile
that lease. Loss of lease ownership aborts the model stream.

The parent closes its process-local intake gate immediately on SIGINT or
SIGTERM. The current child has up to 30 seconds to drain; only a journaled PID
with the same process-start identity can receive SIGTERM and, after a further
10 seconds and a final identity check, SIGKILL. A wall-clock gap over 30
seconds is treated as a possible wake: the child is stopped, durable lease and
mutation state is reconciled, and local intake reopens only after that pass. A
wake may release a worker only to report and finalize an already durable result;
a stop never releases a worker. No hosted pause request is sent. Protocol errors
are reported as bounded `RUNNER_PROTOCOL_MISMATCH` failures and leave the
pending journal available for replay or operator reconciliation.

## Open local setup and diagnostics

```bash theme={null}
# Start the runner with its optional local page.
atoll-runner --profile agent-a run --ui
# Or inspect this machine without starting a runner or claiming work.
atoll-runner --profile agent-a ui
```

Open the printed `http://127.0.0.1:4735` URL. Use `run --ui --ui-port 4736`
or `ui --port 4736` if needed. There is no LAN bind option. Do not expose the
page through a proxy or tunnel. A port conflict or missing frontend asset does
not stop headless runner operation. Without `--ui`, the runner opens no UI port.
`--dry-run` cannot be combined with `--ui`.

Select the existing Atoll credential profile with `--profile` at process start.
The page shows that profile; it cannot switch a live installation or accept
credentials. Use `atoll auth` to repair a profile, then reload configuration.
The diagnostics-only command can show malformed or unreadable runner config
without resetting it or dispatching work.

The page has five sections:

| Section      | What it shows                                                                                                    |
| ------------ | ---------------------------------------------------------------------------------------------------------------- |
| Status       | Runner process, Atoll connection/auth, effective intake, current installation, and policy freshness              |
| Repositories | Authorized project/repository, stable `repo_ref`, local checkout, validation, and each reason work is ineligible |
| Runtime      | Config/file health, selected profile, one-job capacity, and pinned Codex version/auth preflight                  |
| Jobs         | Current and most recent 50 local jobs, issue/claim, phase, worktree, and retained/recovery/uncertain state       |
| Diagnostics  | Actionable errors and at most 100 allowlisted log events from a bounded 64 KiB tail                              |

An authorized repository remains visible when unbound. Enter its local checkout
path and select **Bind checkout**. This uses the canonical runner config writer;
it does not require an assigned issue. Symlink inputs are resolved before saving.
A saved canonical path that later points elsewhere fails revalidation. Wrong
origins, missing paths, unsafe filters, or insufficient filesystem permissions
block eligibility. Validation does not change the primary checkout.

A project with multiple verified repositories can have local bindings, but
issue dispatch remains blocked until the upstream routing contract identifies
exactly one repository. Local setup never selects a default. A revoked mapping
leaves any local binding visible as inert diagnostic data.

**Refresh**, **Reload config**, **Revalidate**, and **Run preflight** read current
state without submitting a Codex turn. **Remove binding** changes only local
configuration. Bind/remove are blocked while the runner has a current job.
Policy reads and preflight become stale after 60 seconds. The page refreshes
while visible and idle; it does not replace text while you edit a binding.
The worker independently rechecks authority, runtime, and repository identity
before execution. A green diagnostic row does not reserve work or grant access.

Atoll failure invalidates repository eligibility immediately on failed readback.
Previously persisted runner self state may remain visible as stale context.
Local config, jobs, and logs stay available. Authentication failures direct you
to profile repair; Codex failures direct you to runtime/auth preflight.

There are no local intake, terminal, process-control, arbitrary file-write,
model retry, or branch/worktree deletion controls. Cleanup-ready is a read-only
state, not deletion authorization. The existing runner loop owns safe lease
reconciliation; uncertain model outcomes remain quarantined.

## Recovery and retained evidence

The runner keeps one active recovery journal under `~/.atoll/runner/` and
stores lease tokens separately with restricted permissions. It persists the
thread ID before it accepts turn-start evidence, and persists completion
before lease finalization. Raw model messages, reasoning, commands, and tool
payloads are not copied into the runner journal or lifecycle comments.

A definitive Codex `turn.failed` event is saved as a bounded failure result
and finalized as failed. Raw error text is not included in lifecycle reports.

A dead worker after turn intent without durable completion becomes
`uncertain_outcome`. The SDK cannot prove whether an interrupted submitted
turn ran. The runner therefore does not submit another turn, create another
thread, or resume the same prompt automatically. Keep the evidence for
operator reconciliation.

A valid attention-resume claim can use only the exact retained thread with
matching runner ownership and worktree evidence. Invalid routing stops the
resume; it never starts a fresh thread as a fallback. Report-only and
acknowledgement-only recovery do not run Codex again.

Lifecycle comments contain bounded completion information and real thread/host
source metadata. The comment API does not support idempotent creation. If a
comment response is lost, recovery reads back the exact prior report. If it
cannot find that report, progress stops for operator reconciliation instead
of sending a possible duplicate.

Terminal branches, worktrees, and local thread evidence remain available for
inspection. The runner does not delete them automatically. An uncertain
outcome or active writer is not eligible for cleanup.

## Manual local smoke

The real Codex path is an opt-in developer harness available only in an Atoll
source checkout. It is not included in the installed npm CLI. Loopback remains
the normal default. Repository owners may run one bounded production
validation turn from a clean source checkout with an exact canonical origin,
organization, project, authenticated agent, issue, runner host and instance,
and the live project repository mapping's opaque `repo_ref` and repository full
name. The supplied `repo_ref` is checked against live issue routing and must
never be hard-coded. The smoke derives the canonical source root from its own
script, requires a clean source checkout, rebuilds the candidate in the
invocation, and records the source root, source `HEAD`, and worker-entry
SHA-256 in bounded mode-`0600` evidence. Exact production admission
performs zero presence `PUT`/upsert operations for either connected or
disconnected targets. Registration and refresh belong to separate operator setup
outside this invocation. The invocation reads the connected runner identity
before claim and rechecks it immediately before the claim; a disconnected target
fails closed. The runner does not explicitly select a model. The Codex runtime
and local Codex configuration supply model selection. `@openai/codex` and
`@openai/codex-sdk` are pinned to package/runtime version `0.153.4`; this
version is not a model name. Use the internal AH-2088 production smoke
runbook for the owner-only command and cleanup procedure.

For the normal loopback fixture profile, use a clean fixture profile with no
current or conflicting retained job, against a local Atoll base URL, from
`packages/cli`:

```bash theme={null}
cd packages/cli && bun run build
ATOLL_RUNNER_MANUAL_SMOKE=1 bun run runner:manual-smoke -- \
  --profile agent-a --issue issue-uuid --repository /path/to/checkout \
  --timeout-ms 300000 --evidence /absolute/path/evidence.json
```

The smoke runs one real Codex turn through the existing runner. It creates and
retains its owned local branch and worktree while preserving the primary
checkout. Its mode-`0600` evidence contains only fixture issue, runner host,
lease generation, retained branch/worktree, base/head SHA, thread ID, terminal
state, and timestamps, and is written outside the checkout. A timeout is
always failure; retained journal state is authoritative. The harness never
pushes or deletes retained state, publishes, deploys, or installs a service.
Codex Desktop visibility is a separate operator observation; automated tests
use a fake SDK and do not spend Codex usage.
