# Dispatch

> Use when operating dispatch, creating or attaching Codex lanes, sending/steering/context-injecting/stopping through dispatch, checking provider capacity, managing triggers, checking daemon status/logs, or configuring the dispatch MCP/plugin surface. Not for changing dispatch source code; use AGENTS.md for implementation work.

- Skill: `outfitter-dev/dispatch` (Agent Skill)
- Install (CLI): `npx skillmds@latest add outfitter-dev/dispatch`
- Raw SKILL.md: https://api.skillmd.com/api/skills/outfitter-dev/dispatch/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: outfitter-dev (https://skillmd.com/u/outfitter-dev)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/outfitter-dev/dispatch

---


# dispatch

Use `$dispatch` to operate the local dispatch control plane. dispatch owns one
App Server connection through a daemon and exposes one authored op registry
through CLI and MCP surfaces. The connection owns a stdio App Server by default;
an explicitly configured local Unix socket attaches without owning server
lifecycle.

For source changes, read the repo-root `AGENTS.md` instead. This skill is for
using the tool.

## Command Surface

When you are in this repo, prefer the in-tree command:

```bash
uv run dispatch --help
```

The current canonical operator grammar is:

- health: `doctor`
- daemon process: `up`, `down`
- daemon reads: `daemon status`, `daemon log`
- registry recovery: `registry migrate`
- model catalog: `models`
- permission profiles: `permissions`
- provider capacity: `usage`
- statusline capture lifecycle (daemon-free): `usage-capture install`,
  `usage-capture run`, `usage-capture status`, `usage-capture remove`
- thread lifecycle/read/search: `new`, `attach`, `list`, `list --unmanaged`,
  `get`, `sync`, `tail`, `history`, `watch`, `search`, `query`
- thread actions: `rename`, `archive`, `restore`
- message verbs: `send`, `stop`
- goals: `goal status`, `goal set`, `goal clear`
- inbox/subscriptions: `subscribe`, `subscriptions`, `unsubscribe`, `inbox list`,
  `inbox read`, `inbox ack`
- triggers: `trigger add`, `trigger list`, `trigger rm`, `trigger pause`,
  `trigger resume`
- schemas/MCP: `schema <command>`, `mcp`

Successful CLI output is JSON-shaped. Use `--json` in scripts when you want the
machine-output contract to be explicit.

## Start Or Inspect The Daemon

```bash
uv run dispatch doctor --no-app-server
uv run dispatch up --json
uv run dispatch daemon status
uv run dispatch daemon log --limit 10
```

If a command fails because the running daemon does not support a current CLI op,
dispatch treats that as daemon/client skew. It restarts an idle daemon and retries
once. If the daemon has active work, it refuses to restart automatically and only
blocks the mismatched ops — commands the daemon still agrees on (`daemon status`,
`roster`, `stop`, ...) keep working, so inspect or drain the work, then run
`uv run dispatch down` and `uv run dispatch up --json` when it is safe.

Use `uv run dispatch doctor` before relying on live thread operations in a new or
untrusted environment. It checks PATH visibility, Codex CLI/auth footprint,
daemon socket/pidfile state, registry schema/integrity, packaged skills/plugin
assets, and a low-risk Codex App Server initialize smoke. Use `--no-app-server`
when you only need local install/runtime diagnostics.

Advanced shared-daemon experiments may set the absolute
`DISPATCH_APP_SERVER_SOCKET` path or `[app_server].socket_path` in the local
Dispatch config. `doctor` must report `transport: unix` before relying on that
topology. An explicit socket fails closed, and `dispatch down` closes only
Dispatch's connection. Do not use shared transport as authority for concurrent
Desktop/Dispatch writes; attached-lane write locks remain unchanged.

If doctor reports an old registry schema, run `uv run dispatch down`, then
`uv run dispatch registry migrate`, then `uv run dispatch up --json`.

Stop only when it is clearly your daemon/session to stop:

```bash
uv run dispatch down
```

Runtime state defaults to `~/.dispatch`. Use `DISPATCH_HOME` for isolation when
testing. Do not point tests at the user's live `~/.codex`; the repo integration
suite uses an isolated `CODEX_HOME`.

## Shell Completions

Use the derived completion command when setting up an operator shell:

```bash
uv run dispatch completion bash
uv run dispatch completion zsh
uv run dispatch completion fish
```

Evaluate the generated script for ad hoc use, or write it to the shell's
completion directory for durable installs.

## Thread Selectors And Lane Rules

Every managed thread has a stored dispatch-local `ref`. Prefer refs for command
arguments. The full Codex thread id is always accepted. Titles and `@handles`
are mutable convenience labels; use them only when a unique human label is more
useful than a ref.

Owned lanes are created by dispatch and are writable. Prefer `new` for a
configured managed thread; it applies `.dispatch/config.toml`, presets, name
prefixes, and can send an initial turn:

```bash
uv run dispatch new --name my-lane --cwd /path/to/project --text "Do the bounded thing."
uv run dispatch new --name my-lane --goal "Loop until green." --text "Start with tests."
uv run dispatch new --name visual-review --text "Review this state." --image ./screen.png
uv run dispatch new --name my-lane --preset reviewer --no-send
```

For rich initial input, repeat `--image PATH` and `--image-url HTTPS_URL`; add `--image-detail auto|low|high|original` when the default detail is not appropriate. Local images must be PNG, JPEG, GIF, or WebP and at most 20 MiB. Remote images must use HTTPS and resolve publicly; Dispatch fetches them under a shared 15-second deadline into ephemeral App Server inputs and never stores the bytes.

`new` selects the execution provider with `--provider codex|claude|hermes`, plus
CLI-only boolean shorthands `--codex`, `--claude`, and `--hermes` that marshal to
the same canonical input. Config `[defaults]` and `[presets.*]` accept the
canonical `provider` key only (no shorthands); CLI flags win over presets, which
win over defaults. Omitting all selectors launches a Codex lane. Provider
selectors are mutually exclusive — any combination of two, even the redundant
`--claude --provider claude`, is rejected before any lane work. This execution
provider is distinct from `--model-provider`, and the Claude execution provider is
not launchable yet: resolving to it — from a flag, preset, or config default —
fails with a validation error at launch, never with a silent Codex fallback.

Hermes is an opt-in provider for dedicated local sessions. It requires a configured
owned stdio gateway and the explicit upstream capability contract
`prompt_submit_if_idle_v1` plus `prompt_turn_correlation_v1`; the stock Hermes
`939e45c`/`0.21.2` runtime does not advertise the latter, so Dispatch must refuse
durable Hermes sends rather than infer completion. The capability patch is a local,
unpublished dependency until it is separately verified and adopted upstream.

Configure the binding in global `~/.dispatch/config.toml`:

```toml
[providers.hermes]
hermes_home = "/Users/me/.hermes"
source_root = "/Users/me/src/hermes-agent"
interpreter = "/Users/me/src/hermes-agent/.venv/bin/python"
profile = "default"
```

All paths must be absolute. The worker starts the configured interpreter as
`python -m tui_gateway.entry` with `HERMES_HOME` and the configured source root,
and owns only that child. It does not discover or stop Desktop, serve, or Runs
processes. Launch with an existing directory and plain text:

```bash
uv run dispatch doctor
uv run dispatch new --name hermes-review --provider hermes \
  --cwd /path/to/project --text "Review the current changes." \
  --idempotency-key hermes:review:1 --json
uv run dispatch send <dispatch-ref> "Address the highest priority finding." \
  --idempotency-key hermes:review:2 --json
```

The first Hermes slice rejects goals, images or structured content, output
schemas, custom instructions, staging, workspace/worktree setup, subscriptions,
and Codex-only model or permission overrides before provider I/O. It does not
attach to arbitrary Desktop sessions or fall back to HTTP Runs. When supplied,
reuse an exact launch/send key and request to replay the local record; changing the request is
`delivery_conflict`. Keep unknown or ambiguous receipts held and inspect them
with `delivery get` or `delivery reconcile`; never resend to repair a lost
acknowledgment. A generation change fences existing Hermes sessions, while exact
local receipt replay remains available.

Omit permission-profile, sandbox, approval, model, and service-tier settings when Codex defaults are
acceptable. `dispatch new` omits unset policy/model fields from `thread/start`
and initial `turn/start`, allowing Codex/App Server global, profile, and
project-local configuration to apply. Add explicit values only when the lane
needs Dispatch-owned overrides.

Use `--goal` for a native App Server goal before the initial turn. Do not put
`/goal ...` in `--text`; dispatch treats slash commands as plain text and rejects
that shape so agents do not create a thread that only looks goal-driven.

For durable or parallel launches, drive `new` from a **launch packet** directory
(`goal.md`, `prompt.md`, `output.schema.json`, `base.md`, `developer.md`,
`dispatch.toml`, plus staged-only `hooks/` and `codex/`) or from explicit files:

```bash
uv run dispatch new --name lane-a --cwd /repo --packet ./packet
uv run dispatch new --name lane-a --cwd /repo --goal-file goal.md --input-file prompt.md
printf 'goal text' | uv run dispatch new --name lane-a --goal-file - --input-file prompt.md
uv run dispatch new --name lane-a --cwd /repo --packet ./packet --dry-run --json
uv run dispatch new --name lane-a --cwd /repo --packet ./packet --stage all
```

Use `--input-file` for the prompt file (the file form of `--text`). Precedence
per slot is inline flag > explicit file > packet > repo config. Only one input may
read stdin (`-`).
`--dry-run` resolves and prints the plan (sources with byte/SHA-256, effective
settings, staged parts) without mutating any state. `--stage all|<parts>` writes
durable twins to `.agents/sessions/<ref>/` (with `--inline <parts>` to exclude
some); dispatch stages `hooks/`/`codex/` but never executes hooks. The current
App Server exposes no native worktree request; Dispatch's `--worktree create`
helper is a vanilla git preflight, not a Codex protocol feature.

For worktree-backed lanes, treat the launched runtime as the source of truth.
Dispatch should be given the exact `--cwd`; it should not assume fixed Codex
worktree paths such as `.codex/worktrees/<run>/<lane>` or
`${CODEX_HOME:-$HOME/.codex}/worktrees/<name>`. Codex-managed worktrees may be detached or
unnamed, so an empty `git branch --show-current` is not automatically a failure.
Verify identity with `pwd`, `git rev-parse --show-toplevel`,
`git rev-parse --short HEAD`, `git status --short`, and any repo-provided runtime
or workspace doctor command. A branch name is useful metadata, not proof of
correctness unless the coordinator explicitly required a named branch.

If the repo provides `.codex/environments/environment.toml`, setup/teardown
hooks, or workspace bootstrap scripts, let repo-local tooling own those
semantics. Dispatch may stage the packet, hook files, and Codex config files so
the lane can inspect or run them, but Dispatch should not execute arbitrary hooks
or apply trust-sensitive config on the repo's behalf.

Use `--workspace` when Dispatch should resolve repo-local workspace metadata
before creating the thread:

```bash
uv run dispatch new --name lane-a --cwd /repo --packet ./packet --workspace auto --dry-run --json
uv run dispatch new --name lane-a --cwd /repo --packet ./packet --workspace auto --stage all
uv run dispatch new --name lane-a --cwd /repo --packet ./packet --workspace none
```

`--workspace none` preserves the exact cwd path. `--workspace auto` discovers
`.codex/environments/environment.toml`, reports environment name/version,
setup/cleanup scripts, repo root, and effective cwd, and no-ops with
`state="not_found"` when no supported metadata exists. Dry runs never execute
setup. Setup scripts run only with explicit `--workspace-setup run` or local
daemon policy `[policy] allow_workspace_setup = true`; packet-local config is
not enough to grant setup execution.

Use `--worktree create` when Dispatch should create a vanilla git worktree before
launch:

```bash
uv run dispatch new --name lane-a --cwd /repo --worktree create --dry-run --json
uv run dispatch new --name lane-a --cwd /repo --worktree create --worktree-branch dispatch/lane-a
uv run dispatch new --name lane-a --cwd /repo --worktree create --worktree-path /tmp/lane-a
```

The default root is `~/.dispatch/worktrees/<repo>/<lane>/`, not a repo-local
`.dispatch/worktrees/` directory. `DISPATCH_WORKTREE_ROOT` can override the root.
Do not assume or mimic Claude/Codex private worktree path schemes; Dispatch
reports the exact path/branch/base/head it created. If a branch is already
checked out elsewhere, launch fails before thread creation and names the owning
worktree.

Workspace config can carry worktree defaults, with CLI flags winning:

```toml
[workspace]
default = "auto"
worktree = "create"
worktree_branch = "dispatch/default"
worktree_base = "HEAD"

[workspace.presets.athena]
mode = "auto"
worktree = "create"
worktree_branch = "dispatch/athena"
```

`new` returns `message_accepted`, not proof of assistant completion. After launch,
use `get` to check `latest_turn`, `tail` for persisted history, or `watch` for a
bounded live sample.

Before choosing explicit `--model`, `--model-provider`, or `--service-tier`
values, ask dispatch for the live catalog:

```bash
uv run dispatch models
uv run dispatch models --no-refresh
uv run dispatch schema models
```

Omit model/tier values when Codex defaults are acceptable. If a preset uses a
user-facing tier such as `fast`, Dispatch resolves it through `model/list`
service tiers before starting the thread. The catalog also reports model-defined
reasoning efforts, input modalities, personality support, and upgrade targets.
Do not guess current model ids or effort names from memory; use the catalog
output and its `aliases` field.

Before selecting a named Codex permission profile, query the cwd-aware catalog:

```bash
uv run dispatch permissions --cwd /path/to/repo
uv run dispatch permissions --cwd /path/to/repo --include-disallowed
uv run dispatch schema permissions
```

Use `--permission-profile <id>` on `new`, or set `permission_profile` in global
or repo defaults/presets. Do not combine it with sandbox, approval-policy, or
approval-reviewer overrides. Omit all of them when Codex defaults should apply.
This is distinct from Dispatch `[policy]`, which governs how the daemon answers
inbound interactive requests and does not select a Codex profile.

Use the redacted provider inventory before routing optional work by capacity:

```bash
uv run dispatch usage
uv run dispatch usage --no-refresh --json
uv run dispatch usage --provider codex --host local
uv run dispatch usage --provider claude --host local
uv run dispatch usage --all-hosts --no-refresh
uv run dispatch usage --include-daily --stale-after-seconds 300
uv run dispatch schema usage
```

Default `usage` refreshes local Codex and Claude independently and omits daily
buckets. Claude uses the read-only `claude auth status --json` and `claude
agents --json` surfaces; Dispatch stores aggregate state counts, never roster
cwd/name/session/id fields or raw command output. The observation also records
the bounded semantic version from `claude --version`. Use `--no-refresh` for a
database-only read and `--include-daily` only when historical detail is needed.
The default host is `local`; use `--all-hosts` for mesh inventory. Treat `stale:
true`, `partial`, `signed_out`, `disabled`, `unsupported`, and `unavailable` as
explicit routing constraints. Runtime, capacity, account, usage, and each
window have independent freshness. Output is masked/fingerprinted and never
includes raw email or organization ids, auth material, balances, or
reset-credit mutation ids. Claude account/runtime can be ready before supported
statusline capacity snapshots exist.

Claude capacity snapshots are opt-in via the daemon-free `usage-capture`
lifecycle:

```bash
uv run dispatch usage-capture install --provider claude --dry-run
uv run dispatch usage-capture install --provider claude --yes
uv run dispatch usage-capture status --provider claude --json
uv run dispatch usage-capture remove --provider claude --yes
uv run dispatch usage-capture remove --provider claude --keep-current --yes
```

`install` preserves the operator's complete `statusLine` object in a
restoration record under `~/.dispatch/claude/`, writes the capture wrapper,
then swaps only `statusLine.command` in Claude settings — record, wrapper,
settings, in that crash-safe order. It requires confirmation before touching
Claude settings (`--yes` when non-interactive), is idempotent on rerun, never
records the Dispatch wrapper as the original, and reports malformed settings,
`disableAllHooks`, higher-precedence project/local overrides, and a missing
`dispatch` on `PATH` instead of silently succeeding. On each refresh,
`usage-capture run` reads the statusline JSON from stdin, atomically writes
bounded normalized rate-limit facts beneath `DISPATCH_HOME`, then delegates
the same stdin to the original renderer verbatim; with no original renderer it
emits nothing and Claude keeps its built-in footer.
`dispatch-claude-statusline` is a deprecated alias of the run path.

`status` reports one bounded state — `not_installed`, `prepared`, `installed`,
`drifted`, `broken`, or `disabled` — plus wrapper/record health and last
capture freshness, and never exposes the original command string. `remove`
restores the exact original `statusLine` (or deletes the key when none
existed) before deleting Dispatch artifacts; if settings drifted to something
newer, both `install` and `remove` refuse by default so the restoration
record is never overwritten with the drifted value — run
`remove --provider claude --keep-current` to clean up the artifacts while
preserving the newer setting, then reinstall to adopt it as the new original. `rate_limits` appears only for supported
Claude.ai subscriber sessions after the first API response; missing or stale
snapshots must not erase the last valid capacity windows. Never use the
private OAuth usage endpoint as a fallback.

Attached lanes are existing desktop Codex threads registered by raw thread id:

```bash
uv run dispatch attach <codex-thread-id>
uv run dispatch attach <codex-thread-id> --sync
```

Attached lanes are managed by dispatch but turn-writing/history-mutating
operations such as send, stop, goal mutation, fork, rollback, or compact are
blocked by default. ADR-0005 and
ADR-0018 keep this boundary locked because the default Desktop/Dispatch topology
uses separate App Server processes, and optional shared transport still has no
verified writer lease or handoff protocol.
Local operators can opt in with `[policy] allow_attached_writes = true` in
`~/.dispatch/config.toml`; when that policy is enabled, Dispatch may send,
inject context, and set goals on attached lanes. Check `writable`,
`capabilities`, and `write_locked_reason` in `list --json` or `get --json`
before deciding whether to write:

```bash
uv run dispatch list --json | jq '.lanes[] | select(.writable)'
uv run dispatch list --json | jq '.lanes[] | select(.capabilities.context)'
```

Explicit metadata/lifecycle commands (`rename`, `archive`, `restore`) are
allowed regardless because they do not start turns or mutate turn history.

Attach is compact by default: it verifies the thread with
`thread/read(includeTurns:false)`, registers metadata, and does not resume turn
history. Use `--sync` or `sync` when you want dispatch to refresh its local
indexed view.

```bash
uv run dispatch sync <dispatch-ref-or-thread-id>
uv run dispatch sync <dispatch-ref-or-thread-id> --max-turns 20 --max-items 200
uv run dispatch sync <dispatch-ref-or-thread-id> --full --max-bytes 16777216
```

Sync establishes metadata-only live observation, indexes recent App Server history
first, and persists bounded turn/item cursors to reconcile missed newer turns before
later backwards continuation. It also indexes source identity, sync state, latest event time, latest turn id, and
bounded incremental JSONL facts when Codex exposes a rollout path. Check
`history_capability`, `history_complete`, `truncated`, page/item counts, scanned
bytes, and duration in JSON output. Older binaries fall back to metadata/JSONL
with an explicit unsupported capability. `turn-page-fallback` means turn paging
works but item paging does not; an atomic turn that exceeds the configured
persistence budget stays pending/truncated until sync is rerun with a larger
explicit budget. The aggregate byte target is checked between provider pages;
`scanned_bytes` may exceed it by one received page, but a page that would exceed
the remaining persistence budget is not indexed. One `--max-seconds` deadline
bounds metadata, provider history, local parsing, persistence, and archive
reconciliation. Durable cursor-cycle detection prevents repeated syncs from
spinning. Bare `history` reads the local index only.
When experimental paging is unavailable, sync retries stable metadata-only
resume and reports observation separately from history capability. An oversized
complete JSONL record remains at its current offset with an actionable
`--max-bytes` diagnostic rather than being silently skipped.
Selector-scoped transcript reads through `tail`, `history`, or
transcript-inclusive `get` still use App Server `thread/read(includeTurns:true)`
as the canonical source, and those reads backfill Dispatch's normalized local
history index with turns, items, and refs for that one thread. If the selector
is a raw unmanaged Codex thread id, `sync` first registers it as an attached
read/metadata-managed lane, then refreshes the index. That does not grant write
authority.

Sending to a raw unmanaged Codex thread id also performs that registration and
quick sync first. The write still follows the attached-lane policy: without
`allow_attached_writes`, Dispatch records the thread as managed/indexed and then
refuses the turn-writing action with an authority error. With the policy enabled,
the send path resumes the attached thread and starts the turn.

Sync also reconciles known App Server archive membership for the target. Archive
state is lifecycle metadata, not cleanup: dispatch does not delete provider
events or normalized history evidence during `archive`, `restore`, sync
reconciliation, or event indexing.

## History Capture Policy

Dispatch captures normalized history into its local SQLite registry. Default
`standard` capture keeps operational facts and bounded searchable history facts.
Live App Server item events and transcript replay share one canonical normalizer.
Codex 0.144 message, reasoning, command, file, MCP/dynamic/collaboration tool,
subagent, web, image, review, sleep, and compaction items become the same local
rows and refs in either path. Unknown future item types stay visible. Concrete
tool/server/status, arguments, errors, durations, files, and child-thread ids
stay queryable without raw retention. Raw provider payloads stay gated by
retention policy. Transcript replay is additive because provider history can
omit richer tool items already observed live, and lower-retention replay cannot
silently erase a payload retained earlier at a higher capture level. Normalized
text and command/tool metadata are bounded and redact common credential forms
and sensitive argument keys. Minimal capture keeps turn-level state but skips
item-level transcript rows. Bare `history` overview renders from the local
index. Selector-scoped `history` item/tool/file views render from the normalized
index after refreshing one thread; `--raw` keeps its live App Server raw-payload
behavior. Use debug capture only when developing or diagnosing
reducers/search/provider adapters; debug retention can store bounded raw
provider event and item payloads with truncation markers:

```toml
[history]
capture = "debug" # minimal | standard | debug
raw_payload_retention = "debug" # off | errors | debug | all
max_text_bytes = 8192
max_payload_bytes = 65536
```

Prefer isolated `DISPATCH_HOME` and `CODEX_HOME` for debug capture. Use
`dispatch doctor` to confirm the active capture mode; it warns when debug/raw
retention is enabled.

## Discover Sessions

`list` shows threads dispatch already manages. `list --unmanaged` lists
persisted Codex sessions that are not registered in dispatch. It uses App Server
`thread/list` in state-db-only mode, asking for active sessions sorted by recent
updates. It is read-only and does not resume or register anything:

```bash
uv run dispatch list
uv run dispatch list --unmanaged --limit 20
uv run dispatch list --unmanaged --archived --limit 20
uv run dispatch list --parent <ref-or-thread-id>
uv run dispatch list --ancestor <ref-or-thread-id>
uv run dispatch list --root <ref-or-thread-id>
uv run dispatch get <ref-or-thread-id> --topology
```

Topology does not imply authority. Parent/ancestor filters use App Server's
native spawned-thread relationships, while ordinary forks remain separate in
`forked_from` and `forks`. Unmanaged discovery excludes threads that already
have lanes. Plain reads use the local topology cache; `get --topology` performs
a bounded refresh. Check `complete`, `truncated`, and `cycle_detected` before
treating the result as a complete tree, and use `--topology-limit` to bound it.

Use a discovered session `id` with `attach <id>` or `sync <id>`. `list --unmanaged`
is read-only; `sync <id>` is the explicit step that registers the thread as an
attached lane.

## Search And Thread Actions

Use top-level actions when you want to work with either managed threads or raw
unmanaged Codex thread ids:

```bash
uv run dispatch rename @my-lane my-lane-final
uv run dispatch archive <codex-thread-id>
uv run dispatch restore @my-lane
```

`restore` only unarchives; it does not resume or start a turn.

Use `search` before attaching when you need App Server broad search over Codex
history, including unmanaged or not-yet-indexed threads:

```bash
uv run dispatch search "schema drift"
uv run dispatch search "schema drift" --managed
uv run dispatch search "schema drift" --unmanaged
uv run dispatch search "schema drift" --thread <dispatch-ref>
uv run dispatch search "schema drift" --repo .
uv run dispatch search "schema drift" --dir /path/to/project
uv run dispatch search "schema drift" --since 2026-06-01 --until 2026-06-05
```

Use `query` for Dispatch's local indexed managed-history substrate. Query does
not call App Server search and only sees threads Dispatch has indexed through
sync/history/tail/watch/live capture. Text is optional when a structural filter
is present:

```bash
uv run dispatch query "schema drift"
uv run dispatch query --tool linear.save_issue
uv run dispatch query --tool linear.save_issue --tool-status completed --arg-key id
uv run dispatch query --file convex/support/lineage.ts
uv run dispatch query --repo . --since 2026-06-01 --until 2026-06-05
uv run dispatch query --type mcpToolCall --errored
uv run dispatch query --mentions-thread 019f
uv run dispatch schema query
```

Query output includes normalized command/tool metadata and explicit child-thread
refs, so prefer those fields over parsing retained raw payloads with `jq`.

Use `history` after you already know the thread and want summary/items/tools/files
inspection. Sync is separate for managed lanes: it refreshes dispatch's local
index and does not grant write authority. For a raw unmanaged Codex id, `sync` is
also the explicit registration step.

## Message Verbs

`send` is the primary way to put work or context into a lane:

```bash
uv run dispatch send @my-lane "Do the bounded thing."
uv run dispatch send 019ead04-d2f4-77e2-acf7-f34d25456fa8 "Picking this up."
uv run dispatch send @my-lane "Focus on docs first." --steer
uv run dispatch send @my-lane "Stop and do this instead." --interject
uv run dispatch send @my-lane "Context: use lane publicly, thread internally." --context
uv run dispatch send @my-lane "After this finishes, summarize risks." --mode queue
uv run dispatch send @my-lane "Inspect this screenshot." --image ./screen.png
printf 'Compare the attached states.' | uv run dispatch send @my-lane --input-file - --image ./before.png --image ./after.png
uv run dispatch send @my-lane "Can you check this?" --intro
```

The mode flags and `--mode send|steer|queue|interject|context` are mutually
exclusive. `--queue` stores the message durably and starts one queued turn when
the lane is idle. `--image` and `--image-url` are repeatable, and `--image-detail auto|low|high|original` applies to the invocation. Images work with send, steer, queue, and interject. They do not work with context injection; use a normal send when the target must inspect an image. Queued images store references and bounded metadata rather than bytes, then revalidate local files and remote content at delivery.

Use `--intro` when you are sending from one managed Codex thread to another and
want the recipient to know how to reply through dispatch. It derives the sender
from `CODEX_THREAD_ID`, so the current thread must already be managed. Intro
messages append the standard visible Dispatch attribution footer:

```text
<message>

dispatch (dm): [@Sender](codex://threads/<thread-id>) `<ref>`
↳ reply `dispatch send <ref> "..."`
```

Use `stop` to cancel the active turn without replacement text:

```bash
uv run dispatch stop <dispatch-ref>
```

## Inbox And Subscriptions

Use `subscribe` when the current managed Codex thread should hear about another
lane later. A subscription is an event-to-inbox binding. It creates a durable
inbox message when the event matches, then optionally starts a new turn in the
subscriber.

```bash
uv run dispatch subscribe @worker
uv run dispatch subscribe @worker when:done,delivery:inbox
uv run dispatch subscribe @worker --when approval --delivery inbox --repeat
uv run dispatch new --name worker --cwd /repo --text "Do it." --subscribe
uv run dispatch new --name worker --cwd /repo --text "Do it." --subscribe when:done,to:self
uv run dispatch new --name worker --cwd /repo --text "Do it." --subscribe-spec when:done,to:self
```

Default subscription settings are `when:done,to:self,delivery:turn,deliver:idle,
tail:1,once:true,ack:auto,attribution:true` when the subscriber is writable. If the
subscriber is an attached/read-only lane, the default falls back to
`delivery:inbox`; explicit `delivery:turn` still fails unless attached writes are
enabled. `self` is derived from `CODEX_THREAD_ID`, so the calling thread must
already be managed by dispatch. Use explicit `--to <ref>` when one managed lane
is subscribing on behalf of another.

Hermes lanes cannot be subscription targets yet because their native activity is
not projected into the shared lane-event stream. They can receive durable
`delivery:inbox` updates as subscribers; `delivery:turn` remains unavailable.

Useful `when` buckets:

- `done`: completed or failed turns.
- `completed` / `failed`: one terminal outcome.
- `approval`: command, file-change, and permission approvals.
- `needs-attention`: approvals plus user-input, elicitation, and dynamic-tool requests.
- `idle`: idle status events.
- `activity`: any tracked lane event.

Turn-delivered subscription updates use the same visible Dispatch attribution
footer by default, with source thread link, ref, event, and when bucket.
Use `attribution:false` in the compact spec or `--no-attribution` when a subscriber
needs the older compact body without the footer.

Use inbox-only delivery when you want durable collection without waking the
subscriber:

```bash
uv run dispatch inbox list
uv run dispatch inbox list --lane <dispatch-ref> --state pending
uv run dispatch inbox read <message-id>
uv run dispatch inbox ack <message-id>
uv run dispatch inbox ack --all
uv run dispatch subscriptions
uv run dispatch unsubscribe <subscription-id> --yes --json
```

App Server interactive requests are durable and use one generic response path. Check the request's `expected_response` before answering; never put credentials in a response:

```bash
uv run dispatch request list --state pending --json
uv run dispatch request respond <request-id> '{"action":"decline"}' --json
uv run dispatch schema "request respond"
```

Owned requests default to `attention`; attached/unmanaged requests default to `deny`. Local policy can set `owned_interactive_requests` or `attached_interactive_requests` to `attention`, `deny`, or `permissive`, plus `interactive_request_timeout_seconds`. Permissive mode approves supported local approvals only; auth, attestation, and unknown host requests are never synthesized.

Destroy-intent commands prompt by default. In scripts, use explicit confirmation:

```bash
uv run dispatch archive <dispatch-ref> --yes --json
uv run dispatch trigger rm <trigger-id> --yes --json
```

For short inter-lane chat, use the companion `$dm` skill, which is backed by
`dispatch send`.

## History, Watch, And Goals

Use `get` for compact managed-thread metadata:

```bash
uv run dispatch get <dispatch-ref>
```

Check `latest_turn` when a message was accepted but no assistant work is visible.
It records the latest observed turn id, status, and App Server error text/time for
failed turns.

Use `tail` for persisted turn history:

```bash
uv run dispatch tail <dispatch-ref> --limit 50
```

`tail` uses App Server `includeTurns`, which is not available for ephemeral
threads. It also feeds the normalized local history index for the lane.

Use `history` for transcript inspection and rollups. Bare `history` summarizes
managed lanes; passing a selector drills into one thread and can show summary,
items, tools, or files:

```bash
uv run dispatch history
uv run dispatch history <dispatch-ref>
uv run dispatch history <dispatch-ref> --view tools
uv run dispatch history <dispatch-ref> --view files
uv run dispatch history <dispatch-ref> --view items --tool bash --grep "git status" --raw
uv run dispatch history <dispatch-ref> --view items --tool-server linear --tool-status completed --arg-key id
uv run dispatch history <dispatch-ref> --view items --mentions-thread 019f
uv run dispatch history --has-tool bash --changed --min-bytes 100000
```

Bare `history` includes transcript size, estimated tokens, active dates, deduped
tools, worktree identity, and dirty changed-file
names from each lane cwd. Overview filters include `--cwd`, `--source`,
`--status`, `--has-tool`, `--changed/--clean`, and `--min-bytes`. Item views use
`--type`, `--role`, `--phase`, `--tool`, `--tool-server`, `--tool-status`,
`--errored/--not-errored`, `--mentions-thread`, `--arg-key`, `--grep`, and
optional `--raw`. Bare overview reads the
local index only. Selector-scoped history reads refresh one thread and backfill
the normalized local history index. Normal item/tool/file views render from that
index after refresh; `--raw` intentionally reads the live App Server raw item
payloads for jq-heavy inspection.

Use `watch` for a bounded live event sample. It returns raw App
Server method/params until a limit or timeout, and it is not an infinite tail:

```bash
uv run dispatch watch <dispatch-ref> --limit 20 --timeout 10
```

Use native goals on owned lanes when a worker has a durable objective:

```bash
uv run dispatch goal set @my-lane "Loop until checks are green."
uv run dispatch goal status <dispatch-ref>
uv run dispatch goal clear <dispatch-ref>
```

Goals require non-ephemeral App Server threads.

`tail --follow` is not canonical; use `watch`.

## Markdown Thread Links

Use readable handles plus Codex thread URIs in human-facing text. Compose a
Markdown link whose label is the handle and whose destination is the Codex URI:

```markdown
label: @Target
destination: codex://threads/<codex-thread-id>
```

Use raw thread ids for `attach`. Use refs or full thread ids for dispatch
thread arguments.

## Triggers

A trigger binds `when -> action -> lane`.

```bash
uv run dispatch trigger add \
  --name pulse \
  --lane <dispatch-ref> \
  --when interval \
  --seconds 1800 \
  --action send \
  --text "Check in briefly."
```

Use `--idle-only`, `--min-interval`, and `--dedupe` to reduce noisy automation.
Remember that dedupe state is process-local and resets when the daemon restarts.

```bash
uv run dispatch trigger list
uv run dispatch trigger pause <trigger-id>
uv run dispatch trigger resume <trigger-id>
uv run dispatch trigger rm <trigger-id>
```

## Schemas

Use `schema` for derived input/output schemas:

```bash
uv run dispatch schema send
uv run dispatch schema "list --unmanaged"
uv run dispatch schema models
uv run dispatch schema permissions
uv run dispatch schema usage
uv run dispatch schema "goal set"
```

Prefer `schema` for `jq`/automation field discovery. It is derived from the same
op registry as CLI and MCP, including composed spellings like `list --unmanaged`.

## MCP And Plugin

The MCP server is:

```bash
uv run dispatch mcp
```

The MCP surface is grouped for agent ergonomics, not one tool per CLI
subcommand. Tools are grouped by workflow and safety boundary, and each call
selects an `op` inside the tool. In this repo, the workspace-local Codex plugin
lives at `plugins/dispatch`. It exposes these skills and the same MCP registry.
For rich input, pass the thread-write `new` or `send` op a structured `content` array containing `text`, `image`, and `local_image` items. Do not translate CLI flags into an ad hoc JSON string; MCP derives and validates the typed array from the same authored contract.
Use the daemon-read MCP tool's `models` op before setting explicit model or
service-tier arguments, its `permissions` op before selecting a named profile,
and its `usage` op before making capacity-based routing decisions.
The thread-write MCP tool's `fork` op accepts `last_turn_id` to fork through one
completed turn, inclusive.
The thread-read MCP tool derives the same topology inputs and outputs used by
CLI `list` and `get`; topology reads never attach or grant write authority.
If the plugin does not appear immediately, restart Codex for the workspace.
Installed PyPI packages also include read-only copies of these skills and the
plugin bundle under `outfitter.dispatch.assets`; use the repo copies for editing.

## Guardrails

- Do not mutate source files, Git, PRs, Graphite, or tracker state as part of
  ordinary dispatch operation.
- Do not install launchd autostart unless the user explicitly asks.
- Start troubleshooting with `dispatch doctor`; use its recovery hints rather
  than guessing about stale sockets, PATH, auth, or registry shape.
- If doctor reports an old registry, stop the daemon and run
  `dispatch registry migrate` before starting it again.
- Do not describe `tail --follow` as canonical or streaming forever. Use `watch`
  for bounded live samples until dispatch grows a subscription-capable control socket.
- Do not treat `rollback` as file undo.
- If a request becomes long-running owned work, use a proper delegated lane or
  goal workflow rather than a casual message.

