Skip to main content
The environment is your project’s machine recipe: which setup scripts prepare a fresh machine, which processes start on every boot, which commands you and the agent can launch, and how big the VM is. It’s the highest-leverage configuration in Capy, because an agent that can’t run your tests can’t verify its work. On a machine where 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:
Scripts and lists are optional; leave one empty when it has no work to do.

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-manager preinstall 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 postgres is this kind.
  • An entry with a port is ready once that port is listening, and keeps running in its session. pnpm dev with "port": 3000 is 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.
Don’t background a server with 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 runs test 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 reads the file from GitHub on the branch your setup names for that repository, never from a machine’s checkout. A push to that branch that changes the file saves a new setup version, attributed to the repository and the commit, and the version history, rollback, and snapshot rebuilds treat it like any other save. In the app the repository shows as owned by its file: its scripts and entries are read-only there, and the agent’s setup tool refuses to change them and points at the file instead, so the agent edits .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.
A build runs on a dedicated build machine at your configured size, follows your setup exactly, and activates on success. Snapshot build machines aren’t billed to your organization.

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 so initialize 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.

When a build fails

Open the build from the Dev environment page’s build history: every build records per-step logs, and each setup phase’s output survives with the failing line intact. Or just ask Capy why the last snapshot build failed; it reads the same build history and logs, and can fix the setup and rebuild in one go. The failure costs you speed, not availability: machines keep booting fresh with full setup until a build succeeds.