Skip to content

Swarm v0.4.0

Choose a tag to compare

@github-actions github-actions released this 22 Aug 12:23

The enforcement release: rules that watch file writes, not just Bash; a backlog Swarm can read; servers Swarm starts and stops by pid; and an Incidents feed you can clear.

Added

  • Rules on file writes — two new rules evaluated on Write / Edit / MultiEdit / NotebookEdit paths (and Bash working directories), not only Bash commands. no_foreign_worktree (default ask) stops a session from editing inside a worktree another claim holds — never touch a worktree you don't hold is now a hook decision, with holding inferred from the session's cwd. claim_required_to_write (opt-in) makes a repo's shared checkout read-only without a claim: claim a task, get a worktree, write there. Both per repo as ask | deny | off.
  • Incidents view — the denied-action feed as its own tab: Open / All, per-rule counts, reason and session per row, Ack and Ack all; the open count sits in the nav. GET /v1/incidents?open=1&project=, POST /v1/incidents/:seq/ack, POST /v1/incidents/ack, /v1/state.openIncidents. The Board keeps a short open-only section.
  • Task source.swarm.toml [tasks] source = "docs/plan.md" points at a markdown file whose ID | Task | Depends | Status tables are the backlog (✅ / 🟡 / ⚪, dependencies by task id or milestone prefix). The Board gets a Tasks section (Ready / Open / All, Claim per row), the CLI swarm tasks [--ready], and agents swarm_next_task — the first unclaimed task whose dependencies are done. Swarm's own roadmap is its task source. Markdown only (OQ-5 decided).
  • swarm serve / swarm procswarm serve start --name web -- npm run dev allocates a free port (ledger + bind probe), runs the command detached with PORT set and logs under ~/.swarm/logs/<project>/, registers pid + start time, and acquires the singleton — so a second web fails closed and the port is protected for every other session with no config. serve ls | stop [name|pid], proc start | ls | stop for workers without a port. Stop signals registry pids only, verified by start time; nothing is ever killed by pattern. POST /v1/ports/allocate, GET/POST/DELETE /v1/processes; Processes section on the Board with Stop.
  • Star nudge — once a month at most, never on first open, the dashboard asks for a GitHub star. Later snoozes 30 days, Don't ask again is final; localStorage only.
  • Sidebar drag-and-drop — pinned projects reorder by dragging; the order persists on the daemon (PUT /v1/projects/order, Project.order).
  • Desktop app menu — a real application menu (Swarm / Edit / View / Window): ⌘C/⌘V work, View › Zoom In / Zoom Out / Actual Size (⌘+ ⌘− ⌘0) scale the dashboard (persisted), plus Reload and Full Screen.

Fixed

  • Shared-tree rules no longer lose sight of a session mid-turn. shared_tree / destructive_git keyed on a 2-minute last-seen window fed only by hooks, so a neighbour three minutes into a long turn became invisible — and its uncommitted work unguarded. Transcript growth now counts as activity (the tailer bumps last_seen_at), and the liveness window is the daemon's 10-minute idle threshold (LIVE_WINDOW_MS). A false positive costs one confirmation; a false negative cost someone's work.
  • destructive_git also matches git stash drop, git stash clear and git branch -D.
  • Dashboard type scale is one step larger across the board (base 13 → 14 px; the smallest labels 10 → 11 px) — it had drifted too small, especially in the desktop app.
  • The PRs tab icon (and the branch/commit glyphs) were near-invisible at 15 px; they use the pixelarticons sharp variants now.
  • The daemon dot stayed red for up to 15 s after load on a healthy connection (the SSE stream sent nothing until its first heartbeat); the stream now flushes immediately.
  • The nav flashed "Fleet" before the restored tab was applied; session-detail event kinds (userpromptsubmit) no longer overflow into the message column; "Unpinned · seen, not pinned" keeps its spacing.

Changed — docs

  • README and site now say what the code does: Codex CLI and Grok sessions are tailed alongside Claude Code; the requirements and architecture diagram list all three.
  • Rules are described as guardrails against accidents, not a sandbox — the guide has a new "What rules are — and aren't" section spelling out that a denied Bash command can be routed around (script, heredoc, direct file edit), and that worktree isolation via claims is the real fix. The site's feature cards lead with claims, rules and resources instead of Fleet and Spend.

Changed — performance

  • Daemon never spawns git on a request. Worktree status (git worktree list + status/rev-list per worktree, ~0.8 s across a fleet) moves to a 15 s background refresh with async Bun.spawn; /v1/state serves the cache (612 ms → ~15 ms). Claim/release invalidate it.
  • Hook round-trips are two indexed statements, not two git rev-parse spawns plus a transcript-directory scan: cwd → project is cached 60 s, the inline transcript tail is debounced to once per 2 s per session (the 5 s tailer covers steady state), and subagent directories are re-listed only when their mtime moves.
  • Events store ~2 KB, not ~10 KB. tool_input is clipped at 2 KB and tool_response at 4 KB in payload ({truncated, bytes, preview}), and the tool I/O is no longer duplicated in raw. Existing databases are rewritten once on boot and VACUUMed (96 MB → 27 MB here). Retention: events older than 30 days are pruned daily (incidents kept), raw is cleared after 7 days.
  • Wire shape. SSE frames, GET /v1/events replays and GET /v1/sessions/:id/events carry seq/ts/type/projectId/sessionId/payload{hook,summary,…} only — no raw, no tool I/O (a 5.5 MB session fetch is now ~150 KB). GET /v1/events/:seq returns one stored event in full; ?full=1 on the SSE replay does the same. ?since=0 replays the last 200 events, not the table.
  • Incremental session viewGET /v1/sessions/:id/events?after=<seq>&afterTs=<iso>; the dashboard appends instead of re-fetching 500 events + 500 turns on every hook.
  • Dashboard render loop — one requestAnimationFrame scheduler, snapshot seq short-circuit, paused while the tab is hidden, exponential SSE reconnect backoff; session log merges two sorted lists and caches rendered rows; data-grid memoises persisted layout and uses one Intl.Collator; charts memoise the turn strip.
  • SQLite: indexes on events(type, seq) and turns(ts), mmap_size 256 MB, cached prepared statements (db.query), sessions/spend/incidents memoised per write generation; /v1/spend is its own endpoint.
  • Background tick: Codex/Grok discovery every 15 s when idle; Grok summary.json re-read only on mtime change.

Install: bunx @ra3orblade/swarm setup · Docs: https://getswarm.vercel.app/docs/ · Changelog: https://getswarm.vercel.app/changelog