These pages describe what Apograph does. The repository’s Architecture Decision Records describe why, including what was rejected and what it cost.
They live in docs/adr/ and are worth reading before proposing a change to
anything structural.
| Record | Decided | Status |
|---|---|---|
| 0001 | Record architecture decisions | Accepted |
| 0002 | Plugin-based architecture | Accepted |
| 0003 | Tactical DDD inside plugins | Proposed |
| 0004 | Model-agnostic copilot provider | Proposed |
| 0005 | The copilot acts as its user, never as itself | Proposed |
| 0006 | The CMS is an MCP server, over one shared tool registry | Proposed |
| 0007 | One tool registry, two surfaces | Proposed |
| 0008 | GraphQL is a protocol adapter over the public content API | Accepted |
| 0009 | The copilot asks in the moment, then applies directly | Proposed |
| 0010 | Skills are prompt configuration, authored in two places | Proposed |
| 0011 | Rich text is a structured document, not an opaque string | Proposed |
| 0012 | One storage provider per deployment, passed as one object | Accepted |
| 0013 | SSO is a provider port, and the core owns the handshake | Proposed |
| 0014 | Export and import as a separate plugin, one hop deep | Accepted |
| 0015 | Alarms flag content, and never block a write | Accepted |
| 0016 | Webhooks deliver from a queue, never from the outbox subscriber | Accepted |
| 0017 | Publication is protected per content type, and an approval belongs to a revision | Accepted |
“Proposed” here means the record has not been formally ratified, not that the decision is unimplemented — every one of these is in the shipped code.
The ones that explain the most
0002 — plugin architecture. Why there is a host that owns nothing, and why capability lives in plugins. Everything about extending Apograph follows from this.
0005 and 0009 together — the copilot’s authority. 0005 established that a run has no identity of its own and holds exactly its user’s permissions. 0009 then amended it: the queue of proposals waiting to be accepted is gone, replaced by a prompt that appears before the call runs.
Reading 0009’s context section is the clearest statement of why a review step that produces twelve approvals for one sentence is worse than no ceremony at all. It is also the honest account of a feature being removed after shipping.
0008 — GraphQL as an adapter. Why there is no second implementation of the content API, and why the GraphQL schema is built per workspace grant set — so introspection cannot enumerate types the workspace was not granted.
0011 — rich text as a document. Why a body is a node tree rather than an HTML string, and what that makes checkable.
0015 — alarms never block. Why a severity has no authority, and why the moment it could refuse a publish there would be two competing authorities on whether an entry is valid.
0016 — webhooks deliver from a queue. The outbox dispatcher calls its subscribers inside its claim transaction, so a subscriber that made an HTTP request would hold a pool client for a stranger’s response time, and a throw would count against the outbox row’s attempts — eventually parking an event the activity log was also waiting for. So the subscriber only queues a delivery row per endpoint, and a worker POSTs with nothing open. Read it before writing any subscriber that reaches the network.
0017 — publication protection. Why an approval is bound to a revision rather than to an entry, so an edit leaves it behind with no dismissal logic; why there is no third entry status; and why there is no approve tool for the copilot or over MCP, now or later. It is also the record that had to answer 0009 — reading the two together is the clearest account of when pausing on a human is worth its ceremony and when it is not.
Amendments
Two records have been amended rather than superseded, and reading only the original will mislead you:
| Record | Amended by | What changed |
|---|---|---|
| 0005 §5–6 | 0009 | Writes no longer queue as proposals for later acceptance |
| 0005 §3 | 0007 | The tool catalogue became the shared registry, not a copilot-private one |
Everything else in 0005 stands — and carries more weight than before, since the capability profile and the prompt are now the whole of the enforcement.
Where else the reasoning lives
| Location | What it holds |
|---|---|
docs/adr/ | The decisions themselves |
docs/design/ | Design docs for work proposed but not yet built |
AGENTS.md per package | How that package works, and why it is that way |
.agents/skills/ | Authoring conventions, as machine-readable skills |
.cursor/BUGBOT.md | Recurring bug patterns reviewers watch for |
The per-package AGENTS.md files are the most detailed material in the project
by a wide margin. They are written for AI agents, which in practice means they
state the reasoning behind a decision and the failure it prevents — including
several bugs that shipped and were then fixed.
If a page in this documentation left you wanting more detail about a specific package, that file is where the rest of it is.