Architecture rules
This page is the definition of record for how aizk is put together. The eight rules came out of the architecture audit, each with its one-line rationale, and the import contracts at the bottom make the layering executable rather than aspirational. Tooling that automates architecture checks starts from exactly this page.
Layers
Section titled “Layers”Dependencies point strictly downward. A module may import anything beneath it and nothing above it.
flowchart TD
T[transports<br/>mcp, api, web] --> M[memory service]
M --> E[engines<br/>artifacts, background, graph, retrieval, extract, serving]
E --> S[store]
S --> C[config]
The diagram shows the spine. The enforced layer contract in pyproject.toml is exhaustive
over every top-level package, so the support packages (admin, auth, backup, cli,
common, exceptions, export, integrations, ontology, ops, provenance,
runtime, storage, types, and usage) each hold an explicit layer with the same
downward direction, and a new package fails the lint gate until it is assigned one.
config is a leaf that imports nothing internal, which its independent bottom-layer
placement enforces, so any module can read settings without pulling in the rest of the
engine.
The eight rules
Section titled “The eight rules”- SQL lives in the store. Every statement is composed inside
store, and transports and services read throughUser.execor a model method, so the complete query surface is auditable in one place. - Queries are model classmethods. A statement is a classmethod on the model that owns its
primary table or view, matching the
LiveFact.touchingprecedent, so the schema and the queries that depend on it change together. - Patos models over
__init__and manual validation. Pydantic validators and constrained types on thepatosbases replace hand-written constructors and raise-on-bad-input blocks, so invariants are declared once and enforced at every boundary. - Maintained libraries over hand clients. Generated or maintained clients replace hand-rolled HTTP and protocol code, so upstream fixes arrive by upgrade instead of by patch.
- Composition root over singletons.
runtime.pybuilds every shared service once from settings, so wiring is visible in one file and tests swap dependencies without patching globals. - Span-based usage. Usage accounting reads the spans the code already emits instead of threading counters through call sites, so measurement never distorts the code being measured.
- Template-owned markdown. Every user-facing markdown surface renders from a Jinja template, so prose changes never touch Python and formatting stays in one language.
- No duplicate projections. One row model per projection shape, shared by every reader, so a field change cannot silently fork a wire format.
Enforced contracts
Section titled “Enforced contracts”import-linter reads [tool.importlinter] in
pyproject.toml and fails the lint gate when a contract breaks.
| Contract | Type | What it guarantees |
|---|---|---|
| Every aizk package sits in one enforced layer | layers | the layer diagram made exhaustive over every top-level package, with mcp and api independent of each other, the leaves (config, types, exceptions, provenance, common) mutually independent at the bottom, and any unassigned new package breaking the gate |
Non-SQL packages reach the store only through model methods and User.exec |
forbidden | every package outside the documented SQL-composing set never imports sqlmodel or sqlalchemy, and tests/test_contracts.py keeps that split exhaustive as packages appear |
Ruff overlays in src/aizk/mcp/ruff.toml and src/aizk/api/ruff.toml extend the package
configuration and ban sqlmodel.select and the session APIs inside the transports through
TID251, catching the call sites the import contracts cannot see.
The gate runs as lint-imports in the CI lint job, and locally as
chefe run lint-imports-aizk from the monorepo root.