Adoption Files
How adoption files record your project-specific decisions and keep AI assistants aligned with your team's standards.
Adoption files are your project-specific decisions, recorded in .pair/adoption/. They tell AI assistants what your team chose, which tech stack, architecture pattern, way of working, and product requirements define your project.
While the Knowledge Base provides general guidelines and process guides, adoption files provide the constraints. Together, they ensure the AI generates code that follows both industry best practices and your team's specific decisions.
Structure
What Each File Records
architecture.md
Documents your chosen architecture pattern, API style, and data storage approach:
tech-stack.md
Lists every approved technology with exact versions:
When the AI generates code, it only uses libraries listed here. During code review, unlisted dependencies are flagged.
way-of-working.md
Defines your development process:
It also hosts an optional ## State Mapping section: only needed if your board's column names differ from pair's canonical states. Omitted by default.
## Process Profile: running a subset of the process
Optional. Not every team runs every step. A ## Process Profile section declares which process steps this project does, and no disabled step is ever proposed.
| Profile | What it enables |
|---|---|
default | Every step. This is what an absent section means: a team running the full process configures nothing. |
poc | No DDD mapping and no strategic planning layer: specify-prd, bootstrap, brainstorm, plan-stories, refine-story, plan-tasks, implement, review. |
custom | Exactly the steps you name in a whitelist. |
The unit is the step, not the skill. A step has up to two representations, a how-to guide (the manual path) and an executable skill, and the profile governs the step, so a project with no skills installed, working from the how-to guides by hand, gets exactly the same behaviour. The mapping lives in the step catalogue; the schema and the built-in profiles in process-profiles.md.
What a disabled step does, in the three places you can meet one:
/pair-nextnever proposes it.- Invoked directly, the skill warns that the step is disabled and asks for confirmation: a profile is a project convention, not a permission system.
- Reached as a composition from an enabled step, it is silently skipped, exactly like a skill that is not installed. It never stops to ask about a step you did not name.
A profile is validated, not trusted: a misread profile does not surface as an error anyone sees: it removes a step from every suggestion, which looks exactly like that step not being due yet. Every case below HALTs instead of quietly resolving to default.
| Case | Outcome |
|---|---|
Unknown profile name, or an unknown step id in a whitelist | HALT, listing the valid values, in two deliberately different messages: a typo in a step id is not the same mistake as naming a profile that does not exist. |
custom with no whitelist, or with an empty one | HALT, again in two messages, "you wrote none" is not "you wrote an empty one". An empty whitelist is a misconfiguration, never "everything off". |
A line the schema can't read (- profile: poc, value unbackticked) | HALT restating the shape. The key is detected loosely and its value accepted strictly: reading the line as "no declaration" resolves to default and re-enables the whole process. |
A partially readable whitelist (- `whitelist`: `implement`, review) | HALT on the whole line, never on the readable half, keeping the backticked ids and dropping the bare one would remove a step with nothing reported. |
The section declared twice, at a level other than ##, or as a setext heading | HALT. Only the first section is read, and a heading at another level (### Process Profile) is not this section at all. |
The same key on two lines, or a key with no bullet or a numbered one (1.) | HALT. Only the last line would be read; a bullet-less key would be passed over as prose and resolve to default. Backticks are optional behind a marker and required without one, so 1. profile: poc HALTs too: the two errors compose. |
A value that spills past its key's line (wrapped, or ending on a dangling ,) | HALT. Read up to the wrap the line looks complete, so the ids after it vanish from every suggestion with nothing reported. |
| A key indented by four spaces or a tab | HALT, at that indent it is indistinguishable from a sublist item, and the same key indented by two spaces IS read. |
A key inside a blockquote (> - `profile`: `poc`) | HALT. > is not indentation, so the line would be passed over as prose and resolve to default. A key inside a documentation table row is the deliberate opposite: no declaration, and no HALT either. |
A key under no ## Process Profile section at all, ## Process Profiles (plural), ## ProcessProfile, an unrelated heading, or none | HALT naming the line. Every heading rule above closes only the spellings it names; a key outside every section was read by nothing and reported by nothing, which is the same silent default. A key-shaped line in a code block, an HTML comment or a table row is not a key. |
| The same step id named twice in one whitelist | HALT naming it, never deduped in silence: a repeat is as likely an unfinished edit as a harmless one, and unread it doubles that step's warning and every "N steps enabled" count. |
| An unterminated fence or HTML comment anywhere in the file | HALT. Everything after it reads as non-content, so the real section is never found and nothing else would report that the file went unread. |
| A whitelist that disables a step another enabled step depends on | Reported with the minimal fix: the one case that is not fatal, and never silently repaired. |
What the reader accepts without complaint:
- Heading: spelled as loosely as the keys (
## Process profile,## Process Profile (optional), the closed-ATX## Process Profile ##, up to three spaces of indent), so decorating it cannot make the declaration evaporate. - Key in any case:
- `Profile`: `poc`,- `Whitelist`: …. Case is part of the key's spelling, so it is detected like any other decoration. The value stays strict:`POC`is an unknown profile name and HALTs. - CRLF files: normalized before anything is read. Read literally, a Windows checkout would match no key and no heading at all.
- Code blocks: all three CommonMark forms (
```,~~~and four-space indent) are examples, never declarations. A fence closes only on a run of the same character at least as long as the one that opened it. - HTML comments: masked, so neither an illustrative block nor a commented-out section can become the project's profile.
Guidelines are not steps: testing, security, code design and the quality gates apply to whatever process you run, so poc shortens the process without loosening the bar on the code.
PRD.md
Your Product Requirements Document: the source of truth for what the project does, who it serves, and what features are prioritized (P0/P1/P2).
context-map.md
An optional, living domain model: dispatcher + subdomain index + core (glossary, entities, common rules and invariants). Unlike the other adoption files, it isn't scaffolded during bootstrap: it's created and updated inline the first time a brainstorm or refine session sharpens a domain term, per the Context Map Maintenance guideline. There's no dedicated skill for it: no ceremony, just an embedded step in the skills that already touch domain scope.
For a worked example, see Pair's own map: the framework dogfoods this artifact to register its canonical terms (macrostate, risk matrix, DDR, checkpoint, working area, marketplace…): .pair/adoption/product/context-map.md.
The unit is the subdomain, not a separate "domain" taxonomy. When a subdomain's inline context grows too large, the session proposes moving it to a co-located subdomain/<slug>.context.md file, lazy, and only after you approve. The map keeps the index entry and the domain-wide rules; everything subdomain-specific (glossary, entities) moves to the sibling file.
If the file doesn't exist, AI assistants skip the domain check entirely: this is the expected state for most projects, not an error.
automation.md
Optional. tech/automation.md declares how much of the delivery flow may run unattended. Every section has its own fail-closed default: an absent file or section never widens automation; only a project that writes the section opts in. The sections below are the ones you write by hand; ## Harness and ## Model Policy (supported engines, per-tier model class) are written by /pair-capability-setup-harness and documented in the guideline.
## Autonomy: one declaration for which cards an unattended run works on, how far it takes them and when a human takes over. Keys: filter (any-of labels, matched by plain string equality against the labels classification already put on the card, no AND/OR/NOT grammar), assignee (@me or a login), status (macrostates), root, until (ready | pr | merged, default pr) and two gates, prepare and merge: <always|never|when>[; has: <labels>][; lacks: <labels>] (has/lacks only with when). Under until: merged, merge: always parks the card for a human (the default), never and when enter the merge stage, and a when card carrying a has label or lacking a lacks label escalates instead (status escalated, exit 1, on-halt, one comment on the card; the PR stays open). The merge stage's own checks (reviewed head, pair-review, explicit approval, green gate set) always stay mandatory. No filter: every open card is a candidate for a driver given an explicit root; nothing runs unattended until the section exists.
The prepare gate says who prepares a Draft card: always (default) keeps the human alignment gate; never proceeds alone, recording every self-answer under ## Assumptions; when proceeds alone unless a stage boundary (before refinement, after it, after the task breakdown) escalates: the card then stays Draft with a needs-review label and one comment, and is not re-picked until a human acts.
Precedence is argument > ## Autonomy > KB default, and every run prints each effective value with its source. pair-implement-batch honours the same keys and, under until: merged, merges a card only when the merge gate allows it; pair-loop selects, hands the gates to the batch and owns no merge call.
Legacy sections (projects written for v0.4): ## Eligibility (one label) reads as filter, ## Auto-Advance as the merge gate ((none) is always, a tier is when; lacks: <tier>). Both keep working; a ## Autonomy section that contradicts them is refused. Write ## Autonomy only in a new project. See Tag-driven automation.
## Stop Predicate: when an unattended run stops. <selector> ⇒ <condition> (selector: root / tag:<label> / type:<issue-type>; condition: a canonical macrostate and/or has-tag:<label>) plus a max-iterations: <n> backstop that always applies. Absent ⇒ max-iterations: 1, no predicate: a run advances exactly one iteration and stops.
## Max Parallelism: the ceiling pair-loop passes to the batch engine as min(dependency-allowed, max_parallelism); an optional per-tier override may follow the global integer. Absent ⇒ 1 (fully sequential).
## Audit Location: the project-relative path (under working_path) an unattended run appends its per-iteration record to. Absent ⇒ automation/loop-audit.md. See automation-policy.md for the full schema, every fail-safe default, and every HALT trigger.
## Workflows: the tag→workflow mapping, and the declaration that makes automation opt-in per card. One entry per line, <tag> ⇒ <workflow>: the tag is a plain label a card may carry (matched by string equality, exactly like the filter), and the workflow is the skill that runs on it. An optional Precedence: line resolves a card carrying two mapped tags, first listed wins.
Five properties are worth knowing before you write one:
- A card with no mapped tag runs nothing. There is no default workflow and no fallback: the absence of a route is the opt-in boundary.
- Absent section ⇒ nothing is routable. A dispatch reports
no mapping declaredand exits cleanly; that is the shipped default, not an error. - Ambiguity halts, it never guesses. A workflow that is not installed, a workflow outside the guideline's catalog, or a card carrying two mapped tags with no
Precedence:covering them: each stops the run with a message naming the file and the value to fix. - The card travels as the workflow's own argument.
pair-looptakes it as--root <card>andpair-process-plan-tasksas--story <card>, and the latter selects the highest-priority story on the board when it gets no story at all, so a workflow the dispatcher cannot scope to the card is refused rather than run on a card nobody tagged. Those two are the mappable set: a skill outside it is refused even when installed, because a dispatch takes the card's lock and posts a record on it, and doing that for a skill that only reports leaves a card claiming work nothing did. - A workflow that needs a human in the room is not mappable. A dispatch runs unattended, so
auto-refine ⇒ pair-process-refine-storyis refused even though the dispatcher knows how to scope it: refinement's alignment gate ends only on an explicit human approval, and unattended that means either a stall until the timeout (holding the card's lock, on a card publicly commented as started) or an agent approving its own work past the gate. Refine by hand, or declare thepreparegate (never/when) to take the human out deliberately: the only autonomous route, with every self-answer recorded as an assumption; map the tags for what follows.
The filter is applied before routing, so a mapped card that carries none of its labels is skipped and logged without ever being routed. A trigger passes the card and its labels to the entry point, pair-cli run --card <id> --card-tags <list>, which is where the routing happens.
## Cycle Hooks: deterministic shell commands the delivery cycle's coordinator runs around its stages, the same semantics as ## Publish-PR Hooks generalized to every stage. One bullet per command: - `pre-verify`: `pnpm build` .
- Names are a pattern.
pre-<stage-id>/post-<stage-id>for every stage the cycle has (a stage added later gets its hooks with no schema change), pluspre-cycle,post-cycleandon-halt. pre-*blocks, everything else logs. A non-zeropre-*(orpre-cycle) stops the cycle before the stage runs (failed-hook), printing the command's own output.post-*,post-cycleandon-haltfailures are reported and never stop anything.- Once per invocation.
pre-cycleandpost-cyclerun once per run, not per remediation round;on-haltruns on anyfailed-*orescalatestop, never onready-for-merge. - Absent ⇒ nothing runs, silently. A misspelled hook name is reported once as a warning and never fires.
- Where they run.
pre-<stage-id>/post-<stage-id>run in the story worktree; a verify hook runs in the story worktree at the PR head, not the review worktree.pre-cycle,post-cycleandon-haltrun in the main checkout. - Per-hook timeout.
- `timeout`: `<seconds>`bounds each command; absent means 600 seconds;0disables it. A timed-outpre-*isfailed-hook; the others are logged. - Who runs them.
pair-workflow-cycleandpair-cli run --card, through one shared executor.pair-implement-batchhas no shell and runs none: it says so once per run.
risk-matrix.md
Optional, and the other half of the optional-adoption-file pattern: tech/risk-matrix.md records your project's delta to the quality model, which classification tags are projected onto cards (## Tag Projection), a criticality table (service/domain → High/Medium/Low), and threshold/reviewer overrides. Fully absent means the KB defaults apply and no tags are emitted: nothing fails.
It matters to tech/automation.md: eligibility matches the labels Tag Projection actually emits, so with no projection active no card carries a risk:* label and nothing is eligible. See Quality Model: Project Overrides.
How They Get Created
You don't write adoption files by hand, with one exception, noted at the end of this section. There are two paths:
New project: Run /pair-next in your AI assistant. It guides you through the full structured process: PRD creation, bootstrap checklist, architecture assessment, tech stack evaluation, and so on. Each step produces the corresponding adoption file automatically.
Existing project: Ask your AI assistant to analyze the codebase and generate adoption files from what already exists. For example: "Analyze this project and write the adoption files, architecture, tech stack, way of working." The AI inspects your dependencies, folder structure, CI config, and conventions, then produces adoption files that reflect your current reality. From there, skills stay aligned with your actual stack.
The exception (the two optional files above. tech/automation.md and tech/risk-matrix.md are the ones you author yourself: nothing scaffolds them) for risk-matrix.md, classify and bootstrap may propose a section and write only what you confirm, while automation.md is never proposed and is created entirely by you. Their absence is a valid state that turns the corresponding behaviour off rather than failing.
Either way, once adoption files exist, every subsequent AI session reads them and stays consistent.
How the AI Uses Them
Every time an AI assistant starts a session, it reads the adoption files to understand your constraints:
- Generating code: uses only approved libraries and follows the architecture pattern
- Creating tasks: references adoption files in each task specification
- Code review: checks that new code conforms to recorded decisions
- Making decisions: when a new choice arises, records it as an ADR, ADL, or DDR; a completed technical analysis is logged separately (analysis-log)
- Generating stories: reads the decision log, the ADRs and the context map before drafting, so a new story never re-proposes something already rejected (see below)
If a developer asks the AI to add a feature and the implementation requires a library not in tech-stack.md, the AI flags it and creates an Architectural Decision Record (ADR) to document the choice.
Adoption is read back when stories are generated
Recording a decision is only half the loop. Story generation (/pair-process-plan-stories, /pair-process-brainstorm's triage phase, and /pair-process-refine-story) reads your recorded decisions and your context map before it drafts anything, and that read does exactly three things:
- Constrains: a candidate that contradicts a live decision is reshaped to fit it or dropped, and one that re-proposes something a decision already rejected is never generated.
- Cites: whatever a decision shaped carries it inline:
(per ADR-013),(per decision-log/2026-07-12-adr-sequential-naming-convention),(per DDR-004),(per context-map: macrostate). You see why the story is shaped that way without opening the adoption tree. - Flags a revisit: a story that genuinely needs to reopen a decision is allowed, but it says so (
Revisits ADR-013: <why>) in the proposal you confirm. Silently contradicting a decision is the one thing it won't do.
The read has four properties:
- Read-only: generation never writes a decision. That stays with you and
/pair-capability-record-decision. - Bounded: every record is indexed from its metadata alone (title, date, status, category), in one sweep per directory. Only the records in the story's scope are opened in full, so a large decision log doesn't flood the prompt.
- Live-aware: only a record currently in force constrains,
Acceptedfor an ADR/DDR andActivefor an ADL. AProposeddraft does not constrain yet, aDeprecatedone no longer does, and aSupersededone is replaced by the record that superseded it. A status that can't be resolved is surfaced to you, never silently read as "not live". - Decision-only: an analysis-log entry sharing the decision-log directory is read as context, never as an authority. It concluded in a recommendation, not a decision, so it never drops a candidate, never gets cited, and is never something to "revisit".
On a fresh project with no decisions yet, generation behaves exactly as it did before: no citations, no warnings, never a failure.
The mechanics are defined once, in the adoption-informed generation convention, and pointed at from each generating skill.
Architectural Decision Records
When significant technical decisions are made, they are recorded as ADRs in .pair/adoption/tech/adr/:
Each ADR documents:
- Context: what problem needed solving
- Decision: what was chosen and why
- Consequences: tradeoffs and implications
ADRs provide an audit trail of your project's technical evolution.
Domain Decision Records
Domain decisions (business rules, glossary terms, or behavior) are recorded as DDRs in .pair/adoption/product/ddr/, but only when they clear a 3-criteria gate: hard to reverse, surprising without context, and a real trade-off. A candidate that fails the gate is recorded as an ADL or an inline context-map note instead.
Each DDR documents:
- Context: the domain rule affected and evidence for each gate criterion
- Decision: the rule now in force
- Consequences: tradeoffs and implications
- Context Map Impact: which entry in
context-map.md(or<slug>.context.md) changed
Unlike ADRs, a DDR always syncs a second artifact (the context map) so the domain's current state never diverges from why it got that way.
Analysis Log
A completed technical analysis (performance investigation, dependency evaluation, benchmark) isn't itself a decision, so it's recorded differently: an analysis-log entry in .pair/adoption/decision-log/: the same directory ADL uses, distinguished only by Category Analysis. No new artifact directory is introduced.
Each analysis-log entry documents:
- Context: what prompted the analysis
- Analysis / Findings: what was investigated and found
- Recommendation: what should happen next, if anything
- Adoption Impact: which adoption file's current-state summary now reflects this analysis
Like DDR↔context-map, this follows a history↔state split: the record is history (never edited once written: a repeated analysis always adds a new record), while the pertinent adoption file's summary is current state, always replaced with the latest analysis' findings. If no adoption file is clearly pertinent, the AI asks instead of guessing; if the findings contradict an existing ADR, it flags that instead of silently overwriting the summary.
Package-Local Override
On large monorepos an opt-in override co-locates rules in <pkg>/.pair/ and turns the central file into a router; skills then load only the packages a diff or story touches. See Package-Local Adoption.
Protection
Adoption files are protected by design. When pair updates the Knowledge Base (pair-cli update):
- Knowledge files update: guidelines, process guides, templates refresh
- Adoption files stay: your decisions are never overwritten
This is enforced by the asset registry system: the adoption registry uses add behavior, meaning it only creates files that don't already exist. Your team's decisions are permanent until you explicitly change them.
Next: Canonical states, how skills read your board.