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.yamlwith commented examples. - It adds
.envand.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:
dbis a postgres. bothy exports its URL asDATABASE_URL.cacheis a redis. bothy exports its URL asREDIS_URL.webis your dev server. bothy runsnpm ci, then the command. It puts a free port in$PORT. It startswebonly afterdbandcacheare 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 initwrites aSessionStarthook and aCwdChangedhook into.claude/settings.json. The services are ready before the first tool call. The hooks write the variables to the file inCLAUDE_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 --jsonandbothy schemagive the state of the machine, the environment of this worktree, and the format ofbothy.yamlas 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 enterstarts the worktree and prints the variables as bashexportlines.--waitmakes it wait until the tools are onPATHand the services pass their health checks. Without it,bothy enterreturns after 100 milliseconds, as it does for a shell prompt.--shell-pidnames the process that holds the worktree open. In a hook,$PPIDis 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 077makes 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.yamlin your CI. - Examples has complete projects to copy.