# awf commands

`awf` is the operator's command: you run workflows, follow them and answer them with it. An agent
inside a run never types `awf`; it answers with [`wfa`](/guide/how-it-works#the-words).

Each command's help below is the file `awf {command} -h` prints, included as it is, so this page
and the command can't disagree. The prose around it says what the command is for and which page
teaches it.

## Help {#help}

```sh
awf -h               # the commands, grouped; also awf, awf help, awf --help
awf help run         # one command's usage, options, exit codes; awf run -h
awf help reference   # every command's usage lines, on one page
awf --version
```

Every command takes `-h` or `--help` anywhere before `--`, and prints its help whatever else is
given. A word that must be `-h` itself, such as a message's text, goes after `--`.

An unknown command exits 2 and names the commands it is closest to; a workflow file given as a
command gets the `awf run` line it meant.

## JSON {#json}

With `--json`, stdout always ends with one document, but for `awf inbox`, which prints none when
it has no items to read. When the command worked, it is the result. When it failed, it is a
refusal, and stderr still says why, for people:

```json
{"v": 1, "code": "no-run", "error": "no run review-mr/7 under /work/.awf/runs, or in a project awf knows"}
```

A document with `code` is a refusal; no result has one. Check for it first, as the exit code
doesn't tell them apart: `awf emit` exits 1 with a full result when the run's attempt didn't
start, and `awf tick` exits 1 with the runs it woke when it couldn't wake some. A command that
prints a line per item, as `awf inbox --json` does, ends with its refusal. A usage error is a
refusal too, whenever `--json` is anywhere in the arguments before `--`. The exit codes are each
command's own, as its help says. A project that installs an `agentswf` from before this rule runs
on it, and prints what it printed: there, an empty stdout is a failure stderr explains.

A code means the same in every command that prints it, and, where the client meets the same
failure, is the word [`ClientError`](/reference/client-api#clienterror) throws for it:

| `code` | The command failed because | Printed by |
| --- | --- | --- |
| `usage` | its arguments can't be parsed, or two of them clash | every command |
| `no-run` | there is no such run | `run`, `emit`, `runs`, `logs`, `inbox`, `reply`, `send` |
| `ambiguous` | a name fits more than one: a run's id, runs of two workflows or projects; or `--as`, an agent or `--to`, those with `choices` | `emit`, `runs`, `logs`, `inbox`, `reply`, `send` |
| `id` | a run or event id the grammar refuses, one the workflow's `id(args)` returns, or one another run has | `run`, `emit`, `runs`, `logs`, `inbox`, `reply`, `send` |
| `file` | the workflow file is missing, doesn't load, or doesn't export the workflow | `run`, `emit` |
| `event` | the workflow doesn't take the event's name, data or source, or takes no events | `emit` |
| `prepare` | the workflow refuses its arguments or options, or its `id(args)` throws | `run`, `emit` |
| `no-id` | a workflow that starts on events, with no `--id` and no `id(args)` | `run` |
| `other-args` | a run there, started with other arguments or options | `run` |
| `not-started` | no attempt started: Ctrl-C, a session not taken, a claim given up, or a run that is running or completed, or whose working folder is gone | `run` |
| `unreachable` | the run's files, a new run's folder included, can't be read or written, or it doesn't answer | `run`, `emit`, `runs`, `logs`, `inbox`, `reply`, `send` |
| `cursor` | a cursor or handle doesn't fit: made for another run or filter, past the run's end, or naming no event | `logs`, `inbox` |
| `no-engine`, `other-engine` | the project's `agentswf` is missing, or another version than this awf | `run`, `emit`, `runs`, `logs`, `inbox`, `reply`, `send` |
| `project` | the workflow isn't in a project | `run`, `emit` |
| `internal-error` | something failed that is neither the caller's nor the run's, such as awf's own record of projects; `error` says what | every command |

`awf reply` and `awf send` also refuse for the [participant's reasons](#reply), and `awf inbox`
with `no-participant`, a run that never connected the one asked for.

A command's `--json` carries `"v"`, which changes when a field is renamed or removed. It is 1 for
every command but these: `awf runs {run} --json` is at 2, as its agent and stage states are
[a fixed set](#runs); `awf inbox --json` is at 2, as it took the shared `no-run` and `cursor`;
and `awf send --json` and `awf reply --json` are at 3, as [their success](#reply) lost its
`code`. A refusal carries its command's `"v"`. `awf run --json` prints the run's `out.json`, whose
own `version` says its format, and its refusals carry `"v": 1`.

## Durations {#durations}

`--timeout` and `--wait` take a duration, as a workflow does: a decimal and `s`, `m`, `h` or `d`,
such as `90s`, `1.5h` or `1d`.

## Run a workflow

### awf run {#run}

Starts a run of a workflow file, or continues one with `--id` and `--continue`.
[Quick start](/guide/quick-start) runs one for the first time; [Runs](/guide/runs) covers ids,
attempts, stages, `--continue`, `--from-stage` and `--values`, and owns the exit codes of an
attempt. `--timeout` is on [Timeouts](/guide/timeouts), `--sandbox` on
[Sandboxes](/guide/sandboxes), `--here` on [Calling session](/guide/calling-session), and
`--workspace` and `--keep-panes` on [Harnesses](/guide/harnesses#where-panes-go).

```text
usage: awf run [options] <workflow-file> [options] [-- workflow arguments...]
       awf run [options] <workflow-file> --id <id> --continue
run options: --id <id>, --continue, --from-stage <stage>, --values <file>,
             --timeout <duration>, --run-root <directory>, --cwd <directory>, --sandbox <file>,
             --workspace <run|origin|name>, --keep-panes <never|on-failure|always>,
             --json, --quiet, --no-watch, --here

Each awf run is an attempt of a run: a new run, with --id's id or a generated one, or, with
--continue, the run --id names, with the arguments, --cwd, --sandbox, --workspace and
--keep-panes it was started with. A continue reuses the stages that succeeded, up to the first
with no record or to --from-stage, and runs the rest.
A new run with --from-stage starts there. A stage before it with no record to reuse takes its
value from --values, a JSON object by stage name, checked by its result; one that returns
nothing is passed. Without one the attempt stops, naming the stage and its schema.
A run is kept in .awf/runs/<workflow>/<id> under --cwd, unless --run-root names another folder
for .awf/runs. A workflow that gives file: import.meta.path keeps its runs in its file's project
unless --run-root names another folder: the nearest directory at or above the file holding .awf/,
else the file's own, so --continue finds a run a program started there from anywhere.
A run has no deadline unless --timeout gives it one, per attempt: a duration, a decimal and s, m,
h or d, such as 20m or 1d.
A workflow that knows how to present its result prints that; --json prints the full result instead.
Either way the full result is kept as out.json in the run's folder, beside report.md
when the workflow writes one. A run that fails or is cancelled once its agents have started keeps
out.json too, with what it spent and why it ended; --json prints it. When no run comes of it,
--json prints a refusal whose code says why: usage, file, prepare, no-id, id, no-run,
other-args, not-started, unreachable, project, no-engine, other-engine or internal-error;
--here takes no --json, nor does opening a run of a workflow with events, which prints no
document. A second Ctrl-C stops awf at once, without it; a continue then reads
what its agents spent.
Progress, the workflow's log and how the run ended go to stderr, and only the result to stdout.
--quiet, for a scheduler or supervisor, leaves out the progress and the log: a run that completes
or parks writes nothing to stderr, and one that doesn't says how it ended. awf's own problems and
a person's question still print. awf logs reads everything left out.
--cwd sets the directory the workflow and its agents work in; it defaults to the current one.
--sandbox puts every agent of the run in one sandbox, working in --cwd; the file is a JSON spec
such as {"read": ["/data/request.md"], "srt": {}}, naming srt or docker, whose relative paths are
from --cwd. A workflow that opens a sandbox of its own is refused.
A sandbox with its own Herdr, as a docker box has, gets a tab in the run's workspace showing its
panes; --no-watch leaves it out, and awf still prints the command that shows them.
--workspace says where each TUI agent's tab opens: run, the run's own Herdr workspace (the
default); origin, the one awf run was typed in; or any other word, the workspace with that label,
found or made. --keep-panes says whether an agent's pane stays once it is done: never (the
default), on-failure, when its last turn wasn't answered, or always. A kept pane's harness is left
running, and yours. Both are kept with the run for every attempt, a woken one too. A run an event
started has neither, and takes the defaults. A continue may give either again with the run's
value, or its default where the run had none, and no other.
--here, run by an agent in a Herdr pane, starts the run in a new tab and has it take that agent's
session over as one of its agents, from its next turn until the run ends. It prints a line for
the agent to end its turn with, which is how the run finds the session.
A run of a workflow with events is named by --id, or by the workflow's id(args); one without
starts may have an id made, as any run. awf run starts it with its arguments and no event,
detached in its project, as awf emit would, and the run waits at its first receive. A run already
there is only woken. A run an event starts has no argv: its args are what prepare makes of none.
Its --continue takes --from-stage, as any run's does, and no --values. A --from-stage the attempt
never reaches runs the attempt until it would park or return, and stops it there.
Exits 0 when the run completed or parked, 1 when it failed or timed out, 2 on a usage error or
when the run is refused, 3 when it stopped and can go on with --continue, and 128 plus the
signal's number when it was interrupted.

Examples:
  awf run examples/minimum-review/review-loop.ts
  awf run --timeout 20m examples/minimum-review/review-loop.ts
  awf run examples/minimum-review/review-loop.ts -- packages/engine/src
  awf run examples/minimum-review/review-loop.ts --cwd ../other-repo
  awf run examples/pane-layout/workflow.ts --workspace origin --keep-panes on-failure

Workflow files are trusted code and run with your filesystem and process authority.
```

### awf test {#test}

Runs a workflow's tests, each agent replaced by scripted answers: no agent starts, nothing is
metered. [Testing](/guide/testing) teaches writing them; the [Testing API](/reference/testing-api)
lists the calls.

```text
usage: awf test [paths...] [-t <pattern>] [--watch] [--timeout <duration>]

Runs a workflow's tests (*.test.ts and *.spec.ts files) under the current directory, or under
each file or directory given, with agentswf/workflow, agentswf/testing and typebox served as
awf run serves them: nothing is installed. -t runs the tests whose names match the pattern;
--watch reruns them on a change; --timeout bounds each test, 5s by default.

Exits 0 when every test passed, 1 when one failed or none was found, 2 on a usage error. What
it prints is for reading, not parsing.
```

### awf init {#init}

Sets up a folder for writing workflows, with a `hello` workflow and its test to start from.
[Quick start](/guide/quick-start) begins with it.

```text
usage: awf init [dir] [--no-install]

Sets up the current directory, or dir, for writing workflows: hello/workflow.ts, a workflow
with one agent, and hello/workflow.test.ts, its test; package.json, tsconfig.json, .gitignore
and AGENTS.md. Then runs bun install, unless --no-install, for editor types: awf runs a
workflow against its own copies either way.

Never overwrites. A file that exists is skipped, except that package.json gains the
devDependencies and scripts it lacks and .gitignore the lines it lacks.

Exits 0; 1 when a file could not be written, or bun install failed (the files written are
kept); 2 on a usage error.
```

### awf emit {#emit}

Sends an event to a workflow with events, starting its run or waking the one that waits; or, by
the run's ref, to a run that is there, as a participant's message to its code, `{key}:reply`.
[Events](/guide/events) covers workflows with events; [Hosting events](/guide/hosting-events)
calls `awf emit` from a webhook or a CI job.

```text
usage: awf emit --target <file> --id <id> <event> [<json>] [--event-id <id>] [--source <name>] [--json]
       awf emit <run> <key>:reply '{"text": …, "author": …}' [--event-id <id>] [--source <name>] [--cwd <directory>] [--run-root <directory>] [--json]

Sends an event to the run of a workflow with events that --id names. That run starts if it has
none and the event is one that starts one, and is woken if it has one: told while it runs,
continued when it is parked or crashed. The run lives in the project of <file>, the nearest
directory above it holding .awf/, whoever sends. --event-id names the occurrence: the same id
again is a duplicate. --source names the integration that sent it, such as github, which the
event a wait returns carries.

The second form sends a participant's message to the code of a run that is there, <workflow>/<id>
or an <id> only one workflow has, here or in a project awf knows: <key>:reply, which the workflow
declares, checked against the file the run's last attempt ran. "author" is optional. Its
--event-id is the message's own id, so a resend is a duplicate. It never starts a run, and may
arrive before the run connects the participant: it waits for the run's next wait. The first form
takes only the workflow's own events.

Prints what became of the event, and what was done to the run: accepted, duplicate or refused.
--json prints one document: the result, or a refusal whose code says why it failed: usage,
no-run, ambiguous, id, file, event, prepare, project, no-engine, other-engine, unreachable or
internal-error. Exits 0 for any of the three; 1 when <file> could not be loaded, or the run
isn't found or can't be read, and nothing is kept, or when the run's attempt could not be
started (the event is kept, and the next event or awf tick tries again); 2 on a usage error, for
a workflow with no events, or when the event is wrong for its target: its name, its data, or its
id.
```

### awf tick {#tick}

Wakes the waiting runs of workflows with events whose timers are due, and continues the ones that
crashed. Run it on a schedule: [Hosting events](/guide/hosting-events) covers that.

```text
usage: awf tick [--cwd <directory>] [--run-root <directory>] [--json]
       awf tick --all [--json]

Wakes every run of a workflow with events under --cwd's .awf/runs, or --run-root, the runs awf
runs lists there, as an event would: continues a parked run whose wait's timer is due, and a
crashed or failed one until it has failed three times in a row. Runs of other workflows are left
as they are. --all wakes them in every project awf has started one in, as ~/.awf/projects lists
them. A project's runs are woken by its own agentswf: one at another version than this awf runs
its own awf tick for them. Run it on any schedule; a run is continued once however many wakers
come at once.

Prints a line for each run it woke, and nothing when it woke none, and one line on stderr
counting runs with events it can't read, which it doesn't wake; --json prints one document,
those runs under unreadable, on exit 1 too, or a refusal whose code says why it failed: usage or
internal-error. Exits 0; 1 when a project is gone, declares agentswf without installing it, or
its own agentswf failed, or a run could not be woken, the others still woken; 2 on a usage
error.
```

## Look at runs

### awf runs {#runs}

Lists the runs here, or shows one in depth: its attempts, its stages, what each agent is doing,
and what to run next. [Following runs](/guide/following-runs) teaches it.

```text
usage: awf runs [--limit <n>] [--cwd <directory>] [--run-root <directory>] [--json]
       awf runs <run> [--cwd <directory>] [--run-root <directory>] [--json]

The runs here, newest first, or one run in depth: its attempts, the stages of the last, what
each agent is doing, and what to run next. The list shows 20 unless --limit says another
number. A run is <workflow>/<id>, or an <id> only one
workflow has. Runs are listed under --cwd's .awf/runs, or --run-root, as awf run keeps them; a
run named is found there first, then in each project awf has started a run with events in.

--json prints one document, the interface for scripts: when it fails, a refusal whose code says
why: usage, no-run, ambiguous, id, unreachable, no-engine, other-engine or internal-error. Exits
0 on any read that worked, 1 when there is no such run, 2 on a usage error, 3 when the run's
files could not be read.
```

In `awf runs {run}`, and its `--json`, an agent's state is one word, the one `wfa peers` uses:

| State | The agent |
| --- | --- |
| `idle` | has had no turn yet |
| `working` | is in a turn |
| `waiting` | is in a turn, after `wfa waiting`; `live` has its reason and check-in |
| `awaiting-reply` | is in a turn, awaiting a peer's reply; `live` names the peer and until when |
| `answered`, `unanswered`, `blocked`, `timed-out`, `failed`, `cancelled` | ended its last turn so |
| `stopped` | was stopped while in a turn |
| `interrupted` | was in a turn when the run's process went (version 1 said `was working`) |

A stage's state is `running`, `interrupted`, `reused`, `provided`, or how it ended: `succeeded`,
`stopped` or `failed`.

### awf logs {#logs}

What a run did and is doing, its events and its agents' sessions merged by time, read once or
followed. [Following runs](/guide/following-runs) teaches it, including following a run from
another agent.

```text
usage: awf logs [<run>] [@<handle>] [--agent <key>] [--stage <name>] [--after <cursor>]
                [--limit <n>] [--wait <duration> | --follow] [--full] [--cwd <directory>]
                [--run-root <directory>] [--json]

What a run did and is doing: its events and what its agents did, read from their own session
files, merged by time. With no run, the newest here. Without --after, the last 40 events; with
it, what came after, oldest first. Every answer ends with the command that gets only what is
new. --wait: with nothing to show yet, wait for the first event, at most that long, up to 5m,
such as 90s; --follow prints each event as it comes until the run ends. @<handle> names one
event, and --full prints its text, with common credential shapes masked; --json carries it
always.

With --json, a failure prints a refusal whose code says why: usage, no-run, ambiguous, id,
cursor, unreachable, no-engine, other-engine or internal-error. Exits 0 on any read that worked,
1 when there is no such run or the cursor or handle does not fit it, 2 on a usage error, 3 when
the run's files could not be read.
```

### awf open {#open}

Opens a copy of an agent's session in its harness's TUI where the agent worked, or asks it one
question with `-p`; `--no-fork` opens the session itself, the run held meanwhile.
[Following runs](/guide/following-runs#awf-open-ask-an-agent-why) teaches it.

```text
usage: awf open <run> <agent> [--no-fork] [--session <id>] [-p [<text>]]
                [--cwd <directory>] [--run-root <directory>] [--json]

A run is <workflow>/<id>, or an <id> only one workflow has, here or in a project awf knows.
Without an agent, it prints the command that opens each of the run's agents.
Opens a copy of an agent's latest session in its harness's own TUI, where the agent worked: its
working directory, its home and skills, its sandbox, on the model and effort it ran on, with the
harness's own permission prompts. The copy is made without asking the model, and is yours: the
run and the agent's session are unchanged, and awf keeps no record of it.

--no-fork opens the session itself: what you say stays with the agent, awf logs shows it, and the
next attempt resumes it. It is refused while the run is live; while it is open, no attempt starts
and an event waits, and once the harness exits a run with events is woken for it.
--session opens another session in the agent's place: a copy an earlier awf open made, as it is;
or one of the agent's earlier sessions, as its latest would be.
-p asks one question headless and prints the answer; the text is its argument, or stdin without
one. It runs as the agent's turns ran, without prompts, in its sandbox when it had one; on
claude, billed per token headless, it is refused unless the agent ran metered.
As the harness exits, awf prints the command that opens the same session again.

With --json, -p prints {"v": 1, "session", "forkedFrom"?, "reply", "again"}, or a refusal whose
code says why: usage, no-run, ambiguous, id, no-session, live, opened, unreachable, no-engine,
other-engine or internal-error. no-session is an agent with no session or operation to open;
live, --no-fork, or a sandboxed agent, on a running run, or a --no-fork whose session changed as
it held the run; opened, a run another awf open --no-fork holds, or a sandbox another awf open
has open; internal-error, a copy or a -p question that failed. A TUI has no JSON: --json without
-p is a usage error. Exits with the TUI's exit code, or 0 once -p printed its answer; 1 when
refused or failed; 2 on a usage error, or a -p or a TUI the agent's harness can't give; 3 when
the run or the agent's place could not be read, or awf itself failed.
```

## Coordinate a run from outside

### awf inbox {#inbox}

Reads what agents said to a participant outside the run, a person or an app, and what it said
back. [Answering for a participant](/guide/people) teaches it.

```text
usage: awf inbox [<run>] [--open] [--workflow <name>] [--as <key>] [--after <cursor>] [--wait <duration>]
                 [--cwd <directory>] [--run-root <directory>] [--json]

What agents said to a participant outside the run, and what it said back: routes, messages,
answers, questions that closed unanswered, and each attempt's end, oldest first. Without a run,
every run here that connected one; --workflow narrows that. A run is <workflow>/<id>, or an <id>
only one workflow has. --after reads on from a cursor the last answer gave; --wait waits up to
that long, at most 5m, when nothing is new. --open lists the questions still waiting for an
answer, a snapshot. --json prints one item per line, and when it fails a refusal last whose code
says why: usage, no-run, ambiguous, id, cursor, no-participant, unreachable, no-engine,
other-engine or internal-error.

Exits 0 on a read that worked, 1 when there is no such run, it has no participant, or the
cursor is another's, 2 on a usage error, 3 when the run's files could not be read.
```

### awf reply and awf send {#reply}

`awf reply` answers a question an agent asked; `awf send` messages an agent first. A message on a
participant to a run's code is [`awf emit {run} {key}:reply`](#emit). They share one help.
[Answering for a participant](/guide/people) teaches both.

<a id="send"></a>

```text
usage: awf reply <run> <agent> [--to <id>] [--as <participant>] [<text>]
       awf send <run> <agent> [--as <participant>] [<text>]
each takes [--cwd <directory>] [--run-root <directory>] [--json]

A run is <workflow>/<id>, or an <id> only one workflow has, here or in a project awf knows.
Answers a question an agent asked a participant outside the run, or messages an agent first,
over a route the workflow allowed. The text is the argument, or stdin without one. --to names
the question, as awf inbox prints it; it may be left out when the agent has one open. --as
names the participant when the workflow connects more than one. Neither waits for an answer:
it comes back in awf inbox. A message on a participant to the run's code is sent with awf emit
<run> <participant>:reply '{"text":…,"author":…}' --event-id <id>.

With --json, each prints {"v": 3, "id": …} when delivered, or a refusal whose code says why: the
participant's, or usage, no-run, ambiguous, id, unreachable, no-engine, other-engine or
internal-error. Exits 0 when delivered, 1 when refused (the code says why), 2 on a usage error,
3 when the run could not be reached.
```

With `--json`, each prints one line: `{"v": 3, "id": …}` when delivered, or a
[refusal](#json) with its `code` and `error`. Past the shared codes, a participant's own:
`ended`, `no-participant`, `unknown-agent`, `ambiguous` (with `choices`), `expired`, `no-route`,
`peer-stopped`, `closed`, `too-large`, `limit`, `inbox-full`, or `unsupported-version`, when the
run's engine is another version of awf than the command's, as after installing awf anew while the
run is live. Version 1 named `peer-stopped` `stopped`; version 2 said `"code": "delivered"` on
success, and `not-found` for `no-run`.

## This machine

### awf allowance {#allowance}

What is left of each harness's plan, as the harness itself shows it.
[Harnesses](/guide/harnesses) explains subscription and metered; [Cost](/guide/cost) covers a
run's time and tokens.

```text
usage: awf allowance [harness...] [--json]

What is left of each harness's plan, as the harness itself shows it: claude's /usage, as
claude -p /usage prints it; codex's /status, as its app-server answers it; and cursor's /usage,
read in a Herdr pane. pi has none of its own. Nothing asks a model. Every harness when
none is named: claude, codex, pi, cursor.

A plan's monthly price is an estimate at the vendor's list price as of the table's date:
plans 2026-10-05.

--json prints one document, the interface for scripts, with the plan as the harness names it and
no price; what it prints otherwise is for reading. A harness that could not be read says why; a
failure is a refusal whose code says why: usage or internal-error. Exits 0 once every harness
was asked, 2 on a usage error.
```
