The .architecture system
- Ref
- FLD-46BD69
- Published
- 2026-04-26
- Kind
- DEVLOG
- Project
- Sidebar Agent
- Author
- Roman Sanine
An agent that wakes up cold each session has no memory of the project except what's in the repo. If the repo is just code, the agent re-derives intent from scratch every time — and gets it wrong, because intent isn't always recoverable from a diff. The .architecture/ directory is the workaround. It's the thing that makes session 2 competent.
The premise is one sentence: architecture is the source of truth, code reflects it. A bug isn't fixed by writing the patch — it's fixed by updating its yaml record with a fix_approach, then writing code that implements that approach. The patch is the consequence.
The shape
.architecture/
_config.yaml # topology: layers, domains, conflict detection
_cycle-state.yaml # current TDD phase per active feature
constitution.md # MUST / SHOULD principles
inbox/ # raw user reports, pre-triage
bugs/ # BUG-NNN.yaml records
bugs/archived/ # closed bugs, kept for reoccurrence detection
<layer>/<domain>/ # feature folders, one per active design decision
history.jsonl # append-only log of state transitions
_config.yaml declares the topology — layers (frontend, backend, integration, infrastructure), domains, the TDD cycle (red → green → refactor → runtime), and gates between feature states.
# _config.yaml (excerpt from decent-td)
topology_type: hybrid
primary_axis: layer
secondary_axis: domain
workflow:
tdd_cycle: [red, green, refactor, runtime]
gates:
feature_to_active: ['requirements_defined']
active_to_in_progress: ['design_approved', 'tests_planned']
in_progress_to_completed: ['all_tests_passing', 'acceptance_criteria_met']
The gates are enforced — a feature can't go in_progress → completed without all_tests_passing. Skipping that step requires marking it explicitly, which is auditable.
Two paths for new work
Trivial bugs go through hotfix. ≤2 files, ≤50 lines, single layer. The bug stays as a yaml record in .architecture/bugs/. The fix is direct, verification is fast.
# bugs/BUG-018-tower-turret-vertical-centering.yaml
id: BUG-018
title: Tower turret vertically miscentered on slope cells
status: fixed
severity: low
layer: frontend
component: game/Tower.vue
reproduction:
- place tower on a sloped grid cell
- observe turret model is offset ~0.3u above the base
fix_approach: |
Tower placement was using cell.center.y; should use
cell.surface.y (which accounts for slope normal).
One-line fix in Tower.vue:42.
verified_in: production
Complex bugs and features expand into feature folders with standard artifact names — REQ.yaml, DES.yaml, TODO.yaml, TEST.yaml. The names are not negotiable: an agent looking for "the requirements" should always look at REQ.yaml, never have to guess at auth-rewrite-spec.yaml vs auth_requirements.md.
.architecture/backend/multiplayer/lobby-sync/
REQ.yaml # what we're solving and for whom
DES.yaml # how we'll solve it (chosen approach + alternatives rejected)
TODO.yaml # ordered work items, each linked to a test
TEST.yaml # acceptance + unit + integration tests, with current status
Escalation criteria are concrete: cross-layer changes, architectural implications, anything needing a real design decision. A race condition that touches the websocket plugin and three components is not a hotfix.
Why the constitution exists
constitution.md is short, project-specific, and re-read at the start of any non-trivial task. For Decent TD:
### MUST (Critical - violations block implementation)
1. Server-Authoritative — game state lives server-side
2. Stateless Backend — server can restart without losing state
3. Real-time First — game interactions use WebSocket, not REST polling
4. Security by Default — endpoints require auth except public pages
Constitutions get violated by accident, not on purpose. A coding agent will reach for REST polling for "simplicity" if nothing tells it that's banned. The constitution is the thing that says.
The MUST/SHOULD split matters: SHOULD violations are allowed with a written justification logged in the feature's DES.yaml. MUST violations block implementation outright.
The bug ledger as memory
Closed bugs don't get deleted — they archive. When a new report comes in, triage matches against past bugs by error code, message, stack trace, or component location.
For example, BUG-011 was filed as "missing combat audio" — fixed, verified, archived. Two weeks later, a similar report came in. The matcher caught it: same component, similar symptom. Reopened with a reoccurrence_count: 2 flag. The investigation found the original fix had introduced an excessive debounce (BUG-013), which was the actual root cause. Without the reoccurrence link, BUG-013 would have been filed as a new bug and we'd have re-debugged the audio pipeline from scratch.
A bug that reoccurs twice is no longer a bug — it's a design problem masquerading as one. The ledger surfaces that.
Status discipline
The rule that costs agents the most: don't mark status: fixed until verified in production. Use status: deployed if you can't verify yet. The temptation to write the patch, run the tests, mark it fixed, and move on is constant — and a year of "fixed" bugs that quietly regressed is how a codebase rots.
For the website project, "verified in production" means the devtools-proxy loop: navigate to the affected route, console clean, screenshot matches intent. For Decent TD, it means a real game session reproduces the original report and the fix path doesn't trigger.
Resumable mid-feature work
_cycle-state.yaml is the file that makes session 2 work:
# _cycle-state.yaml
active_features:
- path: backend/multiplayer/lobby-sync
phase: green # red | green | refactor | runtime
last_test: TEST.yaml#unit-007
blockers: []
- path: frontend/game-ui/wave-banner
phase: refactor
last_test: TEST.yaml#integration-003
blockers: ['waiting on visual review']
A new session reads this, sees lobby-sync is mid-green, opens TODO.yaml, finds the next unchecked item, continues. No "what was I working on" reconstruction from chat history.
What this enables
A few patterns fall out:
- Multi-agent parallelism. Two agents working on different feature folders can't conflict if the topology config marks them in non-overlapping layers/domains. Conflict detection runs by proximity in the layer × domain space.
- Auditable history.
history.jsonlis append-only — every state transition (filed → triaged → in-progress → fixed → reopened) leaves a trace. "Why did we change this?" has an answer. - Triage is mechanical. New report → check against archived bugs → either reopen-with-reoccurrence or file new → match against severity rules → set initial gate. The agent doing triage is doing a checklist, not a judgment call.
The trade-off
The system has a real cost: a non-trivial slice of every task is yaml editing. For a single human writing code in flow, that overhead is irritating. For an agent that wakes up cold each session, that overhead is the memory. The yaml is what makes the agent competent on session 2, session 7, session 30.
The bet is that per-task overhead is repaid by every session that doesn't have to start from scratch. Three months in, that bet has held — and the bug reoccurrence detection alone has paid for the rest.