# Project export: Mnemex: Anchored Decision Memory for Coding Agents

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: OpenAI Build Week
- Tagline: Mnemex verifies whether a past decision still governs your code by content hash, not by vibes. It blocks fresh violations, allows legitimate refactors, and runs fully local.
- Devpost: https://devpost.com/software/mnemex-anchored-decision-memory-for-coding-agents
- GitHub: https://github.com/notsointresting/mnemex
- Video: https://www.youtube.com/embed/QKabGASxcKw?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 1 GitHub contributor(s) — Sahil (28 commits)

## Devpost submission (written by the team)

### Overview

Coding agents have memory now. They can remember that "authentication is stateless" or "payment writes must be idempotent." But memory recalls a sentence — it doesn't know whether the code that sentence was about still exists, still looks the same, or was quietly refactored three commits ago. We kept hitting the same failure: an agent confidently remembered a past decision and applied it to code that had already moved on. Memory systems (Mem0, Zep-class) retrieve relevant history but can't tell you if it's still true. ADR tools enforce written rules but have no idea what the code actually says today. Nobody was answering the real question: Does this past decision still govern this code, right now — and can you prove it? That's the gap Mnemex fills. Mnemex records a software decision against a code symbol and its content hash. The core unit isn't a chat message—it's an auditable decision. When the anchored code moves, changes, or disappears, the decision doesn't silently rot into stale context—it becomes reviewable. Anchors are reported as: ✅ Fresh ⚠️ Stale ❌ Orphaned ...based on content hashes, not vibes. Violation vs. Evolution The distinction we care about most is violation vs. evolution. The same guard should reject a real contradiction while standing aside for a legitimate refactor. The semantic guard blocks only a fresh, cited contradiction with high confidence: [ \text{block} \iff (\text{verdict}=\texttt{contradiction}) \land (\text{confidence}\ge0.90) \land \text{fresh} ] Everything else is advisory. Deterministic tagged constraints (constraint:forbidden:...) can block a violation without using a model at all. The whole system is one local SQLite brain. Retrieval Core SQLite + FTS5 keyword retrieval (BM25) FastMCP No ML model No API key No network required Structural Indexer Parses Python and TypeScript/TSX Extracts symbols, calls, and imports Content-hashes every symbol for exact freshness tracking Optional Intelligence sqlite-vec for hybrid vector retrieval Opt-in GPT-5.6 semantic judge (OpenAI Responses API) Transport MCP over stdio (JSON-RPC) End-to-end verified with a subprocess test that: runs initialization lists tools invokes them successfully End-to-end verified with a subprocess test that: runs initialization lists tools invokes them successfully Guardrails Write-time secret & PII redaction Zero telemetry in local mode Hard context caps: Session brief: 800 tokens Just-in-time context: 400 tokens Guard evidence: 800 tokens Hard context caps: Session brief: 800 tokens Just-in-time context: 400 tokens Guard evidence: 800 tokens Developer Experience One-command setup for: Claude Code Cursor VS Code Codex Installation is: idempotent byte-identical on rerun touches only the Mnemex entry CI CI builds a wheel and runs clean-install smoke tests across: Python 3.10–3.13 Linux macOS Windows Mnemex was built in close collaboration with OpenAI Codex (GPT-5.6). GPT-5.6 also runs inside the product as the optional semantic judge. The division of labor mirrors the implementation: Deterministic code selects and bounds evidence. The model makes only the semantic judgment. Knowing when a memory expired Storing and recalling decisions is easy. Determining whether a decision is stale required anchoring validity to symbol content hashes while cleanly separating: Fresh Stale Orphaned Not crying wolf Catching real violations without blocking legitimate refactors is the entire value proposition. We solved this by making deterministic logic own policy and gating the LLM behind the anchor layer. The model supplies bounded evidence—never policy. Living inside a token budget Hard caps (800 / 400 / 800) forced retrieval to be genuinely selective instead of dumping context. Trust & safety Every remote payload is: sanitized capped summarized Local mode never even imports the OpenAI package. mnemex doctor self-tests the redaction pipeline using password, provider-key, and private-tag vectors before reporting ready. Being honest about evidence Our scorecard is a deterministic recorded-fixture replay. It is explicitly labeled as such—not presented as a live-agent benchmark. Scoping our claims accurately was a deliberate design choice. Cross-platform reality Windows vs. POSIX subprocess behavior consumed more time than expected. CI across all three operating systems keeps everything honest. Guard that blocks a fresh contradiction while allowing legitimate evolution Fully local core: no cloud no model no telemetry Fully local core: no cloud no model no telemetry LLM remains strictly optional LLM remains strictly optional Cross-agent continuity: export decisions import into another agent preserve anchors, hashes, provenance, and audit trail Cross-agent continuity: export decisions import into another agent preserve anchors, hashes, provenance, and audit trail 60-second, zero-key offline demo 60-second, zero-key offline demo Anyone can verify the core claim without setup. The hard part of agent memory isn't storage or retrieval. It's knowing when a memory stopped being true. Anchoring decisions to content hashes transforms: "The agent remembers a rule." into "The agent can prove the rule still applies here." We also learned that honest, bounded scoping—determinism by default, models only where they genuinely add value—builds more trust than inflated benchmarks ever could. More language indexers beyond Python and TS/TSX Hunk-level precision (the diff gate is currently file-scoped) Live-agent evaluation alongside recorded fixtures Published skill and package so onboarding becomes one line

## README (from the GitHub repository)

# mnemex

Memory systems retrieve relevant history. ADR tools check written rules.
**Mnemex verifies whether a past decision still governs the code — by content
hash, not by vibes — then gives the agent the minimum evidence for the current
edit.**

## Judge path — no key, no vector extra (under 60 seconds)

Install the wheel, then run the deterministic offline demonstration. It needs
no API key, embedding model, vector extension, or network access after install.

```bash
python -m pip install dist/mnemex-*.whl
python -m mnemex demo --offline
```

Expected evidence includes a deterministic fresh-decision block:

```text
BLOCKED: Deterministic constraint violation: Forbidden phrase appears in the proposed change.
Status       fresh at guard time
```

The same demo then records an explicit override, supersedes the decision,
changes its anchor, and shows the resulting stale state. For structured output
suited to an automated check, use `python -m mnemex demo --offline --json`.

## The important distinction: violation vs. evolution

The same guard should reject a fresh decision violation and stand aside when a
legitimate change preserves the decision. The checked-in, deterministic replay
fixture demonstrates both cases; it is not a live provider claim.

| Fresh anchored decision | Proposed change | Result |
|---|---|---|
| Payment writes must be idempotent | Remove the idempotency check and retry after a ledger write | **BLOCKED** — `contradiction`, confidence `0.96` |
| Payment writes must be idempotent | Extract the same idempotency check into a helper before the ledger write | **Advisory / allowed** — `compatible` |

Run the fixture from [examples/violation-vs-evolution](examples/violation-vs-evolution/README.md).

## Recorded-fixture scorecard

<!-- codex-guard-scorecard:start -->
**Synthetic recorded-fixture replay, not a live-agent outcome claim.** The
numbers below are generated from
[the checked-in results JSON](benchmarks/results/codex-guard-scorecard.json)
and are locked against README drift by `tests/test_scorecard.py`.

| Metric | Recorded fixture replay |
|---|---:|
| Decision violations caught | 2/2 |
| False blocks on legitimate evolution | 0/2 |
| Stale decisions correctly advisory | 1/1 |
| Average recorded treatment context tokens / cap | 0/800 (1 observation) |

Reproduce:

```bash
python tools/evaluate_codex_guard.py benchmarks/codex-guard-fixtures/example-results.synthetic.json --format json
```
<!-- codex-guard-scorecard:end -->

## What it is

Mnemex records a software decision against a code symbol and its content hash.
When that code moves, changes, or disappears, the decision becomes reviewable
instead of silently becoming stale context. MCP tools and the CLI retrieve only
the evidence needed for the current change.

It is not a generic chat-memory store. Its core unit is an auditable decision:

```text
decision -> code symbol -> content hash -> freshness -> evidence for an edit
```

## Why It Matters

Coding agents can remember a sentence such as "authentication is stateless" but
still lose track of the code it governed and whether that code has changed.
Mnemex keeps those facts connected:

- Decisions can be anchored to indexed Python or TypeScript/TSX symbols.
- Fresh, stale, and orphaned anchors are reported separately.
- `why` combines anchored decisions with caller context.
- The optional semantic guard records evidence, verdicts, and overrides. It
  blocks only a fresh, cited `contradiction` at confidence `>= 0.90`.

Core storage, indexing, retrieval, freshness, and deterministic constraints
stay local in SQLite. The OpenAI semantic judge is optional and disabled by
default.

| Category | Recall | Enforcement | Knows when stale (content hash) | Local-only | Audited override |
|---|---|---|---|---|---|
| Mem0 / Zep-class memory | Yes | No | No | Varies | No |
| adr-kit-class enforcement | Manual rules | Yes | No | Yes | Varies |
| Mnemex | Bounded FTS5; optional vectors | Fresh, explicit decisions | Yes | Yes in core mode | Yes |

## Architecture

```text
source files
    | index
    v
symbols + calls + imports -----------------------+
    | content hashes                             |
    v                                            |
anchored decisions in one SQLite database        |
    |                                             |
    +-- freshness / lifecycle / provenance       |
    +-- bounded retrieval / JIT context           |
    +-- optional semantic guard <-----------------+
    |
MCP (stdio or local HTTP) + CLI + project brain bundles
```

## Quick Start From This Checkout

Mnemex is installable from source and works without an embedding model, an
OpenAI key, or network access after dependencies are installed. The default
install is **core mode**: FastMCP plus SQLite/FTS5 keyword (BM25) retrieval. The
Mnemex core does not require or load the sqlite-vec native extension.

```bash
python -m pip install .                 # core: FTS5/BM25 retrieval
mnemex init . --db .mnemex/mnemex.sqlite3
mnemex doctor --db .mnemex/mnemex.sqlite3
```

Optional extras layer onto the same single SQLite brain; there is no second
database:

```bash
python -m pip install ".[vector]"      # optional hybrid vector retrieval (sqlite-vec)
python -m pip install ".[openai]"      # optional GPT-5.6 semantic judge
python -m pip install ".[vector,openai]"
```

`MNEMEX_NO_VEC=1` force-disables vector loading even when the extra is present.
In core mode `mnemex doctor` reports `retrieval_mode: bm25-only` with a stable
`sqlite_vec_status` such as `package-not-installed` or
`disabled-by-environment`; missing vector support is not a doctor failure.

For editable development:

```bash
python -m pip install -e ".[dev]"
python -m ruff check src tests tools
python -m pytest -q
```

The CI workflow exercises Python 3.10-3.13 on Linux, macOS, and Windows,
builds a wheel, and performs a clean-install smoke test.

## Demo Modes

### Local evidence demo

```bash
mnemex demo --offline
```

This no-network demo indexes `authenticate`, creates an explicitly tagged
stateless-authentication constraint, and proposes Redis-backed server sessions.
It deterministically reports **BLOCKED**, records an explicit override,
supersedes the decision, changes the anchor, and then reports staleness. Use
`--json` when a recording or test needs structured output.

### Optional semantic guard

Install the optional dependency and set credentials only for a semantic check:

```bash
python -m pip install ".[openai]"
set OPENAI_API_KEY=...
set MNEMEX_SEMANTIC_JUDGE_ENABLED=true
mnemex serve --db .mnemex/mnemex.sqlite3 --semantic-judge
mnemex demo --semantic --json
```

On PowerShell, use `$env:OPENAI_API_KEY` and
`$env:MNEMEX_SEMANTIC_JUDGE_ENABLED = "true"`. The provider uses the OpenAI
Responses API with the configured model. Missing credentials, a timeout, or
malformed provider output produces `unavailable` or `uncertain`; it never
blocks an edit. Every remote payload is sanitized, capped, and summarized in
the guard result.

## Agent Setup

One command wires the mnemex MCP server into a project-local agent config. It
writes **only** the `mnemex` entry, leaves every other setting untouched, and
is byte-identical on re-run:

```bash
mnemex setup cursor                 # or: claude-code | codex | vscode
mnemex setup claude-code --guard    # also write the decision-guard block to AGENTS.md
```

| Agent | Config written (project-local) |
|---|---|
| `claude-code` | `.mcp.json` |
| `cursor` | `.cursor/mcp.json` |
| `vscode` | `.vscode/mcp.json` |
| `codex` | `.codex/config.toml` |

Each writes the stdio launch entry `python -m mnemex serve --db
<root>/.mnemex/mnemex.sqlite3` and prints a JSON report of the exact path it
changed. An existing config that is not valid JSON is reported as an error and
left untouched rather than overwritten. Restart the agent afterward so it
reloads the MCP config.

Install without cloning — straight from the repository (verified end-to-end),
then run

[README truncated for size]

## Detected evidence (automated analysis)

Indexed codebase: 90 recognized source files, 765 KB.
- OpenAI (technology) — detected in the code
- Python (language) — detected in the code
- Node.js (technology) — claimed on Devpost, not found in the code
- AI coding agent: Claude Code — evidence: config files committed to the repository
- AI coding agent: Codex — evidence: config files committed to the repository

## Codebase structure (from repository index)

### Files (108 of 108)

```
.gitguardian.yml
.github/workflows/ci.yml
.gitignore
AGENTS.md
benchmarks/2026-07-15-decision-integrity-evaluation.md
benchmarks/2026-07-15-three-repositories.md
benchmarks/2026-07-17-codex-guard-evaluation.md
benchmarks/codex-guard-fixtures/example-results.synthetic.json
benchmarks/codex-guard-fixtures/README.md
benchmarks/codex-guard-fixtures/task-01-stateless-auth-violation.json
benchmarks/codex-guard-fixtures/task-02-payment-idempotency-violation.json
benchmarks/codex-guard-fixtures/task-03-compatible-extraction.json
benchmarks/codex-guard-fixtures/task-04-legitimate-evolution.json
benchmarks/codex-guard-fixtures/task-05-stale-decision.json
benchmarks/decision-integrity-fixtures.json
benchmarks/results/codex-guard-scorecard.json
CHANGELOG.md
CLAUDE.md
codebase-mind-improved-plan.md
CODEX_EXECUTION_PLAN.md
DECISIONS.md
examples/cross-agent-demo/README.md
examples/cross-agent-demo/src/auth.py
examples/scoped-invariants/README.md
examples/violation-vs-evolution/case_evolution.diff
examples/violation-vs-evolution/case_violation.diff
examples/violation-vs-evolution/README.md
examples/violation-vs-evolution/replay/evolution.json
examples/violation-vs-evolution/replay/violation.json
examples/violation-vs-evolution/setup.py
examples/violation-vs-evolution/src/payments.py
goal-command.md
HACKATHON_RESEARCH.md
implementation-plan-opus48.md
LICENSE
npm/mnemex-skills/bin/mnemex-skills.cjs
npm/mnemex-skills/package.json
npm/mnemex-skills/SKILL.md
pyproject.toml
README.md
src/mnemex/__init__.py
src/mnemex/__main__.py
src/mnemex/agent_setup.py
src/mnemex/agents_md.py
src/mnemex/anchors.py
src/mnemex/bundles.py
src/mnemex/codex_setup.py
src/mnemex/config.py
src/mnemex/conflicts.py
src/mnemex/constraints.py
src/mnemex/decision_guard.py
src/mnemex/diff_guard.py
src/mnemex/embedding_providers.py
src/mnemex/evidence.py
src/mnemex/hooks.py
src/mnemex/indexer.py
src/mnemex/judge.py
src/mnemex/lifecycle.py
src/mnemex/mistakes.py
src/mnemex/retrieval.py
src/mnemex/reviews.py
src/mnemex/security.py
src/mnemex/server.py
src/mnemex/storage.py
src/mnemex/tui.py
src/mnemex/vector_backend.py
tests/test_agent_setup.py
tests/test_agents_md.py
tests/test_anchor_adversarial.py
tests/test_anchor_typing.py
tests/test_anchors.py
tests/test_bundles.py
tests/test_cli_config.py
tests/test_codex_setup.py
tests/test_config.py
tests/test_conflicts.py
tests/test_constraints.py
tests/test_context_for_file.py
tests/test_decision_guard.py
tests/test_diff_guard.py
tests/test_embedding_providers.py
tests/test_evidence.py
tests/test_hackathon_evaluation.py
tests/test_hooks.py
tests/test_indexer.py
tests/test_integration_e2e.py
tests/test_judge.py
tests/test_lifecycle.py
tests/test_mcp_stdio_integration.py
tests/test_mistakes.py
tests/test_release_artifacts.py
tests/test_retrieval_golden.py
tests/test_retrieval.py
tests/test_reviews.py
tests/test_scorecard.py
tests/test_security_integration.py
tests/test_security.py
tests/test_server.py
tests/test_smoke.py
tests/test_storage_migrations.py
tests/test_storage.py
tests/test_tui.py
tests/test_vector_backend.py
tools/audit_release_artifacts.py
tools/build_release_bundle.py
tools/collect_windows_security_evidence.ps1
tools/evaluate_codex_guard.py
tools/evaluate_decision_integrity.py
```

### Dependencies

- pyproject.toml: fastmcp@==3.4.4, openai@>=1.0.0, pytest@==9.1.1, ruff@==0.15.21, sqlite-vec@>=0.1.9,<0.2

### Recent commits (newest first)

- docs: verified install-from-GitHub one-liner for setup
- feat: 'mnemex setup <agent>' for Claude Code, Cursor, VS Code, Codex
- feat: scoped path-invariants and staged-diff decision gate
- feat: add Codex Guard fixture scorecard
- docs: add hackathon execution plan
- test: cover release artifact audit markers
- chore: add Windows security evidence collector
- fix: show WHY details in offline demo
- feat: audit release artifacts
- feat: add lazy vector backend
- docs: Built-with-Codex collaboration section
- feat(demo): violation-vs-evolution fixture with recorded replay verdicts
- chore(packaging): relax sqlite-vec pin for wider wheel availability
- docs: evidence-backed Codex/MCP claims and working skill-install path
- fix(server): clamp recall_memories token budget; rename shadowed import
- feat(privacy): surface redaction counts and --show-payload evidence inspection
- fix(cli): doctor redaction probe covers password and provider-key cases
- fix(security): redact password/openai/anthropic/google/stripe/slack secrets at write time
- chore: ignore local hackathon recording scripts
- feat: complete decision integrity platform

## Key source files (fetched from GitHub, selected and truncated for size)

### AGENTS.md

```markdown
# mnemex — Agent Build Context

## Sources of truth
Read `implementation-plan-opus48.md`, `codebase-mind-improved-plan.md`,
`goal-command.md`, and `CLAUDE.md` before changing the project. Build phases in plan
order; Phase 1's anchor core must be complete before any indexer work.

## Goal
Build a local-first MCP server that anchors each remembered decision to a
file/symbol and content hash, then delivers relevant context just in time. Use
one SQLite file with sqlite-vec and FTS5, and distribute context through MCP,
skills, hooks, and AGENTS.md.

## Orchestration rules
- Give each subagent one bounded contract with inputs, outputs, and exact tests.
- Require diffs, test results, and a self-report; never blind-merge.
- A phase is complete only after its objective gate passes.
- Use a separate verifier instance; authors do not grade their own work.
- Keep changes within the current phase and do not add speculative APIs.

## Hard constraints
- Core features stay local and require no cloud API or ML model.
- Context caps are hard limits: 800 tokens at session start and 400 for JIT.
- Support macOS, Linux, and Windows on Python 3.10–3.13.
- Strip secrets and PII at write time; the Phase 6 security gate is blocking.
- Prefer a pluggable structural backend over implementing another parser.

```

### CHANGELOG.md

```markdown
# Changelog

All notable changes to Mnemex are documented here. This project follows
semantic versioning once release publishing begins.

## 0.1.0 - 2026-07-15

### Added

- Local SQLite persistence for anchored decisions, graph nodes, retrieval,
  provenance, review state, conflicts, guard runs, overrides, and redaction
  audits.
- Python and TypeScript/TSX structural indexing with anchors, caller tracing,
  freshness checks, and append-only decision lifecycle operations.
- MCP and CLI workflows for recall, `why`, change checks, reconciliation,
  review, project-brain import/export, dashboard health, initialization, and
  diagnostics.
- Optional OpenAI semantic judgment with bounded, redacted evidence and a
  no-network local default.
- Deterministic tagged constraints, mistake-memory checks, and confirmation-only
  stop-hook suggestions.
- Cross-platform CI, wheel smoke tests, a source release bundle, a private npm
  skill installer, benchmark evidence, and a cross-agent example.

### Security

- Sanitization occurs before persistence and records redaction audit entries.
- Guard failures and unavailable semantic judgment remain advisory.
- Remote evidence payloads are capped and inspectable.

### Notes

- Codex is the first verified live MCP client integration.
- Local HTTP transport is intentionally unauthenticated and must remain local
  or sit behind an authenticated gateway.

```

### pyproject.toml

```
[build-system]
requires = ["setuptools==83.0.0"]
build-backend = "setuptools.build_meta"

[project]
name = "mnemex"
version = "0.1.0"
description = "Anchored memory for AI coding agents — MCP server that binds decisions to code symbols and delivers context just-in-time"
readme = "README.md"
license = "MIT"
requires-python = ">=3.10"
authors = [
    { name = "mnemex contributors" },
]
keywords = [
    "mcp",
    "model-context-protocol",
    "ai-agent",
    "coding-agent",
    "memory",
    "context",
    "claude-code",
    "cursor",
    "codex",
    "gemini-cli",
    "windsurf",
    "sqlite",
    "local-first",
    "decision-anchoring",
    "token-optimization",
]
classifiers = [
    "Development Status :: 4 - Beta",
    "Intended Audience :: Developers",
    "Programming Language :: Python :: 3.10",
    "Programming Language :: Python :: 3.11",
    "Programming Language :: Python :: 3.12",
    "Programming Language :: Python :: 3.13",
    "Topic :: Software Development :: Libraries :: Python Modules",
    "Topic :: Software Development :: Quality Assurance",
    "Topic :: Scientific/Engineering :: Artificial Intelligence",
    "Typing :: Typed",
]
dependencies = [
    "fastmcp==3.4.4",
]

[project.urls]
Homepage = "https://github.com/nkm-ets/mnemex"
Documentation = "https://github.com/nkm-ets/mnemex#readme"
Repository = "https://github.com/nkm-ets/mnemex"
Issues = "https://github.com/nkm-ets/mnemex/issues"
Changelog = "https://github.com/nkm-ets/mnemex/releases"

[project.scripts]
mnemex = "mnemex.__main__:main"

[project.optional-dependencies]
dev = [
    "pytest==9.1.1",
    "ruff==0.15.21",
]
# Optional native vector accelerator. The mnemex core never requires or loads
# the sqlite-vec extension; installing this extra upgrades retrieval to hybrid
# BM25 + vector search using the same single SQLite brain.
vector = [
    "sqlite-vec>=0.1.9,<0.2",
]
openai = [
    "openai>=1.0.0",
]

[tool.setuptools.packages.find]
where = ["src"]

[tool.pytest.ini_options]
testpaths = ["tests"]

[tool.ruff]
target-version = "py310"

```

### npm/mnemex-skills/package.json

```
{
  "name": "@mnemex/skills",
  "version": "0.1.0",
  "private": true,
  "description": "Install the local mnemex coding-agent skill",
  "license": "MIT",
  "homepage": "https://github.com/nkm-ets/mnemex#readme",
  "repository": {
    "type": "git",
    "url": "git+https://github.com/nkm-ets/mnemex.git",
    "directory": "npm/mnemex-skills"
  },
  "bugs": {
    "url": "https://github.com/nkm-ets/mnemex/issues"
  },
  "keywords": [
    "mcp",
    "coding-agent",
    "codex",
    "decision-integrity",
    "local-first",
    "mnemex"
  ],
  "bin": {
    "mnemex-skills": "bin/mnemex-skills.cjs"
  },
  "files": ["bin", "SKILL.md"],
  "engines": {
    "node": ">=18"
  }
}

```

### src/mnemex/server.py

```python
"""Phase 4 — MCP server exposing mnemex tools.

Uses ``mcp.server.fastmcp.FastMCP`` (shipped with the ``mcp`` package, which is
a dependency of ``fastmcp==3.4.4``) to expose tools over stdio transport.
The server is stateful: it opens a single Storage connection at startup and
reuses it for all tool calls.
"""

from __future__ import annotations

from pathlib import Path
from typing import Any

from mcp.server.fastmcp import FastMCP

from mnemex.anchors import (
    Anchor,
    AmbiguousAnchorError,
    AnchorNotFoundError,
    check_freshness,
    forget,
    remember,
)
from mnemex.decision_guard import (
    check_proposed_change as evaluate_proposed_change,
    override_decision_guard as persist_guard_override,
)
from mnemex.evidence import DEFAULT_EVIDENCE_TOKEN_CAP
from mnemex.judge import SemanticJudge
from mnemex.retrieval import Embedder, estimate_tokens, govern_memories, recall
from mnemex.storage import Storage

__all__ = ["create_server", "MnemexServer"]


class MnemexServer:
    """Wraps Storage + FastMCP into a runnable MCP server.

    Parameters
    ----------
    db_path
        Path to the SQLite database file.  Use ``":memory:"`` for testing.
    embedder
        Optional embedding function for hybrid retrieval.  When ``None``,
        the server operates in BM25-only mode.
    semantic_judge
        Optional, explicitly enabled remote semantic judge. ``None`` keeps the
        server entirely local and returns an advisory unavailable verdict.
    """

    def __init__(
        self,
        db_path: str | Path = ":memory:",
        *,
        embedder: Embedder | None = None,
        semantic_judge: SemanticJudge | None = None,
        max_evidence_tokens: int = DEFAULT_EVIDENCE_TOKEN_CAP,
    ) -> None:
        if max_evidence_tokens <= 0:
            raise ValueError("max_evidence_tokens must be positive")
        self.storage = Storage(db_path)
        self.embedder = embedder
        self.semantic_judge = semantic_judge
        self.max_evidence_tokens = max_evidence_tokens
        self._agents_md_content: str | None = None
        self.mcp = FastMCP("mnemex")
        self._register_tools()

    def _register_tools(self) -> None:
        storage = self.storage
        embedder = self.embedder
        semantic_judge = self.semantic_judge

        @self.mcp.tool()
        def remember_decision(
            content: str,
            anchor_file: str | None = None,
            anchor_symbol: str | None = None,
            anchor_node_id: str | None = None,
            scope: str = "project-shared",
            rationale: str = "",
            tags: str = "",
        ) -> dict[str, Any]:
            """Store a decision or convention, optionally anchored to a code symbol."""
            anchor: Anchor | str | None = None
            if anchor_node_id:
                anchor = anchor_node_id
            elif anchor_file and anchor_symbol:
                anchor = Anchor(file=anchor_file, symbol=anchor_symbol)

            try:
                memory = remember(
                    storage,
                    content,
                    anchor=anchor,
                    scope=scope,
                    rationale=rationale,
                    tags=tags,
                )
                if embedder is not None and storage.vec_available:
                    from mnemex.retrieval import ensure_embeddings

                    ensure_embeddings(storage, embedder, scopes=(memory.scope,))
                return {"memory_id": memory.id, "status": "stored"}
            except (AnchorNotFoundError, AmbiguousAnchorError, ValueError) as e:
                return {"error": str(e)}

        @self.mcp.tool()
        def recall_memories(
            query: str,
            scopes: str = "project-shared",
            limit: int = 10,
            max_tokens: int | None = None,
        ) -> dict[str, Any]:
            """Retrieve relevant memories via hybrid BM25+vector search."""
            scope_list = [s.strip() for s in scopes.split(",")]
            # Every retrieval surface is hard-capped; recall is no exception.
            if max_tokens is not None:
                max_tokens = min(max(max_tokens, 0), 800)
            try:
                result = recall(
                    storage,
                    query,
                    scopes=scope_list,
                    embedder=embedder,
                    limit=limit,
                    max_tokens=max_tokens,
                )
                return {
                    "mode": result.mode,
                    "used_tokens": result.used_tokens,
                    "budget_tokens": result.budget_tokens,
                    "included": [
                        {
                            "id": sm.memory.id,
                            "content": sm.memory.content,
                            "rationale": sm.memory.rationale,
                            "score": sm.score,
                            "signals": list(sm.signals),
                        }
                        for sm in result.included
                    ],
                    "dropped_count": len(result.dropped),
                }
            except ValueError as e:
                return {"error": str(e)}

        @self.mcp.tool()
        def forget_memory(memory_id: str) -> dict[str, Any]:
            """Remove a memory by its ID."""
            deleted = forget(storage, memory_id)
            return {"deleted": deleted, "memory_id": memory_id}

        @self.mcp.tool()
        def check_memory_freshness(
            scopes: str = "project-shared",
            memory_id: str | None = None,
        ) -> dict[str, Any]:
            """Check whether anchored memories are fresh, stale, or orphaned."""
            scope_list = [s.strip() for s in scopes.split(",")]
            try:
                reports = check_freshness(
                    storage, scopes=scope_list, memory_id=memory_id
                )
                return {
                    "repor
[truncated — 16209 more characters]
```

### .gitguardian.yml

```yaml
version: 2

# The test_security.py file contains INTENTIONAL fake secrets used to verify
# that mnemex's security module correctly strips them. These are not real
# credentials — they are test fixtures.
secret:
  ignored-paths:
    - "tests/test_security.py"
    - "tests/test_security.py/**"

```

### tests/test_tui.py

```python
from __future__ import annotations

from mnemex.anchors import remember
from mnemex.tui import build_dashboard, render_dashboard
from mnemex.storage import Storage


def test_dashboard_reports_local_decision_health() -> None:
    with Storage() as storage:
        remember(storage, "Use UTC timestamps")

        summary = build_dashboard(storage)

        assert summary.memories == 1
        assert summary.active == 1
        assert summary.decision_health_percent == 100
        assert summary.review_candidates == 1
        rendered = render_dashboard(summary)
        assert "active decisions" in rendered
        assert "decision health" in rendered
        assert "pending conflicts" in rendered

```

### tests/test_mcp_stdio_integration.py

```python
from __future__ import annotations

import asyncio
import sys
from pathlib import Path

from mcp.client.session import ClientSession
from mcp.client.stdio import StdioServerParameters, stdio_client


def test_stdio_mcp_initialize_list_and_call(tmp_path: Path) -> None:
    asyncio.run(_exercise_stdio_server(tmp_path / "mnemex.sqlite3"))


async def _exercise_stdio_server(database: Path) -> None:
    parameters = StdioServerParameters(
        command=sys.executable,
        args=["-m", "mnemex", "serve", "--db", str(database)],
        cwd=str(Path.cwd()),
    )
    async with stdio_client(parameters) as (reader, writer):
        async with ClientSession(reader, writer) as session:
            initialized = await session.initialize()
            assert initialized.serverInfo.name == "mnemex"

            tools = await session.list_tools()
            assert "check_proposed_change" in {tool.name for tool in tools.tools}

            stored = await session.call_tool(
                "remember_decision",
                {"content": "Keep authentication stateless."},
            )
            assert stored.isError is False

            checked = await session.call_tool(
                "check_proposed_change",
                {
                    "path": "src/auth.py",
                    "patch_summary": "Add server-side session state.",
                },
            )
            assert checked.isError is False

```

### tests/test_anchor_typing.py

```python
"""Regression tests for public anchor-API input-type validation.

The independent Phase 1 verifier observed that an invalid ``anchor`` argument
type previously surfaced a raw ``AttributeError`` from internal attribute
access. A public entry point should fail closed with a clear ``TypeError``.
"""

import pytest

from mnemex.anchors import Anchor, remember, resolve_anchor
from mnemex.storage import Storage


@pytest.mark.parametrize("bad_anchor", [123, 4.5, b"node", ["node"], {"n": 1}])
def test_resolve_anchor_rejects_non_str_non_anchor(bad_anchor: object) -> None:
    with Storage() as storage:
        with pytest.raises(TypeError, match="anchor must be an Anchor or str"):
            resolve_anchor(storage, bad_anchor)  # type: ignore[arg-type]


@pytest.mark.parametrize("bad_anchor", [123, object()])
def test_remember_rejects_bad_anchor_type_without_persisting(
    bad_anchor: object,
) -> None:
    with Storage() as storage:
        with pytest.raises(TypeError, match="anchor must be an Anchor or str"):
            remember(storage, "should not persist", anchor=bad_anchor)  # type: ignore[arg-type]
        assert storage.list_memories(("project-shared",)) == []


def test_valid_anchor_types_still_resolve() -> None:
    with Storage() as storage:
        node = storage
        assert isinstance(node, Storage)
        # A str and an equivalent Anchor must remain accepted paths.
        with pytest.raises(Exception) as by_string:
            resolve_anchor(storage, "absent-node")
        with pytest.raises(Exception) as by_anchor:
            resolve_anchor(storage, Anchor(node_id="absent-node"))
        # Both take the not-found path, not the TypeError guard.
        assert not isinstance(by_string.value, TypeError)
        assert not isinstance(by_anchor.value, TypeError)

```

### tests/test_config.py

```python
from __future__ import annotations

import pytest

from mnemex.config import MnemexConfig


def test_defaults_keep_semantic_judge_disabled() -> None:
    config = MnemexConfig()

    assert config.semantic_judge_enabled is False
    assert config.openai_model == "gpt-5.6"


def test_environment_key_does_not_enable_semantic_judge() -> None:
    config = MnemexConfig.from_env({"OPENAI_API_KEY": "key-present"})

    assert config.semantic_judge_enabled is False
    assert config.openai_api_key == "key-present"


def test_blank_environment_key_is_treated_as_absent() -> None:
    config = MnemexConfig.from_env({"OPENAI_API_KEY": "   "})

    assert config.openai_api_key is None


def test_from_env_loads_explicit_openai_settings() -> None:
    config = MnemexConfig.from_env({
        "MNEMEX_SEMANTIC_JUDGE_ENABLED": "true",
        "MNEMEX_OPENAI_API_KEY": "configured-key",
        "MNEMEX_OPENAI_MODEL": "test-model",
        "MNEMEX_OPENAI_TIMEOUT_SECONDS": "4.5",
        "MNEMEX_MAX_EVIDENCE_TOKENS": "321",
    })

    assert config.semantic_judge_enabled is True
    assert config.openai_api_key == "configured-key"
    assert config.openai_model == "test-model"
    assert config.openai_timeout_seconds == 4.5
    assert config.max_evidence_tokens == 321


@pytest.mark.parametrize("value", ["", "maybe", "enabled"])
def test_from_env_rejects_invalid_enablement(value: str) -> None:
    with pytest.raises(ValueError, match="MNEMEX_SEMANTIC_JUDGE_ENABLED"):
        MnemexConfig.from_env({"MNEMEX_SEMANTIC_JUDGE_ENABLED": value})


def test_config_rejects_invalid_limits() -> None:
    with pytest.raises(ValueError, match="max_evidence_tokens"):
        MnemexConfig(max_evidence_tokens=0)

    with pytest.raises(ValueError, match="openai_timeout_seconds"):
        MnemexConfig(openai_timeout_seconds=0)

```

[76 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]