ai-memory v0.8.0

Migrating from v0.6.4 to v0.7.0

v0.7.0 — attested-cortex rolls together the v0.6.5 cortex-fluent legibility work with ROADMAP §7.3’s full v0.7 trust + A2A maturity scope, plus (per operator directive 2026-05-09) the originally-v0.7.1 postgres+AGE first-class work, plus the post-grand-slam ship-readiness wave (Batman Forms 1-6 + 7th-form Option-B foundation + QW-1/2/3 + reconciliation security sweep). The substrate becomes both more articulate (capabilities v3, named loaders, compacted schemas, Batman MemoryKind vocabulary, persona/atomisation/multistep-ingest primitives) and cryptographically trustworthy (Ed25519 attestation, sidechain transcripts, programmable 25-event hook pipeline, enforced namespace inheritance, V-4 cross-row signed-events hash chain).

Status: Released 2026-05-15 at HEAD c9472c1. v0.7.0 closes the attested-cortex epic at 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, plus the post-grand-slam Forms 1-6 + 7th-form + QW closeout (PRs #761-#766). See CHANGELOG.md for the full release entry and docs/v0.7.0/release-notes.md for the per-area walk-through. The canonical post-grand-slam feature truth lives at docs/internal/v070-feature-inventory.md.


What’s new at a glance

Area What ships Default behavior Opt-in surface
Capabilities v3 summary, to_describe_to_user, callable_now, agent_permitted_families, schema_version="3" Returned alongside v2 fields Read v3 fields if present; v2 unchanged
Loader tools memory_load_family, memory_smart_load join core Always-on under --profile core (now 7 tools, was 5) Replace memory_capabilities --include-schema ergonomics
Hook pipeline 25 lifecycle events, exec + daemon modes No change — no hooks fire ~/.config/ai-memory/hooks.toml (doc)
Ed25519 attestation Per-agent keypair, link signing, attest_level enum, signed_events audit table attest_level = "unsigned" for legacy callers ai-memory identity generate
Signed-events V-4 chain Cross-row prev_hash + sequence SHA-256 chain On for new daemons (backfilled by migrate_v34_backfill_chain) ai-memory verify-signed-events-chain (doc)
Sidechain transcripts zstd-3 BLOB store, memory_transcript_links, memory_replay Off [transcripts] config per namespace (doc)
Apache AGE acceleration Cypher backend for KG ops, memory_find_paths SQLite/CTE path unchanged Install AGE Postgres extension
Postgres-first SAL ai-memory serve --store-url postgres://…, ai-memory schema-init Sqlite default unchanged Build with --features sal-postgres
G1 inheritance enforcement resolve_governance_policy walks the namespace chain Behavior change for pre-v0.6.3.1 v0.6.x users Per-policy inherit: bool (default true)
Permission system Refactored governance with rules + modes + hooks → decision permissions.mode = "enforce" (was "advisory" in v0.6.4) ai-memory governance migrate-to-permissions (doc)
Federation hardening mTLS + X-API-Key + fingerprint allowlist Same as v0.6.4 if not configured Three new AI_MEMORY_FED_* env vars (doc)
K8 quota tool memory_quota_status + /api/v1/quota/status Per-agent daily quota tracked, surfaced on demand doc
K10 SSE approvals /api/v1/approvals/stream with mandatory HMAC Off when permissions.mode = "off" doc
Batman Form 1 (online dedup-and-synthesis) Single-batch action-emitting LLM on store Opt-IN via existing autonomy flag legacy_per_pair_classifier = true to revert
Batman Form 2 (synchronous atomise) memory_atomise MCP tool + pre-store hook auto_atomise_mode = Off default Synchronous | Deferred (doc)
Batman Form 3 (multi-step ingest) memory_ingest_multistep MCP tool Caller-driven doc
Batman Form 4 (fact provenance) Citations + source-URI + atom-grain spans Caller-driven via memory_store payload doc
Batman Form 5 (auto-confidence) memory_calibrate_confidence MCP tool Shadow mode by default Four AI_MEMORY_CONFIDENCE_* env vars (doc)
Batman Form 6 (MemoryKind vocab) 10-variant enum, optional auto-classify pre-store hook auto_classify_kind = off default regex_only \| regex_then_llm (doc)
Batman 7th-form (Layer-4 wiring) Operator-signed rules R001..R004, memory_check_agent_action, memory_rule_list Substrate-INTERNAL writes gated; agent-EXTERNAL callable_now flag v0.8.0 full cover per #697
QW-1 file-backed reflection export memory_export_reflection MCP tool Opt-IN per namespace auto_export_reflections_to_filesystem = true
QW-2 persona-as-artifact memory_persona + memory_persona_generate tools, MemoryKind::Persona Opt-IN per namespace auto_persona_trigger_every_n_memories = N (doc)
QW-3 context-offload primitive memory_offload + memory_deref tools Caller-driven doc
Provider-agnostic LLM substrate (#1067) LlmProvider::OpenAiCompatible wire shape, 15 vendor aliases, OllamaClient::from_env() Backend defaults to ollama if AI_MEMORY_LLM_BACKEND unset — every v0.6.4 caller keeps working Set AI_MEMORY_LLM_BACKEND + (alias-specific) API key env var to route the smart / autonomous tier to any of xAI / OpenAI / Anthropic / Gemini / DeepSeek / Kimi / Qwen / Mistral / Groq / Together / Cerebras / OpenRouter / Fireworks / LMStudio / vLLM / llama.cpp
Mobile target CI (#1068) aarch64-apple-ios + aarch64-linux-android cross-compile gates, iOS xcframework + Android jniLibs release artifacts, simulator/emulator runtime tests No change for desktop deploys Consume ai-memory-ios.xcframework.tar.gz + ai-memory-android.tar.gz release artifacts; C-ABI FFI surface itself lands v0.7.x

Why this matters

v0.7.0 closes three long-standing gaps at once.

Legibility gap (cortex-fluent): the 2026-05-05 NHI Discovery Gate verdict on v0.6.4 came back 6/6 PASS, GATE GREEN, but reasoning-class LLMs (Grok 4.2 reasoning) didn’t find the runtime loader because it lived inside an introspection tool’s parameter set. v0.7.0 promotes loaders to first-class tools (memory_load_family, memory_smart_load) and pre-computes per-agent calibration in memory_capabilities v3.

Trust gap (attested): v0.6.3 left an Ed25519 signature column in memory_links that nothing populated (the v0.6.3 audit’s “dead column” finding). Hook events were advertised via subscriptions but lifecycle hooks weren’t programmable. Permissions were advisory, not enforced. Namespace inheritance was display-only — a parent Approve rule didn’t actually block a child write. v0.7.0 fills the column, ships the hook pipeline, enforces inheritance, and lands an append-only signed_events audit chain with cross-row hash chaining (V-4 closeout #698).

Write-time-investment gap (Batman): the v0.7.0 audit at commit 53b4d39 (docs/internal/batman-framework-audit.md) found 0 of Batman’s 6 forms cleanly IMPLEMENTED. The post-grand-slam Forms 1-6 wave + the 7th-form Option-B foundation closed every gap; HEAD c9472c1 ships all 7 forms IMPLEMENTED.


Action required

Most users on v0.6.4 see two intentional behavior changes + a few opt-in surfaces:

  1. permissions.mode flips advisoryenforce (F8 — Round-2 NHI sweep). Operators who relied on default-permissive behavior must opt back in via [permissions] mode = "advisory" in config.toml.
  2. ai-memory forget --pattern / --tier without --namespace requires --confirm-global (F11). Scoped forget is unchanged.
  3. Pre-v0.6.3.1 v0.6.x users — the G1 namespace inheritance fix (already shipped in v0.6.3.1) means parent Approve policies now block child writes. See G1 inheritance fix below.
  4. Operators with custom governance policies — run ai-memory governance migrate-to-permissions --dry-run before upgrading to preview the migration.
  5. SDK consumers reading memory_capabilities — v3 adds fields; v2 fields remain. No code changes required, but adopt v3 fields for richer per-agent calibration.

Capabilities v3 schema additions

memory_capabilities v3 is additive. Every v0.6.4 v2 field stays at its current path and shape. v3 layers on:

Field Type Purpose
schema_version string ("3") Top-level version tag.
summary string Top-level pre-computed description (“AI Memory MCP exposes a 7-tool core with N additional families available via runtime expansion.”)
to_describe_to_user string Human-shaped summary the agent can paraphrase verbatim — eliminates calibration drift.
callable_now bool (per tool) Whether this caller may invoke the tool right now (allowlist + profile aware).
agent_permitted_families array Families this caller is allowed to expand into via memory_load_family.
memory_kind_vocab object Form-6 vocabulary + auto-classify modes (see docs/memory-kind-vocab.md).

Clients that pin schema_version: 2 continue to receive the v2 shape — v2 stays supported through v0.7.x.

Backward compat: v0.6.4 SDKs continue to work — they read v2 fields and ignore the new top-level keys.

To inspect the live v3 response (there is no ai-memory mcp call sub-subcommand — probe the stdio server with raw JSON-RPC, or use the HTTP surface):

printf '%s\n' \
  '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"memory_capabilities","arguments":{}}}' \
  | ai-memory mcp --profile full
# or, with a running daemon:
curl -s http://127.0.0.1:9077/api/v1/capabilities | jq .memory_kind_vocab

New MCP tools

v0.7.0 adds the following tools relative to v0.6.4. Every tool ships in src/mcp/registry.rs and is grouped into a Family (see src/profile.rs). Full post-grand-slam inventory: docs/internal/v070-feature-inventory.md.

Tool Family Track / form One-line description
memory_load_family(family) Core B1 Always-on loader — registers the named family’s tools without restarting the MCP server.
memory_smart_load(intent) Core B2 Embedding-matched loader — picks the family that best fits a natural-language intent string.
memory_find_paths(source, target, max_depth=5) Graph J7 Returns paths through the knowledge graph; Cypher on AGE, recursive CTE on SQLite.
memory_replay(memory_id, depth?) Graph I4 / L2-4 Reconstructs the transcript chain for a memory by traversing memory_transcript_links.
memory_verify(link_id) Graph H4 Returns {signature_verified, attest_level, signed_by, signed_at} for a link.
memory_pending_list Power K10 Lists pending approval requests. Note: the original v0.7-alpha drafts called this memory_approval_pending; the shipped name is memory_pending_list.
memory_pending_approve(id, …) Power K10 Approves a pending action. HMAC-signed body required.
memory_pending_reject(id, …, remember=forever?) Power K10 Rejects a pending action; remember=forever enables progressive trust.
memory_subscription_dlq_list Power K7 Lists dead-letter subscription deliveries.
memory_subscription_replay Power K7 Replays a DLQ entry.
memory_quota_status Power K8 Returns the caller’s per-agent daily quota row. See docs/k8-quotas.md.
memory_reflect Power Recursive-learning Task 4/8 Substrate primitive that synthesises a reflection over a recall set; depth-capped per namespace.
memory_reflection_origin Power L2-2 / #667 Reads reflection_origin metadata that federation receivers stamp on import.
memory_dependents_of_invalidated Power L2-3 / #668 Notifies dependents when a Reflection→Reflection supersedes edge fires.
memory_check_agent_action Power 7th-form / #691 Dry-run check against the operator-signed rule corpus.
memory_rule_list Power 7th-form / #691 Read-only listing of the rule corpus.
memory_export_reflection Power QW-1 File-backed reflection-chain export.
memory_offload Power QW-3 Move a large blob out of the agent context window into addressable blob storage.
memory_deref Power QW-3 Read-side companion to memory_offload.
memory_atomise Power WT-1-C / Form 2 Curator decomposes a long memory into 2-10 atomic propositions.
memory_persona Power QW-2 Read/write a MemoryKind::Persona row.
memory_persona_generate Power QW-2 Synthesise a persona over the entity’s observation set.
memory_ingest_multistep Power Form 3 / #756 Multi-step ingest orchestrator with deterministic helpers + prompt-cache reuse.
memory_calibrate_confidence Power Form 5 / #758 Per-source baseline calibration sweep (shadow mode by default).
memory_skill_register Other L1-5 Register a SKILL.md-format Agent Skill.
memory_skill_list Other L1-5 List skills.
memory_skill_get Other L1-5 Read a skill.
memory_skill_resource Other L1-5 Read a resource referenced by a skill.
memory_skill_export Other L1-5 Export a skill back to SKILL.md.
memory_skill_promote_from_reflection Other L2-6 / #671 Promote a Reflection (depth ≥ 1) to a SKILL.md-format Agent Skill.
memory_skill_compositional_context Other L2-7 / #672 Return a skill body + bounded reflection set ranked by recency + recall count.

Per-form migration notes

Form 1 — online dedup-and-synthesis (#754)

The store path now issues a single batch action-emitting LLM call that emits add | update | delete | no-op per existing memory candidate, replacing the v0.6.x per-pair binary yes/no classifier. The new path is gated by the same autonomous_hooks toggle that gated the v0.6.x classifier; behavior is opt-in.

To preserve the v0.6.x per-pair behavior on a specific namespace, set on the namespace standard:

{
  "governance": {
    "legacy_per_pair_classifier": true
  }
}

The shipped code path is src/synthesis/mod.rs. Tests: tests/form_1_synthesis.rs.

Form 2 — synchronous atomise-before-embed (#755)

New memory_atomise MCP tool + auto_atomise_mode namespace-policy field with three settings:

Token budget is governed by auto_atomise_threshold_cl100k (default 800) and auto_atomise_max_atom_tokens (default 200). Each atom is a first-class memory with an atom_of back-pointer and a signed derives_from edge.

Operator doc: docs/atomisation.md. Cookbook: cookbook/atomisation/01-basic-flow.sh.

Form 3 — multi-step ingest orchestrator (#756)

memory_ingest_multistep threads deterministic Jaccard+FTS helpers through prompt-cache-stable LLM stages. The two reference variants (two-phase Understand-Anything exemplar; four-step OpenKB exemplar) both produce reports with a single distinct cache key per run — provable by the cookbook recipe.

Operator doc: docs/multistep-ingest.md. Cookbook: cookbook/multistep-ingest/01-two-phase.sh.

Form 4 — fact provenance (#757)

No new MCP tool; provenance rides on the existing memory_store / memory_atomise payloads. Schema migration 0032_v07_form4_provenance.sql adds the columns. Federation wire shape stays backward-compatible — pre-v0.7.0 peers ignore the new fields.

Operator doc: docs/provenance.md.

Form 5 — auto-confidence + shadow-mode calibration + freshness decay (#758)

memory_calibrate_confidence MCP tool + four env vars:

Schema migration 0033_v07_form5_confidence_calibration.sql. Capability registry entry: CapabilityConfidenceCalibration at src/config.rs:1331.

Operator doc: docs/confidence-calibration.md.

Form 6 — MemoryKind Batman vocabulary (#759)

10-variant MemoryKind enum: Observation (default) + Reflection, Persona, Concept, Entity, Claim, Relation, Event, Conversation, Decision. No schema migrationmemories.memory_kind TEXT NOT NULL DEFAULT 'observation' has no CHECK constraint, so new variants land as new string values on the existing column.

Optional auto_classify_kind pre-store hook on each namespace standard: off | regex_only | regex_then_llm. See docs/memory-kind-vocab.md for the per-variant matrix and recall-filter wire shapes.

7th-form — agent-EXTERNAL Layer-4 wiring (#760)

Status: Option-B foundation SHIPPED in v0.7.0; full cover at v0.8.0 per #697.

What v0.7.0 ships:

What v0.7.0 ALSO ships (post-PE-1/PE-2/PE-3 fold — supersedes the earlier “v0.8.0 cover” framing):

What v0.7.0 does NOT ship (v0.8.0 cover, #697):

Audit-honest framing: see docs/RECURSIVE_LEARNING.md §Substrate authority claim. Operator doc: docs/policy-engine.md + docs/governance/agent-action-rules.md.


Hook pipeline (opt-in)

v0.7.0 ships 25 lifecycle hook events (20 baseline + 5 grand-slam additions) at every memory operation point — a programmable extension surface that R3 (auto-link inference) and R5 (auto-extraction) build on.

Default: no behavior change. Hooks fire only when ~/.config/ai-memory/hooks.toml exists and registers them.

[[hook]]
event = "post_store"
command = "/usr/local/bin/auto-link-detector"
priority = 100
timeout_ms = 5000
mode = "daemon"
enabled = true
namespace = "team/*"

Event matrix (25 events):

Decision contract: hooks return one of Allow, Modify(delta) (pre- events only), Deny{reason, code}, or AskUser{prompt, options, default}. Chain ordering is priority-desc; first Deny short-circuits.

Hot-path constraint: post_recall and post_search default to daemon mode, preserving the v0.6.3 50ms recall p95 budget. mode = "exec" requires explicit override.

Operator doc: docs/hook-pipeline.md.


Ed25519 attestation (opt-in)

v0.7.0 fills the dead signature column shipped in v0.6.3 with real cryptographic attestation. Per-agent Ed25519 keypair (operator-supplied — not derived from agent_id). Outbound signing on every memory_links write. Inbound verification against the observed_by claim.

ai-memory identity generate --agent-id ai:claude-code@host:pid-12345
ai-memory identity list
ai-memory identity export-pub --agent-id ai:claude-code@host:pid-12345

Keys live at ~/.config/ai-memory/keys/<agent_id>.{pub,priv} with mode 0600 / 0644.

attest_level enum (links / signed events): unsigned (no keypair present — preserves v0.6.4 backward compat), self_signed (active agent has a keypair; outbound writes signed), peer_attested (federated link verified against the peer’s known public key), plus two later v0.7.0 additions: signed_by_peer (L4 memory_capture_turn host-signed memory verified against AI_MEMORY_L4_HOST_PUBKEY_ALLOWLIST) and daemon_signed (the daemon’s own signature on its governance-audit emissions). (The store-path write attestation in metadata.attest_level uses a separate two-value vocabulary: claimed / agent_attested.)

Append-only signed_events audit table records every signed write — no UPDATE or DELETE through the application layer. The V-4 closeout (#698) added a cross-row hash chain (prev_hash BLOB + sequence INTEGER) so the chain itself is tamper-evident, not just each row. Verify with:

ai-memory verify-signed-events-chain --format json

Operator doc: docs/signed-events-v4.md.

Hardware-backed key storage (TPM / HSM / Secure Enclave) is out of OSS scope per ROADMAP; available in the AgenticMem commercial layer.


Sidechain transcripts (opt-in per namespace)

v0.7.0 adds raw conversation/reasoning trail storage in zstd-3-compressed BLOBs, linked to derived memories via memory_transcript_links. Substrate for R5 auto-extraction.

Default: off. Opt in per namespace via [transcripts] config (times are in seconds; per-namespace tables live under [transcripts.namespaces."<pattern>"]):

[transcripts]
default_ttl_secs   = 2592000   # 30d archive-eligibility
archive_grace_secs = 604800    # 7d linger before prune

  [transcripts.namespaces."team/*"]
  default_ttl_secs   = 2592000
  archive_grace_secs = 604800
  auto_extract       = true    # opt into the R5 transcript-extractor hook

Background sweeper archives transcripts whose memories are all expired, then prunes after a grace period. memory_replay(memory_id, depth?) reconstructs the transcript chain; depth=0 reproduces the pre-L2-4 shape.

Operator doc: docs/sidechain-transcripts.md.


Apache AGE acceleration (opt-in)

v0.7.0 detects Apache AGE in Postgres at SAL initialization (SELECT * FROM pg_extension WHERE extname='age'). When present, KG operations route through Cypher; otherwise the recursive-CTE path used since v0.6.x stays in place.

Install AGE in your Postgres instance and restart ai-memory. To confirm which backend is live, check the daemon’s startup log — the postgres adapter emits one INFO line at connect (tracing target store::postgres):

Postgres KG backend: AGE     # Cypher path active
Postgres KG backend: CTE     # recursive-CTE fallback

(The v0.7-alpha drafts referenced an ai-memory doctor --kg-backend flag and a populated kg_backend capabilities field; neither shipped at v0.7.0 — the capabilities envelope declares the kg_backend field but no production surface populates it yet, so the startup log line is the authoritative check.)

Acceptance gate: AGE p95 must beat CTE p95 by ≥30% at depth=5 to ship — the bench gate (feat/v0.7-j-8-age-bench-gate) enforces it. If AGE isn’t faster on your hardware, stay on the CTE path.


G1 inheritance fix (behavior change)

Note: this fix already shipped in v0.6.3.1. If you upgraded through v0.6.3.1 → v0.6.4 → v0.7.0, you have it already. This section is for users still on pre-v0.6.3.1 v0.6.x jumping straight to v0.7.0.

The change: resolve_governance_policy(namespace) now walks build_namespace_chain(namespace) and returns the first non-null policy encountered, not just the leaf. A parent Approve policy will now block a child write that previously slipped through.

Worked example:

Mitigation for backward compat: every GovernancePolicy row gains an inherit: bool field (default true). Existing rows are backfilled to inherit=true. To preserve pre-v0.6.3.1 behavior on a specific child policy, set inherit=false so its parent’s policy is not consulted.

UPDATE governance_policies
   SET inherit = false
 WHERE namespace = 'team/alice';

Audit log records which ancestor’s policy fired on every gate decision.


Permissions migration

v0.7.0 refactors the existing governance system into:

→ a single Decision. Default deny-first; ask-by-default for ambiguous cases.

Migration tool (idempotent, dry-run by default):

ai-memory governance migrate-to-permissions               # dry-run (prints, writes nothing)
ai-memory governance migrate-to-permissions \
  --config-out ~/.config/ai-memory/config.toml            # commit (in-place merge)

The dry-run prints the rendered [[permissions.rules]] block derived from the legacy [[governance.policy]] entries; --config-out <path> writes it (an in-place merge when the path matches the loaded config; --config-in <path> overrides the source). Re-running is safe.

Honest disclosures from v0.6.3.1 close out:

Operator docs: docs/governance.md, docs/policy-engine.md, docs/k10-sse-approvals.md.


Provider-agnostic LLM backend (#1067 + #1146)

v0.7.0 promotes the LLM client to a provider-agnostic substrate. Pre-v0.7.0 the smart and autonomous tiers required local Ollama; post-v0.7.0 the same tiers run against any of: local Ollama, xAI Grok, OpenAI, Anthropic (via the OpenAI shim), Google Gemini, DeepSeek, Kimi (Moonshot), Qwen (Alibaba), Mistral, Groq, Together AI, Cerebras, OpenRouter, Fireworks, LMStudio, vLLM, or llama.cpp server.

No action required for v0.6.4 callersAI_MEMORY_LLM_BACKEND defaults to ollama if unset, and the legacy OLLAMA_BASE_URL env var is still honoured. Every v0.6.4 setup keeps working byte-identically.

v0.7.x ships a single source of truth for LLM / embeddings / reranker / storage configuration. Every surface (MCP stdio, HTTP daemon, ai-memory atomise, ai-memory curator, the boot banner, the ai-memory doctor reachability probe) consumes the same resolver output, so the boot banner and the live MCP server are guaranteed to report the same backend.

# ~/.config/ai-memory/config.toml
schema_version = 2

tier = "autonomous"
db   = "/Users/<you>/.claude/ai-memory.db"

[llm]
backend     = "xai"                    # ollama | openai | xai | anthropic | gemini |
                                       # deepseek | kimi | qwen | mistral | groq |
                                       # together | cerebras | openrouter |
                                       # fireworks | lmstudio | openai-compatible
model       = "grok-4.3"               # vendor-specific identifier
base_url    = "https://api.x.ai/v1"   # optional; vendor-default if unset
api_key_env = "XAI_API_KEY"            # process-env-var name (NOT the literal key)

# Alternative to api_key_env — file (mode 0400 enforced).
# api_key_file = "/etc/ai-memory/keys/xai.key"

Secret-handling discipline. Inline api_key = "<literal>" in config.toml is rejected at parse timeconfig.toml is typically world-readable, so literal keys would be a credential leak. Operators MUST use either api_key_env (process-env reference) or api_key_file (file path; mode 0400 enforced, AI_MEMORY_PASSPHRASE_FILE_ALLOW_LAX_PERMS=1 escape hatch available per #1055). api_key_env and api_key_file are mutually exclusive.

Why this is the recommended path. Pre-#1146 operators routed LLM config through the mcpServers.<name>.env block of ~/.claude.json (or the equivalent per AI client). That works but has three operator-hostile properties:

  1. The ai-memory boot SessionStart hook reads from the parent shell env, not the MCP-spawned subprocess env, so the boot banner reported the legacy Ollama default while the live MCP server correctly used the configured backend — two sources of truth, one confused operator.
  2. Every AI client (Claude Code, Cursor, Codex CLI, Cline, Continue, Zed, Windsurf, Goose, Roo Code, Aider, Cody, openclaw, …) needed its own duplicate env block to behave consistently.
  3. API keys lived inline in ~/.claude.json — typically world-readable in $HOME.

[llm] in config.toml retires all three. The same file is consulted by every surface; the resolver enforces no-inline-keys; the boot banner reports llm=<backend>:<model> matching the live MCP server.

Migration tool

# Rewrite legacy v0.6.x flat-field config.toml into v2 sectioned shape.
ai-memory config migrate              # writes config.toml.bak.<ts> + rewrites in place
ai-memory config migrate --dry-run    # prints the diff to stderr, writes nothing

# After verifying the new config works, sweep stale env blocks from claude.json.
ai-memory config migrate --also-clean-claude-json

Idempotent — running against an already-v2 file is a no-op INFO log.

Verification

# Boot banner — should now report your configured backend, not the legacy default.
ai-memory boot --quiet --limit 1

# Reachability probe — DNS + TLS + auth round-trip against the resolved base_url.
ai-memory doctor | grep -A8 "LLM Reachability (#1146)"

The doctor probe emits PASS / WARN / CRIT with the resolved provenance facts (backend, model, base_url, config_source, key_source) so operators can see WHICH precedence layer won.

Precedence ladder (uniform across all four resolvers)

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

Same ladder applies to resolve_llm_auto_tag, resolve_embeddings, resolve_reranker, and resolve_storage.

Override path — AI_MEMORY_LLM_* env vars

Env vars still work — they’re the override layer ABOVE the config file. Useful for CI / per-session tweaks. Same examples in env-var form:

# Example: xAI Grok 4.3 for the autonomous tier (v0.7.0 compiled default for xai backend)
export AI_MEMORY_LLM_BACKEND=xai           # alias — pre-fills base URL
export AI_MEMORY_LLM_MODEL=grok-4.3
export XAI_API_KEY=xai-…                    # per-vendor fallback (or AI_MEMORY_LLM_API_KEY)

# Example: OpenAI gpt-5 (v0.7.0 compiled default for openai backend)
export AI_MEMORY_LLM_BACKEND=openai
export AI_MEMORY_LLM_MODEL=gpt-5
export OPENAI_API_KEY=sk-…

# Example: Anthropic Claude Opus 4.7 (v0.7.0 compiled default for anthropic backend)
export AI_MEMORY_LLM_BACKEND=anthropic
export AI_MEMORY_LLM_MODEL=claude-opus-4.7
export ANTHROPIC_API_KEY=sk-ant-…

# Example: generic OpenAI-compatible endpoint (vLLM, llama.cpp server, etc.)
export AI_MEMORY_LLM_BACKEND=openai-compatible
export AI_MEMORY_LLM_BASE_URL=http://your-host:8000/v1   # REQUIRED — no default URL
export AI_MEMORY_LLM_MODEL=your-model
export AI_MEMORY_LLM_API_KEY=

Tier independence. Setting AI_MEMORY_LLM_BACKEND (or [llm].backend) routes the LLM client through the provider-agnostic path regardless of tier. Tier still determines which other features run (embedder, reranker).

Operator references


Mobile target CI (#1068)

The Rust library (crate-type = ["rlib", "staticlib", "cdylib"]) now cross-compiles for iOS + Android. v0.7.0 ships the CI pipeline:

The C-callable FFI surface itself (#[no_mangle] extern "C" items) lands in a v0.7.x follow-up; v0.7.0 ships the BUILD pipeline + artifact layout. Consuming the artifacts:

Operator doc: tests/mobile/README.md — test selection rationale + ~$10/month cost-cap rationale.


Federation hardening

v0.7.0 hardens the v0.6.x federation surface with mTLS + X-API-Key + SHA-256 cert fingerprint allowlist + three new AI_MEMORY_FED_* env vars:

Operator doc: docs/federation.md.


Upgrade steps

# 1. Stop the running daemon (there is no `ai-memory stop` subcommand —
#    use your service manager, or pkill -INT for shell-launched daemons)
systemctl --user stop ai-memory   # or: pkill -INT ai-memory

# 2. Backup your DB before the schema migration. Default DB filename
#    is `ai-memory.db` (NOT `memory.db`); confirm via `ai-memory --help`.
cp ~/.claude/ai-memory.db ~/.claude/ai-memory.db.v0.6.4.bak

# 3. Upgrade the binary. UNTIL v0.7.0 IS PUBLISHED to the
#    alphaonedev/homebrew-tap formula AND crates.io, prefer the
#    GitHub Releases tarball or the --tag v0.7.0 source build.
#    Release assets are named by Rust target triple
#    (x86_64-unknown-linux-gnu / aarch64-unknown-linux-gnu /
#    x86_64-apple-darwin / aarch64-apple-darwin /
#    x86_64-pc-windows-msvc.zip). Example, Linux x86_64:
curl -fsSL "https://github.com/alphaonedev/ai-memory-mcp/releases/download/v0.7.0/ai-memory-x86_64-unknown-linux-gnu.tar.gz" \
  | tar -xz -C ~/.local/bin
# OR from source (locks the tag explicitly):
cargo install --git https://github.com/alphaonedev/ai-memory-mcp \
  --tag v0.7.0 --locked ai-memory
# Once the tap formula + crates.io publication land:
# brew upgrade ai-memory   /   cargo install ai-memory

# 4. Migrate config.toml (v0.6.x flat fields → v2 sectioned shape)
ai-memory config migrate --dry-run        # preview
ai-memory config migrate                  # apply with timestamped .bak
ai-memory config migrate --also-clean-claude-json   # OPT-IN: also strip
                                                     # mcpServers.<*>.env
                                                     # from ~/.claude.json
                                                     # (only direct
                                                     # `ai-memory` launchers
                                                     # — wrapper-script
                                                     # launchers need manual
                                                     # audit: `grep -l
                                                     # AI_MEMORY_LLM
                                                     # ~/.claude.json`)

# 5. Preview the permissions migration (governance v0.6.4 → permissions v0.7.0)
ai-memory governance migrate-to-permissions               # dry-run

# 6. Apply if happy (in-place merge into the loaded config)
ai-memory governance migrate-to-permissions \
  --config-out ~/.config/ai-memory/config.toml

# 7. (Optional) Generate an Ed25519 keypair for outbound link signing.
#    Omitting --agent-id uses the NHI-hardened default id.
ai-memory identity generate

# 8. Restart
systemctl --user start ai-memory   # or relaunch your MCP host

# 9. Verify — `ai-memory doctor` now reports 10 sections incl. the new
#    "LLM Reachability (#1146)" section that probes the resolved backend.
ai-memory doctor
ai-memory doctor --tokens
curl -s http://127.0.0.1:9077/api/v1/capabilities | jq '.schema_version'
ai-memory verify-signed-events-chain --format json | jq -e .chain_holds

For the comprehensive 3-tier walkthrough (non-technical / SME-fleet / DevOps-idempotent recipes), see docs/MIGRATION_QUICKSTART.md. The QUICKSTART covers the security-posture default flips (federation sig+nonce, permissions enforce, SSRF DNS-fail-CLOSED, governance fail-CLOSED, passphrase strict-perms) with their staged-rollout opt-out env vars.

Schema migrations (sqlite v20 → v57, postgres v15 → v57 logical) run automatically on first start of a sqlite-backed daemon and are idempotent. Postgres schema bootstrap is via ai-memory schema-init per docs/migration-v0.7.0-postgres.md. Canonical anchors: CURRENT_SCHEMA_VERSION = 57 in both src/storage/migrations.rs (sqlite) and src/store/postgres.rs (postgres); the highest on-disk migration file is migrations/sqlite/0047_v56_list_composite_indexes.sql (sqlite splits per-bump, file-name counter ≠ logical version; v54 is an in-code backfill arm with no new .sql file, while v55/v56 add their indexes via their own .sql files, and v57 is a sqlite no-op version stamp) and the postgres in-process migrate_v57() arm (postgres runs a single greenfield+upgrade pair, with v34 → v57 deltas applied through in-process migrate_v34()migrate_v57() async functions; migrate_v55() and migrate_v56() are version-stamp no-ops on postgres — memories_updated_at_idx (updated_at DESC) already serves the federation-catchup range scan, and the #1579 v56 composite list/archive indexes are sqlite-side fixes paired with the sargable storage::list rewrite — while migrate_v57() (#1579 B2) adds the stored generated tsv tsvector column + memories_tsv_gin GIN index and drops the legacy memories_content_fts expression index, so the search/recall shapes match AND rank on a write-time-precomputed column; the ADD COLUMN takes an ACCESS EXCLUSIVE table rewrite, sub-second at ~8k rows).

Per-bump narrative for the post-v34 sqlite ladder (every operator SHOULD back up to a sibling .bak before ai-memory serve first-touches the DB — the migrator also writes its own automatic <db>.pre-migration-v<from>-to-v<to>-<token>.bak VACUUM INTO snapshot before any schema-mutating upgrade — because the daemon will traverse all 24 of these bumps on first open):

Operator advisory. Before the first ai-memory serve against a v0.6.4-era DB, run:

cp ~/.local/share/ai-memory/ai-memory.db ~/.local/share/ai-memory/ai-memory.db.bak.pre-v07
sqlite3 ~/.local/share/ai-memory/ai-memory.db.bak.pre-v07 'PRAGMA schema_version;' \
  > /dev/null && echo "backup verified readable"

The ladder is idempotent on restart but forward-only (not reversible) — once schema_version reaches 55, you cannot downgrade to v34 in place; restore from your .bak file (or the automatic pre-migration snapshot) if you need to roll back to v0.6.4.


What did not change