---
name: acpx
description: Use acpx as a headless ACP CLI for agent-to-agent communication, including installed-agent inspection, prompt/exec/sessions workflows, session scoping, queueing, permissions, output formats, system-prompt overrides, and multi-agent flows authored with defineFlow/decision/decisionEdge.
---

# acpx

## When to use this skill

Use this skill when you need to run coding agents through `acpx`, manage persistent ACP sessions, queue prompts, override the Claude system prompt, prune stale sessions, consume structured agent output from scripts, or compose multi-agent workflows declaratively with `acpx/flows`.

## What acpx is

`acpx` is a headless, scriptable CLI client for the Agent Client Protocol (ACP). It is built for agent-to-agent communication over the command line and avoids PTY scraping.

Core capabilities:

- Persistent multi-turn sessions per repo/cwd
- One-shot execution mode (`exec`)
- Named parallel sessions (`-s/--session`)
- Idempotent session creation (`sessions ensure`)
- Session retention controls (`sessions prune` with age filters and history cleanup)
- Portable session export/import for moving records and history across machines
- Queue-aware prompt submission with optional fire-and-forget (`--no-wait`)
- Cooperative cancel command (`cancel`) for in-flight turns
- Graceful cancellation via ACP `session/cancel` on interrupt
- Session control methods (`set-mode`, `set <key> <value>`)
- Agent reconnect/resume after dead subprocess detection
- Prompt input via stdin or `--file`
- Config files with global+project merge and `config show|init`
- Session metadata/history inspection (`sessions show`, `sessions history`)
- Passive replay and live observation (`sessions watch -s <name> --cursor <cursor>`; omit the cursor for retained history)
- Local agent process checks via `status`
- Stable ACP client methods for filesystem and terminal requests; filesystem paths preserve symlink/parent traversal order within the working directory
- Stable ACP `authenticate` handshake via env/config credentials
- Structured streaming output (`text`, `json`, `quiet`) with optional `--suppress-reads`
- Prompt-response `_meta` preservation across direct, queued, compare, and embedded-runtime results
- Built-in agent registry plus raw `--agent` escape hatch
- Claude system prompt override via `--system-prompt` / `--append-system-prompt`
- Optional ACP filesystem and terminal capability opt-outs via `--no-fs` and `--no-terminal`
- Adapter-specific tool approval controls (`--allowed-tools`), turn cap (`--max-turns`), retry on transient failures (`--prompt-retries`)
- Multi-agent flows via `acpx flow run` and the `acpx/flows` authoring API (`defineFlow`, `decision`, `decisionEdge`, `acp`, `action`, `compute`, `checkpoint`)

## Install

```bash
npm i -g acpx
```

For normal session reuse, prefer a global install over `npx`.

## Command model

`prompt` is the default verb.

```bash
acpx [global_options] [prompt_text...]
acpx [global_options] prompt [prompt_options] [prompt_text...]
acpx [global_options] exec [prompt_options] [prompt_text...]
acpx [global_options] compare <agent>... '<prompt_text>'
acpx [global_options] compare <agent>... --file <path>
acpx [global_options] cancel [-s <name>]
acpx [global_options] set-mode <mode> [-s <name>]
acpx [global_options] set <key> <value> [-s <name>]
acpx [global_options] status [-s <name>]
acpx [global_options] sessions [list | new [--name <name>] | ensure [--name <name>] | close [name] | show [name] | history [name] [--limit <count>] | watch [-s <name>] [--cursor <cursor>] | export [name] --output <path> | import <archive> [--name <name>] [--cwd <dir>] | prune [--dry-run] [--before <date> | --older-than <days>] [--include-history]]
acpx [global_options] config [show | init]
acpx [global_options] flow run <file> [--input-json '<json>' | --input-file <path>] [--default-agent <name>]

acpx [global_options] <agent> [prompt_options] [prompt_text...]
acpx [global_options] <agent> prompt [prompt_options] [prompt_text...]
acpx [global_options] <agent> exec [prompt_options] [prompt_text...]
acpx [global_options] <agent> cancel [-s <name>]
acpx [global_options] <agent> set-mode <mode> [-s <name>]
acpx [global_options] <agent> set <key> <value> [-s <name>]
acpx [global_options] <agent> status [-s <name>]
acpx [global_options] <agent> sessions [list | new [--name <name>] | ensure [--name <name>] | close [name] | show [name] | history [name] [--limit <count>] | watch [-s <name>] [--cursor <cursor>] | export [name] --output <path> | import <archive> [--name <name>] [--cwd <dir>] | prune [--dry-run] [--before <date> | --older-than <days>] [--include-history]]
```

If prompt text is omitted and stdin is piped, `acpx` reads prompt text from stdin.

## Built-in agent registry

Friendly agent names resolve to commands:

- `pi` -> `npx --package=pi-acp -- pi-acp` (ACPX-owned package range; see `agents/Pi.md`)
- `openclaw` -> `openclaw acp` (see the [OpenClaw guide](https://github.com/openclaw/acpx/blob/main/agents/OpenClaw.md))
- `codex` -> `npx -y --package=@agentclientprotocol/codex-acp -- codex-acp` (ACPX-owned package range)
- `claude` -> `npx -y --package=@agentclientprotocol/claude-agent-acp -- claude-agent-acp` (ACPX-owned package range; see `agents/Claude.md`)
- `gemini` -> `gemini --acp`
- `cursor` -> `cursor-agent acp`
- `copilot` -> `copilot --acp --stdio`
- `ante` -> `ante acp` (experimental). Requires Ante 0.2.1 or newer and a separately installed `ante-acp` on `PATH`. Prefer `exec` for fresh work; context is limited to the live ACP connection, with no reopening after disconnect. AskUser is excluded, and client-supplied MCP servers and extra directories are ignored. See [Ante](https://github.com/openclaw/acpx/blob/main/agents/Ante.md).
- `antigravity` -> `agy_acp_server.par` (`--uid=` on Linux; `agy_acp_server.exe` on Windows). Install the official runtime/helper pair and configure its own sign-in first; see [Antigravity](https://github.com/openclaw/acpx/blob/main/agents/Antigravity.md). Fixed-choice interaction questions are cancelled, including with `--approve-all`, host permission callbacks, and existing custom launchers; acpx cannot supply user answers through permission approval.
- `deepseek-harness` -> `dsh --profile acp` (DeepSeek Harness, separate from DeepSeek models; configure the provider in dsh first. Uses advertised `session/resume` without transcript replay, not `session/load`; see the [DeepSeek Harness guide](https://github.com/openclaw/acpx/blob/main/agents/DeepSeekHarness.md))
- `devin` -> `devin acp`
- `droid` -> `droid exec --output-format acp` (`factory-droid` and `factorydroid` also resolve to `droid`)
- `fast-agent` -> `uvx fast-agent-mcp acp`
- `fx` -> `fx acp` (install and authenticate a provider first; see the [fx guide](https://github.com/openclaw/acpx/blob/main/agents/Fx.md))
- `grok-build` -> `grok agent stdio` (Grok Build CLI, distinct from Grok Bot cloud routines and their webhook triggers; see the [Grok Build guide](https://github.com/openclaw/acpx/blob/main/agents/GrokBuild.md)).
- `goose` -> `goose acp` (install Goose and configure its provider first; see the [Goose guide](https://github.com/openclaw/acpx/blob/main/agents/Goose.md))
- `iflow` -> `iflow --experimental-acp`
- `junie` -> `junie --acp=true` (install and authenticate Junie first; see the [Junie guide](https://github.com/openclaw/acpx/blob/main/agents/Junie.md))
- `kilocode` -> `npx -y --package=@kilocode/cli -- kilo acp`
- `kimi` -> `kimi acp`
- `kiro` -> `kiro-cli-chat acp`
- `mcode` -> `mcode acp` (install/authenticate MCode first; prefer `exec` for one-shot work, and require advertised ACP reload support for cross-invocation continuity)
- `mux` -> `npx -y --package=mux -- mux acp` via an ACPX-owned npm range
- `octomind` -> `octomind acp developer:general` (install Octomind and sign in with `octomind login` or provider keys first; see the [Octomind guide](https://github.com/openclaw/acpx/blob/main/agents/Octomind.md))
- `opencode` -> `npx -y --package=opencode-ai -- opencode acp`
- `pool` -> `pool acp`
- `qoder` -> `qodercli --acp`
  Forwards Qoder-native `--allowed-tools` and `--max-turns` startup flags from `acpx` session options. Custom `qoder --acp` launchers receive the same startup and shutdown handling.
- `qwen` -> `qwen --acp` (Qwen Code, distinct from QwenWork; see the [Qwen Code guide](https://github.com/openclaw/acpx/blob/main/agents/Qwen.md) for text filesystem delegation and native file discovery)
- `trae` -> `traecli acp serve`
- `zeroclaw` -> `zeroclaw acp`

Rules:

- Default agent is `codex` for top-level `prompt`, `exec`, and `sessions`.
- Unknown positional agent tokens are treated as raw agent commands.
- `--agent <command>` explicitly sets a raw ACP adapter command.
- Claude package fallback uses the ACPX-owned adapter range. An adapter installed alongside ACPX takes precedence; update it and any explicitly selected native Claude executable separately. Existing custom launchers stay operator-controlled.
- Do not combine a positional agent and `--agent` in the same command.
- Gemini version checks, Copilot ACP support checks, and their startup diagnostics use the selected agent's working directory and child environment, including session environment and embedded runtime overrides.
- Embedded host lifecycle admission also covers compatibility and diagnostic invocations. Correlate their events by `launchId`; one session can produce several adapter processes. Checks retire their owned processes and output pipes before settling.

## Embedded hosts

When embedding ACPX, import `createAgentRegistry` from `acpx/agent-registry` and use `inspect(agentId)` to obtain installed launch facts without starting an agent. Treat `undefined` as uninspectable. Check the returned missing command/package requirements before acquiring a session; recheck when launching because installed files can change. Authentication and native model discovery belong to session acquisition.

Custom registry names such as `constructor` and `__proto__` work with resolution, listing, and inspection. Without an explicit registry entry, unknown names remain raw commands.

Use `getStatus({ handle }).models.availableModels` when present for native display names, and pass the selected opaque model ID, including any slashes, unchanged to `setModel`. Model changes validate against the connected session's advertised models, including after reconnect. `setConfigOption` applies the same validation when `key` names the advertised model option and returns that operation's native ACP response with the accepted `configOptions`. Use the response to update model and reasoning controls together. Embedded callers can identify unknown or ambiguous model failures with `isRequestedModelUnsupportedError(error)` and `error.reason === "unadvertised-model"`. When a Cursor alias matches multiple advertised variants, the error also has `ambiguous: true`; a selector with no advertised match omits that flag. Claude ACP retains its forwarding exception for selectors absent from its advertised list.

Use `prepareFreshSession({ handle })` to persist a fresh-session request across restart, then ensure without `resumeSessionId`. Ordinary `close` retains session continuity; explicit remote discard requires the adapter’s optional close capability.

Embedded controls save accepted settings with the current conversation. Turn finalization and one-shot cleanup wait for admitted controls, preserving final output, usage, and selections in the saved session. Ordinary `close` can return before active prompt cancellation finishes; the later save preserves its close/reset markers.

An unprompted persistent session recorded as created through `session/new` may be replaced after an ACP internal load/resume error (`-32603`) so its first prompt or control can run with saved preferences. Explicit native resumes, older records without creation provenance, earlier prompts (including cancelled turns), agent output, legacy resume markers, or imported provenance forbid this recovery; an unsupported or missing session still fails with `SESSION_RESUME_REQUIRED`.

For persistent sessions, compatible `ensureSession` calls may reuse an active record, including from permission callbacks during control reconnects. Changing its working directory, adapter invocation, or resume identity while turns or controls remain unfinished rejects with `ACP_SESSION_INIT_FAILED`; await that work or use a different key. After an active close, await its turn result before reopening. Idle replacement retires the old owner before initializing the successor; other records and distinct one-shot sessions remain independent.

For installed entrypoints and prerequisites, read the matching [agent guide](https://github.com/openclaw/acpx/tree/main/agents). For resolver callbacks, custom command overrides, and lifecycle details, read [embedded agent discovery](https://github.com/openclaw/acpx/blob/main/docs/session-control.md#embedded-agent-discovery).

## Commands

### Prompt (default, persistent session)

Implicit:

```bash
acpx codex 'fix flaky tests'
```

Explicit:

```bash
acpx codex prompt 'fix flaky tests'
acpx prompt 'fix flaky tests'   # defaults to codex
```

Behavior:

- Uses a saved session for the session scope key
- Auto-resumes prior session when one exists for that scope
- If no session exists for the scope, exits with `NO_SESSION` and prompts for `sessions new`
- Is queue-aware when another prompt is already running for the same session
- On interrupt during an active turn, sends ACP `session/cancel` before force-kill fallback

Prompt options:

- `-s, --session <name>`: use a named session within the same cwd
- `--no-wait`: enqueue and return immediately when session is already busy
- `-f, --file <path>`: read prompt text from file (`-` means stdin)

### Exec (one-shot)

```bash
acpx exec 'summarize this repo'
acpx codex exec 'summarize this repo'
acpx --model gpt-5.6-sol codex exec --config-option reasoning_effort=xhigh 'review this repo'
```

Behavior:

- Runs a single prompt in a temporary ACP session
- Applies `--model`, then repeatable `--config-option <key=value>` selections, before prompting
- Fails before the prompt if the adapter rejects a requested config option
- Does not reuse or save persistent session state

### Compare (multi-agent one-shot)

```bash
acpx compare pi openclaw codex 'summarize this checkout'
acpx --format json compare codex claude --file prompt.md
```

Behavior:

- Runs the same temporary-session prompt against each listed agent
- Runs agents serially in the requested workspace
- Reuses the global `exec` controls: cwd, timeout, permissions, `--policy`, auth, terminal, retries, model/system options, and output format
- `--format text` prints one summary table row per agent
- `--format json` or `--json` prints `CompareRow[]`
- `--format quiet` prints `<agent>\t<status>` per row
- Does not create saved sessions or separate compare transcript directories

### Cancel / Mode / Config / Model

```bash
acpx codex cancel
acpx codex set-mode auto
acpx codex set model gpt-5.6-sol
acpx codex set reasoning_effort max
```

Behavior:

- `cancel`: sends cooperative `session/cancel` through queue-owner IPC.
- `set-mode`: calls ACP `session/set_mode`.
- `set-mode` mode ids are adapter-defined; unsupported values are rejected by the adapter (often `Invalid params`).
- On allowed fresh fallback, `set-mode` replaces the old saved mode without replaying it. Other saved settings restore first; the new mode preference is saved only after acceptance.
- Prompt reconnect uses model metadata received while restoring a saved mode before replaying the selected model, and retains changed or removed model support in the final session metadata.
- `set`: calls ACP `session/set_config_option`.
- A config key named `mode` keeps config-option transport; explicit `set-mode` uses the separate `session/set_mode` control.
- Current codex-acp releases expose `model` and `reasoning_effort` as separate config options.
- `--model <id>`: Claude-compatible adapters may consume session creation metadata; other agents must advertise a model config option or legacy `models` metadata.
- `set model <id>`: validates against the connected session's advertised models, uses `session/set_config_option` for model config options, and preserves `session/set_model` for explicitly advertised legacy models. Claude Code still accepts or rejects selectors absent from its advertised list.
- Model switches can change or remove reasoning-effort controls. ACPX reconciles saved config selections with the accepted response; select a supported effort again if needed.
- After reconnect, ACPX restores the saved model when advertised and replays saved config selections before prompting. A replay failure is reported instead of silently using defaults.
- `set-mode`/`set` route through queue-owner IPC when active, otherwise reconnect directly.

### Sessions

Use `acpx --format json pi sessions watch -s reviewer` to observe another client's work. Resume with `--cursor` using the last event's opaque cursor. Watchers do not cancel turns when stopped; use `turn_result` for the settled outcome, and treat owner-loss errors as unknown outcomes rather than retry instructions. See [Watching sessions](https://github.com/openclaw/acpx/blob/main/docs/session-watch.md).

```bash
acpx sessions
acpx sessions list
acpx sessions list --filter-cwd .
acpx sessions list --cursor <cursor>
acpx sessions list --local
acpx sessions new
acpx sessions new --name backend
acpx sessions ensure
acpx sessions ensure --name backend
acpx sessions close
acpx sessions close backend
acpx sessions show
acpx sessions history --limit 20
acpx sessions export backend --output backend-session.json
acpx sessions import backend-session.json --name backend-restored
acpx sessions prune --dry-run --older-than 7
acpx sessions prune --older-than 30 --include-history
acpx status

acpx codex sessions
acpx codex sessions new --name backend
acpx codex sessions ensure --name backend
acpx codex sessions close backend
acpx codex sessions show backend
acpx codex sessions history backend --limit 20
acpx codex sessions export backend --output backend-session.json
acpx codex sessions import backend-session.json --name backend-restored
acpx codex sessions prune --before 2026-04-01 --include-history
acpx codex status
```

Behavior:

- `sessions` and `sessions list` are equivalent
- `sessions list` uses ACP `session/list` when the agent advertises it; JSON
  includes agent `SessionInfo`, `_meta`, and `nextCursor`
- `sessions list --filter-cwd <dir>` applies the ACP cwd filter, and
  `--cursor <cursor>` requests a specific page
- `sessions list --local` reads saved acpx records instead
- `new` creates a fresh session for the current `(agentCommand, cwd, optional name)` scope
- `new --name <name>` targets a named session scope
- when `new` replaces an existing open session in that scope, the old one is soft-closed
- `ensure` returns the nearest matching active session for the scope, or creates one when none is open. Idempotent — safe to call before every prompt in scripts.
- `close` targets current cwd default session
- `close <name>` targets current cwd named session
- `show [name]` prints stored metadata for that scoped session
- `history [name]` prints stored turn history previews (default 20, use `--limit`)
- `export [name] --output <path>` writes a portable JSON archive containing session state and event history
- `import <archive>` creates a fresh local record, reopens the copied session as idle, keeps the provider session id, and clears source-machine process metadata
- imported sessions must resume that provider session; if the destination agent cannot load it, prompts fail clearly instead of starting an empty conversation
- `import --name <name>` and `--cwd <dir>` override the destination scope; import fails if that scope already has an active session or another local record already uses the same provider session id
- `prune` deletes closed session records to reclaim disk space
  - `--dry-run` previews what would be deleted without touching disk
  - `--older-than <days>` and `--before <date>` filter by close time, falling back to last-used time when a record was never explicitly closed
  - `--include-history` also removes per-session event stream files (otherwise only the JSON record is removed)

## Global options

- `--agent <command>`: raw ACP agent command (escape hatch)
- `--cwd <dir>`: working directory for session scope (default: current directory)
- `--approve-all`: approve tool permission requests not resolved by a per-tool policy
- `--approve-reads`: approve remaining read/search requests and prompt for other tools (default mode)
- `--deny-all`: deny tool permission requests not resolved by a per-tool policy
- `--non-interactive-permissions <policy>`: choose `deny` or `fail` when the permission mode requires a prompt but stdin or stderr is not a TTY
- `--permission-policy <json-or-file>` / `--policy`: per-tool ACP permission rules that take precedence over the permission mode
- `--format <fmt>`: output format (`text`, `json`, `quiet`)
- `--json-strict`: strict JSON mode; requires `--format json` and suppresses non-JSON stderr output
- `--suppress-reads`: suppress raw read-file contents while preserving the selected format
- `--timeout <seconds>`: response deadline (positive number); partial text is not completion. A final response during the bounded drain keeps its actual outcome. Queued prompts wait for cancellation and retirement of unfinished connections before reconnecting to the saved session; cleanup can extend elapsed time beyond the deadline.
- `--ttl <seconds>`: queue owner idle TTL before shutdown (default `300`, `0` disables TTL)
- `--model <id>`: request an agent model during session creation; non-Claude agents must advertise a model config option or legacy `models` metadata
- `--system-prompt <text>`: replace the agent system prompt. Forwarded to claude-agent-acp via ACP `_meta.systemPrompt`; persisted in `session_options.system_prompt` so reuse keeps the override. Other agents ignore the field.
- `--append-system-prompt <text>`: append text to the agent system prompt. Forwarded to claude-agent-acp via ACP `_meta.systemPrompt.append`; same persistence rules as `--system-prompt`. Both system-prompt flags also apply to persistent and isolated ACP nodes in `flow run`.
- `--allowed-tools <list>`: comma-separated tool list forwarded to the adapter; for Claude it pre-approves listed tools (`""` adds no approvals, rather than disabling tools)
- `--max-turns <count>`: cap session turn count
- `--prompt-retries <count>`: retry failed prompt turns on transient errors (default `0`); cancelling a queued turn stops remaining attempts, while already-received final responses keep their outcome
- `--no-fs`: disable ACP filesystem callbacks so compatible agents use their native file operations. This is a protocol callback policy, not an OS sandbox for the agent's own filesystem access.
- `--no-terminal`: disable the ACP terminal callback. This is a protocol callback policy, not an OS sandbox for the agent's own process access.
- `--verbose`: verbose ACP/debug logs to stderr

Cursor may advertise bracketed model IDs such as `composer-2.5[fast=false]`. An exact
advertised ID always wins. Only Cursor accepts a bare model name through a unique
advertised bracketed variant; unknown or ambiguous names are rejected before the
model change. This rule uses the current connected catalog for `set model`, embedded
`setModel`, and model selections through `setConfigOption`, including after reconnect.

An acknowledged model alias is saved as the resolved current model ID; its original spelling remains the preference replayed on reconnect. Non-list config catalogs are treated as acknowledgements, while an explicit empty list withdraws the catalog.

Permission flags are mutually exclusive.

## System prompt override (Claude)

`--system-prompt` and `--append-system-prompt` let you specialize a Claude session without leaving lingering one-off state, while still benefiting from persistent session reuse.

```bash
# Create a named session with a replacement system prompt.
acpx --system-prompt "You are a code reviewer who challenges every implicit assumption." \
  claude sessions new --name review
acpx claude -s review 'review the current diff'

# Create a named session with an appended guideline.
acpx --append-system-prompt "Always explain trade-offs before recommending a fix." \
  claude sessions new --name impl
acpx claude -s impl 'implement the requested change'
```

Run the creation command once, then select the session with `-s` for later prompts. Running `sessions new` again in the same scope closes the prior local record and creates a fresh session.

The override is forwarded via ACP `_meta.systemPrompt` (or `_meta.systemPrompt.append`) on `session/new`, `session/load`, and `session/resume` and stored in `session_options.system_prompt`. Subsequent `prompt`/`ensure` calls in the same scope keep the override unless you explicitly create a new session. Non-Claude adapters ignore the field, so the same flag is safe inside cross-agent scripts.

Embedded `SessionAgentOptions.systemPrompt` preserves raw nonempty replacement or append text through saving and reconnecting, including whitespace-only text. CLI prompt flags trim surrounding whitespace and reject whitespace-only values.

## Claude settings isolation

Built-in `acpx claude` sessions load Claude project and local settings, but not
user settings. The same isolation and saved session options apply on creation,
load, and resume. This prevents globally enabled channel and daemon plugins from
claiming singleton external resources in an ACP-spawned session.

Set `ACPX_CLAUDE_INCLUDE_USER_SETTINGS=1` only when the spawned session needs
the user's global Claude settings and no such plugin conflict exists. Ambient
credentials and other environment variables are still inherited normally.

On Windows, native Claude executable discovery resolves relative `PATH` entries
from the selected session cwd. An explicit `CLAUDE_CODE_EXECUTABLE` still takes
precedence.

## Sessions cleanup

Closed session records accumulate on disk by default. Use `sessions prune` to enforce retention:

```bash
# Preview what would be deleted (no writes)
acpx codex sessions prune --dry-run --older-than 7

# Remove records closed more than 30 days ago, including their event-stream files
acpx codex sessions prune --older-than 30 --include-history

# Remove everything closed before a date
acpx codex sessions prune --before 2026-04-01
```

Without `--include-history`, only the lightweight JSON record is removed; event-stream files are preserved for audit. With it, the per-session event log is also deleted to reclaim disk space.

## Config files

Config files are merged in this order (later wins):

- global: `~/.acpx/config.json`
- project: `<cwd>/.acpxrc.json`

Supported keys:

- `defaultAgent`
- `defaultPermissions` (`approve-all`, `approve-reads`, `deny-all`)
- `nonInteractivePermissions` (`deny`, `fail`)
- `ttl` (seconds)
- `timeout` (seconds or `null`)
- `format` (`text`, `json`, `quiet`)
- `agents` map (`name -> { argv: [executable, ...args] }`); structured argv is required on Windows, and legacy `{ command, args }` entries migrate automatically
- `auth` map (`authMethodId -> credential`)

Use `acpx config show` to inspect the resolved config and `acpx config init` to create the global template.

For ACP `authenticate` handshakes, use either config `auth` entries or explicit
`ACPX_AUTH_<METHOD_ID>` environment variables such as `ACPX_AUTH_OPENAI_API_KEY`.
Ambient provider env vars such as `OPENAI_API_KEY` are still passed through to
child agents by default, but they do not trigger ACP auth-method selection on
their own.

## Embedded environment inheritance

Embedding hosts can set `agentProcessEnvAllow` or `agentProcessEnvBlock` on
`createAcpRuntime()` to keep parent variables, such as the host's own service
credentials, out of agents, command probes, and ACP terminals. Both are off by
default and the CLI does not set them, so without them children inherit the
whole environment as before.

- `agentProcessEnvAllow` keeps only process essentials (`PATH`, `HOME`, locale,
  temp and similar names) plus the listed names. An empty list keeps only the
  essentials and explicit grants.
- `agentProcessEnvBlock` removes the listed names, applied after the allowlist.
- Explicit grants still apply: `ACPX_AUTH_<METHOD_ID>` variables pass through
  both lists, and `agentProcessEnv`, saved session `env`, and a terminal
  request's own `env` are layered on top.

A filtered provider key does not reach the agent. Agent-specific environment
authentication that ACPX selects itself, such as Grok Build's `XAI_API_KEY` for
`xai.api_key`, is skipped as well when the key is filtered. Keep such a key by
listing it in the allowlist and leaving it off the blocklist, or pass it as an
`ACPX_AUTH_*` grant (for Grok Build, `ACPX_AUTH_XAI_API_KEY`). See
[`docs/runtime-environment.md`](https://github.com/openclaw/acpx/blob/main/docs/runtime-environment.md)
and [`agents/GrokBuild.md`](https://github.com/openclaw/acpx/blob/main/agents/GrokBuild.md).

## Devin ACP compatibility

The `devin` shortcut runs the installed Devin CLI. Authenticate with Devin before starting sessions:

```bash
acpx devin exec 'summarize this repo'
```

`acpx --model <id> devin` uses the advertised ACP model config option. Raw `--agent` overrides remain available for launch flags. Devin launches retain their scoped Windsurf-compatible identity; see [`agents/Devin.md`](https://github.com/openclaw/acpx/blob/main/agents/Devin.md) for detection, version overrides, capabilities, and extension handling.

## Session behavior

Persistent prompt sessions are scoped by:

- `agentCommand`
- absolute `cwd`
- optional session `name`

Persistence:

- Session records are stored in `~/.acpx/sessions/*.json`.
- `-s/--session` selects a named conversation; create it with `sessions new --name <name>` or `sessions ensure --name <name>` before prompting.
- Changing `--cwd` changes scope and therefore session lookup.
- Records saved under an earlier default command of a built-in agent, such as a previous ACPX-owned adapter range, are read as that agent's current command, so they stay in scope after an ACPX upgrade. Records with a custom launcher keep it. When both an upgraded and a current record match a scope, the most recently used one wins.
- closed sessions are retained on disk with `closed: true` and `closedAt` until pruned.
- auto-resume by scope skips closed sessions.

Resume behavior:

- Prompt mode attempts to reconnect to saved session.
- If adapter-side session is invalid/not found, `acpx` creates a fresh session and updates the saved record.
- explicitly selected session records can still be resumed via `loadSession` even if previously closed.
- dead saved PIDs are detected and reconnected on the next prompt.
- each completed prompt stores lightweight turn history previews in the session record.

## Prompt queueing and `--no-wait`

Queueing is per persistent session.

- The active `acpx` process for a running prompt becomes the queue owner.
- Other invocations submit prompts over local IPC.
- On Unix-like systems, queue IPC uses `/tmp/acpx-<home-hash>/<session-hash>.sock`.
- Ownership is coordinated with a lease file at `~/.acpx/queues/<session-hash>.lock`.
- Persistent turns retain a separate filesystem guard through checkpoint and cleanup. Waiting can be cancelled; admitted ownership ends only after cleanup. Live guards never expire by age, and ambiguous guard state is preserved. See [session ownership](../../docs/sessions.md#queue-ownership).
- On Windows, named pipes are used instead of Unix sockets.
- after the queue drains, owner shutdown is governed by TTL (default 300s, configurable with `--ttl`).
- Session controls use the retained adapter while active or idle, and the owner saves accepted settings before success. Idle controls keep the owner alive; prompt completion and close drain admitted controls. Treat timed-out settings as uncertain and do not repeat them automatically. Older callers or retained owners keep their previous persistence behavior.

Submission behavior:

- Default: enqueue and wait for queued prompt completion, streaming updates back.
- `--no-wait`: enqueue and return after queue acknowledgement.
- `Ctrl+C` during an active turn sends ACP `session/cancel`, waits briefly, then force-kills only if cancellation does not finish in time.
- `cancel` sends the same cooperative cancellation without requiring terminal signals.

## Output formats

Use `--format <fmt>`:

- `text` (default): human-readable stream with updates/tool status and done line
- `json`: NDJSON event stream (good for automation)
- `quiet`: final assistant text on stdout; failed prompts emit one structured `[acpx] error:` line on stderr
- `--suppress-reads`: replace raw read-file contents with `[read output suppressed]` in `text` and `json` output
- `--json-strict`: pair with `--format json` to suppress non-JSON stderr noise (logs, banners) for downstream consumers

Example automation:

```bash
acpx --format json codex exec 'review changed files' \
  | jq -r 'select(.method=="session/update") | .params.update
           | select(.sessionUpdate=="tool_call" or .sessionUpdate=="tool_call_update")
           | [(.status // "-"), (.title // "-")] | @tsv'
```

## Permission modes

For ACP tool permission requests, per-tool policy takes precedence over the permission mode. Rules are evaluated in this order: `autoDeny`, `autoApprove`, `escalate`, then `defaultAction`. The selected mode applies only when the policy does not resolve the request.

- `--approve-all`: approve remaining tool permission requests without prompting
- `--approve-reads` (default): approve remaining read/search requests and prompt for other tools
- `--deny-all`: deny remaining tool permission requests
- `--non-interactive-permissions <deny|fail>`: choose what happens when the mode requires a prompt but stdin or stderr is not a TTY
- `--policy <json-or-file>`: match ACP tool permission requests by kind, title, title head, or raw input tool name

An `autoApprove` rule can approve a tool request under `--deny-all`. An `escalate` rule can prompt under `--approve-all` when stdin and stderr are TTYs. Without both TTYs, escalation denies or cancels the current request and adds `_meta.acpx.permissionEscalation` to the ACP response, even when `--non-interactive-permissions fail` is selected.

For Claude, native approvals from `sessionOptions.allowedTools`, skill `allowed-tools`, and other settings can resolve calls before these ACP permission controls; see the [Claude approval boundary](../../agents/Claude.md#permission-approval-boundary) for native enforcement guidance.

Per-tool policies govern `session/request_permission`. ACP filesystem and terminal operations use the client's permission mode separately; a tool approval does not bypass those operation checks.

If every permission request is denied/cancelled and none approved, `acpx` exits with permission-denied status.

Interactive tool, file-write, and terminal questions are serialized within each acpx process. Each question needs a separate answer; EOF denies active and waiting questions.

Pending permissions, file operations, and terminal creation retain their admitting prompt's lifetime. Cancelled, completed, timed-out, superseded, or closed prompts cannot gain new mutation/spawn authority from late answers. Individual ACP request cancellation stays local to that request. Already-dispatched OS work is not rolled back.

When a tool kind is absent, read/search approval is inferred only from complete leading action words. Filenames or substrings in other action titles do not grant approval; ambiguous titles still require the normal permission decision.

Codex refusals prefer an offered non-cancelling one-time rejection. If safe refusal uses cancellation, acpx explains the possible turn termination in text output, quiet stderr, JSON response `_meta.acpx.permissionNotice`, and embedded status events. Permission denial never grants the requested operation. See [Codex](https://github.com/openclaw/acpx/blob/main/agents/Codex.md#permission-refusals).

## Flows (multi-agent workflows)

Flows let you declare a multi-agent workflow as a graph of typed nodes connected by edges, executed by the `acpx` runtime. The runtime owns persistence, retries, timeouts, and routing — the flow file declares the shape, not the engine.

### Run a flow

```bash
acpx flow run ./my-flow.flow.ts --input-file ./flow-input.json
acpx flow run ./my-flow.flow.ts --input-json '{"task":"FIX: add a regression test"}'
acpx --approve-all flow run examples/flows/pr-triage/pr-triage.flow.ts \
  --input-json '{"repo":"openclaw/acpx","prNumber":150}'
acpx flow run ./my-flow.flow.ts --default-agent claude
```

Run artifacts persist under `~/.acpx/flows/runs/<runId>/`. Default per-step timeout is 15 minutes when `--timeout` is unset; flows that declare permission requirements fail fast before starting.

Each attempt has one deadline across preparation, execution, and parsing. Callbacks receive `context.signal`; function actions also receive `context.runShell` to run commands whose processes and output streams the runner owns. A command's own timeout returns partial output with `timedOut: true`; enclosing node cancellation rejects after cleanup and prevents later managed commands. Admitted runtime writes finish before routing or final publication. Check the signal around custom asynchronous side effects: arbitrary JavaScript cannot be forcibly stopped, and completed effects are not rolled back. Persistent reconnects must load the same backend session or fail. See `docs/flows.md` for the command result and cancellation contracts.

ACP nodes accept `modelId` and ordered `configOptions: [{ configId, value }]` before the first prompt. Global `--model` and repeatable `flow run --config-option key=value` provide defaults; node modelId wins and node config options execute last. Legacy node `model` metadata remains inert. Persistent handles pin their first settings, inherit omitted fields, and reject conflicting explicit settings. Use separate handles for different selections. Inspect bundle `requestedSettings` / `acceptedSettings` and adapter response receipts; `model.applied: false` means no model catalog was available. Option `reported: false` marks an acknowledged request whose accepted value was not supplied by the adapter catalog. Persistent creation controls are recorded in binding and step settings evidence, not in `events.ndjson`.

### Authoring a flow

The authoring surface lives in `acpx/flows`. The minimal example:

```ts
import { acp, decision, decisionEdge, defineFlow, checkpoint, extractJsonObject } from "acpx/flows";

const choices = ["bug", "feat", "doc"] as const;

export default defineFlow({
  name: "pr-triage",
  startAt: "classify",
  nodes: {
    classify: decision({
      choices,
      question: ({ input }) =>
        `Classify the PR description below. Reply with one of: ${choices.join(", ")}.\n\n${input.description}`,
    }),
    bug_lane: acp({
      prompt: ({ outputs }) =>
        `The PR is a bug. Write a regression test that reproduces it.\n\nDecision context: ${JSON.stringify(outputs.classify)}`,
      parse: (text) => extractJsonObject(text),
    }),
    feat_lane: acp({
      prompt: () => "List acceptance criteria for the feature, one bullet per criterion.",
    }),
    doc_lane: checkpoint({
      summary: "doc change — needs human review",
      run: ({ outputs }) => ({ route: "doc", note: outputs.classify }),
    }),
  },
  edges: [
    decisionEdge({
      from: "classify",
      choices,
      cases: {
        bug: "bug_lane",
        feat: "feat_lane",
        doc: "doc_lane",
      },
    }),
  ],
});
```

### Node types

| Type                                    | Purpose                                                                                                                                                          |
| --------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `acp({ prompt, parse?, agent?, cwd? })` | Model-driven step. The `prompt` builder receives `{ input, outputs }`. Optional `parse` coerces the raw text (e.g., `extractJsonObject`).                        |
| `decision({ choices, question })`       | Constrained-choice LLM step. `choices` is a `readonly` tuple; the runtime validates the model's reply against it and TypeScript infers the union from `choices`. |
| `action(...)`                           | Runtime-supervised deterministic operation: shell, GitHub API, test execution, comment posting.                                                                  |
| `compute(...)`                          | Pure local data transform: normalization, routing key derivation, signal reduction.                                                                              |
| `checkpoint({ summary, run })`          | Pause point for human or external trigger. `run` returns the outcome to record while paused.                                                                     |

### Edge shapes

```ts
// Linear edge
{ from: "node", to: "next" }

// JSONPath switch — non-decision routing
{
  from: "node",
  switch: {
    on: "$.route",
    cases: { "value-a": "branch_a", "value-b": "branch_b" },
  },
}

// Decision edge — exhaustive at compile time
decisionEdge({
  from: "classify",
  choices,                                 // same readonly tuple as decision()
  cases: {                                 // every choice must map to a node id
    bug: "bug_lane",
    feat: "feat_lane",
    doc: "doc_lane",
  },
})
```

If a `decisionEdge` omits a case from `choices`, the TypeScript compiler refuses to compile — so a flow can't ship with a forgotten branch when new choices are added.

### Why use flows

- **Cross-vendor by construction**: classify with `codex`, write code with `claude`, summarize with `gemini` — same flow file, no glue.
- **Persistence and replay**: every run streams events to disk, replayable via the flow viewer under `~/.acpx/flows/runs/`.
- **Permission preflight**: flows declaring permission requirements fail before any agent starts, instead of mid-run.
- **Typed routing**: the LLM is constrained to a literal union, the compiler verifies exhaustivity, the runtime validates the reply.

See `examples/flows/` in the repo for working samples (`branch.flow.ts`, `pr-triage/`, `two-turn.flow.ts`, `shell.flow.ts`, `workdir.flow.ts`).

## Practical workflows

For explicit `prompt` and `exec`, an agent-level `--file` is inherited unless the subcommand supplies its own file. Agent-level `--no-wait` also applies to `prompt`. Session-list options work on `sessions` or `list`, with explicit child values taking precedence. Watch inherits the agent's session selector unless `watch --name`/`-s` overrides it; its `--cursor` stays local to watch and never inherits the list pagination cursor.

Persistent repo assistant:

```bash
acpx codex 'inspect failing tests and propose a fix plan'
acpx codex 'apply the smallest safe fix and run tests'
```

Parallel named streams:

```bash
acpx codex -s backend 'fix API pagination bug'
acpx codex -s docs 'draft changelog entry for release'
```

Specialized Claude reviewer that survives session reuse:

```bash
acpx --system-prompt "You are a reviewer who refuses to approve untested changes." \
  claude sessions new --name reviewer
acpx claude -s reviewer 'review the diff in src/auth/'
```

Idempotent session bootstrap (safe to call before every prompt in scripts):

```bash
acpx codex sessions ensure -s ci
acpx codex -s ci 'run the smoke suite and report failures'
```

Queue follow-up without waiting:

```bash
acpx codex 'run full test suite and investigate failures'
acpx codex --no-wait 'after tests, summarize root causes and next steps'
```

One-shot script step:

```bash
acpx --format quiet exec 'summarize repo purpose in 3 lines'
```

Machine-readable output for orchestration:

```bash
acpx --format json --json-strict codex 'review current branch changes' > events.ndjson
```

Raw custom adapter command:

```bash
acpx --agent './bin/custom-acp-server --profile ci' 'run validation checks'
```

Periodic cleanup:

```bash
acpx codex sessions prune --dry-run --older-than 14
acpx codex sessions prune --older-than 30 --include-history
```

Multi-agent triage flow:

```bash
acpx --approve-all flow run ./pr-triage.flow.ts --input-json '{"prNumber": 842}'
```

Repo-scoped review with permissive mode:

```bash
acpx --cwd ~/repos/shop --approve-all codex -s pr-842 \
  'review PR #842 for regressions and propose minimal patch'
```
