# CLI Reference

> Complete reference for the Archon command-line interface and all available commands.

Run AI-powered workflows from your terminal.

## Prerequisites

1. Clone the repository and install dependencies:
   ```bash
   git clone https://github.com/coleam00/Archon
   cd Archon
   bun install
   ```

2. Make CLI globally available (recommended):
   ```bash
   cd packages/cli
   bun link
   ```
   This creates an `archon` command available from anywhere.

3. Authenticate with Claude:
   ```bash
   claude /login
   ```

**Note:** Examples below use `archon` (after `bun link`). If you skip step 2, use `bun run cli` from the repo directory instead.

## Forge operations

Use `archon forge resolve --data <json>` for an explicit remote, `archon forge checks --data <json>` for a qualified PR, and `workitem.view`, `workitem.list`, `workitem.create`, `workitem.labels.set`, `workitem.label.add`, `workitem.label.remove`, `workitem.comment.upsert`, `workitem.close`, `repo.labels.list`, `repo.label.ensure`, `pr.view`, `pr.create`, `pr.edit-body`, `pr.ready`, `pr.draft`, `pr.merge`, `checks.rerun`, `pr.reviews` or `comment.upsert` for the rest. Reads return structured observations; writes report whether they were applied and verified, refused, applied but unverified, or left with an unknown outcome. Pass a request carrying authored text with `--data-file <path>` so it stays out of argv. See [Forge operations](/reference/forge/) for request shapes, plugin configuration, credentials and audit behavior. The bundled SDLC pack uses `gh` by default; set `ARCHON_SDLC_FORGE=forge` to opt into a plugin with every required pack operation. Host-only `archon forge metadata --data '{"host":"github.com"}' --json` inspects the selected plugin's capabilities without credentials or a forge operation. See [Use the forge path in the SDLC pack](/reference/forge/#use-the-forge-path-in-the-sdlc-pack).

## Users and roles

`archon user` manages identities and persisted roles as the local operator. It requires no git repository and uses the install's configured database (`DATABASE_URL` for PostgreSQL, otherwise `ARCHON_HOME/archon.db`).

```bash
archon user list                    # Full ids, roles, display names and platform identities
archon user role <id> admin         # Designate an admin using a full Archon user id
archon user role <id> member        # Demote a user
archon user link <id> slack:U123    # Link a platform identity to an existing user
archon user link <id> slack:U123 --move # Reassign an identity from another user
```

Linking requires an existing full user id and a valid platform id. The first colon separates the platform from its opaque user id. A conflicting link fails and names both Archon users unless `--move` is supplied. Moving changes only the identity mapping; both users, their roles and their other data remain unchanged. Linking to the run starter lets chat actions use that user’s existing permissions and record that user as the actor.

Roles must be `admin` or `member`; unknown ids and invalid roles fail with a non-zero exit code. New users are members, while upgrades preserve existing roles. There is no last-admin restriction because the CLI is the operator. Only a run's starter or an admin can act on it; the local operator can always act through the CLI. See [Who can act on a run](/reference/security/#who-can-act-on-a-run) for the action policy and [Users and roles](/reference/security/#users-and-roles) for the upgrade policy and Docker commands.

## Quick Start

```bash
# List available workflows (requires a git repo or an already registered folder project)
archon workflow list --cwd /path/to/repo

# Run a workflow (auto-creates isolated worktree by default)
archon workflow run investigate --cwd /path/to/repo "Why does login fail after a password reset?"

# Explicit branch name for the worktree
archon workflow run plan --cwd /path/to/repo --branch feature-auth "Add OAuth support"

# Opt out of isolation (run in live checkout)
archon workflow run investigate --cwd /path/to/repo --no-worktree "Why does the nightly build fail?"
```

**Note:** Workflow and isolation commands normally require running from within a git repository (running from subdirectories automatically resolves to the repo root). A non-git directory also works if it's a registered [folder project](/getting-started/concepts/#folder-projects) — or on first use by passing `--folder`, which registers it and runs in place. The `version`, `help`, `chat`, `setup`, and `serve` commands and `workflow attention` work anywhere.

## Commands

### `trigger`

Start workflows from deployment bindings and inspect durable resource admission. A server with `ARCHON_TRIGGER_HOST` set hosts the same bindings without these commands. See [workflow triggers](/guides/workflow-triggers/) for configuration, capacity, overlap policy, and recovery requirements.

| Command | Behavior |
| --- | --- |
| `trigger fire --config <file> [--delivery-id <key>] [--inputs <file>]` | Record a receipt and drain its retained binding host. A delivery key deduplicates event inputs within the source. When a binding fails launch preparation, the command exits nonzero and prints the refusal (with `--json`, in `error`); the stored receipt keeps only the failed stage, for example `launch_preparation_failed`. |
| `trigger drain --host <host-id>` | Prepare persisted starts and admit eligible queued work. |
| `trigger list [--limit <1-1000>]` | List recent safe receipt summaries (default 50). |
| `trigger inspect <receipt-or-request-id>` | Show source identity, current disposition, blockers, and recovery guidance. |
| `trigger execute <request-id> --host <host-id>` | Explicitly retry an admitted pending execution using the engine claim. |
| `trigger withdraw <request-id>` | Withdraw untouched queued work. |
| `trigger recover-preparation <receipt> <binding> --owner <owner-id> --yes` | Reset preparation only after verifying its recorded owner has stopped. |
| `trigger schedule <install\|remove> --config <file>` | Manage a native macOS LaunchAgent with an explicitly configured interval. |
| `trigger whoami` | Print the Archon user ID for your CLI identity, to use as a binding's `runAsUserId`. |


### `chat <message>`

Send a message to the orchestrator for a one-off AI interaction.

```bash
archon chat "What does the orchestrator do?"
```

### `setup`

Interactive setup wizard for credentials and configuration.

```bash
archon setup                      # writes ~/.archon/.env (home scope, default)
archon setup --providers codex    # skip the provider selection question
archon setup --scope project      # writes <cwd>/.archon/.env instead
archon setup --force              # overwrite instead of merging (backup still written)
archon setup --spawn              # open in a new terminal window
```

**Flags:**

| Flag | Effect |
|------|--------|
| `--scope home` | Write to `~/.archon/.env` (default). Applies to every project. |
| `--scope project` | Write to `<cwd>/.archon/.env`. Overrides user scope for this repo only, except CLI `GH_CONFIG_DIR` and inherited workflow identity; see [forge configuration](/reference/forge/#plugin-configuration). |
| `--force` | Overwrite the target file wholesale instead of merging. A timestamped backup is still written. |
| `--providers <ids>` | Install comma-separated maintained providers (claude, codex, pi), skipping the provider selection question. Auth and configuration prompts still run. |
| `--spawn` | Open setup wizard in a new terminal window. |

`--providers` takes precedence over `ARCHON_PROVIDERS`; both accept comma-separated IDs and skip the provider selection question. An explicit empty value selects none. Without either, setup asks which maintained providers to install, installs that selection in release binaries, and renders their diagnostics before collecting native authentication and configuration. Source checkouts run maintained providers from source. Selecting none installs nothing and prints `archon provider add <claude|codex|pi>`; setup never silently selects a provider. With multiple selected providers, it asks which should be the default and saves that choice.

**Write safety**: `archon setup` never writes to `<cwd>/.env` — that file belongs to you. The wizard always targets one archon-owned file chosen by `--scope`, merges into existing content (so user-added keys survive), and writes a timestamped backup before every rewrite (e.g. `~/.archon/.env.archon-backup-2026-04-20T09-28-11-000Z`).

For GitHub, choose a personal access token (the solo default) or a [GitHub App](/adapters/github-app-setup/) (recommended for teams). App mode collects the App ID, private-key path or inline PEM, webhook secret, and optional slug. When switching modes, setup asks for confirmation before replacing GitHub authentication keys, removes the other mode’s keys, writes the webhook secret you enter when switching to App mode, and, without `--force`, preserves unrelated values and the other webhook settings. With `--force`, setup replaces the target file wholesale after backing it up. With that flag, Add mode asks for credentials again for every existing platform as well as any additions. Same-mode reruns keep existing non-empty values unless `--force` is given. An incomplete App ID/key pair is replaced together after confirmation; Add also collects missing webhook settings and resolves dual-mode settings through the same confirmation. If the other scope supplies conflicting GitHub credentials, setup stops and names that file so you can remove the conflict explicitly. Secrets are saved to the selected env file and are not displayed.

**Default assistant + chat model**: after you pick the default assistant, the wizard offers an optional default chat model for it — a short curated list (e.g. `sonnet`/`opus`/`haiku` for Claude) plus an "Other…" free-text entry. Press Enter to keep the SDK default. Your selection is recorded in `~/.archon/config.yaml` as `defaultAssistant` (plus `assistants.<provider>.model` when you chose a model) — the same write as [`archon ai default <provider> [<model>]`](#ai) — and re-running setup shows the current model. Pi skips the model prompt because its backend/model pair is chosen earlier in the wizard.

### `doctor`

Verify your Archon setup. Runs a checklist of common failure points: installed provider versions and unavailable-plugin reasons, each registered provider's own diagnostics, the selected SDLC forge source (`gh` by default checks active authentication and repository read access; `ARCHON_SDLC_FORGE=forge` checks the installed plugin capabilities and repository read access; other values fail), the configured assistant's native login, OpenCode runtime SDK and executable presence, database reachability, workspace writability, bundled defaults, folder-project detection (contained repos, when run from one), telemetry state, AI credentials connected in Archon, and adapter token pings (Slack/Telegram, best-effort). Doctor exits 1 when any check fails. A warning does not change the exit code, and the summary line counts the warnings instead of reporting that all checks passed.

Run `archon doctor` from a checkout with a forge remote. The SDLC forge row fails without one.

```bash
cd /path/to/repo
archon doctor
```

With no registered providers, doctor fails and prints `archon provider add <claude|codex|pi>`. Stale maintained plugins fail with their installed and CLI versions and the reinstall command. The AI credentials check uses each connected credential the way a run would: it decrypts it and refreshes an expired subscription grant (saving the rotated grant). It prints one line per credential and fails when one cannot be used (it cannot be read, or the vendor rejected the refresh), naming the command that reconnects it. It warns when a credential could not be verified, for example because the vendor was unreachable. An API key is reported usable once it decrypts; only a model request proves the vendor accepts it. The assistant login check uses the merged configuration and the provider's own runtime, unless that model's vendor already has a credential connected in Archon that the assistant can use. Pi cannot use a connected Claude subscription: with no native Anthropic login, the check fails and names `provider: claude`. When Archon config names no model (Pi then uses its own default), doctor cannot tell which vendor the run uses, so a missing native login is a warning, not a failure, if you have connected a credential the assistant can use. Pi resolves its configured key during the check, so a key command runs during `archon doctor`; a command that prompts can prompt again at run start. The check builds the runtime a run builds, which runs every provider's `!command` key stored in Pi's `auth.json`, not only the configured provider's. For a model in Pi's catalog, a run resolves a `models.json` key command once when a node starts and uses that key for the whole node, so a short-lived token can expire before a long node ends; a model from an extension provider runs its key command on each request. Pi OAuth checks may refresh through Pi's runtime. Codex reads its native account through app-server and asks it to refresh the sign-in, so a revoked login fails the check (a run does not make this call); an API key is usable once resolved. When Codex is configured for a model provider that needs no OpenAI login, the check is skipped. Claude, Copilot, and OpenCode cannot check their native login without starting a model session, so the check is skipped and reports "not checked". The OpenCode check probes that the embedded runtime SDK module resolves and the `opencode` executable is available on the inherited `PATH` — it never boots the runtime (which spawns a child process and binds a port). OpenCode and Copilot are bundled but [deprecated](/getting-started/ai-assistants/#deprecated-providers): doctor runs their diagnostics only when one is your default assistant or has settings under `assistants`, and does not count them as installed providers. The provider support check warns when the default assistant is a [deprecated provider](/getting-started/ai-assistants/#deprecated-providers).

In GitHub App mode, doctor validates the App ID and RSA private key from `GITHUB_APP_PRIVATE_KEY` or `GITHUB_APP_PRIVATE_KEY_PATH` locally. It reports incomplete, invalid, or conflicting App/PAT configuration as a failed check, without printing secrets. This check does not require `WEBHOOK_SECRET` and does not verify GitHub access remotely. The **SDLC forge** row runs the same preflight as the bundled SDLC pack for the current checkout, including its repository workflow `env` settings. With the default `gh` source, it checks active authentication for remote hosts, including a stored `gh auth login` with no token environment variables, then reads the repositories the remotes name with `gh api --hostname <host> repos/<path>`. One readable repository, including a readable mirror, is sufficient. This proves read access, not write access; the first write proves write access. With `ARCHON_SDLC_FORGE=forge`, it requires every pack operation before checking repository read access. It names the credential source used; a refusal fails doctor even when App configuration is valid locally.

Exit code 0 if all checks pass or are skipped; 1 if any critical check fails. Adapter pings degrade to `skip` on network errors — a flaky connection does not flip the result red.

Also runs automatically at the end of `archon setup` (optional).

### `provider add`

Add or replace maintained providers from the matching Archon release:

```bash
archon provider add claude codex pi
```

Adding a provider connects Archon to your agent CLI or SDK; it does not install that CLI.

The command accepts only `claude`, `codex`, and `pi`, pins the first-party repository tag to the CLI version, and uses the plugin installer's checksum verification. Maintained ids cannot be installed from another repository. Remove a receipt with `archon plugin remove coleam00/Archon/plugins/provider-<id>`.

Release binaries ship without Claude, Codex and Pi and load them only from what this command installs. The quick installers run it for the providers you select (`--providers` / `-Providers` / `ARCHON_PROVIDERS`), and `archon setup` runs it for its selection. An installed provider must match the CLI version, so run it again after upgrading Archon. Source checkouts and Docker run these providers from source over the process transport and refuse this add command.

The default provider comes from explicit configuration (`assistant` in project config or `defaultAssistant` in global config), then `DEFAULT_AI_ASSISTANT`, then the only registered provider. With multiple providers and no configured default, a run needing one fails before creating a worktree. A workflow can select its provider explicitly.

### `plugin`

Add and manage plugins published on GitHub. A plugin is `owner/repo[/path]`, the directory holding its `archon-plugin.json`; a version is a tag. Forge plugins, provider plugins, chat plugins, and workflow packs install through this command.

```bash
archon plugin add coleam00/Archon/plugins/forge-github         # forge: latest release
archon plugin add coleam00/Archon/plugins/forge-github@<tag>   # a specific release
archon plugin add owner/repo[/path]                             # workflow pack: default branch head
archon plugin add owner/repo[/path]@<tag>                       # workflow pack: a tag
archon plugin update <id>[@<tag>]
archon plugin remove <id>
archon plugin copy <id>                                             # workflow pack into ./.archon/workflows/
archon plugin list
```

The former plugin `install` verb remains a deprecated alias for `archon plugin add` and prints a warning.

`add` refuses an already-installed plugin (use `update`) and a file it did not install. Every check, including the manifest's `compatibility.archon` range and a provider or chat plugin's staged handshake, runs before installed files or receipts are replaced. Without `@<tag>`, the manifest at the default branch head decides the kind: a workflow pack installs that commit, and a forge, provider, or chat plugin installs its latest release, because its executables exist only as release assets. See [Forge operations](/reference/forge/#install-the-github-plugin) for what a forge install downloads and where it writes.

`list` reports every receipt that cannot be read, parsed, or validated with its file path and error. For a receipt at a valid `owner/repo[/path]` location, `remove <id>` deletes that receipt but leaves the files it named, because they can no longer be identified. `update <id>` installs the plugin again and writes a fresh receipt; it replaces an existing executable only when no other receipt, valid or unreadable, could own it. A binary plugin update must still read the old receipt to back it up. If permissions prevent that, restore read access first, or remove the receipt, then inspect its leftover executable and move or delete it if unused before reinstalling. A stray receipt outside a valid plugin-id location cannot be removed or repaired by these commands; delete the receipt file by hand using the path shown by `list`. `copy <id>` requires a readable workflow-pack receipt for that id and rejects a broken one; run `update <id>` to repair it, or `remove <id>` then reinstall it before copying. `add` of any other plugin refuses until every unreadable receipt is repaired or removed, because the files those receipts own are unknown.

A provider plugin records its descriptor in the receipt and registers on the next CLI invocation or server restart. A workflow names its descriptor id in `provider:`. See [Provider plugins](/guides/publishing-plugins/#provider-plugins) for executable naming, capabilities, credentials, and the process environment.

A chat plugin records its validated descriptor after an initialization-only handshake. On server restart, each usable chat receipt starts one supervised plugin process; a receipt that is unreadable or does not own its executable is logged and skipped until `update <id>` or `remove <id>` repairs it. Install, update, and removal take effect at the next restart. See [Chat plugins](/guides/publishing-plugins/#chat-plugins) for release assets, platform-id collisions, and installation behavior.

A workflow pack installs complete at one commit. The command fetches the tag, or the default branch head, with `git fetch --depth 1` into a private repository, reads the plugin directory of that commit, and refuses the pack if that directory holds a symlink, a submodule, a path that escapes it or a file name containing `\` or `:`, if an entrypoint is missing, or if another installed pack has the same owner and `name`. Git's credential setup applies to the fetch, but the manifest is first read unauthenticated from `raw.githubusercontent.com`, so a private repository cannot be installed. The tree is written to `ARCHON_HOME/plugins/packs/<id>/<commit>/` and then the receipt to `ARCHON_HOME/plugins/installed/<id>/receipt.json`, so a reader sees either the previous complete install or the new one. With a readable receipt, `update` replaces the tree and prints the old and new commit; `remove` deletes the receipt and that tree. When repairing an unreadable receipt, `update` prints only the newly installed commit and cannot identify old trees at other commits. Inspect `ARCHON_HOME/plugins/packs/<id>/` and remove old trees manually only after confirming they are unused. Nothing updates in the background. Installed entrypoints run as `owner/plugin:entrypoint`; see [Installed workflow packs](/guides/global-workflows/#installed-workflow-packs) for the pack layout and how runs resolve them.

`copy` writes the installed tree to `.archon/workflows/<name>/` at the root of the repository you run it in (or `--cwd`); in a folder project, the directory itself. It refuses when that directory exists. The copy is an ordinary project workflow pack from then on: you own and edit it, and `update` or `remove` do not touch it.

### `auth github`

Connect the current CLI user's GitHub identity via the GitHub device flow, so workflow commits name you as author while keeping the ambient Git identity as committer. PR comments and pushes use your connected identity.

```bash
archon auth github
```

Only meaningful on **multi-user installs** running GitHub App mode (`GITHUB_APP_ID` + `GITHUB_APP_CLIENT_ID`) with `TOKEN_ENCRYPTION_KEY` set — solo `GITHUB_TOKEN` installs don't need it and the command exits with an explanatory error. Your CLI identity is resolved from `ARCHON_USER_ID` (explicit override) or `$USER` / `$USERNAME`, mapped to a stable Archon user via the `cli` platform identity.

The command prints a `verification_uri` and a one-time `user_code`; visit the URL, enter the code, and authorize. On success the access/refresh tokens are stored encrypted (AES-256-GCM) in Archon's database. Exit code 0 on success; 1 if per-user GitHub is disabled, the identity can't be resolved, the code expires, or authorization is denied.

### `ai`

Manage **per-user AI-provider credentials** (API keys + subscriptions) and **model-tier config**. CLI identity is resolved from `ARCHON_USER_ID` (explicit override) or `$USER` / `$USERNAME`, mapped to a stable Archon user via the `cli` platform identity — the same as [`auth github`](#auth-github).

The credential subcommands (`key set`, `login`, `list`, `logout`) work on **any install** — the vault is auto-provisioned. CLI identity is resolved from `ARCHON_USER_ID` or `$USER`/`$USERNAME`. The config subcommands (`tier`, `alias`, `default`) are **ungated** — they write `~/.archon/config.yaml` and need no identity.

```bash
# --- Provider credentials (any install — vault auto-provisioned) ---
archon ai key set <vendor>       # connect an API key (masked prompt or piped stdin — never argv)
archon ai login <vendor>         # connect a subscription via OAuth (anthropic, openai, or github-copilot)
archon ai list                   # list connected credentials (metadata only, no secrets)
archon ai logout <vendor>        # disconnect a credential

# --- Model tiers + aliases + default assistant (ungated config) ---
archon ai tier set <small|medium|large> <provider> <model> [--effort <effort>] [--scope user|install]
archon ai tier list [--json]     # show configured tiers (install + yours) vs built-in defaults
archon ai tier unset <small|medium|large> [--scope user|install]
archon ai alias set <@name> <provider> <model> [--effort <effort>] [--scope user|install]
archon ai alias list [--json]    # show @custom aliases (install + yours)
archon ai alias unset <@name> [--scope user|install]
archon ai default <provider> [<model>] [--scope user|install]   # set the default assistant (+ optional chat model)

# --- Provider concurrency caps ---
archon ai capacity [list] [--json]         # provider attempts holding concurrency.providers slots
archon ai capacity release <attempt-id>    # release one whose owner process you verified is gone
```

Credential ids are **vendor-keyed** (`anthropic`, `openai`, `github-copilot`, plus the Pi backends like `openrouter`); legacy `claude`/`codex`/`copilot` are accepted and normalized with a printed notice. `ai login` supports subscription login for **`anthropic`**, **`openai`** (ChatGPT/Codex), and **`github-copilot`**. The `openai` login is an Archon-owned PKCE flow ([#1924](https://github.com/coleam00/Archon/issues/1924)): authorize in the browser, then paste the authorization code or the full `localhost:1455` redirect URL back at the prompt — nothing needs to listen on that port. The API key is never read from argv (it would leak into shell history): pipe it (`echo "$KEY" | archon ai key set openrouter`) or type it at the masked prompt.

`ai tier`, `ai alias`, and `ai default` edit the same `tiers:` / `aliases:` / `defaultAssistant` config you can hand-write in `~/.archon/config.yaml` (see [Configuration](/reference/configuration/)) or edit from the console **AI Settings** page. An unknown provider exits non-zero; `tier unset` removes the override so the tier falls back to its built-in preset — claude and codex only; for any other default provider the tier is left unset and must be reconfigured before use. The full per-user setup walkthrough is in [Per-user credentials and AI Settings](/getting-started/ai-assistants/#per-user-credentials-and-ai-settings).

**`ai default <provider> [<model>]` (default chat model).** The optional `<model>` sets the default **chat** model alongside the assistant. At `--scope install` it writes `assistants.<provider>.model` in `~/.archon/config.yaml` (omitting the model leaves that field untouched). At `--scope user` the provider and model are written **atomically** to your prefs row — `archon ai default pi --scope user` clears any previous model pin, since a pin is only meaningful for the provider it was set with. Your chat model applies to direct chat only; workflow nodes keep resolving the `large` tier. The model may also be an `@alias` or tier keyword.

**`--scope user` (per-user overrides).** On any of the config subcommands, `--scope user` writes your **personal** prefs row in Archon's database instead of the shared `config.yaml`. Your tiers/aliases/default override the install config for runs and chats *you* start — nobody else's. It needs a resolvable CLI identity (`ARCHON_USER_ID` or `$USER`) but **no** `TOKEN_ENCRYPTION_KEY` (model names aren't secrets). `ai tier list` / `ai alias list` show both scopes, marking your overrides with `[just you]`. The same scopes are editable in the console as the "This install / Just me" toggle on **AI Settings**.

### `telemetry status`

Show the current anonymous telemetry state: whether it is enabled, the opt-out reason if not, the install UUID, the active PostHog host, and the key source.

```bash
archon telemetry status
```

Useful for verifying that an opt-out env var (`DO_NOT_TRACK=1`, `ARCHON_TELEMETRY_DISABLED=1`, `CI=true`, `POSTHOG_API_KEY=off`) is being picked up. Inspecting status never creates a `telemetry-id` file while opted out.

### `telemetry reset`

Rotate the persisted anonymous install UUID at `~/.archon/telemetry-id`. The previous ID is overwritten and not recoverable.

```bash
archon telemetry reset
```

Exit code 0 on success; 1 if the ID file cannot be written.

### `workflow list [name]`

List workflows available in the target directory, or inspect one workflow by name.

```bash
# Compact discovery output
archon workflow list --cwd /path/to/repo

# Machine-readable output for scripting
archon workflow list --cwd /path/to/repo --json

# Exact, untouched description for one candidate
archon workflow list archon-review --full --json
```

Discovers flat, one-level grouped, and exact `<pack>/<workflow>/` packaged layouts from `.archon/workflows/` and `~/.archon/workflows/`, plus bundled defaults. See [Global Workflows](/guides/global-workflows/).

The default output is a compact discovery view. Descriptions of at most 160 Unicode code points are returned unchanged. Longer descriptions are whitespace-normalized and reduced to their first sentence when it fits, or to preview content of at most 160 code points at a word boundary. Human-readable output appends ` [truncated]` after a shortened description. JSON keeps the description text separate from its truncation state: `descriptionTruncated` is `true` when content was omitted. Fetch the full description before choosing or launching a workflow when that field is `true`.

The optional `name` uses the same exact, case-insensitive, suffix, and substring resolution as workflow invocation. Ambiguous names fail instead of choosing a workflow; exact names copied from the discovery output are deterministic.

**Flags:**

| Flag | Effect |
|------|--------|
| `--cwd <path>` | Target directory (required for most use cases) |
| `--json` | Output machine-readable JSON instead of formatted text |
| `--full` | Return exact authored descriptions; combine with `name` for one workflow's full detail |

With `--json`, outputs `{ "workflows": [...], "errors": [...] }`. Each workflow has a `description` and `descriptionTruncated` boolean. `--full` returns the exact authored description and sets `descriptionTruncated` to `false`. Optional fields (`provider`, `model`, `effort`, `webSearchMode`, `parseWarnings`) are omitted when not set on a workflow. A workflow written with the deprecated `modelReasoningEffort:` reports its value as `effort`, which is what it is translated to at load — unless it also declares `effort:`, which wins. Each `parseWarnings` entry is a full warning message naming a key the engine dropped or deprecated, the workflow or node where it was found, and what to write instead — see [Unknown keys](/guides/authoring-workflows/#unknown-keys-are-reported-not-rejected). Discovery errors remain in the top-level `errors` array when a name filters the workflow list. If the named workflow is missing or ambiguous, the failure envelope is `{ "ok": false, "error": "...", "errors": [...] }`, retaining errors found during the same discovery.

### `workflow run <name> [message]`

Run a workflow with an optional user message.

```bash
# Basic usage
archon workflow run investigate --cwd /path/to/repo "Why does the export job time out?"

# With isolation
archon workflow run plan --cwd /path/to/repo --branch feature-x "Add caching"

# Supplying a workflow's declared inputs (one flag per input)
archon workflow run review-block --cwd /path/to/repo \
  --input diff="$(git diff)" --input style=terse "focus on the auth changes"

# Rebind only the large tier for this run
archon workflow run issue-to-pr --cwd /path/to/repo \
  --model large=openai/gpt-5.6 "fix #2481"

# Load a saved sparse layer, then replace only its large tier for this run
archon workflow run issue-to-pr --cwd /path/to/repo \
  --config ./config.minimax.yaml \
  --model large=openai/gpt-5.6 "fix #2482"

# Continue the exact estate left by a terminal run
archon workflow run archon-ship --cwd /path/to/repo \
  --adopt 6d5066ca-47b4-4ee8-8d1d-2f3db8039190 "finish the delivery"
```

Progress events (node start/complete/fail/skip, approval gates) are written to stderr during execution. The workflow-start output also names the absolute local path of the run's JSONL transcript. Use [`workflow logs`](#workflow-logs) when you have a run ID and want the content rather than the path.

If the workflow's YAML declares keys the engine ignores, a warning naming each one is written to **stderr before the run starts**. This matters to `--detach --json` callers: `--json` silences all logging, so stderr is the only channel left, and it keeps stdout to exactly the JSON payload.

Note that a real `run` emits a JSON payload **only** under `--detach`. Without it, `--json` suppresses logs but the command still prints human progress to stdout (`Running workflow: …`), so do not pipe a plain real `run --json` into a parser. The side-effect-free `--dry-run --json` mode below is the other exception: it emits exactly one complete trace document. See [Unknown keys](/guides/authoring-workflows/#unknown-keys-are-reported-not-rejected).

**Flags:**

| Flag | Effect |
|------|--------|
| `--cwd <path>` | Target directory (required for most use cases) |
| `--workflow-source <path>` | Read the workflow, its commands, and its scripts from this directory instead of `--cwd`. Lets an **uncommitted** workflow in one checkout run against a different checkout, repository, or folder project, with no commit, push, or merge. Fresh runs only -- rejected with `--resume`, because a resumed run executes the source it already captured. See [Running a workflow from another checkout](#running-a-workflow-from-another-checkout). Repo config is not part of the source: the run reads `.archon/config.yaml` from the launch checkout (`--cwd`); see [Where a run reads `.archon/config.yaml`](/reference/configuration/#where-a-run-reads-archonconfigyaml). |
| `--branch <name>` | Explicit branch name for the worktree |
| `--from <branch>`, `--from-branch <branch>` | Start-point for the new worktree only -- unlike `--base`, it does not change the PR target |
| `--base-branch <name>` | Choose the project base branch on first registration only. Omit to follow the remote default at use time; no prompt. Rejected for folder projects, existing projects, resume/adoption/supersedes, and dry runs. A reachable remote must advertise the branch. With `--base`, this flag stores the project choice while `--base` overrides only this dispatch. |
| `--base <branch>` | Per-dispatch base override for a single run. Sets **both** the worktree cut-from **and** the PR target (`$BASE_BRANCH`), and outranks `worktree.baseBranch` in config plus the codebase default -- see [Base branch precedence](#base-branch-precedence) below. The branch **must already exist on the remote**; a missing one is a hard error, not a fallback. Combine with `--from` to drive the two separately. Rejected with `--no-worktree`, `--folder`, and workflows pinning `worktree.enabled: false`. |
| `--no-worktree` | Opt out of isolation -- run directly in live checkout |
| `--folder` | Register the current directory (with or without git) as a folder project (first use) and run in place -- no worktree. Rejects `--branch`/`--from`/`--base`. |
| `--container` | Run a **folder project** inside an overlay-isolated Docker container instead of in place (writes land in an overlay, not the live root, until an approval-gated write-back). Folder-only; a repo project errors. Requires the runner image (`bun run build:runner-image`). Pauses `docker stop` the container; `--resume`/`approve`/`reject` rediscover and restart it. See the [Container isolation guide](/guides/container-isolation/) and [configuration](/reference/configuration/#container-isolation-folder-projects). |
| `--input <name>=<value>` | Supply one value for the workflow's declared `inputs:`. **Repeat the flag per input.** Splits on the first `=`, so the value may itself contain `=`; `--input name=` supplies an empty string. Omitted inputs take their declared `default:`. A missing **required** input or an **undeclared** name is refused before any worktree, clone, or AI cost, through the same contract a composing `with:` map goes through. Works with `--dry-run` (inputs resolve exactly as in a real run). Rejected with `--resume` (a resume replays the inputs recorded on the run). See [Running a workflow that declares inputs](/guides/authoring-workflows/#running-a-workflow-that-declares-inputs). |
| `--model <name>=<spec>` | Rebind one `small`, `medium`, `large`, or existing `@alias` for this run. **Repeat the flag per binding.** An Archon agent prefix selects that agent (`codex/gpt-5.6-sol`); another valid vendor/model ref selects Pi (`openai/gpt-5.6`); an unqualified model keeps the binding's current provider; a tier or alias RHS copies that preset. Unspecified names keep their user → repo → global → built-in values. Literal `model:` pins and nodes that never reference the rebound name do not change. Bare `--model <spec>` is invalid, there is no run-wide `--provider`, and the flag is rejected with `--resume`. Works with `--dry-run`. |
| `--config <path>` | Load one sparse YAML config layer for this fresh run. Relative paths resolve from the directory named by `--cwd`, even when it is a repository subdirectory. Values in the file override persistent config and user AI preferences; explicit `--model` flags then replace only their named bindings. Works with `--dry-run` and `--detach`; the parent validates and seals the layer before handing it to a detached child, so later file edits cannot change that launch. Rejected with `--resume` because a continuation restores the sealed layer recorded when the run started. |
| `--resume` | Resume from last failed run at the working path, skipping completed nodes and reusing its recorded AI configuration when present. |
| `--adopt <run-id>` | Start a new run in a terminal run's exact worktree or branch, with `adopted_from_run_id` provenance and `$ADOPTED_RUN_DIR` access. Inherits its recorded AI configuration; conflicting AI config or model overrides are rejected. Run-id selection is exact; adoption never infers a run from workflow name or prompt text. |
| `--supersedes <run-id>` | Start in a fresh estate while recording that this run replaces a terminal prior run. Unlike `--adopt`, it inherits no checkout. |
| `--quiet`, `-q` | Suppress all progress output to stderr |
| `--verbose`, `-v` | Also show tool-level events (tool name and duration) |
| `--detach` | Run in a detached background child and return before completion. Exact-id `workflow resume` returns after the child accepts the resume. The child does all the work; find it later with `workflow runs`/`workflow get`. For `workflow run`, human output names both files and the `--json` acknowledgement carries `runId`, `transcriptPath` (the structured per-run JSONL), and `logPath` (the detached child process's stdout/stderr capture). These paths are intentionally distinct. Use [`workflow logs <run-id> --follow`](#workflow-logs) for execution events and [`workflow wait <run-id>`](#workflow-wait) when a host needs the next terminal or gate transition. Also available on `approve`/`reject`/`respond`/`resume`; their acknowledgement differs — see [Detached control verbs](#detached-control-verbs). |
| `--dry-run` | Simulate deterministic DAG control flow in memory. Creates no run, worktree, session, event, artifact, or provider request. |
| `--stubs <path>` | YAML mapping of node ids to scalar or structured outputs for `--dry-run`. Relative paths resolve from `--cwd`. |
| `--stubs-init <path>` | Write a complete stub scaffold for the expanded workflow and exit. Refuses to overwrite an existing file. Relative paths resolve from `--cwd`. |
| `--default-stubs` | Fill reachable nodes omitted from `--stubs` with schema-valid placeholders. Explicit stubs still win; without this flag, a missing reachable stub remains an error unless the node declares `trigger_rule: all_done`. |
| `--exec-code` | During `--dry-run`, execute trusted `bash:`/`script:` nodes locally instead of requiring stubs. Default is no code execution. |
| `--pause-at-gates` | During `--dry-run`, stop at the first deprecated `approval:` gate instead of auto-approving it. An [attention wait](/guides/authoring-workflows/#attention-waits) needs no flag: it pauses the simulation unless the stubs answer it. |

Fresh CLI runs do not create a chat conversation, message history, or title. Execution output stays in stdout and the run's transcript, events, and artifacts. Resuming a run with an existing chat origin keeps recording into that conversation.

#### Per-run config files

New runs record the resolved assistant, provider defaults, tiers, aliases, and model bindings at launch preparation, reading `.archon/config.yaml` from the launch checkout (the current directory or `--cwd`), including uncommitted and gitignored edits. See [Where a run reads `.archon/config.yaml`](/reference/configuration/#where-a-run-reads-archonconfigyaml). Resume and continuation reuse them even after config or user AI preferences change. New child runs inherit the parent's recorded AI base; existing children keep their own record. Detached launches persist this record before spawning the child. Adoption inherits the prior run's recorded AI policy, while supersession prepares fresh policy. Adoption of a recorded run rejects new `--model` bindings and AI fields in `--config`; non-AI fields remain allowed. Older runs without this record keep today's current-config resolution.

Credentials are checked freshly and are excluded from the AI record. Provider-native settings and guidance remain live, including Claude setting sources, provider binary paths, Codex search and additional directories, and Copilot config directory, discovery, login selection, and logging. Process-owned Pi environment and concurrency settings and non-AI runtime settings also remain live. Workflow source selection still controls the graph and scripts separately from this AI policy.

A run config is an ordinary YAML file selected explicitly for one invocation. It is useful for reusable choices such as `config.minimax.yaml`, but it is not a registered profile and does not change `.archon/config.yaml`.

```yaml
# config.minimax.yaml
tiers:
  large: { provider: pi, model: minimax/MiniMax-M3 }
env:
  BENCH_MODE: "1"
```

The layer is sparse: omitted settings keep their normal lower-layer values. In `--config ./config.minimax.yaml --model large=openai/gpt-5.6`, only `large` is replaced by the flag; `small`, `medium`, aliases, and every other omitted setting still fall through. See [Run-scoped configuration](/reference/configuration/#run-scoped-configuration) for the supported keys and fail-fast exclusions.

#### Running a workflow from another checkout

`--cwd` and `--workflow-source` answer two different questions: what the run acts on, and where the workflow itself is read from. They are the same directory unless you say otherwise.

```bash
# Author in ~/dev/archon (uncommitted), run against a clean checkout elsewhere
archon workflow run implement \
  --workflow-source ~/dev/archon \
  --cwd ~/checkouts/archon-target \
  "implement the plan"
```

Every run freezes its workflow source when it starts, whether or not you pass this flag. That means the source directory's `.archon/workflows`, `.archon/commands`, and `.archon/scripts`, plus your home-scoped `~/.archon/` source and Archon's own bundled defaults. Everything a static `include:` can reach is frozen together, so an included global or bundled workflow cannot change shape under a run either. That freeze is what makes the rest predictable:

- **The target stays clean.** Nothing from the source checkout is written into it, so its `git status` and its validators only ever see its own files.
- **A run does not change shape while it is running.** Edit, move, or delete the source checkout after a run starts and a resume still executes what the run began with. Start a new run to pick up the edits.
- **Resume needs no source path.** Every continuation -- `archon workflow resume <run-id>`, `archon workflow run <name> --resume`, `approve`, and `reject` -- loads the run's own captured source, which is why `--workflow-source` is refused alongside `--resume`.

The captured source lives beside that run's artifacts, at `~/.archon/workspaces/<project>/workflow-source/runs/<run-id>/`, and stays as long as the run's other output does. It is deliberately not inside `$ARTIFACTS_DIR`: that directory is the run's output channel, handed to every node and listed for inspection, and the frozen source is neither. Its manifest records a content digest and source-resolution settings, which the run row also stores. Archon checks those pinned values during initial discovery, on resume, and immediately before every later filesystem-backed command, named script, or composed-source read, hashing the full capture each time. If the capture is changed or replaced, the read and any container dispatch are refused. Start a fresh run to execute the current workflow. If a read fails after the run started because this host cannot read the capture, or the capture is missing here, the run pauses instead of failing and is continued by a host that can read it (see `workflow wake`).

These checks detect a mismatch present when Archon checks; they do not seal the directory or turn workflow execution into a security sandbox. Another process running as the Archon user can still change a file after a check, and already-started parallel nodes are not cancelled. Inline node bodies and command bodies compiled into an included graph are already in memory. A runtime `workflow:` child deliberately captures the live authoring source when that child starts, then receives its own pinned capture. If the parent's recorded authoring directory has moved or been deleted, Archon refuses to start the child instead of capturing a same-named workflow from the target checkout.

The one exception is a run that started before Archon captured source at all. It has nothing recorded to honor, so it resumes against the current source on disk with a warning. A run whose recorded capture is missing or unreadable never falls back to live source. After admission, it pauses for continuation on a host that can read the capture; before admission, execution is refused until the capture is readable. Source-integrity failures, such as a changed or replaced capture, still fail or refuse execution.

An older captured run may have a digest in its run row but no out-of-band copy of its source-resolution settings. Archon still verifies its tree against that digest, reads the settings from the manifest, and on the first resume records them on the run row beside the digest, with a warning. Nothing can prove those settings are the ones the run started with, since the manifest is outside the digest; pinning them closes the window, so a later edit to the manifest is refused like any other change.

**Compiled binaries.** A binary embeds its bundled workflows, commands, and scripts as constants rather than files. Those get written into the capture alongside everything else, so they are covered by the same digest — which means upgrading Archon between pausing and resuming a run is fine: the run keeps executing the bundled content it started with, and a change to those bytes would be caught like any other.

`--container` runs get the same treatment: Archon checks the host capture before dispatch, then bind-mounts the capture **read-only at the same absolute path** inside the container. A named script therefore resolves identically whether a node runs on the host or in the container, and a container recreated on resume gets the mount back. `$ARTIFACTS_DIR` is bound the same way but read-write, so a node's outputs land on the host. The read-only bind is not hostile-process containment: native overlay mode has `CAP_SYS_ADMIN`, and a same-UID host process can still race a check. `--workflow-source` is refused with `--container` — a folder project runs in place, so its source and its target are the same directory by definition.

#### Deterministic dry-run

Use dry-run to test DAG routing, joins, loops, `when:`, strict output fields, and variable substitution without starting a real workflow:

```bash
cat > stubs.yaml <<'YAML'
classify:
  issue_type: bug
  severity: high
investigate: "Root cause: stale cache"
YAML

archon workflow run triage --cwd /path/to/repo \
  --dry-run --stubs stubs.yaml "Issue #2100"

# One complete JSON document, safe to pipe in CI
archon workflow run triage --cwd /path/to/repo \
  --dry-run --stubs stubs.yaml --json | jq '.trace, .outcome, .authoredOutcome'
```

Generate a starting fixture when a composed workflow has many nodes, then keep only the values that drive the path you want to test:

```bash
archon workflow run deliver --cwd /path/to/repo \
  --dry-run --stubs-init fixtures/deliver.yaml

# After editing the load-bearing values in the generated YAML:
archon workflow run deliver --cwd /path/to/repo \
  --dry-run --stubs fixtures/deliver.yaml --default-stubs
```

The scaffold is derived from the already-expanded workflow, so included top-level nodes use their flattened ids (for example, `review__classify`) — while `loop_group` BODY nodes keep their bare ids even inside an included block (the group node gets the `<includeId>__` prefix; its body does not). Prefer `--stubs-init` over hand-writing keys: the scaffold is the authoritative source for both spellings. Structured `output_format` values are emitted as YAML objects with native booleans, numbers, arrays, and nested required properties. Loop completion fields are generated as `true`. If Archon cannot prove that a generated value satisfies its JSON Schema, scaffold generation fails before creating the file.

The stub file must contain one YAML mapping. Each value is either a string or an object. Object stubs are preserved as structured output, so downstream `$classify.output.severity` references behave like live structured producers. Strict coverage remains the default: a reachable AI, bash, or script node without a stub fails the simulation and appears in `missingStubs`. The one exception is a `trigger_rule: all_done` join — it runs whatever its upstream did, so a real run reaches it regardless of stub data. It gets a generated placeholder and is listed in both `missingStubs` and `toleratedMissingStubs`, and the absent stub alone never fails the simulation or a `workflow test` fixture. It is only listed in `toleratedMissingStubs` once that placeholder exists: a node whose `output_format` cannot produce one still fails, and stays out of the tolerated list. A `loop:` node carrying the same trigger rule is tolerated on the same terms, with one addition — the placeholder must also end the loop, which it does by satisfying the node's own completion channel, so a tolerated loop always completes on its first iteration. The one loop shape it cannot end is an `output_format` whose only completion channel is a prose `until:`, because the generated JSON never carries the sentinel; that node still fails and stays out of the tolerated list. Add `--default-stubs` to fill only omitted reachable nodes with the same schema-aware placeholders used by scaffold generation; explicit values always take precedence. Without `--exec-code`, a reachable `bash:` or `script:` node that arrived through `include:` needs a stub like any other: a stubbed node never runs its body, so the caller's `with:` values are never delivered to it. With `--exec-code` it can run unstubbed and reads those values as described below. Supplied stubs for unknown or unreachable nodes appear in `unusedStubs`, while generated placeholders do not. Whole-output references retain their normal lenient behavior, while invalid strict `$node.output.field` references fail the consuming node exactly as they do in a real run. See [Node Output References](/reference/variables/#node-output-references).

A workflow's declared `inputs:` resolve exactly as in a real run: omitted inputs take their declared `default:`, `--input name=value` binds a value (visible in the trace's resolved text), and a missing **required** input or an **undeclared** name fails at the invocation gate with the same errors a real run gives — before any trace output. With `--exec-code`, bash and script nodes also receive the same `INPUTS_<UPPER_SNAKE>` environment variables a real run delivers: run-level inputs, an included block's own inputs, and a script node's `with:` bindings.

By default, bash and script nodes are never executed. `--exec-code` is an explicit opt-in for trusted local workflow code and is the only dry-run mode that can cause code-level side effects. An executed node that declares `output_format` is certified exactly as in a real run: stdout must be one JSON document that satisfies the schema, and downstream `$node.output.field` references read that certified value. Executed nodes receive `$ARTIFACTS_DIR` and `$STATE_DIR` under an ephemeral per-simulation directory in `~/.archon/temp/` (honoring `ARCHON_HOME`), created before the first executed node and removed when the simulation ends — a dry run writes nothing inside the repository, and a simulation that executes nothing creates no directory at all. An attention wait pauses the simulation where a real run would pause, unless the stub file gives that wait node a stub; the stub then stands in for its answer and must match the form's output, for example `review: { decision: approve, text: "" }`. Deprecated `approval:` nodes auto-complete unless `--pause-at-gates` is set. Runtime `workflow:` sub-runs are reported as unsupported instead of being launched. Dry-run is incompatible with lifecycle and isolation flags such as `--branch`, `--no-worktree`, `--folder`, `--container`, `--resume`, and `--detach`.

The ordered trace records each node as completed, stubbed, skipped, failed, or paused, including its reason, resolved text, and safe output. The result reports two independent facts. `outcome` is the simulation execution result (`completed`, `failed`, `paused`, or `cancelled`) and still controls the CLI exit code. `authoredOutcome` is `succeeded`, `failed`, or `null`, derived only when the workflow declares `returns:` plus a boolean `outcome_field:` and the fixture supplies that structured result. Generated placeholders from `--default-stubs` or an unstubbed `all_done` node do not invent an authored verdict. A completed simulation can therefore report a failed authored outcome. Human output labels these as `Simulation outcome:` and `Authored outcome:`; simulation JSON always includes both fields. `--stubs-init --json` does not run a simulation and instead returns a scaffold acknowledgement with `workflow`, `stubsPath`, and `nodeCount`.

Every node that takes an AI turn also reports **which provider and model it will run on, and where each value came from** — the same resolution the executor performs, not a second implementation of it. This is how you answer "what will this node actually run on" for a workflow that composes others, since a composed workflow runs with the configuration its own file declares:

```text
STUBBED   review__scope (prompt)
  runs on: codex (node) / gpt-5.6-sol (node) [from review-block]
  effort: high (node)
```

The origin in parentheses is one of `node`, `model ref` (a tier keyword or `@alias`), `workflow`, `assistant config`, or `default assistant`. `[from <name>]` names the workflow file a composed node was authored in. A node whose declared `provider:` disagrees with the provider its `model:` ref resolves to also reports the warning a real run would emit. `--json` carries the same values under each trace entry's `resolution` object.

This validates deterministic engine wiring; it does not validate model reasoning. It adds no workflow-YAML language surface: YAML coordinates, code computes, and agents judge.

**Default (no flags):**
- Creates worktree with auto-generated branch (`archon/task-<workflow>-<timestamp>`)
- Auto-registers codebase if in a git repo
- Reads `.archon/config.yaml` from the launch checkout, not the new worktree, so uncommitted and gitignored config applies

**With `--branch`:**
- Creates/reuses worktree at `~/.archon/workspaces/<owner>/<repo>/worktrees/<branch>/`
- Reuses existing worktree if healthy
- If the branch already exists but no worktree holds it, checks it out at its current head: the local branch, or `<remote>/<branch>` when that contains the local branch (the local branch is fast-forwarded to it). It never resets an existing branch to the base.
- Fails before any work if the local branch and `<remote>/<branch>` have diverged. Reconcile them, or delete the local branch to start from the remote one.
- To start fresh from the base, delete the branch (`git branch -D <name>`, and the remote branch if there is one) or choose another name
- `--from` with a branch that already exists, locally or on the remote, is rejected

**With `--no-worktree`:**
- Runs in target directory directly (no isolation)
- Mutually exclusive with `--branch`, `--from`, and `--base`

#### Base branch precedence

"Base branch" means two things, and by default one flag sets both: the **cut-from**
(what `git worktree add` branches off) and the **PR target** (`$BASE_BRANCH`, which
bash nodes pass to `gh pr create --base`). Four sources can supply it, highest first:

| Precedence | Source | Scope |
|------------|--------|-------|
| 1 | `--base <branch>` | one dispatch |
| 2 | `worktree.baseBranch` in `.archon/config.yaml` | the repo |
| 3 | The registered codebase's stored default branch | the repo |
| 4 | Live symbolic HEAD advertised by the selected remote | the repo |

Levels 2 and 3 are project choices; level 4 is resolved live using `worktree.remote`
or the automatically selected remote. Use `--base` for a different base on one dispatch. New registrations store a branch only when explicitly chosen with `--base-branch`.
An unset project branch follows the remote's live HEAD advertisement, including renames;
resolution requires a reachable remote with a known symbolic HEAD and never guesses `main`.
Existing stored branches remain explicit, including values older versions detected from a
local checkout. See [Troubleshooting](/reference/troubleshooting/) to clear a stale choice.

`--base` is the per-dispatch level, which is what makes parallel multi-base
dispatch (epic slices, A/B variants) config-free.

**Scope: the dispatched run only.** A `workflow:` node with `isolation: worktree`
creates a worktree for its child run, and that worktree is cut using levels 2--4
only -- `--base` and `--from` do **not** propagate to sub-run children. A parent
dispatched with `--base release/2.0` still branches its isolated children off the
repo's configured base. See [Choosing the child's
checkout](/guides/authoring-workflows/#choosing-the-childs-checkout-with-isolation).

**Driving cut-from and PR target separately.** `--from` overrides only the
cut-from, so pairing the two flags splits them:

```bash
# Branch off release/2.0, but open the PR against dev
archon workflow run implement --from origin/release/2.0 --base dev "Backport the fix"
```

`--from` is handed to `git worktree add` verbatim, so a remote ref such as
`origin/release/2.0` works. Note the sync-before-create step refreshes the
`--base` branch, not the `--from` start point -- pass a remote ref when the local
copy of the start point may be stale.

**Where `--base` is rejected** (rather than half-applied): with `--no-worktree`,
against a [folder project](/getting-started/concepts/#folder-projects),
and against a workflow pinning `worktree.enabled: false`. None of these create a
worktree, so the flag could only move the PR target -- which would report a base
no worktree was ever cut from.

**When an existing worktree is adopted** -- `--branch` naming a healthy worktree,
or `--resume` continuing a prior run -- the cut-from is already fixed, so `--base`
changes only the PR target. Archon warns in both cases.

**A continuation keeps the base it started with.** A run records its resolved
`$BASE_BRANCH` when it starts, and every continuation of it -- `--resume`, an approved
gate, or the automatic resume of a parent whose sub-run gate was approved -- reports that
value rather than re-running levels 2--4 against whatever config and git say later.
Passing `--base` again still retargets the PR, as above. A run started before Archon
recorded this re-resolves through levels 2--4 and logs
`workflow.dispatch_not_recorded_resolving_live` when it does.

#### Continuing an existing estate

Use structured continuation whenever a workflow must work on an existing branch or pull request. If you have the prior run id, `--adopt <run-id>` is the authoritative form. Archon reuses the prior worktree as-is. If the prior worktree is gone, Archon reuses a same-repository checkout already holding that branch, or creates one on the exact local branch. It does not fetch, reset, or synchronize the branch; update it first if the remote advanced.

Every node in the new run uses that selected checkout, including bash/script delivery assertions. A branch name written only in the message is model context; it does not move engine-owned nodes to another checkout.

**Name Matching:**

Workflow names are resolved using a 4-tier fallback hierarchy. This applies consistently across the CLI and all chat platforms (Slack, Telegram, Web, GitHub, Discord):
1. **Exact match** - `archon-investigate` matches `archon-investigate`
2. **Case-insensitive** - `Archon-Investigate` matches `archon-investigate`
3. **Suffix match** - `investigate` matches `archon-investigate` (looks for `-investigate` suffix)
4. **Substring match** - `vestig` matches `archon-investigate`

If multiple workflows match at the same tier, an error lists the candidates:
```
Ambiguous workflow 'review'. Did you mean:
  - archon-review
  - custom-review
```

### `workflow status`

Show **active** workflow runs (running and paused) for the current project. The project is resolved from `cwd` through the registered checkout, including linked git worktrees. Use `--all` for install-wide active runs. For full history (all statuses), use `workflow runs`.

```bash
archon workflow status
archon workflow status --json
archon workflow status --all      # active runs across all projects
archon workflow status --verbose   # add a per-node summary for each run
archon workflow status --json --verbose
```

If `cwd` is an unregistered Git checkout, the command falls back to install-wide active runs and says so. Every successful JSON result carries `scopeFallback`: `true` for that fallback and `false` for a resolved project or explicit `--all` request. A registry lookup failure fails the command instead of returning install-wide runs under the fallback label. A registered folder project scopes normally; an unregistered non-repository directory is rejected with `Not in a git repository` before status lookup.

The normal human and JSON views include every active node without fetching each run's event
history. In JSON, `active_nodes` is the ordered list of unresolved node starts: `node_started` adds
an identifier and `node_suspended` keeps it active, while `node_completed`, `node_failed`, `node_skipped`, and
`node_skipped_prior_success` remove it. Retries re-add the node in their new start position. This
is node lifecycle state, not evidence that a process owner is alive.

Tool-call advisories appear in nonverbose text and in the JSON `attention` field. The run remains `running`; every overdue tool shows its node, provider, sanitized command/title, and elapsed/no-progress duration. The default is 30 minutes without reported progress; see [workflow attention settings](/reference/configuration/#workflow-attention-and-continuation-settings).

### `workflow runs`

List recent runs of **every** status (completed, failed, cancelled, running, paused) for the current project. The project is resolved from `cwd` the same way `workflow run` does. Complements `workflow status` (which is active-only).

```bash
archon workflow runs
archon workflow runs --json
archon workflow runs --json --verbose  # add per-node state and attention to each run
archon workflow runs --status failed   # filter to one status
archon workflow runs --limit 50        # cap rows (default 20)
archon workflow runs --all             # list across all projects (ignore cwd scope)
```

If `cwd` is not a registered project, the command falls back to a global list and says so — `--json` carries this as a `scopeFallback: true` field so a consuming agent never mistakes a global result for a project-scoped one.

The run-list JSON uses the same `active_nodes` contract as `workflow status`. The retained singular
fields are compatibility fields: `current_step_name` and `current_step_status` are populated only
when exactly one node is active, and are `null` for zero or concurrent active nodes. `total_steps`
is always `null` because lifecycle events do not own a truthful declared DAG total.

`--json --verbose` adds two fields to every run, including `--open` rows:

- `nodes`: every node the run declared, in declared order, followed by any node that ran
  without being declared (a loop body such as `candidates.step`) in the order it first ran. A
  declared node no event has reached is `{ "nodeId": "...", "state": "pending" }`; every other
  entry has the same shape as the `nodes` array of `workflow status`/`get --json --verbose`. A
  run with no recorded graph (it has not started executing, or an older Archon ran it) lists
  only the nodes that ran.
- `attention`: what the run needs from outside, if anything: `null`, or an object whose `kind`
  is `terminal`, `awaiting_response` (an approval or response gate), `attention` (a
  `wait: attention` waiting for its answer), `usage_limit` (node `nodeId` hit a provider
  usage limit, or a rate limit that outlasted its retries; the run continues by itself at `resumeAt`), `blocked_on_child` (the child run is the one to inspect), `blocked_on_children` (a fan-out
  waiting on the listed child runs), `stalled_tool_calls` (still-running
  work with overdue provider tools or reported subtasks), or `unreadable`.
  It is read from the run row alone.

The node events for every listed run are read in one query, so the cost does not grow with
one query per run.

The listing shows short 8-character run ids. Every `<run-id>` command below (`get`, `logs`, `wait`, `resume`, `cancel`, `abandon`, `approve`, `reject`) accepts these short ids when run from the project directory: a unique prefix resolves to the full id, an ambiguous prefix errors, and full ids keep working from any directory. Short ids from `--all` rows belonging to *other* projects can't be resolved — use the full id from `--json` for those.

### `workflow get`

Show detail for a single run by ID, regardless of status (unlike `status`, which is active-only). Use it to inspect both how execution progressed and, when declared, the workflow author's verdict. Exits non-zero when the run is not found.

```bash
archon workflow get <run-id>
archon workflow get <run-id> --json
archon workflow get <run-id> --verbose   # add the per-node summary
archon workflow get <run-id> --json --verbose
```

For a paused gate, human-readable output lists its declared decision IDs and optional
labels, with an exact `archon workflow respond <run-id> <decision> [text]` command
for each choice. JSON exposes these choices in `metadata.approval.decisions`.

Once answered, `metadata.approval` also records the surface the answer came through as
`answeredVia` (`cli`, `api`, `manage_run`, or a chat platform id such as `slack`) and the
time as `answeredAt`. The `approval_received` event carries the same surface as `via`, and
the `occurrence` it answered.
A reject that cancels the run leaves no resumable gate context, so on that path the
`approval_received` event (with its `via`, `occurrence` and timestamp) is the record.
These are audit facts. They never name who answered, and nothing routes on them.

`workflow status`, `workflow runs`, and `workflow get` report two independent facts:

- **Execution status** (`pending`, `running`, `paused`, `completed`, `failed`, or `cancelled`)
  controls terminality, resume and cancellation, filters, and CLI exit behavior.
- **Authored outcome** (`succeeded` or `failed`) is the workflow's declared verdict from
  `outcome_field`. Human output labels it separately when present. JSON always carries it as the
  nullable `outcome` field beside `status`.

The axes may disagree without either being rewritten: `completed` with authored outcome `failed`
means execution finished normally but the workflow judged the work unsuccessful; `failed` or
`paused` with authored outcome `succeeded` records a successful verdict before execution later
failed or paused. A null outcome means the workflow did not declare one, the selected node has not
authored it yet, or the run predates the field. In that case human output keeps its status-only
presentation. Foreground `workflow run` uses the same labels when an outcome exists, but its exit
code remains driven by execution success or failure.

Verbose JSON node summaries include `execution` for newly recorded work. It identifies the
invocation, attempt, provider, requested model and any model the provider reported. Inner retries
share an invocation; each retry has its own attempt. A new loop-group iteration creates new body
invocations. Resuming unfinished work keeps its invocation identity.

The run's starting checkout appears as `checkout_baseline` in JSON and as the `Start:` line
in human output. The engine observes it once, right after the run wins its execution claim
and before the first node, and never rewrites it — a resume keeps it. A Git observation gives
the commit, that commit's tree, whether the worktree was clean or dirty (with staged,
unstaged, and untracked counts), and, when Archon created the run's branch, the commit it was
cut from. `not_git` means the directory is not in a Git repository; `unavailable` means the
engine could not read it. `null` means not recorded: the run predates this field or never
started. Node execution records carry the same observation as `invocation.checkoutStart` and
`attempt.checkoutStart`, so you can see which commit each node started at.

Pressing Ctrl-C on a foreground run stops it without losing it. The process that owns the
run records why it stopped, and the run stays `failed`, which is the resumable status: it is
still found by `workflow run <name> --resume` and accepted by `workflow resume <run-id>`.
For a `failed` run, human output adds a `Stopped:` line naming the interrupt and the
signal, above the usual `Error:` line. JSON carries the same fact as `metadata.stop_reason`, an object with the
categorical `reason` and the `signal` that arrived. Resuming the run clears it, so a run
that resumed and then completed does not keep reporting an interrupt. Runs that stopped
before this field existed carry no `stop_reason`.

After recording why the run stopped, the owner ends the process tree of every `bash:` or
`script:` node still running, including an `until_bash` check, so a resume does not overlap the
interrupted attempt, apart from the escapes below. This applies to foreground `workflow run`, `workflow resume` and
`trigger execute`. A node counts as running until its output closes, so a background job that
still writes to the node's output ends with it. On macOS and Linux those subprocesses run in
their own session during a foreground run, so they have no controlling terminal, and a small
watcher process ends the node's process group if the owner dies before its teardown does (a
terminal hangup, a second Ctrl-C, `kill -9`); a run whose stop was never recorded stays
`running`. A process that starts its own session or process group (`setsid`, a daemon)
escapes this. On Windows the tree is ended from the node's root process, so a descendant whose
root already exited is not reached, and an owner killed before its teardown leaves the tree
running.

Each usage observation is either `{ source: "provider", value: ... }` or unavailable. Reasons
separate unsupported reporting, a supported value not reported, unknown capability, non-provider
work and invalid reported numbers. A reported zero stays zero. Historical nodes omit `execution`
when their rows lack these facts; they do not receive a guessed model or start time.

`timing.durationMs` is elapsed wall time, not active compute time. Resumed loop durations can
include time spent paused; bare approval retains the duration observed before the pause.
`execution.binding.sessionPreview` holds at most eight session-ID characters; the full id is
in the node's `sessionIds`, described below. JSONL
transcripts retain the names `node_start`, `node_complete` and `node_error`; their `execution`
metadata describes the same fact as the durable node event. Suspended nodes remain active and
appear as running until their gate or wait resolves.

Every `workflow get --json` shape includes `terminal_record`. The API detail endpoint,
`GET /api/workflows/runs/:runId`, exposes the same value as `run.terminal_record`.
The engine persists this record with the terminal status transition, including detached
runs and failures before a reporting node. It contains observed node states and skip
causes, the selected `returns:` value or its explicit unavailability, and an artifact
manifest. Historical runs without a record and active resumed runs report `null`.

An event-query failure exits non-zero with `error: "workflow_events_unavailable"` in
JSON; it does not masquerade as a missing historical record.

When the run holds proof that it created its worktree, the record has
`worktree_disposition`. `{ "disposition": "release" }` means the completed run's checkout
was clean, fully on a remote and unpublished, so the engine removes it after the
completion write, keeping the branch. The record is written before the removal, so a
removal that is refused because work appeared in the meantime, or that fails, leaves the
checkout and is logged. Otherwise it is
`{ "disposition": "retained", "reason": ... }` with an optional `detail`:

| Reason | Meaning |
|---|---|
| `resumable` | The run failed and can resume; `workflow abandon` or the stale sweep releases it |
| `cancelled` | The run was cancelled; `workflow abandon` releases it |
| `workspace_retained` | The originating platform keeps its workspaces |
| `in_use` | Another claimable run uses the checkout; `detail` names it |
| `uncommitted_changes` | The checkout has uncommitted changes |
| `unpushed_commits` | A commit on the HEAD or branch is on no remote; `detail` gives the count |
| `published_branch` | The configured remote has the branch; `isolation cleanup --merged` removes it after its PR merges |
| `check_failed` | The check could not run; `detail` gives the error |

The artifact manifest records paths, sizes, and typed metadata observed at termination.
It survives later file deletion, but does not preserve file contents or contain previews.
Its `limitations` array identifies missing roots, unreadable entries, invalid metadata,
and excluded links. Files may change during the scan, especially during cancellation:
this is an observation, not an atomic filesystem snapshot. The separate leave-behind
file listing reflects the filesystem when you query it.

`leave_behind.artifactFiles` lists the files a person or an agent would open, capped at
200 for display. Each directory contributes its own files before its subdirectories,
sorted by name, so a run's top-level reports lead the list. It excludes the engine's
own `$ARTIFACTS_DIR/.archon/` child — the typed-artifact listings and node-output
spills the engine writes for itself, which on a long run outnumber the reports.
Nothing leaves the list quietly: `leave_behind.artifactFilesOmitted` reports how many
engine-internal files were skipped (`internalFiles`), whether the display cap was
reached (`truncated`), and any directory the walk could not read (`unreadable`). Human
output prints the same facts under the file list. The console run page leaves out the
same child and nothing else, so both show a workflow's own dotfiles.

Human output includes `Transcript: <path>`. Every successful JSON shape includes the
same value as `transcript_path`, including verbose node summaries and raw events. A
historical run whose storage location can no longer be resolved remains inspectable and
reports `null` (human output says `(unavailable)`) instead of guessing a path from the
current directory.

For both commands, `--json --verbose` adds a `nodes` array. Nodes are ordered by the
first appearance of each node in the deterministically ordered event stream. Every
entry includes `nodeId` and `state`; nodes with a start event include the original ISO
`startedAt`, and terminal nodes with both start and end events include `durationMs`.
Completed nodes may include an `outputPreview`, truncated after 200 characters with
ASCII `...`, while failed nodes include `error` (or `Unknown error` when none was
recorded). A node a resume reset for re-running (`always_run`, or a stale cached
success) reads `pending` until its new attempt starts, matching `terminal_record.nodes`.
Nodes whose provider reported a session include `sessionIds`: the full
session id of each attempt and loop iteration, in order. Human `--verbose` output prints
them on a `Session:` or `Sessions:` line, so you can continue a node's conversation in
the provider's own tool, for example with `claude --resume <id>`. Claude Code finds a
session by the directory it ran in, so run it from the node's working directory: the run's
worktree when the run used one.

Add `--events` to `--json --verbose` to return raw `events` rows instead of `nodes` for
debugging. Raw events are not the recommended integration surface.

### `workflow logs`

Print the run's existing JSONL transcript, or follow it as rows are appended:

```bash
archon workflow logs <run-id>
archon workflow logs <run-id> --follow
archon workflow logs <run-id> --follow --format text
```

Without `--follow`, the command copies the snapshot that exists at invocation time to
stdout and exits. With `--follow`, it announces the resolved local path on stderr, waits
for a live run's file to appear, and streams appended bytes until the run becomes
`completed`, `failed`, or `cancelled`. A paused run is still live: the follower stays
attached across approval gates and resumes, reading the same file. Each resume appends one
`workflow_resume` row (the run's first execution writes `workflow_start`), and each gate
approval or rejection appends a `gate_decision` row with the gate's `step`, the
`decision`, and the operator's comment or rejection reason in `content`.

Stdout is the transcript's exact JSONL, with no log messages or wrapper document. Each
line is one persisted event and fields may be added over time, so consumers should parse
the fields they need and tolerate others.

Provider activity (agent text, thinking, tool calls with their input and output,
warnings, MCP status, compaction, subtasks, hooks and state) is written as
`provider_event` lines: the line frame (`type`, `workflow_id`, `ts`, `step`) plus the
engine envelope `attemptId`, `seq`, `observedAt` and `event`, where `event` is the
provider's object unchanged. `seq` counts one node attempt's events from 0. Transcripts
written before this line type have `assistant` lines (`content`) and `tool` lines
(`tool_name`, `tool_input`) instead, with no step, call id or output. `--json` is invalid because the output is
already JSONL and a live stream cannot satisfy the CLI's one-document JSON contract;
`--events` is also limited to `workflow status/get`.

`--format text` reads the same transcript, with the same snapshot and follow behaviour,
and prints it for a person instead: workflow start, resume, completion and failure; node
start, completion with its duration, failure and skip with its cause; gate waits and
decisions; and each subprocess's retained output with its exit code. Each node is named
by its node id, and a command node's start line also names its command.

A node's provider activity is indented beneath it: agent text in full, thinking on one
line, one line per tool call, and each call's outcome naming the tool with its exit code
and the last lines of its output. Warnings, MCP servers that failed, need auth or are
disabled, compaction, subtask starts and ends, failed or cancelled hooks, and waits on
the user get one line each; progress-only events (running subtasks, started or
successful hooks, connected or pending MCP servers) are left out.
`provider_event` lines record their node, so when parallel nodes interleave a `[node]`
line marks each switch. The older `assistant` and `tool` lines record no node and are
indented without a label. Rows the text view does not render (watchdog renewals,
historical rows, row or event types newer than the CLI, or a line that is not JSON) are
left out; `--format jsonl`, the default, keeps every row.

A missing or empty snapshot exits `1`; for a live run the diagnostic points to
`--follow`. Follow mode waits while the run is live, performs a final read after a
terminal status, and exits `1` if a terminal run never produced content or if the file
shrinks. Stopping the follower only stops the reader. It never resumes, cancels, or
otherwise changes the run.

Transcripts can contain user prompts, tool inputs, and retained subprocess output. Treat
access to the local file as access to the run's input and execution data.

### `workflow wait`

Block until a run reports attention: it finished, awaits a gate response or an answer
to an attention wait, has an overdue live tool call, or lost its execution owner while still
non-terminal. Then print what it needs. This is the intended
partner of `--detach --json`: take the `runId` from the launch ack and wait on it,
instead of polling `workflow get` in a loop.

The answer a pause is waiting for does not have to come from a person. An
orchestrating agent can supply it with `workflow respond` or `workflow answer` just as a
reviewer can; the engine only reports that one is owed, and who answers is the waiting
host's business. Pass the reported `attention.occurrence` back with `--occurrence` on
`approve`, `reject`, `respond` or `answer`. The answer is then refused, naming the current
occurrence, if the run has moved to a different pause in the meantime.
For an attention wait, the output carries `attention.kind: "attention"`, the authored
message, the paused node id, the `form` and the `options` an answer may choose from.
Answer it with [`workflow answer`](#workflow-answer); use `archon workflow abandon
<run-id>` if the run should not continue.

An overdue tool returns `result: "attention"` with `attention.kind: "stalled_tool_calls"`, `status: "running"`, the affected `runId`, and `calls` naming nodes, providers, tools, sanitized titles, timestamps, `elapsedMs`, and `stalledForMs`. Exit `0` means attention was found; it does not mean the run completed. Inspect with `workflow logs` or explicitly cancel if appropriate. This advisory grants no approve or resume action. Reattaching returns it immediately while active. When a parent waits on a child, the advisory names the child's run id.

```bash
archon workflow wait <run-id>
archon workflow wait <run-id> --json
archon workflow wait <run-id> --json --timeout 900
```

There is no default timeout. A run reports when it is done, and a wait that ended on
its own clock would be answering a question only the run can answer. `--timeout
<seconds>` is there for a host that needs an upper bound anyway.

**Exit codes describe the command, not the run.**

| Exit | Meaning |
| --- | --- |
| `0` | The run said something — it finished (`completed`, `failed`, or `cancelled`), is waiting for a response, needs attention, has an overdue tool call while still running, or lost its execution owner. The status is data on stdout. |
| `3` | The timeout passed with the run still live. The `--json` payload carries `observedStatus`. |
| `1` | The wait itself failed — unknown run id, database unreachable, or output that could not be delivered. |

A `failed` or `cancelled` run is still exit `0`: mapping run state onto the process
exit code would make a legitimately cancelled run look like a broken command.

Owner loss is also exit `0`: the wait obtained a typed answer, but Archon did not
invent a terminal status or change the run. Its JSON result is `owner_lost` with the
persisted non-terminal `observedStatus` and no `attention` or terminal `status` field.
A slow or incomplete owner handshake is not evidence of loss; the wait retries until
it can attach, observe attention, or reach an explicit timeout.
After verifying that the run's work has stopped, release its persisted state with
`archon workflow abandon <run-id>`.

The owner endpoint is local to one host and, on POSIX, one user. `owner_lost` is
reported only when the run's recorded execution owner is on this host and user and its
endpoint is gone. When the owner ran on another host or as another user, as with a
PostgreSQL database shared by several machines, or the run records no owner, the wait
cannot tell whether the owner is alive. It says so on its `waiting` line and keeps
watching the run's stored state until it changes or `--timeout` passes. `abandon` prints
the host the run recorded and says when it is not this one.

`--json` emits one document. On a wake it carries the attention value:

```json
{ "ok": true, "action": "wait", "runId": "…", "result": "attention",
  "attention": { "kind": "terminal", "runId": "…", "status": "completed", "at": "…" } }
```

`attention.kind` is one of:

- `terminal` — the run finished; `status` is `completed`, `failed`, or `cancelled`.
- `awaiting_response` — a gate is waiting for a decision. `respondTo` names the run and
  node where that response is recorded. **That run is not always the one you waited on**:
  a parent blocked on a `workflow:` sub-run wakes when the chain below it reaches a gate,
  and `respondTo.runId` is the child you answer. A parent blocked on a child that is
  merely still running wakes nobody.
- `attention` — a `wait: attention` needs an answer to `message`, in its `form`
  (`message`, `approval`, `select` or `multi_select`). `options` lists the ids an answer may
  choose (none for `message`, `approve` and `reject` for `approval`), and `occurrence`
  identifies this pause. A wait paused before attention waits took answers reports a
  `legacy:<node>` occurrence and the `message` form; it takes no answer and continues
  with `workflow resume <run-id>`.
- `usage_limit` — node `nodeId` hit a provider usage limit, or a rate limit that outlasted
  its retries. Nothing needs an answer: the run continues by itself at `resumeAt`. Resume
  it to try sooner, or cancel or abandon it to stop it.
- `unreadable` — the run is parked but cannot describe itself (corrupt gate metadata, a
  gate type this build does not know, a sub-run pointer with no row). `detail` says which.
  It also reports `reason: source_release_not_continued` for a container paused by a
  host-local captured-source read failure. Resume that container by hand on a host
  that can read its source; see [Attention queue](/guides/attention-queue/).

Two pauses deliberately do **not** wake a waiter, because neither is owed a response: a
gate or attention wait that has already been answered and is awaiting continuation, and a
[`wait:` node](/guides/authoring-workflows/) whose timer or event has not fired.

Once the wait is watching, it says so once on **stderr** — one plain sentence, or the
same envelope with `"result": "waiting"` and the status it attached on under `--json`:

```json
{ "ok": true, "action": "wait", "runId": "…", "result": "waiting", "observedStatus": "running" }
```

When the owner cannot be checked from this host, the line adds `"ownerLiveness":
"unknown"` and, if the run recorded one, the owner it names:

```json
{ "ok": true, "action": "wait", "runId": "…", "result": "waiting", "observedStatus": "running",
  "ownerLiveness": "unknown", "recordedOwner": { "host": "build-2", "pid": 4242, "uid": 501 } }
```

Until that line the command is completely silent, so a host cannot tell a watch that
has begun from one still resolving the id. It is on stderr precisely so stdout keeps
carrying exactly one document. A run that already has something to say answers on the
first read, and never prints it.

The run id may be the short prefix printed by `workflow runs`. Once the wait returns,
inspect the run normally with `workflow get <run-id>`.

### `workflow attention`

List, count, stream or read the [attention queue](/guides/attention-queue/): every paused
run that needs an answer or inspection, plus observable provider usage- and rate-limit waits.
Branch on `kind`: `attention` and `awaiting_response` accept answers, `usage_limit`
continues automatically after reset, and `unreadable` needs inspection. Read-only.

```bash
archon workflow attention --json                                   # this project
archon workflow attention --json --all                             # the whole install
archon workflow attention --json --project acme/web --label ci=other
archon workflow attention --json --sort label:urgency
archon workflow attention --json --all --count-by label:class
archon workflow attention <run-id> --json                          # one item
archon workflow attention --watch --json --all                     # every change, as lines
archon workflow attention --watch --json --after <cursor>          # resume a stream
```

- **Scope.** The default is the current project, resolved from the working directory as
  [`state`](#state) resolves it. `--project` takes a codebase id, a codebase name, or a
  project key such as `_cwd/scratch`; `--all` covers the install. A run is in scope when
  its codebase id matches, when both sides have one, and otherwise when its project key
  matches, so a run paused before its codebase was renamed is still listed under the new
  name.
- **`--label k=v`** is an exact match; repeat it and every filter must match.
- **`--sort`** is `age` (oldest pause first, the default) or `label:<key>` (by that
  label's value as a plain string; items without it last).
- **`--count-by`** is `project` or `label:<key>`, and replaces the list with counts.

`--json` prints `{ ok, action: "attention", scope, items }`, or `counts` in place of
`items` with `--count-by`. With a run id it prints `{ ok, action, runId, item }`, where
`item` is null when there is no queue item; a parent blocked on sub-runs resolves to the
item of the child that holds the pause, or to its own `unreadable` item when its sub-run
tree cannot be read. The list holds the same items. An unknown run id prints `error: "not_found"` and
exits `1`. The item shape is described in the [guide](/guides/attention-queue/#queue-items).

**`--watch`** streams the queue instead of reading it once: one `attention` line per open
item in scope, then `ready`, then a line for every change any process commits, as
`attention`, `resolved`, `unmatched`, `terminal`, `resumed`, `owner_lost` and idle `bookmark` lines.
With `--json` each line is one compact JSON object; without it, a short sentence per
change. Scope and `--label` work as for the list; `--sort`, `--count-by` and a run id are
refused. **`--after <cursor>`** takes the `cursor` of an earlier line and, after the
snapshot, replays every change since it. The line shapes and the consumer contract are in
the [guide](/guides/attention-queue/#stream-the-queue). Exit codes: `0` when a signal or a
closed stdout stops the stream (a closed stdout is noticed at the next line), `1` when it
cannot start, `4` when the store cannot read the stream's cursor, an `--after` cursor this
install did not issue or the stream's own position (with `--json`, the last line is
`{"type":"error","error":"cursor_invalid"}`; rerun without `--after`).

HTTP: `GET /api/runs/attention` takes `project`, `all`, `label` (repeated), `sort` and
`countBy`, and returns `items` beside the console's `runs`, or `{ counts }`. A server has
no working directory, so without `project` it covers the whole install.
`GET /api/runs/<run-id>/attention` reads one item.

### `workflow attention label`

Set or remove labels on the attention wait a run is paused at, without answering it.

```bash
archon workflow attention label <run-id> --occurrence <id> --set class=ci-other --set urgency=high --json
archon workflow attention label <run-id> --occurrence <id> --unset urgency --json
```

A set key replaces the same key; every other label stays. The write is refused, naming
the current occurrence, when the run has moved to another pause, and refused for a gate or
an answered wait: only `wait: attention` pauses carry labels. Each write bumps the item's
`revision` and commits an `attention_labeled` event, in the same transaction, carrying the
occurrence, the keys set and unset, the surface as `via`, and the new revision. Who may
write is the same as who may answer.

`--json` prints `{ ok, action: "attention_label", runId, occurrence, revision, labels }`.
HTTP: `POST /api/runs/<run-id>/attention/labels` with `{ occurrence, set?, unset? }`. Chat
agents use the `manage_run` action `label`.

### `workflow resume`

Resume a failed or paused workflow run. Re-executes the workflow, automatically skipping nodes that completed in the prior run.

```bash
archon workflow resume <run-id>
archon workflow resume <run-id> --json   # validate + ack only; does NOT re-execute inline
```

In `--json` mode the command is a non-blocking control-plane ack: it validates the run is resumable and reports its state but does **not** re-execute inline (execution streams output to stdout, which would corrupt the JSON). To actually drive a resumable run to completion, use the blocking form or `workflow resume <run-id> --detach`.

When you already hold a run id, prefer that exact-id form. `workflow run <name> --resume --detach` selects the newest resumable run of that workflow **in the current checkout**, which is a different question — from another worktree it correctly finds nothing, and in a checkout with several historical runs it expresses less than the id you already have. Keep the name form for the case you actually mean: "the latest failed run of this workflow, here."

Adding `--detach` **inverts** that: the child is re-invoked without `--json`, so it takes the inline path and does re-execute the run — just outside your shell. The ack carries `continues: true` to say so. Detached resume acknowledges only after the engine accepts it and clears the old pause. An immediate `workflow wait` can therefore report a genuine new pause, including repeated attention at the same node, but never the cleared pause. See [Detached control verbs](#detached-control-verbs).

### `workflow wake`

Without running a server, wake due durable time waits, event deadlines, signaled events, scheduled quota resumes, answered gates, and runs released because their host could not read their frozen source:

```bash
archon workflow wake --json
archon workflow wake --watch --json
```

A pass scans up to 25 due continuations across the configured install database, regardless of the current directory. It awaits each admitted execution segment. A segment can complete, fail, or pause at another wait or gate; later occurrences belong to another pass. A run released for an unreadable source is due 60 seconds after its release and retries the failed source read. This can include restoring the captured source before any node starts. Concurrent CLI and server hosts use the engine's existing occurrence claim, so only one executes a continuation.

In a long-lived `workflow wake --watch` or server process, if a run's frozen source is missing, unreadable, or fails verification (invalid manifest or source record, digest or resolution settings mismatch, or missing captured workflow), automatic continuation stops attempting that occurrence on this host after three failures and logs one warning per run per process naming the run, the path when available, and the operator actions. Resume it on the owning host with `archon workflow resume <run-id>`, or abandon it with `archon workflow abandon <run-id>`. The run's status stays unchanged, and another host can still resume it. The bound lives in process memory and resets on restart. Temporary I/O failures, such as exhausted file descriptors, and other retryable admission failures keep the 60-second retry. A successful manual resume can reach a new wait, gate, or quota occurrence; that occurrence can wake in the same process. One-shot `workflow wake` invocations start a fresh process each time and cannot accumulate this bound. Installed wake schedules run `workflow wake --watch` so repeated passes share the bound.

`--watch` repeats serial passes, sleeping five seconds between completed passes. Its JSON output is JSONL: one document per pass with `ok`, `action`, `accepted`, and per-run `outcomes`. Outcomes distinguish admission from settlement (`completed`, `paused`, or `failed`). Without `--json`, the command prints a summary and run-specific outcomes.

Exit status is 0 for an empty batch, a concurrent claim loser, a completed segment, a legitimate pause, or a run whose resource is full, reported with the run or request that holds it. A paused run stays paused and keeps its place (`waiting_for_capacity`); a later pass continues it once capacity frees. A failed run with a scheduled quota resume stays failed and keeps no place (`resource_busy`, `refusal: "failed"`): it is retried after the 60-second deferral and admitted only after waiting runs and queued requests. Other refusals, execution failures, and deferral failures return 1. Watch continues after failures and returns 1 on shutdown if any pass failed. Retryable prerequisite failures defer the exact occurrence for 60 seconds.

The CLI preserves the run's recorded source, inputs, configuration, user, conversation, and working path. It accepts local CLI/API conversations. Container execution, missing paths or captured source, and external platform origins are refused with the run ID and reason. Use manual `workflow resume` for a container, or the server hosting the external conversation. A run stays parked if no waker runs; age never marks it failed.

SIGINT/SIGTERM stops scheduling new passes and drains admitted segments and owner cleanup. Shutdown can wait for a long executing segment. An abrupt kill retains the existing ambiguous-owner behavior; it does not provide automatic recovery of a running segment. On Windows, SIGTERM ends the process immediately, so it behaves as an abrupt kill rather than a drain.

#### Wake timers

On macOS, install a per-install LaunchAgent:

```bash
archon workflow wake schedule install --interval 5 --json
archon workflow wake schedule remove --json
```

The launch interval is a positive integer in seconds, defaulting to 5. It starts the job again if it has exited; the watch process polls every five seconds. The job invokes `workflow wake --watch --json` as one long-lived process, runs at load, and records an absolute executable, working directory, and `ARCHON_HOME`. Reinstalling identical configuration verifies that its launchd job is registered and fails if registration cannot be verified. Remove then install to change its interval or executable, or to upgrade an existing one-shot wake schedule to watch mode. Native installation is macOS-only; see [workflow trigger scheduling](/guides/workflow-triggers/#macos-scheduling-limits) for login, sleep, environment, and scheduler caveats.

On Linux, use a long-lived systemd user service with `ExecStart=/absolute/path/to/archon workflow wake --watch --json`, `Environment=ARCHON_HOME=/absolute/path/to/.archon`, and `Restart=on-failure`. Enable it with `systemctl --user enable --now <name>.service`.

On Windows Task Scheduler, select the absolute Archon executable, set arguments to `workflow wake --watch --json`, and start the task at login. Disable the task's execution time limit and select “Do not start a new instance” if it is already running. Set `ARCHON_HOME` in the task's environment or invoke a wrapper that sets it, and use that home as the working directory.

### `workflow signal`

Signal an exact event wait occurrence and attempt its execution immediately:

```bash
archon workflow signal <full-run-id> --event checks.complete \
  --occurrence '<metadata.wait.pauseId>' --data '{"conclusion":"success"}' --json
```

Use the event name and `metadata.wait.pauseId` from the run's current state. The full persistent run ID is required; this command does not resolve a short prefix against the invocation directory. `--data` accepts any JSON value and is validated before mutation. Duplicate, expired, mismatched, and non-event signals are rejected without resuming. `--resume-at <ISO timestamp>` remains accepted for compatibility and is required when `--occurrence` is omitted. Without an occurrence, a stale signal cannot be distinguished from a later wait with the same event and deadline. When both selectors are supplied, both must match. Persisted waits without a `pauseId` can still be signaled with `--resume-at`.

The result reports `signaled` separately from admission and settlement. If the signal succeeds but execution is refused or fails, it remains durable, the command returns 1, and a later `workflow wake` can retry after the problem is resolved. A concurrent resume winner after a successful signal is a normal outcome.

### `workflow cancel`

Stop a running workflow. Every cancel surface (this command, `/workflow cancel` in
chat, the Web UI's Cancel action, `POST /api/workflows/runs/{runId}/cancel`, the Slack
Cancel button, and the chat agent's `manage_run` tool) does the same thing, decided by
who owns the run:

- **The process handling the cancel executes the run** (a run the server started, cancelled
  from that server): the run is marked `cancelled` and its executor stops at its next
  status check.
- **Another live process owns it** (a `--detach` run or a trigger-started run): cancel
  contacts that process, terminates its host process tree, confirms termination, and
  only then records the run as `cancelled`.
- **No owner answers, or the owner cannot be stopped:** cancel fails and leaves the run
  unchanged. When no owner answers, it prints what the run recorded (host, pid, last
  activity) and points you at `abandon`. A foreground `archon workflow run` or a run
  executing in another Archon server answers but cannot be stopped from here; interrupt
  the foreground command, or cancel the run on the server that executes it.

```bash
archon workflow cancel <run-id>
archon workflow cancel <run-id> --json
```

When cancel refuses, it exits nonzero. Under `--json` it also prints `{ "ok": false, "error": ... }`.
`workflow resume` and `workflow abandon` follow the same contract, so a script or bash node can
gate on the exit status.

Refusing is deliberate: `cancelled` releases the run's worktree lock and resource slot,
and a database transition cannot prove that host work stopped. After verifying that the
owner process is gone, use `workflow abandon <run-id>`.

A `workflow:` sub-run normally executes inside its root run's process, and the root's row
keeps the worktree lock and resource slot. When no owner answers for the sub-run itself
but one answers for its root, cancel records `cancelled` and the root's executor stops the
sub-run at its next status check. When no owner answers for the root either, cancel
refuses and points at abandon, as for any other run. A sub-run resumed on its own (after a
durable wait or a scheduled resume) has its own owner, and cancel stops that owner like
any other run's. Only a `running` run can be
cancelled; abandon a paused or failed run instead.

After termination is confirmed, `cancel` records cancellation through the same run-tree
operation as `abandon`. Cancelling a parent therefore cancels every non-terminal
descendant and can report the same cascade failures or blocked parent described below.

For a detached container run, cancel must also confirm container teardown after stopping
the owner and before recording cancellation. If that teardown fails, cancel fails and
leaves the run's state unchanged, even though the owner process has stopped. With
`--json`, this returns `ok: false` and an error, without `cleanupWarnings`.

Once cancellation is recorded, further managed container reclamation is best-effort. If it fails,
the run stays `cancelled`, but container resources may remain allocated. Every cancel
surface reports a warning identifying the run and environment; inspect the managed
containers before retrying cleanup. Successful `--json` responses include an optional
`cleanupWarnings` array of warning strings when reclamation fails; the field is omitted
when there are no cleanup warnings. A cleanup warning does not change `ok: true`.

### `workflow abandon`

Discard a workflow run by marking it `cancelled`. `cancelled` releases the run's
worktree lock and resource slot, so abandon first asks the run's live-owner endpoint on
this host:

- **An owner answers:** abandon stops it through the same path as `workflow cancel`
  (proves the owner, terminates its process tree, waits), then records `cancelled`.
- **No owner answers:** abandon records `cancelled` and prints what the run recorded:
  the host and pid of the process that last executed it, and its last activity. If that
  host is not this one, or the owner ran as another user on this host, it says so:
  abandon can only reach owners on its own host running as its own user. Nothing
  decides the run is dead from its age or pid.
- **An owner answers but cannot be stopped:** abandon fails with the reason and leaves
  the run unchanged. This includes a foreground `archon workflow run` (interrupt it in
  its terminal) and a run executing inside a live Archon server. Cancel a server-executed run
  from that server (its Web UI Cancel action, `POST /api/workflows/runs/{runId}/cancel`,
  or `/workflow cancel <run-id>` in its chat): the server executes the run, so it
  cancels it at the executor's next status check.

```bash
archon workflow abandon <run-id>
archon workflow abandon <run-id> --json
```

`--json` adds an `owner` object: `{ "outcome": "stopped", "pid": … }`, or
`{ "outcome": "no_owner_answered", "thisHost", "recordedHost", "recordedPid",
"recordedUid", "lastActivityAt" }`.

Abandon also removes the worktree Archon created for this run, and for each cancelled
`workflow:` sub-run. Abandon is final for that checkout: removal is forced, so
uncommitted, untracked, and ignored files (including copied `.env` files) and
initialized submodules are deleted with it. The branch is kept, so committed work,
pushed or not, stays recoverable. The result lists each removed worktree and its
branch (`releasedWorktrees` in `--json`), and the isolation record is marked `destroyed`.

Only a checkout this run created is removed. Adopted, reused, inherited, and legacy
checkouts without that creation proof stay in place, with a reason. A checkout another
resumable or live run uses, one locked with `git worktree lock`, one whose owner runs on
another host or as another user, or one under a descendant that could not be accounted
for is also kept.

The run is cancelled even when removal fails; the failure is reported as a cleanup
warning. Retry `workflow abandon <run-id>` on that cancelled run to finish removal
without repeating cancellation.

Managed container reclamation is best-effort here too. A failure leaves the run
`cancelled` and reports a warning on every abandon surface because container resources
may remain allocated. Inspect the managed containers before retrying cleanup.
Successful `--json` responses include an optional `cleanupWarnings` array of warning
strings when reclamation fails; the field is omitted when there are no cleanup
warnings. A cleanup warning does not change `ok: true`.

**Sub-run trees (#2121 Phase 2):** abandoning a parent that spawned `workflow:` sub-runs cascade-cancels every non-terminal descendant (children and grandchildren; already-terminal runs are left alone). These are database transitions, not process termination; an in-flight host command can continue until it returns. If part of the tree could not be reached, the command reports the count so you know descendants may still be alive. Conversely, abandoning a **child** that its parent is paused-and-blocked on strands that parent (nothing re-fires the auto-resume hook); the command surfaces the blocked parent's run id so you can `resume` it (which fails the sub-run node cleanly) or abandon it too.

### `workflow respond`

Resolve a paused gate with `archon workflow respond <run-id> <decision> [text]`.
When the gate authors `approval.decisions`, only those declared ids are accepted,
including for `approve` and `reject`. An undeclared decision is refused with the
allowed ids, and the run stays paused. Without authored decisions, use `approve`
or `reject`.

### `workflow answer`

Answer a paused [attention wait](/guides/authoring-workflows/#attention-waits) in the form
it declares. `--occurrence` is required: pass the `occurrence` that `workflow wait --json`
or `workflow get` reports, and an answer meant for an earlier pause is refused, naming
the current one.

```bash
archon workflow answer <run-id> --occurrence <id> --text "Rotated the key"           # message
archon workflow answer <run-id> --occurrence <id> --choose reject --text "Not yet"   # approval
archon workflow answer <run-id> --occurrence <id> --choose use-dev                   # select
archon workflow answer <run-id> --occurrence <id> --choose lint --choose test --json # multi_select
```

`--choose` names a declared option id; repeat it for a `multi_select`, and omit it for a
`message`. `--text` is free text recorded with the answer. An undeclared option, the wrong
number of options, or an option on a `message` wait is refused and the run stays paused.
The next node reads the answer as `$<wait-id>.output`: `{ text }`, `{ decision, text }`, or
`{ decisions, text }`. In the `approval` form `reject` never cancels the run.

The answer records `answeredVia` and `answeredAt` on the run's `metadata.wait`, and a
`wait_answered` event carries the surface as `via` and the `occurrence`. Like `approve`,
human mode continues the run inline, and `--json` records the answer and returns
`{ ok, runId, action: "answer", answer, workflowName, resumable }` without continuing
it; `archon serve` or [`workflow wake`](#workflow-wake) continues it. The HTTP equivalent
is `POST /api/workflows/runs/<run-id>/answer` with `{ occurrence, choose?, text? }`.

`approve`, `reject` and `respond <option>` also answer an attention wait, as shorthand:
`approve [text]` answers a `message` wait with its text, and otherwise each verb chooses
the option of the same id.

### `workflow approve`

Approve a paused workflow run. At an attention wait this is the shorthand described under [`workflow answer`](#workflow-answer). At a deprecated `approval:` gate with authored `approval.decisions`, this command requires `approve` in that list; otherwise it is refused and the run stays paused. An optional comment is available as `$<gate-id>.output.text` for authored decisions, or `$LOOP_USER_INPUT` at a deprecated interactive-loop gate.

**Sub-run child gates (#2121 Phase 2):** when a `workflow:` sub-run pauses at its own gate, the parent run pauses "blocked on child". Approve (or reject) the **child** by its own full run id, available from `archon workflow get <parentRunId> --json` at `.metadata.approval.childRunId`, not the parent's. The stored block message contains only a short child label; a surface notice may include the full child command. The parent auto-resumes when the child completes. A child inside a `fan_out:` expansion pauses the same way: it stays paused as its own run, and the parent pauses on a `fan_out` gate listing every unfinished child, then continues once they finish. `approve`/`reject` against the parent's id while it's blocked on a child are refused with a redirect to the child id (every listed child for a fan-out).

**Deprecated interactive-loop gates — finalize vs iterate:** when the gate paused on an iteration where any declared completion channel fired (`workflow get <run-id> --json` → `.metadata.approval.completionSignaled` is `true`), approving with **no comment** accepts the completion — the node finalizes from the already-computed output on resume, with no re-run. Approving **with** a comment runs another iteration using it as `$LOOP_USER_INPUT`. When no completion condition met, both forms run another iteration. On the final iteration (`max_iterations`), only a comment runs one more; approving with no comment fails the node with `max_iterations`.

```bash
archon workflow approve <run-id>
archon workflow approve <run-id> "Looks good, proceed"
archon workflow approve <run-id> --comment "Looks good, proceed"
archon workflow approve <run-id> --occurrence <occurrence> --json   # refused if the run moved on
archon workflow approve <run-id> --json   # record approval + ack; does NOT auto-resume inline
```

In human mode `approve`/`reject` auto-resume the run inline. In `--json` mode they record the decision and return an ack **without** resuming. An answered gate is a due continuation, so a running `archon serve` or [`workflow wake`](#workflow-wake) continues it; `workflow resume <run-id>` continues it by hand. Container runs are the exception and need the manual resume.

#### Detached control verbs

`approve`, `reject`, `respond`, and `resume` accept `--detach`. The parent validates the run
**read-only** with the same preconditions the operation itself enforces, so a
wrong-status, missing-context, `child_workflow`-blocked, already-resolved, or
no-working-path run is refused synchronously and nothing is spawned. The parent then
hands the whole command to a detached child that owns all state mutation in its own
process group. A shell that dies mid-flight can no longer wedge the run.

For `resume`, the parent waits for the child's engine admission receipt before
acknowledging success, rather than waiting for workflow completion. Even a run that
immediately pauses again or finishes is acknowledged normally; use `workflow wait`
to learn that outcome. Confirmation is bounded to 60 seconds. If acceptance is not
confirmed, the command reports an error with the available log path. The child may
still continue: inspect the run before retrying. A timeout never stops the child or
changes run state.

Other detached control verbs use the child's startup window. A child that dies during
startup surfaces as an error carrying the tail of its log. A run that finishes inside
that window is acknowledged normally; its outcome belongs to the run.

```bash
archon workflow approve <run-id> --detach
archon workflow approve <run-id> --detach --json
```

**`--detach --json` deliberately differs from bare `--json`.** Bare `--json` records the
decision and withholds the inline auto-resume (a continuation host continues the run).
`--detach --json` spawns a child that takes the ordinary inline path, so the run **is**
driven onward — approve's auto-resume, reject's legacy `on_reject` rework, resume's re-run —
just outside your shell. The ack carries `continues: true` to say so:

```json
{
  "ok": true,
  "runId": "…",
  "action": "approve",
  "detached": true,
  "continues": true,
  "workflowName": "my-workflow",
  "logPath": "~/.archon/logs/detached-run-<id>.log"
}
```

Read `continues` to decide whether your automation still owns continuation. `logPath`
is `null` when the log file could not be opened — the child still runs, but its output
is discarded, so do not assume a string. This acknowledgement has no `transcriptPath`;
use its `runId` with `workflow logs <run-id>` or `workflow get <run-id>` to locate the
transcript. Precheck failures follow each verb's existing error contract:
`{ ok: false }` under `--json`, a thrown error otherwise.

### `workflow reject`

For a deprecated `approval:` gate with authored `approval.decisions`, this command requires `reject` in that list; otherwise it is refused with the allowed ids and the run stays paused.

Reject a paused workflow run. At an attention wait this is the shorthand described under [`workflow answer`](#workflow-answer); the run continues with `decision: reject` and does not cancel. At a deprecated `approval:` gate an optional reason is available as `$<gate-id>.output.text` for authored decisions, or `$REJECTION_REASON` inside a legacy `on_reject` prompt.

```bash
archon workflow reject <run-id>
archon workflow reject <run-id> --reason "Needs more tests"
archon workflow reject <run-id> --json
```

### `workflow cleanup`

Delete old terminal workflow run records and prune state-document revisions from the
selected store (files or database). Every latest revision survives, including tombstones:
removing a tombstone would allow revision numbers to repeat and a stale replacement to overwrite recreated state.
The command reports both counts.

```bash
archon workflow cleanup        # Default: 7 days
archon workflow cleanup 30     # Custom threshold
```

### `workflow reset-sessions`

Clear persisted per-node AI sessions for a workflow — the cross-run memory stored by
nodes that opt in via `persist_session` (or workflow-level `persist_sessions: true`).
Use it when a workflow should forget its prior conversation and start fresh.

```bash
archon workflow reset-sessions <workflow-name> --yes             # ALL scopes (cross-scope wipe)
archon workflow reset-sessions <workflow-name> --scope <key>     # one scope only
archon workflow reset-sessions <workflow-name> --node <id> --yes # single node, still all scopes → needs --yes
archon workflow reset-sessions <workflow-name> --scope <key> --json
```

**Flags:**

| Flag | Required | Description |
|------|----------|-------------|
| `--scope` | No | Scope key to reset — typically the conversation UUID. Omitting it wipes **every** scope and requires `--yes`. |
| `--node` | No | Restrict the reset to a single node id. |
| `--yes` | Only for cross-scope wipes | Confirm a wipe across all scopes (when `--scope` is omitted). |
| `--json` | No | Machine-readable output (`{ success, deleted }`). |

Extra positional arguments are rejected rather than silently reinterpreted — use `--node <id>`
to filter by node, since this is a destructive command.

### `workflow event emit`

Emit a workflow event directly to the database. Primarily used inside workflow loop prompts to record story-level lifecycle events.

```bash
archon workflow event emit --run-id <run-id> --type <event-type> [--data <json>]
```

`<run-id>` accepts either the full ID or an unambiguous prefix from `workflow runs`.
A prefix resolves only from the originating registered project's directory, including
its worktrees; use the full ID elsewhere.

**Flags:**

| Flag | Required | Description |
|------|----------|-------------|
| `--run-id` | Yes | Full workflow run ID, or an unambiguous prefix used from the originating registered project's directory or one of its worktrees; use the full ID elsewhere |
| `--type` | Yes | Event type (e.g., `ralph_story_started`, `node_completed`) |
| `--data` | No | JSON string attached to the event. Invalid JSON prints a warning and is ignored. |

Node-state events, including completion, failure, skip, and resume-cache changes, and
`workflow.checkout_shared` events print `Event persisted` only after the database write
succeeds. Storage failures exit with code 1. Checkout-sharing data must contain
`parent_run_id`, `parent_node_id`, `working_path`, and `sibling_run_id` as nonempty strings.
Other events print `Event submitted (best-effort)`; check server logs if they appear missing.

Exit code: 0 after persistence or best-effort submission, 1 when a required argument is
missing, the event type is invalid, run-ID prefix resolution fails, required event data is
invalid, or a required event write fails.

### `isolation list`

Show all active worktree environments.

```bash
archon isolation list
```

Groups by codebase, shows branch, workflow type, platform, and days since activity.

Includes worktrees created for `workflow:` sub-run children that declared `isolation: worktree`
(branch `archon/task-<parentRunId8>-<nodeId>-<hash>-child-<n>`) — they are tracked and cleaned
up exactly like top-level run worktrees. Avoid `complete` on one while its run tree is still
resumable: a resume reuses the child's recorded worktree and fails if it has been removed.
`cleanup` removes it only under the run-lock rule below.

### `isolation cleanup [days]`

To explicitly discard a resumable run and remove the worktree it created, use
`archon workflow abandon <run-id>`. Ordinary cleanup follows the run-lock rule at the end
of this section. Abandon removes a checkout only when the run holds its matching creation
proof, even if the checkout has since been adopted. It deletes uncommitted work there and
keeps the branch; see [`workflow abandon`](#workflow-abandon) for what it keeps and how to retry.

Remove stale environments. Worktree cleanup only handles worktree records. Container
cleanup removes eligible containers and their volumes through the container backend,
including when no stale worktrees exist. A missing host folder does not make a container
destroyed. To clean eligible containers without waiting for the default age threshold,
run `archon isolation cleanup 0`; containers owned by resumable runs remain protected.

Project deletion (`DELETE /api/codebases/{id}`) returns `409` while active container
environments remain. The error lists their environment IDs and the cleanup command.
Clean them up explicitly, then retry deletion.

Age cleanup uses the originating platform's registered policy.
Both the CLI and server read chat policies from installed plugin receipts without starting
the plugins. A receipt supersedes a bundled chat policy with the same platform id.

If an environment names a platform that is no longer registered, cleanup keeps it and
reports the reason. This also applies to merged branches and missing-path reconciliation:
removing a plugin must not make its historical workspaces eligible for deletion. `isolation list`
shows the cleanup skip reason. Rows with no originating platform keep their existing cleanup
behavior.

Ordinary removal requires a stored creation proof matching
Archon's marker and Git registration in the expected repository. Replacement
repositories and checkouts without matching stored proof, including newly registered
adoptions and older records, are retained for operator inspection. Reusing a registered
checkout sets `adopted: true` while preserving its creation proof, so that checkout
can remain eligible for cleanup. If Git removal leaves a directory behind, cleanup reports it
and keeps the environment active; inspect that path before removing it manually.

Cleanup deletes an environment's local branch only when Archon created that branch.
A branch that already existed when the run started (see `--branch` above) is kept,
since it may hold commits that exist nowhere else. It is deleted only together with
its remote branch: by `--merged` (and `--include-closed`) once its PR or branch is
finished, or by `archon complete`.
Records created before Archon tracked this are treated the same way, keeping their branch.

These ownership checks also apply to `/worktree remove` and `archon complete`;
`--force` never bypasses them.

```bash
# Default: 7 days
archon isolation cleanup

# Custom threshold
archon isolation cleanup 14

# Remove environments with branches merged into the base branch (also deletes remote branches)
archon isolation cleanup --merged

# Also remove environments whose PRs were closed without merging
archon isolation cleanup --merged --include-closed
```

Merge detection uses three signals in order: git branch ancestry (fast-forward / merge commit),
patch equivalence (single-commit squash-merge via `git cherry`), and GitHub PR state via the
`gh` CLI. A squash merge of more than one commit is invisible to git here, so PR state is what
recognises it. A merged or closed PR counts only while neither the worktree's HEAD nor the
local branch has anything past the PR's head commit: run branch names get reused, and new
commits on a reused branch or a detached HEAD are not covered by the old PR, so that
environment is kept. The same goes for a merge git detects: the worktree's HEAD must be
merged into the base too, not just the branch. A PR head commit pushed from somewhere
else is fetched from the remote before that comparison; if the fetch fails, the environment is
kept and reported as a failed merge check. The `gh` CLI is optional — if absent, only git
signals are used. If `gh` is installed but the lookup fails (auth, rate limit), the environment
is kept and reported as `PR state lookup failed`. The scheduled
sweep uses the same three signals, so both paths agree on what counts as merged. Each
codebase's output names the base ref the comparison actually used — the configured
`worktree.baseBranch`, or the git-detected default branch when that is unset.

Both git signals read the local branch ref. When that ref is gone but the worktree remains,
the PR decides, checked against the worktree's HEAD; if no PR answers for the branch either, the environment is kept and reported
as `merge state unverifiable` rather than removed on an unverified guess.

By default, branches with a **CLOSED** PR are skipped. Pass `--include-closed` to clean
those up as well. Branches with an **OPEN** PR are always skipped.

A running, pending, or paused run keeps its environment in both modes, regardless of
age or merge state. The scheduled sweep and chat `/worktree cleanup` apply the same
run lock. Stale environments with quiet conversations still pass the activity filter;
the run lock is what keeps them on disk.

A **failed** run keeps its environment only until its outcome is settled:

- `--merged` (and the server's scheduled cleanup) removes it once its branch is merged
  or its PR is merged. `--include-closed` also removes it once its PR is closed.
- The stale sweep removes it once the run has been inactive since it failed for longer
  than the stale window (`[days]` here; `STALE_THRESHOLD_DAYS` for the server and chat).

The output names each failed run whose environment it removed, with the reason:
`merged`, `PR closed`, or `stale`. Removal leaves the run's status unchanged. Resuming
it afterwards fails with `the working path from the run no longer exists`; start a
fresh run. To discard a failed run sooner, use `workflow abandon`.

Age alone never removes work that exists nowhere else. The stale sweep, here and in the
server's scheduled cleanup and chat `/worktree cleanup stale`, keeps any environment whose
worktree HEAD or branch holds a commit on no remote, whether or not a run owns it. It
reports a failed run's environment as `failed run <id> has <n> unpushed commit(s)`, and
any other as `<n> unpushed commit(s); remove it explicitly with archon complete <branch> --force`.
The scheduled cleanup keeps an environment whose checkout is missing the same way while
the branch Archon created for it holds such a commit.

A completed run removes the worktree it created when it completes, if the checkout is
clean, every commit on its HEAD and branch is on a remote, and the configured remote
does not advertise the branch. The removal keeps the branch, so the run can still be
adopted. A branch that was published waits for `--merged`, which also deletes it. When
the remote cannot be read, the worktree stays and the record says `check_failed`. The
run's `terminal_record.worktree_disposition` says what happened and why (see
[`workflow get`](#workflow-get)).

### `validate workflows [name]`

Validate workflow YAML definitions and their referenced resources (command files, MCP configs, skill directories).

```bash
archon validate workflows                 # Validate all workflows
archon validate workflows my-workflow     # Validate a single workflow
archon validate workflows my-workflow --json  # Machine-readable JSON output
```

Checks: YAML syntax, DAG structure (cycles, dependency refs), command file existence, MCP config files, skill directories, provider compatibility, tier/alias model refs, and supported static environment reads in inline or named exec sources. An unprovided `INPUTS_*` read is an error when the workflow declares an input contract; other unprovided Python or JavaScript/TypeScript environment reads warn. See [Variables](/reference/variables/#shell-quoting-in-bash-vs-script) for the supported forms and fixes. For bundled and global workflows, validation rejects `@custom` model aliases because they are not portable across projects; use `small`, `medium`, `large`, or a literal provider model string instead. Returns actionable error messages with "did you mean?" suggestions for typos.

Exit code: 0 = all valid, 1 = errors found.

### `validate commands [name]`

Validate command files (.md) in `.archon/commands/`.

```bash
archon validate commands                  # Validate all commands
archon validate commands my-command       # Validate a single command
```

Checks: file exists, non-empty, valid name.

Exit code: 0 = all valid, 1 = errors found.

### Removed: `continue <branch> [message]`

`archon continue` inferred a run from a branch name and injected a prose preamble of git history, pull-request text, and prior artifacts. Continuation that inherits prior work is identified by exact run id instead:

```bash
archon workflow runs --open                       # find the prior run id
archon workflow run archon-ship --adopt <run-id> "resolve the remaining review finding"
```

`workflow run <name> --adopt <run-id>` reuses a terminal run's exact worktree or branch, records `adopted_from_run_id` provenance, and exposes prior artifacts through `$ADOPTED_RUN_DIR`. The orchestrating agent owns choosing the next workflow and writing the input; see [`--adopt <run-id>`](#workflow-run-name-message).

### `complete <branch> [branch2 ...]`

Remove a branch's worktree, local branch, and remote branch, and mark its isolation environment as destroyed.

```bash
archon complete feature-auth
archon complete feature-auth --force  # bypass uncommitted-change, live-run, open-PR, and unmerged/unpushed-commit checks
```

**Flags:**

| Flag | Effect |
|------|--------|
| `--force` | Skip uncommitted-change, live-run, open-PR, and unmerged/unpushed-commit checks; ownership checks still apply |

Without `--force`, completion refuses an environment a workflow run can still claim, the
same live-run lock `/worktree remove` applies: a running, pending or paused run, or a
failed run that can still be resumed. Unlike `isolation cleanup`, completion never
releases a failed run's lock by merge state or age. If that lookup fails, completion
refuses rather than skipping the check.

Use this after a PR is merged and you no longer need the worktree or branches. If GitHub
has deleted a squash-merged branch, first prune its remote-tracking ref so the local clone
reflects that deletion:

```bash
git fetch --prune origin # replace origin with the configured remote when needed
archon complete feature-auth
```

Without `--force`, completion checks for unique and unpushed commits before deletion. If the
branch's remote-tracking ref is missing, it verifies that any unique commits are patch-equivalent
to the configured base branch or detected remote default branch. With `--force`, these checks
are skipped, so deletion can discard the only reachable copy of commits. Accepts multiple
branch names in one call.

### `serve`

Start the web UI server in the foreground. The same command works from a binary install and from a source checkout. The server runs as a child process.

**Binary installs** automatically download the server executable and web UI from the matching GitHub release on first run. The command announces the version, asset and download size, verifies each against the checksum embedded in the CLI, and starts the server. Later runs use the cached artifacts. Non-serve commands need no server installation.

**Source checkouts** start `packages/server/src/bin.ts` with Bun and serve the web UI you build yourself, at `packages/web/dist`. Run `bun run build:web` from the repo root before your first `archon serve`, and again after frontend changes. Nothing is downloaded, so `--download-only` is refused, and a missing build stops the command with the build command to run instead of serving an empty page.

```bash
# Start web UI server (binary installs download the server and UI on first run)
archon serve

# Override the default port
archon serve --port 4000

# Download the server and web UI without starting the server (binary installs only)
archon serve --download-only
```

**Flags:**

| Flag | Effect |
|------|--------|
| `--port <port>` | Override server port (default: 3090, range: 1–65535) |
| `--download-only` | Download and cache the server and web UI, then exit without starting the server. Binary installs only |

The server executable is cached at `~/.archon/server/<version>/` and the web UI at `~/.archon/web-dist/<version>/`. Upgrading the CLI downloads matching artifacts on the next `archon serve`. A failed download installs nothing for that asset and exits non-zero with its URL; retry with `archon serve --download-only`. Docker continues to bundle both.

### `skill add [path]`

The former skill `install` verb remains a deprecated alias for `archon skill add` and prints a warning.

Install the bundled `archon-cli` skill into both `.claude/skills/archon-cli/`
(Claude Code) and `.agents/skills/archon-cli/` (Codex). The command overwrites
existing files so both destinations match the current Archon binary. It also
removes the retired `archon` and `manage-run` skill directories.

```bash
# Install into the current directory
archon skill add

# Install into a specific project
archon skill add /path/to/project
```

The unified skill covers workflow execution, run management, setup, configuration,
authoring, and prompt guidance. It is also installed automatically during
`archon setup`.

### `version`

Show version, build type, and database info.

```bash
archon version
```

## Global Options

| Option | Effect |
|--------|--------|
| `--cwd <path>` | Override working directory (default: current directory) |
| `--quiet`, `-q` | Log warnings and errors only (the default for every command except `archon serve`) |
| `--verbose`, `-v` | Show debug-level logs on stderr (on stdout for `archon serve`) |
| `--json` | Output machine-readable JSON (workflow `list`, `status`, `runs`, `get`, `wait`, `attention`, and the write commands `approve`/`reject`/`abandon`/`resume`). Implies log suppression so stdout is exactly the JSON payload. |
| `--after <cursor>` | For `workflow attention --watch`: replay the stream after the line that carried this cursor. |
| `--timeout <seconds>` | For `workflow wait`: give up after N seconds and exit `3`. Omitted means wait indefinitely. |
| `--follow` | For `workflow logs`: wait for the transcript and stream appended rows until the run ends. |
| `--format <jsonl\|text>` | For `workflow logs`: `jsonl` (default) prints the exact transcript; `text` renders it as progress lines for a person. |
| `--events` | With verbose JSON workflow `status`/`get`, return raw event rows instead of ordered node summaries. |
| `--help`, `-h` | Show help message |

### Logs

A command's stdout carries only its output, so `archon workflow list --full > out.txt`
captures the listing and nothing else. Engine logs go to stderr, and by default only
warnings and errors appear. `--verbose` (or `LOG_LEVEL=debug`) adds debug logs, still on
stderr. `--json` and `workflow logs` print no logs at all.

`archon serve` is the exception: its logs are its output, so it logs at `info` (or debug with `--verbose`) on stdout,
as the server does when started directly.

Workflow definition problems, such as deprecated or unknown keys, are reported by
`archon validate workflows`, inline under the workflow in `workflow list`, and on stderr
before `workflow run` starts. A file that fails to load is listed with its error by
`workflow list` and `validate workflows`, and `workflow run` names the error. These problems
are logged only at debug, not for every workflow a command happens to discover. An invalid
value for an optional workflow field (for example a malformed `tags:` block) is dropped with
a warning log, because no other report names it.

## Working Directory

The CLI determines where to run based on:

1. `--cwd` flag (if provided)
2. Current directory (default)

Running from a subdirectory (e.g., `/repo/packages/cli`) automatically resolves to the nearest git repository root (e.g., `/repo`). A nested repository resolves as its own codebase and registers on first workflow use. Linked worktrees resolve to their registered source codebase. Ordinary subdirectories of registered folder projects continue to resolve to the registered folder. An exact folder registration wins over git detection. `--folder` retains the supplied directory rather than selecting a surrounding git root. Linked Git worktrees do not inherit a folder registration from the primary checkout. Listing commands do not register projects.

When project registration fails, the command fails with the registration error, even with `--no-worktree`. Registering a second clone of an already-registered remote is refused with the registered project's path; use worktrees (`--branch`) for parallel checkouts. A new project whose name or storage folder is already taken by a different path registers under the first free suffixed name, such as `_local/api-2` or `my-app-2` for a folder project. This also applies to `/register-project` and URL cloning. Existing project names and storage stay unchanged, so projects that an older version registered into one storage folder keep sharing it.

When using `--branch`, workflows run inside the worktree directory.

> **Commands and workflows are loaded from the working directory at runtime.** The CLI reads directly from disk, so it picks up uncommitted changes immediately. This is different from the server (Telegram/Slack/GitHub), which reads from the workspace clone at `~/.archon/workspaces/` -- that clone only syncs from the remote before worktree creation, so changes must be pushed to take effect there.

Legacy registrations with a relative stored project path fail with a project-named
error. Attempt repair in Archon chat with
`/register-project "project-name" /absolute/path/to/project`; changing the CLI's
working directory does not repair the stored path. Repair refuses a replacement
path or kind that would move managed storage or worktrees. A successful repair
preserves the existing project's identity and history. See
[Stored project path is relative](/reference/troubleshooting/#stored-project-path-is-relative)
for recovery details.

## Environment

At startup, the CLI removes every key named in the CWD project env files from `process.env`, whatever its value or source, including shell exports and direnv. A detached child (`--internal-detached-run-config`) then restores Archon's own install-context keys (`TOKEN_ENCRYPTION_KEY`, `ARCHON_HOME`, `ARCHON_DOCKER`, `WORKSPACE_PATH`, `HOME`, and `USERPROFILE`) from the trusted detached config, never from the project `.env`. This prevents project API keys from overriding subscription auth and incurring API billing, and prevents project keys from overriding Archon's environment. It also strips nested Claude Code session markers, then loads Archon-owned env files with `override: true`. Put install credentials such as `GH_TOKEN` in `~/.archon/.env`; those later trusted sources pass through to AI subprocesses. See [target repo env isolation](/reference/security/#target-repo-env-isolation).

On startup, the CLI:
1. Strips keys named in `<cwd>/.env`, `.env.local`, `.env.development`, and `.env.production`, plus nested Claude Code session markers from `process.env` (via `stripCwdEnv`). Emits `[archon] stripped N keys from <cwd> (...)` when N > 0.
2. Loads `~/.archon/.env` (user scope). Emits `[archon] loaded N keys …` when N > 0 **and** `ARCHON_VERBOSE_BOOT=1` or `LOG_LEVEL=debug/trace` is set.
3. Loads `<cwd>/.archon/.env` (project scope, overrides user scope except `GH_CONFIG_DIR` and inherited workflow identity). Same verbosity gate as step 2. The CLI preserves the user-scope `GH_CONFIG_DIR`, including its absence; inside a workflow, an inherited login directory takes precedence over both files. See [forge configuration](/reference/forge/#plugin-configuration).
4. Auto-enables global Claude auth if no explicit tokens are set.

`<cwd>/.env` is never loaded — it belongs to the target project. See [Configuration Reference: `.env` File Locations](/reference/configuration/#env-file-locations) for the full three-path model.

## Database

- **Without `DATABASE_URL` (default):** Uses SQLite at `~/.archon/archon.db` -- zero setup, auto-initialized on first run
- **With `DATABASE_URL`:** Uses PostgreSQL (optional, for cloud/advanced deployments)

Both work transparently. Most users never need to configure a database.

## Examples

```bash
# One-off AI chat
archon chat "How does error handling work in this codebase?"

# Interactive setup wizard
archon setup

# Investigate a bug (auto-isolated on a generated archon/task-... branch)
archon workflow run investigate --cwd ~/projects/my-app "Why do API errors return 500 instead of 400?"

# Investigate without isolation
archon workflow run investigate --cwd ~/projects/my-app --no-worktree "Why do API errors return 500 instead of 400?"

# Plan a feature (auto-isolated)
archon workflow run plan --cwd ~/projects/my-app "Add rate limiting to the API"

# Implement with explicit branch name
archon workflow run implement --cwd ~/projects/my-app --branch feature-rate-limit "Add rate limiting"

# Branch from a specific source branch instead of auto-detected default
archon workflow run implement --cwd ~/projects/my-app --branch test-adapters --from feature/extract-adapters "Test adapter changes"

# Approve or reject a paused workflow
archon workflow approve <run-id> "Ship it"
archon workflow reject <run-id> --reason "Missing test coverage"

# Check worktrees after work session
archon isolation list

# Clean up old worktrees
archon isolation cleanup
```

### state

Durable, audited JSON documents. A document lives in a project or in install scope.
The command uses the selected store: files, SQLite or PostgreSQL. Values are read on demand;
the engine never injects them into prompts.

```bash
archon state get review/123
printf '{"seen":[]}' | archon state put review/123 --if-revision 0
printf '{"ops":[{"op":"append","path":"/seen","value":123}]}' | archon state patch review/123
printf '{"ops":[{"op":"set","path":"/owner","value":"b"}]}' | archon state patch review/123 --if-revision 2
archon state log review/123 --limit 100
archon state list --prefix review/
archon state list --prefix review/ --values
archon state delete review/123 --if-revision 3
archon state get notes --install --raw
archon state list --project acme/widgets
```

Every command writes JSON and names the scope it used: `{kind: "project", key}` or
`{kind: "install"}`. `get` returns `{scope, name, revision, value}`; an absent document
returns revision `0` and value `null`. A deleted document returns value `null`,
`deleted: true` and its tombstone revision, which is the revision a recreating `put` must
name. `put`, `patch`, and `delete` return the scope and the new full revision. `log`
returns `{scope, name, revisions}`, newest first (use a positive `--limit` to bound the
result), including tombstones, operation, writer and timestamp. `list` returns
`{scope, documents}`: each document's name, latest revision, `updatedAt`, writer and
`deleted`, ordered by name, without values. `--prefix` keeps names that start with
the given text. `list --values` reads every live document's value in one call instead:
each entry is `{name, revision, value}`, and deleted documents are left out.

`put` requires `--if-revision`: `0` creates a new document; otherwise it replaces exactly
that revision. `delete` accepts the same optional check. `patch` applies `{ops:[...]}` from
stdin to the latest value, initially `{}`. With `--if-revision <n>` it applies only when
`n` is still the latest revision, and otherwise exits `3`. Use it whenever the change was
computed from what you read. Without it, writes to one document are serialized, so a
patch never loses a revision race; it applies to whatever an earlier write left, which
can make it invalid (for example, removing a member another patch already removed). A
revision's recorded operation includes `ifRevision` when the patch named one.
Operations are `set`, `remove`, and `append`, using RFC 6901 JSON Pointer paths. `set`
replaces a value, `remove` deletes an existing member (or array element), and `append`
adds one value to an array, creating it when missing. Missing intermediate objects are
created for `set` and `append`. Array indices must be canonical, in-range integers;
`-` is unsupported. The empty pointer addresses the entire document. Root `null` is
reserved for deletion; nested nulls are ordinary JSON values.

A text document is a JSON string value. `put --raw` stores stdin as that string
unchanged, and `get --raw` prints it without JSON encoding (exit `4` when the value is not
a string). Use plain `get` when you need the revision for a following `put`.

Names contain 1–200 lowercase letters, digits, `.`, `_`, `-`, and slash-separated segments.
Values are limited to 64 KiB of serialized UTF-8 JSON. Exit codes: `0` success, `1` usage
or storage failure, `3` revision conflict, `4` oversized value, invalid patch or non-text
`--raw` value, `5` the CLI opened a store that does not know the calling run. Failures
return `{ok:false, error, code}` and rejected writes append no revision.

Inside a bash/script node, or the shell of an AI node's agent, `WORKFLOW_ID` and
`ARCHON_NODE_EXECUTION` resolve the run's project, and the writer is recorded as `{kind: "run", runId, nodePath}`. A run whose store
does not know it fails with exit `5` naming the opened store kind; a missing project or
node identity also fails. A node reaches only its run's project, or install scope with
`--install`; `--project` is refused inside a run. Outside a run, the current checkout
(auto-registered when needed) or registered folder resolves the project, `--project <key>`
names one directly (the workspace-relative key, such as `owner/repo` or `_folder/ops`),
and `--install` selects install scope. The writer is recorded as
`{kind: "surface", via: "cli"}`: the surface the write came through, not who made it.

`archon workflow cleanup <days>` also prunes old state revisions, keeps each live document's latest
revision, retains the latest tombstone of deleted documents, and reports both counts.
See [Durable state documents](/guides/authoring-workflows/#durable-state-documents).