ai-memory  /  v0.7.0 architecture
v0.7.0 attested-cortex · post-#1174 substrate surface

The substrate, post-refactor train.

ai-memory v0.7.0 ships three interfaces (MCP stdio, HTTP REST, native CLI) over one SQLite-backed core with optional Postgres+AGE adapter. This page is the canonical architecture snapshot at release/v0.7.0 @ 1cb95bbe7 — post-#1174 pm-v3.1 refactor train, post-#1182 vendor-neutrality sweep, post-#1192/#1196 cross-surface RuntimeContext.

release/v0.7.0 @ 1cb95bbe7 74 MCP / 89 HTTP / 80 CLI schema v57 15-vendor LLM substrate lint-gated CI
Surface counts (canonical, code-asserted)

Three interfaces. One core.

MCP — profile full
74

Advertised entries on --profile full (73 callable tools + the always-on memory_capabilities bootstrap). Default --profile core ships 7 tools. Plus 2 prompts. Canonical assertion: Profile::full().expected_tool_count() in src/profile.rs.

HTTP — /api/v1/*
88

Production .route(...) registrations in src/lib.rs (75 unique URL paths × multiple HTTP methods per path) plus the bare /metrics Prometheus surface. Authoritative source: codegraph codegraph_search kind=route limit=100.

CLI — subcommands
79

Top-level Command enum variants in src/daemon_runtime.rs (default build ships 80; --features sal/sal-postgres unlocks 82 by adding migrate + schema-init, both #[cfg(feature = "sal")] — not postgres-only).

Each count is asserted by a regression test. The architectural North Star (per CLAUDE.md prime directive): long-term codebase manageability. Growth narrative: 70 MCP / 73 HTTP / 40 CLI at v0.6.4 → 74 / 89 / 80 at v0.7.0 (FX-12/ARCH-3 + FX-C3 batch2 added 16 CLI subcommands closing MCP/CLI parity; v0.7.0 attested-cortex feature surface + #1146 sectioned-config + #1156 per-namespace K8 quota + #1255 federation-nonce persistence (federation_nonce_cache) drove the HTTP + schema bumps).

#1174 refactor train (pm-v3.1)

Vendor-neutrality, named constants, RuntimeContext.

The pm-v3.1 refactor mandate (canonical memory f5334545-c1f5-4f5c-9efb-a0ec3a0c1fcd) landed across 10 PRs (#1184-#1200), supplemented by the #1192+#1196 follow-up (#1204) and the #1183 split. The combined output:

PRScopeOutcome
#1187PR1 — extract 74 MCP tool namesNew crate::mcp::registry::tool_names::* const module. Every tool-name literal at production-code sites reads from here instead of scattered "memory_*" string literals.
#1188PR2 — HTTP MIME constantsHEADER_CONTENT_TYPE / MIME_JSON extracted; eliminates the "application/json" literal-by-grep refactor risk.
#1185PR3 — SECS_PER_* constantsSECS_PER_HOUR (3_600), SECS_PER_DAY (86_400), SECS_PER_WEEK (604_800). Lint-gated against Duration::from_secs(3600 | 86400 | 604800 | ...).
#1184PR4 — Ollama backend literal sweepEvery production-code "ollama" literal reads crate::llm::BACKEND_OLLAMA. Substrate is vendor-neutral by code, not just by docs.
#1190PR5 — DEFAULT_NAMESPACE extractionDisambiguated from quotas::GLOBAL_NAMESPACE. The user-facing default + the quota sentinel are now two semantically distinct named constants.
#1186PR6 — raw tier strings → Tier::<X>.as_str()Every "short" / "mid" / "long" literal in production code routes through the typed enum.
#1191PR7 — collapse permissions-mode dual-sourceEliminated the ACTIVE / OVERRIDE_PERMISSIONS_MODE static drift surface. Single RwLock source of truth.
#1195PR8 — Class A SHOULD statics → AppStateWave-2 boundary: scattered OnceLock<T> statics promoted to AppState / metrics registry fields. Tests can now construct an isolated AppState without bleeding global state.
#1189PR9 — vendor-specific test-fixture deflakeTest fixtures stop hardcoding "claude" as a stand-in source-of-truth; routed through DEFAULT_NHI_SOURCE.
#1200PR10 — lint-gate CI enforcementscripts/check-vendor-literals.sh in CI. HARD-BLOCK on vendor identifiers + SECS_PER_* magic numbers outside the 7-file substrate carve-out. Self-test verifies the gate is load-bearing.
#1204#1192+#1196 follow-up — RuntimeContextCross-surface RuntimeContext threading: HTTP / MCP / CLI / federation paths thread one struct carrying the resolved agent + config + caller-context instead of N positional args.
#1199#1183 — WrapStrategy splitPer-CLI-binary WrapStrategy table moved from src/cli/wrap.rs to sibling module src/llm_cli_wrap.rs. Vendor-specific knowledge stays in the substrate carve-out.

Why the lint-gate is load-bearing. A scripts/check-vendor-literals.sh --self-test step in CI injects a contrived "anthropic" literal at a production site, runs the gate, verifies the gate exits non-zero with the expected violation message, then cleans up. A regression in the gate's detection logic (e.g. an over-broad allowlist, a broken test-boundary heuristic) trips immediately rather than silently letting drift back in. Per pm-v3.2 NO FAIL MISSION closure discipline (memory 2cb15d34-...): the gate itself must be load-bearing, not decorative.

Public consts surface (canonical lookups)

Read from named constants, not from grep-and-hope.

The post-#1174 public-const surface gives every production-code path a single source of truth for substrate identifiers:

Module pathWhat it carries
crate::mcp::registry::tool_names::*All 74 MCP tool names (e.g. RECALL, STORE, CAPABILITIES). Production code reads these instead of bare string literals.
crate::llm::BACKEND_OLLAMA
crate::llm::BACKEND_OPENAI
crate::llm::BACKEND_XAI
15 vendor-backend identifiers (ollama, openai, xai, anthropic, gemini, deepseek, kimi, qwen, mistral, groq, together, cerebras, openrouter, fireworks, lmstudio) + the generic openai-compatible escape hatch.
crate::config::KNOWN_EMBEDDING_DIMSCanonical embedding-model → vector-dim lookup table (closes #1169 drift surface). canonical_embedding_dim(model) helper threads through ResolvedEmbeddings.
crate::DEFAULT_NHI_SOURCEVendor-neutral default source identifier ("nhi") for the reflection / curator / synthesis surfaces. Replaces the pre-#1175 hardcoded "claude" (the hardcode was removed by commit 84ec159c1; current source-stamp site uses DEFAULT_NHI_SOURCE from src/validate.rs:85).
crate::DEFAULT_NAMESPACE
crate::quotas::GLOBAL_NAMESPACE
User-facing default vs quota-sentinel namespaces (disambiguated per PR5 #1190).
crate::SECS_PER_HOUR
crate::SECS_PER_DAY
crate::SECS_PER_WEEK
Time-unit constants. Lint-gated against magic-number Duration::from_secs(3600 | 86400 | 604800).
crate::HEADER_CONTENT_TYPE
crate::MIME_JSON
HTTP header + MIME constants. Eliminates "application/json" string-literal drift surface.
crate::Tier::Short.as_str()
crate::Tier::Mid.as_str()
crate::Tier::Long.as_str()
Typed-enum tier identifiers. Pre-#1186 raw "short" / "mid" / "long" literals are gone from production code.
RuntimeContext (cross-surface)

One context object. Four entry surfaces.

Per #1192/#1196 follow-up landed in PR #1204: the HTTP / MCP / CLI / federation entry surfaces now thread a single RuntimeContext carrying the resolved agent, config slice, caller-context, and admin-allowlist resolution rather than N positional args. The pattern eliminates the pre-#1174 drift surface where (a) adding a new resolution input meant patching N callers per surface and (b) wire-side handlers and MCP-side handlers could diverge on which inputs they consumed.

Why this matters at the substrate level:

Heterogeneous LLM substrate (#1067)

15 vendors. One client. Tier-independent.

Pre-#1067 the LLM client (then OllamaClient) was Ollama-only and gated by feature tier. Post-#1067 (v0.7.0): tier no longer dictates LLM vendor. Every tier (keyword / semantic / smart / autonomous) can speak to any provider via AI_MEMORY_LLM_BACKEND:

The substrate-side struct name OllamaClient is preserved post-#1066 for call-site backward compat; rename to LlmClient is non-breaking and tracked separately.

Schema v57 ladder

Single logical version, two adapters.

Both adapters carry CURRENT_SCHEMA_VERSION = 57. SQLite ladder ends at the v57 version stamp in src/storage/migrations.rs (the v56 composite-index arm is version-pinned); postgres ladder ends at migrate_v57() in src/store/postgres.rs. The two adapters share a single logical schema number even though the on-disk file-name counters differ because the sqlite split numbers per-bump while the postgres ladder is a single greenfield+upgrade pair.

Migrations run on first open of the new binary. The migration set is dry-run-tested against the operator's own DB by scripts/dogfood-rebuild.sh before a release ships.

Next

Where to go from here.