# Dispatch agent guide

> Dispatch at rundispatch.sh, the local CLI for validating coding-agent changes. Source: github.com/rundispatch/dispatch. Binary: `dispatch`. Version 0.4.11, an experimental developer preview.

This guide is for coding agents and the people who run them: when Dispatch
fits, how to install it, how to read its verdicts, and which actions need the
user's authorization. The same page is available as Markdown at
https://rundispatch.sh/agents.md. It describes Dispatch 0.4.11.

## When to use Dispatch

Use it when:

- The repository changes while an agent works: you commit, you pull a teammate’s merge, or another agent’s Work lands.
- Several agents work in separate worktrees on one repository and land one at a time. After each landing, Dispatch judges the rest against the new code.
- You are about to integrate a change an agent made from an older snapshot, and you want to know whether it still holds, with reasons, before it lands.

It adds little when:

- One agent works on a repository nothing else changes during the task. The code did not move, so the verdict is CONTINUE.
- You need a security sandbox. Agents and checks run with your operating-system permissions.
- You want stale Work fixed for you. Dispatch does not rebase, resolve conflicts or rerun an agent by itself; it tells you to redo the Work.
- You need a hosted service or a server-side merge queue. Dispatch is a local CLI with no server.
- You need symbol-level analysis outside Rust and Python. Other languages get file-level facts: any change to a file the Work edits or names is a REFRESH.
- You are not on macOS arm64 or Linux x86_64. Those are the only prebuilt releases; other platforms are untested.

A rebase and your tests tell you whether the merge is clean and the tests pass. Dispatch also keeps the snapshot the Work started from, names the declaration it relied on that changed, and blocks `dispatch accept` until the Work is redone.

Dispatch judges the local checkout it watches. A teammate’s merge counts once you pull it into that checkout. The verdict gates `dispatch accept`, which applies Work only when it still holds. Interactions between pieces of Work are advisory and block nothing.

## Before you start

- Prebuilt releases: macOS arm64 and Linux x86_64. You need Git, and the
  tools the project's own checks use. There is no account, and nothing is
  uploaded.
- Agents need no protocol to be checked. An agent working inside a workspace
  Dispatch made or observes should just do its task. Checking and integrating
  are for whoever runs Dispatch.
- See whether Dispatch is already installed: `command -v dispatch` and
  `dispatch version`. This guide describes 0.4.11. For any other
  version, read the README at that version's tag:
  `https://github.com/rundispatch/dispatch/tree/v<version>#readme`.

## Authorization

Some Dispatch actions record a decision or acknowledge a risk on the user's
behalf. Take them only with the user's authorization. Authorization can come
from:

- the current request (for example, "integrate it if it still holds");
- an earlier explicit instruction in the session;
- a standing rule the user set, such as an instruction in their AGENTS.md
  or CLAUDE.md.

When authorization exists, act on it without asking again. When it does not,
report the verdict and the exact command the user can run.

| Action | What it does | Needs |
|---|---|---|
| `dispatch check`, `status`, `diff`, `history`, `explain`, piped `watch` | Read state; change nothing | Nothing |
| Installing Dispatch | Downloads and installs a binary | The user's request or standing permission to install tools |
| `dispatch start` / `dispatch stop` | Start or stop one background watcher for the project | The user asked you to watch the project; stop only watching you started |
| `dispatch attach -- <agent>` | Runs that agent, with the user's operating-system permissions | The user asked you to run that agent |
| `dispatch accept <run-id>` | Applies the Work if it still holds and records the user's acceptance | Authorization to integrate |
| `dispatch reject <run-id>` | Records a rejection; the source is untouched | Authorization to reject |
| `--auto-apply` | Applies eligible Work without a human review | The user turned it on |
| `--allow-unsafe-local` | Acknowledges that agents and checks run with the user's permissions; not a sandbox | The user's acknowledgment |
| `dispatch accept --despite-refresh --explanation "<why>"` | Overrides a REFRESH that comes only from the analysis | The user's decision and reason |
| `dispatch setup`, `dispatch setup --checks` | Sets up agent profiles, hooks and checks that may run by themselves | The user, interactively |
| `dispatch clean --yes` | Removes workspaces of finished Work without asking | Authorization to delete them |

## Install

Download the archive for the platform and `SHA256SUMS` from the release,
verify the checksum, then install. Stop if verification fails.

macOS arm64:

```sh
curl -LO https://github.com/rundispatch/dispatch/releases/download/v0.4.11/dispatch-macos-arm64.tar.gz
curl -LO https://github.com/rundispatch/dispatch/releases/download/v0.4.11/SHA256SUMS
grep 'dispatch-macos-arm64.tar.gz' SHA256SUMS | shasum -a 256 -c -
tar -xzf dispatch-macos-arm64.tar.gz
mkdir -p "$HOME/.local/bin"
install -m 0755 dispatch "$HOME/.local/bin/dispatch"
"$HOME/.local/bin/dispatch" version
```

Linux x86_64:

```sh
curl -LO https://github.com/rundispatch/dispatch/releases/download/v0.4.11/dispatch-linux-x86_64.tar.gz
curl -LO https://github.com/rundispatch/dispatch/releases/download/v0.4.11/SHA256SUMS
sha256sum -c SHA256SUMS --ignore-missing
tar -xzf dispatch-linux-x86_64.tar.gz
mkdir -p "$HOME/.local/bin"
install -m 0755 dispatch "$HOME/.local/bin/dispatch"
"$HOME/.local/bin/dispatch" version
```

`dispatch version` must print `dispatch 0.4.11`. The macOS binary is
unsigned and not notarized. Other platforms can build from source with
`cargo install --git https://github.com/rundispatch/dispatch --tag v0.4.11 --locked`;
they are untested.

## The shortest workflow

```sh
cd my-project
dispatch start                                      # watch this project in the background
dispatch attach --task "<task>" -- <agent command>  # prints the run ID when the agent exits
dispatch check <run-id> --json                      # preliminary verdict; read validity.decision
dispatch accept <run-id>                            # only with authorization; or: dispatch reject <run-id>
dispatch stop                                       # when the user is done watching
```

Always name the run ID. Without one, commands resolve the latest run for the
current source, which is ambiguous when several pieces of Work exist.
`dispatch history` lists recent runs with their IDs, including Claude Code
sessions that registered themselves. Piped or with `--json`, `dispatch watch`
keeps streaming until it is stopped, so do not wait for it to exit.

## Reading a verdict

`dispatch check` exits 0 for CONTINUE, REFRESH and STOP alike (and 1 only
when it cannot evaluate). The exit status is not the verdict: read
`validity.decision`.

`check` is preliminary. It asks whether the patch still applies and whether
what the Work relied on still holds. The project's checks run on the merged
code only at `dispatch accept`. A CONTINUE from `check` is not permission to
integrate.

| `validity.decision` | Meaning | Next step |
|---|---|---|
| `continue` | Nothing the Work relied on changed, as far as the analysis sees | With authorization, `dispatch accept <run-id>`. Accept can still refuse if the checks fail on the merged code |
| `refresh` | The patch no longer applies, something it relied on changed, or a check failed on the merged code | Nothing is applied. Redo the Work on current code: `dispatch refresh <run-id>` for a run Dispatch launched, or run the agent again and attach it. Or reject, with authorization |
| `stop` | The same change is already in the source (`landed_by` names the run) | Nothing is applied. Reject it, with authorization |

## The integration gate

`dispatch accept <run-id>` is the only way Work should land. It evaluates the
Work again against the code as it is now, runs the project's checks on the
merged tree when the code moved, and applies only if the Work still holds.
It applies the patch to the watched checkout's working tree and does not
commit: committing and pushing stay with the user's usual workflow. When it
refuses, it exits 1, prints the reason, and leaves the source and the review
untouched:

```text
$ dispatch accept <run-id>
error: source has changed; this work is stale (REFRESH): pub fn validate(token: &Token) -> Result<User, AuthError> => pub fn validate(ctx: &AuthContext, token: &Token) -> Result<User, AuthError>. The source was left unchanged. Run your agent again on the current source and attach it, or run 'dispatch reject 01M4MC7975JQMBESM9QN7EVP3A'.
# exit status 1
```

Do not route around it. Do not `git apply` the output of `dispatch diff`, and
do not merge or cherry-pick the `dispatch/<run-id>` branch or copy files out
of the workspace. Building or testing a copy in a scratch directory to
investigate is fine; applying it to the project is not.

`--despite-refresh --explanation "<why>"` applies a REFRESH that comes only
from the file and symbol analysis, after the project's checks pass on the
merged tree. It never overrides STOP, a patch that no longer applies, or a
failed check. Use it only on the user's decision.

## Auto-apply

Auto-apply is a supported way for Work to land without anyone running
`accept`. It is switched on explicitly: `--auto-apply` on `dispatch run`,
`dispatch refresh` or `dispatch attach`, or Shift+Tab in the interactive
session. It applies Work only when the evidence is complete for the exact
code it lands on. It records `applied_by: auto_apply` and leaves the review
pending, so it is never a human acceptance. Do not turn it on to avoid asking
for authorization.

## JSON output

`dispatch check <run-id> --json` prints one object. These were captured from
the 0.4.11 release binary (pretty-printed here):

CONTINUE:

```json
{
  "run_id": "01M4MC76XZBQNN28AWZAAHFC3A",
  "validity": {
    "analysis": "files_only",
    "changed_files": 2,
    "decision": "continue",
    "evaluated_at": "2026-10-11T02:21:12.278290Z",
    "reasons": [],
    "world_changed": true,
    "world_digest": "56a12d4ebe870bc1078205ea7aaac5844b86dd5b0da9735109002a47cff1df45"
  }
}
```

REFRESH, with the declaration that changed:

```json
{
  "run_id": "01M4MC7975JQMBESM9QN7EVP3A",
  "validity": {
    "analysis": "symbols",
    "changed_files": 1,
    "decision": "refresh",
    "evaluated_at": "2026-10-11T02:21:14.401737Z",
    "reasons": [
      {
        "code": "fact_broken",
        "detail": "pub fn validate(token: &Token) -> Result<User, AuthError> => pub fn validate(ctx: &AuthContext, token: &Token) -> Result<User, AuthError>",
        "fact_id": "6cc9cad0a4ec78b4",
        "path": "src/auth.rs"
      }
    ],
    "world_changed": true,
    "world_digest": "f4d56c0467c1cf486407268db74cb230a11a11dd1085c60f91e46a60cc4ae547"
  }
}
```

STOP, with the run that already landed the change:

```json
{
  "landed_by": "01M4MC7BA1614YQ7S110PNEN4Y",
  "run_id": "01M4MC7CP80ZBSRS8TPY1AEPGR",
  "validity": {
    "analysis": "files_only",
    "changed_files": 1,
    "decision": "stop",
    "evaluated_at": "2026-10-11T02:21:17.757369Z",
    "reasons": [
      {
        "code": "already_applied",
        "detail": "the patch is already present in the source",
        "fact_id": null,
        "path": null
      }
    ],
    "world_changed": true,
    "world_digest": "7dec016614a09eb3e3b6f2891c8dac65cf48613d93da45f7621f560d2df35678"
  }
}
```

- `validity.decision`: `continue`, `refresh` or `stop`.
- `validity.reasons[]`: `code` (for example `fact_broken`,
  `already_applied`), `detail`, `path` and `fact_id`.
- `validity.analysis`: how deep the analysis went, for example `symbols`
  (Rust and Python declarations) or `files_only`. Core also documents
  `integration`, for a verdict the merged-tree checks decided.
- `check --json` has no `schema_version` field.

`dispatch status <run-id> --json` and `dispatch run --json` carry
`schema_version: 1`. After an attempt to integrate, read `outcome`:

- `outcome.application`: for example `applied`, `not_applied` or
  `blocked_by_source_drift`;
- `outcome.review`: `pending`, `accepted` or `rejected`;
- `outcome.applied_by`: `human` or `auto_apply`.

`dispatch run` exits 0 when a result is ready (verification passed or not
configured), 3 when configured verification failed, 4 when it is waiting on a
human, 124 at the deadline, 1 on failure and 2 on a usage error.
`dispatch accept` exits 0 when it applied the Work and 1 when it refused or
failed.

## Work against Work

While a project is watched, Dispatch compares the Work that is not yet
integrated, and says where two pieces touch: the same declaration or file,
overlapping lines, a signature one changes and the other uses, or a file one
changes and the other relies on. `dispatch status <run-id>` explains it
under `Concurrent`. `watch --json` work objects, and `status --json` while the
project is watched, carry `interactions`. It is
advisory: Work can be CONTINUE and still interact, and nothing is blocked or
reordered. Once one piece lands, the others are judged against the new code.

## Claude Code, Codex and other agents

- **Claude Code.** With the hooks from `dispatch setup` (Runtime
  integrations), a Claude Code session in its own worktree of a watched
  project (`claude --worktree`) becomes Work by itself. Each session ends
  tracked, pending, or told it is not tracked. A session working directly
  in the checkout is not tracked.
- **Codex, Cursor, scripts.** Run them through
  `dispatch attach -- <agent command>`. Dispatch makes an isolated
  workspace and judges the Work when the agent exits. Codex has no hook
  integration.
- **Dispatch launches the agent.** `dispatch run "<task>"` needs a profile
  from `dispatch setup`, or `--agent claude|codex|cursor`, plus
  `--allow-unsafe-local` in scripts.

## Boundaries

- Local CLI: no account, no server, no upload. Agents use their own logins.
- Not a sandbox: agents and checks run with the user's operating-system
  permissions.
- Symbol-level facts for Rust and Python; file-level facts elsewhere. A
  change the analysis cannot see is caught only if the project's checks cover
  it.
- Experimental developer preview. On Dispatch's own 36-scenario fixture
  matrix there are zero false CONTINUE and zero false REFRESH verdicts. Rates
  on real repositories are not measured, and no time, token or cost saving is
  claimed.

## References

- [Getting started](https://rundispatch.sh/docs)
- [Download and verify](https://rundispatch.sh/download)
- [Safety and data](https://rundispatch.sh/trust)
- [Core README at v0.4.11](https://github.com/rundispatch/dispatch/tree/v0.4.11#readme)
- [Coherence reference](https://github.com/rundispatch/dispatch/blob/v0.4.11/docs/coherence.md)
- [What is claimed](https://github.com/rundispatch/dispatch/blob/v0.4.11/docs/coherence-validation.md)
- [Attach, watching and runtime reference](https://github.com/rundispatch/dispatch/blob/v0.4.11/docs/attach.md)
- [Building with parallel agents](https://github.com/rundispatch/dispatch/blob/v0.4.11/docs/self-hosting.md)
