Guided install
This page is written to be read by an AI coding agent. You do not follow it yourself; you hand it to a coding session running in your repository, and the session interviews you, writes the configuration, runs the installer, and checks that mail actually flows.
Point your agent at this page
Open a coding session in the repository you want to adopt Relay, and give it this instruction:
Set up Pibmo Relay in this repo by following https://pibmo.com/relay/onboarding/.The session will fetch this page and work through it top to bottom, asking you the questions in section 2 one at a time. The rest of this page is addressed to that session.
If you are an agent reading this: follow it top to bottom. Ask the questions in section 2 one at a time, in order, and wait for an answer before moving on. Do not guess an answer you were not given. Do not run the installer until section 4. Everything you need to decide is a question to the human, not a default you may choose for them.
A human can also follow this page by hand. It is the same sequence either way.
1. What you are setting up
Relay carries addressed, read-tracked mail between agent sessions over a git repository. Setting it up means deciding four things:
| Decision | Why it cannot be defaulted |
|---|---|
| Who the members are | Only you know your team’s roles and where their checkouts live |
| Where the transport repository lives | It must sit outside your product repository, and whether it has a remote at all depends on how your team works |
| Whether members are separate operating-system users | This changes the security model, not just the paths |
| What the instance is called | Relay never assumes its own name in your tree |
Before you start
The human needs git on PATH, a configured git identity (git config user.email, because the installer makes a commit), and Python 3.9 or newer.
Check those, and whether the pibmo-relay CLI is already installed:
python3 --version && git --version && git config user.email && pibmo-relay --versionIf pibmo-relay is missing, install it — ask the human before running
anything that changes their machine. Relay is installed from source and is not
on any package index, so the installer requires you to say where from:
curl -fsSL https://pibmo.com/relay-install.sh \ | RELAY_SOURCE=<git URL or path to a checkout> shDo not pass a bare package name. pibmo-relay is unregistered on PyPI;
anything resolving under that name is not Relay. The installer refuses a bare
name rather than resolving it, so this fails closed rather than installing
something else.
That installs two commands, pibmo-relay (the client) and pibmo-relay-install
(the installer), using uv, pipx, or pip, whichever is present. If the
command is still not found afterward, the tool’s bin directory is not on PATH;
see Troubleshooting.
2. The interview
Ask these in order. The answers become team.json.
Q1. What should this instance be called?
It becomes the command your team types, as tools/<name>.sh. Something like
team-mail, crew, or desk. Relay hard-codes nothing about its own name.
Q2. Who are the members?
For each member, collect:
- role — the address other members send to. Lowercase, no spaces
(
lead,analyst,web). This is a mailbox name, not a job title. - persona — optional human-readable name, purely routing shorthand.
- checkout_path — the absolute path of that member’s working copy. Strongly recommended: it is the only unambiguous way a session can tell which member it is. Without it, Relay guesses from the directory name and refuses to guess when the answer is ambiguous.
- status —
active,planned, orretired. Planned members can receive mail; retired members cannot be addressed.
Q3. Are the members separate operating-system users?
This is a security question, so ask it plainly and do not infer the answer from the paths.
- No, all checkouts run as one OS user (the common case: several directories
under one login). Either topology works. Use
per-member-clone. - Yes, each member is a separate OS user, so credentials are isolated. You
must use
per-member-clone. Relay will refuseshared-clonehere, because the directory permissions that make a shared clone work are exactly the permissions that let one user read and write another’s mailbox.
Record os_user per member if the answer is yes. The security reasoning behind
this question is in Topologies and the security model.
Q4. Where should the transport repository live, and does it need a remote?
Two separate questions. Ask both.
Location. It must be outside every product repository. Mail is committed
and pushed, so a store inside your project would put agent chatter into your
project’s history and push it to your default branch. The installer refuses this
outright. A sibling directory is the usual answer: for a project at
~/code/myproject, the store goes at ~/code/myproject-mail.
Remote. Ask the human directly, because both answers are legitimate:
- No remote (local only). Everything runs on one machine. Mail is committed
locally and never pushed. Simplest, no network, no hosting. This is a real
supported mode, not a degraded one.
syncandflushwill report that no remote is configured, which is expected. - A shared remote. Members have their own clones and synchronize through it.
You need a git URL every member can reach and push to — a private hosting
repo (a clone holds every message), or, for several checkouts on one machine
that you still want to sync through a repo, a local bare repo
(
git init --bare ~/code/project-mail.git) whose filesystem path is the URL.
If they choose a remote, also ask what its default branch is called: not every
repository uses main, Relay will not assume, and the branch set in the roster
must match the transport’s actual default branch or the first push fails.
Q5. Should sessions be told about new mail automatically?
Recommended: yes. The installer writes a SessionStart hook so each session
opens with a banner listing unread mail. Without it the transport still works,
but mail sits unread until someone thinks to check, which is the failure mode
this whole system exists to prevent. See Host integration.
3. Write the configuration
Produce team.json in the adopting repository from the answers. A worked
example for a three-member team on one machine with no remote:
{ "instance_name": "team-mail", "topology": "per-member-clone", "members": [ { "role": "lead", "persona": "Ada", "status": "active", "checkout_path": "/home/you/code/project-lead" }, { "role": "analyst", "persona": "Grace", "status": "active", "checkout_path": "/home/you/code/project-analyst" }, { "role": "web", "status": "planned", "checkout_path": "/home/you/code/project-web" } ]}With a shared remote whose default branch is trunk, add:
{ "remote": "origin", "branch": "trunk"}Show the human the file and get confirmation before running anything.
Full key-by-key documentation: Configuration reference.
4. Run the installer
The interview configures; this script installs. Everything from here is deterministic and re-runnable.
pibmo-relay-install \ --adopter-dir . \ --config team.json \ --store-dir ../project-mailAdd --remote-url git@github.com:you/project-mail.git if the team chose a
remote in Q4. Omit it entirely for local-only. If you omit --store-dir, the
installer defaults to a sibling directory named <repo>-mail.
The installer will:
- Create and
git initthe transport store, on the configured branch. - Create a mailbox skeleton for every member and commit it, so a member who clones the store receives their mailboxes and the roster.
- Write
tools/<instance>.sh, a wrapper holding no machine-specific paths, so it is safe to commit and share. - Write
.relay/config.envwith this machine’s paths (RELAY_STORE_DIRandRELAY_BIN), and add.relay/to.gitignore. - Register the
SessionStarthook in.claude/settings.json, merging with whatever is already there. Pass--no-hooksto skip this step.
Re-running it with the same configuration reports that nothing changed. If it refuses because the store would sit inside the adopting repository, that is the guard in Q4 doing its job; pick a location outside the repo.
Repeat for each member’s checkout
How this works depends on whether the team has a remote:
- With a shared remote (
per-member-clone): run the same command in every member’s working copy, pointing--store-dirat that member’s own clone of the store. Each member has their own clone and they synchronize through the remote. The first member’s install publishes the store to the remote; every later member’s install clones it. - On one machine with no remote: there is nothing to synchronize through,
so separate clones would never see each other’s mail. Point every checkout’s
--store-dirat the same store directory instead — one shared store that all seats read and write. This is the natural fit forshared-clone; the worked example above uses no remote, so its checkouts must share one store.
In short: separate clones need a remote to exchange mail; a single-machine team with no remote shares one store directory. See Topologies & Security.
5. Verify it works
Do not report success until a message actually crosses from one checkout to
another. Under per-member-clone (this guide’s example) each member has their own
clone of the store: a send commits and pushes to the transport, and the
recipient pulls it with sync. So the check spans two checkouts.
In the sender’s checkout (here lead — run these from that working copy):
./tools/team-mail.sh who # identity resolves to: lead
./tools/team-mail.sh send --to analyst \ --type request --subject "Setup check" --body "Reply if you can read this."# sent 5a253779-... lead -> analyst [request] "Setup check"In the recipient’s checkout (analyst, a different working copy):
./tools/team-mail.sh sync # pull from the transport -- see the note below./tools/team-mail.sh list # [unread] from=lead ... "Setup check"Inside a member’s checkout you do not pass --role / --from: the member is
resolved from its checkout_path. who printing identity: (unresolved) means
this checkout could not be matched — set checkout_path for that member and
re-run the installer, or export RELAY_ROLE. And ./tools/team-mail.sh is just
the pibmo-relay command with this checkout’s store preset.
The most common “it looks broken” is a missing
sync. With separate clones (per-member-clone), a message the sender pushed is not in the recipient’s clone until the recipient runssync. The session banner reports what is already in the local clone and does not fetch, so runsyncwhen you sit down to a session or before checking mail. Ashared-clonesingle-store team has one store and nosyncstep — every seat sees each write immediately.
Then start a fresh session in the recipient’s checkout and (after a sync) confirm
the unread-mail banner appears. If it does not, see Host integration.
6. Tell the team what it does not do
Say this out loud to the human before you finish, because it is the property people most often assume backwards:
Relay provides addressing and read-tracking. It does not provide confidentiality between members. The transport is a git repository, git has no path-level access control, and any member who can clone it holds every message ever sent, including those addressed to someone else. Sender names are self-asserted and are not authenticated.
Under isolated operating-system users you additionally get credential and compute isolation. That is isolation of members from each other’s machines, not of messages from other members.
If a team needs per-member message confidentiality, a git-native transport is the wrong architecture and no configuration of Relay will provide it. The full security model is in Topologies and the security model and Architecture.
Where to go next
- Overview — what Relay is and why it exists
- Getting started — the shortest path to a first message
- Core concepts — members, roles, mailboxes, and the store
- Configuration reference — every
team.jsonkey - CLI reference — every command and flag
- Topologies and the security model
- Host integration — the session banner and mail escalation
- Troubleshooting
- FAQ