Repository navigation
Releases: rundispatch/dispatch
Release list
Dispatch v0.4.13
Dispatch 0.4.13 — Work that appears and ends by itself
Run dispatch start in a project and use Claude Code the way you already do. Before 0.4.13, a
Claude Code worktree session became Work only if Claude Code's hooks were installed, and a
claude -p session never said it had ended. Now, with no hooks installed, the session's worktree
becomes Work. The Work says while its agent runs, and says when the agent is gone. Dispatch
records only what it can observe, and it still never finishes Work because a session ended.
-
Discovery without hooks. On each tick the watching owner lists the project's Git worktrees
(git worktree list). It never walks a directory. A worktree Claude Code made (under
.claude/worktrees/, with Claude'sCLAUDE_BASEfile) that appears after watching began and
is locked by aclaude session …becomes Work:- S0 is the worktree's merge base with the project, with full confidence: everything in a
worktree that appeared while you watched is its session's.watchshows
S0 merge-base c45fd977 (full; found by Dispatch), and the origin isdiscovered. - This works for
claude --worktreeandclaude -p --worktree. Aclaude -prun that
starts and ends between two ticks still becomes Work, with its session shown as ended. - With hooks installed too, whichever sees the worktree first creates the Work, and the other
joins it. There is one Work per worktree. - If a hook could not register a session in time and told it so, Dispatch may still track its
worktree when it finds it later.
- S0 is the worktree's merge base with the project, with full confidence: everything in a
-
Found, not tracked. Claude worktrees that were already there when watching began are not
turned into Work, because Dispatch cannot know what they held before. Neither are new ones
without aclaude sessionlock, such as subagent or background worktrees, whose lock has not
been observed yet. They are listed with the command that attaches them:01M4VJ2H · discovered claude · S0 merge-base c45fd977 (full; found by Dispatch) · unmoved · working · checks not run found, not tracked · dispatch attach --workspace ~/src/project/.claude/worktrees/oldwatchlists them under the Work,statusin a "Found, not tracked" section, and
watch --jsonin theinventoryfield of thewatcherrecord. The list belongs to the
watch: it survives an owner restarted after a crash, anddispatch stopclears it. Worktrees
you made yourself (git worktree add) are never listed. -
Liveness from Claude's worktree lock. Claude Code locks a worktree it made with
claude session <name> (pid <pid> start <time>). While that exact process runs, the Work
showsagent running (pid N). A pid reused by another process does not count. Dispatch
records the end of the session once the process is gone and its lock is left, asclaude -p
leaves it (process_gone), or once the lock is released (lock_released). For either, it
must first have seen the process running. The Work then readsidle, with 0.4.12's
"session ended — finish, resume or close". Nothing is finished, applied, closed or signalled.
An unreadable process start, or a lock never seen running, proves nothing, and nothing is
recorded. A hook's end and a lock's end give one end. Only worktrees the runtime made are
read; a person'sattach, with or without--pid, is never touched. -
Live progress, with no fake zeros. Working attached Work shows the files it has changed so
far, as the owner last counted them, and how long Dispatch has tracked it:Candidate A (claude) Status running Verification not configured Runtime unknown Tracked for 4s Cost unknown Task tokens unknown Token basis unknown Files changed 2 nowUntil the owner has counted, and whenever no owner watches the project, files read
unknown.
Before,statusprintedRuntime 0.0s,Files changed 0andDiff +0 / -0for every
working attached Work. Inwatchthe selected Work's details have a Progress line, such as
files: 2 changed · tracked for 12m · agent running (pid 4242), orfiles: none changed yet
for a count of zero. A finished result keeps the numbers recorded when it was delivered. -
First use without a terminal ceremony.
-
dispatch startin a project without checks, on a terminal, shows the checks Dispatch
detected, with "Continue without checks" focused, so Enter alone never writes to the
project:This project has no checks. Dispatch found: sh ./verify.sh make test Saving them writes checks.verify in dispatch.yml; it does not let Work run them by themselves."Save these checks" writes them to
dispatch.yml. Esc starts nothing. -
Without a terminal (a script, CI, an agent's shell),
startwatches at once, detects and
runs nothing, and says:
No checks are configured, so Work here is not verified. Add them: dispatch setup --checks, or dispatch setup --checks --verify '<command>'. -
dispatch setup --checks --verify 'cargo test' --verify 'make lint'saves exactly those
commands, in that order, and keeps the rest ofdispatch.yml. On a terminal,--verify
pre-fills the same menu. -
Consent is never granted without a terminal. Letting Work run checks by itself still needs
dispatch setup --checksin a terminal, or--allow-unsafe-localon a singlefinish.
Saving other commands ends consent given for the old ones. The consent screen now says that
checks run project code, including code an agent wrote, on this host with your permissions,
not in a sandbox.
-
-
Watching no longer takes Git's
index.lock. The owner runsgit statusin your
repository on every tick. Git refreshes the index underindex.lockwhen it can, so your own
git commitcould fail while Dispatch watched. That has been true since 0.4.5. Dispatch's Git
commands now setGIT_OPTIONAL_LOCKS=0. The race showed up while building this release, as a
test's owngit commitfailing under load.
Not in 0.4.13: other agents' worktree layouts are not recognized; for them,
dispatch attach -- <agent> gives the agent a workspace of its own. There is no process
scanning, and nothing is finished, closed or cleaned up automatically. A lock is read only in a
watched project.
No migration: the schema stays at 24. JSON changes are additive: the discovered provenance,
via: "discovery", the runtime.agent_running and runtime.agent_gone events, the
process_gone and lock_released end reasons, and watch --json's inventory. Once 0.4.13
has found Work, 0.4.12 cannot read that state directory. Roll back only from a backup.
"Upgrading to 0.4.13" in docs/release-install.md has the details; restart the owner after
upgrading.
Built with Dispatch: four code packets ran at the same time, each an interactive Claude Code
session that a pinned Dispatch 0.4.12 started in its own workspace
(dispatch attach --root <clone> -- claude), then a docs packet. A human reviewed and accepted
them one at a time. Checking each packet against what had already landed, by an independent
suite and by the merged checks at accept, caught three integration problems before release.
A real-agent trial with no hooks caught a fourth: a claude -p worktree session that
discovery found while it ran, and that ended before Dispatch looked again, kept its session
open; discovery now records the running session's process as the Work's agent, so its end is
seen however soon it comes.
Dispatch v0.4.12
Dispatch 0.4.12 — Review the Work you mean
While investigating field reports (a finish that said "Ready for review" while check said
REFRESH, Work that seemed to need full IDs, landed work recorded as rejected, clean leaving
worktrees behind), review found a worse problem. Two pieces of attached Work, A and B, finish one
after the other. A's finish summary ends with dispatch accept / dispatch reject, with no ID.
Following it accepts B: an ID-less accept takes the newest result waiting for that source,
applies it and records a human acceptance for it, while A stays pending. A fixture reproduced it
on 0.4.11. 0.4.12 fixes it, and the problems those reports raised.
-
Decisions name their Work, and ambiguity is refused. Every finish and result summary
printsdiff,accept,rejectorclosewith the Work's full ID.dispatch acceptor
dispatch rejectwithout an ID acts only when exactly one result waits for review for the
source. With two or more, it changes nothing and lists them:$ dispatch accept error: 2 results wait for review for ~/src/project; nothing was decided. Name the one you mean: dispatch accept 01M4SNHQS8ZP2FEGVG5A92BNNC (work B) dispatch accept 01M4SNHPQV8CY5GWW19BZ3RDSJ (work A)Work still in progress is listed as not counted. Unique ID prefixes still resolve, and what is
recorded names the full ID. -
dispatch closeretires Work without judging it.dispatch close <id> --reason <reason>
records onework.closedevent and no review. It never applies, commits, deletes a workspace
or signals an agent, and it refuses Work a liveattach --wrapper still owns. The reasons:unfinished: Work nobody will finish. Its changes are frozen into its patch first, without
running checks.workspace-removed: unfinished Work whose workspace is gone. Its kept changes stay.already-present: a result whose whole patch is already in the source. Part of it is not
enough.superseded --replacement <id>: a result that other finished Work replaces. Both patches
are kept.
STOP now points to
dispatch close <id> --reason already-presentinstead ofreject, so
changes that landed some other way are no longer recorded as a rejection.rejecton
unfinished Work refuses and namesclose. Indispatch watch,ccloses the selected Work
after asking for the reason and a confirmation; Enter alone or Esc records nothing. A
session that ended says so: finish it, resume it, or close it.A
claude --worktreesession's end, which Claude Code reports from the main checkout, is now
recorded on its Work by session;claude -psessions report no end, so finish or close their
Work explicitly. -
Finish, check and accept say what holds, and what was verified.
finish, and a native
run's result summary, now show the patch, a verdict computed against the source now, and what
accept would verify:Finished, but stale (REFRESH) … Coherence: REFRESH — 1 file changed underneath patch_conflict: the patch no longer applies to c.txt; Git's full output: <run dir>/patch-conflict.txt; … At accept accept would refuse it now (REFRESH), before any merged checks- The heading follows the verdict:
Ready for review,Finished, but stale (REFRESH),
Finished, already in the source (STOP),Verification failedorClosed. - REFRESH and STOP never offer accept.
- A patch that no longer applies names its paths and keeps Git's full output in
patch-conflict.txtin the run directory. checkadds the sameAt accept:line.acceptprintsMerged checks:before it applies anything. Either they passed, with the
log directory, or they did not run, and it says why: the source has not moved, no checks
are configured, merged checks are turned off, the Work has no recorded permission to run
checks on the host, the patch is empty, strict mode, or the configuration could not be
read.watch's accept notice and the review screen say the same.- If the source moves while merged checks run, accept now says nothing was applied because
the source changed after it checked.
- The heading follows the verdict:
-
cleannever removes a workspace live Work uses, and says what it keeps. Before, a
workspace Dispatch made could be listed for removal while new Work was attached to it, for
example after repairing it and attaching it again. Now a workspace Dispatch made is cleanable
only when no unfinished Work, or Work waiting for review, uses its path. Closed results are
cleanable. Worktrees you or an agent's runtime made are listed as kept, with their Work IDs
and how to inspect or remove them with Git, and are never removed. "Nothing to clean" no
longer suggests that every agent worktree is gone. -
Work that began from older source says so, once. Accept does not commit, so a worktree
made from HEAD, or from a remote branch, can miss accepted changes or local commits. For
attached Work that Dispatch did not start from the current source (an existing worktree you
attach, or a session a runtime reports), Dispatch compares the source with the Work's S0 once,
right after it records the Work, in both directions.attach,status,explainandwatch
show the result:Source differed from its S0 at 1 path (observed 2026-10-13 03:40 UTC, source HEAD aab43dbb): a.txt (in applied Work 01M4SNJ9XR31N21H3CT04PN7XR) Work started with dispatch attach -- <agent> begins from the current source, including uncommitted changes.- The advice leads with
dispatch attach -- <agent>, which works for any agent. - Only a Claude Code worktree also gets Claude-specific advice: local commits reach it only
withworktree.baseRef: "head", and neither that setting nor a commit copies uncommitted
changes. - A path is named as another Work's only when that applied Work's patch contains it.
- The observation is recorded once and never recomputed. Changes after the start are the
verdict's business. - Runtime-registered Work that no 0.4.12 owner saw recorded, or whose observation
failed, shows "not observed", and is never filled in later. - It is advisory: no verdict depends on it, and Dispatch changes no settings and commits or
pushes nothing.
- The advice leads with
Disclosure. Before 0.4.12, an ID-less dispatch accept or dispatch reject with several
results pending for the same source acted on the newest one. It could record a human decision,
and for accept apply the patch, on Work nobody chose. Existing records are not rewritten.
Not in 0.4.12: it does not make claude --worktree inherit uncommitted source, and it does
not replace Claude Code's worktree creation. Discovering agent worktrees without hooks, session
liveness and project setup are planned for 0.4.13. Nothing is refreshed, retried or applied
automatically.
No migration: the schema stays at 24. JSON changes are additive: outcome.closed, the
attachment's source_at_start, the attach.source_observed event, more work.closed
reasons, and the state closed in watch --json, serve --json and history. Scripts
that call accept or reject without an ID should pass one. "Upgrading to 0.4.12" in
docs/release-install.md has the details; restart the owner after upgrading.
Built with Dispatch: four packets, three for the code and one for these docs, each an
interactive Claude Code session that a pinned Dispatch 0.4.11 started in its own workspace
(dispatch attach --root <clone> -- claude), reviewed and accepted by a human one at a time.
Dispatch v0.4.11
Dispatch 0.4.11 — A clearer dispatch watch
A herdr demo with three Claude Code agents, each in its own worktree, showed that
dispatch watch judged the work correctly but was hard to read. Every Work item was one
sentence of eight to ten fields that wrapped across several lines, with no color. Work was
named only by IDs that differed in one character. The S0 hash shown was a Dispatch snapshot
commit that looked like an ordinary Git commit. If Work left the list, the selection could
silently move to other Work. 0.4.11 redesigns the interactive view only.
-
A table you can read. At 90 columns or more,
watchshows one line per Work, with the
selected Work's details below:WORK AGENT STATE VERDICT CHECKS TOUCHES › me-endpoint claude blocked REFRESH passed – auth-ctx claude applied – passed –Below 90 columns, each Work is a two-line card. The selected Work's reason and next step
stay visible down to 40 columns. -
Work has a name. A row shows the Work's worktree folder (
auth-ctx), else its task,
else a short ID. Duplicate names are told apart. -
The details explain. The selected Work's details show:
- its verdict with the full reason, and, for Work still running, that the verdict is
advisory; - its checks;
- what it touches, naming both pieces of Work ("auth-ctx changes the signature of
validate (tinyauth/auth.py), which me-endpoint uses"); - what it began from, saying plainly when that is a Dispatch snapshot and not a commit
on any branch; - what to do next.
- its verdict with the full reason, and, for Work still running, that the verdict is
-
The selection is the Work, not the row. It stays on the same Work as the list
changes. If the selected Work leaves the list, the next key only says where the selection
went. Every action reloads the Work by ID first. -
Feedback names the Work. For example, "me-endpoint: not applied: stale (REFRESH). The
source is unchanged. Next: r reject it." Messages age out. Confirmations name the Work
they act on. -
Restrained color. Color marks only the selection, an actionable CONTINUE, REFRESH and
STOP, failed checks, refusals, and landed Work, which is dimmed. The words always carry
the meaning, andNO_COLOR,--no-colorand--asciiwork as before. -
No phantom keys.
watchhas no auto-apply mode, so it no longer showsShift+Tabor
reacts to it, including on its review screen. The hint lists only the keys that apply to
the selected Work.
Unchanged: piped watch, watch --plain, watch --json, serve, status and every JSON
contract. Every CLI message and all coherence and interaction semantics are also unchanged.
Built with Dispatch: one implementation worker, an independent review and acceptance-test
worker, and a fix packet, each a Claude Code session in its own worktree, supervised by a
pinned Dispatch 0.4.10 and integrated one at a time after human approval.
Dispatch v0.4.10
Dispatch 0.4.10 — No silent gaps
Building 0.4.9 with Dispatch itself, four agents working at once, exposed two
gaps on a busy machine:
- a Claude Code session's start hook was killed while Dispatch was still
registering it, leaving the session untracked and half-made run state behind; dispatch attachfailed when a build tool deleted a file while Dispatch was
reading it.
0.4.10 closes both.
-
A session is tracked, pending, or told it isn't. For a session in its own
worktree of a watched project, Dispatch finishes registering within 40 s, well
inside the hook's 60 s timeout. The session is told which of three outcomes it
got:- tracked: its Work exists before the hook returns;
- pending: its starting state is saved, and the watching owner creates the
Work moments later; - not tracked: its starting state could not be captured in time, or the
worktree kept changing while it was captured.
Work is assembled outside
runs/and moved in only when complete, so a
killed hook never leaves a half-made run behind. A repeated session start
lands on the same Work. -
Registration ignores build output. Attaching Work no longer reads the
integration root's whole tree. Outside strict mode nothing used that walk,
and it was what made registration slow and fragile: Claude Code worktrees,
each with its own build directory, live inside the root. Where a walk remains
(strict mode), a file that disappears mid-walk counts as a difference, not a
failure. -
A moving workspace never gives a mixed starting state. If the worktree
changes while its starting state is captured, Dispatch captures it once more,
and otherwise refuses rather than record a baseline mixing two moments. -
Running Dispatch on Dispatch.
docs/self-hosting.mdis the setup that
built this release:- a pinned, verified release binary;
- a dedicated state directory outside
/tmp; - a dedicated integration clone, with project-local hooks;
- one isolated workspace per worker;
- landing one contribution at a time.
It also sets the rule for build caches in checks: a cache may be reused only
when one workspace's build can never stand in for another's.
Fixed.
- Notices name the full Work ID. Sessions that started together all read
"Work 01M4BRE4" in their notice; Work created in the same quarter second
shares its first 8 characters.
Evidence. A stress trial on a clone of Dispatch, with three cargo builds
putting 1.5 GB of churning build output under the root, every core busy, a
disk write loop, and load averages of 19–25:
- Six Claude Code sessions started at once: all six were registered by their
own hooks in 8.0–8.3 s, with exactly one Work each. - Hooks killed at 0.05–4 s: those killed before their starting state was
saved left no Work, and the owner removed their leftovers. Those killed after
it was saved were published by the owner. - The owner killed with SIGKILL while a registration was pending: the
restarted owner published it. - A repeated session start: one Work, the same starting state, both sessions
recorded. dispatch attachwhile build output churned: 10 of 10 succeeded, and no
build output entered any starting state or patch.- Two files rewritten continuously during capture: all 6 sessions were told
they were not tracked. No mixed starting state was accepted.
Building 0.4.10 itself, four workers under Dispatch 0.4.9 all registered by
their hooks. Dispatch reported no interactions between them, which was
correct.
An audit of names the facts layer could not resolve covered about 1,000 names
over six real runs. It found none that hid a meaningful interaction, so binding
is unchanged.
Not built.
- Strict mode still walks the root's whole tree when attaching.
- Partial run directories left by older versions are not cleaned up.
- The hook budget assumes the 60 s timeout
dispatch setupinstalls.
Upgrading. No migration; the schema stays at 24. Run dispatch stop && dispatch start so the project owner is 0.4.10.
Dispatch v0.4.9
Dispatch 0.4.9 — Interactions, measured
0.4.8 began reporting where pieces of unintegrated Work touch each other. 0.4.9
records, from real use, how often those reports turn out right, so that nothing
is built on them before that is known.
-
Every landing records what was known. When Work is applied, by
accept
or by auto-apply, Dispatch records the interactions its watching owner had
reported between it and every other piece of Work in progress
(interaction.landed). Pieces with no reported interaction are listed too,
so misses can be counted. -
The owner records what happened next. The watching owner records its
next verdict on each of those other pieces (interaction.outcome): CONTINUE
or not, interaction or not.- An outcome is scored only when nothing else could explain it. The landing
must have been observed, and the other piece analysed and valid before. If
another change reached the project, or the piece itself changed before it
was evaluated, the outcome is kept but labelled with why it cannot be
scored. - Each outcome is recorded once, across owner restarts.
- An outcome is scored only when nothing else could explain it. The landing
-
Documented queries read it.
docs/queries/interactions-measured.sql
gives, from your own state directory:- landings by status;
- outcomes by class;
- precision (invalidated among predicted) and misses, with their counts;
- the same by rule;
- landings whose outcomes are missing.
docs/coherence-validation.mdsays what the numbers can support.
"Invalidated" means Dispatch's own later verdict, not a confirmed conflict.
Nothing leaves your machine. -
Live patches are published atomically. A watcher or the owner could read
a work-in-progress patch while it was being rewritten. Patches are now
written to a temporary file and renamed. -
No policy changes. Interactions stay advisory: no verdict, accept or
auto-apply decision depends on them.
Fixed. A landing auto-applied by an owner on its first tick after a restart
would have seen no interaction view. The owner now compares Work before it
auto-applies.
Evidence. A real-agent trial on a small Python project ran five Claude Code
sessions at once:
- A changed the signature of
auth.validate; - B added a caller of it;
- C changed its body;
- D changed an unrelated module;
- E changed another function in the same file.
Before anything landed, Dispatch reported A against B (B uses the signature A
changes) and A against C (both change validate), and nothing else.
D landed first. Its landing was observed, and within 5 s the owner recorded
four outcomes: all scorable, none predicted, all CONTINUE. Then A landed:
- B: predicted, and REFRESH (
fact_broken); - C: predicted, and REFRESH (
patch_conflict); - E: not predicted, and CONTINUE.
The documented queries over that state report 2 landings and 7 scorable
outcomes. Precision was 2 of 2 predicted, with 0 misses among 5 that were not
predicted. That is one small trial, not a rate.
How it was built. This release was built by four Claude Code sessions
working in parallel, each on one packet in its own worktree. Dispatch 0.4.8
watched them, and every packet was integrated through dispatch accept after
review. Dispatch reported no interactions between the packets, which was
correct: none of them shared a file.
The process found two 0.4.8 defects, both still open:
- a
SessionStarthook can be killed by Claude Code's 60 s timeout while
Dispatch registers the Work under heavy load, leaving the session untracked; dispatch attach --workspacewalks ignored build output, and can fail on a
file that disappears mid-walk.
Upgrading. No migration; the schema stays at 24. Run dispatch stop && dispatch start so the project owner is 0.4.9.
Dispatch v0.4.8
Dispatch 0.4.8 — Work that notices Work
Dispatch has checked whether Work still holds against the project as it moves.
0.4.8 also shows where two pieces of Work that are not yet integrated already
touch each other, before either lands.
-
Where Work touches Work. While a project is watched (
dispatch start),
Dispatch compares every piece of Work not yet integrated and reports when:- both change the same declaration or whole file, or edit overlapping lines;
- one changes the signature of, or removes, a declaration the other uses;
- one changes or deletes a file the other relies on.
This is exact to the declaration for Rust and Python, and to the file
elsewhere, and it says which. -
What is not reported: reading the same thing; different declarations of
one file whose edits keep apart; and a change to a body alone that the other
only calls. Work mid-edit whose file does not parse for a moment keeps its
last clean analysis instead of raising a whole-file warning. -
Where it shows:
- a
dispatch watchrow ends withinteracts with <id>; - the selected row's
Concurrentlines say how, for example "it changes the
signature of validate (auth.py), which this Work uses"; dispatch statushas aConcurrentsection;watch --jsonandstatus --jsoncarry a structuredinteractionslist.
It sits apart from the verdict: Work can be
CONTINUEand still interact. - a
-
Advisory only. Nothing is blocked, reordered, refreshed or stopped. When
one piece lands, it leaves the comparison and the other is judged by the
coherence check, as before.
Fixed.
- A Claude check that timed out no longer refuses the profile for good.
Under load,claude --versioncould take longer than its 5 s. Dispatch read
the missing answer as "Claude CLI version changed" and refused the profile
until you authorized it again. Aclaude auth statusthat did not answer
was recorded the same way.- Now only a contradicting observation is a lasting refusal: a different
version, a different account, a changed executable, or an expired
approval. - A version check that gives no answer lets the run go ahead: the
executable's hash, checked first, already fixes its version. - An account check that gives no answer refuses that one launch.
- Now only a contradicting observation is a lasting refusal: a different
- Work launched together is told apart. IDs are shortened to 8 characters,
which Work created within a quarter of a second shares. The view and
interactions now show as much of each ID as tells them apart, and the removal
hook's suggested commands carry the full ID.
Evidence. A real-agent trial with Claude Code 2.1.280 and Cursor Agent
started five pieces of Work together on one small Python project:
- A changed the signature of
auth.validate; - B added a caller of it;
- C changed only its body;
- E changed another function in the same file;
- D changed an unrelated module.
Before anything landed, Dispatch reported A and C changing the same
declaration, and A's signature change against B's new caller. The A-to-B
interaction was already there while B was still running under dispatch attach. It reported nothing for C against B, E, or D.
After A was accepted, it left the comparison within two seconds. The
coherence check then refreshed B (its call no longer matches the signature)
and C (its patch no longer applies), and kept D and E at CONTINUE.
On a 2,000-file repository, comparing 50 pieces of Work costs 1 ms a tick.
Analysing one piece's changes costs about 150–260 ms, and is repeated only
when those changes do.
Not built. Interactions are not stored, not used by any policy, and not
known without a watching owner. There is no ordering advice, no automatic
refresh or stop, no transitive analysis, and no measured precision yet on real
repositories.
Upgrading. No migration; the schema stays at 24. Run dispatch stop && dispatch start so the project owner is 0.4.8.
Dispatch v0.4.7
Dispatch 0.4.7 — Work you can finish where you see it
0.4.6 picked up Claude Code sessions as Work by themselves. 0.4.7 lets that Work
be verified, reviewed and cleaned up without you copying run IDs between commands.
-
Checks can run by themselves, when you allow it.
dispatch setup --checks
can let Work in a project run that project's checks by themselves.- When Claude Code removes a session's worktree, Dispatch keeps its exact
changes, as before. Then it runs the checks in a workspace rebuilt from S0 and
those changes. The Work becomes Ready, or shows its failed checks, and waits
for your review. - The permission names the exact
checks.verifycommands you approved. If they
change, it no longer holds until you approve the new ones;dispatch status
anddispatch watchsay which. - It is stored in Dispatch's private state, never in the repository, so a
repository cannot grant itself the right to run commands on your machine. - It runs checks only. Nothing is applied without your review.
- When Claude Code removes a session's worktree, Dispatch keeps its exact
-
Act from
dispatch watch. On a terminal, select a row with ↑/↓, then:ffinishes attached Work, asking first before running checks nobody allowed;aaccepts, through the same gate asdispatch accept;rrejects, after asking;dor Enter opens the review.
Each key is exactly the command you would type.
watch --jsonand output that
is not a terminal are unchanged. -
dispatch clean. Workspaces Dispatch made fordispatch attach -- <agent>
are kept after a reject.cleanlists the ones whose Work is over and removes
them, with their branches, only after you confirm.--dry-runonly lists them,
and--yesconfirms where there is no terminal. Nothing is ever deleted on a
timer, and each run's record and patch stay.
Fixed.
- Accept now runs the merged-tree checks for discovered Work you finished.
Discovered Work finished withdispatch finish --allow-unsafe-localwas accepted
without its checks running on the merged tree. The flag was not recorded on the
run, so a change that only the merged tree catches could apply. The authority
is now recorded, and accept runs the checks. - Rejecting Work whose worktree was removed now closes it.
dispatch reject
refused it ("no delivered result to review"), so it could not be closed without
finishing it. Reject now closes it and keeps its patch in the run. - A worktree with no changes that vanished without notice now closes. It
used to show aslost; it closes with no changes, since nothing was lost. finishno longer fails on a busy project.finishrefused the run when the
background owner was busy with it at that moment. It now waits up to 5 s. Accept
or reject typed at the command line no longer fails as "stale" because the
owner had just recorded a verdict.
Evidence. A real-agent trial with Claude Code and Cursor Agent:
- With consent. A
claude -p --worktreesession registered Work with local
authority. Its worktree was removed, and the exact changes were kept before the
hook returned. The owner then verified the Work by itself: Ready, checks
passed, nothing applied. After a teammate's commit, it was accepted from
dispatch watchwith the merged-tree checks run. - Consent voided. Changing the project's checks voided the consent. The next
session's Work kept its changes on removal and waited. The trial found that
rejecting it was refused, which is fixed above. - Clean.
dispatch attach -- cursor-agentfrom the checkout finished Ready and
was rejected.dispatch clean --dry-runlisted its workspace, anddispatch cleanremoved it only after confirmation.
Not built. There is no Codex lifecycle integration: Codex's hooks need a trust
review of every hook definition, and its session end also means "idle".
dispatch attach -- codex remains the supported way to isolate Codex. There is
also no automatic apply for discovered Work, no retention timer, and no start at
login.
Upgrading. No migration; the schema stays at 24. State is forward-only: 0.4.6
cannot read a run finished by consent. After upgrading, run dispatch stop && dispatch start so the project owner is 0.4.7.
Dispatch v0.4.6
Dispatch 0.4.6 — Work that appears on its own
Run your agent the way you already do, and Dispatch picks up its work:
- Claude Code sessions become Work by themselves. With Claude Code's hooks
installed (dispatch setup→ Runtime integrations, shown and approved first),
a session in its own worktree of a project you watch (claude --worktree)
becomes Work with no Dispatch command.- S0 is the worktree as the session found it, captured before its first edit.
- Later sessions in that worktree join the same Work.
- Its verdict follows the project as it moves.
- Any agent gets its own workspace.
dispatch attach -- <agent>run from your
checkout now makes the workspace. It sits outside the checkout, on its own
dispatch/…branch, and starts as the checkout's exact world, uncommitted
files included. The agent needs no worktree support of its own.- The workspace is removed once its work is applied, and kept after a reject
or a crash;dispatch status <id>says where it is.
- The workspace is removed once its work is applied, and kept after a reject
- Nothing is claimed where it can't be. A session running directly in your
checkout is told that Dispatch cannot tell its edits from yours, and nothing is
tracked.
A removed worktree keeps its work. Claude Code can delete a worktree at
session exit or through its ExitWorktree tool.
- Before either, Dispatch writes the work's exact final changes durably into the
run and records the removal. Only then does the hook let the deletion go ahead. - If that cannot be done, the hook fails and the worktree stays.
- Removal never finishes the work for you:
dispatch finishverifies it in a
workspace rebuilt from S0 and the kept changes, or yourejectit. - A worktree with no changes simply closes.
Fixed.
- Finishing no longer fails on ignored build output.
dispatch finishrefused,
and marked the work failed, when the workspace held over 4 GiB, 200,000 files, a
file over 512 MiB, or a symlink out of the tree. That included ignoredtarget/
ornode_modules/. Only paths that can enter the changes count now. - Process identity on macOS no longer drifts. A process's identity included
kern.boottime, which the clock adjusts while the machine runs, so live
processes gradually looked "reused":dispatch stoprefused, and a live
wrapped attach could have been adopted as orphaned.- The identity now uses the boot session's UUID.
- Identities recorded by older versions still match by pid and exact start
time, sodispatch stoprecognises an owner started before upgrading.
- The project view tells you more:
- it names where work came from:
native,attached,discoveredor
isolated; - it says
idle,removed,lostandrejectedwhere it saidworkingor
ready; - it stops calling a moved world "unmoved".
- it names where work came from:
Evidence. Real-agent trials with Claude Code 2.1.280 and Cursor Agent:
- A
claude -p --worktreesession registered itself, and its Work was created 6 s
before the agent's first edit. - A teammate's commit moved its verdict with no command run.
- Removing the worktree through
ExitWorktreekept the exact changes. The trial
found that this tool does not runWorktreeRemove, which is why Dispatch now
also hooks it. - The work was then finished in a rebuilt workspace and applied.
dispatch attach -- cursor-agentfrom the checkout worked in its own workspace
while a teammate committed. It was applied, and its workspace released.- A wrapper killed mid-run left its workspace intact, and the owner adopted the
work. - Replayed and resumed sessions never made duplicate work.
Not built. Codex's hooks exist, but every hook definition needs the user's
trust review and its session end also means "idle", so a Codex adapter waits;
dispatch attach -- codex isolates Codex today. There is no process scanning,
automatic verification, start at login, or retention cleanup of rejected
workspaces yet.
Upgrading. No migration; the schema stays at 24. State is forward-only: 0.4.5
cannot read runs that use the new attachment fields. Install Claude Code's hooks
through dispatch setup; install them again if you move the binary or the state
directory.
Dispatch v0.4.5
Dispatch 0.4.5 — Dispatch in the background
Turn watching on and get on with your work:
$ dispatch start
✓ Watching ~/src/project
Dispatch is running in the background. See it: dispatch watch · Stop: dispatch stop
While it runs, Dispatch keeps the verdict of every Work item it knows about current
as the code moves:
- results waiting for your review, native or attached;
- attached work still in progress;
- attached work it may apply.
No agent needs to be set up to watch a project.
New commands.
dispatch startwatches the project in the background and returns at once.
It is one local process per project, in its own session, so closing the
terminal does not stop it.dispatch watchshows the project's Work live, under a line saying who
watches. Leaving it (Ctrl+C) does not stop watching.dispatch stopstops exactly this project's watcher.
dispatch status now ends with a Project: line saying whether the project is
watched.
Trustworthy lifecycle.
- "Watched" means the project's lock is held, and the operating system releases
that lock when the watcher exits, crashes or the machine reboots. So nothing
claims a project is watched when it is not, andstartrecovers after a crash. stopsignals a process only when its recorded identity (pid, start time and
boot) still matches.- Two
starts at once leave one watcher. - After a reboot, run
dispatch startagain.
Fixed.
- The project owner re-checks results waiting for review when the code moves. A
Ready result used to keep the verdict it finished with, so the project view
could say CONTINUE whilechecksaid REFRESH. They now agree, and a refusal by
the merged-tree checks still stands until the code moves. - A verdict that flipped back within a minute was never recorded, and a restarted
serveignored a stored verdict that no longer held. The owner now compares
each verdict with the one stored on the run. - Attached work with no live owner is re-checked when that work changes, not only
when the project moves (the gap 0.4.4 closed for native runs). - Following attached work used to re-hash the whole workspace every tick. The
owner now keeps a Git index per Work item, cutting the cost fourfold (about
50 ms per item on a 2,000-file project). - Results waiting for your review stay in the project view until you decide.
Reasons quoting Git's errors stay on one line.
Evidence. A real-agent trial on a small Python service:
dispatch startreturned in 0.1 s.- On its first tick the watcher found the four results left waiting for review
that morning and marked them correctly: two REFRESHpatch_conflict, one
REFRESHfact_broken(a caller ofvalidate(token)after the API became
validate(ctx, token)), and one STOP (already landed). - A native Claude Code result and a foreign-attached Cursor session then ran in
parallel. Two seconds after a teammate's commit touched the same code,
dispatch watchshowed the Claude result as REFRESH, with no command run. - After SIGKILL,
statusandwatchsaid "not watched",stopsignalled
nothing, andstartrecovered.
Cost (measured on a 2,000-file Git project; no telemetry is collected):
- An idle tick costs about 50 ms for the project signal, plus about 50 ms for
each attached Work item in progress. - When the code moves, each affected item takes about 0.4-0.5 s to re-evaluate.
Not built. Dispatch does not discover agents it did not launch or that you did
not attach, and it does not start at login. There is still no network service,
protocol or automatic refresh.
Upgrading. No migration; the schema stays at 24. The state directory gains
watchers/. watch --json adds a watcher object, and serve --json is
unchanged.
Dispatch v0.4.4
Dispatch 0.4.4 — verdicts you can trust and act on
A multi-agent trial ran eleven Work items, from Claude, Claude Code and Cursor,
against one project while it changed underneath them. Dispatch caught stale work
where plain git apply would have let it through. The trial also showed where its
verdicts could not yet be trusted or acted on. 0.4.4 fixes those places and lets a
human overrule a verdict the analysis got wrong.
Fixed.
- A refused accept no longer records an acceptance. When coherence refuses an
accept, nothing is applied, no review is recorded, and the result stays pending
for you to refresh or reject. Accept now applies first and records your review
only once the change has landed. - Every surface shows the same verdict. After the checks on the merged tree
refused a result,checkandstatusshowed CONTINUE, andcheckadvised accept
again, whileserveshowed REFRESH. They now all show the refusal until the
source moves.refreshpasses the failure on to the new agent. - Integration failures say what failed. The reason quotes the failing check in
its own words, for exampleERROR: test_admin_token (…) / TypeError: validate() missing 1 required positional argument: 'token', not only a log path. - The mid-run watcher also follows the agent's work. It used to re-evaluate only
when the source moved. A change that landed before the agent touched the same
code was therefore never reported mid-run. It now re-evaluates when either the
source or the agent's work so far changes. The work is fingerprinted with the
trusted baseline repository, so ignored build output never counts. - Adding an optional Python parameter keeps callers valid. A referenced Python
function that gains only defaulted parameters,*argsor**kwargsno longer
marks every callerfact_broken. Any other signature change still does, and
Rust signatures compare exactly. - Clearer words:
- attached work is told to run its agent again, not to
dispatch refresh; - native S0 shows the project commit;
- a result applied to an unmoved source shows
unmoved; - STOP names the run that already landed the same change;
- a run waiting for your answer shows
question, notworking.
- attached work is told to run its agent again, not to
- Answering a question no longer ends the run when the source moved. A native
run stopped withsource_driftwhen it was answered after any change to the
project, even though the accept gate judges the result against the source as it
is then. The continuation now proceeds from the run's snapshot like any attempt.
New: recorded human override. dispatch accept <run> --despite-refresh --explanation "<why>" applies a REFRESH that comes only from the file and symbol
analysis, when you have checked that the work still holds.
- Your checks must still run and pass on the merged tree.
- The overridden verdict and your explanation are recorded (
coherence.overridden). - It never overrides STOP, a patch that no longer applies, or a failing check.
- Auto-apply never uses it.
Evidence. The same trial was re-run with real agents on this release:
- The Claude Code patch that still called
validate(token)after the API changed
was refused as before. An override attempt was refused too, because the merged
tree's tests failed with thatTypeError, which the refusal quotes. - The redundant timeout change was STOP, "landed by" the run that applied it first.
- The format change that broke a landed test was refused by the checks, and
check,statusandserveagreed afterwards. The refused accepts stayed
pending. - The Cursor caller of
format_user(user)now applies after the optional parameter
landed; 0.4.3 refused it. - Given the failing test, the refreshed agent asked whether it could update that
test, where 0.4.3's refresh repeated the failure.
Upgrading. No migration; the schema stays at 24. CoherenceRecord gains an
optional overridden field. serve --json gains overridden, and check --json
gains landed_by for STOP.