Skip to content
Build and shipProduct pagePricing

Plan repositories

A plan repository holds a project’s engineering records: plans, specs, decisions, milestones, runbooks and the other documents a change needs. Each record is a Markdown file with front matter, and Fir keeps it as an object: a number, versions made when it merges, typed blocks with stable anchors, and links stored in both directions to the commits, pull requests, checks and releases that touch it.

People meet records as Plans: Engineering › Source › Plans in the console, and the Plans tab of every repository. Agents use fir record, fir plan and fir why, and the records tools of Fir’s MCP server.

A record’s file is stored in exactly one home: the code repository’s own tree, or the project’s plan repository, such as acme/plans. There are never two copies. Everything people and agents use reads one project index of records, versions, numbers and links, never the storage location, so a record has the same type, checks, links, Why, place on Today, approvals and delivery to agents in either home.

What differs In the code repository (records.home: repository) In the plan repository (records.home: plans)
Where the file is docs/plans/… in the repository’s tree and history acme/plans, in its type’s folder
What a workspace shows The files, as in any tree The same path under docs/plans/, mounted: lazy, sparse, readable and writable
A pull request that edits it Plan changes in the same repository, atomic with the code Plan changes as a companion change to the plan repository: atomic in one cell, in order otherwise
Readers The repository’s readers; a read grant on docs/plans/ lets product or support read records without the code The plan repository’s readers
Export to git In the tree, always With the plan repository; export: materialize also writes it into each code repository’s export
Number By scope: a record about one repository takes that repository’s number (acme/infra#88) The same rule: acme/storefront#469 is stored in acme/plans; a project record takes the plan repository’s number (acme/plans#50)

The records folder. docs/plans/ in a code repository’s workspace shows every record about that repository, whatever its home: records stored in its tree are files of the tree, and records stored in the plan repository that are scoped to the repository or to the whole project are mounted beside them. It is one namespace: Fir refuses a create, a move or an enrollment that would put two records at one path. A new file goes to the default home for its type and scope, and Fir says where it went: fir status prints “stored in acme/plans”, and the console’s tree shows one entry, docs/plans · 20 records · 14 stored here · 6 mounted from acme/plans. Project-wide records are read-only in the mount unless your grant covers them.

Settings › Plans › Records home, .fir/records.yaml, a Terraform resource and the API hold the same setting. It is layered organization → project → repository → type, and the more specific level wins unless a level above locked it:

records:
home: repository # repository | plans: where records about this repository alone are stored
home_by_type: {} # optional overrides, such as {decision: plans} or {spec: repository}
mount: {"on": true, path: docs/plans/} # the plan repository's records about this repository, in its workspaces
paths: [docs/adr/] # extra record roots kept in this tree (an import keeps its folders)
lock: false # an organization may lock the home for every repository

fir record new --home plans chooses the home for one record. Records that span repositories, and project records (goals, programs, milestones, risks, and the SLOs, incidents, lessons and reviews of the project), are stored in the plan repository whenever the project has one, so cross-repository work always has one place.

Shape of the project Default home
One repository, or a monorepo The repository. No plan repository until a record spans repositories
Several repositories Records about one repository in that repository; records that span repositories and project records in the plan repository, created with the first such record
A public repository The private plan repository; public records, such as an open-source project’s RFCs, in the tree by choice
An imported repository Where its records already are: docs/adr/ or specs/ become record roots, and moving them is one command later
An organization that wants no records in code repositories The plan repository for every record, locked at the organization; the mount keeps the files in every workspace
Terminal window
fir record move acme/infra#88 --to plans
fir record move --repository acme/infra --to plans --dry-run

A move opens one reviewed pull request: the file leaves one home and arrives in the other, atomic in one cell, in order otherwise. It is a mechanical version, so approvals carry. The record keeps its number, ID, key, versions, links, comments and approvals; the path under docs/plans/ does not change, so agents and links in text see nothing move; the old storage path stays an alias. A move is also a permission change, and its pull request shows the readers it adds and removes before it merges.

Acme’s two choices (demo data): acme/storefront stores its records in acme/plans (records.home: plans), so product and support read plans without reading code, and its workspaces mount 37 records at docs/plans/. acme/infra keeps the default: plan acme/infra#88 “Gateway egress through a fixed allowlist” is a file of its tree at docs/plans/88-gateway-egress-allowlist.md, beside runbook RB-0012 and decision ADR-0029, because its runbooks travel with the Terraform tree into every workspace and every git export. Milestone acme/plans#50 counts #469 (stored in acme/plans) and #88 (stored in acme/infra) the same way.

# acme/plans, the plan repository
README.md # the project plan; appears as docs/plans/PROJECT.md in mounts
goals/ plans/ milestones/ risks/ briefs/ rfcs/ research/
specs/ decisions/ contracts/ architecture/ security/ glossary.md
runbooks/ slos/ incidents/ test-plans/ reviews/ docs/ standards/ lessons/
.fir/types/*.yaml .fir/templates/*.md .fir/views/*.yaml
.fir/records.yaml # enrolled repositories, homes, mounts, record rules, key prefixes
# acme/infra, a code repository with records.home: repository
docs/plans/README.md # the repository plan (stored here)
docs/plans/88-gateway-egress-allowlist.md # acme/infra#88 (stored here)
docs/plans/runbooks/RB-0012-rotate-egress-allowlist.md
docs/plans/PROJECT.md docs/plans/milestones/50-peak-ready-checkout.md # mounted from acme/plans

Record types follow a record’s scope, never its home: the project’s types apply to every record of the project wherever it is stored, and a code repository’s .fir/types/ adds to the records scoped to it (Record types).

  • A number in its scope’s one sequence, shared with issues and pull requests, given when its first version merges, so an abandoned draft takes none.
  • An ID that never changes, wherever the file moves, and links point at it.
  • A key for types that carry one, such as ADR-0031, RB-0007 or RISK-003, from the type’s own sequence on the server. Keys are never reused.
  • Versions. Each merge that changes a record makes a version, and Fir computes its change kind from the structured diff: mechanical, tightening or material. An approval carries across mechanical and tightening versions and is void on a material one (The planning flow).
  • Blocks with anchors. Headings with an ID (## Retry a capture that times out {#CAP-4}), typed rows (AC1, D2, item 1.2), front matter fields, and prose anchored by quote. Comments, approvals and links attach to a block at a version and follow it through later edits.
  • IDs reserved by the server. The editor and the reserve_ids tool reserve block IDs, so two open pull requests never add the same CAP-10.
  • States Fir owns. States that come from events (merged, done, proof states, a decision in force) are written by Fir; nobody types them.

Every file in either home is a record. A Markdown file with no type is a doc, any other file is an attachment, and a type is structure a team opts into: twenty-two built-in types in six groups, packs for regulated and other industries, and types of your own with no ceiling on what they can say. Record types has the catalog, the type language and the three ways to make a type.

Group Types
Direction goal, brief, project plan, RFC, research
Work plan (tiers fix, task, program, inquiry, incident, migration), milestone, risk
Truth spec, decision, contract, architecture, glossary, threat model
Operations runbook, SLO; a postmortem is a plan of tier incident
Quality test plan (kinds tests, eval and experiment), review
Knowledge doc, standard, instructions, lesson

A plan that outgrows one file is part of a program: a tree of nodes with no fixed depth (program, plan, phase, section, item by default), run by many squads and models, with milestones whose progress counts only merges with passing proofs. A plan repository opens on Today for the person signed in. Programs has the tree, Today, the views, the insights and milestones.

Fir writes every link from what it already knows, so nobody types one. A person is asked for one key at most.

Where the link comes from Who writes it Trust
The session’s binding: plan version, items, pull request, workspace Fir, into the intent of every commit the session makes attested
The stack layer made from plan items Fir, from the layer’s items attested
The workspace’s active plan (fir plan use 469) Fir, from the person’s choice declared
A suggestion accepted with one key: the active plan, the branch’s earlier pull requests, the issue the change fixes, plans whose paths cover the changed files The person declared
The merge The merge queue, in the merge’s transaction as the intent
A check at a merged version Frazer CI attested
A release Frazer Releases attested
An import The importer recovered
A model’s inference for history older than Fir, when asked Fir inferred until a person confirms
An outside tool (OSLC, a pasted URL) The person or the tool declared, never counted by a rule

Every commit’s intent carries a link block, hashed into the commit’s ID:

plan: {ref: acme/storefront#469, written_against: 4, digest: "b3:9f2c41…", trust: attested}
items: [1.2, 1.3]
implements: [payments/capture#CAP-4]
follows: [ADR-0031]
fixes: [acme/storefront#479]
session: s-81f2
receipt: {id: cr-9b3e, digest: "sha256:…"}
in_force_digest: "sha256:…"
epoch: 3

A push is never refused for a missing link: you can push any code you like. The record rules decide at the merge queue: requires_plan: suggest shows a red Plan row with the suggestion, require holds the pull request until it links a plan or a verified exemption. A commit made outside any workspace, from a laptop or a script, gets the suggestion at fir pr create and in the console’s pull request form.

Link From → to Written by
written_against A commit, pull request or stack → a record version The pull request’s intent, at merge
implements A pull request or layer → a plan item or a requirement The intent
proves A check result at a version → a criterion or requirement Frazer CI, when the result arrives
covers A requirement, runbook, contract or threat → paths, services Front matter, checked at merge
governs A decision or a standard’s rule → paths Front matter
follows A pull request or plan → a decision The intent; front matter
in_milestone A plan or issue → a milestone Front matter; the issue’s field
depends_on A plan → a plan; a milestone → a milestone Front matter
derived_from A plan → an RFC, brief or research record Front matter
contributes_to A program, milestone or plan → a goal Front matter
mitigates A plan or requirement → a risk, threat or hazard The risk’s mitigation rows
learned_from A lesson → a postmortem, plan or incident The lesson’s evidence rows
planned_in An issue → a plan item The plan’s items; the triage agent
supersedes A record → the record it replaces Front matter
references A record → paths, symbols, commands, configuration keys Parsed from the text

A type adds link kinds of its own and says which links its records must have (Record types).

Links point at the pull request’s stable change_id and the record’s ID, never at a commit hash or a key typed into text, so no rewrite breaks them:

Rewrite What happens to the links
A new version of a pull request (rebase, amend, push) Nothing moves: the intent belongs to the change; “written against v3, the plan is at v4” shows when the plan advanced
A restack Copied to the new version by restack kind; a conflicted restack carries them with the items’ proofs unproven at that version
A conflict resolved by a person or a model A new version of the same change; links carry, and the version records how it was resolved
A fold, a split, a dropped layer A fold unions the items; a split sends each item with the layer whose files match its paths and never leaves one in neither; a dropped layer’s items reopen
A squash merge, a rebase merge One link row per merged commit, with the layer’s intent
A cherry-pick or a backport backport_of plus the original’s links, at the plan version the original was written against
A revert reverts; the lines get their earlier origins back; the items reopen
A record moved between homes, renamed or retyped; a repository renamed Nothing changes; old paths, numbers and names become aliases
An export to git, and its import back Plan: and Plan-Digest: trailers and .fir/links.jsonl; Fir’s own signed export comes back attested

“Everything is accounted for” is a number per repository and per project, over a window (30 days by default), on the repository’s Plans tab, in Settings › Plans › Rules, in Provenance and as the insight link_coverage. Counts are written at merge, so reading them scans nothing.

Measure Counted as Target
Merged pull requests Linked (attested or declared), exempt by a verified class, or none 100 % accounted for under require; 99 % or more under suggest when agents do most of the work
Merged commits The same, per commit The same
Lines at main@verified Origin linked, exempt (generated, dependency, format), or older than Fir Every line written on Fir has an accounted origin
Records Records with no inbound link, requirements with no implementing merge, suspect links Findings, never a held merge unless a rule says so
Terminal window
fir links coverage --repo acme/storefront --since 30d

Before work starts, the plan check plan/links fails a plan when a link its type requires is missing or does not resolve (The planning flow).

Links pin versions: when either end changes, the link turns suspect, and a passing proof clears it. Fir also checks consistency, not only links: a requirement and the test that proves it name the same values, and a decision in force and a requirement on the same path do not contradict. A baseline names a set of record versions and the commit of every enrolled repository; fir trace and fir impact accept --baseline. Traceability exports as CSV, JSON and ReqIF 1.2, and an OSLC RM 2.1 provider per plan repository lets requirement tools link to records live.

Ask why any line, range, symbol, file, commit, test or configuration key exists, and one call answers with everything behind it:

  1. Origin. The change that wrote the line, read from the stored origin index. Format-only, move, rename and revert changes are skipped by their class, moves are followed across files, a generated line points to its generator and its source, and a vendored line to its dependency bump.
  2. Change. The pull request, its version and main@N, the layer and its stack.
  3. Plan. The plan version the change was written against, the plan now with its summary card, the item it delivered and the approval of that version.
  4. Spec. The requirements the change implements and the ones that cover the line, each with its proof state.
  5. Decisions, then and now. The decisions in force for the path when the change merged and today, each with how it evolved.
  6. Session. The session and its receipt: what it was given, for readers allowed to see it.
  7. Evidence, shipped, after. Proofs at the merged version, the release, and the reverts, incidents and issues that followed.
  8. History. Up to three earlier meaningful changes on these lines, each with its plan item.
  9. Withheld. What you may not read, as Request access where a readable record names it, and nothing otherwise.
$ fir why services/checkout-api/payments/client.go:88
services/checkout-api/payments/client.go:88 at main@48213
req.IdempotencyKey = o.CaptureKey()
Origin #473 "Add idempotency keys to the payments client" · merged as main@48190 · coder for lena
skipped: main@48198 (format only)
Plan #469 "Payment capture retry" · written against v1, merge made v2 · now v3, in progress
item 1.1 "Every capture call carries the order's key" · v1 approved by tomasz, Oct 13
Spec payments/capture#CAP-7 "never capture one order twice" · proof NoDoubleCapture passed at main@48207
Decision ADR-0031 in force since Sep 24 · supersedes ADR-0019 ("keys from the cart ID"): a cart split into two orders
and the gateway rejected the second capture as a duplicate · followed 4 times
Session s-41a7 · receipt cr-5d2a · 6 sources, In force 7 rows
Shipped checkout-api 2.14.0 · canary 25 %
History #455 "Add the payments gateway client" (Sep 12) · plan #450 v3
  • Stored, not recomputed. The origin index is written at every merge from the parent’s line runs and the change’s hunks. Nobody keeps an ignore list: Fir knows each change’s class.
  • Hard cases. A line many layers changed answers with the newest meaningful change and lists the ones it replaced. A line in your workspace or an open pull request says “this pull request, not merged”. A line a conflict resolution wrote says how it was resolved and against what. Lines older than Fir show the import record, the recovered links and, on request, an inferred plan a person must confirm, labeled “before Fir”.
  • Built for agents. Sections fill in order until the budget is spent, each with its token count; a section that does not fit returns a handle; the stable prefix carries no timestamps, so prompt caches stay warm. --concise answers in about 250 tokens, one line per section; the full answer has a 1,500-token default budget. --as-of main@48190 answers as of an earlier commit.
  • Fast. Designed to answer in under 3 ms at the median and 15 ms at p99 warm, end to end, and 60 ms at p99 cold.
Terminal window
fir why services/checkout-api/payments/client.go:88 --concise
fir origin services/checkout-api/payments/errors.go:10-24

fir origin shows a range’s origins with the moves and skipped changes.

Provenance: what a change used and how it linked

Section titled “Provenance: what a change used and how it linked”

Every pull request, layer, plan, decision and release has a Provenance tab, and agents get the same rows with get_provenance:

Section What it lists Trust
Given Each receipt: the In force rows, the task, the sources with versions and anchors, and what was left out and why attested
Read Records and files the sessions opened, with version, time and how attested; declared when a harness’s hook reported the read
Cited Anchors cited in the plan, assertions, notes, handoffs, As built and the intent, and whether each resolves attested
Changed Files, records, symbols and contracts attested
Linked Every link row the change made or received, with kind, how, trust, by whom, when and who confirmed it as each row
Proved Criteria and requirements with their proofs at the merged version attested
Approved Approvals with signer, time and meaning, and what carried from earlier versions attested

The tab is a W3C PROV graph, exported as PROV-JSON and as an in-toto statement bound to the merged commit by its digest. It reads stored rows only, designed to open in under 5 ms at the median.

Terminal window
fir provenance '#482' --format prov-json
  • Move in. The importers bring issues, pull requests, milestones, projects, wikis, documents, decision and RFC folders and requirement tools’ requirements into Fir as records and issues, reviewed once as pull requests.
  • Keep identity. Every imported object keeps its outside identity as an alias, such as jira:PAY-88, linear:ENG-42 or git:3f9c2e1, with URL patterns, so a pasted outside URL turns into the record (the console’s search box and the resolve tool) and text in imported records keeps working.
  • Link only as declared. A live link to a system that stays outside is a references row of trust declared, shown with its fetch time and “outside Fir”. It never satisfies a record rule, never counts toward coverage and never enters In force, because In force needs a version Fir holds and can re-check. To make it bind, choose Move into Fir: the import opens as a pull request and keeps the outside identity as an alias.

A record rule asks for a record before a merge. Each rule has three modes: off, suggest (a red row with the fix; the merge is not held) and require (the merge queue holds). A rule is scoped by branch, path, change class, record type and risk class, has exemptions checked against the change’s class, and shows a 30-day backtest of what it would have held before you choose require.

Rule Asks New repositories
requires_plan An approved plan, or a fix’s plan inline in the intent suggest
requires_spec_change A change to code a requirement covers names the requirement, or edits the spec suggest once a spec exists
requires_decision A new dependency, a new service, or an architectural path links a decision off
requires_contract_check A contract’s source passes its compatibility check; a breaking change needs its owners suggest once a contract exists
requires_runbook_review A change to alerting or a covered service updates the runbook, or records that it holds suggest once a runbook exists
requires_threat_review A change on security paths links the current threat model and one of its owners off
requires_review A plan of a risk class links a passed review of a kind, such as production readiness off

A type can bring its own rule. Every rule is a row of Settings › Plans › Rules on a repository, a plan repository, a project or an organization, where the stricter level wins; the same rows are .fir/records.yaml, a Terraform resource and the API. Agents read the rules that apply to their change with get_record_rules before they open a pull request.

  • People open a record in the console: rendered, with its outline, typed blocks as widgets and the type’s form for front matter. Edit opens a draft in a server-side workspace that several people and agents can co-edit live; comments attach to a block or a quote, and text an agent wrote is marked. Publish opens the record’s pull request, or adds a version to it.
  • Agents write the Markdown file in their workspace and get typed errors with anchors on push (sections.Outcome: AC3 has no proof), or call update_record with block operations and expected_version, so two agents never overwrite each other.
  • Merges go by block and row ID, so pull requests that append As built entries or add requirements with reserved IDs merge cleanly; two edits to the same block conflict, as code does.

Every part of this page changes by a reviewed, versioned act that agents can read:

What How it changes What people and agents see
A project’s or repository’s home Change the setting; fir record move moves what exists, in one pull request The record shows “moved from acme/infra on Oct 20 by #512”
Link kinds New kinds in a versioned catalog; types add their own; a renamed kind keeps its old name as an alias list_link_kinds with versions; each row keeps the kind it was written with
A link’s trust Rows are append-only: a person confirms or retires a row, with a reason, never edits it The row’s history in Provenance
How origins are computed A new version of the origin index, backfilled newest first; the old one serves until the backfill ends Why names the origin version that answered
Why’s answer Additive fields; a new section never reorders the stable prefix The answer’s format_version
Coverage targets and record rules Settings with history and owners The rules page’s history; get_record_rules
Terminal window
fir record new milestone --title "Peak-ready checkout"
fir record new plan --home plans
fir record view acme/plans#50 --json
fir record list --type spec --path services/checkout-api/
fir record diff payments/capture v5 v6
fir record links acme/plans#50
fir record check docs/plans/specs/payments/capture.md
fir record move acme/infra#88 --to plans
fir record move --repository acme/infra --to plans --dry-run
fir record settings --repo acme/infra
fir plan new --from-issue 479 --tier task
fir plan use 469
fir plan view 469 --at main@48190
fir why services/checkout-api/payments/client.go:88 --as-of main@48190
fir origin services/checkout-api/payments/errors.go:10-24
fir provenance '#482' --format in-toto
fir links coverage --repo acme/storefront --since 30d
fir trace payments/capture --baseline storefront-2026.11
fir impact payments/capture#CAP-4

fir spec view and fir decision view are shortcuts for fir record view on that type. fir plan versions, fir plan as-built --draft and fir record evidence --framework soc2 --period 2026-Q3 complete the set; the Fir CLI reference lists every command.

Every tool is also a fir command and an API method (frazer.records.v1). Tools in the default toolset load with Fir’s MCP server; the rest are in the records toolset.

Tool Does Toolset
get_record Any record by number, key, slug or reference, at a version or now, whole, by block or concise Default
why Why a line, range, symbol, file, commit, test or key exists; origin, as_of, a workspace snapshot, concise Default
resolve A number, key, alias, outside URL or path to its record Default
get_plan, get_commit_plan, get_record_rules A plan; the plan version a commit was written against; the record rules for your change Default
lookup, get_summary Find any plan by key, item, path, symbol, milestone or text, with its card; a plan’s summary Default
assemble_context The record sections a task needs, each with its reason (Agents and plans) Default
get_origin, get_provenance, get_link_coverage A range’s origins with moves and skipped changes; the Provenance tab; coverage for a scope records
move_record, get_records_settings A move between homes, as a pull request; the layered records setting of a repository records
list_records, search_records Records by type, state, owner, path, milestone, program or link; full-text search records
create_record, update_record, reserve_ids Create from a template; block operations with expected_version; reserve IDs records
diff_record_versions, list_record_links, impact, trace Structured diffs; links both ways; what a change touches; the traceability matrix records
check_record The type’s rules and the readiness inputs, as the record check runs them records
get_mount A code repository’s records folder: stored and mounted records records

Types, programs, decisions and the planning flow have their own tools on their pages. Every record and version is also an MCP resource, such as fir://acme/plans/records/50 and fir://acme/plans/records/50/v2; a session subscribes to the records it works from and is told when one changes.

Import reads docs folders in common layouts (spec and plan folders, decision record folders, RFC repositories), issues, milestones and project boards, the pages, documents and projects of Notion, Confluence, Google Docs, Linear and Jira, and ReqIF 1.2 from requirement tools. A mapping preview shows what each source object becomes and what will not carry over before anything is written, and the import opens pull requests, so every imported record is reviewed once. Numbers and keys are kept. A repository’s own record folders stay where they are and become record roots; moving them is fir record move, later, with every version kept.

Export writes the files as they are, .fir/types/, a log of every version with its approvals (signer, time and meaning), every stored link with how it was made and its trust (.fir/links.jsonl), every alias (.fir/aliases.jsonl), and ReqIF for specs. Records stored in a code repository’s tree are in that repository’s export as they are, and each export writes a .git-blame-ignore-revs from Fir’s change classes, so plain git blame comes close to Fir’s origin. Import reads all of it back.

A record stored in a code repository is readable by that repository’s readers, and a read grant with a path condition on docs/plans/ lets people who do not read the code read its records. A record stored in the plan repository is readable by the plan repository’s readers. Path rules and sealed paths narrow both, so a restricted record lives on a restricted path such as security/. A link to a record you cannot read shows Request access when a record you can read names it, and nothing otherwise; counts count only what you can read. Approvals carry the signer, the time and their meaning, and are attested with the merge, so they cannot be copied to another record.

Records, types, versions, links, the index, Why with origins, provenance, link coverage, aliases, moves between homes, milestones with progress and forecasts, record rules, traceability, impact and context assembly are part of Fir; records are stored as Fir data. Work a model does (a cold review, a panel’s opinions on a decision, a drafted milestone update or summary, an inferred link for old history) bills as tokens through the LLM gateway.