Global vs Repo Config

View as markdown

Shelbi 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: task 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

PieceGlobal modeIn-repo mode
Shared config (name, default_branch, orchestrator, agent_runners, zen, heartbeat, git, workflows/, agents/, workspace-settings.json.template)~/.shelbi/projects/<name>.yaml and ~/.shelbi/projects/<name>/<repo>/.shelbi/project.yaml and <repo>/.shelbi/
User-local config (repo path, machines, workspaces, editor)Same YAML: ~/.shelbi/projects/<name>.yaml~/.shelbi/projects/<name>/local.yaml
State (state.json, tasks/, HANDOFF.md, .claude/, workspaces/, events.log)~/.shelbi/projects/<name>/ (+ ~/.shelbi/events.log)~/.shelbi/projects/<name>/ (+ ~/.shelbi/events.log)
DiscoveryReverse-lookup: cwd matched against every registered project's local work_dirWalk up from cwd for <dir>/.shelbi/project.yaml, then require a matching local.yaml
Shared with clones?No: every teammate reruns shelbi initYes: everything under <repo>/.shelbi/ (minus the gitignored state pieces) travels with git clone

The full field-by-field bucket lists are in shelbi_core::model under SHARED_PROJECT_FIELDS and LOCAL_PROJECT_FIELDS; a 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

The project's shared config is committed at <repo>/.shelbi/project.yaml and everyone who clones the repo gets the same workflows, agent prompts, and runner settings. Each teammate still has their own machines and workspace pool. Those live in a per-user local.yaml that never gets committed.

On-disk layout

<repo>/
  .shelbi/
    project.yaml              # ← shared: name, default_branch, orchestrator,
                              #   agent_runners, zen, heartbeat, git, github_url,
                              #   workspace_permissions_mode, 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: repo, machines, workspaces,
                              #   editor
      state.json              # ← state — never committed
      tasks/
      HANDOFF.md
      .claude/
      workspaces/
  events.log                  # ← state — cross-project

The 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.

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 shared config, Shelbi reads the name: field out of it and checks that a matching ~/.shelbi/projects/<name>/local.yaml exists on your machine. If it doesn'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).

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.yaml

Every 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. Rename the alias later with shelbi project rename if you want something friendlier.

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:

  1. Splits ~/.shelbi/projects/<name>.yaml into a committed <repo>/.shelbi/project.yaml (shared half, with config_mode: in-repo) and a per-machine ~/.shelbi/projects/<name>/local.yaml.
  2. Moves workflows/, agents/, and workspace-settings.json.template from ~/.shelbi/projects/<name>/ into <repo>/.shelbi/. The mover prefers fs::rename; on cross-filesystem failure it copies then removes.
  3. Prints the .gitignore snippet above and offers to auto-append it (interactively), or applies it non-interactively with --yes.
  4. Deletes the original ~/.shelbi/projects/<name>.yaml, 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-run

Prints 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 myapp

Interactive: 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 snippet

Everything 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:

  1. git revert the migration commit, which restores <repo>/.shelbi/ to its pre-migration state.
  2. Move ~/.shelbi/projects/<name>/local.yaml back to ~/.shelbi/projects/<name>.yaml (the global-mode YAML shape).

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: /Users/you/.shelbi/projects/shelbi.yaml
✓ wrote workspace settings template: /Users/you/.shelbi/projects/shelbi/workspace-settings.json.template
✓ created agent workspace: agents/orchestrator/
✓ created agent workspace: agents/developer/
✓ wrote project statuses: /Users/you/.shelbi/projects/shelbi/workflows/statuses.yaml
 
✓ picked up `shelbi` from /Users/you/work/shelbi/.shelbi/project.yaml.
 
next:
  1. add machines/workspaces to ~/.shelbi/projects/shelbi.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 registers a matching entry in your local ~/.shelbi/projects/. 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: /Users/you/.shelbi/projects/shelbi-2.yaml

✓ picked up `shelbi` from /Users/you/work/shelbi/.shelbi/project.yaml as local alias `shelbi-2`.
  Tip: `shelbi project rename` can retitle the local alias to something friendlier.
 
next:
  1. add machines/workspaces to ~/.shelbi/projects/shelbi-2.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, tasks, 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-task; 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 task, 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 --help and shelbi project migrate-to-in-repo --help — the full flag reference for the two commands this page covers.