# AgentSwitch documentation (full text)

> ax is a daemonless switchboard for CLI coding agents. One picker, one verb set, local and remote sessions.

---

# AgentSwitch: Quickstart

# Run every agent session from one picker.

Install ax, then start a coordinator in the current project.

1. Install ax

macOS

```
brew install --cask agentswitch-org/ax/ax
```

Linux / WSL

```
curl -fsSL https://agentswitch.org/install.sh | sh
```

Windows PowerShell

```
irm https://agentswitch.org/install.ps1 | iex
```

2. Start a coordinator

Run from your project root (any platform)

```
ax coordinate "Ship the importer. Done when: go test ./... passes."
```

What this does

The built-in coordinator behavior is written to `~/.config/ax/behaviors/coordinator.md` on first use (edit it to customize), and a self-propelled coordinator starts in the current directory: it triages the goal into `.coordinator/backlog.md`, delegates to tracked workers, and verifies results.

## What is AgentSwitch

### Daemonless switchboard for CLI coding agents

`ax`. It gives CLI coding agents one searchable session history and one set of shell verbs.

- Open the picker with `ax`, filter, press `Enter`.
- Launch Claude Code, Codex, pi, opencode, or a custom CLI harness.
- Fold local and remote hosts into the same list, including host-qualified rows such as `win01/<id>`.
- Detach with Ctrl-a then d, keep the session running, and reattach later.
- Use it on macOS, Linux, or native Windows; no daemon or server.

tmux and zellij can add mux windows and tabs, but they are optional. The built-in holder is enough for the launch, detach, monitor, reattach loop.

### Compose agent work with shell commands

ax gives every session a set of shell verbs: launch, read, send, ask, reply, wait, kill. Each verb prints session ids and JSON on stdout and returns a meaningful exit code, so sessions compose with standard shell tools.

Because those verbs print ids, JSON, and exit codes, normal shell tools can drive them. Use `jq` to pick a session id, a `for` loop to launch workers, or a check script to decide whether a run passed. A short behavior prompt can turn one session into a coordinator that launches, steers, and verifies the others, with fences on cost, fan-out, and time.

The [recipes page](recipes.html) collects copy-paste playbooks that run as written: CI gates, model-tier escalation, parallel fan-outs over a file corpus, scheduled briefings, email triage, and human-approval holds. Each one is shell plus ax.

### Two ways to run work

Run a recipe directly: copy it, edit the task text, and run the launch command. This suits a scheduled job or a known one-shot.

Or give the coordinator behavior a goal. The coordinator behavior (`behaviors/coordinator.md`) plans the work, launches recipes and workers, supervises them, and checks the result against your criteria. If you ask it to fix website prose, the coordinator can run the taste-gate recipe, pass the reviewer its behavior file (`behaviors/reviewer.md`), and iterate until the prose clears the rubric or the iteration cap is hit.

Use the coordinator when you want it to pick and run the recipe. Use a recipe directly when you already know the script you want.

## Install by operating system

The details below cover package alternatives and optional mux bindings:

- Install `ax`.
- Run `ax` to open the picker, or `ax new` to start a session.
- Optionally bind the picker inside tmux or zellij.

No multiplexer is needed. ax's own holder keeps sessions alive when you detach. On native Windows, the default process backend uses ConPTY and named pipes; on macOS and Linux, tmux or zellij are optional. For the full install matrix, see the manual's [Installation](manual.html#installation) chapter.

### macOS

#### 1. Install ax

Install with Homebrew:

```
brew install --cask agentswitch-org/ax/ax
```

Or download the release binary. Go to the [latest release](https://github.com/agentswitch-org/ax/releases/latest), download the `.tar.gz` for your architecture (`darwin_arm64` for Apple Silicon, `darwin_amd64` for Intel), extract `ax`, and place it on your `PATH`.

Developer fallback with the Go toolchain (requires Go 1.26.4+):

```
go install github.com/agentswitch-org/ax@latest
```

Or build from source:

```
git clone https://github.com/agentswitch-org/ax.git
            cd ax && make install
```

`make install` copies `ax` to `~/.local/bin`, which is not on the default macOS `PATH`. Add `export PATH="$HOME/.local/bin:$PATH"` to your shell profile if `ax` is not found.

#### 2. Multiplexer (optional)

Without a multiplexer, ax runs on its own session holder. To let ax drive a multiplexer natively, install tmux with Homebrew:

```
brew install tmux
```

Or install zellij instead:

```
brew install zellij
```

#### 3. Optional: set the mux config

Only needed if you installed a multiplexer. Tell ax to drive it in `~/.config/ax/config.toml`. With no setting, ax uses its own no-mux session holder.

```
# ~/.config/ax/config.toml
            mux = "tmux" # or "zellij" if you installed zellij
```

#### 4. Bind the picker to a key

See [Bind the picker](#setup-keybinding) below. The keybindings are the same on macOS, Linux, and WSL.

### Linux

#### 1. Debian, Ubuntu, and WSL

Install the `.deb` directly from the latest GitHub release. The command selects `amd64` or `arm64` from the Debian package architecture.

```
set -eu
arch=$(dpkg --print-architecture)
tag=$(curl -fsSL https://api.github.com/repos/agentswitch-org/ax/releases/latest |
  sed -n 's/.*"tag_name": *"\([^"]*\)".*/\1/p')
test -n "$tag"
version=${tag#v}
curl -fL \
  "https://github.com/agentswitch-org/ax/releases/download/$tag/ax_${version}_linux_${arch}.deb" \
  -o /tmp/ax.deb
sudo apt install /tmp/ax.deb
ax version
```

Manual package install

To install a package downloaded in the Windows browser:

1. Run `dpkg --print-architecture` in WSL. The result is usually `amd64`; use `arm64` on an ARM-based Windows machine.
2. Open the [latest ax release](https://github.com/agentswitch-org/ax/releases/latest) in the Windows browser. Under Assets, download `ax_<version>_linux_<arch>.deb` for that architecture.
3. In WSL, run `explorer.exe .` and move the downloaded `.deb` into the folder that opens.
4. Install the package with the command below.

```
sudo apt install ./ax_*_linux_*.deb
ax version
```

#### 2. Other Linux distributions

Install the latest tarball to `~/.local/bin`:

```
curl -fsSL https://agentswitch.org/install.sh | sh
ax version
```

Fedora, RHEL, Rocky, and AlmaLinux users can download the matching `.rpm` from the [latest release](https://github.com/agentswitch-org/ax/releases/latest), then install it:

```
sudo dnf install ./ax_<version>_linux_<arch>.rpm
```

Developer fallback with the Go toolchain (requires Go 1.26.4+):

```
go install github.com/agentswitch-org/ax@latest
```

Or build from source:

```
git clone https://github.com/agentswitch-org/ax.git
            cd ax && make install
```

`make install` copies `ax` to `~/.local/bin`. Make sure that directory is on your `PATH`.

#### 3. Multiplexer (optional)

Without a multiplexer, ax runs on its own session holder. To let ax drive a multiplexer natively, install tmux with your package manager.

Debian and Ubuntu:

```
sudo apt install tmux
```

zellij is not in every apt repository. Install it from the [zellij release binaries](https://github.com/zellij-org/zellij/releases/latest) or with `cargo install zellij`.

#### 4. Optional: set the mux config

Only needed if you installed a multiplexer. Tell ax to drive it in `~/.config/ax/config.toml`. With no setting, ax uses its own no-mux session holder.

```
# ~/.config/ax/config.toml
            mux = "tmux" # or "zellij" if you installed zellij
```

#### 5. Bind the picker to a key

See [Bind the picker](#setup-keybinding) below. The keybindings are the same on macOS, Linux, and WSL.

### Windows

#### 1. Install ax

Install the latest release with PowerShell:

```
irm https://agentswitch.org/install.ps1 | iex
```

Manual PowerShell install:

```
$cpu = $env:PROCESSOR_ARCHITEW6432
            if (-not $cpu) { $cpu = $env:PROCESSOR_ARCHITECTURE }
            $arch = if ($cpu -eq 'ARM64') { 'arm64' } else { 'amd64' }
            $release = Invoke-RestMethod 'https://api.github.com/repos/agentswitch-org/ax/releases/latest'
            $asset = $release.assets | Where-Object { $_.name -match "windows_$arch\.zip$" } | Select-Object -First 1
            if (-not $asset) { throw "No Windows asset for $arch in the latest release" }
            $dir = Join-Path $env:LOCALAPPDATA 'ax\bin'
            $exe = Join-Path $dir 'ax.exe'
            $tmpDir = $null
            try {
            $tmpDir = Join-Path ([IO.Path]::GetTempPath()) ('ax-install-' + [Guid]::NewGuid().ToString('N'))
            $extractDir = Join-Path $tmpDir 'extract'
            $zip = Join-Path $tmpDir $asset.name
            New-Item -ItemType Directory -Force -Path $tmpDir, $extractDir, $dir | Out-Null
            Invoke-WebRequest $asset.browser_download_url -OutFile $zip
            Expand-Archive -LiteralPath $zip -DestinationPath $extractDir -Force
            $stagedExe = Get-ChildItem -LiteralPath $extractDir -Filter 'ax.exe' -File -Recurse | Select-Object -First 1
            if (-not $stagedExe) { throw 'Release asset did not contain ax.exe' }
            Copy-Item -LiteralPath $stagedExe.FullName -Destination $exe -Force
            } finally {
            if ($tmpDir -and (Test-Path -LiteralPath $tmpDir)) {
            Remove-Item -LiteralPath $tmpDir -Recurse -Force -ErrorAction SilentlyContinue
            }
            }
            $userPath = [Environment]::GetEnvironmentVariable('Path', 'User')
            if ($userPath -notlike "*$dir*") { [Environment]::SetEnvironmentVariable('Path', "$dir;$userPath", 'User') }
            $env:Path = "$dir;$env:Path"
            ax version
```

Developer fallback with the Go toolchain (requires Go 1.26.4+):

```
go install github.com/agentswitch-org/ax@latest
```

#### 2. Use the native no-mux backend

Leave `mux` unset. Native Windows uses ax's `process` backend: each harness runs under ConPTY, steering input goes through a per-session named pipe, and Ctrl-a then d detaches without ending the session.

tmux popup bindings do not apply on native Windows. Use `ax`, `ax new`, `ax attach`, `ax read`, and `ax send` from PowerShell or Windows Terminal. WSL is still fine when you want the Linux toolchain.

#### 3. Optional: federate a Windows host

A Windows box such as `win01` can be a remote host. If its OpenSSH default shell is PowerShell, set `shell = "pwsh"` so ax quotes remote arguments for PowerShell.

```
[[host]]
            name = "win01"
            transport = "ssh -t win01"
            shell = "pwsh"
            # headless = true # use if remote interactive launch/attach is not verified
```

Remote session ids appear as `win01/<id>`. `ax config status` shows each host's OS, shell, ax version, and wire compatibility.

### Release candidates

Release candidates publish as GitHub prereleases, which `releases/latest` does not return; these blocks resolve the newest release directly.

macOS and Linux:

```
set -eu
tag=$(curl -fsSL https://api.github.com/repos/agentswitch-org/ax/releases |
  sed -n 's/.*"tag_name": *"\([^"]*\)".*/\1/p' | head -n 1)
os=$(uname -s | tr '[:upper:]' '[:lower:]')
arch=$(uname -m); case "$arch" in x86_64) arch=amd64 ;; aarch64|arm64) arch=arm64 ;; esac
curl -fL -o /tmp/ax.tar.gz \
  "https://github.com/agentswitch-org/ax/releases/download/$tag/ax_${tag#v}_${os}_${arch}.tar.gz"
tar -xzf /tmp/ax.tar.gz -C /tmp ax
/tmp/ax version
mkdir -p ~/.local/bin && install -m 0755 /tmp/ax ~/.local/bin/ax
```

Debian, Ubuntu, and WSL via the `.deb` instead:

```
set -eu
tag=$(curl -fsSL https://api.github.com/repos/agentswitch-org/ax/releases |
  sed -n 's/.*"tag_name": *"\([^"]*\)".*/\1/p' | head -n 1)
arch=$(dpkg --print-architecture)
curl -fL -o /tmp/ax.deb \
  "https://github.com/agentswitch-org/ax/releases/download/$tag/ax_${tag#v}_linux_${arch}.deb"
sudo apt install /tmp/ax.deb
ax version
```

Windows PowerShell:

```
$tag = (irm https://api.github.com/repos/agentswitch-org/ax/releases)[0].tag_name
$arch = if ($env:PROCESSOR_ARCHITECTURE -eq 'ARM64') { 'arm64' } else { 'amd64' }
irm "https://github.com/agentswitch-org/ax/releases/download/$tag/ax_$($tag.TrimStart('v'))_windows_$arch.zip" -OutFile "$env:TEMP\ax.zip"
Expand-Archive "$env:TEMP\ax.zip" -DestinationPath "$env:LOCALAPPDATA\ax\bin" -Force
& "$env:LOCALAPPDATA\ax\bin\ax.exe" version
```

The installers pin any tag with `AX_RELEASE_TAG=v0.1.3-rc.1` (sh) or `$env:AX_RELEASE_TAG` (PowerShell), and Homebrew has the `ax-rc` cask.

### Bind the picker

Bind the picker to a key. One key opens the session table over whatever you are doing, and a second key starts a new session. These bindings are for tmux, zellij, and WSL-style shells; on native Windows, start with the plain `ax` and `ax new` commands.

#### tmux

Add these lines to `~/.tmux.conf`. The prefix is whatever chord you already use.

```
bind-key a display-popup -E -B -s 'bg=terminal' -w 100% -h 100% "ax pick"
            bind-key A display-popup -E -B -s 'bg=terminal' -w 100% -h 100% "ax new"
```

Reload the config with `tmux source-file ~/.tmux.conf`. Then `<prefix> a` opens the picker and `<prefix> A` starts a new session. Use `-w 85% -h 80%` for a floating box instead of a full-screen popup, or source the bundled `tmux/ax.tmux` from the repository.

#### zellij

Add a keybinding to `~/.config/zellij/config.kdl` that runs the picker in a floating pane.

```
keybinds {
            normal {
            bind "Alt a" {
            Run "ax" "pick" {
            floating true
            close_on_exit true
            name "ax"
            }
            }
            }
            }
```

Then `Alt+a` opens the picker in a floating pane that closes when you leave it. Swap `pick` for `new` on a second binding to start a fresh session the same way.

## First run

### Set a default harness

Set a default harness once so a bare prompt launches it. Put this in `~/.config/ax/config.toml`:

```
# ~/.config/ax/config.toml
            default_harness = "claude"
```

After this, `ax "prompt"` launches your default harness. A known verb or harness name shadows a bare prompt, so `ax read`, `ax new`, and `ax codex "prompt"` keep their usual meaning. Every example on this site uses the short form.

### Launch, detach, reattach

Run your first task from any project directory:

```
ax "run the test suite and fix any failures"
```

ax prints the session id and returns your shell prompt. The session runs in ax's own session holder, so it does not depend on the terminal you launched it from. Open the picker to see it working:

```
ax
```

The picker lists every session with its status, model, cost, and a live transcript preview. Press `Enter` to attach and watch. Press Ctrl-a then d to detach, and the session keeps running. Later, reattach from any terminal. That loop does not need tmux, zellij, or any other external holder.

A multiplexer is optional. Install tmux or zellij, set `mux` in the config, and ax drives it natively with a real window per session. See the manual's [Multiplexer backends](manual.html#multiplexer-backends) chapter.

## Run a project coordinator

Use a coordinator when one goal has several independent parts.

- You give one session the goal and acceptance criteria.
- It launches tracked workers, reads their progress, and verifies the result.
- You watch the run in the picker and answer only when it asks.
- A write fence keeps the coordinator in `.coordinator/*.md`; real edits go through workers.

### Start it

One command from the project root, on any platform:

```
ax coordinate "Ship the v2 importer. Done when: go test ./... passes and the README covers the new flow."
```

The coordinator behavior ships inside the ax binary. On first use it is written to `~/.config/ax/behaviors/coordinator.md`; edit that file to customize, and your copy wins on every later run. `--harness` picks the harness (default: `default_harness`, else claude), `--small` selects the trimmed variant for small local models, and any launch flag overrides the defaults (`ax coordinate --help` lists them). The picker's compose flow (`c`, then "coordinator") is the same thing with prompts.

For a scripted or customized bootstrap, the copy-and-own recipe still works: `curl -fsSL https://agentswitch.org/coordinator.sh | sh` (or `irm https://agentswitch.org/coordinator.ps1 | iex` on Windows), built on [behaviors/coordinator.md](https://github.com/agentswitch-org/ax/blob/master/behaviors/coordinator.md) and [recipes/coding-project-coordinator](https://github.com/agentswitch-org/ax/tree/master/recipes/coding-project-coordinator).

### Watch it

`ax coordinate` launches the coordinator with `--write './.coordinator/**/*.md'`, `--fence best-effort`, `--no-subagents`, `--max-workers 2`, `--max-depth 2`, `--keep-live`, `--self-propel`, and `--attach`. Self-propel is ax's outer loop: when the coordinator ends a turn with the backlog still open, ax re-invokes it instead of letting it stall, until the goal's done criteria pass, it genuinely needs you, or an idle cap trips. This works for claude, pi, and codex.

Open the picker with `ax` to watch the run. Press `f` to filter to the run, `T` to show the run tree, and `r` to answer when a session shows `needs you`. Open-ended coordinators intentionally leave cost and token fences off; edit the script if you want a hard cap that cascade-kills the run.

### Use it from compose

The startup sequence leaves the script in `~/.config/ax/recipes`. Point compose at that directory to start it later from the picker:

```
# ~/.config/ax/config.toml
            behaviors_dir = "~/.config/ax/behaviors"
            recipes_dir = "~/.config/ax/recipes"
```

Then open `ax`, press `c`, choose recipe, and launch `coding-project coordinator`.

## Where to go next

The [tutorials](tutorials.html) walk through real runs with captured terminal output: your first ax session, then a coordinated multi-agent task end to end. The [manual](manual.html) covers the picker, control plane verbs, fences, remote hosts, safety, and configuration. The [recipes](recipes.html) are runnable shell examples. Copy one, edit the task text, and run it.

---

# 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. 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 launch, detach, monitor, reattach loop works with no tmux or zellij installed.

A multiplexer is optional. 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 give a session a short behavior prompt that names the commands, and it can coordinate the run.

ax builds on tools native to your system (tmux, ConPTY, SSH, local files) rather than a runtime of its own, so every backend piece is swappable. Extending it takes one `[[harness]]` block. 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 | Platform | Command or Location | Notes |
| --- | --- | --- | --- |
| Homebrew cask | macOS | `brew install --cask agentswitch-org/ax/ax` | Installs from the [agentswitch-org/homebrew-ax](https://github.com/agentswitch-org/homebrew-ax) tap. Recommended on macOS. |
| Debian / Ubuntu package | Linux (deb) | [Linux install commands](index.html#setup-linux) | The quickstart has a pasteable command that resolves the latest `.deb` asset for `amd64` or `arm64` and installs it with `apt`. |
| RPM package | Linux (rpm) | [Linux install commands](index.html#setup-linux) | The quickstart has a pasteable command that resolves the latest `.rpm` asset for `amd64` or `arm64` and installs it with `dnf`. |
| Binary release | macOS, Linux, Windows | [latest release](https://github.com/agentswitch-org/ax/releases/latest) | Download the archive for your platform and architecture (`.tar.gz` on macOS/Linux, `.zip` on Windows), extract `ax` or `ax.exe`, and place the executable on your `PATH`. |
| Go toolchain | macOS, Linux, Windows | `go install github.com/agentswitch-org/ax@latest` | Builds `ax` locally from the module source. Requires Go 1.26.4+. The executable lands in `$(go env GOPATH)/bin` or `$(go env GOBIN)`. That directory must be on your `PATH`. |
| Source checkout | macOS, Linux | `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. ax keeps working when these tools are missing. If you already use one, ax can use it.

| Program | Requirement | Use |
| --- | --- | --- |
| `tmux` | optional | Optional multiplexer backend on macOS and Linux. 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` | optional fallback | Legacy hold backend for hosts that explicitly set `hold_backend = "dtach"`. The default native holder is built into ax and needs no external binary. |
| `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` must be installed on that host too. Install `dtach` there only if that host explicitly uses the dtach hold backend.

## Multiplexer backends

By default ax runs on its own session holder with no multiplexer. 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 / `process` | a detached process ax holds | The zero-dependency path and the native Windows default. ax starts each session under its own holder so it survives the launching terminal and keeps running when you detach with Ctrl-a then d. On Windows the holder runs the harness under ConPTY and drives `ax send` / interrupts over a per-session named pipe; on Unix the process backend uses the native holder and process signals. You watch status and transcript preview in the picker, and reattach anytime with `Enter` or `ax attach`. There is no mux window to move or show in the WIN column. |
| `tmux` | a tmux window | Optional on Unix. 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. |
| `z` | Snap the selected column to its full content width so a truncated directory, title, or tag pile reads whole. Press `z` again to snap it back. |  |
| 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> [--yolo]` | Reattach a held session in the current window. Closing the window detaches it again. Inside a session: Ctrl-a then d detaches, Ctrl-a then a reopens the picker, Ctrl-a then y (or `--yolo`) stops the harness and resumes the same session with its permission-bypass flag added. |
| `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 archive`, `ax unarchive` | Hide or restore old rows from the default picker and list views without deleting transcripts or metadata. |
| `ax prune [--reap-workers]` | Archive safe local lifecycle clutter. `--reap-workers` also closes/kills already-concluded resident workers that pass the same reap gates as delayed cleanup. |
| `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 version` | Print this ax build's version, including the stamped commit and build date when release or deployment ldflags provided them. |
| `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 judgment 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 \
    --write './.coordinator/**/*.md' --no-subagents --max-depth 2 \
    --label role=coordinator --max-cost 100 --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` is the explicit opt-in to a screenless 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. A waited task still runs watched under the holder unless you also pass `--headless`, so it remains attachable while the caller waits. `--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; add `--active --from-now --exclude ID` to watch only live workers from this point forward and skip the coordinator's own progress turns. |
| `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. The id list can mix local ids and host-qualified ids such as `win01/<id>`. 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. Works for claude, pi, and codex. |
| `ax coordinate "GOAL"` | One-command coordinator bootstrap: launches the bundled coordinator behavior fenced to `.coordinator` state, keep-live, self-propelled, and attached. See [The coordinator](#coordinator). |
| `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, model, host-qualified ids, and the local node's ax/wire version report. |
| `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.

```
ax read --run "$AX_RUN" --follow --active --from-now --exclude "$AX_SESSION_ID"
```

That is the usual long-running coordinator stream: active workers only, no old turn replay, and no wakeups caused by the coordinator's own transcript. Use `--exclude-self` when `AX_SESSION_ID` is reliable in the caller's environment.

### Fences, accept check, ask / reply

Fences are hard limits ax enforces, similar to resource limits for a pod. 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 the coordinator behavior: it 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. The one-command bootstrap:

```
ax coordinate "Add dark mode to this app. Done when: the toggle works and 'npm test' passes."
```

The behavior ships inside the binary; on first use it is written to `~/.config/ax/behaviors/coordinator.md`, where your edits win on every later run. `ax coordinate` applies the reference guardrails (`--write './.coordinator/**/*.md' --no-subagents --fence best-effort --max-workers 2 --max-depth 2 --keep-live --self-propel --attach`); any launch flag you pass overrides them, and `--harness` / `--small` pick the harness and the small-model behavior variant. `--self-propel` is ax's outer loop: when the session ends a turn with its task unfinished, ax re-invokes it (pi/codex via their transcript's turn end, claude via its Stop hook's terminal marker) until the done criteria pass, it genuinely needs a human, or an idle cap trips.

A hand-rolled launch stays fully supported, e.g. to pin a model and budget (`--behavior` takes a behavior file path; `--behavior-text` takes inline text):

```
ax "Add dark mode to this app. Done when: the toggle works and 'npm test' passes." \
    --behavior ~/.config/ax/behaviors/coordinator.md \
    --write './.coordinator/**/*.md' --no-subagents --max-depth 2 \
    --label role=coordinator --model opus --max-cost 25 --max-workers 3
```

`--write './.coordinator/**/*.md'` is the fence. The coordinator can write only its own `.coordinator/*.md` state files and nothing else, and all mutating shell is denied, so every code change, config edit, and commit has to go through a worker it launches. That is what keeps the coordinator from editing the project directly.

`--no-subagents` turns off the in-process Task/Agent tools so every unit of work goes through `ax claude`, where it is a tracked session you can watch, attach to, and kill, with its own fence from its own launch. It is required because whether the write fence reaches a Task subagent depends on the Claude Code harness version, and on a build where it does not, a subagent would get an ungated shell. It is not a write sandbox: the write boundary is `--write`, and a fenced coordinator can still launch a writable worker by design. `--max-depth 2` lets a worker launch its own workers, one tier deeper than the default.

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. Install `dtach` only on hosts that explicitly use the dtach hold backend. 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`, `ax wait`, `ax result`, and the rest route the same way; ids are host-qualified, e.g. `win01/<id>`. `m` filters the list by machine and shows each host's status: online, offline, `no ax`, or `old ax`.

Remote compose is intentionally explicit. Choosing a remote target in the compose chooser aborts as unsupported rather than silently degrading to a plain launch. For remote task launches, run `ax <harness> "task" --host NAME`, run `ax new` on that host, or invoke the transport yourself.

```
# Native Windows host reached over OpenSSH + PowerShell
[[host]]
name      = "win01"
transport = "ssh -t win01"
shell     = "pwsh"
# headless = true  # use if remote interactive launch/attach is not verified
```

Use `shell = "pwsh"` when the remote sshd DefaultShell is PowerShell, so ax quotes forwarded arguments with PowerShell rules. `ax config status` reports each host's OS class, shell, ax version, wire version, and profile drift.

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.

### Sandboxing with nono

[nono](https://github.com/nolabs-ai/nono) is a zero-setup sandbox for AI agents: no daemon, no container, no VM. A sandboxed agent gets read/write access to its workspace and nothing else. Your SSH keys, your cloud credentials, and the rest of your disk are invisible to it, enforced by the kernel rather than by the harness's own permission prompts. ax integrates it as a launch axis:

```
ax claude "fix the flaky test" --sandbox
```

That wraps the harness in `nono run --profile nolabs-ai/claude -- …` using the registry profile matching the harness. To sandbox every task launch by default, set it once in `~/.config/ax/config.toml`:

```
[sandbox]
backend = "nono"          # wrap every `ax <harness> "TASK"` launch
# profile = "you/custom"  # optional: override the default profile

[[harness]]
name = "claude"
sandbox_profile = "nolabs-ai/claude"   # optional per-harness override
```

`--no-sandbox` opts a single launch out. An explicit `--sandbox` with no `nono` on `PATH` fails loudly; the config default degrades to an unsandboxed launch with a warning, so a box without nono keeps working. The sandbox wraps only the harness process: attach, detach, `ax send`, heartbeats, and holders behave identically. Custom profiles come from `nono profile init mine --extends nolabs-ai/claude`; macOS and Linux are supported natively, Windows via WSL2.

Sandboxing composes with the fences below: the write fence shapes what a compliant agent does, the sandbox bounds what any agent can do, and disposable boxes bound the blast radius when everything else fails.

### 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 has little to steal and little 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 screenless job form used only by an explicit `--headless` launch, e.g. `claude -p ...`. `--wait` and `--unattended` still run the watched launch unless `--headless` is also present. |
| `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 on a plain terminal and the native Windows process backend on Windows. 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 platform-specific: `sh -c` on Unix and PowerShell on Windows. 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, `raw_argv` for transports that pass argv verbatim (`kubectl exec ... --`, `docker exec`), `shell = "pwsh"` for Windows OpenSSH hosts whose DefaultShell is PowerShell, and `headless = true` for hosts where remote interactive launch/attach is not verified. |
| `[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

### Runnable recipes

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).

---

# AgentSwitch: Tutorials

## Your first ax session

### Setup

Install `ax` (see [Installation](manual.html#installation)). There is no configuration step: Claude Code, Codex, pi, and opencode are built in, and ax reads the transcript stores those harnesses already write.

No mux is the default. Out of the box, ax's own session holder keeps your harnesses alive when you detach (Ctrl-a then d) and lists them in the picker, so the full launch, detach, monitor, reattach loop works on a plain terminal. Native Windows uses the built-in process backend. A multiplexer is optional on systems where you run one: tmux or zellij let ax open each session as a real window or tab. zoxide and ripgrep are picked up on their own too; dtach is only a fallback hold backend if you opt into it. The manual's [runtime requirements](manual.html#installation) list what each one adds. None is required.

### See what you already have

ax indexes the sessions that exist on your machine right now. Before opening any UI, list them:

![ax list output: four sessions across claude and codex, one live and working in ~/src/webshop, with model, age, context, cost, directory, and title columns](images/t1-list.png)

Four sessions across two harnesses. One is live and working: a `claude` run in `~/src/webshop` that was launched through ax. The others are past conversations you can resume.

### Open the picker

`ax` opens the picker: the same table, plus a live preview of the selected session's transcript.

![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)

Move with `j`/`k`. The preview follows the cursor. `J`/`K` scroll the preview itself. The `STATUS` column merges liveness and attention into one cell. The manual's [status table](manual.html#rendered-picker) lists every value.

### Find a session

There are two search prompts with different scopes. `i` filters rows on their metadata (harness, directory, title). `/` searches inside the transcripts themselves:

![the picker in search mode: the query 'rounding' typed at the search prompt has narrowed the list to the one session whose transcript mentions it, with the matching lines shown in the preview](images/t1-search.png)

Typing `rounding` narrowed the list to the one session whose conversation mentions it, with the match shown in the preview. `n`/`N` jump between matches. `Esc` backs out, `Enter` keeps the narrowed list so you can act on it.

### Resume, or start fresh

`Enter` on a row resumes that session with the harness's own resume command, in the session's directory. If it is already open somewhere, ax jumps to it instead of starting a duplicate. `x` kills a session.

`c` (or `ax new`) starts a fresh session: pick a harness, pick a directory, and it launches. From the shell, `ax new claude` skips the picker entirely.

The list is not limited to this machine or these harnesses: a `[[harness]]` entry can point at a local LLM's CLI or a frontier-model harness, and [remote hosts](manual.html#remote-hosts) merge their sessions into the same picker. The next tutorial drives sessions from the shell instead.

## Run a coordinated task

### Launch

A coordinated run is one command. The shorthand is `ax coordinate "GOAL"`, which launches the bundled coordinator behavior with the reference guardrails and self-propel; the long form below spells out what it does. The task states the goal and the done-criteria. The flags cap cost, tokens, workers, depth, and wall-clock time:

![launching a coordinated run: ax claude with a dark-mode task, coordinator behavior, max-cost 25 and max-workers 3, and ax printing the new session id and the group id b3e7a9c2](images/t2-launch.png)

ax prints the new session id and the run's group id and returns. The root session (the coordinator) now splits the work and launches sessions through ax. Each launch inherits the group, so the run tree assembles automatically. `--max-cost 25` and `--max-workers 3` are enforced by ax, not by the model: a launch beyond the session cap is refused, and a cost overrun cascade-kills the run.

The coordinator is an ordinary session handed a behavior prompt that names the ax verbs. Every action it takes from here (launch, read, send, tag, ask) is a shell command you could type yourself.

### Watch the run

The group id addresses the whole run from the shell:

![ax list --group b3e7a9c2: the coordinator and three named sessions (toggle-ui, theme-css, tests) with their status, model, cost, and task, where theme-css shows needs you](images/t2-list.png)

In the picker, `f` filters to the group and `T` renders the parent tree, like a process viewer:

![the picker in run-tree mode filtered to group b3e7a9c2: the coordinator at the root with three indented sessions, theme-css marked red 'needs you', and its pending question shown in the preview pane](images/t2-tree.png)

One session is red: `theme-css` hit a decision it would not make alone, called `ax ask`, and blocked. The question appears in the preview.

### Answer it

Press `r` on the row and type the answer, or from any shell:

![ax reply with the session id and the answer text 'the high-contrast near-black'](images/t2-reply.png)

The blocked `ax ask` call inside the session returns your text on its stdout and the session continues. The reply is free-form: a choice, a correction, or the next instruction. This was the run's only human intervention, and it happened because the behavior chose to ask.

You can also read any session's conversation without attaching to it, as normalized turns from the transcript:

![ax read of the tests session with --limit 2: a JSON object with the session id, two normalized turns with role, timestamp, text, and token counts, and a cursor](images/t2-read.png)

`ax read --run b3e7a9c2 --follow` gives you the same thing as a stream. It blocks and emits one JSON line per turn boundary across the whole run. A coordinator runs on this same stream.

### Review and accept

When the criteria pass, the run does not stop silently. The root tags `--outcome success`, presents a one-line result via `ax ask`, and waits. In the picker that is the green `✓ review` state, ranked with the attention states but distinct from red `needs you`: the run is presenting its result, not blocked.

![the picker after the run concluded: the coordinator row shows a green '✓ review' status, and the preview shows outcome success with the question 'result ready: the toggle works, choice persists, npm test passes 42/42. accept?'](images/t2-review.png)

Reply to accept, or reply with updated criteria and the run continues. With `--accept ./check.sh` on the launch, the success tag additionally has to pass your script, so a run cannot conclude on an unverified claim.

Concluded runs are recorded and can be queried:

![ax runs output: one concluded run record for group b3e7a9c2 with outcome success, cost $18.42, three sessions, depth 1, and the task text](images/t2-runs.png)

### Where to go next

The manual's [Control plane](manual.html#control-plane) chapter covers the full verb set, the fences, and the accept check. The [recipes page](recipes.html) has runnable examples, including running an accept check in CI with `--wait --unattended`, supervising an already-running session toward a goal, and getting notified when a run needs you.

---

# AgentSwitch: Recipes

## Recipes

Every command below runs as written. Edit the quoted task text to match your job. A launch uses the harness's own default model. Add `--model sonnet` (or `opus`, `gpt-5.5`, ...) to pick a different one. The manual's [Control plane](manual.html#control-plane) chapter documents the flags and states these recipes use.

These recipes are a starting point, not a fixed set. Each is shell plus a behavior file. Write your own, share them, and build on other people's.

There are two ways to run a workflow. Run a recipe from this page directly: copy it, edit the task text, and run the launch command. This suits a scheduled job or a known one-shot. Or give the coordinator behavior a goal. The coordinator behavior (`behaviors/coordinator.md`) can run every recipe here, so you can give it a goal instead of copying and editing recipe snippets by hand. The quickstart's [Two ways to run work](index.html#two-ways) section covers both paths. Coordinator recipes below use `--behavior ~/.config/ax/coordinator.md` with the write fence `--write './.coordinator/**/*.md'` and `--no-subagents`. The fence lets the coordinator write only its own `.coordinator/*.md` state, so every real change goes through a worker, and `--no-subagents` keeps all work on tracked `ax` sessions.

These recipes use the short `ax "prompt"` form. Set a default harness once so a bare prompt launches it, then nothing else needs configuring:

```
# ~/.config/ax/config.toml
default_harness = "claude"
```

A known verb or harness name shadows a bare prompt, so `ax read`, `ax runs`, and `ax codex "prompt"` keep their usual meaning. Write `ax <harness> "prompt"` anytime you want a specific harness instead of the default.

### Run a task

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

This runs in your current directory. ax tracks it in a background window that closes when the task finishes. The command prints the session id. Watch it with `ax read <id> --follow`.

Use this for a task that needs no supervision. It runs to completion on its own, stays visible in the picker while it works, and its transcript is indexed like any other session afterward.

### Run an accept check on a task (CI)

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

This blocks and exits 0 only if the accept command passes when the run concludes.

Use this in CI. `--wait` turns the run into an ordinary blocking command with a meaningful exit code. `--unattended` makes `ax ask` return a default instead of waiting on a human, so nothing deadlocks in CI. `--accept` means success requires the check to pass instead of trusting the model's completion claim. `--timeout` caps the wall-clock time. A session sitting idle never counts as done.

### Organize sessions with tags

In the picker: `i` to filter, `Enter` to keep the filter, `v` + `j`/`k` to select rows, `l` to edit their labels, `b` to pivot the list by any tag key. From the shell:

```
ax tag <id> --add-label project=blog
ax move --tag project=blog
```

The second command moves every tagged window into its own tmux session. Tags are `key=value` labels stored in ax's sidecar metadata, never in the harness's transcript, so they are safe to add, change, and remove. A `TAGS` column appears in the picker once anything is tagged.

### Watch a run from a shell

```
ax list --run myrun
ax read --run myrun --follow
ax runs
```

Snapshot, live event stream (one JSON line per turn/waiting/exit/crash), and concluded runs with outcomes.

These three commands are the same view a coordinator uses, so anything it can see, you can see from any terminal. `list` shows who is alive and what each session costs. `read --follow` shows every turn boundary as it happens. `runs` shows what past runs concluded.

### Get notified when a run needs you

Set `notify` in `~/.config/ax/config.toml` to get a message when a run blocks on you, or finishes and wants review, without having to watch the picker:

```
notify = "tmux"     # status-line message on every attached client
# notify = "bell"   # rings the session's tmux window
```

For anything richer, point it at a command. Write each placeholder bare, with no quotes of your own around it, because ax quotes every value into a single shell word. `{state}` is either `needs-you` or `done-review`, so one command can handle both cases:

```
notify = "notify-send ax {state}:{summary}"       # desktop notification
# notify = "curl -s --data-urlencode text={state}:{summary} https://hooks.slack.com/..."
```

The notify command fires from `ax ask`, and from claude's own permission prompts once you run `ax hook install claude`. It does not fire on a crash. Catching a crash would need a daemon, and ax does not run one.

### Coordinate a goal with multiple agents

```
ax "Add dark mode to this app. Done when: the toggle works and 'npm test' passes." --behavior ~/.config/ax/coordinator.md --write './.coordinator/**/*.md' --no-subagents --max-depth 2 --label role=coordinator --model opus --max-cost 25 --max-workers 3
```

`--behavior ~/.config/ax/coordinator.md` points at the coordinator behavior file. The behavior lives at `behaviors/coordinator.md` in the repository. The [quick start](index.html#quick-start) covers getting the checkout and placing the file.

`--write './.coordinator/**/*.md'` is the fence that forces delegation. The coordinator can write only its own `.coordinator/*.md` state files, so every real change goes through a worker it launches. `--no-subagents` turns off the in-process Task/Agent tools so all work runs as tracked `ax` sessions instead of subagents you cannot watch or steer. It is required because whether the fence reaches a Task subagent depends on the harness version, so the coordinator does not rely on it. It is not a write sandbox: the write boundary is `--write`. `--max-depth 2` lets a worker launch its own workers, one tier down.

One session drives the run. It splits the work, launches other sessions, judges their results, and concludes when the criteria pass. The fences cap cost and fan-out. Watch the run in the picker (`f` filters to the run, `T` shows the run tree). Answer its questions with `r` when a row shows "needs you".

State the done-criteria in the task, and make them concrete enough to check. The coordinator behavior concludes on evidence. "Done when: 'npm test' passes" gives it a command to run. "Make it nice" gives it nothing to check. The [coordinated task tutorial](tutorials.html#coordinated-task) walks through one of these runs end to end.

### Run a project with a coordinator

The previous recipe concludes once the goal is met. To run an ongoing project instead, launch a coordinator that keeps running. The built-in form:

```
ax coordinate "Manage the blog project: triage requests into .coordinator/backlog.md, delegate work, verify, report."
```

`ax coordinate` uses the coordinator behavior bundled in the binary (written to `~/.config/ax/behaviors/coordinator.md` on first use; your edits win), fences it to `.coordinator` state, keeps it live, and self-propels it so an ended turn with open work re-invokes it instead of stalling. The equivalent hand-rolled launch, for full control over every flag:

```
ax "Manage the blog project: triage requests into .coordinator/backlog.md, delegate work, verify, report." --behavior ~/.config/ax/behaviors/coordinator.md --write './.coordinator/**/*.md' --no-subagents --max-depth 2 --label role=coordinator --keep-live --self-propel --interactive
```

`--interactive` keeps the coordinator running so you can steer it. The `--write` fence and `--no-subagents` are the same controls as the previous recipe: the coordinator writes only its own `.coordinator/*.md` files, and all work runs as tracked `ax` sessions. Leave `--max-cost` and `--max-workers` off for an open-ended project: a tripped fence cascade-kills every session in the run. Add them only when you want that hard stop.

There is no project feature to turn on. The coordinator keeps a plain `.coordinator/backlog.md` that it reads and rewrites. Each item carries a state and an owner, and the file also holds the decisions waiting on you and the coordinator's own follow-ups.

```
# .coordinator/backlog.md: blog

## In flight
- [~] add RSS feed  (owner: worker/rss)  -- generating feed.xml, verifying links

## Ready
- [ ] dark-mode toggle  (owner: unassigned)  -- done when the toggle persists across reloads

## Decisions for you
- [?] drop IE11 support? blocks the CSS cleanup item

## Done
- [x] fix broken image paths  (verified: all <img> resolve, site build is green)
```

The coordinator delegates each Ready item to a new session (`ax "task"`). The child session inherits the run and labels, so the whole run stays one tree in the picker. Before moving a row to Done, the coordinator verifies the result against something real: the built site, the passing test, the file on disk. It does not mark work done on trust alone.

Send the coordinator a message and it sorts each item into the backlog. A new request becomes a Ready item. A question becomes a Decisions row. An answer unblocks whatever was waiting on it.

```
ax send <id> "ship the dark-mode toggle next, and yes, drop IE11"
```

When several sessions edit files at the same time, give each one its own git worktree so their edits cannot clobber each other. This is a plain git convention, not an ax feature. Pass each session its worktree path in the task text. Tell the coordinator to integrate a session's work only after that session's verification passes.

```
git worktree add ../worker-rss feature/rss
git worktree add ../worker-darkmode feature/darkmode
```

Watch the run in the picker (`f` filters to the run, `T` shows the run tree). Answer with `r` when a row shows "needs you". The backlog file is the one place the project's state lives, so you or any session can see where things stand by reading a single file.

### Supervise an existing session toward a goal

Use this for a session whose context you do not want to restart, driven by a model that works in bursts and stops early. Copy the session's id from the picker preview into the first line, then paste the rest as is:

```
W=the-session-id
ax tag "$W" --run myrun --name worker
ax "Supervise worker $W, an already-running session. Do not restart it. Goal: finish its current task. Done when: it demonstrates the working result. It stalls after bursts. Keep it moving." --behavior ~/.config/ax/coordinator.md --write './.coordinator/**/*.md' --no-subagents --max-depth 2 --label role=coordinator --model sonnet --run myrun --name supervisor --interactive
```

The `ax tag` line adopts the existing session into a named run. The launch then starts a supervisor in that same run, kept running and steerable by `--interactive`.

The supervisor does not do the work itself. The `--write` fence enforces that: it can write only its own `.coordinator/*.md` notes, so it cannot touch the worker's files, and `--no-subagents` keeps every action on the tracked worker session. It watches the session's turns, sends the next concrete instruction whenever the session stalls, verifies "done" against evidence, and asks you only when the decision is yours. Leave `--max-cost` off here. If a fence trips, ax cascade-kills the whole run, including the supervised session.

### Update criteria mid-run

The accept check (`--accept`) usually points at a script. Editing that script is how you update the criteria, because the next success claim runs the new version. After editing it, tell the root session:

```
ax send <root-id> "the acceptance criteria changed: re-read ./check.sh and continue until it passes"
```

If the run already concluded, relaunch with the same run id. The new run picks up the same history:

```
ax "Continue the previous run. Read your state file first. Updated criteria: ..." --behavior ~/.config/ax/coordinator.md --write './.coordinator/**/*.md' --no-subagents --max-depth 2 --label role=coordinator --run myrun
```

The same reply channel works at review time. When a finished run shows the green `✓ review` state, answer its question with new criteria instead of accepting, and it goes back to work.

### Route a task up through model tiers

Run a task on the cheapest model first. If the accept check fails, move to a stronger tier. Stop as soon as one tier passes.

```
for MODEL in haiku sonnet opus; do
    ax "$TASK" --model "$MODEL" --max-tokens 100000 \
        --wait --accept ./check.sh && exit 0
done
exit 1
```

Write `check.sh` to verify the concrete deliverable, not to trust the model's claim. It runs automatically when the session tags itself done. Exit 0 means the tier passed. Any other exit means the run gave up, the fence tripped, or the check rejected the result. The loop moves to the next tier on any non-zero exit.

Fence each tier on `--max-tokens` to prevent runaway spend. On subscription auth (the default), `--max-cost` is inert. On API auth (`--api`), use `--max-cost` in USD instead. After any `--wait` run, the record at `~/.local/state/ax/runs/<run-id>.json` carries cost, token counts, and the outcome field (`success`, `gave_up`, `budget_hit`, `crashed`):

```
for f in ~/.local/state/ax/runs/*.json; do
    jq -r '[.group, .outcome, .cost] | @tsv' "$f"
done | column -t
```

Starting from zero: copy the directory `recipes/cost-routing/`. It ships the escalation wrapper (`escalate.sh`, with the tier list inline) and a sample accept check (`check.sh`). Replace the check with your task's real acceptance criteria and run `./escalate.sh "your task"` (it defaults to `./check.sh`). No prior sessions or data required.

### Share state between agents with a blackboard

Two agents coordinate through a shared JSON file on disk. A producer writes its conclusions to the file. A critic reads them, evaluates them, and appends its verdicts. Neither agent sees the other's context window, only what was written.

```
BB=/tmp/blackboard.json
echo '{"items":[],"verdicts":[]}' > "$BB"

PROD=$(ax \
    "Blackboard: $BB. Read it. Append one item to 'items'. Write it back." \
    --wait --json | jq -r .id)
ax wait "$PROD" --timeout 5m
ax result "$PROD"

CRIT=$(ax \
    "Blackboard: $BB. Read it. For each item, append a verdict to 'verdicts'. Write it back." \
    --wait --json | jq -r .id)
ax wait "$CRIT" --timeout 5m
ax result "$CRIT"

cat "$BB" | jq .
```

The primitives: `--json` on the launch prints the session id before `--wait` blocks, so you can capture both. `ax wait` is a clean block point between pipeline stages. `ax result` reads the final report after the session exits. The blackboard is append-only by convention: each agent writes only to its own array key and never modifies what another agent wrote, so the file doubles as a tamper-evident audit log you can inspect at any point with `cat "$BB" | jq .`

The same pattern extends to N agents and M rounds. Add a judge worker that reads both `items` and `verdicts` and writes a `judgment` array. Run the critic loop twice for a second-pass refinement. Fan out N critics in parallel (each with a distinct key) and join on all keys being populated. The blackboard is the queue.

Starting from zero: create the blackboard file, write a task prompt that tells each agent its file path and its section to write to, and run the two-stage sequence above. No prior sessions or shared infrastructure required.

### Fan out workers over a file collection

Split a corpus into chunks, launch one worker per chunk in parallel, and join on a single verify pass. Each worker reads its chunk and writes structured notes to a shared vault directory.

```
VAULT=./vault
RUN_ID="vault-$(date +%s)"
mkdir -p "$VAULT"

PIDS=()

for CHUNK in fixture/chunks/chunk-*.txt; do
    SLUG="${CHUNK##*/chunk-}"
    SLUG="${SLUG%.txt}"
    ax "Read $CHUNK. Extract key topics, commitments, and dates. Write notes to $VAULT/notes-${SLUG}.md." \
        --run "$RUN_ID" --label "role=worker" --label "correspondent=$SLUG" \
        --max-workers 5 --max-tokens 500000 --timeout 10m \
        --wait --json > "/tmp/launch-${SLUG}.json" &
    PIDS+=($!)
done

wait "${PIDS[@]}"

ax "Check each file in $VAULT/notes-*.md for required sections.
    Report PASS or FAIL per file. Exit 0 only if all pass." \
    --run "$RUN_ID" --label "role=verifier" \
    --wait --accept ./check-vault.sh
```

Each worker is launched with `&` before any is awaited, so all chunks run in parallel. `wait "${PIDS[@]}"` is the join point. The `--run` flag ties all sessions into one tree in the picker (`T` to expand it). The verify worker reads every notes file and the `--accept` check enforces a hard pass before the run can conclude.

For large corpora, add `--model haiku` to the extract workers (mechanical note-taking) and keep the default model for the verify worker. This composes directly with the cost-routing pattern above.

Starting from zero: write a chunking script that splits your source files into per-item text files, define the fields each notes file must contain, write a `check-vault.sh` that verifies those fields are present, and run the fan-out. No prior sessions or vault contents required.

### Audit session history for behavior patterns

Search your session history for a topic, pull transcripts, and run a headless analysis worker that produces a human-reviewable document with recurring patterns, failure modes, and proposed behavior file edits.

```
QUERY="coordinator behavior"
CORPUS=$(mktemp /tmp/audit-corpus.XXXXXX)
PROMPT=$(mktemp /tmp/audit-prompt.XXXXXX)

ax search "$QUERY" --json \
    | jq -r '.ids[:8][]' \
    | while read -r id; do
        echo "--- session: $id ---" >> "$CORPUS"
        ax read "$id" --format text | tail -80 >> "$CORPUS"
    done

cat > "$PROMPT" <<PROMPT_EOF
You are a behavior-audit worker. Corpus:
$(cat "$CORPUS")

Write: recurring successful patterns (with session citations), recurring
failure modes (with citations), curated lessons, and proposed behavior file
edits as [ADD to section X] / [REVISE Y to] blocks. Mark single-instance
observations as 'watch' not 'pattern'.
PROMPT_EOF

ID=$(ax claude - --wait --unattended --max-tokens 400000 --timeout 15m --json \
    < "$PROMPT" | jq -r .id)
ax result "$ID" > audit-output.md
```

The primitives: `ax search --json` returns a ranked list of session IDs matching the query. `ax read --format text` extracts the normalized transcript. `ax claude -` reads the task from stdin, so the whole corpus rides inside the prompt file. With `--wait` the launch blocks until the worker finishes and `ax result` reads its report. The output goes to a file you review and apply manually.

This is not persistent learning. The worker re-derives the summary from transcripts at call time. The behavior file is version-controlled. A human decides what edits to apply. Run it again after a batch of sessions to see what changed.

The default depth fence (`--max-depth 1`) allows a root session plus one level of workers, so this launch works from a plain shell and from inside a root session. It is refused only when the script runs inside a session that is already a worker (depth 1), because the analysis worker would land at depth 2. In that case, run it from a shell outside any ax session, or launch the root with `--max-depth 2`.

Starting from zero: the script writes a blank template if `ax search` returns no sessions. Run it again once you have sessions that match your query.

### Triage email and notify on exceptions

A cron job reads new mail, dispatches archive and unsubscribe actions automatically, and fires a notification only when something requires human attention. By default it stays quiet.

```
# ~/.config/ax/config.toml
notify = "notify-send ax {state}:{summary}"
```

```
# cron: every 4 hours
0 */4 * * *  /path/to/triage-wrapper.sh ~/Maildir >> ~/.local/state/ax/log/email-triage.log 2>&1
```

Inside the wrapper:

```
TASK="Process these emails. For each: emit ACTION|||ARCHIVE|||filename,
    ACTION|||UNSUBSCRIBE|||filename, or ACTION|||DRAFT_REPLY|||filename|||text.
    If all were noise, emit SILENT on its own line and stop.
    If any need human attention, emit IMPORTANT: summary."

SESSION=$(ax "$TASK" --behavior behaviors/email-triage.md --model haiku \
    --wait --unattended --timeout 3m --max-cost 0.25 --max-tokens 200000 --json | jq -r .id)
OUTPUT=$(ax result "$SESSION")

# dispatch action lines; ||| separator preserves maildir filenames containing ":"
while IFS= read -r line; do
    if [[ "$line" == "ACTION|||ARCHIVE|||"* ]]; then
        archive "${line#ACTION|||ARCHIVE|||}"
    elif [[ "$line" == "ACTION|||UNSUBSCRIBE|||"* ]]; then
        unsubscribe "${line#ACTION|||UNSUBSCRIBE|||}"
    elif [[ "$line" == "ACTION|||DRAFT_REPLY|||"*"|||"* ]]; then
        rest="${line#ACTION|||DRAFT_REPLY|||}"
        draft_reply "${rest%%|||*}" "${rest#*|||}"
    fi
done <<< "$OUTPUT"

# sentinel gate: silence is the normal case
echo "$OUTPUT" | grep -q '^SILENT$' && exit 0

# something needs attention: notify
"$NOTIFY_CMD" "$(echo "$OUTPUT" | grep '^IMPORTANT:' | head -1)"
```

The sentinel gate inverts the usual pattern. Most recipes notify on completion. This one notifies only when `SILENT` is absent. The worker decides what each email is and what action to take. The wrapper owns the notification decision with a single `grep`.

`--unattended` means `ax ask` returns a default instead of waiting on a human, so nothing deadlocks in cron. `--timeout 3m` caps the wall-clock time so a hung session never blocks the cron slot. On the default subscription auth `--max-cost` is inert, so `--max-tokens 200000` is the fence that binds.

Starting from zero: create a maildir with a few test messages (mix of newsletters and one real item), write a behavior file that tells the worker how to classify mail and what format to emit, run the wrapper once manually, and verify the sentinel fires correctly before wiring cron.

### Draft listings and wait for human approval before publishing

A read-only coordinator fans out one worker per niche in parallel. `--read-only` is the zero-scope fence: the coordinator itself writes nothing, so every file is written by a worker, and `--no-subagents` keeps all that work on tracked `ax` sessions. Each worker produces a design brief, generates artwork via an image API adapter, writes listing copy, and creates a draft product via your platform's API. An accept check verifies every draft mechanically, and `ax ask` holds the pipeline for explicit human approval before anything publishes.

```
ax "Read niches.json. For each niche, launch a worker that:
    1. Writes a design brief to drops/NICHE/brief.md
    2. Calls scripts/image-api.sh to generate drops/NICHE/design.png
    3. Calls scripts/listing-copy.sh to write drops/NICHE/listing.json
       (title <= 140 chars, exactly 13 tags)
    4. Calls scripts/create-draft.sh to write drops/NICHE/draft.json
       (state=draft, published=false)
    Run all workers in parallel. Done when all niches have passing drafts." \
    --behavior behaviors/pod.md \
    --read-only --no-subagents \
    --max-workers 5 --max-tokens 2000000 \
    --accept "bash scripts/check-drop.sh drops" \
    --interactive
```

The accept check runs before the coordinator can declare success. It verifies that every niche has a design file, a listing with a title under the character limit and the right tag count, and a draft marked as unpublished. No niche passes on the coordinator's word alone.

After the checks pass, the coordinator calls `ax ask` before the publish step:

```
ans=$(ax ask "3 drafts ready. Approve to publish?")
[ "$ans" = "yes" ] || exit 0    # stop at drafts if not approved
```

In attended mode, `ax ask` shows the session as "needs you" in the picker and fires the notify hook. Answer with `r` in the picker or `ax reply <id> yes` from a shell. In unattended mode (`--unattended`), `ax ask` returns immediately with no reply and the coordinator stops at drafts. Nothing publishes unattended. This is the structural guarantee for any cron or CI run.

Starting from zero: create a `niches.json` with two or three entries (each with a slug and keywords), write stub adapter scripts for each platform API call, run the coordinator once with `--unattended`, and verify that drafts are produced but nothing publishes.

### Collect data in a script, format it with an agent

A bash script collects ground-truth data (service health, dependency audit, metrics). Its output is injected into an ax task. A headless formatter session produces a human-readable report or emits `SILENT` if there is nothing to report. The agent does not fetch data itself.

```
# Stage 1: collect data deterministically
SCRIPT_OUTPUT=$(./check-services.sh)

# Stage 2: inject into task, let the agent format only
ID=$(ax "A health check ran. Its output:

== OUTPUT START ==
${SCRIPT_OUTPUT}
== OUTPUT END ==

If the output contains NO_ISSUES: respond with exactly the word SILENT and stop.
If it contains OUTAGE_DETECTED: write a concise incident summary and stop.
Do not run any commands. Do not fetch additional data." \
    --behavior recipes/prescript-formatter/behaviors/lm-formatter.md \
    --wait --unattended --timeout 5m --max-tokens 200000 \
    --json 2>/dev/null | head -1 | jq -r .id)

# Stage 3: sentinel gate
OUTPUT=$(ax result "$ID" --json | jq -r .result)
echo "$OUTPUT" | grep -q '^SILENT$' && exit 0

# Stage 4: deliver
"$NOTIFY_CMD" "$OUTPUT"
```

The behavior file constrains the formatter: read the task, format only, no tool calls. This matters because a curl call inside an agent session is billed and subject to permission prompts. A curl call in bash is free and deterministic. The script handles data collection. The agent handles formatting.

The `--json | head -1 | jq -r .id` pattern captures the session ID from the JSON line printed before `--wait` blocks. After `--wait` returns, `ax result --json` reads the formatter's output cleanly via the `.result` field.

Adapt to any monitoring job by replacing `check-services.sh` with your real check tool and adjusting the sentinel tokens (`NO_ISSUES` / `OUTAGE_DETECTED`) to match what your script emits.

Starting from zero: write a check script that outputs a fixed sentinel word for the clean case, run the wrapper once in each fixture mode, and confirm the sentinel gate suppresses delivery in the clean case before wiring cron.

### Schedule any recurring workflow

One wrapper, one behavior file, any task string on any schedule. A cron entry calls the wrapper, which runs the task headless, reads the output, and delivers a report only when the output does not contain the suppression sentinel.

```
# ax-scheduled-chain.sh: parametrized cron wrapper
TASK="$1"
NOTIFY_CMD="${NOTIFY_CMD:-echo}"
SENTINEL="${SENTINEL:-SILENT}"
RUN_ID="chain-$(date +%Y%m%d-%H%M%S)"

SESSION=$(ax "$TASK" \
    --behavior recipes/scheduled-chain.md \
    --wait --unattended --timeout 10m --max-cost 2.00 --max-tokens 400000 \
    --run "$RUN_ID" --json | jq -r .id)

OUTPUT=$(ax result "$SESSION")

printf '%s\n' "$OUTPUT" | grep -q "^${SENTINEL}$" \
    && exit 0

"$NOTIFY_CMD" "$OUTPUT"
```

```
# crontab: daily briefing at 07:00
0 7 * * *  NOTIFY_CMD=/usr/local/bin/ax-slack \
    /path/to/ax-scheduled-chain.sh \
    "Search the web for the top 5 AI agent developments from yesterday.
     Summarize each in 2-3 sentences." \
    >> ~/.local/state/ax/log/briefing.log 2>&1
```

Only the task string changes between workflows. The same wrapper handles a daily briefing, a nightly dependency audit, a weekly digest, or a competitive repo watch. Wire the sentinel: the worker emits `SILENT` when there is nothing to report (no new vulnerabilities, empty inbox, no commits), and the wrapper suppresses delivery.

For the dependency audit variant, run the deterministic scanner in bash first (the pre-script pattern above), then inject its output into the task string so the agent only interprets, not fetches:

```
VULN=$(govulncheck ./... 2>&1)

ax-scheduled-chain.sh \
    "A Go vulnerability scan ran. Output:

$VULN

If it says 'No vulnerabilities found', output SILENT. Otherwise summarize each
finding: ID, affected module, version found, fixed version, upgrade action." \
    >> ~/.local/state/ax/log/audit.log 2>&1
```

ax has no built-in scheduler. Wire it with cron, systemd.timer, or launchd. The notify hook in `~/.config/ax/config.toml` is an alternative to the `NOTIFY_CMD` variable for delivery targets you already have configured.

Starting from zero: copy the wrapper, set the task string, run it manually once with `NOTIFY_CMD=echo`, confirm the sentinel fires on a clean run, then add the cron entry.

### Run a produce-judge-iterate loop against a rubric

A producer writes or revises a deliverable. A fresh reviewer scores it against a rubric file that encodes your taste. A passing score stops the loop and emits the deliverable. A failing score folds the reviewer's specific violations into the next producer iteration. If the iteration cap is hit while still failing, the loop emits the best attempt marked NOT PASSING. No human is involved until the loop concludes.

```
# Improve an existing draft (seed mode): score the draft on iteration 1,
# then revise it against the violations on each subsequent iteration.
bash taste-loop.sh \
    --task-file goal.md \
    --seed draft.md \
    --rubric rubrics/prose-flat-register.md \
    --threshold 85 \
    --max-iter 5
```

```
# Produce from scratch: the producer writes the first draft,
# then iterates against the reviewer's violations.
bash taste-loop.sh \
    --task "Write the README intro in flat developer-doc register." \
    --rubric rubrics/prose-flat-register.md \
    --threshold 85 \
    --max-iter 4
```

Exit 0 means the deliverable passed. Exit 2 means the cap was hit without passing and the output carries the best attempt plus its remaining violations. `evidence/trajectory.tsv` records the score at each iteration so you can see whether the loop is converging.

The loop ships two behavior files. `behaviors/producer.md` constrains the producer to emit only the deliverable text. No preamble, no explanation. `behaviors/reviewer.md` constrains the reviewer to return one JSON object with a score, a violations list with exact quoted spans, and a pass flag. Both workers run `--read-only`. The reviewer is launched fresh each iteration with no memory of prior rounds.

The rubric is where the taste lives: copy `rubrics/TEMPLATE.md` to write your own. The flagship rubric (`rubrics/prose-flat-register.md`) penalizes rhetorical openers, marketing verbs, em-dashes, semicolons, and rule-of-three padding.

The coordinator behavior uses this recipe autonomously. Given a goal that requires prose at a specific register, a coordinator session runs `taste-loop.sh` and passes the reviewer `behaviors/reviewer.md` as its behavior file. The human sees only the passing result or the cap report, not the intermediate drafts.

Starting from zero: copy `recipes/taste-gate/` from the repository, write a rubric by editing `rubrics/TEMPLATE.md`, and run the loop against a draft or a task prompt. Add `--model sonnet` (or any model name) to set the model for both workers. Bind each worker's spend with `--max-tokens N`.

---

# AgentSwitch: FAQ

## Questions

### Why should I use AgentSwitch?

ax is for people who run more than one CLI coding agent, run tasks long enough to lose track of, or repeat the same agent workflow. It gives you one place to launch, watch, detach, reattach, and coordinate agent sessions across projects, and its [shell verbs](manual.html#verbs) let you compose automations with ordinary shell tools. If you run a single agent interactively and never lose track of it, you may not need ax.

### Can I run all my agents through ax?

Yes. Bootstrap ax once by pasting [a prompt](index.html#hero) into an agent you already run. After that, ax is the place your agent work flows through. It coordinates every harness the same way, so one coordinator you talk to can launch, watch, steer, and verify sessions across all of your projects, and every session stays in one searchable picker. You do not need a separate agent view per project.

### How is ax different from an agent harness?

Think of ax as the anti-harness. A harness (Claude Code, Codex, pi, opencode) susually contains the machinery do drive the llm and may be driven by external programs that integrate with MCPs, servers, GUIs, skills, etc. It holds the model, the tools, and the context, and it does the work. ax does none of that. ax is the control plane around harnesses: it launches them, keeps them alive when you detach, lists and searches every session, and composes and coordinates many at once. It reads the transcripts harnesses already write. It is mechanism around the harness, not a replacement for it.

### Why use ax instead of built-in subagents?

You can, and for a quick one-off they are simpler. But that orchestration lives inside the harness and is opaque. You cannot see how a subagent was spawned, watch it, steer it, read its transcript, or change the coordination logic.

ax moves the control structure out of the harness into a separate program. Any harness drives it the same way, every session is a transcript you can read, attach to, and resume, and the [recipes](recipes.html) and the [coordinator behavior](manual.html#coordinator) are plain files you edit. Harness makers keep folding orchestration into one tool. ax keeps that layer separate, visible, and yours, so the harness stays a good agent and the control plane stays something you own.

### Why does ax not have an MCP server?

For the same reason it also doesn't have the concept of `SKILLS.md` or `AGENTS.md`, ax is already a CLI, and coding agents already run CLIs through their shell. An MCP server would wrap the same verbs in a protocol layer with little benefit for coding agents. MCP tool schemas sit in the model context on every turn, thousands of tokens whether the tools are used or not, while `ax help` is about a thousand tokens read once on demand. CLI output pipes and chains through the shell, while MCP results have to round-trip through the model. And the shell is the one boundary ax already fences, so a second protocol would mean a second permission model.

The one case MCP would help is driving ax from a client with no shell at all, which no coding agent needs today. If that changes, an MCP server is easy to add, because the verbs are already the interface.

### Is ax free or a subscription?

ax is free and open source under the MIT License. It runs on your machine. There is no service, no account, and no ax subscription. Sessions use your harness's own auth: your existing subscription by default at no extra per-token cost, or pay-as-you-go API billing when you launch with `--api`. ax adds no cost of its own.

### What is composition?

Building a workflow by combining ax's small verbs (launch, read, wait, result, and the rest) with ordinary shell tools: pipes, loops, cron, `jq`, exit codes. Every verb prints ids and JSON on stdout and returns a meaningful exit code, so agent sessions plug into tools you already have. You assemble a workflow from parts instead of using one monolithic platform feature. The [recipes page](recipes.html) shows this in practice.

### Is ax more or less efficient than using a harness directly?

For a single interactive task, ax adds almost no overhead, and it does not make the model faster or cheaper per token. It makes you more efficient in three cases: when you run many sessions in parallel, when you must not lose long-running work (the session holder survives detach), and when you automate a repeat workflow (compose it once, run it on cron) instead of babysitting an agent by hand. On cost, the [cost-tiered routing recipe](recipes.html#cost-routing) can reduce spend by escalating to a stronger model only when a cheaper one fails.

### Why does the separation of concerns between ax and the harness matter?

The harness owns the agent: model, tools, context, permissions. ax owns the control plane: launch, persist, list, compose, coordinate. This split keeps ax thin and harness-agnostic. It works with any harness, and a new one takes one [`[[harness]]` config block](manual.html#configuration-file). It never reimplements agent internals, so your auth and permissions stay in the harness where you configured them. The stable control layer can keep working as harnesses change. If ax tried to be the agent too, it would lock to one vendor.

### Do I need tmux or zellij?

No. The no-mux session holder is the default, with zero external dependencies. Launch, detach, monitor, and reattach all work on a plain terminal. A multiplexer is optional: set `mux = "tmux"` or `mux = "zellij"` and ax drives it natively, with a real window or tab per session. See the manual's [multiplexer backends](manual.html#multiplexer-backends) chapter.

### Which harnesses does ax support?

Claude Code, Codex, pi, and opencode are built in and work with no config file at all. Any other CLI agent can be wired in with one `[[harness]]` config block that tells ax where its transcripts live and how to launch and resume it. See [supported harnesses](manual.html#supported-harnesses) in the manual.

### Does ax send my code anywhere?

No. ax is daemonless and runs locally. There is no ax service and no account. Your code and transcripts go only where your harness sends them. Remote sessions travel over your own transport command, usually ssh, and that command carries its own auth. The one network call ax itself makes is `ax models update`, which fetches model price and context data from models.dev when you run it. Even that can be disabled: set `offline = true` in the config (or `AX_OFFLINE=1`) and ax makes zero outbound connections, running on cached or bundled model data.

### What is the difference between a recipe and a behavior?

A recipe is a shell workflow that composes ax verbs from the outside: a script, a loop, a cron job. The [recipes page](recipes.html) collects them. A behavior is a prompt file passed with `--behavior` that shapes how one session acts from the inside, like the [coordinator behavior](manual.html#coordinator) that tells a session to split work and launch workers. Behaviors live in the repository's `behaviors/` directory.

### Are the recipes a fixed set?

No. A recipe is just a shell script plus a behavior file, so the ones shipped are examples, not a closed list. The [verbs](manual.html#verbs) are small pieces you compose into your own workflows. Write recipes for your own work, share them, and build on other people's. The shipped recipes are examples, not the product boundary.
