pnpm test works, the agent proves its changes; on a machine where nothing runs, it guesses and you find out in CI.
Spend the twenty minutes. Every thread, task, and rebuilt machine in the project benefits, and a snapshot makes the result nearly free to boot.
Two ways to configure it
Open your project and go to Dev environment to edit machine size, per-repository scripts, startup entries, commands, and snapshots directly, or commit.capy/setup.json to the repository. Or ask Capy in any thread (“set up this project’s dev environment”) and it reads your manifests, writes the setup, and can verify it by running the scripts on its own machine. Agent edits and your edits land in the same configuration; every save creates a new version in one shared history.
Setup is stored per repository. The shape, if you want to picture it:
The two setup phases
A phase timeout is configurable from 60 to 3600 seconds; a startup entry’s from 1 to 3600.
The selection is per repository: a fresh clone runs
initialize; a restored checkout fetches and fast-forwards its branch and runs refresh only when HEAD actually moved. Every machine then launches the startup entries. A failed initialize is retried on the existing checkout until a run succeeds.
initialize
Everything needed to make a clean clone usable: install dependencies, system packages, global tools, generated artifacts. It also runs during snapshot builds, so whatever it produces gets baked into the snapshot. If dependency installation needs a private registry, authenticate explicitly at the top of the script; a package-managerpreinstall hook can’t authenticate the fetch that happens before it exists.
refresh
Bring a prepared machine up to date after its checkout moves. Keep it fast and safe to rerun: sync dependencies from the lockfile, regenerate derived code. Don’t duplicate an expensive bootstrap here unless a moved checkout genuinely needs it.Startup
Startup entries are the processes and preparation a snapshot can’t capture: databases, queues, dev servers, secrets written at boot. Each entry is a name and a command, and each runs on every boot in its own terminal session titled with its name. Open the session from the thread to read its logs, or ask Capy to. Entries launch in order. The next entry starts once the previous one is ready, so put infrastructure first and the app after it:- An entry that exits with status 0 is ready.
docker compose up -d --wait postgresis this kind. - An entry with a
portis ready once that port is listening, and keeps running in its session.pnpm devwith"port": 3000is this kind. The port is also published as a preview you can open from the thread. - An entry that exits with a non-zero status fails setup, the same as a failed phase.
- An entry that is neither exited nor listening when its timeout (300 s by default) elapses is recorded as not ready and left running; boot continues.
nohup and poll for it; declare its port instead. Don’t install, build, or migrate in a startup entry; those belong in initialize or in a command.
How scripts execute
Each script runs from its repository’s directory, in bash, under its timeout, as a user with passwordless sudo, with your environment variables loaded. A non-zero exit or a timeout fails the phase. A failed phase doesn’t kill the thread: the machine comes up, the thread gets a workspace warning naming the repository and phase, and the agent continues in a degraded workspace it will usually try to repair by hand. That self-repair costs you time and tokens on every machine; fix the script instead.Commands
Commands are named actions that run on demand:test, typecheck, build, a database reset, a dev server you don’t want on every boot. They never run automatically. You launch one from the machine’s menu in the thread pane, and Capy launches one by name; either way it runs in a terminal session titled with its name that you can open. Give a server command its port so its preview is published when it’s up. Keep commands executable and specific. Prose guidance like “always lint before committing” belongs in instructions, not here.
Launching from the thread
Open the + menu in a thread’s pane. Under each machine you’ll find its startup entries under Services, each with a dot for its state (up on its port, running, done, not ready), then a Commands row that opens its commands, then its terminals. With one machine those rows sit directly in the menu instead of under a Machines submenu. Click a startup entry to open its terminal session and read its logs, or to relaunch it when it has exited; pick a command to launch it in a new session and open the tab. An entry with a port also appears under Exposed apps on the Overview, where you can open it in the browser.What the agent can do itself
Capy reads and replaces the whole setup through its environment tools, which is what makes “ask Capy to update your dev environment” work. Two boundaries hold regardless: the agent sees environment variable names and whether each has a value, never the values, and no tool can set a variable’s value. Values are yours to manage in the app; see secrets. Every thread’s prompt lists the project’s startup entries and commands by name, so the agent runstest or restarts api by name instead of guessing the command; the launch runs in the same titled session you’d open from the menu. After a machine boots, the agent gets one note saying which startup entries came up, which are listening, and which never became ready.
PR reviews run on their own machine with a fresh checkout pinned to the exact PR head, credentials read-only and pushes disabled. Shared project variables apply there; personal and thread variables never do.
Keeping Setup in the repository
If you’d rather review setup changes as pull requests, commit.capy/setup.json to the repository. The file is that repository’s entry, without the repository name and branch:
.capy/setup.json in its checkout and opens a pull request like any other change.
A change on a branch does nothing until it merges. To try it first, ask Capy for a test build from that branch; the build reads the branch’s file and never activates. Remove the file and the last version it produced stays as an ordinary editable entry. Machine size and tool hooks stay in the app; the file never carries them, and a project can mix repositories with and without a file.
Snapshots
A snapshot is your project’s post-setup filesystem (repositories cloned, dependencies installed,initialize already run) baked into a bootable image. A fresh machine that restores from one skips the clone and setup work entirely and starts where your setup scripts finished.
Restoring from a snapshot takes roughly 1–2 seconds. Without one, every fresh machine pays your full setup time: clone plus however long initialize takes. Building a snapshot adds roughly 10–30 seconds on top of your setup scripts’ own runtime, paid once per build instead of once per machine. If your initialize takes five minutes, snapshots are the difference between an agent that starts working immediately and one that spends its first five minutes installing dependencies, on every thread, every task machine, every review.
Snapshots never gate a fresh boot. Missing, stale, disabled, or failed, the fallback is always the same: a fresh machine runs your full setup. A snapshot is a cache of your setup, never a second source of truth: your configuration stays in charge, and the snapshot just caches the result of running it.
Turning it on
Enable snapshots in the Snapshots section of your project’s Dev environment page, or ask Capy to do it. Enabling doesn’t start a build by itself; the hourly check picks it up on its next pass. Trigger a build immediately from the same page, or ask Capy to build one now. There’s also a test lane that runs the full build without activating the result, useful for validating setup changes while snapshots stay disabled.When snapshots rebuild
Every hour, Capy compares the frozen build input (the selected repositories at their current commits, the active setup version, the machine size) against what the current snapshot was built from. Anything moved, it rebuilds; nothing moved, it doesn’t. In practice:- New commits on a selected repository’s base branch trigger a rebuild within the hour.
- Changing setup scripts or machine size triggers a rebuild within the hour.
- Identical input never rebuilds, so a quiet project builds nothing.
- A failed build leaves the previous snapshot serving and is retried on the next hourly pass. Machines fall back to full setup only when there’s no current snapshot at all.
Choosing what goes in
A snapshot contains a selected subset of the project’s repositories, not necessarily all of them. Repositories outside the selection stay in the catalog and clone on demand when a thread needs them; they just don’t get the instant-boot treatment. Selection is validated against the machine’s disk budget using each repository’s size from GitHub, and an over-budget selection is refused up front with a per-repo breakdown rather than failing halfway through a build. Each repository row on the Dev environment page carries an include-in-snapshot switch. A newly added project repository isn’t snapshotted until you select it.Machine sizes and disk tiers
Disk size bakes into the image, so a snapshot serves one disk tier: 64 GB covers Small through Ultra, 128 GB is Hyper, 256 GB is Big Guy. Within a tier, one snapshot serves equal or smaller machines than it was built at; a thread requesting a bigger machine than the snapshot’s baked size falls back to fresh setup rather than silently booting undersized. If your setup size is on a bigger tier, the also snapshot smaller machine sizes option bakes the smaller tiers too, each built at that tier’s largest size.Secrets in builds
Builds receive shared project variables soinitialize can authenticate private registries, and Capy scrubs and verifies every platform-managed secret path before capture; a build that can’t prove the scrub fails. The one hole Capy can’t close is a setup script that writes a secret into the workspace; details in secrets.