From e6304bb66a12408bf8af5c090b48ff8e73227a73 Mon Sep 17 00:00:00 2001 From: "Reichenbach, Michael" <755327+Silthus@users.noreply.github.com> Date: Mon, 12 Oct 2026 11:03:36 +0000 Subject: [PATCH 1/2] Adoption: explain how Coherence works before asking the user anything An adopter's agent asked them which invariants to declare before anything had said what an invariant is. The adopt practice now has the agent walk the user through docs/how-coherence-works.md (one page, a diagram and the words they will hear) before its first question, and propose each invariant in plain words: the rule, what breaks if it goes false, and how it would be checked. The page ships in the package, and a test keeps every doc a kernel practice sends an adopter to in package.json's files. Co-Authored-By: Claude Opus 5.5 --- ...04d60773-e8c7-4707-9a73-c0cf5e52af76.jsonl | 5 ++ README.md | 5 ++ docs/how-coherence-works.md | 64 +++++++++++++++++++ package.json | 3 +- src/lifecycle/Lifecycle.practice.md | 6 +- src/spec/practice.test.ts | 8 +++ 6 files changed, 88 insertions(+), 3 deletions(-) create mode 100644 .coherence/journal/04d60773-e8c7-4707-9a73-c0cf5e52af76.jsonl create mode 100644 docs/how-coherence-works.md diff --git a/.coherence/journal/04d60773-e8c7-4707-9a73-c0cf5e52af76.jsonl b/.coherence/journal/04d60773-e8c7-4707-9a73-c0cf5e52af76.jsonl new file mode 100644 index 00000000..73bb0a86 --- /dev/null +++ b/.coherence/journal/04d60773-e8c7-4707-9a73-c0cf5e52af76.jsonl @@ -0,0 +1,5 @@ +{"id":"df-729d4e68","kind":"defect","at":"2026-10-12T10:53:09.008Z","session":"04d60773-e8c7-4707-9a73-c0cf5e52af76","agent":"main","commit":"6cb2b32","dirty":false,"what":"Adoption asked the human which invariants to declare before anything had said what an invariant, a chokepoint or a refutation is, or how Coherence works","evidence":"An adopter set Coherence up in two projects by handing their agent the repository; the agent's first questions to them were which invariants to declare, with no explanation of the terms or of what the invariant set buys them, and they found it confusing (reported in a T3 thread on 2026-10-12)","files":["src/lifecycle/Lifecycle.practice.md"],"introduced":"pre-existing","caught":"adopter","binding":"none: this session owns no active work order"} +{"id":"d-618a16da","kind":"decision","at":"2026-10-12T11:03:19.235Z","session":"04d60773-e8c7-4707-9a73-c0cf5e52af76","agent":"main","commit":"6cb2b32","dirty":true,"chose":"Close df-729d4e68 on its instance: the adopt practice explains how Coherence works from docs/how-coherence-works.md before its first question to the user, and proposes each invariant in plain words","over":["a guard over every question a practice puts to the user"],"because":"adoption is the one place an adopter first meets Coherence's words, and whether an explanation is plain is a judgement no detector checks; the step and its pitfall reach the agent whole each time the practice fires, and a test keeps every doc a kernel practice names in the package","cites":["df-729d4e68"],"binding":"none: this session owns no active work order"} +{"id":"d-941135c0","kind":"decision","at":"2026-10-12T11:03:19.563Z","session":"04d60773-e8c7-4707-9a73-c0cf5e52af76","agent":"main","commit":"6cb2b32","dirty":true,"chose":"Leave this session's run file out of the commit","over":["committing the run recorded in this worktree"],"because":"the worktree had no Python venv, tach or pytest and its git refused a tag fixture, so 7 totality oracles failed there and on the untouched base alike; the change touches no enforcement, and a run that grades them structural defects would misreport main","binding":"none: this session owns no active work order"} +{"id":"rs-f1246851","kind":"resolution","at":"2026-10-12T11:03:27.205Z","session":"04d60773-e8c7-4707-9a73-c0cf5e52af76","agent":"main","commit":"6cb2b32","dirty":true,"of":"df-729d4e68","because":"the adopt practice now explains how Coherence works from docs/how-coherence-works.md before asking the user anything, and proposes each invariant in plain words; the sweep of every practice and README prompt found one other question to the user, the CI mode, which already follows the explanation","decision":"d-618a16da","binding":"none: this session owns no active work order"} +{"id":"en-bd0ca324","kind":"enactment","at":"2026-10-12T11:03:27.732Z","session":"04d60773-e8c7-4707-9a73-c0cf5e52af76","agent":"main","commit":"6cb2b32","dirty":true,"practice":"src/journal/close a defect","version":"45e4c8e1","trigger":"command cli.* defect","steps":[{"text":"classify it: record the defect with --class, --introduced and --caught, or classify one already recorded (classify ); a class declared nowhere is declared once first, as the property class of defect (lexicon propose define defect)","leaves":"the defect's or the classification's id"},{"text":"sweep the codebase for siblings of the same shape (the same comparison, call or pattern wherever else it is written) and fix each one, then record the sweep: what was searched and every sibling it found, or that it found none","leaves":"the sweep: what was searched and each sibling found and fixed, or none"},{"text":"guard or decide: name an invariant whose enforcement covers the whole class (a chokepoint, or a totality oracle over the class whose test fails on any member), adding the bullet when none exists; or record a decision saying why fixing the instance suffices","leaves":"the guard's / or the decision's id"},{"text":"refute the guard: witness a refutation of it, staging a break of the class's shape rather than replaying the instance","leaves":"the refutation's run record"},{"text":"record the close: resolved --because \"\" with --guard / or --decision , then run spec --check","leaves":"the resolution's id; spec --check names no close with neither"}],"pitfalls":["the symbolic-link spelling was fixed where the upgrade test found it, and only a sweep of the same comparison found the second site, in practice delivery's cd routing (df-ccfd4309, 4b1890f)","a second door opened past the warm server's chokepoint reached main because nothing that ran before the merge checked chokepoints; a guard protects only where it runs (df-5961bdb2)","an ownership defect was fixed for one kernel practice when its class was every kernel practice of every release; the guard that held was the one that read them all (df-36efa8f2, 4b1890f)"],"results":{"1":{"result":"deviated","because":"df-729d4e68 recorded with --introduced pre-existing --caught adopter and no --class: no declared class names an adoption that speaks to the user in words it never explained, and one instance does not justify a lexicon change"},"2":{"result":"done","evidence":"searched every practice file and the README for a question put to the user (ask the user, ask me, ask them); one sibling, the CI mode question, already follows the new explanation step, so none needed a fix"},"3":{"result":"done","evidence":"d-618a16da"},"4":{"result":"skipped","because":"closed by a decision, not a guard; the package test that keeps the named doc shipped was seen red before the doc was added"},"5":{"result":"done","evidence":"resolution recorded; spec --check names 0 problems"}},"binding":"none: this session owns no active work order"} diff --git a/README.md b/README.md index e81c4948..ba8aad8e 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,10 @@ Then run `npx --no -- coherence query practice "adopt Coherence"`, work through Ask me whether pull requests should fail on Coherence's findings or get a report comment, then run `npx --no -- coherence ci install --host github --mode fail` (or `--mode comment`) with `--write`. ``` +Before asking you anything, your agent explains how Coherence works from +[docs/how-coherence-works.md](docs/how-coherence-works.md): one page, with a +diagram and the few words you will hear. + It needs Node 22.18 or newer on macOS (Apple Silicon) or Linux. The package is [`@posthog/coherence`](https://www.npmjs.com/package/@posthog/coherence) on npm, published from this repository with provenance; the dev dependency @@ -635,6 +639,7 @@ nothing. The workflow can also be run by hand from the Actions tab. ## Further reading +- `docs/how-coherence-works.md`: one page on how Coherence works and the words it uses, for a person new to it. - `Coherence.spec.md` and the `*.spec.md` file in each folder under `src/`: Coherence's own trust levels and invariants, in its own grammar. - `docs/lexicon.json`: the settled vocabulary; `docs/lexicon.md`: why the lexicon is first-class, with the evidence. - `docs/spec.md`, `docs/enforcement.md`, `docs/journal.md`, `docs/economy.md`, `docs/observation.md`, `docs/scope-shell.md`: how each part works. diff --git a/docs/how-coherence-works.md b/docs/how-coherence-works.md new file mode 100644 index 00000000..533f611d --- /dev/null +++ b/docs/how-coherence-works.md @@ -0,0 +1,64 @@ +# How Coherence works + +Agents write code faster than anyone can read it, and they cut corners where +you can't see them. Coherence keeps the few rules your project must never +break written down next to the code, checks them while the agent works, and +shows you which ones hold. + +```text + you and your agent agree on a rule + │ + ▼ + spec (src/billing/Billing.spec.md) the rule, why it matters, + │ and how it is checked + ▼ + hooks, on every edit ──── broken? ───► the agent hears it in the same turn + │ + ▼ + journal and Scope what was decided and found, + and a map of the project you can open +``` + +## The words you will hear + +- **Requirement**: a rule the project must keep, written in a component's + spec with the reason it matters. For example: "every read of an invoice + checks that it belongs to the signed-in account." Nothing checks it yet. +- **Invariant**: a requirement with an enforcement that has been seen to + fire. Until then the rule is only written down; after, it shows up the + moment it breaks. +- **Enforcement**: what checks the rule. Two kinds: + - a **chokepoint**: the one function every use of something must go + through, such as `invoices.forAccount()` in front of the invoice table. + Coherence asks the language server for every reference and flags any + that go around it. No test to write. + - a **totality oracle**: a test that checks the rule over a whole set, for + rules no single function can guard. +- **Refutation**: the proof that the enforcement works. The rule was broken + on purpose and the check went red. For a chokepoint, Coherence stages the + bypass itself without touching your files. +- **Entrance**: where work comes in from outside: a route, a command, a + queue consumer. A **spec gap** is an untrusted entrance with no control + traced on its way in. +- **Journal**: the project's record of decisions and defects, so the reason + for a change outlives the chat it was made in. +- **Practice**: a method the project keeps, like adopting Coherence. Its + steps reach the agent when its commands run. + +## Why a short set of invariants + +You can't review everything an agent writes. You can name the handful of +rules that would hurt most if they broke: who may read whose data, money +that must add up, a boundary between two parts of the system. With those +as invariants, an agent that breaks one hears it at the edit, not in review, +and Scope shows you which ones hold today. + +During adoption your agent proposes these rules. For each it tells you the +rule, what goes wrong if it breaks, and how it would be checked. Confirm, +reword or drop each one; a few that matter beat many that don't. + +## Questions to ask next + +- "Which invariants does this project have, and which are broken?" +- "Show me the project in Scope." +- "Why did we decide that?" (the journal keeps the answer) diff --git a/package.json b/package.json index 3e28f291..cc418e99 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,8 @@ "docs/enforcement.md", "docs/spec.md", "docs/plugins.md", - "plugins" + "plugins", + "docs/how-coherence-works.md" ], "optionalDependencies": { "pyright": "^1.1.414", diff --git a/src/lifecycle/Lifecycle.practice.md b/src/lifecycle/Lifecycle.practice.md index bc534792..b4af9e14 100644 --- a/src/lifecycle/Lifecycle.practice.md +++ b/src/lifecycle/Lifecycle.practice.md @@ -38,6 +38,7 @@ when: explicit step: confirm the hooks are wired as install would write them: coherence hooks --check --host leaves: hooks --check exits 0 + step: before asking the user anything, tell them how Coherence works, in your own words and within one screen, from docs/how-coherence-works.md in the installed package: its diagram, what a requirement, an invariant, its enforcement and its refutation are, and what a short set of invariants buys them; say that you will propose the invariants for them to confirm, and invite their questions step: write coherence.config.json where the project lives: at the repository root for a whole repository; in a monorepo, in the folder you adopt, after coherence adopt from the root when the repository keeps a registry (a root config with projects), writing only the keys the folder overrides; name, language (a list when the folder spans Python and TypeScript, the first the primary), the folders that are not this project's code or prose under ignore (vendored code, generated output, fixtures), and, where the project has tests, test with testMatch and testJson, or tests, one setup per runner with its own cwd; every key is in docs/config.md in the installed package step: settle the vocabulary: coherence lexicon coverage, then declare the terms that carry the project's meaning (the practice settle a domain term) step: to settle one part of the project first, name its folders or files: coherence lexicon coverage src/billing docs, and coherence lexicon draft src/billing for its unsettled candidates; each path is relative to the root or absolute inside it, and the config's ignore list still holds inside it @@ -46,7 +47,7 @@ step: declare the components and where work enters them: the entry spec with its trust levels, one spec per component folder, then every entrance (the practice declare entrances); the grammar is in docs/spec.md in the installed package step: give each real unit its own spec at whatever depth it sits, not one per top-level folder: run coherence mass, and for each folder it names as having no spec of its own, write its spec or record with decide why it belongs to the component above; repeat until mass names none you have not decided leaves: mass names no folder without a spec or a decision - step: declare the few invariants that matter most (security, tenant isolation, data integrity), each through the practice declare a requirement; for a module boundary whose own code may use its internals, say which references the chokepoint governs (from: outside the component), and in a folder of a larger repository, where the boundary's callers live outside it, choose the references horizon in the config (a list of folders, or repository) + step: declare the few invariants that matter most (security, tenant isolation, data integrity), each through the practice declare a requirement, proposing each to the user first in plain words: the rule, what goes wrong for the project if it breaks, and how it would be checked; for a module boundary whose own code may use its internals, say which references the chokepoint governs (from: outside the component), and in a folder of a larger repository, where the boundary's callers live outside it, choose the references horizon in the config (a list of folders, or repository) step: run, then witness each refutation (the practice witness a refutation); a broken chokepoint is a finding to report with its bypass sites, not a failure to hide; a requirement the code already breaks stays a requirement, with a defect per site and an escalation, and a test that cannot run here is recorded as unable leaves: spec --check with 0 problems step: baseline what the project already held: coherence lexicon baseline, and coherence scaffold control --baseline for entrances with no traced control @@ -65,8 +66,9 @@ pitfall: a fresh adoption found its first requirements already false and its tests unable to run unattended, and had to improvise what to record (df-8ac95c4d) pitfall: in a monorepo, a session started at the repository root took the whole repository as the project: another team's specs reached orient, their practices fired, and .coherence was written at the root (df-0b68c987) pitfall: a chokepoint in a project nested below the repository top passed without searching the rest of the repository, where a product boundary's bypasses live; choose its references horizon (df-0d235229) + pitfall: the user was asked which invariants to declare before anything had said what an invariant is, or what Coherence does with one (df-729d4e68) pitfall: a bullet inserted between another bullet and its refuted:, kinds: or checklist: lines took them, so that invariant fell back to a requirement while spec --check reported 0 problems; append a whole bullet after another's last line (df-f3826eaa) - learned: d-96eb6814, d-127ab8e4, d-a1095ef2, f658594 + learned: d-96eb6814, d-127ab8e4, d-a1095ef2, f658594, df-729d4e68 reach: kernel because: the setup prompt was a method kept in prose, carried out once and reported in a chat reply that no later session reads; as a practice each step is delivered where its commands run, and the enactment is the durable record of how the project was adopted - settle a domain term: A term enters the project's lexicon as the right kind of thing (a concept, an alias, a property, or ordinary code) with the reason recorded, before code or specs use it. diff --git a/src/spec/practice.test.ts b/src/spec/practice.test.ts index 00e9bc20..986fa586 100644 --- a/src/spec/practice.test.ts +++ b/src/spec/practice.test.ts @@ -13,6 +13,7 @@ import { dirname, join } from "node:path"; import { after, describe, test } from "node:test"; import { commandMatches, firedBy, globMatches, parsePractices, type ToolUse } from "./practice.ts"; import { loadSpecModel, projectPractices } from "./model.ts"; +import { COHERENCE_ROOT, kernelPractices } from "./practices.ts"; import { journalVerbs, type Io } from "../journal/cli.ts"; import { appendRecord, loadJournal } from "../journal/store.ts"; import type { Enactment } from "../journal/record.ts"; @@ -409,3 +410,10 @@ test("in an adopter, a kernel practice a Coherence release changed asks no amend const floor = loadSpecModel(root, { runs: false }).problems.filter((p) => /is gone; a practice keeps what it taught/.test(p.message)); assert.deepEqual(floor.map((p) => p.message), [], "no floor gap is named for a kernel practice"); }); + +test("every doc a kernel practice sends an adopter to ships in the package", () => { + const shipped: string[] = JSON.parse(readFileSync(join(COHERENCE_ROOT, "package.json"), "utf8")).files; + const named = kernelPractices().flatMap((p) => p.steps.flatMap((s) => s.text.match(/\bdocs\/[\w.-]+\.md\b/g) ?? [])); + assert.ok(named.length > 0, "the kernel practices name the docs an adopter reads"); + assert.deepEqual([...new Set(named)].filter((doc) => !shipped.includes(doc)), []); +}); From fd6f78249199c61094a4a1b115491f2a5c034598 Mon Sep 17 00:00:00 2001 From: "Reichenbach, Michael" <755327+Silthus@users.noreply.github.com> Date: Mon, 12 Oct 2026 11:40:01 +0000 Subject: [PATCH 2/2] How Coherence works: say when each check runs, what makes an invariant, and what a spec gap spares A chokepoint bypass is reported at the edit; a totality oracle runs with coherence run and on each pull request. An invariant also needs its checklist answered. A spec gap counts unknown trust and spares an entrance with a recorded reason it needs no control. The package test now reads nested docs paths too. Co-Authored-By: Claude Opus 5.5 --- docs/how-coherence-works.md | 25 +++++++++++++++---------- src/spec/practice.test.ts | 2 +- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/docs/how-coherence-works.md b/docs/how-coherence-works.md index 533f611d..11a6925e 100644 --- a/docs/how-coherence-works.md +++ b/docs/how-coherence-works.md @@ -12,7 +12,9 @@ shows you which ones hold. spec (src/billing/Billing.spec.md) the rule, why it matters, │ and how it is checked ▼ - hooks, on every edit ──── broken? ───► the agent hears it in the same turn + hooks, on every edit ── bypassed? ──► the agent hears it in the same turn + │ + run, and CI on every pull request ───► each rule's test, and every chokepoint again │ ▼ journal and Scope what was decided and found, @@ -24,22 +26,25 @@ shows you which ones hold. - **Requirement**: a rule the project must keep, written in a component's spec with the reason it matters. For example: "every read of an invoice checks that it belongs to the signed-in account." Nothing checks it yet. -- **Invariant**: a requirement with an enforcement that has been seen to - fire. Until then the rule is only written down; after, it shows up the - moment it breaks. +- **Invariant**: a requirement whose enforcement has been seen to fire, + and whose ways of breaking (the checklist Coherence asks about each rule) + have been answered. Until then the rule is only written down; after, a + break shows up when its check runs. - **Enforcement**: what checks the rule. Two kinds: - a **chokepoint**: the one function every use of something must go through, such as `invoices.forAccount()` in front of the invoice table. Coherence asks the language server for every reference and flags any - that go around it. No test to write. + that go around it, at the very edit that adds one. No test to write. - a **totality oracle**: a test that checks the rule over a whole set, for - rules no single function can guard. + rules no single function can guard. It runs with `coherence run` and on + each pull request, not at every edit. - **Refutation**: the proof that the enforcement works. The rule was broken on purpose and the check went red. For a chokepoint, Coherence stages the bypass itself without touching your files. - **Entrance**: where work comes in from outside: a route, a command, a - queue consumer. A **spec gap** is an untrusted entrance with no control - traced on its way in. + queue consumer. A **spec gap** is an entrance whose callers are outside + your control or unknown, with no control traced on its way in and no + recorded reason that it needs none. - **Journal**: the project's record of decisions and defects, so the reason for a change outlives the chat it was made in. - **Practice**: a method the project keeps, like adopting Coherence. Its @@ -50,8 +55,8 @@ shows you which ones hold. You can't review everything an agent writes. You can name the handful of rules that would hurt most if they broke: who may read whose data, money that must add up, a boundary between two parts of the system. With those -as invariants, an agent that breaks one hears it at the edit, not in review, -and Scope shows you which ones hold today. +as invariants, a break shows up while the agent works or on the pull +request, not in review, and Scope shows you which ones hold today. During adoption your agent proposes these rules. For each it tells you the rule, what goes wrong if it breaks, and how it would be checked. Confirm, diff --git a/src/spec/practice.test.ts b/src/spec/practice.test.ts index 986fa586..6a0b262e 100644 --- a/src/spec/practice.test.ts +++ b/src/spec/practice.test.ts @@ -413,7 +413,7 @@ test("in an adopter, a kernel practice a Coherence release changed asks no amend test("every doc a kernel practice sends an adopter to ships in the package", () => { const shipped: string[] = JSON.parse(readFileSync(join(COHERENCE_ROOT, "package.json"), "utf8")).files; - const named = kernelPractices().flatMap((p) => p.steps.flatMap((s) => s.text.match(/\bdocs\/[\w.-]+\.md\b/g) ?? [])); + const named = kernelPractices().flatMap((p) => p.steps.flatMap((s) => s.text.match(/\bdocs\/[\w./-]+\.md\b/g) ?? [])); assert.ok(named.length > 0, "the kernel practices name the docs an adopter reads"); assert.deepEqual([...new Set(named)].filter((doc) => !shipped.includes(doc)), []); });