Configuration¶
mothership.yaml¶
workspace: my-platform
# Optional: wraps all task execution with a secret manager
env_runner: "dotenvx run --"
# Optional: branch naming pattern ({slug} is replaced)
branch_pattern: "feat/{slug}"
repos:
shared:
path: ./shared
type: library # "library" or "service"
depends_on: []
env_runner: "op run --" # per-repo override
tasks:
test: unit # override canonical task name
auth-service:
path: ./auth-service
type: service
depends_on: [shared]
Secret management (env_runner)¶
Mothership doesn't manage secrets. It delegates to your secret manager via env_runner:
| Tool | Config value |
|---|---|
| dotenvx | dotenvx run -- |
| Doppler | doppler run -- |
| 1Password CLI | op run -- |
| Infisical | infisical run -- |
| None | omit env_runner |
Monorepo support (git_root)¶
For monorepos where multiple services share one git repo, use git_root to declare subdirectory services:
repos:
backend:
path: .
type: service
web:
path: web # relative — required; interpreted against backend's worktree
type: service
git_root: backend # backend is auto-ordered before web (no depends_on needed)
Rules:
- git_root must reference another repo in the workspace.
- The referenced repo cannot itself have git_root set (no chaining).
- A git_root child's path must be relative and must not contain ..; an
absolute path would resolve to the source checkout instead of the task worktree
and is rejected at config load.
- A git_root parent is auto-ordered before its children — it is materialized
first automatically, so you do NOT need to hand-add depends_on: [parent].
(Declaring parent depends_on child is the opposite order and is rejected as a
dependency cycle.)
- The subdirectory must exist and contain a go-task file (Taskfile.yml,
Taskfile.yaml, or another resolution-set spelling).
- Subdirectory services still have their own depends_on, tags, tasks, and start_mode.
Service start modes (start_mode)¶
For long-running services, set start_mode: background:
repos:
infra:
path: ./infra
type: service
start_mode: background # mship run launches and moves on
backend:
path: ./backend
type: service
start_mode: background
depends_on: [infra]
With start_mode: background, mship run launches the service and continues to the next dependency tier without waiting for exit. Background services keep running until Ctrl-C propagates SIGINT through go-task to their child processes. start_mode only affects mship run. Tests and logs always run foreground.
Healthchecks¶
For services that need time to become ready, declare a healthcheck. mship run waits for the healthcheck to pass before starting dependent services.
repos:
infra:
path: ./infra
type: service
start_mode: background
healthcheck:
tcp: "127.0.0.1:8001" # wait for port to accept connections
timeout: 30s # optional, default 30s
retry_interval: 500ms # optional, default 500ms
backend:
path: ./backend
type: service
start_mode: background
depends_on: [infra]
healthcheck:
http: "http://localhost:8000/health"
web:
path: ./web
type: service
start_mode: background
depends_on: [backend]
healthcheck:
sleep: 3s # unconditional wait
custom:
path: ./custom
type: service
start_mode: background
healthcheck:
task: wait-for-custom # runs `task wait-for-custom`; 0 exit = ready
Probe types: tcp, http, sleep, task (any one per healthcheck). Exactly one probe per healthcheck. If the probe doesn't succeed within timeout, the service is treated as failed and mship run exits non-zero. Healthchecks apply to mship run only.
Task name aliasing¶
If your Taskfile uses different task names than mothership's defaults (test, run, lint, setup), add a tasks: mapping:
repos:
my-app:
path: .
type: service
tasks:
run: dev # mship run → task dev
test: test:all # mship test → task test:all
lint: lint:all
setup: infra:start
mship doctor respects the mapping when checking for standard tasks.
Taskfile contract¶
Each repo needs a Taskfile.yml with standard task names. Mothership calls task <name> in each repo. Override names per repo in the tasks mapping. Default tasks: test, run, lint, logs, setup. Missing tasks are skipped gracefully.
Evidence storage (evidence_storage)¶
Storage mode for acceptance-criterion artifact evidence: published, local,
or encrypted. When unset it inherits spec_storage, mapping that field's
committed to published — a spec is committed into the workspace repo's
tree, whereas evidence is published onto an orphan branch in the repo whose PR
embeds it. Inheriting is what you want unless the cost profiles genuinely
differ — prose is bytes, screenshots are megabytes, so spec_storage:
committed with evidence_storage: local is a reasonable pairing.
Evidence may never be more exposed than its spec. Ordering the modes
published > encrypted > local, a configuration where evidence outranks
the spec is refused at config load: a plaintext screenshot beside an encrypted
spec discloses exactly what the encryption was protecting.
evidence_storage: local # inherits spec_storage when omitted
Embedding evidence in a pull-request body requires published — the other two
modes aren't fetchable by GitHub, so the PR names the artifact instead of
showing it. Embedding therefore means the screenshots are readable by anyone
who can read the repo the pull request targets.
Product assumptions (assumption_storage, assumption_gate)¶
The product-assumptions system (#444) makes agents explicitly disposition the assumptions a plan is built on. Two workspace-level fields govern it:
assumption_storage — where this workspace's L1 assumptions doc
(docs/product_assumptions.md) lives, mirroring spec_storage's modes:
committed (default, plaintext + pushed), local (plaintext but git-ignored),
or encrypted (Fernet ciphertext, unreadable without .mothership/spec-key).
Applied transparently by core/assumptions.py; an invalid value fails loud at
config load.
assumption_gate — the L4 plan→dev gate. off (default) enforces nothing:
merging the feature does not change existing behaviour, so the gate rolls out
dark. enforce additionally requires a fresh, fully-approved
PlanCheckResult before a feature WorkItem may transition plan → dev — every
assumption the checker flagged as not-covered (or left un-dispositioned) must
carry an explicit mship plan assumptions approve <axis> sign-off. Freshness is
plan-hash bound, so editing the plan after a check re-arms the gate.
assumption_storage: committed # committed | local | encrypted
assumption_gate: off # off | enforce
Workspace-level fields¶
Top-level keys on mothership.yaml (alongside workspace, env_runner, branch_pattern, audit, and repos, all covered above):
| Field | Meaning |
|---|---|
default_scope |
Default repo scope for mship spawn when --repos is omitted. "all" (default) uses every repo; "none" requires an explicit --repos; a list of repo names uses just those. (#74) |
spawn_confirm_threshold |
If set and a no---repos spawn's effective scope exceeds N repos, require confirmation (TTY) or --yes (non-TTY). Unset by default. (#74) |
spec_paths |
Workspace-relative paths searched for specs by mship phase dev's soft gate and mship view spec. Default: ["docs/superpowers/specs"]. (#113) |
require_approved_spec |
When true, mship phase dev hard-blocks plan → dev unless a bound, approved spec exists. Default: false. (MOS-151) |
docs_dir |
Workspace-relative directory where the bundled skills write plan docs; plans live at <docs_dir>/plans/. Default: "docs". Does not affect canonical specs (always specs/). |
default_remote |
Host-agnostic base URL prefix used to resolve a member's clone URL when its url is a bare name or omitted (member name appended). Enables mship bootstrap from a fresh clone. (MOS-180) |
relay |
Reverse-tunnel relay connection for mship serve --relay (see relay-hosting.md). |
run_hosts |
Logical run-host role names available to the workspace (only the names are committed here). A repo opts into one via its run_host; each machine maps the role to a concrete {url, token} in the gitignored .mothership/run-hosts.yaml. |
redact |
Extra mship export --redacted regex patterns, unioned with the built-in set. (MOS-102) |
lifecycle_hooks |
Declarative reactions to task / WorkItem / PR lifecycle transitions. Named lifecycle_hooks (not hooks) to disambiguate from the git commit/push hooks. (MOS-220) |
lifecycle_hooks_default_timeout |
Fallback per-hook timeout in seconds when a lifecycle_hooks: entry omits timeout. Default: 30. |
dispatch_models |
Per-mode model map for mship dispatch (implementer / reviewer / standalone). Precedence: --model flag > this map > built-in defaults; every built-in mode defaults to inherit. The sentinel means harness default behavior: the adapter omits its model selector. Explicit configured values are emitted verbatim and require a selector-capable harness adapter; an adapter without a selector rejects them rather than substituting another model. |
workspace: my-platform
default_scope: none # force explicit --repos on every spawn
spawn_confirm_threshold: 3 # confirm a no-flag spawn touching >3 repos
require_approved_spec: true # gate plan -> dev on an approved spec
docs_dir: docs # plans land in docs/plans/
default_remote: https://github.com/atomikpanda # bootstrap members from bare names
relay:
host: relay.example.com
ssh_port: 2222 # optional, default 2222
user: tunnel # optional; omit for the ssh default
run_hosts: [ios-sim-host, android-emu-host] # role names; connections live in .mothership/run-hosts.yaml
redact:
patterns:
- "sk-[A-Za-z0-9]{20,}" # bare string -> a "custom" pattern
- { name: internal-host, pattern: "corp\\.example\\.internal" }
lifecycle_hooks:
- on: pr.merged # a lifecycle event (phase.entered.*, workitem.phase.*, task.finished/closed, pr.merged/closed)
run: notify-slack # a go-task target or shell command
repo: backend # optional: run in this repo's worktree
timeout: 60 # optional: overrides lifecycle_hooks_default_timeout
# required: true # only valid on the pre-mutation events (phase.entered.* / workitem.phase.*)
Per-repo fields¶
Additional keys on each entry under repos: (alongside path, type, depends_on, env_runner, tasks, git_root, start_mode, and healthcheck, covered above):
| Field | Meaning |
|---|---|
not_applicable |
Canonical task names that don't apply to this repo (e.g. [lint]). Skipped without warning; cannot overlap with tasks. (#76) |
tags |
Free-form tags for filtering repos via --tag (mship test/run/build). |
symlink_dirs |
Directories symlinked into each task worktree from the source repo (re-synced by mship bind refresh). |
bind_files |
Files (relative paths or globs, must stay inside the repo) copied into each task worktree from the source repo (re-synced by mship bind refresh). |
base_branch |
Default PR base branch for this repo (overridden by mship finish --base / --base-map; falls back to the remote default branch). |
expected_branch |
Branch the repo's main checkout is expected to be on; drift audit flags unexpected_branch otherwise. |
url |
Explicit clone URL for mship bootstrap (overrides default_remote + name). Non-GitHub members set a full URL here. |
allow_dirty |
Allow a dirty worktree without failing the drift audit. Default: false. |
allow_extra_worktrees |
Allow extra worktrees on the repo without failing the drift audit. Default: false. |
capture |
UI-capture config — a platforms: list mship capture can target (--platform required when more than one). |
run_host |
Logical run-host role this repo uses for --remote execution (mship build/capture). Must name an entry in the workspace run_hosts: list. |
setup_inputs |
Manifests/lockfiles whose content decides whether a remote run re-runs task setup on the run host (glob patterns, matched inside the materialized worktree). Undeclared means setup runs on first materialization only — declaring them is what enables re-run-on-change. See remote-run.md. |
repos:
schemas:
path: ../schemas
type: library
not_applicable: [run] # this repo has no `run` task
tags: [generated]
base_branch: main
expected_branch: main
allow_dirty: false
allow_extra_worktrees: false
url: https://github.com/other-org/schemas # overrides default_remote for bootstrap
symlink_dirs: [.github] # symlinked into each worktree from source
bind_files: [".env.example", "config/*.toml"] # copied into each worktree
ground-control:
path: ground-control
type: service
run_host: android-emu-host # `mship build/capture --remote` targets this role
setup_inputs: [build.gradle, gradle/libs.versions.toml] # remote runs re-run `task setup` when these change
capture:
platforms: [android, ios] # `mship capture --platform android|ios`