Docs · Builder's guide

The page you reach for in month two.

Everything the four pages before this one deliberately skip: what outranks what, the contract every record obeys, the grammar evidence is written in, how a packet is assembled, which files are not yours to edit — and an honest list of what is not built.

  • 0.10.0
  • authority
  • records
  • retrieval
  • the pushed set

01 what this page is

The reference layer, not the first read

Four pages come before this one and this page repeats none of them. Read them first if you are new; come back here once the loop is habit and you need the contract underneath it.

scope

Written against the code at 0.10.0, not against a guide beside it. Everything here is built and tested unless it appears under not built yet near the end — which is honest about the gaps rather than quiet about them.


02 the authority ladder

What outranks what when two sources disagree

None of this is advisory. When a record and the code disagree, the code is right and the record is stale. When a generated view and a record disagree, the view is stale and there is nothing to reconcile.

Rank Layer Authority
01 Primary artifacts The code, the commit, the pull request, the test result. Highest for any factual claim.
02 Curated records NOW.md, DECISIONS.md, LEARNINGS.md and the detail records behind them. Canonical for continuity: what is in flight, what is settled, what was already paid for.
03 The pushed set global/ and blueprint/, authored by a Project Hub owner and read-only here. Constraints you did not write and cannot change locally.
04 Proposed knowledge Anything still proposed or open. Visible on purpose, and not settled — the packet lists it as a link rather than loading it.
05 Agent instructions The managed block and SKILL.md. They say how to use the context; they are never a source of project fact.
06 Derived views The generated registry index tables, and any wiki or summary built on top of them. Never authority.
regenerate, do not reconcile

If an index and a record disagree, the index is stale: run context_index.py and the disagreement is gone. Editing one by hand is worse than having none — a hand-edited index is trusted, and then overwritten.


03 record anatomy

Six required keys, and nothing else required

Detail records in decisions/, questions/, tasks/ and inbox/ carry YAML frontmatter. The registries carry none — NOW.md, PLAN.md, DECISIONS.md, LEARNINGS.md and QUESTIONS.md stay plain Markdown a person can read in a pull request.

project-context/decisions/D-004.md frontmatter
---
id: D-004
kind: decision
status: accepted
title: Serve thumbnails from the CDN, not the app
created: 2026-09-03
asserted_by: person:ren
---

id · kind · status · title · created · asserted_by

Everything else is optional

approved_by, supersedes, superseded_by, evidence, files, valid_at, invalid_at, session, harness and model are validated only when present. Absent means absent: the doctor warns on a generated_at, confidence or aliases key, and on an empty supersedes: [] carried forward from the retired three-block format.

One schema string

project-context/1, recorded in project-context/.project-context.json. The marker also names the product that wrote it, so a reader never compares one product's version number against the other's.

One version number per product

Read from that product's VERSION file, and from nowhere else. TEMPLATE_VERSION and SCAFFOLD_VERSION are retired: a product does not version its templates or its scaffold separately from itself.

IDs have a shape

D-001, L-003, Q-002 and T-012 for authored records; C-2026-09-03-a1b2 for a capsule, whose suffix is derived from its own text. Anything else is reported as invalid-record-id.

Correction, not edit

When something turns out wrong, supersede it or add a correcting record. Never rewrite the meaning of a record someone may have acted on — the history of a decision is worth more than its tidiness.


04 one lifecycle per kind

A question is not an assertion, and a task is not a claim

Each kind has exactly one status vocabulary, and the doctor checks a status against its own kind rather than a permissive union of all of them. A union would let two people write questions two different ways with nothing to catch it, which is the failure a single vocabulary exists to prevent.

Kind States
decision · learning · capsule proposed → accepted → superseded | rejected
question open → answered → superseded
task proposed → active → done | dropped
retired everywhere

candidate and approved are gone: read proposed and accepted. The doctor reports either as retired-status and names its replacement, whatever the kind. The approved_by field keeps its name — it records who accepted.

A state that belongs to another kind is as wrong as one that belongs to no kind: accepted on a question and answered on a decision are both errors. One exception is not a status at all — the activity column in NOW.md is prose about work, and stays as it is.


05 the reference grammar

Eight schemes, validated by shape and never by resolution

One grammar wherever a reference appears — a frontmatter evidence: list, an Evidence: line, a body. The doctor checks that a token claiming a scheme keeps that scheme's shape and stops there: nothing asks whether the commit, ticket or page on the other end exists. A token whose prefix is not one of these is ordinary prose and is left alone.

Scheme Cites
session:<harness>:<id> The session that asserted it.
commit:<binding>:<sha> A commit, by repository binding and 7 to 40 hex characters.
pr:<binding>#<number> A pull request.
review:<binding>#<pr>/<comment-id> One review comment on one pull request.
ticket:<tracker>:<key> An issue in whatever tracker the project already uses.
doc:<binding>:<path>@<commit> A document at the state it was read at.
url:https://… Anything outside the repository; the scheme must be http or https.
capsule:<id> A capsule in inbox/, by its C-… ID.
angle brackets are never literal

They mark a metavariable. A URL reference is written url:https://example.com/x, not url:<https://example.com/x> — and a token with the brackets left in fails the shape check as invalid-reference.

Inside the repository the short form is enough: pin a path to the state it cites, src/auth/session.py@a1b2c3d. A reference to a moving file is a reference to nothing, and the doctor reports evidence-drift once the cited path has changed since the commit it was pinned at. Evidence anchors, in full, on the records page →


06 capture

Writing it down without stopping the work

A decision worth recording almost always surfaces mid-task, and stopping to write a registry entry with an ID, a rationale and consequences is exactly the interruption you decline — so the decision goes unrecorded and the reason is lost. Capture has to be cheap enough to happen during the work.

One note into the inbox, and nothing else

It writes one file. It never edits a registry, never touches a record, and never reaches a network. The judgement — decision, learning, or nothing — is deferred to promotion, where it is cheap.

capture a capsule
python3 .agents/skills/project-context/scripts/context_capture.py --kind decision --text "We standardise on pnpm; npm workspaces could not hoist the native deps." --apply

The kind is what the note is about

--kind is one of decision, learning, question, assumption, constraint or proposal. It is not the record kind: everything in inbox/ is kind: capsule. A proposal is how you ask for a change to something the Hub pushed, since you cannot edit those.

Provenance is recorded without being asked for

The actor comes from your git identity unless --actor person:<name> or agent:<name> says otherwise, plus --session, --harness and --model where the harness knows them — and the current commit is written in as a commit: evidence reference. Anything unknown is omitted rather than recorded as unknown.

Two limits worth knowing

A capsule is at most 200 words and a longer one is refused, because anything longer is the record it should become. And the same text on the same day writes once — the ID is derived from the text, so a Stop hook that fires twice does not leave two identical notes to triage.

Promotion is yours

Write the registry entry the capsule earns and set its status to accepted with a link to what it became, or rejected when it belongs nowhere. Either is a resolution; leaving it proposed is the only outcome that is not.

why there is an inbox at all

The cost of a staging area is capsules nobody promotes — which is why the standing review reports an ageing one, by name and by age. The cost of not having one is decisions nobody records, and that cost is silent.


07 retrieval

What a packet assembles, and why it is only a scan

Before substantial work, assemble the packet instead of reading the folder. What comes back is ordered rather than ranked, and the order is the point.

assemble a packet · read-only
python3 .agents/skills/project-context/scripts/context_packet.py context --task "add rate limiting" --files src/api/gateway.py

The owner's global tier

SUMMARY.md, IDENTITY.md and GUARDRAILS.md from global/, where a Hub has pushed one. A file the owner published blank is skipped: it carries no information and costs budget.

The blueprint

blueprint/EPIC.md in every mode, because the epic is the constraint in every mode. ARCHITECTURE.md joins it under --mode plan and --mode review only — an implement packet carrying it would spend a quarter of the budget on a document the task at hand is not allowed to change.

Project state

NOW.md, and the active items of PLAN.md.

Records anchored to your files

The decisions, learnings and questions whose evidence anchors share a path prefix with the paths you named in --files.

Then records that merely share the vocabulary

A token overlap against the --task line, after the anchored ones. A packet that led with your own notes would bury the constraint that was not negotiable.

no index, no database, no background service

Matching is a path-prefix comparison and a token overlap over a few hundred small Markdown files. There is nothing to keep warm and nothing to rebuild after a pull. The signal that decides relevance is already written down — a decision cites the files it constrains, and a task names the files it touches. This is a deliberate boundary, not a stage on the way to something bigger.

what is loaded, and what is only linked

Only settled records are loaded: accepted and answered, plus active and done for a task. Proposed ones are listed as links, so you can see they exist without being told they are settled, and --verified-only drops even the list. Whatever does not fit the budget — --budget, 4,000 estimated tokens by default — comes back as a link rather than being dropped, so the packet never implies that what it left out does not exist.

Invocation When
context --mode implement The default: anchored records first, then the ones that share the task line's vocabulary.
context --mode plan Writing a plan. The packet leads with blueprint/, architecture included.
context --mode review --diff Reviewing. The file set is taken from the working tree's own changes rather than from --files.
onboard A first session in a repository. There is no task yet, so nothing task-specific — and this is what the SessionStart hook emits.
--format json A machine is reading the packet instead of a session.

08 review · indexes · conformance

Three questions the doctor does not answer

The doctor answers “is this correct?”. These answer three others: what is waiting on a person, whether the derived tables are current, and whether the plan still serves the epic.

The standing review — what is waiting on a person

Proposed records, questions open past --open-days (14 by default), unpromoted capsules, assumptions nobody confirmed, drifted evidence anchors, a stale NOW.md, and a pushed snapshot older than --snapshot-days (90). Oldest first, because latency is the failure this system is exposed to: a five-week-old question matters more than a fresh one whatever their subjects.

the standing review · read-only
python3 .agents/skills/project-context/scripts/context_review.py --target . --open-days 14
a backlog is not a build failure

The review exits zero whatever it finds. A proposed decision is a decision working as intended, and an unanswered question is the discuss step doing its job — they become a problem only by ageing. CI that broke on an open question would teach everyone to stop filing them.

The indexes — derived tables, regenerated

DECISIONS.md and LEARNINGS.md grow without bound and get read end to end. The generator rebuilds a marked table at the top of each, so a reader can answer “does anything here constrain what I am about to do?” without paying for the whole file. They are derived: a hand-edited one is overwritten by exactly the generator that owns it, and --check exits non-zero when they are stale, so CI can hold the line.

verify the indexes are current
python3 .agents/skills/project-context/scripts/context_index.py --check

Plans and epics — what “conforms” means

Where a Hub owner has pushed blueprint/EPIC.md into your repository, each ## M-NNN: item in your PLAN.md names the epic item it advances:

project-context/PLAN.md
## M-001: Ship the search endpoint

- Status: `active`
- Serves: E-002

A plan item naming no epic item is an error

The project is spending effort the epic does not ask for, and the fix is to anchor it or raise a question. An item already dropped is exempt — work that is not happening does not owe the epic an anchor.

An epic item nothing serves is only a warning

An epic is allowed to run ahead of the milestone in front of you; erroring there would force a project to plan the whole epic at once. The review lists it so it is visible without blocking anything.

No blueprint, no epic

A repository with no blueprint/ has nothing to conform to, and PLAN.md stands alone with nothing checked against it. Project Context is a complete product without a Hub.


09 the pushed set

Two folders you must not edit

If your organisation runs a Project Hub, two folders arrive in your repository from it. Everything else under project-context/ is authored here and is yours. A repository with no Hub simply has neither folder, and nothing about that is degraded.

project-context/global/

The organisation's summary, guardrails, workflows, shared skills and shared records. Owner-authored elsewhere, copied in, read-only here.

project-context/blueprint/

EPIC.md, the high-level plan this project serves, and ARCHITECTURE.md, the shape it has to keep. Both are inputs to your packet; neither is yours to change.

Stamps live in the marker, never in the file

A pushed file stays clean Markdown with no injected metadata. .project-context.json records each one's sha256, its source commit and the time it was pushed; the doctor recomputes the digest and compares. A mismatch is pushed-file-modified, an error that names where the change belongs. A file under a pushed prefix with no stamp at all is only a warning — a hand-added file is worth naming but is not a broken install.

If you disagree, raise a question

Your edit is not quietly reverted, either. The Hub recorded that file's hash when it sent it, so the next push sees the change and treats it as a conflict — which stops that push to your repository entirely, not only for the file you touched. The edit blocks the owner rather than persuading them, and they still never learn what you disagreed with. Raise a question in your own project-context/, or capture a proposal: it reaches the owner the next time they pull, and it arrives with the project context that explains it.


10 keeping up to date

update, and how to read its dry run

Install is create-only for everything, which is right for your records and means it carries nothing forward — re-running it upgrades nothing. update is the other half, and it is local only: nothing in it reaches a network.

the exact plan · writes nothing
project-context update --dry-run
apply it, once you have read the plan
project-context update --apply

Read the dry run by authorship. Every line names an action and a path, and the action tells you whose file it is:

Action Whose What it means
refresh ours SKILL.md, the installed skill and its scripts, the managed blocks and the marker's own fields, brought to the current release. Differing from the release is exactly what a stale copy does.
regenerate_index ours A registry index rebuilt. Only the block between the index markers is replaced; the entries it is built from are the untouched part of the same file.
unchanged ours Already matches this release. Running update twice changes nothing the second time.
create yours A scaffold file your install predates, added now. It is a new empty record, never a replacement for one.
preserve_existing yours A record you wrote, left exactly as it is. Seeing one is the command working correctly.
the pushed set is reported, never planned

Update never writes to global/ or blueprint/. Each copy is verified against its stamp and reported: a pushed-file-modified entry means someone edited a file the Hub sent, and the fix is a question, not an edit. The marker is preserved rather than rewritten, so push stamps and any key a later release wrote survive the upgrade — and the doctor runs at the end and reports what it found.


11 not built yet

Named here so you do not go looking

Four gaps, current against the code rather than against a wish list. Two are simply not written yet; two are deliberate and are not coming.

Automatic end-of-session capsules

Writing a capsule automatically at the end of a session that produced none. capture is manual today: the Stop hook runs the trigger gate and offers it, rather than doing it for you.

A promote command

Promotion is editing — write the registry entry, then set the capsule's status. There is no one-liner for it yet, and the standing review is what keeps an unpromoted capsule visible in the meantime.

An index over the questions registry

The generator rebuilds tables for DECISIONS.md and LEARNINGS.md only. Questions are surfaced by the standing review instead, which is the right instrument: a question matters by its age, not by its position in a table.

Cross-project retrieval

Assembling context across several projects at once. That belongs to the Hub side and is deliberately not on the repository side: your packet sees this project and the pushed global snapshot, which is the correct blast radius.


12 rules that will not change

Four you can build on

Not preferences, and not a current release's behaviour. These are the constraints the product is built under, and a change to any of them would be a different product.

Create-only, and preview before apply

Every command takes --dry-run and --apply, and the dry run prints the exact plan. Nothing overwrites a record. A file you may have edited is skipped, and the skip is reported rather than swallowed.

Records are never rewritten by tooling

Correction happens through status and supersession links. Completed records are preserved, and history stays readable as historical evidence.

Standard library only

Zero runtime dependencies, in any script, ever. A repository adopting this must not acquire a dependency by doing so — and nothing here reaches a network, creates a remote, pushes, or invites anyone.

We own our markers and nothing outside them

The managed block between <!-- project-context:start --> and <!-- project-context:end --> is ours. The rest of your CLAUDE.md or AGENTS.md is none of our business, and an install that rewrote it would be a bug however good the intent.


13 next

Where to go from here

This page is the contract. The loop is what turns it into a habit, and the Hub is the other half for organisations that run one.

Clarity comes with context.