| name | grounding-rules |
| description | Distill a system or subsystem into a short, human-readable set of GROUNDING RULES — the minimal load-bearing invariants and design decisions that let a reader gain CONFIDENCE in the system in minutes and adjudicate future design choices, instead of reading a full spec for hours. Use this whenever the user wants the "essence" / "constitution" of a module, the load-bearing rules / single-source-of-truth / key invariants of a system, a first-principles explanation of how a subsystem really works (not API-reference docs), or types /grounding-rules. Three actions: a fresh grounding pass; `update <new facts or corrections>` (re-ground an existing doc when the system changes); and `ask <question>` (answer from the real code in the grounding voice). Favors compression and the good-faith reader: only rules that matter, that can't be inferred from the others, and whose plain reading makes the right choice the obvious one. Writes to a vault path → invokes obsidian-markdown. NOT for exhaustive API reference, tutorials, change logs, or edge-case fencing. |
Grounding Rules
What a grounding-rules document is
A grounding-rules document is to a system what a minimal set of axioms is to a
theory: the smallest set of statements such that, if you know them, you know the
system — almost everything else true about it can be derived with ordinary
reasoning.
It has two jobs, and every rule serves both:
- Teach the system fast. A capable engineer reads the rules in a few minutes
and walks away confident — the nouns, who owns what, how data flows, what can
never happen.
- Drive decisions. A new question ("can a job have two runners?") is answered
by checking it against the rules, the way you'd type-check against an invariant.
A good rule settles a whole class of questions.
This is not documentation and not a spec. Docs describe every surface and
rot as the API changes. A spec fences every case for a machine. Grounding rules
describe only the load-bearing skeleton, for a human, and barely move — because
they capture decisions, not surfaces.
The bar: a good rule makes the right choice the obvious one
This is the heart of the skill. A rule is written for a human acting in good
faith, to give them confidence quickly — not to make every wrong path
impossible.
- The test is "first choice goes right," not "wrong is impossible." A rule
earns its place if a developer's natural first instinct after reading it is the
correct implementation. A reader who contrives a perverse reading is not your
audience; don't write for them.
- Optimize for fast confidence over completeness. The win is a reader trusting
the system after a short read — not after an exhaustive one. A rule that doesn't
buy confidence quickly isn't pulling its weight.
- Rules are not a spec — if you're adding clauses to fence edge cases, stop.
Completeness and edge-fencing belong in the spec, the code, and the tests. The
moment a rule grows qualifiers to block a contrived misreading, it has rotted
into a spec, and nobody reads a spec for confidence. Keep the rule; move the edge
case elsewhere.
- A rule fits in your head and reads the way it should be built. Short enough
to hold; shaped so the obvious reading is the right implementation. If you
can't restate it in your own words, it's too detailed.
- A gap is a missing readable rule, not a missing clause. When something
broke, don't bolt qualifiers onto an existing rule. Ask: what one line would
have made the right choice obvious? — then write that line.
Ground every rule in the running system — never from memory
A rule is only as trustworthy as the source you verified it against. Memory,
plausible inference, a stale note, and a short spec are all hypotheses.
- Verify at the call site, not from recall. Before asserting a contract, open
the code that implements it (or pull the live response) and confirm. "I'm
fairly sure native uses the proxy path" is how a wrong rule gets written; the
one line
getSessionDetail() → ?format=signed is how it gets corrected.
- Pull live data before fixing the shape of a rule. The wire doesn't lie;
an assumed payload does. One real response often decides the whole design.
- When one surface works and another doesn't, diff them. The delta between the
working reference and the broken one usually is the rule you're missing.
- A correction means re-checking, not editing in place. A new fact can make an
old rule wrong, redundant, or duplicated — verify against code, then re-ground
the whole doc (see
update).
The two tests every rule must pass
Apply the code-simplifier mindset to the rule set itself: single source of
truth, no rule restates another, nothing present that could be inferred.
Test 1 — Load-bearing. If this rule were violated, would the system break or
the mental model be wrong? If violating it changes nothing anyone cares about,
it's trivia. Cut it.
Test 2 — Non-inferable. Could a reader derive this from the rules already
written plus ordinary reasoning? If yes, it's a consequence, not an axiom. Cut
it, or fold the surprising part into the rule it follows from.
What survives is small — typically 8–15 rules. Thirty means you're
documenting. The discipline of cutting is the whole value: a reader holds ten
load-bearing rules in their head; they cannot hold thirty facts.
Corollary — say each rule once. A rule in two places (a diagram caption and
a section) breaks its own single source of truth. The diagram names the
entities; the rules govern them. Same fact in two spots → a merge, not two
rules.
Where load-bearing rules cluster
You don't invent rules; you find them. Sweep the system through each lens:
- Single source of truth. For each important fact or piece of state: which
one place owns it? "X is the only place that knows Y" is the strongest, most
common grounding rule. Two owners → a bug, or a rule explaining the relationship.
- Boundaries — and their duals. Every line data crosses (LLM ↔ system, user ↔
user, public ↔ private, client ↔ server, store ↔ view) carries a rule about
what is allowed across and who enforces it. Crucially, a boundary rule
constrains traffic in one direction — so ask what crosses the dual
boundary too. "Only the durable handle crosses into the model" is silent on
what may cross into the renderer; the bug usually lives in the dual you assumed
was covered. Name both.
- Lifecycle & state. The legal states of the main entities and their
transitions — especially the absolutes: "immutable", "append-only", "soft-
deleted, never hard-deleted".
- Named patterns. When a known pattern is in play — append-only log, immutable
snapshot + moving pointer, discriminator column on a unified table, confused-
deputy defense, soft delete, durable handle + resolved projection — name it.
The name compresses a paragraph into a word.
- The one diagram. The entity/relationship skeleton. One small picture
usually grounds every other rule; draw it first.
Output format
Write to the path the user gives (often a Markdown note). Use this shape:
# <System> — Grounding Rules
> One sentence: what this system *is*, in plain language. The mental model in a breath.
## tl;dr
<The foundation rules as plain bullets — the handful a reader must hold to derive
everything else. Max two levels of nesting; no *why*, no anchors, no prose. If the
rest of the doc vanished, these bullets alone would still teach the system's shape.
The ultimate compression test: if you can't get the system down to ~5–10 foundation
bullets, you haven't found which rules are foundational. This REPLACES a separate
"foundations" group — everything below is just these bullets expanded.>
## The model
<The one diagram — entities and how they nest/relate, drawn first; every rule below
annotates it. NAME the nouns here (and the ownership boundary when two parts are
isolated); do not restate the rules. A reader should point at any noun used anywhere
in the doc and find it on this map.>
## Core operations
<The system's core verbs — "log", "instantiate", "recommend". Often better as a
tl;dr bullet; pull into their own section only when not obvious from the model.>
## <Group A — e.g. The nouns & their single source of truth>
**R1. <One-line declarative claim.>**
*Why:* <one or two lines — the reasoning that makes it load-bearing, and that lets
the rule drive a decision its author never foresaw.>
<optional anchor: where it lives, e.g. `models.py:Job.runner_id`>
**R2. ...**
## <Group B — Lifecycle / the ladder / boundaries / ...>
...
## Using these rules
<2–3 real design questions, each answered in one line by citing a rule. The proof
the rules are operative, not decorative.>
Writing into an Obsidian vault? When the target path is inside an Obsidian
vault, invoke the obsidian-markdown skill (Skill tool) and follow it: vault
frontmatter tags, callouts for the high-signal bits (a > [!info] callout for
the one-line mental model, > [!tldr] for the tl;dr, > [!example] for "Using
these rules"), and [[wikilinks]] only for notes that actually exist (plain text
otherwise). The rules stay terse; Obsidian syntax only carries the high-signal
framing. (obsidian-markdown is a soft dependency — if it isn't installed, write
plain Markdown.)
Rule-writing craft
- State the claim in one line, declarative or imperative. If you can't say it
in a sentence, it's two rules — or you don't understand it yet.
- Give the why when it isn't self-evident. A rule whose rationale a reader
can't reconstruct is brittle — people route around rules they don't understand,
and a bare rule can't stretch to a decision its author never foresaw. Add the
why exactly when that rationale is what lets the rule travel. When a rule is
self-evidently load-bearing, the terse assertion is the whole rule — don't pad.
- Group rules by the system's own seams. Theme works (model / lifecycle /
boundaries / invariants), but when two parts are isolated — no shared runtime,
data, or state — let that boundary split the doc; their rules live in separate
sections and never mix. The only rule allowed to span both is the one that
defines the boundary. Structure that mirrors the architecture teaches it for
free.
- Anchor lightly. A
file:symbol pointer per rule helps a reader verify and
find the code, but the body stays prose — never paste code blocks or signatures.
Anchors locate; they don't define.
- Number them (R1, R2…) so they're citable in review ("this violates R5").
What to cut (the same energy as deleting code)
- Anything inferable from a rule already written → a theorem, not an axiom.
- Anything whose violation wouldn't change a mental model or a decision → trivia.
- Exact API signatures, full column lists, request/response shapes, step-by-step
how-tos → reference documentation; it rots and buries the rules.
- Clauses that exist only to fence a contrived misreading → that's spec creep;
the rule's plain reading already serves the good-faith reader.
- Secondary mechanisms serving a primary rule (caches, denormalized columns, SSE
notifications) → mention only if a reader would get the model wrong without
them.
Process
- Get grounded in the system first. Read the code, or ask for / spawn a
research pass. You cannot compress what you don't understand — and you cannot
trust a rule you didn't verify. The rules are the output of understanding.
- Draw the one diagram — the entity skeleton. Everything hangs off it.
- Sweep the lenses and collect candidate rules freely — over-collect.
- Compress. Run every candidate through the two tests and the bar (does its
plain reading make the right choice obvious?). Merge overlaps, delete
consequences, name the patterns. This is where the document earns its keep.
- Add the why to each survivor, and write 2–3 "Using these rules" examples
to prove they're operative.
- Read it cold. Could a smart newcomer gain confidence in the system from this
alone, and settle a design argument with it? If not, a load-bearing rule is
missing or buried. Iterate.
Updating an existing doc (the update action)
/grounding-rules update <new facts or corrections> re-grounds an existing doc
instead of writing a fresh one. The trap: update is not append. A new fact can
make an old rule wrong, redundant, or duplicated, so re-grounding means
reconsidering the whole doc.
- Read the existing doc and treat its rules as the current axioms.
- Fold in the new facts. If the fact is a correction, re-check the code
first — a correction usually means a rule rested on a wrong assumption.
- Prefer a missing rule over a new clause. When the new fact is a gap the doc
didn't cover (often the dual of a boundary it already states), add the one
readable rule that closes it — don't graft qualifiers onto a neighbor.
- Re-run both tests over the merged set. A new fact may subsume an old rule
(delete), contradict it (fix it and everything that leaned on it), or
duplicate it (merge). Single source of truth, maintained over time.
- Preserve voice and structure. Change only what the facts require; keep the
grouping and terseness. The reader gets a sharper doc, not a different one.
- Re-read cold for orphans — a rule that only made sense because of one you
just deleted.
Asking about a grounded system (the ask action)
/grounding-rules ask <question> answers a question about the system from the
real code, in the grounding voice — brief, correct, load-bearing, with a small
example when it helps. The read-only counterpart to writing the doc.
- Ground it in code first. Never answer from memory, the doc, or a prior
research pass — open the code, confirm, cite
file:symbol. The value of ask
is that the answer is true now. If you can't verify a part, say so.
- Answer like a rule, not a tutorial. Lead with the one-line invariant, then a
few load-bearing specifics. Name the one or two places that decide the behavior;
skip the call-graph walk.
- Keep it brief and concrete. A tiny example — the actual shape of the thing —
beats paragraphs. Quote code only when the quote is the answer.
- Reconcile with the doc. If the code contradicts a rule, say so and offer to
update. If it confirms one, cite it ("this is C3"). If it's a new load-bearing
fact the doc is missing, flag it as a candidate rule.
A good ask often surfaces the next update.
Example (excerpt — shape, not content)
R5. Job.runner_id is the sole link between a job and the worker running it.
Set → claimed (and by which worker); NULL → unclaimed. Never both.
Why: one nullable FK on a unified jobs table means exactly one place to ask
"is this job claimed, and by whom?" — so the scheduler resolves the worker from
the row it owns, and no client can claim someone else's job.
(Pattern: discriminator column on a unified table.)
Notice: one-line claim; a why that explains the design (not restates the claim);
a named pattern; immediately usable — "can a job have two runners?" is answered by
R5 alone (no — that's a re-queue into a new job). And a developer's first instinct
after reading it ("resolve the worker from the job row") is the right one.