ADR 0058: discern start — spawn a worktree from the main checkout, and a status guardrail that points at it
Vocabulary amendment: Current pointers use
finish→done,graduate→accept,integrate→update; the decision and reasoning are unchanged.
Status: accepted. Completes the entry of the worktree lifecycle alongside ADR 0011 (the workflow), respects the sibling-placement convention of ADR 0052, and extends the location-aware status verb of ADR 0033. The location-based tool visibility of §2 (the requiresLocation hiding) is later retired by ADR 0062 — the defensive refusals it describes remain; only the hiding from tools/list goes.
Context
§discern isolates every line of work in its own linked worktree, and wraps the transitions of a worktree's life in deterministic verbs: update brings main in, and accept hands the branch back. But the first transition — get into a worktree at all — had a hole. The Claude Code WorktreeCreate hook creates the worktree, and that hook fires only when an orchestrator drives it. An agent invoked directly on the main checkout had no first-class way to spin up its own isolated worktree.
With no affordance, agents improvise badly. The motivating incident: an agent on the trunk ran discern status, saw an idle, up-to-date worktree that belonged to another agent (one that simply hadn't started working yet), and moved in to work there — squatting in someone else's line of work was its only option. The guidance already said "never start work in a worktree you didn't create", but a prohibition without an alternative just leaves the agent stuck.
Two gaps, then: no verb to create a worktree to inhabit, and no nudge that tells an agent on the trunk to use it.
Decision
§1. discern start — a first-class lifecycle verb
§Add discern start (the tool discern_start): run from the main checkout, it mints a fresh worktree, creates it on its own agent/<id> branch at the configured sibling location, runs its first-time setup, and reports where it landed. It is a naming-convention sibling of done / accept / update, and follows the same plan/apply shape: a startResult core returns a DiscernResult the CLI --json, the human renderer, and the MCP tool all render; --dry-run previews and touches nothing.
Minting an id. discern had no id generator — Claude Code's hook supplies the worktree name, and every other identity value derives from it (identity.ts). discern start mints its own through a new generateWorktreeId: a readable <adjective>-<noun>-<hex> id in the spirit of the existing names, sanitized to the same rules a DISCERN_WORKTREE_ID override obeys. The random hex tail makes it unique. Before using the id, start verifies the derived branch and directory are both free, so it always creates a worktree and never adopts one.
The engine/feature split holds. Where a worktree lands is the feature layer's resolveWorktreeRoot, never the stack-neutral engine. So startResult takes the placement root as a parameter; the dispatcher and the MCP server resolve it and pass it in — exactly as the worktree prune wiring already does. One engine core, createAndSetupWorktree, holds the "create the worktree, then set it up" sequence, shared by discern start and the WorktreeCreate hook so they can't drift.
2. Return a path; re-root yourself
§The MCP server has a single root checkout and cannot relocate the agent's session to a worktree it just created. So discern_start does not pretend to move: it returns the new worktree's absolute path in data.path plus a hint, and its description tells the agent it must re-root — start a session rooted there (or cd in) and continue from inside it, never back in the main checkout.
The inverse risk is an agent already inside a worktree calling discern_start and creating a pointless sibling. Two guards: the tool sets requiresLocation: "main", so a server rooted in a worktree does not register it (it is absent from tools/list and from the instructions); and startResult refuses defensively via assertNotInWorktree, mapping to the same precondition_failed envelope accept / update use, in case it is ever invoked anyway.
The same field gates the mirror image. discern_accept and discern_update act on the current worktree and can't run on the trunk, so they take requiresLocation: "worktree" and are hidden from a main-rooted server. The listing is therefore symmetric: a main-rooted server offers discern_start (and not accept/update); a worktree-rooted one offers accept/update (and not start). The CLI has no fixed root to tailor against — it is invoked fresh at the user's cwd — so its equivalent is the same verbs refusing in the wrong location with a clear message, which they already do.
3. A status guardrail on the trunk
§discern status, when rooted in the main checkout with worktrees enabled, now leads its next-steps with a loud guardrail: not an isolated worktree — run discern start and move into your own, never adopt an existing idle one. It rides in hints[] (the --json / MCP channel), placed before the fleet-ownership rule so the constructive action comes first.
The main checkout (a working-copy location) and the trunk branch are independent axes — you can be in the main checkout on a non-trunk branch (a leftover discern-setup branch, a PR checked out directly instead of through a worktree). The literal START_HERE_HINT ("you're on the trunk") fires only when the checked-out branch actually IS the trunk; offTrunkStartHereHint gives the same advice, naming the real branch, otherwise — so the hint never claims a branch identity status didn't verify.
It is agent-facing only. A human running discern status from the main checkout is supervising their fleet, not starting work — so the interactive human renderer filters it out, exactly as it does the fleet-ownership rule. The human-vs-agent split is structural (the rendered text vs. the hints[] channel), not a heuristic, so there is no false-positive nagging of a person at the CLI.
Consequences
§- An agent that finds itself on the trunk has an obvious, first-class way into its own isolated worktree:
discern statuspoints atdiscern start, which creates the worktree and tells it where to go. The squat-in-another's failure mode no longer has "no other option" behind it. discern startonly creates a worktree to inhabit. It never hops into, adopts, or prunes an existing one — that boundary keeps it complementary to the unexposedworktreecommand group and toaccept/update, which operate on the current worktree.- discern now owns a worktree-id generator. Only
discern startuses it; every other path still derives identity from an externally supplied name, so the frozen identity derivation (identity.ts) stands unchanged. - The MCP surface is now location-aware: a single
requiresLocationfield hides any tool whose precondition the server's own root already fails, so the list is tailored to where the server runs (discern_starton the trunk;discern_accept/discern_updatein a worktree). The mechanism is reusable for any future location-specific verb.
See also
§- ADR 0011 — the worktree workflow
startcompletes the entry to. - ADR 0052 — the sibling placement convention
startresolves through the feature layer. - ADR 0055 —
update, the middle transition;startis the same plan/apply, lifecycle-verb shape applied to the entry. - ADR 0033 — the location-aware status verb the trunk guardrail extends.
- ADR 0045 — the MCP surface the return-path-and-re-root wrinkle lives on.