Features Apograph CMS on GitHub

Architecture decisions

Seventeen records covering why the system is shaped the way it is.

Documents 0.5.2 Updated Edit this page Report a problem

On this page

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.

RecordDecidedStatus
0001Record architecture decisionsAccepted
0002Plugin-based architectureAccepted
0003Tactical DDD inside pluginsProposed
0004Model-agnostic copilot providerProposed
0005The copilot acts as its user, never as itselfProposed
0006The CMS is an MCP server, over one shared tool registryProposed
0007One tool registry, two surfacesProposed
0008GraphQL is a protocol adapter over the public content APIAccepted
0009The copilot asks in the moment, then applies directlyProposed
0010Skills are prompt configuration, authored in two placesProposed
0011Rich text is a structured document, not an opaque stringProposed
0012One storage provider per deployment, passed as one objectAccepted
0013SSO is a provider port, and the core owns the handshakeProposed
0014Export and import as a separate plugin, one hop deepAccepted
0015Alarms flag content, and never block a writeAccepted
0016Webhooks deliver from a queue, never from the outbox subscriberAccepted
0017Publication is protected per content type, and an approval belongs to a revisionAccepted

“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:

RecordAmended byWhat changed
0005 §5–60009Writes no longer queue as proposals for later acceptance
0005 §30007The 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

LocationWhat it holds
docs/adr/The decisions themselves
docs/design/Design docs for work proposed but not yet built
AGENTS.md per packageHow that package works, and why it is that way
.agents/skills/Authoring conventions, as machine-readable skills
.cursor/BUGBOT.mdRecurring 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.