COMPASS docs

Concepts

COMPASS stores one form: a Sache (a tracked question) holds Aussagen (dated statements that answer it). Everything else — rules, hypotheses, lessons, personas — is this same form applied to a specific job.

Concept What it is
Sache (subject) A stable question COMPASS tracks the current answer to, e.g. "which test framework do we use". Identified by a kebab-case slug; optionally grouped under a bereich (area) and a stufe (level).
Aussage (statement) One dated claim that answers a Sache. Every Aussage has an art (kind — see below), a herkunft (how it was obtained: measured, said, derived, decided, evidence), and a status.
Fakt (fact, art=fakt) A measured or observed statement — typically written by a check (Prüfweg) after it runs.
Regel (rule, art=regel) The statement type with teeth: at most one regel may hold (status=gilt) for a given Sache and subject at any time — enforced by the database, not by application code.
Entscheidung (decision, art=entscheidung) A human call, recorded with its source (a decided question, an imported log entry, …).
Hypothese (hypothesis, art=hypothese) A provisional statement that must carry a frist (deadline) and a pruefweg_slug (which check will confirm or refute it) — the contract rejects a hypothesis without both.
Lehre (lesson) A separate record (compass_lehren, the "Hall of Lessons") for what went wrong, what went well, or a note from an agent to a human. A lesson can be turned into a rule, but only through an explicit human click — never automatically.
gilt_seit / gilt_bis The validity interval: since when a statement has held, and until when (null = still holds). This is the time axis the domain cares about.
beobachtet_am / erfasst_am The bi-temporal pair: when the underlying fact was actually observed, versus when COMPASS recorded it. The two can differ — a check can report on something that happened earlier.
Superseding, not deleting There is no DELETE on statements. A new statement supersedes the old one (ersetzt points at the predecessor, which gets status=abgeloest and gilt_bis set to the successor's gilt_seit), or a statement is explicitly ended (POST /aussagen/{id}/beenden) when nothing replaces it. History is never erased. Why: see Set and supersede a rule.
Exactly one current rule per Sache A partial unique index (uq_aussagen_eine_geltende_regel, on (kunde_slug, sache, subjekt_id) WHERE art='regel' AND status='gilt') makes a second concurrent rule for the same subject impossible to insert — the database refuses it, not just the API layer.
warum, check, deviation GET /warum/{sache} answers "why does this rule hold" with its decision text and history. A Prüfweg (check) is a read-only, allow-listed command that verifies a rule against reality; a run that doesn't match what the rule expects is an Abweichung (deviation) with a determined cause (violation, tool missing, or unclear).
Mandant + key Every row carries a kunde_slug (tenant) enforced by Postgres row-level security. A presented X-Compass-Key resolves to exactly one tenant and one scope: ro (read), rw (read and write), or bruecke (three narrow write paths only).
Persona + Brief A Persona is an agent identity with a task, boundaries, a style, and a sichtfeld (visibility — which areas it may read). Its Brief is a deterministic Markdown document (no LLM involved) assembled from its rules, memory, and lessons, cut to fit a character budget.

How a statement's lifetime works

A Sache never holds text directly — it holds a chain of Aussagen. Superseding a rule keeps the old row (now abgeloest) and inserts a new one, linked by ersetzt. The diagram below shows one Sache with two rule statements: the first was superseded when the second was written.

Sache which test framework? AUSSAGE · art=regel "we use pytest" gilt_seit 2026-01-10 gilt_bis 2026-06-02 AUSSAGE · art=regel "we use vitest" gilt_seit 2026-06-02 gilt_bis — (status=gilt) ersetzt status: abgeloest status: gilt (the one current rule)

Reading GET /gilt/{sache} at any moment returns the one row with status=gilt — the query never has to reconcile competing answers, because the database only ever allows one to exist.

Check a ledger independently

The single Python file core/scripts/ledger_pruefen.py recalculates a complete exported tenant chain offline, without COMPASS or additional packages. Run python3 ledger_pruefen.py kette.json --kopf kopf.json to compare it with a head you kept separately; this also detects a truncated tail. A break reports the first affected number. An empty chain or a missing head never reports success. The ledger export itself is not yet part of the export endpoint; the input format is defined for its future extension. The checker confirms the match against your saved head, not its provenance or the truth of the stored statements.

Next