An AI coding agent is about to write a file. Before the write lands, Mneme checks the proposed content against the decisions your team has recorded. The reference entry point is mneme check, which takes three inputs: the memory file, the content, and a query describing the task. It answers PASS, WARN, or FAIL.

No step on that path calls a model. The same three inputs produce the same answer on a laptop, inside an agent hook, and in CI.

The enforcement path

A check runs four steps and returns a verdict.

MemoryStore loads typed Decision records DecisionRetriever weighted keyword score, ranked Enforcement scope top-N tier + whole corpus Rule evaluation path selectors + literal matching Verdict PASS / WARN FAIL query content + target path verdict + violations project_memory.json
Fig. 1. The mneme check enforcement path. No step calls a model.

Step 1: MemoryStore loads the decision corpus

MemoryStore reads project_memory.json once and builds typed records. Each entry under decisions becomes a Decision record with a decision statement, a rationale, scope labels, constraints, anti-patterns, and typed rules.

Older memory files keep working. Legacy items of type rule and anti_pattern migrate into Decision records at load time. Items of type preference, fact, and example stay out of the decision corpus.

Step 2: DecisionRetriever scores the corpus

Tokenization

The retriever lowercases the query and splits it into alphanumeric tokens. It drops tokens shorter than four characters and six stopwords: add, use, not, get, set, run. It runs the same tokenizer over each scored field of each decision. It does no stemming, so "migration" and "migrations" count as different tokens.

Field weights

For each field, the retriever counts the query tokens that appear in it and multiplies the count by the field's weight:

# _WEIGHTS in decision_retriever.py decision: 1.0 # the decision statement scope: 2.0 # highest: the areas the decision governs constraints: 1.5 anti_patterns: 1.5 rationale: 0.5 # lowest: explanatory prose

The score is the sum across the five fields. Take the query "payments service postgres client" and a decision scoped to "payments service" with the constraint "no postgres client outside the data layer". Two tokens match the scope for 4.0, two match the constraint for 3.0, and the decision scores 7.0. The retriever returns the per-field match counts with the score, so you can read the reason a decision ranked where it did.

Ranking

The retriever returns the full corpus sorted from highest score to lowest. Decisions with equal scores keep their order from the memory file. A query with no usable tokens gives each decision a floor score of 1.0, and the list stays in file order.

query → token set decision × 1.0 scope × 2.0 constraints × 1.5 anti_patterns × 1.5 rationale × 0.5 score per decision sort ↓, ties keep file order
Fig. 2. DecisionRetriever scoring: token overlap per field, weighted, summed, then sorted.

Step 3: Enforcement scope covers the whole corpus

Ranking tells Mneme which decisions relate to the task. Enforcement has to answer something else: does this content break a rule? A forbidden import stays forbidden when the query shares no token with the decision that bans it. Mneme therefore evaluates the whole corpus, in two tiers:

  • The retrieval-gated tier. The top N decisions with a positive score. Mneme checks each rule they carry. N defaults to 3, and --top changes it.
  • The rest of the corpus. Mneme checks unambiguous literal rules only: typed FORBID_LITERAL rules, plus anti-patterns and "no X" constraints that reduce to one significant term.

Multi-term rules stay gated for a reason. An anti-pattern such as "open() without encoding= in Python" is prose. When Mneme treated each word of it as forbidden, a bare "open" or "without" failed harmless text. Mneme now matches a multi-term anti-pattern as a complete ordered phrase, and only for the top N decisions.

Step 4: Rule evaluation

Mneme evaluates three kinds of rule against the proposed content:

Rule Matches when Severity
Typed FORBID_LITERAL The literal appears in the content and the rule's path selectors apply to the target FAIL
Anti-pattern A single term appears as a whole word, or the full ordered phrase appears FAIL
"no X" constraint A term of X appears as a whole word WARN

A typed rule can carry include_paths and exclude_paths. Mneme compares them with the target path, taken from --target-path or from the input file, and records one outcome per rule: APPLIED, EXCLUDED, or UNKNOWN. A typed rule also skips its own ADR and the memory file, because both have to contain the literal they define.

The verdict

PASS
No rule matched
WARN
Only "no X" constraints matched
FAIL
A typed rule or an anti-pattern matched

In strict mode the exit codes are 0, 1, and 2. In warn mode Mneme prints the same verdict and exits 0. Each violation names the decision ID, the rule, and the term that triggered it.

INCOMPLETE is an operational state. When a path-scoped rule comes back UNKNOWN, for example because the run has no usable target path, Mneme prints Result: INCOMPLETE and exits 2 in both modes. It reports this apart from PASS, WARN, and FAIL because a partial evaluation cannot vouch for the content.

Scope labels and path selectors

Two fields sound alike and do different jobs. A decision's scope is a list of plain-text labels such as "payments" or "general". The retriever scores it like the other fields, at the highest weight, and filters nothing with it.

Path matching belongs to typed rules. A rule with include_paths: services/payments/** applies to target paths under that directory, whatever the query says. Mneme resolves it during rule evaluation.

Three reasons for no embeddings

  1. Reproducibility. An audit needs the same query and the same memory file to return the same ranking a year from now. Vendors update and retire embedding models, and each change shifts retrieval with no diff for you to review.
  2. Explainability. You can read a keyword score: two query tokens matched the scope at 2.0 and one matched a constraint at 1.5, for a total of 5.5. Cosine similarity across 1,536 dimensions gives you a number and no reason.
  3. No runtime dependency. The scorer is pure Python and runs locally. A check makes no network call, so it adds no API latency or cost to an agent's edit.

Embedding search ranks passages by topical closeness, which suits finding background reading. Enforcement needs the decision that governs this file and a literal rule that either matches the content or does not.

Determinism

Tokenizing, scoring, sorting, and rule matching are string operations over one JSON file. Give mneme check the same memory file, query, content, and target path, and it returns the same verdict with the same violations. A FAIL in CI points to one decision record you can open and read.

Benchmark verdicts are separate

The benchmark wraps this path with its own scenario verdicts, including WEAK_RETRIEVAL, and its own retrieval metrics such as recall@K. They measure Mneme, and mneme check does not return them. The benchmark methodology defines each one.