Skip to content

Decision log

An entry records a constraint that future code must obey, that someone could break without knowing, and that a test or an internal/archtest rule keeps. A choice of format, library or behavior is no entry: its reasons belong in the pull request that made it, and the behavior in the concepts, guides and reference pages that describe it. When in doubt, write no entry.

Each entry is a file under docs/src/content/docs/architecture/decisions/, with a title, a one-sentence description, and the lines Context, Decision, Consequence and Enforced by, which names the tests that keep it (TestEveryDecisionNamesATestThatExists). A pull request that changes a constraint changes its entry. make docs-ref writes the table below from the entries.

DecisionWhat it holds
A draft is what its proposer declaresA draft carries its purpose, delivery and effects; nothing follows from its kind, and a draft that posts waits for a person.
A gate’s consequence runs onceA resolved gate records when its consequence ran; the tick reruns one a stopped daemon left, and every consequence is idempotent.
A provider is asked what it can doA plugin declares its capabilities, and a provider id appears only in its plugin and the tables.
A task’s facets are the daemon’sThe daemon computes a task’s facets from its facts, brief and units’ rows, and a unit’s move re-sends the tasks holding it.
A task’s history is keptRetention never deletes the events, turns, records, answers or plan items of a task or of a unit a task holds.
Architecture rules are testsEach architecture rule is a test with a self-test, and its reference page is generated.
Authority comes from youEndorsing, approving and acting for you come from your surfaces or a session you drive, never from an unattended agent.
Behavior lives in the profileBehavior is profile files; Go provides a closed vocabulary and never enumerates intents.
Clients are thin and speak one versioned APIThe daemon computes what clients show; clients speak internal/api and decide nothing.
Every spawn is meteredA package that spawns a process imports the meter, which counts each boundary call.
Gates hold every decisionA decision the loop could wait on is a gate; you decide which wait for you, no rule that releases one reads an untrusted fact, and no unattended agent resolves one.
Go reads categories, never statesThe profile names a task’s states; Go and the store’s SQL read only the closed set of categories.
One adapter reaches each outside toolEach outside tool is reached by one adapter, and its name stays there and in the root.
One brand packageEvery user-visible name derives from internal/brand.
One daemon writes the storeOnly the daemon, and the store commands under its lock, write the store.
One engine plans automatic work, and planning writes nothingWorkflows are the only planner of automatic work, and evaluating a plan writes nothing.
One grammar for the profile languageEvery reserved key of the profile and the configuration is a row of builtin.Grammar.
Only the gates service resolves a gateEvery decision waiting on you is a gate, closed only by you, a trusted policy rule or a passed window.
Only the window adapter imports WailsThe native window is one adapter behind the window client’s port, and only it imports Wails.
Ports and adapters, dependencies inwardThe domain and the core import inward only; adapters implement the core’s ports.
Small ports owned by their consumerA port belongs to the core package that uses it and has at most five methods.
Sources are pluginsA source is a plugin, a process of its own that imports only the SDK; the host names it in one file, and neither side imports the other.
Text an agent reads resolves through the profileEvery text an agent reads is a profile file a user profile can replace.
The lists are the profile’s viewsGo names no view and no task property; it reads views.toml’s conditions and a property’s role.
The plugins declare their vocabularyThe builtin tables hold the core’s rows only; each plugin’s facts, effects, roles and kinds come from its manifest.
The read model lives in memoryEvery write notes the change log the in-memory read model rebuilds from.
The store is the truthManagers and boards receive a projection of the store and are never read back as state.
The store migrates forwardEvery schema change is a new version with a migration, and a newer store is refused.
Units generalize the pull requestWork is keyed by unit.Unit, and only a provider’s codec mints and parses a key.
What the loop proposes to post is a draftWhat the loop proposes to post is a draft, published once and only when approved.
Workflows trigger each other in a bounded chainEvery verb records its event with its actor and its chain, and an event trigger stops at its depth and never re-enters its chain.