How collab works
The architecture under the rig workspace verbs — tapd, the relay, bindings, and what happens when you edit a file.
This page is for the curious, the cautious, and the operators. For day-to-day
collaboration, you don't need it — the rig workspace verbs are the surface.
If you want to know what's actually happening when you save a file in a shared rig, read on.
The pieces
┌────────────┐ ┌──────────────┐
│ rig CLI │ shells out to │ tapd │
│ (you) │ ─────────────────► │ (daemon) │
└────────────┘ │ │
│ watches FS │
│ pushes/ │
│ pulls │
└──────┬───────┘
│ HTTPS
▼
┌────────────────────┐
│ tap relay │
│ (coordinator) │
│ │
│ - bindings │
│ - devices │
│ - invites │
│ - change log │
└────────────────────┘
▲ HTTPS
│
┌──────┴───────┐
│ tapd │
│ (teammate) │
└──────────────┘Three pieces:
rigCLI — what you type. For sync, it shells out totapd.tapd— the local sync daemon. One per workspace per machine. Watches the filesystem, ferries change events to/from the relay.- Tap relay — the coordinator. Hosted at
https://tap-relay.fly.devby default. Stores bindings, devices, invites, and a change-event log.
Identity: who's who
Rig identity lives at Rig Hub. rig login does a loopback OAuth flow
against the Hub's /auth/device page, signs you in via Clerk, and
writes a session JWT to ~/.config/rig/config.json as hub_token.
That JWT is what rig and tapd present to the tap relay for the
two account-facing actions: creating a binding (rig share)
and accepting an invite (rig join).
After binding/join, the per-machine capability token in
.rig/tap-binding.local.json takes over for ongoing sync. The relay
re-checks on every sync request that the bound user is still a
member of the binding, and intersects their requested op with their
current role — so removing or demoting a member takes effect on
their next request.
Bindings, members, devices, invites
A binding is the unit of collaboration. It has:
- An ID (
bnd_…). - An owner (a member with role=
owner). - A set of members (
binding_membersrows, each with a role:owner/editor/viewer). - A set of devices — one per (member, machine) pair, plus any pure-capability devices.
- A set of outstanding invites.
- A change-event log.
A member is a Rig Hub account that's part of a binding, with a role.
Inspect with rig who, change with rig role, remove with rig unshare.
Owners manage members and invites; editors read/write/subscribe; viewers
read/subscribe only.
A device is a per-machine credential within a binding. Created on
rig share (for the owner) or rig join (for invited
collaborators). Each device has its own capability token stored in
.rig/tap-binding.local.json (mode 0600, never committed, never synced).
One member can have multiple devices.
An invite is a relay-issued credential that grants the holder permission to claim a new device on the binding. Invites come in two flavors:
- Member invites (
rig share <email> --role …): the accepter joinsbinding_membersand gets a member-tied capability token. Manageable thereafter viarig who/rig role/rig unshare. - Pure-capability invites (
rig share --ops …, no role): the accepter gets a device-only token with no member row. To kick them, revoke the invite withrig unshare <inv_…>— the cascade kills their token.
Invites are optionally scoped (--ops, --path), email-bound,
expiry-bound (--expires), and have a --max-uses counter.
What gets synced
tapd watches files that match [package].include
and aren't in [package].exclude,
.rigignore, or
[local].dirs.
It does not sync:
- Anything in the default-excluded list (
.env,*.key,node_modules/, etc). .rig/tap-binding.local.json(per-device)..rig/instance.toml(per-install).- Files inside
[local].dirs.
What happens when you edit a file
- You save
data/templates/email.md. tapd(running locally) sees the FS change event.- It computes a delta + content hash.
- It POSTs to the relay:
POST /v1/bindings/<id>/changeswith the event. - The relay appends the event to the binding's change log and broadcasts to other devices.
- Other devices'
tapdinstances pull the event, verify the hash, and write the file locally.
In practice this is sub-second on a good network.
The change-event log
Every write/edit/delete is recorded. This is what powers
rig history
and rig restore.
Each event has:
cursor(monotonic sequence per binding)pathactor(device + user)kind(write/edit/delete)hash(content hash for verification)timestamp
The log is append-only. It's the source of truth for "what happened on this binding."
Endpoints used by the workspace verbs:
GET /v1/bindings/<id>/changes— list events (path-filtered for history).GET /v1/bindings/<id>/blobs/<hash>— fetch a specific blob for restore.
What the relay sees
The relay sees: paths, content hashes, file bytes (stored as content-addressed blobs in S3/R2-compatible object storage), who made each change, when, and the full change-event log. There is no end-to-end encryption in v1 — operators of the relay can in principle read your files.
If that's not acceptable for your data, run your own relay. The relay
is the @tap/relay package; it's
deployable via the Dockerfile + fly.toml at the repo root, or any
Node 20+ host with Postgres + S3-compatible storage attached.
Revoking access
Three surfaces, depending on intent:
- Remove a member:
rig unshare <email>(owner-only). Deletes the member row and cascade-revokes their capability tokens for this binding in one transaction. Their next sync attempt fails with 401. - Demote a member:
rig role <email> viewer. Token stays valid; ops are intersected with the new role on each sync request, so writes start failing immediately. No token rotation required on the member's side. - Revoke an invite:
rig unshare <inv_…>. Stops future joins via that invite and cascade-revokes any tokens already minted through it (useful for pure-capability invites where there's no member row to remove).
A revoked or removed user's local file copy remains intact on disk — sync just stops.
Why this name salad? (rig, tap, tapd)
- rig is the workspace product. CLI, manifest, artifacts, Hub.
- tap is the sync system. It exists because rig needed a way to make workspaces collaborative without coupling that into the rig CLI. The relay is part of tap.
- tapd is the local daemon implementation. You install it (
@rigxyz/tapd); you don't drive it directly. Therigworkspace verbs shell out.
For users: you interact with rig share / rig who / rig status. The rest is plumbing.