The .rivets directory
Two different things share the name .rivets. One is a folder you commit to your repository to tell Rivets how to prepare and run a workspace. The other is a worker’s private state directory in its home folder, which you should not edit by hand.
<repo>/.rivets/ |
~/.rivets/ |
|
|---|---|---|
| Belongs to | The repository | The machine running a worker |
| Committed | Yes | Never |
| Written by | People | The worker |
| Contains | settings.toml |
Configuration, clones, worktrees, workspace files, logs |
Why settings.toml exists
A worker clones each repository once and cuts every task’s worktree from that clone. A fresh worktree only has tracked files: no node_modules, no build output, no .env. Only the repository knows how to fix that. .rivets/settings.toml says so once, for every worker.
What the repository cannot hold is each machine’s secrets, such as a .env with API keys or an auth.json with a package-registry login. Those are workspace files: kept on each worker and copied into every new worktree before setup runs. The repository can declare which ones it needs.
Example
[scripts]
# Runs on every fresh worktree, alongside the agent's first turn.
setup = """
bun install --frozen-lockfile
bun run db:migrate
"""
# Runs when the task is archived. Setup runs again if it is restored.
archive = "docker compose down"
# "nonconcurrent" (the default) stops the other run scripts before starting one.
run_mode = "nonconcurrent"
[scripts.run.dev]
command = "bun run dev -- --port $RIVETS_PORT"
default = true # what the Run button starts on a single press
icon = "play"
preview = true # shown in the task's Preview tab while it runs
[scripts.run.test]
command = "bun test"
icon = "test-tube"
[scripts.run.seed]
command = "bun run db:seed"
hide = true # runnable by id, kept out of the Run menu
# Files each machine must provide. Never the files themselves.
[[workspace_files]]
path = ".env"
description = "Local API keys. Copy .env.example and fill it in."
[[workspace_files]]
path = "auth.json"
The three kinds of script
| Kind | When it runs | Who starts it |
|---|---|---|
setup |
Once per worktree, right after it is created, again after the task is restored, or from the Run menu | The worker, or a person |
run |
On demand | A person, from the Run control |
archive |
When the task is archived | The worker |
setup
setup runs once per worktree, not once per message. Follow-up messages in the same task reuse the worktree and do not run it again.
- It runs alongside the agent’s first turn. The agent is told that setup is still running and to wait for it before installing or testing. Set
async = falseto make the agent wait until setup finishes instead. - A failed setup does not fail the task. The agent still starts. The result and output show in the script’s terminal tab.
- Editing the script re-runs it. Rivets records the command that last succeeded in
.context/setup-state.jsoninside the worktree. A changed command, or a previous failure, runs again on the next turn. - You can run it again by hand. Setup appears in the Run menu, and its tab offers Run again.
- Restoring a task runs it again. Archiving clears the setup marker, because the
archivescript may have torn down what setup built. Restoring the task runs setup straight away if the worker is online, or on the task’s next message if not. Write setup so it is safe to run on a worktree it has set up before. - Workspace files come first. Before every setup run, the worker copies in this machine’s workspace files. If the repository declares a file the worktree still lacks, setup does not start. Its tab shows the reason, for example: “Missing workspace file auth.json. Add it in Repository settings → Workspace files, then run setup again.”
run
Named scripts shown behind the Run control in each client’s terminal strip. A single press starts the script marked default = true, or the first run script if none is. The others are in the menu next to it.
Run scripts have no timeout: servers and watchers are meant to keep running until you stop them. With run_mode = "nonconcurrent" (the default), starting a script first stops the others and waits for them to exit, so two dev servers do not race for the same port. Set run_mode = "concurrent" to allow several at once.
archive
Teardown for anything that outlives the directory, such as containers, tunnels, or cloud resources. It runs when the task is archived, by hand or because its pull request merged:
- The worker stops the task’s run scripts, closes its terminals and idle agent processes, and withdraws its previews. If an agent is still working in the task, the teardown waits for it to finish.
- It runs
archive. - It leaves the checkout on disk, so the task can still be restored for 7 days.
A worker that is offline when the task is archived tears it down when it reconnects. If it never comes back before the worktree is deleted, archive runs then, just before deletion. Otherwise it is not run a second time.
archive is stopped after 2 minutes, whatever its timeout_seconds says, so a stuck teardown never keeps the worker from reclaiming disk. Because archiving, restoring, and archiving again runs it twice, make it safe to repeat. docker compose down is.
It cannot be run by hand, because that would tear down a workspace someone may still be using.
Field reference
setup and archive each take a string (the command) or a table:
| Field | Default | Meaning |
|---|---|---|
command |
none | Shell command, run through the worker user’s login shell. |
cwd |
Worktree root | Relative path inside the worktree. |
async |
true |
For setup: false makes the agent wait until the script finishes. Ignored for archive. |
timeout_seconds |
900 |
Time limit in seconds. Maximum 3600. archive is always stopped after 120 seconds. |
Each [scripts.run.<id>] table takes:
| Field | Default | Meaning |
|---|---|---|
command |
none | Shell command. |
name |
The id | Label on the tab and in the menu. |
cwd |
Worktree root | Relative path inside the worktree. |
icon |
play |
One of the icons below. |
default |
false |
Started by a single press of Run. |
hide |
false |
Kept out of the menu, but still runnable by id. |
preview |
None | What the script serves, shown in the task’s Preview tab while it runs. See preview. |
Script ids use lowercase letters, digits, -, and _. A repository can define up to 24 run scripts, and the file can be up to 64 KB.
Icons: play, hammer, test-tube, circle-check, monitor, database, terminal, rocket, book, wrench, globe, package. An unrecognized icon falls back to play.
setup and archive are reserved: a [scripts.run.setup] entry is rejected and reported. If several run scripts set default = true, the first one wins.
preview
A run script that starts a web server can say so. Every client then shows that server in the task’s Preview tab while the script runs.
[scripts.run.dev]
command = "npm run dev -- --port $RIVETS_PORT"
preview = true
preview = true is one preview on the worktree’s own port, RIVETS_PORT. A table, or a list of up to four tables, says more:
| Field | Default | Meaning |
|---|---|---|
name |
The script’s name | Label in the Preview tab, up to 60 characters. |
port_offset |
0 |
0 to 9. The preview is on RIVETS_PORT + port_offset. |
port |
None | A fixed port instead, for a server that cannot be told where to listen. Two tasks then cannot preview it side by side. Cannot be combined with port_offset. |
path |
/ |
Where the preview opens. Request paths are never rewritten. |
[scripts.run.dev]
command = "bun run dev" # web on $RIVETS_PORT, Storybook on $RIVETS_PORT + 1
preview = [
{ name = "Web" },
{ name = "Storybook", port_offset = 1 },
]
A preview is offered only while its script runs and its port accepts connections on loopback (127.0.0.1, or ::1). It is withdrawn when the script stops, or when the port fails two checks in a row.
Declaring workspace files
[[workspace_files]] names a file every workspace of this repository needs from the machine it runs on, such as a credential or local config that is gitignored and so never in the clone. The declaration is checked in so the whole team sees it. It never contains the file.
| Field | Default | Meaning |
|---|---|---|
path |
none | Where the file goes, relative to the worktree root. |
description |
none | What it is and where to get it, up to 200 characters. |
A plain list also works: workspace_files = [".env", "auth.json"]. A repository can declare up to 32 files. Paths are relative, use /, and cannot be under .git or .context. A bad entry is dropped and reported, and the rest still apply.
A declaration does two things:
- Repository settings shows each machine that is missing a declared file, with its description and a way to add it.
- Setup waits for it. While a declared file is missing, setup does not start, and its tab says which file to add.
Rivets reads the declaration from each worker’s copy of the default branch, as of its last fetch.
Environment
Scripts get the environment an interactive shell in the worktree would get, including the git identity and credential helper, plus:
| Variable | Meaning |
|---|---|
RIVETS_WORKTREE_PATH |
The worktree the script runs in. |
RIVETS_REPO_PATH |
The parent clone, for caches the worktree does not have. |
RIVETS_WORKSPACE_NAME |
The worktree id, for naming per-workspace resources. |
RIVETS_SCRIPT_ID |
The script being run. |
RIVETS_WORKSPACE_FILES |
The workspace files Rivets copied into this worktree, one path per line. Empty when there are none. |
RIVETS_PORT |
The first of ten loopback ports this worktree has to itself, RIVETS_PORT to RIVETS_PORT + 9. Also set in the task’s terminals and for its agent. |
RIVETS_CLI_DIR |
The directory holding this worker’s own rivets command, which is put first on PATH. Also set in the task’s terminals and for its agent. |
RIVETS_PORT is what lets two tasks run the same dev server at once. Each worktree’s block is between 20000 and 32759, is allocated the first time anything runs in it, and stays with it across worker restarts until the worktree is deleted. Point the dev server at it, for example --port $RIVETS_PORT, and declare a preview.
Output and stopping
Every script runs in its own read-only terminal tab on web, Mac, and iOS, showing its state. Right-click the tab for Run again or Stop. Closing the tab only hides it for you; the script keeps running. Anyone with access to the task can read it, so a reviewer can see why setup failed. Script terminals do not count toward the per-person terminal limit.
Stopping sends SIGTERM, then SIGKILL 5 seconds later if the process is still running. A script is only reported as stopped once its process has exited.
Errors in the file
A malformed settings.toml never fails a task. Rivets applies the valid parts and skips the rest, and every client lists the problems next to the scripts.
Workspace files
Workspace files are files a worker copies into every new worktree of a repository: the gitignored .env, auth.json, or certificate that a setup script expects and a fresh clone never has. They live only on the worker, under ~/.rivets/workspace-files/<repoId>/, readable only by the worker’s user.
Manage them from the repository’s settings on web, Mac, iPhone, and iPad. Open the settings from the project row in the sidebar (the gear that appears on hover, or Settings… in the row’s menu), then go to Workspace files. There is one row for each worker you can use for that repository:
- Add file… uploads files. Set each one’s Path in workspace, then choose Add file. On Mac, the file picker shows hidden files, since most workspace files are dotfiles.
- Remove deletes a file from that worker.
- Copy from (or Copy from… when there are several other workers) copies every file from another worker, replacing any with the same path after asking.
- A declared file that worker lacks shows as Missing, with Add… to supply it at the declared path.
How they are applied:
- When. To each new worktree, and again before every
setuprun, so a file you add after setup failed for want of it is there for the next run. - Never over existing files. A file lands at its own path only if nothing is there. If the repository tracks the same path, its copy wins. Symlinks are never followed.
- Kept out of git. Each copied path is added to the clone’s local excludes (
.git/info/exclude), so an agent never commits it and it never shows as a change. - Machine-local. The Rivets server passes the files between your client and the worker, or between two workers for Copy from, but never stores them.
- Shared workers. The files belong to the machine, not to a person. A teammate’s task on a worker you share gets them too.
Limits: 32 files and 2 MB per repository on each worker, and 512 KB per file. A worker on an older version shows Update Rivets on this machine to use workspace files. An offline worker’s files show when it reconnects.
Turning scripts off on a worker
A worker operator can stop a worker from running any repository’s scripts:
rivets connect --no-scripts
This saves "projectScripts": false in ~/.rivets/config.json. The worker stops advertising scripts, and clients hide the Run control for its tasks.
The worker’s home: ~/.rivets
Each worker keeps its state in ~/.rivets/, created with mode 0700. Set RIVETS_HOME to use a different location. The Mac app gives each of its bundled workers a separate home under ~/.rivets/desktop/<workerId>/.
~/.rivets/
├── config.json # backend URL, worker name, and operator options
├── auth/ # worker signing-key metadata
├── bin/rivets-cred-helper # git credential helper the worker installs
├── bin/rivets # this worker's CLI, on PATH in its terminals, scripts, and agents
├── run/ # temporary: credential socket, git identity files
├── repos/<repoId>/ # one clone per repository
├── workspace-files/<repoId>/ # files copied into every new worktree of a repository
├── worktrees/<worktreeId>/ # one working tree per task
├── attachments/ # prompt attachments downloaded for a job
├── terminal-history/ # encrypted terminal scrollback
├── logs/ # redacted service logs
├── service/ # Windows only: the scheduled-task definition
├── worker-health.json # what `rivets status` reads
├── outbound-spool.json # events not yet acknowledged by the server
├── transcripts.sqlite3 # cache of recent chat events for apps on this machine
├── worktree-ports.json # each worktree's RIVETS_PORT block
├── preview-services.json # previews registered by hand (advanced)
└── host-id # identifies this machine
config.json
The only file here you might edit, and rivets connect flags are the better way to change it. It is written with mode 0600.
{
"url": "https://app.rivets.dev",
"name": "mac-mini",
"tokenCredential": "worker-bearer-...",
"maxConcurrentJobs": 4,
"autoUpdate": false,
"projectScripts": false
}
tokenCredential is the name of an entry in the OS credential store, not the token itself. Options are written only when they differ from the default:
| Key | Default | Set by |
|---|---|---|
maxConcurrentJobs |
Unlimited | --max-workers |
directTransport |
Off | --direct |
loopbackTransport |
On | --no-loopback |
autoUpdate |
On | --no-auto-update |
projectScripts |
On | --no-scripts |
Worktrees and the .context folder
Each worktree contains a .context/ folder for prompt attachments, the setup marker, the list of workspace files Rivets copied in, and previews.json, the previews registered in that task with rivets preview. Rivets adds it to the repository’s local excludes (.git/info/exclude), never to your tracked .gitignore.
Cleaning up
repos/ and worktrees/ are the only folders that grow without bound. The worker deletes archived worktrees on a six-hourly sweep, 7 days after they were archived. Their scripts were already stopped, and their archive script run, when they were archived.
Deleting ~/.rivets/ is safe when no worker is running, with three consequences: the worker re-clones every repository the next time it needs one, any worktree with uncommitted work is lost, and the worker’s workspace files are gone. Prefer this order:
- Archive tasks you care about.
- Run
rivets uninstallto stop the service. - Delete the folder.