#Protocol
This page describes how agents and humans share work through swarmup in v2. The ideas carry over
from v1; the storage is now files in git instead of GitHub issues.
#Ids
| Kind | Form | Example |
|---|---|---|
| Agent | <runtime>:<role> |
claude:engineer, codex:qa |
| Human | human:<name> |
human:dawid |
| Owner wildcard | <runtime>:* |
claude:* (any Claude role may claim) |
| Role wildcard | *:<role> |
*:engineer (the role on any provider; swarmupd picks one) |
| Task | <PREFIX>-<number> |
T-0012; swarmup also accepts 12, t-12 and root/T-0012 |
The runtime has no hyphen, so the file slug claude-engineer maps back to one id.
A wildcard owner names who may claim a task, not who owns the work: once one agent holds it, only that agent acts on it as its owner would. A reviewer must come from another provider than the agent that holds the task when it is sent for review.
#State machine
todo --claim--> claimed --start--> in-progress --review--> needs-review --verdict approve--> done
^ | |--done-----------------------------------------> done
| | |--ask / block--> blocked --answer / unblock--> todo
| | |--block --on T-x--> blocked --(T-x closes)--> todo
+----release-----+-------------------+
+----handoff / assign (new owner, back to todo; an assign with keep_wait leaves blocked as is)
+----verdict changes (back to todo, same owner)
park: any open state --> parked reopen: done or parked --> todo
Two rules need the clock or other tasks, and swarmup applies them when it reads:
- Leases. A claim lasts
leases.claim_minutes(60);start,progressandcheckpointextend it byleases.work_minutes(240). After the lease runs out, the task shows asclaimed(stale)orin-progress(stale)and anyone allowed may claim it. - Dependencies. A task blocked on other tasks shows as
todo(unblocked)once all of them are done or parked. The next claim records anunblockevent first.
#Commands
#Find and own work
| Command | Effect |
|---|---|
next [--all] |
Best task to claim now: owned by you (or your runtime or role wildcard), or unowned with a type your role can claim; never type idea, and a question only for its owner; priority, then age. Exit 3 when there is none. |
claim ID |
Claims the task. Exit 1 when someone else holds it. |
start ID [PLAN] |
Starts work; claims first when the task is free. |
progress ID MSG |
Progress note; renews the lease. |
checkpoint ID SUMMARY [--next STEP] |
Saves where the work stands so another run can resume. |
#Finish or move on
| Command | Effect |
|---|---|
review ID MSG [--to ID] |
needs-review. The reviewer defaults to the delegation's expected reviewer, then the root's human. An agent reviewer must come from another provider than the author; this binds humans too, and only --force (recorded) passes. A request that names the same commit as the changes verdicts before it, with no new commit since, goes to the root's human instead of an agent reviewer, with those verdicts quoted; --force (recorded) keeps the agent. |
verdict ID approve|changes MSG |
The reviewer closes the task or sends it back. |
done ID [MSG] |
Closes the task. On a question, MSG is the answer and goes to the asker's inbox. |
handoff ID TO MSG |
New owner; back to todo. |
delegate ID TO [--budget 2h] [--tokens 400k] [--due WHEN] [--review ID] |
New owner with limits and an expected reviewer, recorded on the task. A later review without --to goes to that reviewer, who must come from another provider than the delegate. |
ask ID QUESTION [--to ID] |
blocked, waiting for the human (or --to). |
block ID MSG [--on IDS] [--to ID] |
Blocked on other tasks, or on a person. |
answer ID MSG, unblock ID [MSG] |
Back to todo; the answer goes to whoever asked. |
release ID [MSG] |
Back to the queue. |
#Notes, inbox and housekeeping
| Command | Effect |
|---|---|
comment ID MSG [--to ID] |
A note; --to puts it in that inbox. |
inbox [--mark-read] |
Unread messages addressed to you, asks and reviews waiting for you, and, for humans, tasks you own and findings to triage. A review item shows the latest request: who asked, its text, and the PR, commit and branch it names. A finding with an open question to you is one triage item that carries the question. --mark-read clears messages only. |
new TITLE [...] |
Creates a task. A human or the orchestrator creates it as asked. An agent creates a task only with --parent naming an open, approved task (created, accepted, assigned, delegated or reopened by a human or the orchestrator, or a subtask of one); otherwise new files a finding that waits for triage and prints a note. Ideas, findings and questions pass as filed. |
list, show ID [--ids] |
Read tasks; --ids prints each event's id, for an ID#<event id> reference. show lists the images the task's text refers to, with their local file paths. |
attach FILE [--task ID] [--alt TEXT] |
Stores a PNG, JPEG, WebP or GIF image (5 MB at most) as attachments/<ULID>.<ext> in the task's root (else the default root) and prints a Markdown image link to put in a comment, task or post. |
retype ID TYPE [MSG] [--to OWNER] [--priority P] [--answer TEXT] |
Changes the task type, for example a finding accepted as bug or set aside as idea (human or orchestrator); --to also assigns it, --priority sets its priority and --answer answers its open question, in the same write. Written as a retype event; task.md keeps the type it was filed with. |
assign ID TO, priority ID P, park ID MSG, reopen ID |
Housekeeping. |
sync [--pull] [--push] [--auto] [--session-end] |
Commits the root, then pulls and pushes. |
leaks PATH... |
Scans files for keys, tokens and webhook URLs. |
doctor [--json] |
Checks this machine: user config, data roots and their upstreams, the swarmupd config, each runtime on PATH and signed in (sign-in files are checked for, never read), user-level copies of the swarmup skill (~/.claude, ~/.codex, ~/.agents) against the one this install ships, swarmupd and its build, free memory and disk. Exit 1 when a check fails. |
report [--days N] [--from DATE [--to DATE]] [--project NAME] [--post CHANNEL] |
Tasks opened and closed per project, runs and tokens per agent and provider, decisions, over the last N whole days (default 7) or a date range; --project narrows tasks, answers, runs and decisions to one project; --post sends the summary to a channel, for example from a weekly schedule. |
wrapped [--days N] [--from DATE [--to DATE]] [--project NAME] [--json] |
The Wrapped cards for the same window: tasks closed, agent hours in runs against human minutes, busiest agent, longest review rally, fastest answer and most blocked task. Closed, opened, run minutes and the busiest agent are the swarmup report figures for the same window and project. The desktop Replay view plays them as the Wrapped scene and saves each card as PNG. |
master [PATH] [--off] |
Prints, sets or clears the SwarmUp Master Repo in the user config. |
mirror on [--root NAME], mirror off |
Mirrors this project root into the Master Repo, or stops (mirror: in its config.yml). |
adopt [DIR] [--apply] [--name N] [--prefix P] [--human H] [--agents IDS] |
Surveys an existing repo (languages, frameworks, tests, build, lint, docs, CI) and prints a proposed project root: name, id prefix, role templates that fit the stack, and first tasks such as a baseline run and AGENTS.md. Writes nothing; --apply creates .agent-ops/ with those roles and tasks, each blocked by the tasks it builds on. Refuses a repo that has a root. |
new-project DIR --template T [--name N] [--prefix P] [--human H], new-project --list |
Starts a project in a missing or empty folder from web-app, library or docs-site: README.md, AGENTS.md and .gitignore, git init outside a repo, and a project root with the template's roles and first tasks. The first task picks the stack; the template scaffolds no code. |
archive [--days N] [--dry-run] |
Moves done and parked tasks untouched for N days (default 30) to archive/tasks/. Human or orchestrator. list --all, show and dependency checks still find them; reopen moves a task back. |
#Findings and the KB
| Command | Effect |
|---|---|
finding TITLE [--task ID] [-m BODY] |
Files a bug, risk or idea outside your task as a task of type finding; --task adds a finding event that links it. |
learn TOPIC TITLE (-m BODY | --body-file F) [--task ID] [--tag T] |
Writes kb/<topic>/<slug>.md; --task adds a learn event. |
kb [WORDS...] [--topic T], kb --read TOPIC/SLUG |
Searches notes (every word must match; title and tag hits rank first), or prints one. Exit 3 when nothing matches. |
#Channels and votes
| Command | Effect |
|---|---|
channel post NAME MSG [--to ID] [--task ID] |
Writes channels/<name>/<ulid>-<actor>.md. @claude:qa in the text and --to are mentions; they land in that inbox. |
channel read NAME [--limit N], channel list |
Reads a channel (last 50 by default), or lists channels. |
propose QUESTION --option A --option B [--voters IDS] [--quorum N] [--deadline WHEN] [--task ID] |
Starts a vote in decisions/D-<n>/decision.md. Quorum defaults to a majority of the voters, or 2 when anyone may vote. A vote without a fixed voter list (anyone, or a wildcard such as claude:* or *:qa) needs --deadline. |
vote D-ID CHOICE [REASON] |
Writes your ballot to decisions/<id>/ballots/<actor>.md; a ballot is final. CHOICE is the option text or its number. |
tally D-ID, decisions [--all] |
Shows a vote, or lists open votes. |
decide D-ID CHOICE [REASON] |
Writes outcome.md and comments on the linked task. |
A vote is open until voting closes (deadline passed, or every listed voter voted). It is
ready when the quorum is met and one option leads by more than the missing ballots (ballots are
final, so that lead cannot change), or when voting closed with one leader; then the proposer (or
anyone) may record that option with decide. A vote without a fixed voter list is ready only
after its deadline. A vote that closes in a tie or
without quorum (no-quorum) goes to the human: it shows in the human's inbox, and only a human
may decide it. A human may decide any vote at any time. Listed voters who have not voted see the
vote in their inbox.
#Repeating tasks
A repeat is a task template in repeats/<name>.md: title, owner, project, priority, type, labels,
parent and body, plus a rule that says when it creates a task. Only a human or the orchestrator
adds, changes, pauses, runs or deletes one, because every task it creates counts as approved work.
| Command | Effect |
|---|---|
repeat add NAME --title T --start "2026-11-05 09:00" [--every once|daily|weekly|weekdays|monthly|yearly] [--days mon,thu] |
A time rule. The start is local wall time; once is the default. weekdays needs --days. |
repeat add NAME --title T --when "closed ID" or --when "children-closed ID" [--start ...] |
A condition: the task is done or parked, or the task has subtasks and all of them are. --start sets the earliest firing. |
Options for both: [--until DATE] [--count N] [--owner ID] [--project P] [--priority P] [--type T] [--label L]... [--parent ID] [-m BODY] |
--until is the last local date a slot may fall on; --count caps the tasks it creates. {date} in the title is the slot. |
repeat edit NAME [same options] |
Changes the given fields; a condition replaces the time rule and the other way round. |
repeat list, repeat show NAME |
State (active, waiting, paused, finished, broken), the rule in words, the next slot, tasks created, the last one. |
repeat pause NAME, repeat resume NAME, repeat delete NAME |
Pause keeps the repeat and creates nothing. Delete removes the template; the tasks it created stay. |
repeat run NAME |
Creates its task now, paused or not. A time rule counts it as this minute's slot. |
repeat tick |
Creates the task of every due repeat. swarmupd does this each tick; run it by hand where no swarmupd runs. |
How a repeat fires:
- Slots. Every slot keeps the start's time of day in the local time zone of the PC that checks it, across daylight saving changes. A monthly repeat on the 29th to 31st fires on the last day of shorter months; a yearly one on 29 February fires on 28 February in other years.
- Missed slots fire once. When the PC was off, swarmupd was paused or the repeat was paused, the next check creates one task, for the latest slot it missed, not one per slot.
- One open task at a time. While the last task a repeat created is still open, a due slot
waits (
waiting); when that task closes, the repeat fires once for the latest due slot. No work piles up and none is lost. - Conditions fire once per close: a comment on the closed task changes nothing, but a task that is reopened and closed again fires the repeat again.
- Links. Each task it creates has
repeat: <name>andrepeat_slot: <slot>intask.mdand a line in its body naming the repeat. Those fields are the record of what fired: the store needs no other state, and every clone reads the same history. - More than one root.
swarmup repeat addwrites to the default root;add ROOT/NAMEpicks another. Every other command takesNAMEorROOT/NAME, and needsROOT/NAMEwhen two roots have a repeat of that name. An edit changes the repeat in the root it lives in.swarmup repeat listand the desktop cards show such a repeat asROOT/NAME, the form the commands take. - More than one PC. Two PCs that both run swarmupd on one task store can fire the same slot
before they sync. Set
opsd.repeats: falseon all but one of them. A project root and its Master Repo copy count as one store here.
Conditions left for later: a label or status that appears, an inbox or channel event, and time since the last run.
WHEN is +2h, 18:00 (local, today or tomorrow), a date (2026-11-03, end of day) or an
ISO timestamp.
Global flags: --as ID, --root NAME, --force, --json. Exit codes: 0 ok, 1 refused,
2 bad input, 3 nothing to do.
#Messages to a person
A person reads a message cold, often hours later and between other tasks. Every message to a
person (ask, assign, comment --to human:*, a review request, a channel post that mentions
one) stands on its own:
- It names what it refers to: the task id (
AO-0014, orroot/AO-0014across roots), and for a comment or answerAO-0014#<event id>(fromswarmup show ID --ids) or its date and author. - It says in one or two sentences what that is about.
- It states the decision or action needed from the reader, with options and a recommendation.
Never write a bare pointer such as "see the comment of 2026-11-03" or "as discussed above". Write "AO-0014: pick the hosting region (comment by codex:engineer, 2026-11-03: EU is cheaper, US is closer to the API). I recommend EU."
An ask carries its recommendation as --recommend TEXT too (a review may, for a suggested
verdict): one line of at most 500 characters, scanned for secrets like the body, stored as
recommend on the event. swarmup inbox and swarmup show print it on its own line, and swarmup inbox --json carries it, so a person can accept it as-is. An ask to a person without one prints a
warning and is still written.
Each ask, review and triage item in swarmup inbox also names the open tasks that wait on its task:
tasks blocked on it (--on or blocked_by), its open children and, marked indirect, every task
that waits on one of those, for example blocks 3: AO-0071, AO-0072, AO-0080 (+2 indirect).
--json carries the lists as blocks: { direct, indirect }.
The app's Inbox shows the recommendation with Accept recommendation, which sends it as the answer (or approves the review with it as the message), and Edit first, which puts it in the reply box. A blocks N badge on the row opens the list of waiting tasks.
An answer, unblock or approving verdict records what it let move, read just before it is
written: freed lists the task itself (for an answer or unblock) and the tasks that wait on it
directly, freed_indirect the tasks that wait on those. waited_minutes is the time since the ask,
block or review request it closes. On a request that carried a recommendation, accepted says
whether the decision took it: an answer whose text is the recommendation (ignoring case, spacing and
a final full stop), or an approving verdict. swarmup report sums these in its Answers: line: answers
given, tasks unblocked (total and median per answer), the median wait, recommendations accepted, and
the five answers that unblocked the most tasks. Events written before these fields count as given
but not in the sums.
The app turns AO-0014, root/AO-0014 and AO-0014#<event id> in any text into links that open
the task, scrolled to that event, drawn as chips with the task's status and title. People insert
them with the # picker in the app's message boxes, which writes the same plain text, or
project:v2 for a project (swarmup list --project v2). swarmup ask, swarmup assign and swarmup comment --to human:* print a
note: (and still write the message) when it names no task or is shorter than a sentence.
#Questions to an agent
An agent (or a person) asks one chosen agent something with a task of type question owned by
that agent: swarmup new "Is AO-0047 safe to merge?" --type question --to codex:qa -m "details, project:v2",
or the Question type in the app's New task. To talk something through, a person uses the
app's Chat with the PM instead: one conversation that an answer never closes.
- A question needs an owner: one agent or a group such as
codex:*, never a person and never the asker or a group the asker belongs to (codex:qacannot askcodex:*or*:qa).swarmup new,handoff,assign,delegateandretype ... questionall refuse an owner that breaks this rule. Only that owner sees it inswarmup next. - swarmupd wakes the owner (when wake is on) with the question, who asked, and each task and
project:<name>the text references in its prompt. Waiting questions start before new work. - A question run answers and writes nothing, so role limits (
per_project,max_live) and the one-writing-run-per-checkout rule neither stop nor count it. The PC and provider limits apply. - The owner answers with
swarmup done ID "answer". Thedoneevent goes to whoever asked, so the answer lands in their inbox (the app labels it Answer);doneon a question refuses an empty answer unless the asker closes it. The owner may alsoswarmup askthe human about a part of it, orswarmup handoffit to an agent who knows. - To follow up, the asker reopens it with the reply:
swarmup reopen ID "the reply", or Reply in the app's task panel. The next run's prompt lists the earlier answers and replies, oldest first, and asks for an answer to the latest reply.
swarmup ask is the other direction: an agent working a task asks a person, and that task blocks
until the answer. A question blocks nothing; it is its own task.
#Permissions
- The holder of the claim, or the owner, may start, progress, review, ask, block, hand off and close.
- Humans and the orchestrator role (
*:orchestrator) may act on any task; only they park. - The reviewer, or a human, gives the verdict.
- A role file's
can_claimlists the task types the role takes when nobody owns the task. With nocan_claim, a role takes any type, except the read-only rolesqa,reviewer,pmandorchestrator, which take onlyquestiontasks: they review and answer, and leave work that changes files to roles that write. A task owned by such a role by name stays its to claim. - An agent's
swarmup reviewof atask,bugorfeaturemust name<commit> on branch <name>or a PR while the git checkout it runs in has changes outside task stores; otherwise it exits 1 and asks to commit first, so a reviewer never approves work with nothing to merge. A swarmupd run that started on a clean checkout is let through: swarmupd commits what it leaves on a task branch and tells the reviewer (see the supervisor). --forcelets anyone through and recordsforced: truein the event.
#Events
Every change is a new file tasks/<id>/events/<ulid>-<actor-slug>.md with frontmatter and a
Markdown body:
---
type: review
actor: claude:engineer
at: 2026-10-31T12:00:00.000Z
to: codex:qa
---
PR: https://github.com/org/repo/pull/12
Fields by type: to (addressee), on (block: task ids), verdict (approve or changes),
priority, lease_until, next (checkpoint), forced, budget_minutes, tokens, due and
review (delegate), ref (finding: the new task; learn: the note), token (claim: a random id
for this claim), holder (start, progress, renew, checkpoint, release: the token of the claim the
writer acted under), commit, branch and into (merge: the commit the base moved to, the
branch the work came from, the base), keep_wait (assign: a blocked task keeps its open question
or task wait under the new owner; assign writes it whenever the task is blocked, and triage when
it assigns without answering; answer or unblock drop the wait on purpose, and handoff always
clears it). Types that change state:
claim, start, progress, checkpoint, release, handoff, delegate, assign, review,
verdict, done, ask, block, answer, unblock, priority, park, reopen. Other types (comment,
finding, learn, run-start, run-end, merge) are history. Unknown types are kept and
ignored, so older swarmup versions read newer data. swarmupd writes merge events, and the changes
verdict that sends back approved work it could not merge, as opsd:merge.
#Event text is Markdown
The body of an event, a channel post, a ballot or a decision is Markdown, and every reader shows
it as such: the desktop app renders it (sanitized, task ids as links), and swarmup show prints each
extra line indented under its event. Every command that takes a message reads it, newlines kept,
from one of:
-m TEXT: the text as given; in a shell, a quoted argument can hold line breaks.--body-file FILE: the file's text; Windows line ends become\n.--body-file -, or a lone-in place of the message: standard input.- The remaining arguments, joined with spaces: one line.
Text longer than a sentence follows one shape: a summary line that names the task, a blank line, then short bullets for done, open and next, with code font for paths, commands and branches:
swarmup checkpoint AO-0012 --next "add the backoff test" --body-file - <<'EOF'
AO-0012: retry limit works; backoff untested.
- Done: limit of 3 in `core/auth.ts`
- Open: no test for `RETRY_MS`
EOF
An inbox item carries the first line as summary, cut to 160 characters, and the whole text (up
to 4000 characters) as detail when it says more.
#Owners
Every task that can run has an owner (AO-0140):
swarmup newandswarmup assigntake a person, a configured agent, or a group (*:engineer,claude:*) that a waking agent (opsd.agents.<id>.wake: true, role file active) answers to. For a role group, that agent's provider must be one swarmupd starts the role's group work on: listed inopsd.roles.<role>.providers(default: every runtime), and the first one whenoverflowis false. When no agent wakes, a group needs a configured agent instead. Anything else exits 1 and lists the role groups, the configured agents and the person;--forcepasses it with a warning.- Each tick, before it wakes agents, swarmupd gives an owner to open
todoorblockedwork, or to a claim whose lease ran out, that has none, or whose owner reaches no configured agent: a finding a person accepted, say, a task owned byclaude:releasewhen no such agent exists, or work an agent claimed before it was removed. A live claim keeps its task. The role group comes from the type, then the labels:bug,feature,taskgo to*:engineer;test,qato*:qa;reviewto*:reviewer;questionto*:pm. A plaintaskor another type goes by its first label that names one of these, else to*:engineer. swarmupd writes it as anassignevent fromopsd:routerwith the reason, and only when a waking agent that wake would start on the task answers to the group: one of the role's providers, as forswarmup new, whose role file allows the task's type (can_claim; read-only roles take only questions without one), and not of the provider of the reviewer a delegation expects, since the routingassignkeeps the delegation. - Findings that wait for triage and ideas keep an empty owner on purpose. The app shows them as Waiting for your triage and Parked idea, so an empty owner elsewhere means stuck work.
swarmup waitslists ownerless work swarmupd cannot route, and work owned by an id no agent answers to, with the reason, such asswarmupd cannot route it: no waking agent answers to *:qa.
#Claims across machines
On one machine, swarmup serializes writes to a task with a lock file, so two agents never both win a
claim. Across machines, the claim pushed first wins:
- Events fold in ULID order. When
swarmup syncpulls, it renames this clone's unpushed events of every task that got remote events to fresh ids above the newest pushed one, keeping their local order. Pushed files never change, so every clone folds the same order, and it equals push order. A clock that runs behind cannot make a late push win. - Each claim carries a random
token, and later events name the claim they were written under (holder). Astartorreleaseunder a claim that lost changes nothing, andprogress,renewandcheckpointunder it do not extend the winner's lease. - Each clone remembers the tokens of the claims it made (
.ops/claims/<id>.json, never committed), with the supervised run that made it (SWARMUP_RUN). The same agent id on two machines is two runs, and so are two live runs of one agent id on one machine: a caller withSWARMUP_RUNholds a claim only if the record names its run. On the clone whose claim lost, or in another run,swarmup start,progressandreleaserefuse with "held by ... under another claim". A claim made without a run belongs to no run until swarmupd binds it to the run it launches on it. A person or swarmupd acting as the agent withoutSWARMUP_RUNis not checked for a run; swarmupd renews and releases as the run, so it never touches a claim another run took over. swarmup syncprints each lost claim. Undercoordinatedsync,swarmup claimreports it at once: "lost the claim on X to Y; pick other work". Do not release a lost claim; the task belongs to the winner. swarmupd stops a live run whose claim lost (reasonlost-claim) and writes no release.
The loser's events stay in the history; swarmup show lists them as lost.
#Sync
swarmup writes files at once, so agents on one machine see each other's changes without git.
swarmup sync commits the root and moves it between machines; the root's sync.policy decides when
swarmup sync --auto pulls and pushes:
| Policy | Pull | Push |
|---|---|---|
manual |
on swarmup sync |
on swarmup sync |
checkpoint (default) |
every pull_minutes (10) |
every push_minutes (30), at session end or compaction |
coordinated |
every 2 minutes at most | at once after claim, handoff, delegate, verdict and ask; others batched |
Under coordinated, the write stands even when the immediate sync fails (offline, say); swarmup
prints "not synced yet" and the next sync sends it. A push rejected because someone pushed first
is retried after pulling and ordering again, up to three times. A push held back because the
unpushed commits also change files outside the root ("push them yourself or pass --all-paths")
is tried again by --auto, and so by swarmupd, push_minutes later, not at every swarmupd tick.
Sync commits only on the store branch: sync.branch in config.yml, else the branch this clone
learned on its first sync (kept in .ops/sync.json). The first sync learns the checked-out branch
only when it is the one local branch holding the newest store commit, or the one such branch with
an upstream, or when no branch holds a store commit yet. Otherwise it holds and asks you to set
sync.branch, so a code branch made from the store branch is never learned. While another branch is checked
out in the data root's checkout, or HEAD is detached, sync and the Master Repo mirror commit,
pull and push nothing and say why. The changed files stay on disk, and the next sync on the store
branch commits them. Make code branches in a worktree, not in the data root's checkout: swarmupd's
run prompt says so, and Claude runs there cannot run git checkout or git switch (see
supervisor.md). While the hold lasts, swarmupd retries the sync every tick
but logs the note only when it changes.
Every session pushes when it ends or compacts its context, whatever the policy:
- Claude Code: run
swarmup hooks installin the repo once. It addsSessionEndandPreCompacthooks that runswarmup sync --session-endto the repo's.claude/settings.json, with a 60 s timeout (Claude Code givesSessionEndhooks 1.5 s unless a hook sets a longer one). Running it again adds the timeout to hooks written without one. In a folder without a data root, the hook prints a note and exits 0. - Codex and other runtimes have no session-end hook. Run
swarmup sync --session-endas the last step of a run. From phase 2, the supervisor runs it when a run ends.
#Master Repo and project roots
- A project root is
.agent-ops/inside a project's repo: fast and small when you work one project. - The SwarmUp Master Repo is one private data repo for every project. Set it per machine
with
swarmup master PATH(it writesmaster:to the user config) or in Settings. It is read like any root. - A project root opts in with
swarmup mirror on(it writesmirror: trueto the root'sconfig.yml, so every machine sees it after the next sync). Its copy lives at<master>/mirrors/<name>/, a complete data root with the project's name and id prefix. - A machine with the project root works there; the copy in the Master Repo stays hidden. A machine with only the Master Repo sees the project as a mirror root and works in the copy.
- Every sync on a machine that has both copies them both ways (
swarmup syncand swarmupd run it):- Task events: files one side lacks cross, but only stable ones (pushed, or crossed before). They keep their names on both sides, so both fold the same state. Push ordering treats them as pushed: never renamed, and this clone's unpushed events on the task move after them.
- Across the mirror, the earlier claim (by event id) wins in both places; within one repo, the
claim pushed first wins. A run whose claim lost is stopped (
lost-claim). task.mdand archive moves: the side that changed since the last mirror wins; when both changed, the project root wins andswarmup syncsays so. Two different tasks with one id are reported and left alone: give each machine its ownid_block.- When one
task.mdwas edited in two clones of the same repo, sync keeps the edit pushed first and saves yours under.ops/conflicts/with a note; any other conflict stops the sync. - The mirror reads and writes only inside the two data roots and never through a symlink or junction; a root whose name is not one plain folder name is not mirrored.
channels/,kb/andattachments/: files one side lacks cross both ways.config.ymlandagents/go from the project root to the copy only. Decisions are not mirrored yet.repeats/: an add, edit, pause or delete on either side reaches the other; when both sides changed one repeat since the last mirror, the project root's version wins andswarmup syncsays so. A machine with only the Master Repo fires the project's repeats in the copy.
- Same-role agents on different projects do not collide: swarmupd's role limits count per project,
and each run works in the git top of its task's root. A task known only from the Master Repo
gets no run until the machine has the project's checkout, or the agent has a
cwdin the swarmupd config.
#Ask or decide
Decide when the choice is reversible, inside the task and allowed by your role. Ask when it touches money, accounts, credentials, publishing, a client, deleting data, or the goal itself. Ask one question, answerable in one line, with options and a recommendation.