Teams
Everything so far runs on one machine, free, with no account. Teams is the layer for a second person: a self-hosted team daemon (swarm-teamd) that every machine's local daemon forwards to — ledger events, spend rollups and claims, never transcript text unless a machine opts in. Your laptop keeps working offline exactly as before; the team daemon adds one view across all of them.
The team daemon lives in packages/team under the FSL-1.1-ALv2 source-available license — free for internal use, education, research and professional services, converting to Apache-2.0 two years after each release. Everything else in Swarm is and stays Apache-2.0. The line is simple: one machine free, a second person is the product.
Host a team from the dashboard
Open Team in the dashboard and press Host a team. Swarm mints a shared secret, writes
~/.swarm/team.toml, starts swarm-teamd on this machine, points this machine at it, registers,
and hands you an invite link. A teammate opens their own Team panel, pastes the link into
Join a team, and that is the whole setup — no terminal, no exported secrets on either side.
The team daemon is source-available (FSL-1.1-ALv2) and ships separately from the Apache-2.0
app, so it is never bundled. When it is not on the machine yet, the Team panel says so, links the
license, and offers Accept the license and download: Swarm fetches the swarm-teamd binary
for your platform from the GitHub release that matches its own version, checks it against the
release's swarm-teamd-SHA256SUMS, and keeps it in ~/.swarm/bin. A clone or a swarm-teamd on
your PATH is used as it is. The panel names the log when a daemon cannot start.
Leaving is on the same panel: Leave the team stops forwarding (your ledger, claims and worktrees are untouched), and Leave and stop hosting also stops the daemon this machine started — by pid, never by pattern.
Running the team daemon yourself
Three ways to get it, all the same version as the app:
npm i -g @ra3orblade/swarm-team # needs Bun; puts swarm-teamd on PATH
docker run -d -p 7878:7878 -v swarm-team:/data -e SWARM_TEAM_TOKEN=… ghcr.io/ra3orblade/swarm-teamd
# or the swarm-teamd-<os>-<arch> binary from the GitHub release (the one the app downloads)
The image keeps team.toml and team.db on its /data volume; docker run -it -v swarm-team:/data ghcr.io/ra3orblade/swarm-teamd setup writes the settings once.
swarm-teamd setup # interactive: mode, port, secret → ~/.swarm/team.toml
swarm-teamd # listens on 0.0.0.0:7878
bun packages/team/src/bin.ts # the same thing from a clone
SWARM_TEAM_DB=/srv/swarm/team.db … # state (default ~/.swarm/team.db, SQLite, one file)
Settings come from ~/.swarm/team.toml (name, host, port, mode, token, issuer,
client_id, db) with the environment on top, so every deployment that predates the file keeps
behaving exactly as it did.
Put TLS in front (any reverse proxy) for anything beyond a lab. Auth has three modes, decided by environment:
| Mode | Set | Who can talk |
|---|---|---|
oidc |
SWARM_TEAM_OIDC_ISSUER + SWARM_TEAM_OIDC_CLIENT_ID |
humans log in with the OIDC device-code flow; machines get their own tokens |
token |
SWARM_TEAM_TOKEN |
one shared secret — small teams, labs |
open |
neither | no auth. Never run this beyond a lab |
In oidc mode the team daemon is the OAuth client: it starts the device flow, polls the issuer, verifies the ID token against the issuer's JWKS and issues its own opaque token — your laptop never holds an OIDC credential. The first user to log in becomes admin; everyone after is a viewer until an admin promotes them (roles: viewer / developer / admin).
Connecting a machine
The dashboard's Team panel is the short way (above). The equivalent by hand:
swarm install --config-url https://swarm.example.internal # writes [team] url into ~/.swarm/config.toml
swarm login # device-code login + registers this machine
swarm login prints a verification URL and code, waits for you to approve it, stores your token in ~/.swarm/team-token, registers the machine (its token is bound to you), and pins the team's policy signing key. From then on the local daemon forwards in the background — batched, at-least-once, never on the hook path — and swarm doctor shows the forwarding lag. Offline is fine: everything queues locally and drains when the daemon is reachable again.
What is forwarded is [team] forward in ~/.swarm/config.toml: ["ledger", "cost"] by default — audit events (claims, gates, incidents, PRs, permission answers, with who did it) and daily spend rollups. "transcripts" (session titles + last message) is strictly opt-in and still passes your redaction rules.
What a team gets
- The team dashboard at the team daemon's URL: machines (live/quiet), cluster claims, spend — today, by project, by user, by machine, by day — and the forwarded activity feed. Sign in by pasting your
~/.swarm/team-tokenonce. - Cluster-wide claims: a claim taken on one laptop is registered upstream; a second machine claiming the same task is refused with the holder's name, and if the cluster says someone else holds it, the local claim is revoked — the worktree is never touched. Offline machines degrade to local-only claims.
- Org policy, signed: an admin
POSTs a policy TOML to/t1/policy; every machine fetches it, verifies the ed25519 signature against the key pinned at login, and installs it as the org layer — locked rules included. A tampered policy is reported and never installed. - Budgets + chargeback: org / user / project ceilings (daily + monthly) with the same warn / ask / stop semantics as the local
[budget]; monthly export by user, machine, model or task (GET /t1/spend/export?month=2026-08&by=task&format=csv— task ids are your ticket ids when the task source is GitHub Issues or Linear). /t1/metricsin Prometheus format for your monitoring stack (send the bearer token from the scrape config).- Settings, for admins, at the bottom of the team dashboard: change a member's role or remove them, revoke a machine, and edit the org policy — Sign and publish signs it with the team's key and every machine picks it up. With one shared secret (the default when you host from the app), whoever holds the secret is the admin.
- Found on the network: a team daemon announces itself on the local network, so a teammate's Join lists it with one click to fill in the address. A secret never travels that way — a token team still needs the invite link.
Operations
swarm backup # consistent snapshot of ~/.swarm (VACUUM INTO — zero downtime)
swarm restore <dir> # put it back, with the daemon stopped
swarm doctor --migrate # apply pending database migrations, report the schema version
swarm audit export --since 30d --format csv # the local audit log, with actors
Version pinning across a fleet is installing a pinned package: bun add -g @ra3orblade/swarm@x.y.z.
Source: docs/guide/11-teams.md · v0.16.0.