Installing pi-tehas for a new project
One install serves one project: one host, one Unix user, one product repository. Installing it for another repository means preparing a host and filling in an instance directory. No script is edited.
The work comes in three levels.
| Level | You get | Effort |
|---|---|---|
| 1. Issue to pull request | Queue, cycles, gates, dashboard, live console | Settings only, plus your toolchain in the sandbox image |
| 2. Database clones | A private copy of your data per cycle or preview | Project-specific. Not generic yet |
| 3. Staging stacks and previews | The product running per pull request behind fake domains | Project-specific. Not generic yet |
Level 1 is the supported contract. This page walks through it with the instance
that works on tehas itself: host tehas-tehas, user boss, product
janit/tehas-private. Its settings are in instances/tehas-tehas/ in this
repository, and the record of setting it up is
docs/bringup/2026-10-08-tehas-tehas-log.md.
What the product repository must provide
This is the contract. It lives in the product repository, is written by people, and is the only thing pi-tehas reads from it.
| File | Purpose | Required |
|---|---|---|
AGENTS.md |
The rules agents work under: architecture rules, how to verify, commit style, what never to touch. No sentence granting merge rights, unless you want auto-merge | Yes |
.pi/verify-cmd |
Fast gate. The first line that is not empty or a comment is run in the cycle's worktree, 10 minute limit. Without it pi-rukas guesses from package.json or Cargo.toml, and for other stacks verifies nothing |
Yes |
.pi/verify-cmd-full |
Full gate, same format; stands in for CI | Recommended |
.pi/smoke-cmd |
Proves the built product starts, same format | Recommended |
.pi/worktree-setup |
Shell script run in each new worktree with no arguments and no environment: install dependencies. Must exit 0 | If a bare checkout cannot run the gates |
.pi/settings.json |
defaultProvider and defaultModel for the session |
Yes |
.gitignore entries |
.pi/work-state/, .pi/permissions.json, .pi/decisions.json |
Yes |
.agents/skills/ |
Project skills: short instructions plus scripts for things agents get wrong | Optional |
This repository is a product too, so its own AGENTS.md, .pi/ and
.gitignore are a small working example to copy from. Make the gates print one
line per step and exit non-zero on failure. Before going further, prove them by
hand in a clean checkout.
Work lands on the repository's default branch: pi-rukas branches from it and
the pull request targets it. tehas reads the default branch from the checkout.
Set TEHAS_BRANCH only to use another one.
Level 1: issue to pull request
1. A host
One Linux machine, reachable over a private network, that can reach GitHub and an OpenAI-compatible model endpoint. Bootstrap a host prepares it:
sudo ./bootstrap.sh root boss
./bootstrap.sh user
./bootstrap.sh check
The tehas user ends up in the docker group, which makes it root-equivalent on
the host. The boundary that matters is the sandbox: agents never get the Docker
socket.
2. Credentials
- A GitHub machine account that is a write collaborator on the product
repository. Its token goes in
~/.config/tehas/env, mode 600, asGH_TOKEN=.... Use one bot account per instance: the token enters the cycle's container, so it should reach this product and nothing else. - A read-only deploy key on the platform repository, if it is private.
- The bot's git identity for commits:
git config --global user.nameanduser.emailfor the tehas user.
The bootstrap page has the details and the token trap to avoid.
3. Two clones
The platform checkout is this repository at ~/tehas/platform, where you ran
bootstrap.sh from. Clone the product next to it, with the bot's token:
git clone <the product repository> ~/tehas/repos/<name>
The product checkout is where cycles work. The platform checkout is where
install.sh runs from. Keep them apart even when, as in the example, they are
the same repository: the factory then changes only when a person pulls the
platform checkout and installs.
4. An instance directory
Copy instances/example/ and fill it in. It can live anywhere the tehas user
can read: next to the secrets in ~/.config/tehas (the default place), in a
private repository of your own, or, as the example does, in the platform
checkout.
echo ~/tehas/platform/instances/tehas-tehas >~/.config/tehas/instance-dir
The example's whole instance.env:
TEHAS_REPO=janit/tehas-private
TEHAS_CONTROL_ISSUE=5
TEHAS_ALLOWED_LABELERS="janit"
PI_ENSEMBLE_SUBAGENT_MODEL=qwen3.8-flash-next
PI_ENSEMBLE_SUBAGENT_PROVIDER=fleet
PI_ENSEMBLE_HOST_ALIASES=gb1:100.95.94.105
TEHAS_HOST_ADDR=100.110.154.50
| Setting | What to put there |
|---|---|
TEHAS_REPO |
owner/name of the product repository |
TEHAS_CONTROL_ISSUE |
Number of an issue you create for the pause label. Keep it open |
TEHAS_ALLOWED_LABELERS |
Logins whose tehas:ready starts a cycle, space separated. Never the bot |
PI_ENSEMBLE_SUBAGENT_MODEL, _PROVIDER |
The model every agent role uses. Must be listed in models.json |
PI_ENSEMBLE_HOST_ALIASES |
name:address pairs the sandbox needs, such as a model host whose name only resolves privately |
TEHAS_HOST_ADDR |
The host's private address, to reach the dashboard from other devices. Without it: 127.0.0.1 |
models.json next to it names the provider's baseUrl and the models it
serves. List the model you use first: an agent that loses its model setting
falls back to the first entry. The model in the product's .pi/settings.json
must be listed too.
Everything else has a default. The limits of a cycle's container default to the host's CPUs minus one and half its memory. All settings are on the commands page.
5. Install
cd ~/tehas/platform
./install.sh --check
./install.sh
sed -i '1i [ -f ~/tehas/env.sh ] \&\& . ~/tehas/env.sh' ~/.bashrc
The last line puts env.sh first in ~/.bashrc. It has to come before the
check many distributions have there that returns early for non-interactive
shells, or ssh host '<command>' would run without the tehas environment.
--check first tells you what is missing or malformed in the instance, what
tools the host lacks, and what would be installed. A real run with invalid
settings installs nothing and exits 78.
6. The sandbox image
sandbox/Dockerfile adds deno, shellcheck and Playwright's Chromium to the
pi-rukas image and remaps the user id. Add whatever your gates need. Then:
~/tehas/sandbox/build.sh
7. Prove the gates in the sandbox
Before the first issue. This runs the product's own gates inside the image, as the unprivileged user, with nothing mounted but a clone:
T=~/tehas/tmp/contract-test
git clone -q ~/tehas/repos/<name> "$T"
docker run --rm --cpus 4 --memory 4g --user "$(id -u):$(id -g)" \
-e HOME=/home/vscode -v "$T:$T" -w "$T" --entrypoint bash \
"$PI_ENSEMBLE_IMAGE" -c '
sh .pi/worktree-setup &&
sh -c "$(grep -v -e "^#" -e "^$" .pi/verify-cmd-full | head -n 1)" &&
sh -c "$(grep -v -e "^#" -e "^$" .pi/smoke-cmd | head -n 1)"'
rm -rf "$T"
8. Start
docker compose -f ~/tehas/edge/docs.compose.yaml up -d # this site and the dashboard
tehas-dispatch --init-labels
tehas-dispatch --dry-run
systemctl --user enable --now tehas-dispatch.timer
Then label one small, well-specified issue with tehas:ready and watch it on
the dashboard. Operating tehas takes it from there.
Levels 2 and 3: clones and previews
Both are off by default (TEHAS_CLONES=0, TEHAS_PREVIEWS=0) and then nothing
of them is installed or running.
What is in this repository for them is one product's implementation, not an interface:
- Clones.
pg-clonegives each cycle or stack a private, writable copy of one PostgreSQL database in seconds, using reflink copies.pg-refreshrestores a nightly dump into the snapshot it clones from. The database names, the container and the backup location in those two scripts are that product's. - Previews.
tehas-stackbuilds and runs that product's services per pull request, behind a fake top-level domain served by a small DNS server and an HTTP proxy. The services, their environment, the health checks, the smoke pages and the route generator are that product's. The reusable parts are the edge itself, the lifecycle (build before replace, retry, state files) and the dispatcher's handling of previews.
Staging shows them in use. A second product that needs previews is what would turn this into a shared interface; nobody has needed it yet, so none was designed. Until then, adopting levels 2 or 3 means writing your own versions of those files.
Two rules to carry over whatever the product:
- Staging must be inert. No outbound job, sync, purge, email or paid API from a preview.
- The product must not know about staging. Do not teach it a staging domain. Keep its links on production and let a gate fail a file or page that names the staging domain.
Switching a feature off later is safe: install.sh stops what the feature runs
before it removes the files.
What is not generic yet
- Previews and clones, as above.
- More than one project per host. A second project needs its own Unix user, or better its own machine.
bootstrap.shsupports Ubuntu 26.04 on x86_64 only.