# Configuration

Workspace settings and at least one enabled agent are required; everything else has a default.

| Key                                  | What                                                                    |
| ------------------------------------ | ----------------------------------------------------------------------- |
| `workspace.projectDir`               | Parent dir for cloned repos and the default task worktree root.         |
| `workspace.worktreeDir`              | Optional parent dir for task worktrees.                                 |
| `workspace.knownRepositories`        | Repos searched for in task descriptions to infer where work belongs.    |
| `workspace.useTaskTitleForPanelName` | Opt-in: title cmux panels with the ticket title instead of the task id. |
| `agents.definitions`                 | Enabled agent set. Built-in presets can be enabled with `{}`.           |

The branch prefix (`<prefix>-<TASK>`) defaults to `os.userInfo().username`; override it with `git.branchPrefix` (see the full reference below). Changing it only affects newly created worktrees; existing local branches keep their original names until cleaned up. Groundcrew picks up every issue assigned to your API key's viewer that carries an `agent-*` label across every visible team and project, governed by a single `orchestrator.maximumInProgress` budget.

## Repository Layout

Groundcrew never clones repositories for you. `crew init --repo OWNER/REPO`
prints the clone command to run. If you are cloning manually, clone each string
`workspace.knownRepositories` entry into `workspace.projectDir` using the same
relative path the config uses.

```bash
PROJECT_DIR="$HOME/dev"
mkdir -p "$PROJECT_DIR/OWNER"
git clone git@github.com:OWNER/REPO.git "$PROJECT_DIR/OWNER/REPO"
```

Bare-name entries have no owner, so pick the remote URL yourself and clone to
`$PROJECT_DIR/<name>`. To keep a repo clone somewhere else, use
`{ name: "OWNER/REPO", projectDirOverride: "~/other" }` and clone it under that
parent dir.

By default, task worktrees are created beside the repos under
`workspace.projectDir`. Set `workspace.worktreeDir` to collect worktrees under a
separate root, regardless of where each source repo clone lives. Changing
`workspace.worktreeDir` only affects worktrees discovered under the new root.
Clean up existing worktrees before switching it, or temporarily unset
`worktreeDir` when you need `crew cleanup` to find worktrees created beside the
repos.

## Scripted Worktrees (Sparse Checkouts)

A `workspace.knownRepositories` entry can be an **object** instead of a string when you want groundcrew to provision the worktree with a custom command — for example a sparse checkout via `graft` — instead of `git worktree add`:

```ts
workspace: {
  projectDir: "~/dev/groundcrew",
  knownRepositories: [
    "your-org/your-repo",
    {
      name: "billing",
      provision: {
        create: "graft new ${branch} billing --from ${baseRef} --dir ${dir}",
        remove: "graft rm ${branch} -f",
      },
      workdir: "services/billing",
    },
  ],
},
```

- **`name`** is a logical name — it is the token matched in task descriptions and the basename of the per-task worktree directory. The physical clone is the command's concern, so several scripted entries can share one underlying clone.
- **`provision`** marks the entry as scripted. **`provision.create`** runs in place of `git worktree add`; **`provision.remove`** runs in place of `git worktree remove`. Both are required — a `provision` block missing either is rejected at config load. `projectDirOverride` (the per-repo source-directory override for native entries) cannot be combined with `provision`; a scripted entry has no groundcrew-managed clone, so it is rejected at config load.
- Both templates run on the host via `sh -c` with the working directory set to the worktree root (`worktreeDir`, defaulting to `projectDir`). They interpolate `${branch}`, `${dir}`, `${baseRef}` (`<remote>/<defaultBranch>`), `${repo}`, and `${task}`; each value is shell-quoted.
- **`workdir`** (optional) is a relative subdirectory of the worktree. When set, the agent's working directory, the `prepareWorktree` hook, and the `.groundcrew/config.json` lookup all re-root to `<worktree>/<workdir>` — use it when a sparse checkout materializes a monorepo whose project lives in a subdirectory (e.g. `uv sync` must run there). The worktree root is unchanged: it is still what create/remove/list operate on, and a sandboxed agent keeps full read/write access to the whole checkout. `workdir` must be relative with no `..` segments; if it is missing after checkout, `crew` fails fast.
- **`hooks`** (optional) sets per-repo operator hooks for this entry, reusing the same `HookCommands` shape as `defaults.hooks` and the in-repo `.groundcrew/config.json`. Today that is `hooks.prepareWorktree`. It slots between the repo-committed file (wins) and `defaults.hooks` (fallback) — see [Default Hooks](#default-hooks). You must use the object form to attach `hooks` (string entries only carry the repo name), but unlike `provision`, adding `hooks` does not make the entry scripted; the worktree is still created natively.
- A dirty scripted worktree is still protected from data loss: `crew cleanup` refuses to run `remove` unless you pass `--force`. Orphan and branch cleanup are delegated to your `remove` template, since groundcrew does not track the scripted clone.

Set `graft` (or whatever tool your templates call) up once, outside groundcrew:

```bash
graft repo add ~/dev/owner/monorepo
graft alias add billing services/billing libs/common
```

`crew doctor` does not parse or validate provisioner templates; if your setup is more than a simple command, put it in a wrapper script and call that from `provision.create` / `provision.remove`.

## Config Discovery

Resolution order:

1. `GROUNDCREW_CONFIG`
2. cosmiconfig project-walk from cwd
3. `${XDG_CONFIG_HOME:-$HOME/.config}/groundcrew/crew.config.ts`

The project walk checks:

- `crew.config.{ts,mjs,js,json}`
- `.crewrc{,.json,.ts}`
- `.config/crew.config.{ts,json}`
- `.config/crewrc{,.json}`

The "Loaded config from ..." line at startup tells you which config won.

## Agent Label Routing

- `agent-claude`, `agent-codex`, `agent-pi`, or `agent-<name>` routes to that enabled launch profile.
- `agent-any` routes to the agent with the most session headroom, after skipping agents over their session limit or weekly paced budget.
- Unknown `agent-<name>` falls back to `agents.default`.
- A built-in `agent-<name>` label whose agent is not enabled falls back to `agents.default` with a warning.
- No `agent-*` label is ignored by `crew run`. Dispatch on demand with `crew start <TASK>`, which falls back to `agents.default`.
- Todo tasks blocked by non-terminal blockers are skipped until their blockers reach a terminal status.

Agent names are launch profiles, not just vendor names. To choose a model per
task, define model-specific profiles and route tasks to them:

```ts
export default {
  agents: {
    default: "claude-fable",
    definitions: {
      "claude-fable": {
        cmd: "claude --model claude-fable-5 --permission-mode auto",
        color: "#C15F3C",
        usage: { codexbar: { provider: "claude" } },
      },
      "claude-opus": {
        cmd: "claude --model claude-opus-4-8 --permission-mode auto",
        color: "#8A4FFF",
        usage: { codexbar: { provider: "claude" } },
      },
    },
  },
};
```

Use the same profile name in every source: Linear label `agent-claude-fable`,
todo.txt token `agent:claude-fable`, shell JSON `"agent": "claude-fable"`,
or `crew task create ... --agent claude-fable`.

Status classification uses Linear's default status names `In Progress` and `In Review` to disambiguate multiple `started` workflow states. Statuses that do not match those names fall back to Linear's workflow `state.type` (`unstarted`, `started`, `completed`, `canceled`, `duplicate`), so broad lifecycle classification still works without configuration. Parent issues with children are ignored; sub-issues are the work items.

If your Linear workflow uses different names, explicitly declare the built-in Linear source and override only the names you need:

```ts
export default {
  sources: [
    {
      kind: "linear",
      statuses: {
        inProgress: ["Doing"],
        inReview: ["Code Review"],
      },
    },
  ],
};
```

Configured names replace the default for that status; omitted fields keep their defaults. Matching is case-insensitive and trims surrounding whitespace.

Linear is implicit-on, but you can turn it off entirely with the opt-out sentinel `{ kind: "linear", enabled: false }`. This suppresses the built-in Linear source so no adapter is constructed and no API key is required — useful for shell-only setups, where a failing Linear probe would otherwise mark the whole queue unavailable:

```ts
export default {
  sources: [
    { kind: "linear", enabled: false },
    {
      kind: "shell",
      name: "plans",
      commands: {
        fetch: "~/.config/groundcrew/plans-fetch.sh",
      },
    },
  ],
};
```

## Enabling Agent Presets

Groundcrew ships built-in presets for `claude`, `codex`, `cursor` (Cursor's composer-2.5), `cursor-grok` (grok-4.5), and `pi`, but agents are not enabled by default. List the agents you want in `agents.definitions`:

```ts
export default {
  agents: {
    default: "claude",
    definitions: {
      claude: {},
    },
  },
};
```

To enable several shipped presets:

```ts
export default {
  agents: {
    default: "claude",
    definitions: {
      claude: {},
      codex: {},
      pi: {},
    },
  },
};
```

Rules:

- `agents.definitions` is the enabled launch profile set; `crew doctor` only probes listed profiles.
- Built-in entries can be `{}` or partial overrides such as `{ cmd: "..." }`.
- The Pi preset runs `pi --approve`, which prevents project trust from blocking an unattended launch. If you override its `cmd`, retain either `--approve` (load project-local Pi resources) or `--no-approve` (ignore them).
- Pi needs authentication only for the selected model's provider, not every supported provider. Authenticate before unattended use as described in [Pi Provider Authentication](./credentials.md#pi-provider-authentication).
- Custom launch profile names must provide `cmd` and `color`.
- `agents.default` must point at an enabled agent.
- Legacy agent entries like `codex: { disabled: true }` are rejected with migration guidance; remove unwanted entries instead.

## Resuming the agent's conversation

`crew resume` reopens the agent's previous conversation in the worktree — **no config required** for the built-in agents. The shipped `claude`, `codex`, `cursor`, `cursor-grok`, and `pi` presets carry a `resumeArgs` default (`--continue`, `resume --last`, `--continue`, `--continue`, `--continue`), which groundcrew appends to the agent's command on resume.

How it works:

- The first launch is unchanged — the agent creates its own conversation in the worktree.
- `crew resume <TASK>` appends `resumeArgs` to the agent's `cmd`, so the agent reopens that conversation (`claude --continue`, `codex … resume --last`). groundcrew stores no session id — each task has its own worktree, so the agent's "resume the latest conversation in this directory" primitive is enough.
- `crew resume --new <TASK>` ignores `resumeArgs` and cold-starts a fresh conversation.
- `resumeArgs` is appended after `cmd`, so the agent binary (and its safehouse profile) and the trailing prompt argument are unaffected. It assumes one conversation per worktree — if you manually open extra sessions in that directory, "latest" may not be the one you expect.

Set `resumeArgs` yourself to enable this for a **custom** agent, or to override/replace the preset default:

```ts
agents: {
  definitions: {
    "claude-opus": { cmd: "claude --model claude-opus-4-8 --permission-mode auto", color: "#8A4FFF", resumeArgs: "--continue" },
  },
},
```

## Prompt Customization

Groundcrew ships one agent-agnostic unattended prompt by default. It tells the agent to make reasonable assumptions, follow repository instructions, run documented verification, review its diff, open a PR when GitHub/`gh` is available, and include a workspace continuation hint when known.

This prompt describes how the agent works, not what it does. The task is the task description, which groundcrew passes through unchanged. Keep source-specific instructions, acceptance criteria, links, and output requirements in the task body. Override `prompts.initial` only to change the execution contract for every dispatched task — team-wide review rules, required verification, local tool conventions — not to encode behavior for a single task type.

For a personal workflow, keep the prompt next to your local config and load it with `readFileSync`:

```ts
import { readFileSync } from "node:fs";

export default {
  prompts: {
    initial: readFileSync(new URL("./initial-prompt.md", import.meta.url), "utf8"),
  },
};
```

This keeps package defaults portable while letting your private config reference team-specific statuses, tools, plugins, or review loops.

### Loading the prompt from a file (`prompts.promptFile`)

`readFileSync` works for `.ts` configs but not for `crew.config.json`, which has no way to reference an external file. Set `prompts.promptFile` instead and groundcrew reads the file's contents into the initial prompt at load time:

```json
{
  "prompts": {
    "promptFile": "prompt-initial.md"
  }
}
```

- The path is resolved **relative to the config file's directory** (matching the `.ts` `import.meta.url` behavior); a leading `~` is expanded and absolute paths are used as-is.
- `prompts.initial` and `prompts.promptFile` are **mutually exclusive** — setting both is a hard error. Set neither to keep the built-in default.
- Placeholder validation runs on the loaded file contents, so an unknown `{{placeholder}}` in the file fails the same way it would inline.

## Default Hooks

`hooks.prepareWorktree` resolves through three layers, highest priority first:

1. **`.groundcrew/config.json` `hooks.prepareWorktree`** — committed in the repo, authored by its maintainers. Wins.
2. **`workspace.knownRepositories[].hooks.prepareWorktree`** — per-repo, set by the operator in `crew.config.ts`.
3. **`defaults.hooks.prepareWorktree`** — global operator fallback in `crew.config.ts`.

Repo-local `.groundcrew/config.json` is the preferred place for the hook. To
provide a global fallback for repos that do not define one, set
`defaults.hooks.prepareWorktree`:

```ts
export default {
  defaults: {
    hooks: {
      prepareWorktree: "test ! -f package-lock.json || npm ci",
    },
  },
};
```

For a repo you can't (or don't want to) commit a `.groundcrew/config.json` into
— a third-party repo, say — set the hook for just that repo on its
`knownRepositories` entry. It beats `defaults.hooks` but still yields to a
committed `.groundcrew/config.json`:

```ts
export default {
  workspace: {
    projectDir: "~/dev",
    knownRepositories: [
      "your-org/your-repo",
      {
        name: "other-org/their-repo",
        hooks: { prepareWorktree: "uv sync --dev --frozen" },
      },
    ],
  },
};
```

The per-repo `hooks` reuse the same container and `prepareWorktree` contract as
the other two layers. This is the additive hook seam: `hooks.*` runs _around_ the
worktree lifecycle, distinct from `provision.*`, which _replaces_ the git
worktree porcelain (`create`/`remove`).

See [Prepare Worktree Hooks](./setup-hooks.md) for the repo-local config shape
and hook contract.

## Full Reference

| Key                                                   | Default              | What it does                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| ----------------------------------------------------- | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `sources`                                             | `[]`                 | Additional pluggable task sources, dispatched alongside the built-in Linear adapter. Built-in kinds: `shell`, `linear`. Declare `{ kind: "linear", team: "ENG", statuses: { ... } }` to configure Linear task creation's default team and/or override Linear status names used for `in-progress` / `in-review` disambiguation. Disable the implicit Linear source with `{ kind: "linear", enabled: false }` (no API key required) — useful for shell-only setups.                                                |
| `sources[].sandboxWritePaths`                         | optional             | Shell sources only. Local directories that the `safehouse` sandbox runner opens for read and write while an agent works a task from that source. Use this when a shell-backed task store lives outside the worktree, such as `~/plans`. Paths may be absolute, relative to the agent working directory, or `~`-prefixed. Not supported by the `sdx` runner.                                                                                                                                                      |
| `git.remote`                                          | `"origin"`           | Remote used for `fetch` and as the worktree base ref.                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| `git.defaultBranch`                                   | `"main"`             | Branch fetched from `git.remote` and used as the worktree base.                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| `git.branchPrefix`                                    | OS username          | Prefix groundcrew puts before the task id when naming a worktree branch (`<branchPrefix>-<task>`). Must be a slash-free slug of letters, digits, `.`, `_`, or `-`. Defaults to the OS account username. Changing it only affects newly created worktrees; existing local branches keep their original names until cleaned up. Prefer a per-user config for personal prefixes — a committed `git.branchPrefix` gives every contributor the same branch prefix.                                                    |
| `workspace.projectDir`                                | **required**         | Parent dir for cloned repos and the default task worktree root.                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| `workspace.worktreeDir`                               | optional             | Parent dir for task worktrees. When unset, worktrees are created under `workspace.projectDir`. Changing this only affects worktrees discovered under the new root; clean up existing worktrees before switching it, or temporarily unset it when you need `crew cleanup` to find old worktrees.                                                                                                                                                                                                                  |
| `workspace.knownRepositories`                         | **required**         | Repos searched for in task descriptions to infer where work belongs. Entries can be strings under `workspace.projectDir` or `{ name, projectDirOverride }` objects when a repo clone lives under a different parent dir. A task labeled for groundcrew (`agent-*`) fails fast when no known repo appears; unlabeled tasks are ignored.                                                                                                                                                                           |
| `workspace.useTaskTitleForPanelName`                  | `false`              | When `true`, cmux workspace panels are titled with the task's ticket title instead of the task id; only a literal `true` opts in. Identity still keys on the task id (stored in the cmux description marker), so renaming a panel is safe. Only cmux paints a panel title — tmux and zellij ignore it.                                                                                                                                                                                                           |
| `workspace.knownRepositories[].hooks.prepareWorktree` | optional             | Per-repo operator hook. Used when the repo has no committed `.groundcrew/config.json` `hooks.prepareWorktree`; beats `defaults.hooks.prepareWorktree`. Set it on a repo's object entry for repos you can't or don't want to commit groundcrew files into. Reuses the same `hooks` container and hook contract as the other two layers.                                                                                                                                                                           |
| `defaults.hooks.prepareWorktree`                      | optional             | Global fallback repo-preparation command used only when neither the worktree's `.groundcrew/config.json` `hooks.prepareWorktree` nor the per-repo `knownRepositories[].hooks.prepareWorktree` is set. The hook runs after worktree creation and before the agent starts. Repo-local config and the per-repo layer both win.                                                                                                                                                                                      |
| `orchestrator.maximumInProgress`                      | `4`                  | Cap on in-progress tasks at once for this `crew` instance.                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| `orchestrator.pollIntervalMilliseconds`               | `120_000`            | Poll interval in `--watch` mode.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| `orchestrator.sessionLimitPercentage`                 | `85`                 | Number in `(0, 100]`. An agent whose codexbar session window exceeds this percentage is skipped that tick. Agents are also skipped when codexbar reports weekly usage over the current weekly paced budget.                                                                                                                                                                                                                                                                                                      |
| `agents.default`                                      | `"claude"`           | Tiebreak for `agent-any` resolution and fallback for explicit but unknown `agent-*` labels. Also used by `crew start <TASK>` for unlabeled tasks. `crew run` ignores unlabeled tasks and does not apply this default. Must exist in `agents.definitions`. If you enable only `codex`, set `default: "codex"`.                                                                                                                                                                                                    |
| `agents.definitions`                                  | **required**         | Enabled launch profile set. Built-in keys (`claude`, `codex`, `cursor`, `cursor-grok`, `pi`) can use `{}` to opt into the shipped preset. Custom profile names must provide `cmd` and `color`; use custom profiles such as `claude-fable` and `claude-opus` to select model-specific commands per task.                                                                                                                                                                                                          |
| `agents.definitions.<name>.cmd`                       | preset for built-ins | Shell command launched for the agent. Required for custom profiles. Runs in the worktree through the resolved `local.runner`. `{{worktree}}` is replaced before launch; `{{sandbox}}` expands to the sbx sandbox name under the sdx runner and an empty string otherwise.                                                                                                                                                                                                                                        |
| `agents.definitions.<name>.color`                     | preset for built-ins | Color for the workspace status pill (cmux only; tmux and zellij silently drop it). Required for custom profiles.                                                                                                                                                                                                                                                                                                                                                                                                 |
| `agents.definitions.<name>.usage`                     | preset for built-ins | If set, codexbar usage is fetched for this agent and gated by `sessionLimitPercentage` plus the weekly paced budget when codexbar exposes a weekly window. When `usage.codexbar.source` is omitted, groundcrew uses `oauth` for Codex on macOS, `cli` for Claude on macOS (its OAuth probe breaks whenever the token rotates, and codexbar's CLI cannot refresh it), `auto` for other macOS providers, and `cli` elsewhere. Set to `{ disabled: true }` to disable usage gating while keeping the agent enabled. |
| `agents.definitions.<name>.sandbox`                   | optional             | Docker Sandboxes binding for the agent. Required at launch when `local.runner` resolves to `sdx`. Field: `agent` (required sbx agent name). Groundcrew assumes the `groundcrew-<agent>` sandbox already exists.                                                                                                                                                                                                                                                                                                  |
| `agents.definitions.<name>.preLaunch`                 | optional             | Host-only shell snippet run before the agent exec and outside Safehouse/sdx. Exports survive into the launch shell; under the default `safehouse` runner they are only forwarded to the agent when listed via `preLaunchEnv` or when `cmd` includes its own `safehouse --env-pass=NAMES`. `{{worktree}}` is substituted. A non-zero exit aborts launch. Not supported when `local.runner` resolves to `sdx` in v1.                                                                                               |
| `agents.definitions.<name>.preLaunchEnv`              | optional             | Companion to `preLaunch`: list of env var names to append to groundcrew's Safehouse `--env-pass=` flag, so `preLaunch` exports reach the agent without overriding `cmd`. Each entry must match `[A-Za-z_][A-Za-z0-9_]*`. `none` clears those names before `preLaunch`; its exports then inherit as-is. An empty array is a uniform no-op under every runner; non-empty lists reject a Safehouse-prefixed `cmd`, `sdx` runners, or Groundcrew-managed worker env vars.                                            |
| `agents.definitions.<name>.resumeArgs`                | preset default       | Shell args appended to `cmd` on `crew resume` so the agent [reopens its previous conversation](#resuming-the-agents-conversation) in the worktree. Defaults to `"--continue"` (claude, cursor, cursor-grok, pi) and `"resume --last"` (codex) on the built-in presets; set it for custom agents or to override. `crew resume --new` ignores it and cold-starts. No session id is stored.                                                                                                                         |
| `prompts.initial`                                     | unattended template  | First message sent to the agent: the execution wrapper around each task. The task description is the task-specific prompt. Placeholders: `{{task}}`, `{{worktree}}`, `{{title}}`, `{{description}}`. Override only to change the execution contract for every task, such as team-wide review rules or tool conventions. Mutually exclusive with `prompts.promptFile`.                                                                                                                                            |
| `prompts.promptFile`                                  | optional             | Path to a UTF-8 file whose contents become `prompts.initial`, read at load time. Resolved relative to the config file's directory; `~` is expanded and absolute paths are used as-is. The JSON-friendly alternative to inlining a large prompt or `readFileSync`. Mutually exclusive with `prompts.initial`.                                                                                                                                                                                                     |
| `workspaceKind`                                       | `"auto"`             | Terminal session manager. `"auto"` picks `cmux` when on PATH, else `tmux`. Set to `"cmux"`, `"tmux"`, or `"zellij"` to fail loudly when the chosen backend is missing.                                                                                                                                                                                                                                                                                                                                           |
| `local.runner`                                        | `"auto"`             | Local isolation backend. `"auto"` uses `safehouse` on macOS and `sdx` on Linux/WSL. Explicit: `"safehouse"`, `"sdx"`, `"none"`. `"none"` is never picked implicitly.                                                                                                                                                                                                                                                                                                                                             |
| `local.networkEgress`                                 | `"allowlisted"`      | Network egress posture. With `local.runner: "safehouse"`, `"allowlisted"` wraps with Clearance and `"open"` keeps the Safehouse filesystem sandbox while opening network egress. Ignored by `sdx` and `none`.                                                                                                                                                                                                                                                                                                    |
| `local.readOnlyDirs`                                  | `~/.config/tfenv`    | Host directories re-opened read-only inside the Safehouse sandbox, for toolchains the profile masks but does not re-open. `~` is expanded. Defaults to tfenv's config root so `terraform`/`tfenv` work in the sandbox; set your own list to add or replace entries. Ignored by `sdx` and `none`.                                                                                                                                                                                                                 |
| `logging.file`                                        | XDG state path       | Append-mode log file. `log()` / `logEvent()` tee here in addition to stdout. Defaults to `${XDG_STATE_HOME:-$HOME/.local/state}/groundcrew/groundcrew.log`.                                                                                                                                                                                                                                                                                                                                                      |
