ai-memory v0.8.0

v0.7.0 Epic — attested-cortex

One sentence: v0.7.0 rolls the v0.6.5 cortex-fluent legibility work together with ROADMAP §7.3’s full v0.7 trust + A2A maturity scope into a single release — the substrate becomes both more articulate (loaders that say “load,” capabilities that pre-compute their own description, schemas at half the token cost) and cryptographically trustworthy (Ed25519 attestation, sidechain transcripts, hook pipeline, namespace inheritance enforcement, A2A maturity).

Status: PLANNED · 2026-05-05 Supersedes: v0.6.5 epic (cortex-fluent) — rolled into this document Predecessor: v0.6.4 — quiet-tools — shipped 2026-05-05 Original v0.7 spec: ROADMAP.md §7.3 (Q2 2026, June target — now consolidating into attested-cortex) Tracking issues: #545 · #546 · #512 · plus all G/H/I/J/K issues to be filed at kickoff


Why this release exists

Two narratives converge in v0.7.0:

Narrative 1 — cortex-fluent (legibility) — from v0.6.5 epic: The 2026-05-05 NHI Discovery Gate verdict on v0.6.4 came back 6/6 PASS, GATE GREEN, but three real-world LLM observation cells captured the same day showed a legibility gap — reasoning-class LLMs (Grok 4.2 reasoning) didn’t find the runtime loader because it’s hidden inside an introspection tool’s parameter set. The substrate did its job; the language hadn’t quite caught up.

Narrative 2 — attested (trust) — from ROADMAP §7.3: The v0.6.3 release left Ed25519 signature columns in memory_links that nothing populates (“dead column” — flagged in §5.3 audit). Hook events are advertised via subscription system but lifecycle hooks aren’t programmable. Permissions are advisory, not enforced. Namespace inheritance is display-only — leaf-policy gating means a parent Approve rule doesn’t actually block a child write. A2A correlation, ACK retries, replay protection are all known gaps.

v0.7.0 closes both at once. The substrate becomes the thing it has been describing itself as: a self-documenting, attested, programmable, cryptographically trustworthy memory cortex that an NHI fleet can ground its identity in.


Goals (priority-ordered)

Mandatory cutline (per ROADMAP §7.3)

  1. G1 — Namespace inheritance enforcement. “Even if everything else slips, this fix ships.” resolve_governance_policy walks the namespace chain; first non-null policy wins.

Required for cortex-fluent narrative (from v0.6.5 epic)

  1. Loader tools (memory_load_family, memory_smart_load(intent)) get names that say “load.” Reasoning-class LLMs find them on first ask.
  2. memory_capabilities v3 carries summary, to_describe_to_user, callable_now, agent_permitted_families — pre-computed calibration.
  3. --profile full drops to ≤ 3,500 input tokens via schema compaction.

Required for attested narrative (from ROADMAP §7.3)

  1. Hook pipeline ships: 20 lifecycle events, JSON-stdio + daemon-mode IPC, decision types, chain ordering, hot reload.
  2. Ed25519 attestation: per-agent keypair, outbound signing, inbound verification, attest_level enum, append-only signed_events audit table.
  3. Sidechain transcripts: zstd-compressed BLOBs, per-namespace TTL, memory_replay, R5 auto-extraction substrate.
  4. Apache AGE acceleration: AGE detected in Postgres SAL, Cypher implementations of KG ops, dual-path tests, R2 memory_find_paths.
  5. A2A maturity: correlation IDs, ACKs, retry, TTL, replay protection. Subscription reliability (DLQ, replay-from-cursor, HMAC). Per-agent quotas. Permission system with deny-first semantics. Approval API (HTTP + SSE + MCP, HMAC, remember=forever).
  1. Discovery Gate T0 calibration cells re-run after ship; ≥95% convergence across 4 LLMs.
  2. Cross-harness benchmark + per-harness positioning updates.

Non-goals


Workstream summary

Track Scope Tasks Effort Required? Source
A — Capabilities v3 response shape 5 ~1 wk 🔴 REQUIRED v0.6.5 #545  
B — Loader tools (memory_load_family, memory_smart_load) 5 ~2-3 wk 🔴 REQUIRED v0.6.5 #546  
C — Schema compaction 5 ~1 wk 🟢 STRONGLY REC v0.6.5 #546  
D — Per-harness positioning + tests 4 ~3-5 d 🟢 STRONGLY REC v0.6.5  
E — Discovery Gate T0 cells 3 ~3 d 🟢 STRONGLY REC v0.6.5  
G — Hook Pipeline (Bucket 0) 11 ~3-4 wk 🔴 REQUIRED ROADMAP §7.3 Bucket 0  
H — Ed25519 Attested Identity (Bucket 1) 6 ~2 wk 🔴 REQUIRED ROADMAP §7.3 Bucket 1  
I — Sidechain Transcripts (Bucket 1.7) 5 ~2 wk 🔴 REQUIRED ROADMAP §7.3 Bucket 1.7  
J — Apache AGE Acceleration (Bucket 2) 8 ~3 wk 🔴 REQUIRED ROADMAP §7.3 Bucket 2  
K — A2A + Permissions + G1 cutline (Bucket 3) 11 ~3-4 wk 🔴 REQUIRED (G1 mandatory) ROADMAP §7.3 Bucket 3  
F — Docs + release 6 ~3-5 d 🔴 REQUIRED (every release) v0.6.5 + this rollup  

Total: 69 tasks across 11 tracks.

Sequential effort: ~17-20 weeks (~4-5 months). With moderate parallelism (2-3 contributors): ~10-12 weeks. Highly parallel (5+): ~6-8 weeks.

The original v0.7 ROADMAP.md target was Q2 2026 (June). Adding the v0.6.5 cortex-fluent rollup pushes that to roughly Q3 2026 (July-August) at the same staffing. Cuts:

Most aggressive cut path: ship Tracks A + B + G + H + K (core legibility + hooks + attestation + G1 cutline + permissions) in ~8-10 weeks. That’s the real v0.7.0 minimum. Everything else slips to v0.7.1.


How to use the NHI starter prompts

Same conventions as the v0.6.5 epic — every task below has a self-contained NHI starter prompt that any agent (Claude Code, Grok, Codex, etc.) can take to begin work without further briefing.

Branch naming: feat/v0.7-<track>-<task>-<short-name> (e.g., feat/v0.7-h-2-link-signing)

Commit convention: feat(<scope>): <imperative summary> (#<task-id>)

PR title: feat: v0.7-<track><number> — <one-line title>

Definition of done: explicit per task, agent self-checks against the listed criteria.


Track A — Capabilities v3 response shape

Track goal: memory_capabilities v3 response pre-computes the calibration work LLMs do today. Agents converge on accurate first-answer descriptions regardless of calibration bias.

This track is unchanged from the v0.6.5 epic. Five tasks A1-A5. See v0.6.5 epic Track A for full details + starter prompts. Summary:


Track B — Loader tools

Track goal: the loader of last resort gets a name that says “load.” Reasoning-class LLMs find it on first ask. Path 1’s reliability stops being curiosity-gated.

This track is unchanged from the v0.6.5 epic. Five tasks B1-B5. See v0.6.5 epic Track B for full details + starter prompts. Summary:


Track C — Schema compaction

Track goal: --profile full drops from ~6,200 input tokens to ≤3,500. Every harness benefits.

This track is unchanged from the v0.6.5 epic. Five tasks C1-C5. Summary:


Track D — Per-harness positioning + tests

Unchanged from v0.6.5 epic. Tasks D1-D4: cross-harness benchmark, landing-page compatibility-matrix update, install-time system-prompt snippet, harness integration tests.


Track E — Discovery Gate T0 cells

Unchanged from v0.6.5 epic. Tasks E1-E3: orchestrate T0 cells across 4 LLMs, post-ship convergence verification, loader-cell additions to T1-T3 tiers.


Track G — Hook Pipeline (Bucket 0)

Track goal: programmable lifecycle events at every memory operation point. Subprocess JSON-over-stdio with daemon-mode IPC for hot paths. The substrate that R3 (auto-link inference) and R5 (auto-extraction) depend on, plus a permanent extension surface for downstream automation.

Task G1: Hook config schema (hooks.toml)

File(s): src/hooks/config.rs (new), ~/.config/ai-memory/hooks.toml (config path) Branch: feat/v0.7-g-1-hooks-config

Deliverable: TOML schema for hook configuration, parsed at boot, hot-reloadable on SIGHUP. Each hook entry: event (enum), command (path), priority (i32), timeout_ms (u32), mode (exec daemon), enabled (bool).

Schema example:

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

NHI starter prompt for G1

You are working on alphaonedev/ai-memory-mcp toward v0.7.0 — task G1.

Goal: define the hooks.toml schema and config-loading pipeline. This is
the substrate every other Track G task depends on.

What to do:
1. Create src/hooks/config.rs with:
   - struct HookConfig { event, command, priority, timeout_ms, mode, enabled, namespace }
   - enum HookEvent { 20 variants — see G2 task for the full list }
   - enum HookMode { Exec, Daemon }
   - impl HookConfig::load_from_file(path) -> Result<Vec<HookConfig>>
2. Default config path: ~/.config/ai-memory/hooks.toml (use dirs crate).
3. SIGHUP triggers reload; in-flight hook executions complete on old config.
4. Validation: priority is i32, timeout_ms is u32 ≤ 30_000, namespace is
   a glob pattern that matches src/namespace.rs::matches_pattern.
5. Parse-time errors must include line numbers from the toml::Spanned API.
6. Add unit tests covering valid/invalid configs.

Branch: feat/v0.7-g-1-hooks-config
Commit: feat(hooks): hooks.toml config schema with hot reload (#<filed>)

Definition of done:
- HookConfig parses canonical examples
- SIGHUP reload tested
- Invalid config produces line-number errors
- Hot reload doesn't kill in-flight hook executions

Task G2: 20 hook event types

File(s): src/hooks/events.rs (new) Branch: feat/v0.7-g-2-hook-events

Deliverable: Define the 20 lifecycle events the hook pipeline supports. Each event has a payload type and a documented invocation point.

Event matrix (16 base + 2 compaction + 2 transcripts):

Event When fired Payload
pre_store before persist MemoryDelta (writable)
post_store after persist Memory (read)
pre_recall before recall query RecallQuery (writable)
post_recall after recall returns RecallResult (read)
pre_search before FTS query SearchQuery (writable)
post_search after FTS returns SearchResult (read)
pre_delete before delete MemoryRef (writable)
post_delete after delete MemoryRef (read)
pre_promote before tier promotion MemoryRef + new tier
post_promote after promotion MemoryRef + tier change
pre_link before link creation LinkDelta (writable)
post_link after link creation Link (read)
pre_consolidate before consolidation ConsolidationDelta
post_consolidate after consolidation ConsolidationResult
pre_governance_decision before gate decision GovernanceContext
post_governance_decision after gate decision GovernanceDecision
on_index_eviction (G2 audit) ANN eviction EvictionEvent
pre_archive before archive MemoryRef
pre_transcript_store (1.7) before transcript store TranscriptDelta
post_transcript_store (1.7) after transcript store Transcript (read)

NHI starter prompt for G2

You are working on alphaonedev/ai-memory-mcp toward v0.7.0 — task G2.
Depends on: G1 (hook config exists).

Goal: define the 20 lifecycle event types and their payloads.

What to do:
1. Create src/hooks/events.rs.
2. enum HookEvent — 20 variants per the table in V0.7-EPIC Track G2.
3. For each event, define the payload struct. Pre- events have writable
   deltas; post- events have read-only payloads.
4. Implement Serialize for each payload (JSON for stdio IPC).
5. Document each event's invocation point with a doc comment that names
   the source-code location (file:line) where it will fire.
6. Add unit tests asserting each variant round-trips through JSON.
7. Update CHANGELOG with the full enum for the v0.7.0 release notes.

Branch: feat/v0.7-g-2-hook-events
Commit: feat(hooks): 20 lifecycle event types with payloads (#<filed>)

Definition of done:
- 20 variants present
- All payloads serialize to JSON
- Each event documented with invocation file:line

Task G3: Hook executor (subprocess JSON-stdio + daemon mode)

File(s): src/hooks/executor.rs (new) Branch: feat/v0.7-g-3-hook-executor

Deliverable: Two execution modes:

  1. exec — subprocess per fire; JSON over stdin → stdout; clean shutdown on close.
  2. daemon — long-lived child process; JSON-RPC framed; reconnection on crash; backpressure.

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

NHI starter prompt for G3

You are working on alphaonedev/ai-memory-mcp toward v0.7.0 — task G3.
Depends on: G1, G2.

Goal: build the hook executor. Two modes: exec (subprocess per fire)
and daemon (long-lived child with JSON-RPC framing).

What to do:
1. Create src/hooks/executor.rs.
2. trait HookExecutor { fn fire(event: HookEvent, payload: serde_json::Value)
   -> Result<HookDecision>; }
3. struct ExecExecutor — uses tokio::process::Command per fire. Stdin =
   JSON payload. Stdout = JSON decision. Clean shutdown on stdin close.
4. struct DaemonExecutor — long-lived child process. Connection pool of
   1 per hook command. JSON-RPC framing (newline-delimited or length-prefixed
   — pick one; document choice). Reconnect-on-crash with exponential backoff.
   Backpressure: if child can't keep up, queue drains to deadline; oldest
   first dropped with warning log.
5. ExecutorRegistry — maps HookConfig → HookExecutor instance.
6. Integration tests with a fake subprocess (in tests/hooks_executor_test.rs):
   - exec mode: 100 concurrent fires complete within their timeout
   - daemon mode: 1000 fires through one daemon child complete within
     deadline; child crash mid-stream triggers reconnect
7. Add backpressure metrics to ai-memory doctor --tokens (new --hooks
   subcommand): events fired, events dropped, mean latency.

Branch: feat/v0.7-g-3-hook-executor
Commit: feat(hooks): subprocess executor with exec + daemon modes (#<filed>)

Definition of done:
- Both modes pass integration tests
- 50ms recall budget preserved when post_recall is daemon-mode
- Child crash recovery tested
- Doctor reports executor metrics

Task G4: Decision types (Allow / Modify(MemoryDelta) / Deny / AskUser)

File(s): src/hooks/decision.rs (new) Branch: feat/v0.7-g-4-decision-types

Deliverable: Hook subprocess returns one of four decision shapes; hook chain processes them.

enum HookDecision {
    Allow,
    Modify(MemoryDelta),  // pre- events only
    Deny { reason: String, code: i32 },
    AskUser { prompt: String, options: Vec<String>, default: Option<String> },
}

NHI starter prompt for G4

You are working on alphaonedev/ai-memory-mcp toward v0.7.0 — task G4.
Depends on: G2 (HookEvent + payload types).

Goal: define HookDecision and its serialization contract.

What to do:
1. Create src/hooks/decision.rs.
2. enum HookDecision { Allow, Modify(...), Deny{reason, code}, AskUser{...} }.
3. Modify is only valid on pre- events (compile-time guard via separate types
   or runtime validation in the dispatcher).
4. Document the JSON serialization at the top of the file:
   {"action": "allow"} | {"action": "modify", "delta": {...}}
   {"action": "deny", "reason": "...", "code": 403}
   {"action": "ask_user", "prompt": "...", "options": [...], "default": "..."}
5. Implement Deserialize with strict validation — unknown actions or
   missing required fields produce a parse error that the executor
   surfaces as a "hook returned malformed decision" warning.
6. Unit tests covering each variant + invalid payloads.

Branch: feat/v0.7-g-4-decision-types
Commit: feat(hooks): decision type contract (#<filed>)

Definition of done:
- 4 variants present and serialized correctly
- Invalid payloads rejected with named errors
- Modify validated against pre-event-only constraint

Task G5: Chain ordering + first-deny-wins short-circuit

File(s): src/hooks/chain.rs (new) Branch: feat/v0.7-g-5-chain-ordering

Deliverable: When multiple hooks fire on the same event, they execute in priority order (descending). First Deny short-circuits the chain. Modify accumulates (later hooks see the latest delta). AskUser is queued; first non-AskUser decision continues.

NHI starter prompt for G5

You are working on alphaonedev/ai-memory-mcp toward v0.7.0 — task G5.
Depends on: G3, G4.

Goal: chain multiple hooks on the same event; resolve conflicts.

What to do:
1. Create src/hooks/chain.rs.
2. struct HookChain { hooks: Vec<HookConfig> } — sorted by priority desc.
3. impl HookChain::fire(event, payload) -> ChainResult:
   - iterate hooks in priority order
   - call executor.fire() for each
   - if Deny: short-circuit, return Deny
   - if Modify: update payload with delta, continue chain
   - if AskUser: queue prompt, continue chain (first non-AskUser
     decision wins)
   - if Allow: continue chain
4. ChainResult enum: Allow, ModifiedAllow(payload), Deny, AskUser(queue).
5. Unit tests:
   - 3 hooks priority 100/50/0; first denies → chain stops at first
   - 3 hooks all allow with modifications → final payload is composition
   - Hook crashes mid-chain → log warning, continue chain, treat crash
     as Allow (fail-open). Configurable per hook with fail_mode = "open"|"closed".
6. Integration with the existing dispatch_event path in src/subscriptions.rs
   — hooks fire BEFORE subscriptions for pre- events, AFTER for post-.

Branch: feat/v0.7-g-5-chain-ordering
Commit: feat(hooks): chain ordering + first-deny-wins (#<filed>)

Definition of done:
- Priority-desc ordering tested
- Modify-then-allow composition tested
- Crash fallback tested (default fail-open)
- Subscription integration verified

Task G6: Hard timeouts per event class

File(s): src/hooks/timeouts.rs (new) Branch: feat/v0.7-g-6-event-class-timeouts

Deliverable: Per-event-class deadlines: 5000ms for write events (pre_store, pre_link, etc.), 2000ms for read events (pre_recall, pre_search). Per-hook timeout_ms cannot exceed its event class’s class deadline. Total chain runtime is bounded.

NHI starter prompt for G6

You are working on alphaonedev/ai-memory-mcp toward v0.7.0 — task G6.
Depends on: G2, G5.

Goal: enforce hard per-event-class timeouts so hooks cannot collectively
break the 50ms-recall p95 budget.

What to do:
1. Create src/hooks/timeouts.rs.
2. fn event_class(event: HookEvent) -> EventClass { Write, Read, Index, Transcript }
3. const CLASS_DEADLINES: HashMap<EventClass, Duration> — 5000ms write,
   2000ms read, 1000ms index, 5000ms transcript.
4. At HookChain::fire entry, compute deadline = now + class_deadline.
   Each hook gets min(class_remaining, hook_timeout_ms).
5. If a hook exceeds its budget: kill the subprocess (or close the daemon
   stream, depending on mode), log warning, treat as fail-open Allow.
6. CI test: a deliberately-slow hook chain on post_recall must not push
   total recall p95 above the existing 50ms budget. Run the bench suite.
7. Doctor reports deadline-violation count alongside other hook metrics.

Branch: feat/v0.7-g-6-event-class-timeouts
Commit: feat(hooks): per-event-class timeout enforcement (#<filed>)

Definition of done:
- Class deadlines hardcoded per the table
- Per-hook timeout cannot exceed class
- Recall-budget bench test stays green with hooks active
- Doctor reports timeout-violation counts

Task G7: Hot reload via SIGHUP

See G1 (already covers SIGHUP reload). This is a verification task.

Branch: feat/v0.7-g-7-hot-reload-test

Deliverable: Integration test that fires SIGHUP, replaces hooks.toml, asserts new hooks fire on next event without server restart.

NHI starter prompt for G7

You are working on alphaonedev/ai-memory-mcp toward v0.7.0 — task G7.
Depends on: G1.

Goal: verify hot reload works end-to-end.

What to do:
1. tests/hooks_hot_reload.rs — start ai-memory in test mode with
   hooks.toml = "config A".
2. Fire a memory_store, assert hook A's command was invoked.
3. Replace hooks.toml on disk with "config B" (different hook command).
4. Send SIGHUP.
5. Wait for reload signal (or poll for ≤500ms).
6. Fire a memory_store, assert hook B's command was invoked (not A).
7. In-flight hook from before SIGHUP must complete on old config.

Branch: feat/v0.7-g-7-hot-reload-test
Commit: test(hooks): hot reload integration test (#<filed>)

Task G8: G2 audit absorption — on_index_eviction event

File(s): src/storage/embedding_index.rs, src/hooks/events.rs Branch: feat/v0.7-g-8-eviction-event

Deliverable: When the ANN index evicts (LRU or capacity), fire on_index_eviction with the evicted memory_id. Surface eviction count in ai-memory stats.


Task G9: G7 audit absorption — reranker batching (Mutex throughput)

File(s): src/reranker.rs Branch: feat/v0.7-g-9-reranker-batching

Deliverable: Concurrent rerank requests group into a single forward pass over the union of inputs; demux on output. Closes the v0.6.3 audit’s G7 finding (single Mutex serializing rerank calls).

NHI starter prompt for G9

You are working on alphaonedev/ai-memory-mcp toward v0.7.0 — task G9.

Goal: close the G7 audit finding (Mutex throughput on reranker).

What to do:
1. Read src/reranker.rs and locate the existing Mutex-protected single-
   request forward pass.
2. Replace with a batching layer:
   - Incoming requests join a queue; each request gets a oneshot::Sender
     for its result.
   - A worker drains the queue every 5ms (configurable) OR when N requests
     are queued (configurable; default 8).
   - Worker runs a single forward pass over the union of all queued
     query+candidate sets.
   - Worker demuxes results back to each oneshot::Sender.
3. Bench: 100 concurrent rerank requests should complete in 2× the time
   of a single request (not 100× as today's Mutex serialization implies).
4. Add a CI bench test that asserts batched throughput ≥10× single-request
   throughput at concurrency=20.

Branch: feat/v0.7-g-9-reranker-batching
Commit: perf(reranker): batched single-pass throughput (#<filed>)

Definition of done:
- Mutex serialization removed
- Throughput bench test green at ≥10× speedup
- Single-request latency unchanged or improved

Task G10: G10 audit absorption — pre_recall daemon-mode hook for query expansion

File(s): src/hooks/events.rs, src/recall.rs Branch: feat/v0.7-g-10-pre-recall-expand

Deliverable: memory_expand_query (advertised as a planned tool, currently MIA) becomes pipeable through the new pre_recall daemon-mode hook. Doesn’t violate “zero tokens until recall” — the expansion is server-side and never advertised in tools/list.


Task G11: R3 — Auto-link inference as post_store daemon-mode hook

File(s): tools/auto-link-detector/ (new utility), docs/hooks/auto-link.md (new) Branch: feat/v0.7-g-11-auto-link-r3

Deliverable: A reference post_store hook implementation that examines the just-stored memory + recent neighbors, proposes related_to / contradicts links to a downstream LLM scoring step, persists high-confidence links via memory_link. Default off; opt-in per namespace.

NHI starter prompt for G11

You are working on alphaonedev/ai-memory-mcp toward v0.7.0 — task G11 (R3).
Depends on: G1, G2, G3 (hook pipeline).

Goal: ship a reference auto-link detector hook. Recovers commitment R3
from the prior phased roadmap.

What to do:
1. Create tools/auto-link-detector/ as a separate Rust binary with its
   own Cargo.toml.
2. The binary reads JSON HookEvent::PostStore from stdin (daemon mode):
   - Receives the just-stored Memory.
   - Queries ai-memory for neighbors via memory_recall (uses a thin
     ai-memory SDK Rust client).
   - Embeds the new memory + each neighbor; computes cosine.
   - Heuristic: if cosine > 0.85 and neighbor is in same namespace → propose
     "related_to" link.
   - Heuristic: if cosine in (0.6, 0.85) but content has obvious negation
     ("not", "won't", "doesn't") aligned with neighbor positive form →
     propose "contradicts" link.
   - For each proposal: emit memory_link(...) call back through HookDecision::
     Modify (so the chain can persist it transactionally with the original
     store).
3. Emit metrics: proposals_emitted, links_persisted, conflicts_detected.
4. Default off — example hooks.toml entry shipped in
   docs/hooks/auto-link.md as opt-in instructions.
5. Integration test in tests/hooks_auto_link.rs that runs ai-memory with
   the hook enabled, stores a contradiction pair, asserts the link is
   created.

Branch: feat/v0.7-g-11-auto-link-r3
Commit: feat(hooks): R3 auto-link detector reference hook (#<filed>)

Definition of done:
- Reference binary builds + runs as daemon-mode hook
- Heuristic produces correct link types in test corpus
- Default off; opt-in documented
- Per-namespace opt-in tested

Track H — Ed25519 Attested Identity (Bucket 1)

Track goal: fill the v0.6.3 dead signature column with real cryptographic attestation. Per-agent Ed25519 keypair (operator-supplied, explicit; not derived from agent_id). Outbound signing on every memory_links write. Inbound verification against observed_by claim. attest_level enum: unsigned / self-signed / peer-attested. Append-only signed_events audit table.

Task H1: Per-agent Ed25519 keypair management

File(s): src/identity/keypair.rs (new), CLI: ai-memory identity generate, ai-memory identity import, ai-memory identity list Branch: feat/v0.7-h-1-agent-keypair

Deliverable: CLI to generate, import, list per-agent Ed25519 keypairs. Keys stored at ~/.config/ai-memory/keys/<agent_id>.{pub,priv}. Private keys mode 0600; public keys mode 0644. Operator-supplied — not derived from agent_id.

NHI starter prompt for H1

You are working on alphaonedev/ai-memory-mcp toward v0.7.0 — task H1.

Goal: introduce per-agent Ed25519 identity. CLI surface to generate /
import / list. File-based key storage with strict permissions.

What to do:
1. Create src/identity/keypair.rs:
   - struct AgentKeypair { agent_id, public, private (Option) }
   - fn generate(agent_id: &str) -> AgentKeypair (uses ed25519-dalek)
   - fn save(keypair, dir) -> Result<()>; mode 0600 priv, 0644 pub
   - fn load(agent_id, dir) -> Result<AgentKeypair>
   - fn list(dir) -> Result<Vec<AgentKeypair>>; private key NOT loaded
2. CLI subcommand: src/cli/identity.rs
   - ai-memory identity generate --agent-id <id>
   - ai-memory identity import --agent-id <id> --pub <path> --priv <path>
   - ai-memory identity list
   - ai-memory identity export-pub --agent-id <id>  (for sharing)
3. Default key storage: ~/.config/ai-memory/keys/
4. Integration with NHI agent_id: the CLI default agent_id matches the
   NHI-hardened default (ai:<client>@<host>:pid-<pid> etc.).
5. Test: round-trip a keypair (generate → save → load), permissions
   verified.
6. Hardware-backed storage is OUT of OSS scope per ROADMAP.md; just
   doc-comment this clearly and point at AgenticMem commercial layer.

Branch: feat/v0.7-h-1-agent-keypair
Commit: feat(identity): per-agent Ed25519 keypair CLI (#<filed>)

Definition of done:
- generate / import / list / export-pub all work
- File permissions strictly enforced
- Round-trip test green
- Hardware-key disclaimer documented

File(s): src/storage/links.rs, src/identity/sign.rs (new) Branch: feat/v0.7-h-2-link-signing

Deliverable: Every memory_links insert signs the canonical-CBOR-encoded link with the active agent’s private key. Signature stored in the signature column (currently dead). attest_level set to self-signed.

Canonical CBOR encoding (per RFC 8949 §4.2.1) of: {src_id, dst_id, relation, observed_by, valid_from, valid_until}.

NHI starter prompt for H2

You are working on alphaonedev/ai-memory-mcp toward v0.7.0 — task H2.
Depends on: H1.

Goal: every outbound memory_link write signs the link with the active
agent's private key. Fills the dead signature column shipped in v0.6.3.

What to do:
1. Create src/identity/sign.rs:
   - fn canonical_cbor(link: &Link) -> Vec<u8> — RFC 8949 §4.2.1 encoding
     of {src_id, dst_id, relation, observed_by, valid_from, valid_until}
   - fn sign(keypair: &AgentKeypair, link: &Link) -> Vec<u8>
2. Update src/storage/links.rs::insert_link to:
   - Look up the active agent's keypair (from AppState::active_keypair)
   - Compute canonical CBOR
   - Sign with private key
   - Store signature (64 bytes) in the existing signature column
   - Set attest_level = "self-signed"
3. If active agent has no keypair: leave signature NULL, attest_level =
   "unsigned" (preserves v0.6.4 backward compat).
4. Unit test: insert a link with a generated keypair, read it back,
   verify signature against the public key.
5. Integration test against existing memory_link MCP tool — assert the
   wire-level response includes attest_level field.

Branch: feat/v0.7-h-2-link-signing
Commit: feat(identity): outbound Ed25519 signing on memory_links (#<filed>)

Definition of done:
- All outbound links carry a valid signature when keypair present
- attest_level correctly populated (unsigned / self-signed)
- Unit + integration tests green
- v0.6.4-compatible: links without keypair still write

Task H3: Inbound verification against observed_by claim

File(s): src/storage/links.rs, src/identity/verify.rs (new) Branch: feat/v0.7-h-3-inbound-verify

Deliverable: When a link arrives from a peer (federation), verify the signature against the public key associated with the link’s observed_by claim. If verification fails, reject the link with a logged warning. If observed_by has no known public key, accept-and-flag as unsigned.


Task H4: attest_level enum + verify() MCP tool

File(s): src/storage/links.rs, src/mcp.rs Branch: feat/v0.7-h-4-attest-level

Deliverable: attest_level enum with three variants (unsigned self_signed peer_attested). New MCP tool memory_verify(link_id) returns {signature_verified: bool, attest_level, signed_by, signed_at}.

Task H5: Append-only signed_events audit table

File(s): schema migration to v21, src/storage/signed_events.rs (new) Branch: feat/v0.7-h-5-signed-events-table

Deliverable: New table signed_events(id, agent_id, event_type, payload_hash, signature, attest_level, timestamp). Append-only — no UPDATE or DELETE statements supported via the application layer. Each memory_link write also appends to this table for an immutable audit chain.


Task H6: Verification end-to-end test

File(s): tests/identity_e2e.rs (new) Branch: feat/v0.7-h-6-verify-e2e-test

Deliverable: Test that:

  1. Generates a keypair for agent A
  2. Stores a link as agent A
  3. Reads the link back, confirms signature column is non-NULL
  4. Calls memory_verify(link_id) → asserts signature_verified: true
  5. Negative test: tamper with the link content; verify returns false

Closes G12 audit finding (signature column was dead in v0.6.3).


Track I — Sidechain Transcripts (Bucket 1.7)

Track goal: raw conversation/reasoning trail in zstd-compressed BLOBs, linked to derived memories via memory_transcript_links. Default off (opt-in per namespace). Substrate for R5 auto-extraction.

Task I1: memory_transcripts table schema (BLOB + zstd-3)

File(s): schema migration v21 → v22, src/storage/transcripts.rs (new) Branch: feat/v0.7-i-1-transcripts-schema

Deliverable: New table with columns: id, namespace, created_at, expires_at, compressed_size, original_size, zstd_level (3), content_blob (BLOB).

NHI starter prompt for I1

You are working on alphaonedev/ai-memory-mcp toward v0.7.0 — task I1.

Goal: schema for compressed transcript storage.

What to do:
1. Add migration v21 -> v22 in src/storage/migrations/.
2. CREATE TABLE memory_transcripts (
     id TEXT PRIMARY KEY,
     namespace TEXT NOT NULL,
     created_at TEXT NOT NULL,
     expires_at TEXT,
     compressed_size INTEGER NOT NULL,
     original_size INTEGER NOT NULL,
     zstd_level INTEGER NOT NULL DEFAULT 3,
     content_blob BLOB NOT NULL
   );
3. Index on (namespace, created_at) for archive sweeps.
4. cli::boot::MAX_SUPPORTED_SCHEMA bumped to 22.
5. src/storage/transcripts.rs:
   - fn store(namespace, content: &str, ttl: Duration) -> Transcript
     - Compresses with zstd level 3
     - Writes row with computed sizes
   - fn fetch(id) -> Result<String>
     - Reads row, decompresses, returns content
   - fn purge_expired() -> usize
     - DELETE WHERE expires_at < now AND archived = true
6. Unit tests: store + fetch roundtrip; expected compression ratio 5-10×
   on chat-shaped text.

Branch: feat/v0.7-i-1-transcripts-schema
Commit: feat(transcripts): schema v22 with zstd-3 BLOB storage (#<filed>)

Definition of done:
- Migration is idempotent
- Round-trip test green
- Compression ratio ≥5× on chat-shaped input

Branch: feat/v0.7-i-2-transcript-links

Deliverable: Join table memory_transcript_links(memory_id, transcript_id, span_start, span_end). Allows multiple memories derived from one transcript and one transcript shared across multiple memories.


Task I3: Per-namespace TTL with archive → prune lifecycle

Branch: feat/v0.7-i-3-transcript-lifecycle

Deliverable: Each namespace can configure a transcript TTL via [transcripts] table in config. Background sweeper archives transcripts whose memories are all expired, then prunes after a grace period.


Task I4: memory_replay <id> MCP tool

Branch: feat/v0.7-i-4-memory-replay

Deliverable: New MCP tool memory_replay(memory_id) that traverses memory_transcript_links and reconstructs the transcript chain for a given memory. Returns the decompressed text + span metadata.


Task I5: Substrate hook for R5 auto-extraction

Branch: feat/v0.7-i-5-pre-store-transcript-hook

Deliverable: A reference pre_store hook that, when given a transcript, extracts derived memories from it (uses an LLM via the existing query_expand infrastructure). Defaults off; opt-in per namespace. Recovers commitment R5.


Track J — Apache AGE Acceleration (Bucket 2)

Track goal: Postgres SAL adapter detects Apache AGE and projects memory_links as a property graph for Cypher access. Recursive CTE path stays as the SQLite fallback. Closes commitment R2 (memory_find_paths).

Task J1: AGE detection in Postgres SAL

Branch: feat/v0.7-j-1-age-detection

Deliverable: At Postgres SAL initialization, run SELECT * FROM pg_extension WHERE extname='age';. If present, set kg_backend = "age"; else kg_backend = "cte".


Task J2: Cypher implementation of memory_kg_query

Branch: feat/v0.7-j-2-kg-query-cypher

Deliverable: When kg_backend == "age", route memory_kg_query through Cypher (MATCH (a)-[:RELATION*1..N]->(b) ...). SQLite path unchanged.


Task J3: Cypher implementation of memory_kg_timeline

Branch: feat/v0.7-j-3-kg-timeline-cypher


Task J4: Cypher implementation of memory_kg_invalidate

Branch: feat/v0.7-j-4-kg-invalidate-cypher

Deliverable: Cypher implementation of memory_kg_invalidate plus G14 absorption (audit-edge emission on invalidate).


Task J5: Dual-path tests (AGE-Postgres vs. CTE-SQLite identical results)

Branch: feat/v0.7-j-5-dual-path-tests

Deliverable: Test harness that runs the same query against AGE-Postgres + CTE-SQLite + asserts the result sets are equivalent under set-equivalence (since order may differ). Required across all three KG operations.

NHI starter prompt for J5

You are working on alphaonedev/ai-memory-mcp toward v0.7.0 — task J5.
Depends on: J2, J3, J4.

Goal: assert AGE and CTE paths produce identical results. Dual-path
discipline is the safety net against Cypher-vs-CTE divergence.

What to do:
1. tests/kg_dual_path.rs (new file).
2. setUp: a fixture corpus of 200 memories + 800 links covering enough
   topology to exercise depth-1 through depth-5 traversals.
3. For each KG operation (kg_query, kg_timeline, kg_invalidate):
   - Run against an AGE-enabled Postgres test DB
   - Run against a CTE SQLite test DB (same fixture)
   - Assert result sets equivalent under set-equivalence (since order
     may differ between paths)
4. Test depths: 1, 2, 3, 5. Cyclic and non-cyclic graphs.
5. Run only when AI_MEMORY_TEST_AGE_URL env var is set; skip otherwise
   (CI matrix configures this for the AGE-postgres job).

Branch: feat/v0.7-j-5-dual-path-tests
Commit: test(kg): AGE vs CTE dual-path equivalence (#<filed>)

Definition of done:
- 12 test cases (3 ops × 4 depths) passing on both paths
- Set-equivalence assertion (not order-sensitive)
- CI runs both paths

Task J6: PERFORMANCE.md updated with separate AGE/CTE budgets

Branch: feat/v0.7-j-6-perf-budgets


Task J7: R2 — memory_find_paths(source, target) MCP tool

Branch: feat/v0.7-j-7-r2-find-paths

Deliverable: New MCP tool memory_find_paths(source, target, max_depth=5). Cypher one-liner on AGE; recursive CTE on SQLite fallback. Recovers commitment R2.


Task J8: Bench gate — AGE-mode p95 ≥ 30% faster than CTE-mode at depth=5

Branch: feat/v0.7-j-8-age-bench-gate

Deliverable: New bench.yml CI gate that runs AGE + CTE at depth=5, asserts AGE p95 ≥ 30% faster. Justifies the AGE complexity. If AGE isn’t faster, drop AGE.


Track K — A2A + Permissions + G1 Inheritance (Bucket 3)

Track goal: the mandatory cutline. G1 inheritance is “even if everything else slips, this fix ships” per ROADMAP §7.3. Plus A2A maturity, subscription reliability, per-agent quotas, permissions overhaul, approval API.

Task K1 — G1: Namespace inheritance enforcement (CUTLINE-PROTECTED)

File(s): src/governance/policy.rs, src/namespace.rs Branch: feat/v0.7-k-1-g1-inheritance-fix

Deliverable: resolve_governance_policy(namespace) walks build_namespace_chain(namespace) and returns the first non-null policy encountered, not just the leaf. Per-policy inherit: bool flag (default true).

Test (mandatory per ROADMAP §7.3): Parent namespace has Approve policy; child namespace has none. Write to child must require approval.

NHI starter prompt for K1

You are working on alphaonedev/ai-memory-mcp toward v0.7.0 — task K1.
THIS IS THE CUTLINE FIX. Mandatory per ROADMAP §7.3 even if everything
else in v0.7.0 slips.

Goal: namespace inheritance enforcement. Today's behavior: a parent
"Approve" policy doesn't block a child write because resolve_governance_policy
only consults the leaf namespace. Fix: walk the namespace chain.

What to do:
1. Locate src/governance/policy.rs::resolve_governance_policy.
2. Replace its leaf-only logic with:
   for ancestor in build_namespace_chain(namespace).iter().rev():
       if let Some(policy) = lookup_policy(ancestor):
           if policy.inherit OR ancestor == namespace:
               return policy
   return None
3. Update GovernancePolicy struct: add `inherit: bool` field, default true.
4. Migration: existing rows get inherit=true (backfill via UPDATE).
5. Ship-gate test:
   - Parent namespace "team" has GovernancePolicy(approve_required=true).
   - Child namespace "team/alice" has no policy.
   - Write to "team/alice" → must require approval (currently does not).
   This test goes in tests/governance_inheritance.rs and is FATAL —
   if it fails, the release is blocked.
6. Update docs/governance.html with a worked example of inheritance.
7. Add an audit-log entry on every gate decision: which ancestor's
   policy fired.

Branch: feat/v0.7-k-1-g1-inheritance-fix
Commit: fix(governance): G1 namespace inheritance enforcement (#<filed>)

Definition of done:
- resolve_governance_policy walks the chain
- inherit:bool added with default true and backfill migration
- Cutline test green
- Audit log records ancestor whose policy fired
- Doc updated with worked example

CRITICAL: this fix MUST ship in v0.7.0. If anything else slips, this
stays.

Task K2: pending_actions timeout sweeper

Branch: feat/v0.7-k-2-pending-actions-sweeper

Deliverable: Background task sweeps pending_actions table every 60 seconds; expires entries past default_timeout_seconds. Single SELECT-and-UPDATE statement. Closes the v0.6.3.1 honest-Capabilities-v2 disclosure that default_timeout_seconds was advertised but unused.


Task K3: permissions.mode actually consulted by gate

Branch: feat/v0.7-k-3-permissions-mode-honest

Deliverable: The permissions.mode = "advisory" honest-disclosure shipped in v0.6.3.1 becomes obsolete because permissions are now actually enforced. Gate consults permissions.mode and changes behavior accordingly: "enforce" → blocks; "advisory" → logs; "off" → allows.


Task K4: Approval-event routing through subscription system

Branch: feat/v0.7-k-4-approval-event-routing

Deliverable: When a pending_action requires approval, dispatch an approval.requested event through the existing subscriptions system. The Approval API HTTP+SSE handler in K10 consumes these events. Closes the v0.6.3.1 honest disclosure that approval.subscribers was advertised but never published.


Task K5: rule_summary populated

Branch: feat/v0.7-k-5-rule-summary

Deliverable: Capabilities v3’s rule_summary field gets a real value: ordered list of active governance rules with a one-line summary each. Closes the v0.6.3.1 honest-Capabilities-v2 disclosure that left this field as a placeholder.


Task K6: A2A correlation IDs + ACKs + retry + TTL + replay protection

Branch: feat/v0.7-k-6-a2a-maturity

Deliverable: Every A2A message carries a UUIDv4 correlation ID. Recipients ACK within configurable TTL. Sender retries up to 3× on no-ACK. Recipients track seen correlation IDs in a bounded LRU; duplicates dropped (replay protection).

NHI starter prompt for K6

You are working on alphaonedev/ai-memory-mcp toward v0.7.0 — task K6.

Goal: A2A maturity. Today, federation messages can be lost or replayed
with no detection. Add correlation IDs, ACK semantics, retry, replay
protection.

What to do:
1. src/federation/a2a.rs gets:
   - struct A2AMessage { correlation_id: Uuid, sender, recipients,
     payload, expires_at }
   - struct A2AAck { correlation_id: Uuid, recipient, accepted: bool,
     reason: Option<String> }
2. Sender:
   - Generate UUIDv4 for each outbound message
   - Wait for ACK with configurable TTL (default 30s)
   - Retry up to 3× on no-ACK with exponential backoff
3. Receiver:
   - Maintain a bounded LRU of seen correlation IDs (size 10_000)
   - On duplicate: ACK with accepted=false, reason="duplicate"
   - On expired (now > expires_at): drop, log warning
4. Tests:
   - 100 messages: all ACK'd
   - Replay attack (same UUID 2× from same sender): rejected
   - Network partition simulation: retries kick in
   - Expired message: dropped, not processed
5. Performance bench: A2A throughput under realistic mesh sizes
   (3-node, 5-node, 10-node) — record p50/p95/p99 in PERFORMANCE.md.

Branch: feat/v0.7-k-6-a2a-maturity
Commit: feat(federation): A2A correlation IDs + ACK retry + replay protection (#<filed>)

Definition of done:
- All 4 tests pass
- Replay attacks verifiably rejected
- A2A bench numbers recorded in PERFORMANCE.md

Task K7: Subscription reliability (DLQ, replay-from-cursor, HMAC)

Branch: feat/v0.7-k-7-subscription-reliability

Deliverable: Subscription dispatcher gains:


Task K8: Per-agent rate limits and storage caps

Branch: feat/v0.7-k-8-per-agent-quotas

Deliverable: Config table [quotas.per_agent] with rps_limit, storage_mb_cap. Enforced at MCP entry. 429 returned on RPS overage; insert blocked on storage cap.


Task K9: Permission system (rules + modes + hooks → decision; deny-first)

Branch: feat/v0.7-k-9-permission-system

Deliverable: Refactor existing governance system into: rules (declarative policies) + modes (enforce/advisory/off) + hooks (programmable from Track G) → Decision. Default deny-first; ask-by-default for ambiguous cases.


Task K10: Approval API (HTTP + SSE + MCP, HMAC mandatory, remember=forever)

Branch: feat/v0.7-k-10-approval-api

Deliverable: Three surfaces:

remember=forever parameter on decide: subsequent identical requests auto-approve (progressive trust). HMAC signing mandatory — non-optional per ROADMAP §7.3.


Task K11: ai-memory governance migrate-to-permissions CLI

Branch: feat/v0.7-k-11-permissions-migration

Deliverable: One-shot migration tool that converts existing governance rows into the new permissions schema. Idempotent. Dry-run by default; --apply commits.


Track F — Docs + release (consolidated v0.7.0 scope)

Task F1: docs/MIGRATION_v0.7.md

Branch: feat/v0.7-f-1-migration-guide

Deliverable: Comprehensive migration guide for users coming from v0.6.4. Covers:

Task F2: docs/whats-new-v07.html (Pages page)

Task F3: Top-nav + landing-page badges + v0.6.4→v0.7.0 references

Task F4: README + ADMIN_GUIDE updates (substantial — multiple new tracks of features to document)

Task F5: CHANGELOG, version bumps, tag, CI release

Task F6: docs/v0.7/rfc-attested-cortex.md (design RFC)

Deliverable: Design RFC documenting the architectural decisions for v0.7.0:


Critical-path sequencing

Phase 1 (Weeks 1-3) — Cortex-fluent + cutline
   Track A complete (A1→A2→A3→A4→A5)
   Track B complete (B4→B1→B3→B2→B5)
   Track C complete (C1→C2→C3→C4→C5) — parallel with A/B
   Track K cutline: K1 (G1 inheritance) — parallel with A/B
   F1 + F6 drafted

Phase 2 (Weeks 3-7) — Hooks + Attestation
   Track G complete (G1→G2→G3→G4→G5→G6→G7→G8→G9→G10→G11)
   Track H complete (H1→H2→H3→H4→H5→H6) — parallel with G
   Track I drafted (I1→I2 in this phase; I3-I5 spillover into Phase 3)
   Track K2-K5 (the smaller permissions cleanups) — parallel

Phase 3 (Weeks 7-10) — AGE + Permissions overhaul
   Track J complete (J1→J2→J3→J4→J5→J6→J7→J8)
   Track K complete (K6→K7→K8→K9→K10→K11)
   Track I3-I5 finished

Phase 4 (Weeks 10-11) — Validation + Release
   Track D complete (D1-D4)
   Track E complete (E1-E3)
   F2-F5 finished
   ai-memory-test-hub campaigns/v0.7.md — full evidence
   ai-memory-discovery-gate runs/v0.7-ship-date/ — T0+ convergence
   v0.7.0 release tagged + 5/5 distribution channels published

With one full-time engineer: 11-12 weeks (~3 months). With two engineers in parallel: 7-8 weeks (~2 months). With three engineers + good track decomposition: 5-6 weeks (~6 weeks).

The mandatory cutline for v0.7.0 ship: K1 (G1 inheritance) + Track A + Track B + Track G + Track H + F1+F5. That alone is ~6-8 weeks with one engineer. Tracks I, J, C, D, E can defer to v0.7.1 if scope pressure forces it — the v0.7.0 narrative (attested-cortex) still holds without them.


Definition of release-ready

v0.7.0 ships when all of these are true:

What’s deferred (out of v0.7.0 scope, per agreement)


Refs


Codename: attested-cortex — the substrate becomes both more articulate and cryptographically trustworthy in one release. The two narratives (legibility from v0.6.5 + trust from ROADMAP’s v0.7) are not incidental siblings; they’re the same story told from two angles. An NHI fleet needs to know what its memory cortex says about itself and that what it says is signed.