| layout | home | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| hero |
|
Coral is a set of rules for organising code in a repository. It is written to be followed by coding agents as well as by people, so the rules are stated explicitly, numbered, and most of them are checkable by a program rather than by argument.
The organising principle is one sentence: one trigger, one owning capability, end to end.
A capability is one thing the software does — one command, one HTTP endpoint, one event handler. The unconditional part of Coral is about ownership: whatever answers that trigger owns the whole of answering it — parsing, validating, doing the work, returning the result, and the tests that prove it — and every unit of code has one of five known roles.
Ownership is not the same as physical colocation, and Coral separates the two deliberately. Where
that owned behavior sits — code grouped by what it does, not by what kind of code it is: packages
named for the capability or concern they own, no global handlers / services / repositories layer,
tests beside the code they verify — is Coral's production baseline. It is
optional and adopted explicitly, and most projects that want Coral want it; but a project
can own its triggers end to end without taking on Coral's opinion about what the directories are called.
A small expense-tracking CLI with two commands, expenses add and expenses list. It shows a project
that has taken Coral's production baseline — an optional layer, adopted explicitly — so
the colocated tests, the root crosscuts constructed once and passed in, and the absence of a
handlers/services/repositories layer are that layer's policy rather than the minimum a Coral
codebase owes. What Coral asks without adopting anything is a much shorter list, and the section
below says which part is which.
expenses/
__main__.py entry point — argv in, exit code out
app.py registers the commands, constructs the shared parts, injects them
errors.py the error taxonomy, defined once
money.py parsing and formatting money, defined once
db.py connection and transaction handling, defined once
expense/
add.py the whole of `expenses add`
add_test.py its test, next to it
list.py the whole of `expenses list`
list_test.py
Everything expenses add does lives in add.py: reading the arguments, validating them, writing the
row, returning the result. Its test sits beside it.
There is no handlers/ directory holding the argument parsing, no services/ directory holding the
logic, and no repositories/ directory holding the SQL.
The same shape holds for a backend: one directory per endpoint or event handler, with its tests, and the shared parts constructed at startup. An HTTP endpoint in Go works through the case where a language's own rules — import cycles, code generation — force one capability to span more than one package, which stays legitimate as long as every package is named for that capability.
Every file above is one of five things, and knowing which one you are writing answers most questions about where to put it. The five are the unconditional part — in a Coral codebase every unit of code fits one of them, whatever else the project has adopted. That is a classification, not a checklist: a given app need not contain all five, and small ones usually do not. How each category is then built is where the optional layer starts, and this section says which is which.
A slice is one capability, complete: add.py, list.py. Most of a codebase is slices.
A crosscut is one concern that several slices need: money.py, errors.py, db.py. Code does not
become a crosscut just because it appears twice — it has to be genuinely cross-cutting and carry an
invariant that would be a bug if the copies drifted apart, which is one of the rules that applies to every
Coral codebase. Duplication that fails that test is left alone deliberately. The production baseline
adds the discipline around it: give each one a precise name, and pass it in rather than letting a slice
reach for it.
The composition root is where the parts are brought together and started: app.py. The baseline adds
that it stays thin — registering, constructing and wiring, with no business logic of its own.
A published contract is the part other code is allowed to depend on. For this CLI it is the exit
code, the separation of stdout from stderr, and the shape of the --json output.
An adapter is the code that speaks to one specific piece of infrastructure — a database driver, an S3
client, a payment API. Small apps often have none: the CLI above has none, because a db.py crosscut is
enough. The baseline adds the part that does the real work: the slice declares the interface it
needs, the adapter implements it, and the dependency points from the adapter to the slice. Turn that
arrow around and you have a repositories layer, where one shared package decides what every caller
gets.
The italicised additions above are production baseline rules. A project that has not adopted that layer still classifies every unit of code with the same five categories; it simply owes none of the discipline in italics.
There is a sixth thing most codebases have, and it is not one of the five: a directory named for nothing
in particular — utils, shared, common, services, helpers. Coral calls that a forbidden
bucket, and when code does not obviously belong to a slice the answer is either a crosscut with a real
name, or leaving the duplication alone. The rule that actually bans one is [BUCKET-1], which a linter
can decide on its own — and it belongs to the production baseline, an optional layer described below,
rather than to the core of Coral, because it is good engineering whoever writes the code.
An app is one deployable unit: many slices, one composition root, one set of crosscuts. A system is several apps. A channel is the pathway between two apps, and the contract governing what crosses it.
One shape repeats at all three sizes:
- Own your trigger end to end — the one request, command, or event you answer.
- Consume another unit through what it publishes, never by reaching into its internals; share a concern by holding it in one definition rather than copying it.
- Between apps, that published surface is a channel.
That is the whole vocabulary: eight nouns — slice, crosscut, adapter, composition root, published
contract, app, system, channel. CONVENTIONS.md defines each one precisely, and every other document
refers back to it rather than restating it.
The nouns are the vocabulary; the production discipline around them is a separate, optional decision.
"Never a utils bucket", "apps never share a database", "a channel is versioned and takes one of three
forms", "crosscuts are injected rather than reached for" are real Coral rules — and they belong to the
production baseline, which a project adopts explicitly. The
section below draws the line.
Coral assumes a coding agent writes most of the code and a person reviews it. Four consequences follow, and they are what the kernel — the small set of rules Coral imposes on every Coral codebase — traces back to.
A slice fits in one context window. An agent can read everything a change depends on at once, rather than discovering afterwards that it never loaded some of it.
A change is confined to one slice. The reviewer's job has a known size before they start reading.
Placement is decided by the structure. "Where does this go?" has one answer, so it stops consuming judgment — in the prompt and in review alike.
Every slice can be checked from outside itself. The expense CLI's test runs the real command against
a real database and asserts on the exit code and the --json payload — the same contract a user of the
tool depends on. An agent can run that and see whether the change worked, rather than reporting that it
should have.
A rule is a kernel rule when its presence, or the strictness Coral states it at, materially comes from those four consequences — remove the agent-author premise and Coral would substantially relax it. That subset is named and justified in the Coral kernel.
Most of what Coral publishes is not that, and does not claim to be. Error taxonomies, transaction
scope, retry semantics, cache invalidation, concurrency strategy, configuration, trust boundaries, HTTP
status codes — these are load-bearing because the software needs them, and their justification survives
a human-authored codebase. Coral publishes them as the production baseline and as per-app-type
profiles: opinionated, coherent, and optional. A project takes them on deliberately, in its
CORAL.md, or takes none of them and is still a Coral codebase.
Some domains have one central concept that every feature reaches into: a tax engine, a scheduler, a
pricing solver. Splitting those by capability cuts across the thing that actually holds the complexity,
and the result is worse than a conventional layout. Coral states this as a rule rather than a footnote —
[SCOPE-2] — and the recommendation there is to use something else and say so.
To see the rules applied before reading them, a real backend service reviewed against Coral is the shortest route: what the service already did right, two problems the rules surfaced, and three places where following Coral would have been wasted effort.
Otherwise, in order:
CONVENTIONS.md— the vocabulary, the rule numbering, the enforcement classes, how a project declares how much of Coral applies to it, and how it records where it knowingly deviates.ARCHITECTURE.md— the kernel-facing app architecture: the shape of one app, and the rules that bind every Coral codebase without being adopted. Short.PRODUCTION.md— the production baseline for one app: the long, opinionated production-engineering layer. Read it to decide whether you want it; it applies only once yourCORAL.mdsays so.SYSTEM.md— how separately-built apps compose over a channel. Also optional, and two independent opt-ins: the system-scale baseline, and the runtime-agent orchestration rules.- Appendices — one document per app profile: CLI, backend, web, library, GitHub Action — plus the runtime-agent addendum, which an app of any shape adds when it calls a model at runtime.
- Worked examples — real code, in Python and Go.
Every rule carries an ID like [DUP-2] and exactly one enforcement class: [auto] if a linter can decide
it, [review] if it needs a person's judgment, [guide] if it is rationale rather than a pass/fail gate.
On this site each citation links to its definition, and the build fails if a citation has no definition, if
a rule has no class, or if a published ID disappears — so the documents' internal consistency is checked
rather than trusted.
Not all of it applies to you, and which part does is your decision, not ours. A small set — the
kernel — applies to every Coral codebase. Everything else, the production
baseline included, applies because a project's CORAL.md says it does: the app profiles it has taken on,
whether it wants the baseline, whether it calls a model at runtime, and whether it is one app or several
apps composing. Adding a rule or a profile here changes nothing for an existing project until that project
adopts it. The rules for that are in
what applies to a project.
If you are looking for one rule rather than reading through, the rule index has every one of them on a single page — ID, class, and a one-line statement, generated from the documents themselves.