ai-memory v0.8.0

ai-memory v0.7.0 — attested-cortex (release notes)

Release procedure (operator-gated, post v0.7.0)

v0.7.0 separates CI verification from publish. ci.yml runs on every push + PR + tag (lint, check matrix, feature gates, dockerfile-validate, coverage). release.yml runs ONLY on explicit workflow_dispatch and handles the actual 5-channel fanout (binary builds + GitHub Release + crates.io + Homebrew tap + GHCR Docker + Fedora COPR).

To publish a tag:

# 1. Create the signed tag locally
git tag -s v<X.Y.Z> -m "..."

# 2. Push the tag — fires ci.yml verification only
git push origin v<X.Y.Z>

# 3. Wait for ci.yml to land GREEN (Check matrix is the release gate)

# 4. Manually trigger publish — operator-gated, intentional
gh workflow run release.yml \
  --repo alphaonedev/ai-memory-mcp \
  -f tag=v<X.Y.Z>

Pre-release tags (SemVer - suffix, e.g. v0.7.0-rc.1) auto-skip the downstream stable channels (crates.io, Homebrew, Docker, COPR) so operator dry-runs are safe.

This separation closes the historical gap where CI passing on a tag-push auto-fired the entire publish pipeline. The act of releasing is now a deliberate, named action — not a side effect of green tests.


Status (2026-05-09): v0.7.0 is release-pending Wave 1-4 cert. The original attested-cortex epic shipped at commit fcdd2a5 on 2026-05-06; the Round-2 multi-agent NHI sweep (PR #643 against round-2-fixes) closed 13 follow-on findings (F6-F18) including 3 blockers; the v0.7.0 A2A campaign re-cert and the operator-directed postgres+AGE first-class scope expansion (Wave 1-4) are landing in the same v0.7.0 tag rather than a separate v0.7.0.1 / v0.7.1.

Tag-cut criterion: two consecutive 100% GREEN A2A rounds against the binary built from round-2-fixes after Wave 1-4 lands, with both droplets pointed at a shared postgres+AGE backend (Wave 4 live-on-postgres acceptance gate).

Headline

v0.7.0 closes the attested-cortex epic — 69/69 tasks across 11 tracks (A/B/C/D/E/F/G/H/I/J/K) plus the grand-slam recursive-learning + Agent Skills + L1-6 substrate-rules wave (and the V-4 closeout #698 cross-row hash chain) — and ships postgres + Apache AGE as a first-class storage backend including live daemon support (ai-memory serve --store-url postgres://…), full schema parity with sqlite (Wave 1-4 narrative v15 → v28 port; terminal v0.7.0 ship is sqlite v34 / postgres v33 after L0.7 + L2 wave + V-4 closeout), 6-factor recall scoring parity, link migration, and a new ai-memory schema-init CLI verb.

The substrate becomes both more articulate (capabilities v3 with pre-computed calibration strings, named loaders, 52% MCP-tool token reduction on the full profile) and cryptographically trustworthy (per-agent Ed25519 attestation with append-only signed_events audit chain — with V-4 cross-row hash chain at v34 (#698) — sidechain transcripts with memory_replay, programmable 25-event hook pipeline, opt-in Apache AGE acceleration, K1/G1 namespace-inheritance enforcement, real permission system with deny-first semantics, A2A maturity). Note: signed-events row sig population is gated on the resolved daemon agent_id having an Ed25519 *.priv on disk under the key directory (src/main.rs:116-118load_daemon_signing_key returning None deliberately swallows the failure with the “continuing unsigned” stderr line; the cross-row hash chain itself remains tamper-evident in either posture).

v0.7.0 hardcoded-literal SSOT remediation — #1558 campaign (2026-06-09)

A five-batch burn-down of duplicated string/numeric literals across the substrate, enforced going forward by the pm-v3.1 mechanical ratchet gate (scripts/check-hardcoded-literals.sh, companion to scripts/check-vendor-literals.sh — baseline counts may only shrink). Predominantly behavior-preserving refactors; the wire-visible exceptions are called out explicitly below.

Operator advisories (behaviour changes — read before upgrading)

Structural SSOT modules (batches 1-5)

Gate + test fixes landed during the campaign

Federation operational findings (documented posture, not code changes)

Known operational postures at v0.7.0 (#1531 residual round, 2026-06-09)

Documented dispositions — deliberate v0.7.0 behavior, stated explicitly so operators don’t have to reverse-engineer it from source.

Substrate-native API embeddings (#1598, 2026-06-11)

The #1067 provider-agnostic substrate now extends to the embedder. Pre-#1598, semantic recall embeddings were local-only (Ollama-native /api/embed or the in-process MiniLM/nomic path); the [embeddings] section accepted only backend = "ollama" shapes. Post-#1598 the [embeddings] section is fully API-capable and CPU-only nodes can run the full semantic/autonomous tiers against a cloud or self-hosted embedding API.

New configuration surface.

Behaviour changes.

New operator tooling.

Reference shapes. The operator-selected reference cloud model is openrouter + google/gemini-embedding-2 (3072-dim, 8192-token context, ~$0.20/M tokens, Google AI Studio provider, USA; paid-tier only — no :free routes). For airgapped/self-hosted deployments the Apache-2.0 USA model set is nomic-embed-text-v1.5 (768d — zero re-embed for existing corpora), Snowflake arctic-embed l/m/s (1024/768/384) and IBM granite-embedding (768/384), served via TEI, vLLM, or llama.cpp server. See the two new enterprise reference architectures: CPU + Memory federated nodes and CPU + Memory + GPU federated nodes.

v0.7.0 provider-agnostic LLM substrate (#1067, 2026-05-21)

The historical Ollama-only LLM client is now a provider-agnostic LLM substrate. Every tier (keyword / semantic / smart / autonomous) can speak to any vendor — one code path, two wire shapes, fifteen pre-configured vendor aliases.

Wire shapes:

Vendor aliases (pre-filled base URL + per-vendor API-key env var):

Alias Default base URL Fallback env vars
ollama http://localhost:11434 (none — no auth)
openai https://api.openai.com/v1 OPENAI_API_KEY
xai https://api.x.ai/v1 XAI_API_KEY
anthropic https://api.anthropic.com/v1 ANTHROPIC_API_KEY
gemini https://generativelanguage.googleapis.com/v1beta/openai GEMINI_API_KEY, GOOGLE_API_KEY
deepseek https://api.deepseek.com/v1 DEEPSEEK_API_KEY
kimi (= moonshot) https://api.moonshot.cn/v1 MOONSHOT_API_KEY, KIMI_API_KEY
qwen (= dashscope) https://dashscope.aliyuncs.com/compatible-mode/v1 DASHSCOPE_API_KEY, QWEN_API_KEY
mistral https://api.mistral.ai/v1 MISTRAL_API_KEY
groq https://api.groq.com/openai/v1 GROQ_API_KEY
together https://api.together.xyz/v1 TOGETHER_API_KEY
cerebras https://api.cerebras.ai/v1 CEREBRAS_API_KEY
openrouter https://openrouter.ai/api/v1 OPENROUTER_API_KEY
fireworks https://api.fireworks.ai/inference/v1 FIREWORKS_API_KEY
lmstudio http://localhost:1234/v1 (none)
openai-compatible (none — REQUIRES AI_MEMORY_LLM_BASE_URL) AI_MEMORY_LLM_API_KEY

Env-var surface (new):

Tier independence. The substrate’s tier no longer dictates which vendor speaks. Pre-#1067 the autonomous tier required local Ollama, forcing every customer to procure a GPU. Post-#1067, customers pick from a deployment matrix that spans Raspberry Pi / mobile edge ($0/mo) to enterprise multi-GPU clusters ($10k+/mo); the substrate is identical, only the env vars change.

Auto-tag chat-shape routing (2026-05-21 follow-up to #1067). The runtime smoke against IronClaw-on-Grok-4.3 surfaced an L5 hook regression: auto_tag was routing through generate_with_body which was hardcoded to /api/generate (Ollama text-completion). Non-Ollama vendors don’t expose /api/generate, so every store request 404’d against the configured Grok endpoint. Fixed by routing auto_tag through generate_with_model_override which selects the provider-aware chat-shape (/api/chat for Ollama, /v1/chat/completions for OpenAI-compat); the model-override path (gemma3:4bgrok-4.3 etc.) is preserved across both branches. Wiremock tests updated; generate_with_body retained as a private legacy helper for the test that pins the check_outbound() gate ordering invariant.

docker-compose for IronClaw on Grok 4.3. The infra/lan-parity-test/docker-compose.yml ic-parity-alice + ic-parity-bob services now set AI_MEMORY_LLM_BACKEND=xai, AI_MEMORY_LLM_MODEL=${AI_MEMORY_LLM_MODEL:-grok-4}, and XAI_API_KEY=${XAI_API_KEY:?…} (REQUIRED, fail-fast). Operators run set -a; source ~/.env; set +a before docker compose up to inject the secret from a 0400-mode env file.

Schema reference: module-level docs at src/llm.rs; LlmProvider enum + OllamaClient::from_env() constructor are the canonical anchors.

v0.7.0 enterprise configuration standard (#1146, 2026-05-23)

The #1067 LLM substrate landed env-aware resolution into every production code path that builds an LLM client, but the operator configuration surface remained fragmented across five disjoint sources (~/.config/ai-memory/config.toml partial flat fields, ~/.claude.json MCP env block, the SessionStart hook env, compiled tier presets in src/config.rs, and process env). The boot banner reported one backend; the live MCP server ran another; API keys lived inline in ~/.claude.json. #1146 retires that fragmentation.

Single source of truth — sectioned config schema v2. ~/.config/ai-memory/config.toml now carries dedicated [llm], [llm.auto_tag], [embeddings], [reranker], and [storage] sections. The compile-time tier presets (src/config.rs::FeatureTier preset constructors + default_tier_llm_model) remain as the last-resort fallback under the precedence ladder, not as a parallel source of truth.

schema_version = 2

[llm]
backend     = "xai"
model       = "grok-4.3"
base_url    = "https://api.x.ai/v1"
api_key_env = "XAI_API_KEY"            # process-env-var name (NOT the literal key)
# api_key_file = "/etc/ai-memory/keys/xai.key"   # mode 0400 enforced; alt to api_key_env

[llm.auto_tag]
backend = "ollama"
model   = "gemma3:4b"

Canonical resolver — one shape, every surface. Every LLM-init site (MCP stdio at src/mcp/mod.rs, HTTP daemon at src/daemon_runtime.rs, ai-memory atomise at src/cli/commands/atomise.rs, ai-memory curator at src/cli/curator.rs, the boot banner at src/cli/boot.rs, the doctor probe at src/cli/doctor.rs) now consumes AppConfig::resolve_llm(cli_backend, cli_model, cli_base_url) -> ResolvedLlm with a uniform precedence ladder:

CLI flag  >  AI_MEMORY_LLM_* env  >  [llm] section  >  legacy flat fields  >  compiled default

Sister resolvers resolve_llm_auto_tag, resolve_embeddings, resolve_reranker, and resolve_storage follow the same ladder. The ResolvedLlm struct’s Debug impl redacts the resolved api_key to <redacted> so accidental {:?} prints never leak credentials.

Inline-key rejection. [llm].api_key = "<literal>" is rejected at parse time with a clear stderr error and the daemon falls back to AppConfig::default() so it still boots. Operators MUST use either [llm].api_key_env = "<ENV_VAR_NAME>" (process-env reference) or [llm].api_key_file = "/path" (file path; mode 0400 enforced via the AI_MEMORY_PASSPHRASE_FILE_ALLOW_LAX_PERMS escape hatch from #1055). api_key_env and api_key_file are mutually exclusive.

Migration tool — ai-memory config migrate. Rewrites a legacy v0.6.x flat-field config.toml in place (with a timestamped .bak) to the v2 sectioned shape. Idempotent. --dry-run prints the diff without writing; --also-clean-claude-json additionally removes mcpServers.<*>.env blocks whose command resolves to ai-memory from ~/.claude.json after the operator has verified the new config works.

ai-memory config migrate
ai-memory config migrate --dry-run
ai-memory config migrate --also-clean-claude-json

Reachability probe — ai-memory doctor. A new LLM Reachability (#1146) section in the doctor report resolves the canonical config and probes the endpoint with the resolved Bearer key (GET <base_url>/api/tags for Ollama, GET <base_url>/models for OpenAI-compatible). Severity partition:

Severity Outcome
INFO 200 (vendor reachable + auth OK)
WARN 401 / 403 (auth issue; URL reachable)
WARN 429 (rate-limited; reachable)
WARN 5xx (vendor outage; reachable)
CRIT 4xx other (likely wrong base_url / endpoint)
CRIT network / DNS / connect-refused / TLS error

The report surfaces the resolved provenance facts (backend, model, base_url, config_source, key_source) so operators see WHICH precedence layer won.

Legacy v0.6.x flat fields (llm_model, ollama_url, embed_url, embedding_model, cross_encoder, default_namespace, archive_on_gc, archive_max_days, max_memory_mb, auto_tag_model) continue to parse and feed the resolver’s Legacy arm. Loading a legacy config emits a Once-gated stderr WARN pointing at ai-memory config migrate. Legacy fields will be removed in v0.8.0.

HTTP / CLI surface additions. The Config subcommand and the /api/v1/config/* HTTP routes are new at v0.7.x; the count change is captured in CLAUDE.md §Architecture (88 HTTP routes, 78 CLI subcommands in default build / 80 under –features sal — SSOT: ai_memory::EXPECTED_PRODUCTION_ROUTES_COUNT and EXPECTED_CLI_SUBCOMMANDS_{DEFAULT,SAL} in src/lib.rs).

Test pins. Resolver precedence + secret-handling discipline are pinned by 19 tests under src/config::tests::*1146* + src/cli/commands/config::tests::migrate_*. Adding a new resolver field requires updating the resolver function AND the precedence test for that resolver.

Operator references.

v0.7.0 mobile target CI (#1068, Posture-1a 3-layer ship, 2026-05-21)

Lands the three-layer CI coverage for the Posture-1a (Edge / Mobile) row from #1067 — ai-memory’s claim to run on iPhone + Android via aarch64-apple-ios and aarch64-linux-android.

Layer Workflow Coverage
1 — cross-compile .github/workflows/ci.ymlmobile-cross-compile matrix job cargo check --no-default-features --features sqlite-bundled --lib on every PR + push to release/** for both aarch64-apple-ios (macos-latest with Apple SDK) and aarch64-linux-android (ubuntu-latest with NDK r26d via nttld/setup-ndk; CC_* / AR_* / CARGO_TARGET_*_LINKER pointed at android24-clang so rusqlite’s bundled SQLite C blob compiles against the Android sysroot). Catches ~80% of mobile bit-rot risk.
2 — release artifacts .github/workflows/release.ymlmobile-ios + mobile-android jobs mobile-ios builds aarch64-apple-ios + aarch64-apple-ios-sim + x86_64-apple-ios static libs, combines via xcodebuild -create-xcframework into AiMemory.xcframework with a cbindgen-generated C header + module.modulemap, publishes ai-memory-ios.xcframework.tar.gz + .sha256. mobile-android builds aarch64 / armv7 / x86_64 / i686 -linux-android .so files in the canonical jniLibs/<abi>/ layout, publishes ai-memory-android.tar.gz + .sha256. Gated on stable (non-prerelease) tags. Until #[no_mangle] extern "C" items land in src/lib.rs (v0.7.x follow-up), the C header is a stub.
3 — simulator / emulator runtime .github/workflows/mobile-runtime.yml ios-simulator runs tests/mobile_runtime.rs (13 scoped tests under tests/mobile/) on iPhone 15 via xcrun simctl spawn on macos-latest. android-emulator runs the same binary on a KVM-accelerated Android API-30 emulator via reactivecircus/android-emulator-runner@v2. Gated on release/** push + workflow_dispatch (PRs run iOS arm only to keep cost down). Tests cover: fs sandboxing + WAL/SHM sibling cleanup, FTS5 + PRAGMA journal_mode round-trip, HNSW build/query + zero-vector NaN pin, candle CPU tensor + matmul smoke, reqwest + wiremock OpenAI-compat TLS round-trip.

Selection rationale + ~$10/month cost-cap rationale documented at tests/mobile/README.md.

Cargo.toml [lib] crate-type was extended to ["rlib", "staticlib", "cdylib"] so the static lib (iOS) + dynamic lib (Android) artifacts actually link; rlib default preserved for every other consumer (bins, integration tests, downstream crates).

The “5 supported platforms” framing in older docs (Mac, Linux, Windows desktop / Mac, Linux server) is superseded — v0.7.0 ships CI coverage for 7+ targets: x86_64-linux, aarch64-linux, x86_64-apple-darwin, aarch64-apple-darwin, x86_64-pc-windows-{msvc,gnu}, aarch64-apple-ios, aarch64-linux-android.

v0.7.0 ship-readiness items (6-agent review batch, 2026-05-21)

The 6-agent v0.7.0 code+security review (post-PR-#820 merge) surfaced four discrete items that landed in commit e10830887 before the #1067 / #1068 substrate work.

Two additional CRITICAL items landed under commit 71baf2956 on the postgres SAL adapter:

Two additional follow-on closures landed in commit 7c7c102a2 beyond the auto_tag chat-shape fix described in §#1067 above:

v0.7.0 ship-readiness session 2026-05-20/05-21 — security + audit hardening

The 2026-05-20 → 2026-05-21 wave closed roughly 50 agent-filed issues spanning federation/audit chain, governance fail-CLOSED postures, MCP wire honesty, postgres parity, and the v48→v49 archived_memories carry. Every fix below has a regression test pinning it. Grouped by severity.

Operator advisories (behaviour changes — read before upgrading)

Three v0.7.0 postures flip from v0.6.x fail-open behaviour to v0.7.0 fail-CLOSED. Each has a one-line env-var escape hatch (documented in CLAUDE.md §Environment Variables rows 36-41) for operators who need the legacy posture during peer enrolment:

CRITICAL — schema bump + audit chain integrity

HIGH — federation, audit chain, governance

MEDIUM — postgres parity, audit-trail hygiene, performance

LOW — doc hygiene + cleanup

v0.7.0 ship-readiness session 2026-05-21 — registry refactor (Wave-2 Tier-D1)

The 2026-05-21 ship-readiness session closed #972 (MCP registry split) end-to-end across the D1.1 → D1.8 sub-tickets. The net change is structural — the wire shape is byte-identical modulo the documented allowed-diffs catalog — but the contributor recipe and the source-of-truth surface flip:

The contributor-facing recipe is documented in src/mcp/tools/README.md, CLAUDE.md § “Adding New Functionality”, and docs/audience/developer.html. The pre-D1.6 recipe (“add a JSON definition in tool_definitions() + add a match arm”) is gone.

v0.7.0 substrate-canonical-discipline (#1174 PR-train, pm-v3.1, 2026-05-23/24)

The pm-v3.1 substrate-canonical-discipline campaign closed #1174 end-to-end across 10 sequenced PRs (#1184#1200) plus the #1183 WrapStrategy split (#1199) and the #1192 / #1196 / #1205 RuntimeContext follow-up (#1204). Three independent decorrelated codegraph-driven QC audits confirmed ZERO-DEFECTS-CONFIRMED per pm-v3.2 NO FAIL MISSION closure discipline (ai-memory global/policies memory 2cb15d34-2399-4611-a020-df6ef91683fe).

The net change is structural — the wire shape is byte-identical — but the substrate-vendor literal scatter is gone, magic time-second literals are gone, the per-CLI-binary WrapStrategy table is split into a sibling module, and a process-wide RuntimeContext singleton replaces the dual ACTIVE_* / OVERRIDE_* static pairs.

Wave 1 — substrate const extraction (6 PRs).

Wave 2 — static-state extraction + cross-surface containers (5 PRs).

Wave 3 — load-bearing lint-gate enforcement (1 PR).

scripts/check-vendor-literals.sh (#1200) HARD-BLOCKs (a) vendor-monoculture literals ("claude", "openai", "xai", "anthropic", "gemini", "deepseek", "groq", "ollama", "grok", "mistral", "cohere", "huggingface") outside the 7-file substrate carve-out (src/llm.rs, src/config.rs, src/mine.rs, src/validate.rs, src/cli/wrap.rs, src/llm_cli_wrap.rs, src/harness.rs) and (b) Duration::from_secs(3600 | 86400 | 604800 | 3_600 | 86_400 | 604_800 | 7200 | 21600 | 172800) magic numbers anywhere in production code. The gate’s own --self-test mode injects a contrived "anthropic" literal at a production site, verifies the gate trips, then cleans up — providing a CI-side canary against future detection-logic decay. Wired into .github/workflows/c8-precheck.yml alongside the existing four cargo gates (fmt + clippy + test + audit). Full contract documented in CLAUDE.md §”Lint gates (issue #1174 PR10)”.

Wire impact. The PR9 (#1189) source: "claude"source: "nhi" / "api" test-fixture flip changes the source field on memories stored by the test harness only — production daemons preserve caller-supplied source verbatim (no live wire change). The PR4 (#1184) "ollama" substrate sweep is internal-only — all wire surfaces continue to accept and emit the literal string "ollama" as a backend name. The PR #1199 WrapStrategy module move is purely internal — src/cli/wrap.rs’s public surface is unchanged (the move split detection logic from the per-vendor table).

The CHANGELOG entry is the canonical record (commit 0b7530c92).

What’s new since v0.6.4

HNSW async rebuild + double-buffering (Wave-2 Tier-C3, issue #968)

The in-memory HNSW vector-index rebuild path is now non-blocking. Before this change, every rebuild — both the REBUILD_THRESHOLD-triggered auto-rebuild and the 100k-cap eviction-edge rebuild — ran synchronously on the request thread. Graph construction is O(N log N) with constant factors that put 100k vectors at ~3-10 s on commodity hardware, so the producer’s memory_store call (and every search caller contending on the inner mutex) blocked for the full build window. Recall p95 spiked from <20 ms to multi-second on the eviction edge.

Post-#968 the rebuild runs on a background thread (std::thread::spawn — HNSW build is CPU-bound; no tokio runtime needed). The new graph warms up in an Arc<Mutex<Option<RebuildResult>>> slot while readers and writers continue against the existing active graph; the swap into active is a single std::mem::swap under the inner mutex held for microseconds. Concurrent writes during rebuild flow into overflow normally; the swap path trims only the overflow prefix already in the new graph, so no write is ever dropped.

Operator-visible win. At the 100k cap eviction edge, memory_store returns in microseconds instead of blocking for the multi-second graph build. Bench-verified by cargo bench --bench hnsw_rebuild_async (release build, 2k-vector fixture): search p95 = 43 µs during a rebuild, well under the published 35 ms PERFORMANCE.md budget.

Four regression tests pin the contract in src/hnsw.rs::d1_968_tests:

The pre-existing synchronous VectorIndex::rebuild() is preserved as a shim that delegates to rebuild_async().join() + try_swap_warming() so the v0.6 test contract (“the graph is rebuilt by the time this returns”) is unchanged. New code paths should call rebuild_async() directly.

Provenance gaps 1-7 + dogfood-fix sprint (2026-05-18)

ai-memory v0.7.0 documented a 7-level provenance framework (Identity, Source, Causal, Capture confidence, Versioned, Reciprocal, Decoration) on the capabilities surface, but the substrate’s write + read paths carried partial coverage — every gap was a real defect under the prime directive. This sprint closes all seven end-to-end across the sqlite and postgres adapters, lands the four wire-schema + docstring fixes a 2026-05-19 dogfood session surfaced, and ships the postgres parity work tracked under issue #894. Tool count rises 71 → 73 (Gap 3 memory_recall_observations + the Gap 4 confidence_tier callable). At v0.7.0 release HEAD the schema ladder reaches v49 on both backends (single logical version after the #933 v48 federation_push_dlq table land and the #1025 v49 14-column carry on archived_memories). Cross-link to the full evidence bundle: docs/v0.7.0/test-campaign-2026-05-18-dogfood/.

The 7-level framework — gap × before × after × evidence.

Gap Level Before After Issue Commit
1 Versioned (optimistic concurrency) memory_update was last-write-wins; concurrent writers silently clobbered each other memories.version BIGINT NOT NULL DEFAULT 1; update_with_expected_version returns typed VersionConflict { id, expected_version, current_version }; MCP expected_version arg + HTTP If-Match: <version> → 409 with structured envelope #884 6ad87c8
2 Source (URI as first-class) source_uri lived in metadata JSON; un-indexable, un-queryable, surfaced only by full-row decode First-class column with partial index idx_memories_source_uri WHERE source_uri IS NOT NULL; schema v45 backfills from metadata.source_uri AND citations[0].uri; insert path promotes it out of metadata automatically #885 6ad87c8
3 Causal (recall-consumption ledger) Substrate couldn’t tell which recall candidates the caller actually cited downstream Schema v47 recall_observations ledger keyed by (recall_id, memory_id) with retriever, rank, score, consumed, consumed_by_memory_id columns; memory_recall stamps UUIDv4 recall_id into every response; memory_store + memory_link consume hook reads recall_id + cited_memory_ids and flips matching rows; new memory_recall_observations MCP tool for filtered read-back; TTL pruner gated by AI_MEMORY_OBSERVATIONS_TTL_DAYS (default 7) #886 3cd8c11
4 Capture confidence (tier breakpoints exposed) confidence was a bare f64; callers re-derived Confirmed / Likely / Ambiguous against undocumented breakpoints ConfidenceTier enum (Confirmed >= 0.95, Likely >= 0.7, Ambiguous < 0.7); Memory::confidence_tier() method; capabilities-v3 confidence_calibration.tier_thresholds block surfaces ConfidenceTierThresholds { confirmed, likely, ambiguous }; memory_recall accepts confidence_tier: Option<String> filter #887 23379e2
5 Reciprocal (edit-source on supersede) update_with_archive_on_supersede archived the old row but emitted no supersede-lineage audit columns archived_memories.archive_reason = 'superseded' on OLD row; new_memory.metadata.superseded_id forward pointer on NEW row; atomic write inside a transaction (SELECT FOR UPDATE → archive → delete old → insert new); the FK target_id REFERENCES memories(id) prevents a memory_links row at supersede time — provenance is encoded via the two metadata mechanisms instead #888 6ad87c8
6 Source (query by URI) source_uri filter unsupported — callers had to full-scan and post-filter MCP memory_search accepts source_uri query arg; storage search_with_source_uri + list_by_source_uri hit the partial index from Gap 2; namespace composability preserved #889 6ad87c8
7 Decoration (recall response audit envelope) memory_recall returned raw rows; callers re-derived freshness, link-attest, tier from N+1 lookups Default verbose_provenance=true decorates every row with confidence, derived confidence_tier (from Gap 4), source, source_uri, derived freshness_state (computed from expires_at + last_accessed_at + access_count), access_count, last_accessed_at, latest_link_attest_level (strongest AttestLevel across incident links); envelope echoes Gap 3 recall_id UUID for downstream citation #890 c3e344c

Wire contract notes.

Dogfood findings (2026-05-19 session) — 5 surfaced, 4 fixed in this sprint.

Per pm-v3 (memory cd8ede94): documentation drift between code behavior and docstrings is a real defect — file AND fix. The dogfood session that validated Gaps 1-7 on a live MCP daemon caught five contract violations the unit tests had not pinned. All five were filed at discovery; four shipped fixes in the same session.

Finding Class Resolution Commit
#892 MCP wire schema memory_store schema missing source_uri AND handler dropped it on the floor at validation.rs:224 (hard-coded None). Both sides fixed; SQL row now persists source_uri end-to-end through MCP. Verified against doc:dogfood-2026-05-19-verify test memory. CLOSED. 39aa158
#893 MCP wire schema memory_update schema missing expected_version + edit_source — handlers already read them but NHIs couldn’t discover them via tools/list. Schema fix also exposes source_uri on the update path. Verbose token budget trimmed 10196 → 9998 (under 10000 ceiling) by tightening 8 docstring blocks. CLOSED. 39aa158
#895 Docstring drift SupersedeResult docstring claimed a supersedes link was written; impl correctly skips (lines 1417-1423) because the FK target_id REFERENCES memories(id) would reject pointing at an archived id. Docstring corrected to document the actual two-mechanism encoding. CLOSED (docs path). The expensive path (relax FK to allow memory_links → archived_memories, OR parallel archive_links table) tracked separately for v0.7.0 consideration. 19b0854
#894 Adapter parity cargo build --features sal-postgres failed with 11 distinct compile errors in src/handlers/* (Memory / Utc / ConfidenceSource / StorageBackend / store_err_to_response / get_with_visibility_retry missing imports), blocking postgres adapter work from reaching the gate. All fixes scoped to cfg(sal-postgres)-gated import shuffles or visibility tweaks. Postgres SAL parity methods + 5 migrations landed in the same issue. CLOSED IN-SESSION (all sub-tasks: migrations + SAL methods + parity harness + unblocker). a69eed0, e3ae0a5, 9bec43c, 62cf9e4
#891 HTTP behavior HTTP /api/v1/search rejects source_uri-only with 400 — search_memories early-returns on empty q before the source_uri-only branch can run. One AC pin in tests/store_parity_gaps.rs is #[ignore]-marked against this. FILED, retained open for handler-side fix (pinned by the ignored AC). (pending)

Postgres + Apache AGE parity (issue #894). Five new migrations (commit a69eed0) mirror the sqlite v45/v46/v47 ladder onto postgres v25 → v29:

Greenfield deploys pick up identical columns + indexes inline from postgres_schema.sql; existing PG installs traverse the five-step ladder. Six inherent PostgresStore SAL methods (commit e3ae0a5) bring byte-identical parity with the sqlite-side storage:: free functions (update_with_expected_version, update_with_archive_on_supersede, search_with_source_uri, list_by_source_uri, plus the Gap 7 link-decoration twins). ~870 LOC. Inherent (not on the MemoryStore trait) so call-sites holding Arc<PostgresStore> can drive them today; the trait can be widened in a follow-up once both adapters stabilise.

Cross-adapter parity harness. tests/store_parity_gaps.rs (commit 9bec43c) adds six verify_<gap>_sqlite reference functions and six matching pg_parity_gap_<n> postgres twins. Sqlite-side tests always run; postgres-side tests are #[ignore] and self-skip when AI_MEMORY_TEST_POSTGRES_URL is unset. The harness compiles cleanly under both default and --features sal-postgres so a future runner that flips the env var picks up zero-friction parity coverage.

Track C/D status. The cross-adapter parity tests are green on the sqlite side and compile-clean on the postgres side, but live postgres execution remains gated on the issue #79 inter-subnet routing blocker (192.168.50.100 cannot reach the 192.168.1.50 postgres node — different subnets, no bridge / VPN / route). The substrate change is complete; what’s missing is network plumbing. The Track C/D verdict memo will mint to SHIP once the operator-side routing change lands and the same harness re-runs green against the live PG+AGE backend.

Regression coverage (51 new pin tests). Commit ce1415a maps every acceptance criterion in the seven gap issues to a named regression test. Total provenance-gap coverage advances 28 → 79 tests across 9 files (7 extended + 2 new HTTP files). Per-issue new test counts: #884 +5 (missing/clone/downcast/HTTP) + 5 new http_if_match_concurrency; #885 +5 (insert promotion / limit / idempotence); #886 +7 (since/until/noop/probe filters); #887 +5 (boundaries / serde / unknown filter); #888 +7 (parse / inherit / new-row v1); #889 +3 (ordering / namespace compose / kg_query) + 4 new http_source_uri_query; #890 +7 (freshness states / recall_id UUID). MCP recall_observations tool param-branch coverage (commit 913a2ff) pins the three previously-uncovered closure branches in src/mcp/tools/recall_observations.rs::handle_recall_observations (since / until / limit), lifting file line coverage from ~94.5% to > 98%.

Headline new capability — postgres+AGE first-class

Wave-3 Continuation 6 — F7 closure + mTLS-validated cert posture

Security hardening — federation red-team P2 closeouts

Two red-team #230 findings on /api/v1/sync/* are closed in v0.7.0 proper rather than deferred to v0.8.0:

Cert-SAN extraction follow-up. Today’s mTLS substrate (FingerprintAllowlistVerifier) pins client certificates by SHA-256 fingerprint but does not propagate the cert’s SAN/CN to handler code (axum-server 0.8 has no per-request extension surface for that). v0.7.0 closes the substantive integrity gaps using the x-peer-id header convention bound to fingerprints via operator deployment runbook. The cryptographic-attestation surface (cert SAN ↔ peer-id binding inside the verifier) lands in v0.8.0 — tracked as a follow-up to #238/#239.

Track-level rollup (the original epic, unchanged)

Round-2 NHI sweep findings (F1-F18, all closed in v0.7.0)

The v0.7.0 A2A campaign and the parallel post-ship NHI Round-2 sweep surfaced 18 findings; all 18 are closed in the v0.7.0 ship.

ID Severity Title Status
F1 P1 namespace_owner doesn’t walk parent chain — deep-child Owner write 403s Closed (commit e0d2086, issue #644)
F2 P1 audit sequence resets to 1 across daemon restart Closed (commit e0d2086, issue #645)
F3 P3 S70 import CLI flag drift (test-side) Closed
F4 P3 Harness.node_db_path() helper for multi-droplet topology Closed
F5 P3 AGE perf gate documentation Closed
F6 P3 postgres SQL views + migrate-links + schema-init CLI surfaces Closing in v0.7.0 via Wave 1-4 (issue #646)
F7 BLOCKER HTTP POST /memories bypasses agent_quotas Closed (commit f9ef40a)
F8 SECURITY permissions.mode defaults to advisory — flipped to enforce Closed (commit 579afe2, 63c46ab)
F9 release-notes HTTP missing-required field returns 422 not 400 Closed (commit f9ef40a)
F10 release-notes Embedder timeout silently produces un-indexed row at 201 Closed (commit f9ef40a)
F11 release-notes forget --pattern X without --namespace is GLOBAL — --confirm-global now required Closed (commit 579afe2, bd01978)
F12 release-notes Ed25519 keypair NOT auto-generated on serve startup Closed (commit 579afe2, 63c46ab)
F13 release-notes memory_capabilities schema/behavior drift Closed (commit 66f48ae)
F14 release-notes Smart-load router under-weights underscore tokens Closed (commit 66f48ae, 5b36d7c)
F15 release-notes MCP memory_store/memory_update missing metadata in inputSchema Closed (commit 66f48ae)
F16 release-notes agent_type MCP enum closed but daemon permissive Closed (commit 66f48ae)
F17 release-notes find_paths max_depth cap; directed vs undirected docs Closed (commit 082c999, f02d092)
F18 release-notes check_duplicate similarity caps at ~0.92 for byte-identical strings Closed (commit 082c999, 63c46ab)

Round-2-fixes folding (2026-05-11) — items originally triaged for v0.7.0.1, now in v0.7.0

Operator directive 2026-05-11: there will be no v0.7.0.1 patch release. The following items fold into v0.7.0 directly.

ID Severity Title Status
#318 high MCP stdio writes bypass federation fanout Closed in v0.7.0 — opt-in mcp_federation_forward_url forwards MCP memory_store to local HTTP daemon which runs broadcast_store_quorum
#355 low rustls-pemfile RUSTSEC-2025-0134 (unmaintained, transitive via axum-server) Closed in v0.7.0 — axum-server 0.7 → 0.8; cargo audit clean
#507 medium config.toml db = "~/..." not expanded Closed in v0.7.0 — expand_tilde helper in AppConfig::effective_db
#625 low E1/E2 orchestration scripts ported from bash to Rust binaries Closed in v0.7.0 — tools/t0-orchestrate/ + tools/post-ship-converge/ crates; bash deleted; #![cfg(unix)] gates dropped

Plus three v0.7.0 cert-driven fixes surfaced by Plan C R4:

Substrate-native recursive refinement (issue #655)

ai-memory v0.7.0 ships substrate-native recursive refinement with cryptographic provenance and bounded depth, alongside the broader attested-cortex epic and the Anthropic dreaming research preview. An agent reads one or more memories, synthesises a higher-order reflection (a lesson, pattern, contradiction-resolution, etc.), and persists it with cryptographic-grade provenance back to each source it reflects on. The reflection memory is just another memory row — the same recall, search, governance, federation, attestation, and audit primitives apply to it. The recursion is what’s new.

Bounded by design — not by aspiration. Reflection depth is substrate-enforced, not application-enforced: every reflection write goes through a single db::reflect substrate function that consults GovernancePolicy.max_reflection_depth (per-namespace), falls back to a compiled default of 3, and refuses any reflection whose proposed depth exceeds the cap with a structured REFLECTION_DEPTH_EXCEEDED error (HTTP 409). The cap is set in JSON governance metadata so operators can tune it per namespace without a schema migration. A per-namespace cap of Some(0) is a documented kill-switch — every reflection refuses, regardless of depth — for deployments that want to opt every namespace under that subtree out of the primitive entirely. No autonomous goal modification, no model fine-tuning loops, no unbounded recursion.

Concrete API hooks shipped in Tasks 1-4 of the epic (commits below; Tasks 5-8 land on the same branch and roll up into this v0.7.0 tag):

The relevant CHANGELOG block sits under the same v0.7.0 heading (“v0.7.0 recursive-learning add-on”). Conceptual model, depth-cap rationale, directionality contract, and the find_paths chain-walk behaviour are written up in docs/RECURSIVE_LEARNING.md. The reproducibility script is at scripts/reproduce-recursive-learning.sh — a self-contained Bash demo that builds the release binary, inserts three sample memories into a fresh sqlite DB under .local-runs/, reflects on them at depth=1, recursively reflects up to depth=3 (the default cap), and demonstrates the refusal at depth=4 with a clear REFLECTION_DEPTH_EXCEEDED verdict block.

Cryptographic provenance for cap refusals (Task 5/8)

Every memory_reflect call that would exceed the namespace’s resolved max_reflection_depth now appends a row to the append-only signed_events audit table before the cap refusal propagates back to the caller. The row carries event_type = "reflection.depth_exceeded" and a canonical-CBOR (RFC 8949 §4.2.1) payload binding (agent_id, attempted, cap, namespace, source_ids, proposed_title, created_at) under a SHA-256 payload_hash. The row is written with attest_level = "unsigned" (the substrate refusal is the operation being audited; per-event Ed25519 signing of refusal records is a separate Track-H Bucket-1.5 line item). The reflection’s content body is deliberately omitted from the payload — only enumerable provenance fields are part of the signed bytes, so PII the caller may have placed in content never enters the audit chain. Audit-write failures are best-effort: logged at WARN (target = "signed_events") but the substrate cap refusal still propagates so the wire contract stays unchanged for callers. (commit c61a05b.)

Hook integration (Task 6/8)

The Track-G hook pipeline grows from 21 to 23 events with two new HookEvent variants:

The pipeline event count is 21 → 23, not 20 → 22 — the G10 hot-path pre_recall_expand event had already raised the floor from 20 to 21 before Task 6 landed. Hook vetoes do not emit the Task 5 reflection.depth_exceeded audit row: caller-policy refusals carry their own provenance via the hook’s own audit channel, and conflating them with substrate-cap refusals would dilute the audit signal. The MCP-side wire-in of hooks.tomlReflectHooks is deferred to G7+; the v0.7.0 memory_reflect MCP handler ships an unreachable HookVeto arm against that bridge so the wire surface is forward- compatible without yet emitting hook events from the production handler. (commit fbf093c.)

Substrate-Native Recursive Learning Grand-Slam (NEW)

Operator-level summary. The v0.7.0 grand-slam wave extends the recursive-learning substrate primitive (issue #655) from “an MCP verb that mints a reflection memory” into a complete substrate-native learning loop: a curator that reflects across a namespace asynchronously, federation-aware cross-peer depth bookkeeping, invalidation propagation, transcript replay union, procurement-grade forensic export, reflection-to-skill promotion, skill ↔ reflection composition, and a reflection-aware reranker boost. The L1-6 substrate rules-enforcement engine ships the operator-keypair-signed rule store and the bypass-impossibility test fleet. Schema bumps to v33 (L2 wave CHECK constraint on memory_links.relation) and then v34 (V-4 closeout #698: signed_events cross-row hash chain — prev_hash BLOB + sequence INTEGER) per the v0.7.1-fold decision (05e0cb9a). Postgres parity is at v33 (the V-4 closeout maps to postgres v33 since the postgres ladder ran one step behind). The MCP tool count moves from 60 → 71 over the L2 wave + Batman Forms 1-6 + 7th-form + QW-1/2/3 closeout (the +8 over the original 63 narrative cover Forms/QW/L2 additions enumerated below); the full reflection narrative lives in docs/RECURSIVE_LEARNING.md, the Agent Skills surface in docs/agent-skills.md, and the forensic-export surface in docs/forensic-export.md.

Schema and tool-surface deltas

L1-6 substrate rules engine (issues #691, #693)

The v0.7.0 Option B substrate-authority foundation:

Audit-honest framing. Substrate authority is a foundation in v0.7.0, a complete cover in v0.8.0. Operators evaluating the authority claim today should read docs/RECURSIVE_LEARNING.md §Substrate authority claim alongside this section. Any “100% substrate authority” marketing that elides the wiring gap is inaccurate.

L2 wave — what landed

Task Commit Issue Headline
L2-1 reflection-pass curator c3f6e82 #666 Asynchronous curator clusters Observation-kind memories and mints reflections via the substrate path. Opt-in per namespace; honors the substrate cap; one level of reflection per pass.
L2-2 federation reflection coordination 0b1c9cc #667 Cross-peer depth bookkeeping. Receivers stamp metadata.reflection_origin = {peer_origin, original_depth, local_depth_at_arrival} on import and enforce the local cap on derived writes — federation cannot launder depth. memory_reflection_origin MCP tool.
L2-3 invalidation propagation 3f419be #668 A Reflection→Reflection supersedes edge fires the walker; one notification memory is written per dependent under <dependent.namespace>/_invalidations. Notification, NOT cascade. Cascade rollback is v0.8.0 Pillar 2.5.
L2-4 transcript replay union a50b34c #669 memory_replay on a reflection memory returns the union of transcripts reachable by walking reflects_on to the source observations. Caller controls the walk depth (depth=N); depth=0 reproduces the pre-L2-4 shape.
L2-5 forensic bundle bb870b3 #670 ai-memory export-forensic-bundle + verify-forensic-bundle: deterministic POSIX-ustar tar, byte-identical mod timestamp, operator-signed when keypair is on disk. AgenticMem Attest tier integration. See docs/forensic-export.md.
L2-6 reflection-as-skill 505c538 #671 memory_skill_promote_from_reflection promotes a reflection (depth ≥ 1) to a SKILL.md-format Agent Skill. Each reflects_on source becomes a references/source_{i}.md resource. Round-trip digest-identical to a hand-authored SKILL.md. Closes the recursive-learning loop.
L2-7 skill composition 0966b57 #672 composes_with_reflections SKILL.md frontmatter declares a skill’s affinity for one or more reflection-bearing namespaces. memory_skill_compositional_context returns the body + bounded reflection set ranked by recency + recall_count. Per-namespace max_reflection_depth is the authoritative ceiling — composition cannot bypass the cap.
L2-8 reflection-aware reranker boost 90291c0 #673 Reranker applies boost * (1 + per_depth_increment * min(depth, cap)) to Reflection-kind memories AFTER the cross-encoder blend. Defaults 1.2 / 0.05 / 3. boost = 1.0 is the documented kill-switch.

Agent Skills (Pillar 1.5)

The L1-5 Agent Skills ingestion substrate landed on the grand-slam branch as the substrate path for agentskills.io-compliant SKILL.md modules:

The closing-loop bridge: a reflection memory ↔ a skill manifest. Operators codify learnings into skills via memory_skill_promote_from_reflection, then activate them on demand with memory_skill_compositional_context returning a bounded reflection context alongside the skill body.

Forensic export (AgenticMem Attest)

The L1-3 verify-reflection-chain and L2-5 export-forensic-bundle / verify-forensic-bundle triad ships the procurement-grade evidence path. Full surface in docs/forensic-export.md. Headlines:

Quality

Post-grand-slam ship-readiness wave (2026-05-15)

After the original attested-cortex epic landed at fcdd2a5 (2026-05-06) and the Round-2 NHI sweep closed F1-F18, a final ship-readiness wave folded into the v0.7.0 tag rather than slipping to v0.7.1:

Backward compatibility

Breaking changes

The v0.7.0 ship has two intentional behavior changes over v0.6.4 that may affect existing deployments:

F8 — permissions.mode flips from advisory to enforce

Before (v0.6.4 / v0.7.0-alpha): fresh deploys had no write enforcement by default — a security default-bad. The Round-2 NHI sweep flagged this as a SECURITY DECISION.

After (v0.7.0 ship): permissions.mode defaults to enforce. Operators who relied on the old default-permissive behavior must opt back in explicitly:

# config.toml
[permissions]
mode = "advisory"

The first ai-memory serve boot prints a one-time migration banner explaining the change.

F11 — forget --pattern and forget --tier without --namespace require --confirm-global

Before: ai-memory forget --pattern foo silently deleted matching memories across all namespaces.

After: the same command refuses to run without an explicit --confirm-global flag. --namespace-scoped forget is unchanged.

# v0.6.4 behavior — global delete (now refused):
ai-memory forget --pattern 'PII:.*'

# v0.7.0 — must be explicit:
ai-memory forget --pattern 'PII:.*' --confirm-global

Upgrade path

From v0.6.4 (sqlite, staying on sqlite)

  1. Backup ~/.local/share/ai-memory/memory.db.
  2. Install v0.7.0 (brew upgrade ai-memory / cargo install ai-memory / your distro path).
  3. First start auto-migrates v20 → v34 (transcripts, signed_events, audit chain, attest_level on memory_links, recursive-learning reflection_depth, memory_links.relation SQL-side CHECK, and the V-4 closeout signed_events.prev_hash + sequence cross-row hash chain). Watch the daemon log for schema migration: v20 → v34 complete.
  4. Read docs/MIGRATION_v0.7.md for the v0.6.4 → v0.7.0 surface changes (permissions.mode, forget safety, new MCP tools).

From v0.6.4 (sqlite, switching to postgres)

Follow docs/migration-v0.7.0-postgres.md:

  1. Provision postgres + Apache AGE + pgvector per docs/postgres-age-guide.md.
  2. ai-memory schema-init --store-url postgres://….
  3. ai-memory migrate --from sqlite:///… --to postgres://… --dry-run.
  4. Real migration; verify row counts + content fingerprint.
  5. Re-point the daemon at postgres via --store-url or AI_MEMORY_STORE_URL.
  6. Confirm /api/v1/capabilities reports store_backend: PostgresStore and kg_backend: Age.

From v0.7-alpha (postgres at schema v15)

  1. ai-memory schema-init --store-url postgres://… --upgrade to walk v15 → v33 idempotently (Wave 1-4 ported v15 → v28; subsequent L0.7 / L2 wave / V-4 closeout added v29-v33 on the postgres side).
  2. Restart the daemon.
  3. (Optional) Re-run the migration tool to backfill links if your v0.7-alpha migration predated the Wave 1 link-walk fix: `ai-memory migrate –from sqlite:///… –to postgres://… –since ` — only the delta migrates.

Operator references

Tracking issues + PRs

Post-tag follow-up batches (NHI re-run, 2026-05-17 / 2026-05-18)

After the v0.7.0 tag, the NHI re-run campaign (canonical results at docs/v0.7.0/test-campaign-2026-05-18/) surfaced a fix-batch the lane-1 meta-lane absorbed without slipping the tag. All items shipped in local/install-815-816 (HEAD 875bc19 on 2026-05-18) and pre-merged into the v0.7.0 retag candidate:

Closed documentation-labeled issues as part of this lane-5 sweep:

Acknowledgements

The Round-2 NHI sweep was driven by a 5-agent parallel orchestration against the live v0.7.0-alpha binary on a multi-droplet DigitalOcean topology. The expanded postgres+AGE scope was driven by a 3-stream parallel implementation under PR #643. The full A2A campaign artifact trail is at https://alphaonedev.github.io/ai-memory-a2a-v0.7.0/.

— AlphaOne LLC, 2026-05-09