Browse docsDevelopers

Inside a VM

The checkout, the jail an agent's commands run in, what persists between calls, the verbs that reach GitHub, where secrets go, and the expression that shapes it all.

updated 2026-10-06

An agent's rift_bash, rift_read and rift_write land in a jail rooted at your repository's checkout, on a VM built from your expression. This page is the reference for what's in there, what isn't, and why.

The checkout

The repository is at /home/dev/repo: a full clone, on a real branch, at the commit the VM was built from. git log, git diff, branches, tags — everything a clone has. It's writable; it's where the work happens.

The jail

Commands run in a jail, as an unprivileged user: the root and the VM's Nix store read-only, the checkout and the home writable, a fresh /tmp and /dev/shm, the network shared with the VM — a build has to fetch — and no secret anywhere in it. No repository secret and no GitHub token is ever mounted. What a command can read, an agent can read, and the jail is drawn so that set holds nothing worth stealing.

As on any NixOS system, /usr/bin/env and /bin/sh are there, so a script whose first line is #!/usr/bin/env node or #!/bin/sh runs as it is. Every other tool is on the PATH, from the Nix store.

The jail is the working directory of every call. Paths in rift_read and rift_write are resolved from its view.

What persists between calls

Every call is a fresh jail around the same checkout and the same home. The checkout and the home persist for the life of the VM, both on the VM's volume, so every cache in the home persists with no declaration: ~/.m2, ~/.cache, ~/.npm and the rest. /tmp and /dev/shm last for one call. A write anywhere else fails: a tool that writes under /usr, /var or /run needs a setting that points it at the home or /tmp. A VM made before the root was read-only keeps a root that a call can write, for that call only. rift_vm_new and rift_vm_ls say what persists on each VM: a VM made before the home persisted keeps its jail, in which only the checkout, ~/.cache and the declared caches persist.

The shell of a call reads /etc/profile, which the VM's system writes, and no file of the home: ~/.bashrc and ~/.profile do not run, so a broken one cannot stop the next call. A tool that needs its own shell file, such as nvm, works when the command reads that file. ~/.local/bin, ~/bin, ~/.npm-global/bin, ~/go/bin and ~/.cache/cargo/bin are on the path, after the verbs.

The verbs

git and gh on the jail's PATH are the real ones, unwrapped: everything local works at the jail's authority, and anything that needs GitHub fails as git and gh fail. What reaches GitHub is a verb: a named operation with typed arguments, declared in nix, run outside the jail in its own sandbox as the user who gave the agent its token, with that user's GitHub token. The platform declares push, force-push, fetch, clone, pr-create, pr-view, pr-checks and pr-merge; a repository's expression may add its own.

verbs lists the catalog; <verb> --help renders its declaration; a verb takes --name value for each declared argument and nothing else. The checkout's origin is a mirror of the repository on the VM, owned by the verbs' side: fetch refreshes it from GitHub and then runs the local git fetch origin for you; push sends a branch up from it. Neither side's git ever opens the other's repository. A plain git fetch reads the mirror, not GitHub, so when the mirror is more than a minute old it says how old, and which verb refreshes it.

push sends a new branch, or a fast-forward, and never overwrites. force-push overwrites GitHub's branch only if it is where the mirror last saw it, so a branch someone else moved since your last fetch is refused. clone --repo owner/name --dir path clones another repository the token reaches; run it again with the same directory to update that clone.

A verb that names a credential gets it per run, from the control plane, after its arguments are validated and before it runs — so the token exists on the VM in one process, for one run. Each such run is recorded on the VM first: the verb, its target, whether it writes, its arguments. Writes are refused for any repository but the VM's own, and a repository may not declare a verb that both writes and names the GitHub credential.

A credential is github — your GitHub token — or the name of one of the repository's secrets, which the run gets as the environment variable of that name: credential = "FLY_API_TOKEN" for a verb that runs flyctl. A name the repository has no secret by is refused: the VM has no credential by that name, and an admin sets it on the repository's Secrets page at https://fixedlabs.dev/app.

Without your GitHub account connected, a verb that needs it is refused, and the refusal says that you have not connected GitHub. Push from a VM shows how to connect it.

Secrets

The repository's secrets are for verbs only. The jail never sees one, and the VM keeps none: a secret is on the VM only in the one process of a verb run that names it, for that run.

The expression

What shapes the VM is one flake output in your repository, fixed-labs.rift.<system>, with no inputs of Rift's:

nix
fixed-labs.rift.x86_64-linux = {
  packages = with pkgs; [ nodejs_22 pnpm ripgrep ];
  env = { NODE_ENV = "development"; };
};
  • packages — on the login user's path, ahead of the base system's.
  • env — environment variables of every session.
  • caches — deprecated: the home persists, and holds every cache. An expression that declares caches still builds, with a warning.
  • verbs.<name> — a command run outside the jail with authority it lacks, as data: binary, typed args, an argv template the broker fills, the credential it needs (github or a secret's name), writes, and an optional after argv run in the jail. A repository may not declare a GitHub write, or a verb with the name of a platform verb (fetch, push, force-push, clone, pr-create, pr-view, pr-checks, pr-merge).

Nothing else is accepted: an unknown attribute fails the build with its name. There are no services, no user, no phases, no readiness gate, no ports and no sysctls in this version. A repository with no expression, or no flake, gets the base system and the checkout — the normal first state, and the one an agent writes the expression from.

Checking it. nix eval .#fixed-labs.rift.x86_64-linux on the VM catches the shape errors and touches nothing. Building the whole system from the working tree, in Nix's sandbox, is safe too. Activating a built system on a running VM is not offered: a VM runs the system built once, at warm time, from its commit, and a changed expression takes effect on a new VM. The path to a VM with new tools is the pull request, the merge, and the next claim — walked in Give your VMs their tools.

The VM's agent

A small program outside the jail owns the VM's side of everything: a heartbeat every 30 seconds, a long poll for commands that parks up to 25 seconds, the ready report once the system is built, and the broker the