# AgentSwitch: Manual

## Getting Started

### Synopsis

```
ax [pick]                     open the picker
ax new [harness [flags...]]   start a new session
ax "task" [flags]             run a task on your default harness
ax <harness> "task" [flags]   run a task on a named harness
ax read | send | tag | ask | reply | runs | kill | list
ax help
```

`ax "task"` is the short form once you set `default_harness` (see [Configuration file](#configuration-file)). A known verb or harness name shadows a bare prompt, so `ax <harness> "task"` still targets a specific harness. Both forms appear below.

### Description

AgentSwitch is a daemonless switchboard for CLI coding agents. It ships as a single Go binary called `ax` that launches, watches, detaches, reattaches, and coordinates agent sessions from one TUI. Each coding agent (a harness) already writes its own transcripts. ax reads those stores, lists every session in one searchable terminal picker, and resumes the one you pick by running that harness's own resume command.

ax does not replace your harness, and it has no LLM of its own. There is no daemon either: everything works off local files, databases, and the heartbeat files live sessions write. No mux is the default, and it is first-class, not a fallback. Out of the box, with zero external dependencies, ax's own session holder keeps your harnesses alive when you detach (Ctrl-a then d), and the picker lets you check on them across projects and reattach. The full launch, detach, monitor, reattach loop works with no tmux or zellij installed.

A multiplexer is optional, never required. When you run tmux or zellij, ax drives it natively, opening and managing panes and windows for your sessions. If a session is already open, ax jumps to its window instead of starting a second copy. Sessions on other machines fold into the same list over SSH (see [Remote hosts](#remote-hosts)).

ax also has a set of shell verbs, so one session can drive others: launch sessions, read their turns, steer them, gate their completion on an accept check, and stop them. Hard limits on cost, fan-out, and time bound every run (see [Control plane](#control-plane)). Run the verbs yourself, or hand a session a short behavior prompt that names the commands, and it becomes the coordinator.

ax builds on tools native to your system (tmux, dtach, ssh) rather than a runtime of its own, so every backend piece is swappable. Extending it takes one `[[harness]]` block, and that block is the whole interface. One picker and one verb set cover local LLMs, frontier-model harnesses, and remote sessions.

For step-by-step walkthroughs with real captured terminal output, see the [tutorials page](tutorials.html).

### Installation

#### Install Methods

| Method | Command or Location | Notes |
| --- | --- | --- |
| Binary release | [latest release](https://github.com/agentswitch-org/ax/releases/latest) | Download the archive for your platform, extract `ax`, and place the executable on your `PATH`. |
| Go toolchain | `go install github.com/agentswitch-org/ax@latest` | Builds `ax` locally from the module source. The executable lands in `$(go env GOPATH)/bin` or `$(go env GOBIN)`. That directory must be on your `PATH`. |
| Source checkout | `make install` | Clone the [repository](https://github.com/agentswitch-org/ax) and run `make install`. It builds and copies `ax` to `~/.local/bin`. |

#### Runtime Requirements

Fuzzy matching is built in, so you don't need `fzf`. Everything below is optional and degrades gracefully when it is missing. If you already use one, ax picks it up on its own.

| Program | Requirement | Use |
| --- | --- | --- |
| `tmux` | optional | Optional multiplexer backend. When present, ax drives it natively: each session runs as a window and ax opens and manages them for you. The bundled `tmux/ax.tmux` adds bindings that open the picker in a tmux popup. Without it, ax's own session holder keeps every session alive and tracked, so the full launch, detach, monitor, reattach loop works with no mux at all. Zellij is the alternative (see [Multiplexer backends](#multiplexer-backends)). |
| `dtach` | recommended | Holds a session so closing its window detaches it instead of ending it. `ax attach <id>` reattaches from the shell. Without it, closing a window ends the session. |
| `ripgrep` | optional | Speeds up transcript content search. `/` in the picker and `ax search` work the same either way. Without it, AgentSwitch searches in-process. |
| `zoxide` | optional | Supplies directory candidates when starting a new session. Without it, type a path (`/` or `~` to browse). |

For a remote host, `ax` and `dtach` must be installed on that host too.

## Multiplexer Backends

By default ax runs on its own session holder with no multiplexer. That is first-class, not a fallback: sessions launch, stay alive when you detach (Ctrl-a then d), show up in the picker, and reattach on demand. Set a multiplexer and ax drives it natively, so live sessions run as real windows or tabs you can jump into. One config setting picks the backend.

| Backend | A session runs as | Behavior |
| --- | --- | --- |
| no mux (built-in holder) | a detached process ax holds | The default, with zero external dependencies (POSIX only). ax starts each session detached so it survives the launching terminal and keeps running when you detach with Ctrl-a then d. You watch it in the picker by its live status and transcript preview, and reattach anytime with `Enter` in the picker or `ax attach`. `ax send` writes into the session as typed input, and an interrupt delivers a real SIGINT to redirect a turn without killing the session. That is the full launch, detach, monitor, reattach loop with nothing else installed. |
| `tmux` | a tmux window | Optional and first-class. When set, ax drives tmux natively: sessions open in the background or focused, ax finds a session's window again by a tag it set at launch, multi-line `ax send` arrives as one bracketed paste, and `ax move` sorts related windows into a named tmux session. |
| `zellij` | a Zellij tab | The optional alternative. Each session gets its own named tab holding one pane. Launch, jump, send, and interrupt all work. Zellij's CLI is weaker at queries than tmux, so a few things are best-effort: a new tab always opens focused because Zellij has no open-in-background, ax only sees the Zellij session you are attached to, a multi-line send submits at each newline, and `ax move` is not supported. |
| `none` | the current terminal | Bare pass-through with no window tracking and no holder. ax launches the session in the terminal you are in and leaves it there. |

Pick a backend with the `mux` setting in your config file:

```
# ~/.config/ax/config.toml
mux = "tmux"     # optional. unset uses the built-in no-mux holder. tmux, zellij, process, or none
```

An unset or unrecognized value means the built-in no-mux holder (the same behavior as `process`), so ax works with no config at all and no multiplexer installed. Set `mux` to tmux or zellij to have ax drive that multiplexer instead. Under `none` ax launches the session in the current terminal and does not track it.

The tmux and zellij backends prefix every window, session, and tab name they create with `ax:`, so ax-managed windows stand out in the native status bar and a single prefix match selects all of them. Set `mux_prefix` to use a different prefix, or `mux_prefix = "off"` to disable it. The process and none backends have nothing to name, so the prefix does not apply to them.

## The Picker

### Layout

Each harness tells ax where its transcripts live, how to pull a session id out of them, which parser to use, and how to launch or resume a session. ax scans those stores and builds one table: status, model, age, context use, cost, directory, window, and title for every session, local or remote.

Sessions ax launches write heartbeat files while they run. From those files, tmux window metadata, and a harness state hook if you install one, the picker knows whether a session is working, idle, blocked on you, done and waiting for review, or dead after a bad exit.

### Example Screen

The picker is a terminal table with a transcript preview pane below it. The example uses the default columns and sample data.

![the ax picker: a session table with harness, status, model, age, context, cost, directory, and title columns, a key hint row, and a transcript preview of the selected webshop session below](images/t1-picker.png)

#### Status Values

The `STATUS` column folds attention, activity, and liveness into one cell. When several apply, the most pressing wins: attention first, then activity, then a crash. Blank means the session is not running.

| Status | Meaning |
| --- | --- |
| `✓ review` | A run finished and is presenting its result. It waits for you to accept (green). |
| `needs you` | Blocked on your input: a permission prompt or an `ax ask` question (red). |
| `needs auth` | Blocked on a login or OAuth flow. Attach to complete it (yellow). |
| `working` | Live with recent terminal output. Shown with a spinner. |
| `idle` | Live but quiet. |
| `crash` | The heartbeat went stale. The session died without a clean exit. |
| blank | Inactive. A past session you can resume. |

#### Columns

| Field | Description |
| --- | --- |
| `HARNESS` | The command-line agent or harness that owns the transcript. |
| `STATUS` | The merged status cell described above. |
| `MODEL` | Model recovered from transcript metadata. |
| `AGE` | Time since the last recorded session activity. |
| `CTX` | Estimated context-window use, from harness token counts and model context size. Colored as it approaches compaction. |
| `COST` | Recorded harness cost when available. Otherwise an estimate from token counts and model pricing. |
| `DIR` | Project directory of the session. A leading `!` marks a folder that was renamed or moved. Resuming prompts you to relink it. |
| `WIN` | Live tmux location when the session is already open. Blank means no matching window. |
| `TITLE` | Session title or inferred summary, depending on the harness transcript format. |

A few columns show up on their own: `HOST` when you have remote hosts, `NAME` and `GROUP` when there are runs, and `TAGS` once anything is tagged. Put `tag:<key>` in the `columns` config to pin one tag key as its own column.

### Keys

The picker opens in normal mode. You can rebind any key below under `[keys]` in the config. The help screen (`?`) and the hint row always show the keys you have set. `Enter` (open), `Esc` (leave a mode), and the arrow keys are fixed.

| Group | Keys | Action |
| --- | --- | --- |
| movement | `j` / `k` | Line down / up. |
| `g` / `G` | Jump to top / bottom. |  |
| `d` / `u` | Half page down / up. |  |
| preview | `J` / `K` | Scroll the transcript preview. |
| `n` / `N` | Next / previous content-search match. |  |
| sorting | `H`, `L`, `s` | Select a column and sort by it. Press `s` again to reverse. |
| view | `t` | Toggle all sessions vs active only. Saved between opens. |
| `m` | Filter by machine, cycling all, local, each host. |  |
| `f` | Filter by run. |  |
| `T` | Toggle the run tree view. |  |
| `b` | Cycle the group-by pivot: flat, by directory, by run, then one per tag key. |  |
| search | `i`, `a` | Filter the rows on metadata. `Esc` returns to normal mode, `Enter` keeps the filter. |
| `/` | Search transcript text. Matches highlight in the preview. |  |
| session | `Enter` | Open: resume the session, or jump to its window if already open. |
| `e` / `E` | Resume without / with the harness's configured flags. |  |
| `c` / `C` | New session without / with the harness's configured flags (`Ctrl-N` also works while filtering). |  |
| `x` | Kill the session. |  |
| `r` | Reply to a session waiting on an `ax ask` question. |  |
| `Tab` | Mark / unmark a row for a multi-session action. |  |
| `v` | Visual select: `j`/`k` extends, `v` or `Enter` keeps the marks, `Esc` drops them. |  |
| `l`, `M` | `l` tags the selection (`key=value` sets, `-key` removes). `M` moves its windows into a tmux session you name. |  |
| exit | `?` | Show the in-terminal key reference. |
| `q` | Quit the picker. |  |

### Commands

| Command | Description |
| --- | --- |
| `ax`, `ax pick` | Open the picker. |
| `ax new [harness [flags...]]` | Start a new session interactively. With a harness argument it skips the picker, e.g. `ax new claude --dangerously-skip-permissions`. |
| `ax list [--run R] [--json]` | Print indexed sessions. `--json` emits the federation format other hosts and coordinators read. |
| `ax attach <id>` | Reattach a held session in the current window. Closing the window detaches it again. |
| `ax search <query>` | Print ids of sessions whose transcript matches (`--json` available). |
| `ax kill <id>... \| --run R` | Stop sessions. `--run` cascade-kills a whole run. |
| `ax move --tag k=v \| --run R \| <id>... [--to NAME]` | Move sessions' windows into their own tmux session. |
| `ax log` | Print the diagnostic log (session launches, errors). |
| `ax models update` | Refresh model price and context data from models.dev. |
| `ax help` | Print command usage, including the control-plane verbs. |

## Control Plane

### Model

A session is a job. One session can drive others: launch them, read what they produce, steer them, decide when they are done, and stop them once a check passes, with little or no human in the loop. ax itself is only the mechanism. It has no LLM and never judges whether a task is finished. It reports facts (a turn finished, a session is waiting, a session exited or crashed), performs actions (launch, read, send, tag, ask, kill), and enforces limits. The thinking always happens in a session.

There is no daemon. The verbs are stateless CLI commands that read and write sidecar files. The only long-running piece is the coordinator, and that is itself a session ax manages.

### The Launcher

A task launch (`ax "task"`, or `ax <harness> "task"` for a specific harness) launches without a prompt and prints the session id and run id, so a script or a coordinator can grab them:

```
$ ax "fix the flaky test"
a1b2c3d4  a1b2

$ ax "add a CHANGELOG entry" --wait --unattended
$ echo $?
0

$ ax "build a blog" --behavior ~/.config/ax/coordinator.md --max-cost 100 \
    --max-depth 2 --accept ./verify.sh
```

By default a task runs watched: the harness runs interactively in a tracked window and the session concludes into a `done` state when the task finishes, so you can watch it, `ax send` to it, and read its final report after. It skips the folder-trust prompt, so a session started in a fresh directory (a worktree, a `/tmp` dir) never hangs waiting on it. `--close-on-done` ends the window when the task finishes instead of holding it in `done`. `--headless` (or `--wait`) opts into a headless job that runs the harness non-interactively (for Claude Code, `claude -p`) and exits on done.

`--wait` blocks and hands back an exit code: 0 when the session tagged success (and `--accept` passed), non-zero on a fence trip, a give-up, or a crash. Idle never counts as done. `--unattended` makes `ax ask` return a default instead of blocking, so a CI run can't deadlock waiting on a human. The one-line CI shape:

```
ax "make 'go vet ./...' pass" --wait --unattended --accept "go vet ./..." --timeout 30m
```

### Verbs

Every verb is non-interactive: ids and data on stdout, diagnostics on stderr, and exit codes that reflect the outcome. Any verb that takes an id also takes a `host/id`, and routes over that host's transport.

| Verb | Purpose |
| --- | --- |
| `ax <harness> "TASK"` | Launch a task. Mints and prints the session id and run. |
| `ax read <id> \| --run R` | Read a session's turns from the parsed transcript, never a scraped screen. `--follow` blocks and streams one NDJSON event per turn boundary. `--run` multiplexes a whole run into one stream, so a loop blocks on the next event instead of polling. |
| `ax send <id> "text"` | Type input into a running session. `--interrupt` redirects a session mid-turn without killing it. `--no-enter` skips the submit. |
| `ax tag <id>` | Set metadata: `--name`, `--run`, `--parent`, `--add-label`/`--rm-label`, and `--outcome`, which concludes and applies the accept check to a run. |
| `ax ask "question"` | Human ask / reply, called by a session: blocks until answered, and the picker shows `needs you`. |
| `ax reply <id> "answer"` | Answer a blocked `ax ask` (or use the picker's `r`). The reply is free-form text, so it can be the next instruction. |
| `ax wait <id>... [--all\|--any]` | Block until sessions reach a terminal state. Exit 0 when they conclude success, non-zero on failure, 124 on `--timeout`. The scripting counterpart to `--wait` on a launch. |
| `ax result <id> [--json]` | Print a concluded session's final report (its last assistant message) plus outcome and exit, the watched equivalent of the final answer from a headless `claude -p`. |
| `ax continue <id> "TASK"` | Resume a session's context with a new task, tracked and scriptable. The reuse primitive between `ax send` (needs a live window) and a cold launch. Watched by default, and `--wait` runs it as a job. |
| `ax restart <id> [--fresh]` | Rebuild a session from its persisted launch spec (same task, model, fences, env, and auth), pinned back into its run. `--fresh` also cleans up its socket and process files. |
| `ax check` | Run this run's `--accept` check and print its output and status, without concluding the run. |
| `ax list --json [--run R]` | The coordinator's live world snapshot: the run tree, per-session state, cost, and model. |
| `ax kill --run R` | Cascade-kill a whole run, root session last, so no session is orphaned. |
| `ax runs [--follow] [--json]` | List concluded run records with outcomes. |
| `ax metrics` | Session and run cost, tokens, duration, and outcome counts. `--json` for scripting, `--prom` for a Prometheus textfile export. |
| `ax hook install <harness>` | Make the harness report its state authoritatively through its own hooks instead of ax inferring it from output. |

`read --follow` emits `turn`, `waiting` (reason `input` or `auth`), `exit`, and `crash` events, one JSON object per line, each carrying a cursor and a short preview.

### Fences, Accept Check, Ask / Reply

Fences are hard limits ax enforces, the LLM-workload version of pod resource limits. Depth and session fences are checked at launch. Cost, token, and time fences are polled by the run's root session, which cascade-kills the run and writes the run record the moment one trips.

| Fence | Bounds | Default |
| --- | --- | --- |
| `--max-cost N` | Total spend across the run, at API pricing. | none |
| `--max-tokens N` | Total tokens across the run. Use on a subscription, where marginal cost is zero and `--max-cost` is inert. | none |
| `--max-workers N` | Concurrent live sessions in the run (tree width). | none |
| `--max-depth N` | Recursion depth (tree height). A deeper launch is refused. This prevents runaway recursive launches. | 1 (flat) |
| `--timeout DUR` | Wall-clock limit for the whole run. | none |

The accept check is `--accept ./check.sh`. When a session tags `--outcome success`, ax runs the script. A non-zero exit rejects the tag and feeds the output back, so a run cannot conclude on an unverified claim. For ask / reply (`ax ask` / `ax reply`), the behavior decides when to ask a human. ax blocks the session, sends the notification, and holds the question until you answer.

Launch from inside a session and it inherits that session's run and parent automatically (through `AX_SESSION_ID`, `AX_RUN`, `AX_DEPTH`, and the fence limits ax puts in the environment), so the run tree assembles automatically. `--group` and `AX_GROUP` still work as deprecated aliases for `--run` and `AX_RUN`. A `[policy]` section can also allow-list harnesses and allow or deny models at launch.

### The Coordinator

A coordinator is a session launched with a coordinator behavior prompt. That prompt is a recipe file, not something baked into the binary, so `--behavior` takes the path to it. Copy `recipes/coordinator.md` to a stable location once (the [quick start](index.html#quick-start) does this) and point `--behavior` at that path. The coordinator splits the work, launches sessions, judges their results against the evidence, and concludes when the criteria pass. ax imposes no shape on the run: pipeline, fan-out, tournament, and recursion are all decided by the session.

`--behavior` takes the path to a behavior file (or a literal string). Copy `behaviors/coordinator.md` to `~/.config/ax/coordinator.md` and pass that path.

```
ax "Add dark mode to this app. Done when: the toggle works and 'npm test' passes." \
    --behavior ~/.config/ax/coordinator.md --model opus --max-cost 25 --max-workers 3
```

Watch the run in the picker: `f` filters to the run, `T` shows the run tree.

```
 ax v0.1.0  NORMAL ───────────── run:blog-x1  tree ── 4 sessions · $19 · 4 live  ? help

 HARNESS  STATUS      NAME           GROUP    MODEL       AGE  CTX  COST   TITLE
 claude   ⠧ working   coordinator    blog-x1  opus-4-8    now  38%  $9.80  build a blog about racecars
 claude   ⠋ working   ├─ posts       blog-x1  sonnet-4-6  now  22%  $5.14  write the first six posts
 claude   needs you   ├─ theme       blog-x1  sonnet-4-6  1m   17%  $3.02  dark retro theme
 codex    idle        └─ deploy      blog-x1  gpt-5.5     3m   9%   $1.21  wire the pages deploy
```

Attention rolls up the tree: the root session shows `needs you` whenever any session under it is blocked, so you go straight to the blocked session instead of scanning. Answer with `r`.

#### Done and Review

An attended run does not stop silently. When the session's criteria pass, it tags `--outcome success` (and runs the `--accept` check if you set one), shows a one-line result through `ax ask`, and waits. The picker marks this the green `✓ review` state, separate from the red `needs you`: the run is presenting its result, not blocked. Reply to accept it, or reply with new criteria and it keeps going. Finished runs land in `ax runs` with an outcome of `success`, `gave_up`, `budget_hit`, or `crashed`.

## Remote Hosts

### Federation

ax merges sessions from other machines into one list over SSH, or any transport command:

```
# ~/.config/ax/config.toml
[[host]]
name      = "laptop"
transport = "ssh -t user@laptop"
```

Each host needs `ax` installed (and `dtach`, so its sessions survive). No daemon, no open port: ax runs the transport command, and that command carries its own auth. Pick a remote session and it attaches over the transport in a local window. `ax kill`, `ax read`, and the rest route the same way. `c` can start a new session on a host, and `m` filters the list by machine and shows each host's status: online, offline, `no ax`, or `old ax`.

Containers work the same way: a transport like `container exec -i <id>` or `kubectl exec` makes a container or a pod another host, and throwaway boxes can register themselves into the picker. Setup details, advice on credentials, and the reasoning behind sandboxing are in the [GitHub README](https://github.com/agentswitch-org/ax#remote-hosts) and [docs/control-layer.md](https://github.com/agentswitch-org/ax/blob/master/docs/control-layer.md).

## Running Agents Safely

By default, ax launches each agent in your harness in autonomous or permission-bypass mode so agents work without blocking. That means they run unattended. Run them in an environment you trust. You can control this behavior per harness in the config.

### Disposable Boxes

An unattended agent will do whatever it decides the task needs, and it can be steered by anything it reads. So don't run it on your workstation. Give it a dedicated, disposable box, one clone or worktree per task, and tear it down when the task is done. A box that only ever held one job holds nothing valuable to steal and nothing important to break.

Keep trust one-directional. Your machine reaches into the agent box. The box does not reach back. ax fits this shape by design: it pulls over your transport, runs no daemon, and opens no port. Nothing on the box waits for a connection, so the agent has no path back to your machine.

Never forward your SSH agent into an agent box. An injected prompt that lands on a box with a forwarded agent can authenticate as you to every host your key reaches, and `ssh -A` (or a careless `ProxyJump`) grants exactly that. The default `ssh -t` transport does not forward the agent.

### Secrets and Egress

Keep no standing secrets on the box. No personal SSH keys, no cloud credentials, no password manager. Inject short-lived, task-scoped credentials for the one job at hand, so a compromised box leaks a narrow, expiring grant instead of your long-lived credentials.

Default-deny egress. An agent that can reach the whole internet can exfiltrate anything it touches and pull in anything it is told to. Allowlist only what the task needs: the LLM API, the package registries it builds against, and the git remotes it pushes to. Everything else stays blocked.

## Configuration and Data

### Configuration File

Claude Code, Codex, pi, and opencode are built in and work with no config file at all. To change one of them or add your own harness, copy [`config.example.toml`](https://github.com/agentswitch-org/ax/blob/master/config.example.toml) to `~/.config/ax/config.toml`. Overrides happen field by field: a `[[harness]]` with just `name` and `args` adds flags but keeps the built-in glob, resume, and launch. Point `AX_CONFIG` at another file to use it instead. When `XDG_CONFIG_HOME` is set, the default path is `$XDG_CONFIG_HOME/ax/config.toml`.

```
# ~/.config/ax/config.toml

default_harness = "claude"

columns = ["harness", "status", "model", "age", "ctx", "cost", "dir", "win", "title"]

[[harness]]
name = "myagent"
glob = "~/.myagent/sessions/*/*.jsonl"
id_regex = "_(?P<id>[0-9a-f-]{36})\\.jsonl$"
format = "pi"
resume = "cd {dir} && myagent --resume {id} {args}"
launch = "myagent {args}"
args = "--profile work"

[keys]
kill = "K"

[[host]]
name      = "laptop"
transport = "ssh -t user@laptop"
```

#### Harness Fields

| Field | Description |
| --- | --- |
| `name` | Harness name. A built-in name overrides that built-in, field by field. |
| `glob` | Transcript file pattern. |
| `db` | Database path for database-backed harnesses. |
| `id_regex` | Regular expression extracting the session id from a path. |
| `format` | Transcript parser: `claude`, `codex`, `opencode`, or `pi`. A new harness can reuse an existing parser. |
| `resume` | Resume command. Placeholders: `{id}`, `{dir}`, `{model}`, `{args}`. |
| `launch` | Command starting a new session. Placeholders: `{dir}`, `{args}`, `{newid}` (a fresh id ax mints so it can track the window), `{model}`, `{behavior}`, `{task}`. Empty placeholders drop out with their flag. |
| `launch_headless` | The job-mode form (used for a task launch, `--wait`, `--unattended`) that runs to completion, e.g. `claude -p ...`. Without it a task runs `launch` under tmux. |
| `args` | Default flags, applied only by the with-flags keys (`C`, `E`). `c`, `Enter`, and `e` launch without them. |
| `waiting_re` | Pattern marking a session blocked on a sub-prompt (a permission y/n, an OAuth login) for the `waiting` follow event. Fallback when no state hook is installed. |

#### Other Sections

| Section | Description |
| --- | --- |
| `default_harness` | The harness a bare `ax "prompt"` launches, so you can drop the harness name. A known verb or harness name shadows a bare prompt, so `ax read`, `ax new`, and `ax codex "prompt"` keep their meaning. Unset means you name the harness every time. |
| `columns` | Picker column order. Valid keys: `host`, `harness`, `status`, `state`, `spin`, `activity`, `name`, `run`, `tags`, `tag:<key>`, `model`, `age`, `ctx`, `cost`, `dir`, `win`, `title`. |
| `mux` | Multiplexer backend. Unset uses the built-in no-mux session holder (the default). Optional values: `tmux`, `zellij`, `process`, or `none`. `mux_prefix` changes or disables the `ax:` window-name prefix. See [Multiplexer backends](#multiplexer-backends). |
| `shell` | What ax runs a harness through. The default is `sh -c`. Use a login shell like `zsh -lic` on a host whose harness sits behind a tool manager (mise, nvm, asdf). |
| `[keys]` | Rebind picker keys by action name. Only what you list changes. A value is one key or a list. |
| `[[host]]` | A remote machine: `name`, `transport`, optional `ax` path, and `raw_argv` for transports that pass argv verbatim (`kubectl exec ... --`, `docker exec`). |
| `[policy]` | Launch fence: a harness allow-list and a model allow/deny list a coordinator cannot step outside. |

### Supported Harnesses

| Harness | Session store | Resume command |
| --- | --- | --- |
| Claude Code | `~/.claude/projects/<mangled-cwd>/<uuid>.jsonl` | `claude --resume <id> --model <model>` |
| OpenAI Codex CLI | `~/.codex/sessions/<yyyy>/<mm>/<dd>/rollout-<ts>-<uuid>.jsonl` | `codex resume <id>` |
| opencode | `~/.local/share/opencode/opencode.db` | `opencode --session <id>` |
| pi coding agent | `~/.pi/agent/sessions/<mangled-cwd>/<ts>_<uuid>.jsonl` | `pi --session <id>` |

### Model Data

Context sizes and prices come from models.dev. A snapshot ships inside the binary so it works offline. `ax models update` writes a fresh copy to the state directory, and that copy wins at runtime.

Treat the cost columns as a guide, not a bill. When a harness records its own cost, ax shows that. Otherwise it estimates from the transcript's token counts and models.dev pricing. It won't always match what you are billed under subscriptions, bundled usage, credits, or provider-specific pricing.

### Local State

State files live under `~/.local/state/ax/`, or `$XDG_STATE_HOME/ax/` when it is set. ax never touches your transcripts. Everything it adds is sidecar data.

| Path | Purpose |
| --- | --- |
| `text/` | Plain-text transcript cache used for content search. |
| `index.json` | The session index cache. |
| `live/<id>` | Heartbeat records for running sessions. |
| `meta/<id>.json` | Per-session metadata sidecar: name, task, group, parent, labels, outcome. |
| `runs/<gid>.json` | Run records written when a group concludes: the tree, totals, and outcome. |
| `ask/<id>.json` | Pending `ax ask` questions awaiting a reply. |
| `hosts/<name>.json` | Dynamic self-registered host records (ephemeral boxes). |
| `hookstate/<id>` | Authoritative state written by an installed harness hook. |
| `run/*.sock` | dtach sockets holding detached sessions. |
| `models.json` | Updated model price and context snapshot from `ax models update`. |
| `dirmap.json` | Remembered relinks for project directories that were renamed or moved. |
| `ui.json` | Picker preferences such as the active-only scope toggle. |
| `log` | The diagnostic log printed by `ax log`. |

## Recipes

### Copy-Paste Playbooks

Every recipe runs as written. Edit the quoted task text to match your job. No configuration is needed first. Example:

```
ax "run go test ./... and fix any failures"
```

All the playbooks are on the [recipes page](recipes.html): run a task, run an accept check in CI, organize sessions with tags, watch a run from a shell, get notified when a run needs you, coordinate a goal with multiple agents, run an ongoing project with a coordinator, supervise an existing session, and change the goalposts mid-run. The source they come from is [docs/recipes.md](https://github.com/agentswitch-org/ax/blob/master/docs/recipes.md).
