Global vs Repo Config
View as markdownShelbi splits everything it writes to disk into two categories:
- Config covers the declarative decisions about the project: its name, the default branch, workflows, agent prompts, runner settings. These are the same for every teammate.
- State covers the per-user, per-machine, mutable data: issue cards, the events log, workspace status files, the orchestrator's dashboard worktree. Two teammates on the same project have different state.
Config can live in two places: under your $HOME (the default,
global mode), or committed to the repo itself (in-repo mode).
State always lives under ~/.shelbi/, regardless of mode.
First-run shelbi and shelbi init -y create global-mode config. You can move
a project to in-repo mode later with a one-shot command. The interactive
shelbi init path also offers an explicit --mode choice.
Which mode?
Solo repos and quick experiments belong in global mode. It's the default and needs zero repo-side changes. Reach for in-repo mode when you want teammates to clone your workflows, agent prompts, and runner settings without each of them having to redo the setup wizard.
The two modes at a glance
| Piece | Global mode | In-repo mode (fresh init / --pick-up) |
|---|---|---|
Shared config (name, default_branch, orchestrator, agent_runners, zen, heartbeat, git — SHARED_PROJECT_FIELDS) | ~/.shelbi/projects/<name>.yaml (one flat file) | Committed <repo>/.shelbi/project.yaml (with config_mode: in-repo) |
User-local config (repo path, machines, workspaces, editor — LOCAL_PROJECT_FIELDS) | Same flat ~/.shelbi/projects/<name>.yaml | Per-machine ~/.shelbi/projects/<name>/local.yaml (no flat <name>.yaml) |
| Shared directories (workflows/, agents/, workspace-settings.json.template) | ~/.shelbi/projects/<name>/ | <repo>/.shelbi/ |
| State (state.json, tasks/, HANDOFF.md, .claude/, workspaces/, events.log) | ~/.shelbi/projects/<name>/ (+ ~/.shelbi/events.log) | ~/.shelbi/projects/<name>/ (+ ~/.shelbi/events.log) |
| Discovery | Reverse-lookup: cwd matched against every registered project's local work_dir | Walk up from cwd for <dir>/.shelbi/project.yaml, then require a matching ~/.shelbi/projects/<name>/local.yaml |
| Shared with clones? | No: every teammate reruns shelbi init | Yes: everything under <repo>/.shelbi/ (the shared project.yaml plus the workflows/agents/settings directories, minus the gitignored state) travels with git clone; each teammate adds their own local.yaml via --pick-up |
A present local.yaml is authoritative
Fresh shelbi init --mode in-repo and shelbi init --pick-up produce this
split directly: the shared half is committed at <repo>/.shelbi/project.yaml
and your machines live in ~/.shelbi/projects/<name>/local.yaml. Once that
local.yaml exists the loader treats it as authoritative — it reads the
committed shared half plus your local half, and a stale flat
~/.shelbi/projects/<name>.yaml (a pre-migration global registration, a hand
copy) can never shadow it.
migrate-to-in-repo is now just the
path that converts an existing flat/global project into the same split.
The full field-by-field bucket lists are in
shelbi_core::model
under SHARED_PROJECT_FIELDS and LOCAL_PROJECT_FIELDS; a committed shared
YAML that carries a user-local field (or vice versa) errors on load rather
than silently letting one side win.
Global mode
The default. First-run shelbi, the interactive wizard, and shelbi init -y write
~/.shelbi/projects/<name>.yaml with everything (shared and
user-local) in one flat file. Nothing is added to your repo. The
project only exists on your machine.
Resolution is by reverse-lookup: Shelbi scans ~/.shelbi/projects/*.yaml
once, collects each project's machines[].work_dir (local machines only;
an SSH host's work_dir is a path on another box), and matches cwd (or
an ancestor) against them. The deepest match wins, so nested checkouts
resolve to the sub-project. shelbi -p <name> short-circuits the walk
entirely.
Global mode is the right choice for:
- Solo projects nobody else clones.
- Scratch experiments where committing anything to the repo would be noise.
- Repos you don't own the write-access story for.
If you want to try in-repo mode later, migrate with a single command.
In-repo mode
In-repo mode commits the project's shared config — <repo>/.shelbi/project.yaml
plus the shared workflows/, agent prompts, and settings template under
<repo>/.shelbi/ — so everyone who clones the repo shares the same declarative
setup. Each teammate still has their own machines and workspace pool; those
user-local fields live in a per-user ~/.shelbi/projects/<name>/local.yaml
that never gets committed. Fresh shelbi init --mode in-repo writes both
halves of that split; shelbi init --pick-up writes just the local.yaml half
against a shared config a teammate already committed.
On-disk layout (fresh init / --pick-up)
<repo>/
.shelbi/
project.yaml # ← committed shared half: name, display_name,
# default_branch, orchestrator, agent_runners,
# zen, heartbeat, git, config_mode: in-repo
workflows/
default.yaml
statuses.yaml
agents/
orchestrator/
developer/
_shared/ # optional shared preamble
workspace-settings.json.template
.gitignore # ← carries the state entries; see below~/.shelbi/
projects/
<name>/
local.yaml # ← user-local half: repo, machines, workspaces,
# editor (no flat <name>.yaml for in-repo)
state.json # ← state — never committed
tasks/
HANDOFF.md
.claude/
workspaces/
events.log # ← state — cross-projectThe shared/local split is enforced at parse time: a machines: block inside
<repo>/.shelbi/project.yaml, or a zen: block inside local.yaml, raises
an error identifying the misplaced field and naming the file it belongs in.
Migrating an existing global project produces the identical layout — see
migrating a project to in-repo mode.
Discovery walk-up
From anywhere inside the repo (the root, a nested directory, wherever
you happen to cd), Shelbi walks up looking for the first ancestor
containing .shelbi/project.yaml. This mirrors how git finds its
.git directory: no env var, no marker file to place by hand.
Once the walk-up finds a committed project.yaml, Shelbi reads the name:
field out of it and checks that you've registered the project locally
(normally a ~/.shelbi/projects/<name>/local.yaml). If you haven't, the
resolver returns ProjectNotPickedUp and points at shelbi init --pick-up,
the flow for a fresh clone that hasn't been registered locally yet
(see below). Pick-up writes that
user-local local.yaml and reads the committed shared half in place; it never
rewrites the committed file.
Walk-up wins over reverse-lookup: if a repo happens to sit inside another
registered project's work_dir, the walk-up match takes precedence.
The .gitignore list
State that lives at <repo>/.shelbi/ (bind-mounted or symlinked in
from ~/.shelbi/) must never land in a commit. shelbi project migrate-to-in-repo prints (and optionally appends) this snippet at
<repo>/.gitignore:
.shelbi/state.json
.shelbi/tasks/
.shelbi/HANDOFF.md
.shelbi/.claude/
.shelbi/workspaces/
.shelbi/events.log
.shelbi/local.yamlEvery line names one state footprint. Using in-repo mode without running
the migration? Add the snippet by hand. The point is that
git grep -F 'shelbi' .gitignore at the repo root surfaces the full
list, so you never have to memorize the layout.
The local.yaml line is defensive. Today local.yaml lives under
~/.shelbi/ and needs no ignoring. But if you ever symlink it into the
repo (say, so an editor picks it up), the ignore line prevents an
accidental commit.
Local-alias collisions
The name: in <repo>/.shelbi/project.yaml is a shared contract with
every future clone. It stays stable. But two teammates can each clone
into ~/work/shelbi/ and end up with a local registry collision on the
name. shelbi init --pick-up handles this by auto-suffixing the local
alias:
- First clone: registered as
shelbi. - Second clone on the same machine: registered as
shelbi-2(-3,-4, …). The committed name is unchanged; the alias is only how you refer to that clone locally.
You reach the suffixed clone with shelbi -p shelbi-2. The local alias is
fixed at pick-up time; there is no rename command, so if you want a
different alias, re-pick-up the clone (or edit the registry filename and its
entry by hand).
Migrating a project to in-repo mode
shelbi project migrate-to-in-repo is a one-way command that splits an
existing global-mode project into the in-repo layout:
- Splits
~/.shelbi/projects/<name>.yamlinto a committed<repo>/.shelbi/project.yaml(shared half, withconfig_mode: in-repo) and a per-machine~/.shelbi/projects/<name>/local.yaml. - Moves
workflows/,agents/, andworkspace-settings.json.templatefrom~/.shelbi/projects/<name>/into<repo>/.shelbi/. The mover prefersfs::rename; on cross-filesystem failure it copies then removes. - Prints the
.gitignoresnippet above and offers to auto-append it (interactively), or applies it non-interactively with--yes. - Retires the original
~/.shelbi/projects/<name>.yamlby renaming it to<name>.yaml.migrated(it is not deleted — the copy stays as a rollback source), whose contents now live in the two files above.
State (state.json, tasks/, HANDOFF.md, .claude/, workspaces/,
events.log) stays put under ~/.shelbi/.
Try it dry first
shelbi project migrate-to-in-repo --project myapp --dry-runPrints the ordered plan (every write, move, delete) without touching disk. It's diff-oriented, so a reviewer can vet the migration before it runs.
Apply it
shelbi project migrate-to-in-repo --project myappInteractive: prompts for the .gitignore append. Pass --yes to skip
the prompt (useful in scripts). --project is optional: a bare
shelbi project migrate-to-in-repo resolves the project from $SHELBI_PROJECT
or the current directory the same way any other subcommand does.
The migration is idempotent: rerunning on an already-migrated project is a no-op, and rerunning on a half-migrated one completes the outstanding steps. Safe to retry.
What to commit
After a successful migration, commit:
.shelbi/project.yaml
.shelbi/workflows/
.shelbi/agents/
.shelbi/workspace-settings.json.template
.gitignore # updated with the state snippetEverything else is either state (already ignored) or lives outside the repo entirely.
It's one-way
There is no migrate-to-global command. Reverting is:
git revertthe migration commit, which restores<repo>/.shelbi/to its pre-migration state.- Restore the global-mode YAML: rename the retained
~/.shelbi/projects/<name>.yaml.migratedcopy back to~/.shelbi/projects/<name>.yaml(or reconstruct it from~/.shelbi/projects/<name>/local.yamlplus the committed shared half).
A merged migration commit is expensive to undo. --dry-run first.
Picking up a teammate's project
When a teammate has committed <repo>/.shelbi/ and you clone the repo,
you need a local registry entry (the local.yaml with your machines
and workspace pool) before the walk-up will resolve. shelbi init --pick-up walks you through it:
$ git clone git@github.com:acme/shelbi.git
$ cd shelbi
$ ls .shelbi/
agents/ project.yaml workflows/ workspace-settings.json.template
$ shelbi init --pick-up
✓ scaffolded /Users/you/.shelbi
✓ registered project (in-repo): /Users/you/.shelbi/projects/shelbi/local.yaml
✓ wrote workspace settings template: /Users/you/work/shelbi/.shelbi/workspace-settings.json.template
✓ created agent workspace: agents/orchestrator/
✓ created agent workspace: agents/developer/
✓ wrote project statuses: /Users/you/work/shelbi/.shelbi/workflows/statuses.yaml
✓ picked up `shelbi` from /Users/you/work/shelbi/.shelbi/project.yaml.
next:
1. add machines/workspaces to ~/.shelbi/projects/shelbi/local.yaml if needed
2. spawn your first agent: shelbi spawn TASK --on hub --runner claude "…"--pick-up walks up from cwd to find the committed
<repo>/.shelbi/project.yaml, reads the canonical name, and writes a
user-local ~/.shelbi/projects/<name>/local.yaml that points your machines at
the checkout — it reads the committed shared half in place and never rewrites
it. (If the committed file is a legacy name-only stub that predates the
shared/local split, pick-up instead falls back to a flat
~/.shelbi/projects/<name>.yaml registration so the project still loads.)
From here, shelbi inside the repo just works.
If the canonical name is already taken locally (you already have another clone, or another project named the same), the alias is auto-suffixed:
$ shelbi init --pick-up
✓ scaffolded /Users/you/.shelbi
note: local alias `shelbi` was already taken — using `shelbi-2` on this machine instead (the committed name is unchanged)
✓ registered project (in-repo): /Users/you/.shelbi/projects/shelbi-2/local.yaml
…
✓ picked up `shelbi` from /Users/you/work/shelbi/.shelbi/project.yaml as local alias `shelbi-2`.
next:
1. add machines/workspaces to ~/.shelbi/projects/shelbi-2/local.yaml if needed
2. pass `-p shelbi-2` on the command line to target this project
(the committed name `shelbi` was already taken locally — the alias only affects your machine)Safety net: bare shelbi init on an unregistered clone
If you forget the --pick-up and run a plain shelbi init in a cloned
repo that already carries <repo>/.shelbi/project.yaml, Shelbi refuses
to scaffold over the top:
$ shelbi init
Error: found /Users/you/work/shelbi/.shelbi/project.yaml (committed) but no local registry entry for `shelbi` — this repo looks like a teammate's shelbi project. Run `shelbi init --pick-up` to register it locally.shelbi reload semantics
shelbi reload respawns the Shelbi-owned panes (sidebar, issues, review,
machines) and self-heals default agent workspaces and the workspace
settings template. As part of that, it re-reads the project's YAML, so
edits to <repo>/.shelbi/project.yaml or local.yaml (in-repo mode) or
~/.shelbi/projects/<name>.yaml (global mode) take effect on the next
reload.
The one important nuance: a running workspace keeps its current prompt until it hands off. A reload never swaps the prompt mid-issue; changes reach a workspace on its next dispatch, once it picks up a fresh card. This is deliberate. A mid-turn prompt swap would surprise the agent and make debugging much harder.
If you need a running workspace to pick up new instructions immediately, kill its issue, reload, then re-dispatch.
See also
- Project config — the field-by-field reference for the YAML this page routes to disk, including the exact shared vs. user-local field split.
- Set up your first project — the detected, one-confirmation global-mode setup.
shelbi reload— respawn the TUI panes after editing the YAML or installing a new binary.shelbi init --helpandshelbi project migrate-to-in-repo --help— the full flag reference for the two commands this page covers.