Browse docsDevelopers

Give your boxes their tools

Write the fixed-labs.rift expression into your repository from inside a box, open the pull request, and claim the box that has your toolchain in it.

updated 2026-10-06

In Getting started with Rift your agent claimed a box built from your repository — the base system, with your checkout in it. This time you'll give that box your project's tools, and end with a fresh box where they're simply there.

The tools are declared in one place: a flake output in your repository named fixed-labs.rift. It says which packages go on the path, which environment variables every session gets, and which directories persist between calls. You won't write it from scratch: your agent reads Rift's own guide to the expression, writes it from a box, and opens the pull request — because a box never changes after it is built, and the way to change the next one is to change the repository.

Before you start you'll need everything from the first tutorial — Rift connected to your agent, a branch watched, a box you can claim — and a repository you can open a pull request against. About twenty minutes.

1. Claim a box, and hand the agent the guide

Open a task and claim a box on the branch you watch, exactly as before. Then tell the agent what you want: "add a Rift expression to this repository for its toolchain". Rift's server carries a resource, rift://nix-guide, that the agent is told to read before it touches the expression — it has the contract, a complete example, and the lifecycle from an edit to a merged pull request. A good agent reads it unprompted; it does no harm to ask.

2. What the agent writes

The expression is a flake output fixed-labs.rift.<system> in your repository's own flake.nix, with no inputs of Rift's. It's a plain attribute set with three things in it:

nix
{
  outputs = { self, nixpkgs, ... }: {
    fixed-labs.rift.x86_64-linux =
      let pkgs = nixpkgs.legacyPackages.x86_64-linux; in {
        packages = with pkgs; [ nodejs_22 pnpm ripgrep ];
        env = { NODE_ENV = "development"; };
      };
  };
}
  • packages go on the login user's path, ahead of the base system's.
  • env is the environment of every session.
  • The home persists for the life of the VM, so every tool's cache in it does: there is nothing to declare.

Nothing else is accepted — an unknown attribute fails the build with its name, so the expression can't quietly mean nothing. If your repository has no flake.nix yet, the agent creates one around exactly this.

Review it like a PR. The package list is the thing to read: does it match what your project needs to build and run? A missing tool is one more entry later, but this is the moment to catch the obvious gaps.

3. Check it, on the box

Evaluating the expression is safe on the box — it runs in Nix's sandbox and touches nothing — so the agent checks the shape before it pushes:

sh
cd /home/dev/repo
git add flake.nix
nix eval .#fixed-labs.rift.x86_64-linux

Success prints the attribute set back. An error names the problem: most often a package attribute that doesn't exist, or an attribute the contract doesn't know. Fix and re-run until it evaluates.

In a git repository, Nix only sees tracked files.

If nix eval claims there is no flake.nix, it hasn't been git added yet — Nix ignores untracked files entirely. Staged is enough.

4. Push it, and open the pull request

git on the box is ordinary git, and everything local works: the branch, the commit. What needs GitHub is a verb — push, pr-create — that runs outside the jail as you, with your GitHub token, so the commit is yours and the pull request is yours. verbs lists them; each answers --help.

sh
git checkout -b rift-expression
git commit -am "Add the Rift expression"
push --branch rift-expression
pr-create --title "Add the Rift expression" --body-file pr.md
The first push asks you for a device code.

A box builds and runs without your GitHub account connected; a push is the first thing that needs it. If push is refused because you haven't connected GitHub, the agent calls rift_github_login and shows you a URL and a code. Open the URL, enter the code, and tell the agent — it calls the tool again to check, then runs the verb again. The credential is fetched for each run, so the same box pushes the moment you're connected.

5. Merge, and let the pool rewarm

Merge the pull request. The push to your watched branch moves its head, and Rift's webhook hears it: the warm box at the old head is drained, and a new one warms at the new head — cloning, building the system your expression describes, and parking. rift_whoami shows the head the pool is at; the first build with real packages in it takes a few minutes.

6. Claim the box that has your tools

The box you've been working in is still the old one, and it won't change. Close the task, or destroy the box, and claim a fresh one:

text
rift_vm_destroy  <task>  <old box>
rift_vm_new      <task>  github:<you>/<repo>  main
rift_bash        <task>  <box>  "node --version && pnpm --version"

That's the full loop: the repository declares its environment, Rift builds it at every push to the watched branch, and every box your agent claims boots ready to work.

🎉 Your boxes have their tools. Where to go next:

  • Inside a box — the jail, the verbs, what persists between calls, and where secrets go.
  • Secrets — the credentials your verbs need, given to each run outside the jail and never to the box.
  • Warm pools — what a watched ref keeps warm, and when it rewarms.