Getting started

From an empty project to two worktrees that run at the same time.

This page assumes that you ran bothy install. If you did not, see Install.

1. Scaffold the project

Go to the root of your project and run:

bothy init

This command does three things:

  • It writes a bothy.yaml with commented examples.
  • It adds .env and .bothy/ to .gitignore.
  • It writes hooks for Claude Code into .claude/settings.json. See Use bothy with an agent.

2. Declare the tools

Open bothy.yaml. The smallest useful file has only tools:

tools:
  - nodejs_22
  - ripgrep

Each entry is the name of a package in nixpkgs, the package set that Nix uses. You can search for names at search.nixos.org.

Now cd out of the project and back in. You get that node and that ripgrep. Your own node outside the project does not change. When you leave, it comes back.

The first entry is slow, because Nix downloads the packages. bothy gives your prompt back at once and continues in the background. bothy status shows the progress. Later entries are fast.

3. Add the services

Add the services that the project runs against:

tools:
  - nodejs_22

services:
  db:
    type: postgres
    url_env: DATABASE_URL
  cache:
    type: redis
    url_env: REDIS_URL
  web:
    type: command
    command: "npm run dev -- --port $PORT"
    setup: "npm ci"
    port_env: PORT
    depends_on: [db, cache]

This file tells bothy these things:

  • db is a postgres. bothy exports its URL as DATABASE_URL.
  • cache is a redis. bothy exports its URL as REDIS_URL.
  • web is your dev server. bothy runs npm ci, then the command. It puts a free port in $PORT. It starts web only after db and cache are ready.

At your next prompt, bothy starts the services in that order and exports the variables:

$ echo $DATABASE_URL
postgres://bothy@127.0.0.1:28000/postgres
$ echo $PORT
28002

psql "$DATABASE_URL" now connects to a postgres that belongs to this worktree only. When you cd out, bothy waits 60 seconds and then stops the services. The data stays for the next time.

Commit bothy.yaml. It is the shared description of what the project needs, like a package.json.

4. See what bothy does

These commands show the state of the worktree:

bothy explain         # what this worktree declares, runs and sets
bothy status          # what runs now, with ports and health
bothy logs web        # the output of one service

bothy explain starts nothing, not even the daemon. Run it while you edit bothy.yaml. It shows the errors in the file at once.

For the file above, it prints:

$ bothy explain
~/src/app (57cbefee)
  not started yet -- ports show as <port> until you enter the worktree

services, in start order:

  cache (redis, port not allocated yet, not running)
    starts:      on entry
    runs:        redis-server --port {port} --bind 127.0.0.1 --save '' --dir ~/.local/state/bothy/w/57cbefee/cache
    ready when:  redis-cli -p {port} ping
    data dir:    ~/.local/state/bothy/w/57cbefee/cache
    stops with:  SIGTERM to the process group, then SIGKILL after 5s

  db (postgres, port not allocated yet, not running)
    starts:      on entry
    setup:       initdb --username=bothy --auth=trust -D ~/.local/state/bothy/w/57cbefee/db (first start only)
    runs:        postgres -D ~/.local/state/bothy/w/57cbefee/db -p {port} -h 127.0.0.1 -k ~/.local/state/bothy/w/57cbefee/db
    ready when:  pg_isready -h 127.0.0.1 -p {port}
    then:        createuser -h 127.0.0.1 -p {port} -U bothy --superuser bothy (unless psql -X -h 127.0.0.1 -p {port} -U bothy -d template1 -tAc 'select 1 from pg_roles where rolname = 'bothy'' prints 1)
                 createdb -h 127.0.0.1 -p {port} -U bothy --owner bothy postgres (unless psql -X -h 127.0.0.1 -p {port} -U bothy -d template1 -tAc 'select 1 from pg_database where datname = 'postgres'' prints 1)
    data dir:    ~/.local/state/bothy/w/57cbefee/db
    stops with:  SIGINT to the process group, then SIGKILL after 5s

  web (command, port not allocated yet, not running)
    starts:      on entry
    after:       db, cache
    setup:       npm ci
    runs:        sh -c "npm run dev -- --port $PORT"
    ready when:  none -- counts as started the moment it spawns
    data dir:    ~/.local/state/bothy/w/57cbefee/web
    stops with:  SIGTERM to the process group, then SIGKILL after 5s

tools, put on PATH and not supervised:
  nodejs_22

environment -- your shell and every service:
  BOTHY_CACHE_DATA_DIR = ~/.local/state/bothy/w/57cbefee/cache
                         ^ cache: every service gets a per-worktree data dir
  BOTHY_DB_DATA_DIR    = ~/.local/state/bothy/w/57cbefee/db
                         ^ db: every service gets a per-worktree data dir
  BOTHY_WEB_DATA_DIR   = ~/.local/state/bothy/w/57cbefee/web
                         ^ web: every service gets a per-worktree data dir

also exported by the shell hook: PATH, BOTHY_ROOT, BOTHY_SESSION_ID, BOTHY_VARS, BOTHY_ENV_SOURCE, BOTHY_PENDING

The commands show {port} until you enter the worktree, because bothy allocates the ports at entry. For the same reason, web shows ready when: none. At entry, web gets a port, and bothy waits until the port accepts a connection.

5. Add a second worktree

There is no bothy step here. The cd is the whole setup:

git worktree add ../myproject-wt2 -b some-branch
cd ../myproject-wt2

The committed bothy.yaml came with the checkout, and the hook is already in your shell. bothy gives this worktree new ports and new data directories. Both stacks run at the same time:

$ bothy status --all --print
~/src/myproject      d8f23243925cce66  1 session (pid 156700)
    web  command  healthy  port 28002  pid 156733
~/src/myproject-wt2  7c77b581a8989fc9  1 session (pid 180012)
    web  command  healthy  port 28003  pid 180076

A migration in the second worktree changes only the database of the second worktree.

6. Add a secret

bothy.yaml is committed, so a password must not go in it. Put such values in the .env file beside it:

STRIPE_KEY=sk_test_123

bothy gives every value to your shell and to every service. You declare nothing. See Secrets for the file format, and for services that must not share credentials.

Use bothy with an agent

Parallel agent sessions are the reason bothy exists. An agent does not type cd in an interactive shell, so bothy gives it three other ways in:

  • bothy init writes a SessionStart hook and a CwdChanged hook into .claude/settings.json. The services are ready before the first tool call. The hooks write the variables to the file in CLAUDE_ENV_FILE, and Claude Code loads that file before every Bash tool call. If the agent moves to another worktree, it gets the ports of that worktree.
  • bothy exec -- <command> runs one command inside the worktree, with the tools and the services ready. Use it for any agent or script that has no shell hook.
  • bothy status --json, bothy explain --json and bothy schema give the state of the machine, the environment of this worktree, and the format of bothy.yaml as data.

Agents other than Claude Code

The hooks that bothy init writes run this command:

umask 077; bothy enter --wait --shell-pid $PPID >"${CLAUDE_ENV_FILE:-/dev/null}"

You can use the same command in any agent that runs a hook when a session starts:

  • bothy enter starts the worktree and prints the variables as bash export lines.
  • --wait makes it wait until the tools are on PATH and the services pass their health checks. Without it, bothy enter returns after 100 milliseconds, as it does for a shell prompt.
  • --shell-pid names the process that holds the worktree open. In a hook, $PPID is the agent itself. The session ends when that process exits, and bothy stops the services 60 seconds later.
  • The output holds the values of your secrets. umask 077 makes the file readable by you only.

Send the output to a file that the agent loads before each shell command. If the agent has no such file, use bothy exec -- <command> for each command instead.

Next

  • Configuration explains every field and every service type.
  • CI runs the same bothy.yaml in your CI.
  • Examples has complete projects to copy.