ADR 0075: discern setup is a staged, consent-driven handshake
Amendments.
- Vocabulary: current spellings are
mapwhere it names the command, config, or tree (formerlydocs), and known/customjob(formerly gatecapability/ customcheck); the decisions below are unchanged.- ADR 0131 — no adoption:
verify's existing-docs adoption question retired — setup reassures that existing docs stay untouched and never offers adoption.- ADR 0320 — authority and continuation: each effectful phase now derives point-in-time write probes from its plan, phase state serves the bounded restart continuation, and the provider registry owns one exact activation check plus recovery.
verifyremains read-only and owner consent remains distinct from provider authority.- ADR 0322 — bounded closing sequence: setup provenance is an exact self-declared identifier or
unreported; unfinished registration defers restart; an unlanded completion stops at Proof and landing, while acceptance serves activation before optional improvement.- ADR 0365 — v1 split: the parent no longer forwards scaffold options to
begin.discern setupanddiscern setup beginare separate command and result contracts, with the read-only/effectful boundary visible in option ownership.
Status: accepted; revises ADR 0036 (unify init + bootstrap into one discern setup), ADR 0037 (incompleteness is observable), ADR 0044 (involve, don't gate), and ADR 0065 (setup keeps its promises). Builds on ADR 0069 (agent auto-detect), ADR 0052 (worktree sibling placement), and the provider registry of ADR 0031/ADR 0043.
Context
§ADR 0036 collapsed setup into one zero-config discern setup: the user installs the binary, tells their coding agent to "run discern," and a single command scaffolds the machinery, lays the doc skeletons, and prints the authoring brief. ADR 0037 made the unfinished state observable; ADR 0044 made the agent's conversation warm rather than gated; ADR 0065 made completion a proven gate. Each hardened the single-shot shape: one command that acts on first contact.
Running that shape end-to-end with agents — and humans — that had never heard of discern surfaced a cluster of defects the hardening could not reach, because they trace to the shape itself:
- First contact is addressed to the wrong reader. The install message says "tell your coding agent to run discern," but the first thing a curious human does with a new binary is run it themselves — before they will trust an agent with it. They are met with a frame addressed to "you, the agent," and a command that immediately checks out a branch and writes files. The one reader most in need of reassurance gets the least.
- A command that mutates on first contact cannot also earn consent. Handed the brief by the same call that did the scaffolding, an agent reads it two failure-prone ways. One stops and reports the work done without doing it (the echo-back ADR 0037 fights). The other freezes: the brief says it is "in control," but nothing told it when it had the user's go-ahead to act, so it asks "may I?" and waits — the consent-paralysis ADR 0044's tone could not dissolve, because the missing thing was a structural moment of consent, not a warmer sentence.
- Conflicts surface too late, or never. A repo with its own
docs/tree, or a hand-authoredCLAUDE.md, or an opinion about where worktrees belong, is the common case — not the exception. The single-shot command discovers these while writing (the dirty-tree refusal) or not at all (it folds an existingCLAUDE.mdintoguidance.mdbut never tells the human it happened, nor asks where their docs should live). The human is never given the facts to weigh before the scaffold lands.
- Progress is invisible and only prose-enforced. The brief is eight steps; weaker models skip some.
setup donecatches the outcome (markers must clear, the gate must be green), but nothing shows what is left along the way, so a skipped step is invisible until the final gate bounces it.
- No provenance. Setup is the single highest-leverage moment in a project's life with discern, and its quality is bounded by the model that did it. When a "discern is broken" report arrives, nothing records which model configured the project — the one fact that most often explains it.
The unifying root is that orientation and mutation were fused. A command that acts on first contact cannot also be the thing a curious human safely pokes, nor the thing an agent deliberately commits to. Splitting them is the lever.
Decision
§discern setup becomes an explicit, mostly read-only state machine whose one hard invariant is that nothing is written until begin. Four phases:
discern setup(and barediscern, pre-setup) → welcome. Read-only, and three-state: fresh (no config, in a git tree), in progress (beginran,[meta].bootstrappedstill false), and done (bootstrapped → today's grouped help). Human output is dual-addressed — a Humans block (what discern is, that it is safe, reversible, and touches nothing outside this folder) and an Agents block (you drive setup; rundiscern setup verify).--jsoncarriesphaseandnext_actionso a JSON-consuming agent is funneled the same way.
discern setup verify→ preflight. Read-only. It machine-checks the ground and produces the consent conversation from facts about this repo: git state (repo? clean tree?), an existingdocs/tree, a pre-existingCLAUDE.md/AGENTS.mdthatbeginwill fold intoguidance.md, the agents detected onPATH(ADR 0069), and the exact worktree sibling path that will be created (ADR 0052). It hands the agent a short checklist to confirm with the human — model capability and a fresh session, the worktree location, readiness for the branch checkout — then points atbegin.
discern setup begin→ the first mutation. This is today's scaffold (runSetup's clean-tree check, thediscern-setupbranch, the seed plan, the compile, the skeletons) plus the brief, now reached only here. It records provenance (a--modelself-declared model id, alongside the discern version it observed). The declarative--config/flag path (CI, presets) lands here too and skips the handshake.
discern setup done→ unchanged proof.refresh → doctor → done, then[meta].bootstrapped(ADR 0065). It now also emits the provider-aware reactivation handoff: thediscern_*MCP tools, session hooks, and project rules are wired but load at session start, so each configured agent is told its specific reactivation step (new session; for some, a one-time trust). Each step is DERIVED from that agent's required wiring (its livemcpserver, itshooks, its project rules, and itstrustgate) — not a hand-listed table — so a reuse-canonical agent that wired nothing is never told to restart, and a parity test ties the derivation to thePROVIDERSregistry (ADR 0051): a new vendor's setup-completion handoff follows from its declaration and cannot be silently forgotten.
The read-only→destructive boundary sits exactly at verify | begin.
The funnel is soft; the outcome stays hard-gated. Each phase's next_action points at the next, but no phase hard-refuses to run before its predecessor — there is no pre-begin state to gate on (on a fresh repo there is no discern.toml until begin writes it), and the outcome is already gated where it cannot be faked: setup done refuses while any marker remains or the gate is red (ADR 0065). This extends ADR 0037's principle — make the state observable, not the path mandatory.
Progress is derived, not self-reported. A new setupProgress(root) reads which scaffolded files still carry a skeleton marker (findSkeletonMarkers, already the setup done predicate) and which jobs are wired versus unset, and the welcome's in-progress state and status render it. Skipping a step becomes visible without any self-reported "mark step N done" call an agent could fake.
The explicit nos:
- No hard
verify → begingate. It would require a state marker written before any config exists, breaking the read-only-until-begininvariant; the funnel plus thedoneoutcome-gate already prevent a skipped-step completion. Deferred exactly as ADR 0037 deferred its per-step state machine — until real use proves it insufficient.
- No self-reported per-step tracking. A
setup done --step=Nthat writes a step number to[meta]reintroduces ADR 0037's echo-back at finer grain (an agent marks a step it did not do), taxes the loop with round-trips, and most brief steps have no machine-checkable predicate anyway.discern setup step <n>exists only as a read-only re-serve of one step's text, for a model that lost the thread — it tracks nothing.
- The welcome shows for both TTY and non-TTY. Dual-addressing both readers is robust where detecting them is not: an agent's shell can be a terminal and a human can pipe, so
isTerminal()would address the wrong reader on a wrong guess. Each reader self-selects its block. ADR 0088 later narrows this stance: content remains dual-addressed, while decoration alone may branch on TTY for the fresh welcome.
discern setupkeeps its declarative behaviour. Given--configor the scaffold flags (the CI/preset path),setupproceeds asbegin; only the bare invocation welcomes. The retiredsetup/setupaliases still redirect (ADR 0036).
- The commands stay non-interactive. Each prints and exits (ADR 0036/0044); the conversation — including the new consent checklist — is the agent's to hold, not a CLI prompt.
- Turning worktrees off is not offered as a setup suggestion. The worktree workflow is load-bearing to discern's model;
verifypresents keep the sibling location or relocate it ([worktree].root), not disable it.
Consequences
§- The curious human is the design centre, not an afterthought. Running
discernwrites nothing and reads as a welcome, so the instinct to poke the binary first is rewarded with reassurance instead of a confusing agent-addressed scaffold. This is the first impression the launch lives or dies on.
- Consent is structural. The agent reaches
beginonly after a phase that told it what to confirm with the human — so "you are in control" finally has a moment attached to it, dissolving both the false-done and the consent-paralysis failures without a heavier gate.
- The consent conversation is grounded.
verifyturns generic questions into ones about this repo — your existing docs, your existing instructions, the precise path that will appear beside your checkout — which is both better UX and the natural home for the teaching moment that discern's docs tree is "what is inferable from the code," distinct from a hand-curated one. ADR 0080 makes the chosen location persistent:verifyremains read-only and passes it tobegin, which records[map].dir.
- Provenance makes triage possible.
doctorsurfaces who set the project up; a broken-install report can be read against the model that produced it.
- More round-trips before scaffolding. Welcome → verify → begin is three calls where there was one. The friction is the point — it is where consent and the first impression are bought — but it is real, and a power user who wants the old immediacy uses
discern setup begin(or the declarative--config) directly.
- The handshake stays CLI-only. MCP is not wired until
begin, so setup cannot run over thediscern_*tools; the reactivation handoff atdoneis what bridges the gap to the post-setup MCP/hook surface — the seam this ADR also closes.
- More surface to keep coherent. Four phases, a three-state welcome, and the derived-progress view are more moving parts than one command. They are kept coherent by the existing single-source disciplines: the phase vocabulary and the not-set-up wording live once in
shared/setup_state.ts(shared by the CLI router,status, the session hook, and the MCP server), and the phase set is tied to its satellites by a forcing function (ADR 0051), so a new phase cannot drift across surfaces.
- The tests must run cold. ADR 0065's lesson was that the suite scaffolded
bootstrapped = trueand so never exercised the un-set-up state where the bug lived. The welcome's three states,verify, andbeginare all driven from the un-bootstrapped state, the only place they are real.
Alternatives considered
§- Keep single-shot setup; fix the framing with TTY detection. Rejected: the earlier proposal to fork the message on
isTerminal()is fragile — an agent's shell can be a terminal and a human can pipe output — so a wrong guess addresses the wrong reader. Dual-addressing both, always, cannot misfire, and the deeper problem (a command that mutates before consent) is untouched by framing alone.
- Hard-gate
verify → beginwith a state marker. Rejected for now: there is nodiscern.tomlto record "verified" in untilbeginwrites one, so the marker would have to be a sidecar that breaks the read-only-until-begininvariant. The outcome-gate atdonealready makes a skipped-step completion impossible, so the marginal integrity is not worth the cost. Revisit if cold runs show agents skippingverify.
- Self-reported per-step tracking in
[meta]. Rejected: it is the precise failure ADR 0037 exists to prevent, one altitude down — an agent that will report the whole job done without doing it will mark a single step done just as readily — and it buys round-trips for no real verification. Derived progress cannot be faked, and is free.
- Flags (
--agent-verification/--agent-begin) instead of sub-verbs. Rejected: a flag that transforms a command from "show information" into "scaffold a repository" is a smell — flags modify, verbs act.setup verify/beginmatch the existingsetup done, are discoverable in--help, and self-identify to the agent through the welcome's Agents block rather than the verb name.
- Fold the preflight into the welcome's Agents block. Rejected: the welcome must stay short and legible to a human, and the full preflight is agent-facing detail. Separating them keeps each surface addressed to one reader with one purpose, which is the whole point of the split.