Projects
Wall one tenant off from another so a single deployment can serve many clients whose data never crosses.
What a project is
A project is a wall between clients. One Pepe install can serve many clients, and nothing crosses from one to another: not files, not routing, not model keys.
Every client (tenant) is a project, including the one you get out of the box. A fresh
install has a single default project (slug default), and that is the project
every command uses when you omit --project. If you only serve yourself, nothing
changes: bare agent names go to the default project, so you never have to think about
projects until you want a second one. Add one only when you have to keep clients
walled off from each other.
project list like any other, it can be renamed, and it
carries its own billing. There is no special "root" scope with different rules;
omitting --project simply falls back to the default project.The handle is the identity
An agent’s real identity is its handle. In the default project the handle is
just the bare name (sales). In another project it is qualified as project/name
(acme/sales). The same bare name can be reused in every project, so acme/sales
and globex/sales are two different agents.
The handle is what addresses everything: routing, sessions, and channel bindings all use it. Under the hood every project and every agent also carries a stable internal id, and it is that id, not the name (which can change), that routing, permissions, defaults, and cron/bot/token bindings are recorded against. Renaming a project or an agent just relabels it and moves its directory; every reference follows, so nothing dangles.
Files
An agent’s workspace is ~/.pepe/projects/<slug>/agents/<name>/ and its project’s
shared space is ~/.pepe/projects/<slug>/shared/. Equally named agents in different
projects never write to the same directory, and a shared/... path can never leak
across tenants. The default project follows the same layout under its own slug
(~/.pepe/projects/default/…).
Routing
send_to_agent never crosses a project boundary. A bare target name resolves to a
peer inside the sender’s own project, and a hard guard refuses any cross-project
route even if an allowlist asks for one.
Models and keys
An agent resolves its models inside its own project first, then falls back to the default project. A project can therefore pin private provider keys that no other project can see, or inherit one shared global provider. A project’s agent or model is never promoted to the global default, not even when it is the first one created.
Creating and using a project
pepe project add acme --description "Acme Inc"
pepe project add globex
pepe project list
# agents, models and routes all take --project
pepe model add llm --project acme --base-url ... --api-key '${ACME_KEY}' --model ...
pepe agent add sales --project acme --prompt "..." --can-message support
pepe agent add support --project acme --prompt "..."
pepe agent route sales support --project acme # both resolve inside acme
pepe agent list --project acme # only Acme's
pepe agent list # only the default project
pepe agent list --all # every project
pepe chat --project acme sales # or: pepe run acme/sales "..."
Renaming and removing
pepe project rename acme umbrella # relabels it and moves its directory;
# all bindings follow, since they are by id
pepe project remove acme # refuses while it still owns agents
pepe project remove acme --force # removes it, and drops its agents too
Because references are by id, renaming a project (or an agent) never breaks a route, a token, a cron, or a bot binding. The name is a label; the id is what everything points at.
What it looks like in config
Projects live in a "projects" map keyed by a stable id, each entry carrying a
slug and a name, and a top-level "default_project" names the id that bare,
unqualified references fall back to.
"default_project": "p_1a2b3c4d",
"projects": {
"p_1a2b3c4d": { "slug": "default", "name": "Default" },
"p_5e6f7a8b": { "slug": "acme", "name": "Acme Inc", "default_model": "llm" }
},
"agents": {
"assistant": { "can_message": [] }, // default project
"acme/sales": { "can_message": ["acme/support"] },
"acme/support": { "can_message": [] }
}
Projects and channels
A Telegram bot bound to an agent in a project keeps its whole conversation inside that project. A bot bound to an agent in the default project serves the default project, exactly as it did before you added any second project at all.
Spend and message caps
The project is also the unit that billing meters. Every model call is metered per project, and a project can carry a monthly spend cap, a monthly customer-message cap, and a billing markup, including the default project. See Billing & limits for how to set, clear, and reset those, and Agents for the agent fields that projects scope.