ai-memory v0.9.0

Agent attestation — setup guide (required by default in v0.9.0)

What changed in v0.9.0. Agent attestation is now required by default on every direct write (#1751). An unsigned memory_store (MCP) / POST /api/v1/memories (HTTP) / ai-memory store (CLI) is rejected with 403 ATTESTATION_FAILED instead of quietly landing as attest_level="claimed".

⚠️ Solo user on Claude Code / Cursor / any non-signing MCP client? Read this first.

Those clients call memory_store without a signature, and there is no server-side auto-signing for memory writes (unlike signals/checkpoints, memory writes are never self-signed by the daemon). So under the v0.9.0 default, your MCP writes are rejected with 403 ATTESTATION_FAILED — this hits every local single-user setup the first time the MCP server restarts on v0.9.0.

If you run a single-operator local setup, the fix is one line — set AI_MEMORY_REQUIRE_AGENT_ATTESTATION=0 on the MCP server (how ↓). Required-mode attestation is designed for multi-agent / shared / federated deployments (where the client signs) or signed CLI writes — not the solo Claude Code path.

You have exactly two ways to move forward. Pick one:

  When to use One-liner
A — Turn it off Single operator, local / trusted host, you don’t need cryptographic write-provenance set AI_MEMORY_REQUIRE_AGENT_ATTESTATION=0
B — Sign your writes Multi-agent / shared / federated substrate, you want every write provably authored generate a key → register → bind → sign

Both are covered below, in copy-paste form, for CLI, HTTP, and MCP, on Linux, macOS, Windows, iOS, and Android.


First: these are NOT SSH or GPG keys

ai-memory attestation uses its own native Ed25519 keypairs — a separate key store from anything you already have:

The git tag -s signing used to cut the ai-memory release is a separate mechanism (git’s SSH/GPG commit signing). It has nothing to do with write-attestation. Don’t reuse SSH keys here — let ai-memory mint its own.

Where the keys live (auto-resolved per OS):

OS Default key directory
Linux ~/.config/ai-memory/keys/
macOS ~/Library/Application Support/ai-memory/keys/
Windows %APPDATA%\ai-memory\keys\
iOS / Android inside the app sandbox — you set it (see Mobile)

Override the location anywhere with the AI_MEMORY_KEY_DIR environment variable, or the --key-dir <path> flag on ai-memory identity.


Option A — Turn attestation off

The single knob is the environment variable AI_MEMORY_REQUIRE_AGENT_ATTESTATION:

Set it in the environment of whichever surface you write from. See Setting environment variables per OS for exact syntax, and MCP configuration for the Claude-Code / MCP-host case (the most common one).

# CLI / HTTP daemon (bash / zsh)
export AI_MEMORY_REQUIRE_AGENT_ATTESTATION=0

That’s the whole opt-out. Nothing else to install.


Option B — Sign your writes (4 steps)

The flow is the same everywhere: generate a key → register the agent → bind its public key → sign. Steps 1–3 are one-time; step 4 is every write.

Step 1 — Generate the keypair

Desktop — Linux, macOS, Windows (identical command):

ai-memory identity generate --agent-id my-agent

Output (the pub_b64 is what you bind in step 3 — copy it):

generated keypair for my-agent
  key_dir = /home/you/.config/ai-memory/keys
  pub_b64 = cuOFCoGj1UCDK9H52vsoRJKbKlcktsyMVaAaHg52_3U

Step 1 (on iOS / Android)

There is no shell on the phone — ai-memory runs as the embedded library (ai-memory-ios.xcframework / Android .so). So the key is generated by your app on first init, into the app’s sandbox:

  1. Point ai-memory at a directory inside your app container by setting AI_MEMORY_KEY_DIR before the library initializes:
    • iOS: an app-group or Application Support path (e.g. <container>/Library/Application Support/ai-memory/keys).
    • Android: context.getFilesDir() + /ai-memory/keys.
  2. On first use the library auto-generates the daemon keypair there (the same code path ai-memory serve uses on desktop) — you don’t call a separate keygen API.
  3. To bind that device’s public key (step 3) from a registration host, export the raw 32-byte <agent-id>.pub from the sandbox and base64-url-encode it, or register the device from a desktop host that shares the key directory.

Hardware-backed key storage (Secure Enclave / TPM / Android Keystore) is out of OSS scope — it lives in the AgenticMem commercial layer. The OSS library stores the raw key in the sandbox directory you choose.

Step 2 — Register the agent

ai-memory agents register --agent-id my-agent --agent-type ai:claude-opus-4.8

--agent-type accepts human, system, or any ai:<name> form (ai:gpt-5, ai:gemini-2.5, …). The agent must exist before you bind a key.

Step 3 — Bind the public key

Paste the pub_b64 from step 1 — use the --pubkey=<key> form (with the =):

ai-memory agents bind-key --agent-id my-agent \
  --pubkey=cuOFCoGj1UCDK9H52vsoRJKbKlcktsyMVaAaHg52_3U

Tip — keys starting with -. Public keys are URL-safe base64, so they can begin with - (e.g. -nLCEF…). Always use the --pubkey=<key> form (with the =); --pubkey -nLCEF… makes the shell read -n as a flag and errors.

Now the daemon can verify signatures from my-agent. Re-binding overwrites in place (that’s how you rotate — see below).

Step 4 — Sign on each write surface

CLI — add --sign (loads <agent-id>.priv locally, signs, stamps agent_attested):

ai-memory store --agent-id my-agent --sign \
  --title "Deploy runbook" --content "…"

HTTP — present a base64 Ed25519 signature over the canonical SignableWrite envelope plus the created_at you signed (the server adopts that timestamp so the verifier rebuilds the identical bytes):

curl -X POST http://localhost:9077/api/v1/memories \
  -H 'Content-Type: application/json' \
  -d '{
        "agent_id":   "my-agent",
        "title":      "Deploy runbook",
        "content":    "…",
        "created_at": "2026-07-08T12:00:00Z",
        "signature":  "<base64 Ed25519 signature over the SignableWrite envelope>"
      }'

MCP — pass signature + created_at in the memory_store arguments (see the next section for the full MCP story):

{
  "name": "memory_store",
  "arguments": {
    "agent_id":   "my-agent",
    "title":      "Deploy runbook",
    "content":    "…",
    "created_at": "2026-07-08T12:00:00Z",
    "signature":  "<base64 Ed25519 signature>"
  }
}

The signed envelope is SignableWrite = agent_id + namespace + title + kind + created_at + sha256(content). If you’re scripting HTTP/MCP signing yourself, that’s the byte layout to reproduce; most operators use the CLI --sign path or Option A for MCP.


MCP configuration (crystal clear)

This is the case most people hit first, because the ai-memory MCP server is what Claude Code (and other MCP hosts) talk to. The MCP memory_store tool enforces attestation exactly like the other surfaces:

Today’s common MCP clients (including Claude Code) call memory_store without signing, so you must choose a posture explicitly.

Where MCP environment variables go

The ai-memory MCP server is launched by the MCP host from a config block. In Claude Code that’s ~/.claude.json (or the settings the ai-memory install claude-code --apply installer writes), under mcpServers.<name>.env:

{
  "mcpServers": {
    "ai-memory": {
      "command": "ai-memory",
      "args": ["mcp", "--profile", "full"],
      "env": {
        "AI_MEMORY_DB": "/home/you/.ai-memory/memory.db",
        "AI_MEMORY_REQUIRE_AGENT_ATTESTATION": "0"
      }
    }
  }
}

Any MCP host (Cursor, Windsurf, custom) has the same shape — set the env var in that server’s env map, then restart the host so the MCP server reboots with the new environment.

You run ai-memory on your own machine for your own Claude Code session and don’t need per-write signatures:

  1. Add "AI_MEMORY_REQUIRE_AGENT_ATTESTATION": "0" to the server’s env block (above).
  2. Restart Claude Code (or your MCP host).

Done — memory_store works, writes land claimed.

Recipe 2 — Secure / multi-agent MCP

You want every MCP write cryptographically attributed:

  1. Do Option B steps 1–3 for the agent id the MCP server presents (leave AI_MEMORY_REQUIRE_AGENT_ATTESTATION unset / 1).
  2. Use an MCP client that signs — it must send signature + created_at on each memory_store call (the SignableWrite envelope above).
  3. Bind that client’s public key with ai-memory agents bind-key.

The MCP server’s own daemon self-writes (curator/autonomy, routed through the admin surface) are exempt from the requirement — the gate applies to caller-originated memory_store, not the substrate’s internal maintenance writes.


Verify it worked

Every stored memory carries metadata.attest_level:

attest_level Meaning
agent_attested Signature verified against the agent’s bound key ✅
claimed Unsigned, accepted because attestation is off (=0)
signed_by_peer Federated write attested by an enrolled peer
unsigned Legacy / pre-attestation row
ai-memory get <id> --json | grep attest_level
# → "attest_level": "agent_attested"

A signed write that returns 403 ATTESTATION_FAILED means the signature didn’t verify — see Troubleshooting.


Rotate & revoke

# Rotate the key (overwrite in place), then re-bind the new public key
ai-memory identity generate --agent-id my-agent --force
ai-memory agents bind-key --agent-id my-agent --pubkey <new pub_b64>

# Rotate WITH a signed lineage handoff so the identity survives the
# rotation (v0.9.0 #1828 — the retiring key signs the succession first)
ai-memory identity succeed --agent-id my-agent

# Revoke — the agent reverts to permissive "claimed" until a fresh key is bound
ai-memory agents revoke-key --agent-id my-agent

Troubleshooting

Symptom Cause Fix
403 ATTESTATION_FAILED on an unsigned write attestation is required (v0.9.0 default) sign the write (Option B) or set AI_MEMORY_REQUIRE_AGENT_ATTESTATION=0 (Option A)
403 ATTESTATION_FAILED on a signed write presented signature didn’t verify the bound public key doesn’t match the signing key — re-run agents bind-key with the current pub_b64; or the created_at you signed drifted outside the freshness window (sign with the timestamp you send)
--sign requires a local keypair for agent '<id>' no <id>.priv in the key dir ai-memory identity generate --agent-id <id> (check AI_MEMORY_KEY_DIR)
MCP writes silently fail / host shows a tool error MCP server rejecting unsigned writes set the opt-out in the server’s env block, or switch to a signing client (see MCP configuration)
Works on CLI, fails under MCP/daemon env var set in your shell but not in the server’s environment set it where the surface runs — the mcpServers.<name>.env block, or the systemd/launchd unit for the HTTP daemon

Appendix — setting environment variables per OS

Set AI_MEMORY_REQUIRE_AGENT_ATTESTATION (or AI_MEMORY_KEY_DIR) in the environment of the process that does the writing.

# bash / zsh (Linux, macOS)   — session, or add to ~/.bashrc / ~/.zshrc
export AI_MEMORY_REQUIRE_AGENT_ATTESTATION=0

# fish
set -x AI_MEMORY_REQUIRE_AGENT_ATTESTATION 0

# Windows PowerShell (session) / persistent
$env:AI_MEMORY_REQUIRE_AGENT_ATTESTATION = "0"
setx AI_MEMORY_REQUIRE_AGENT_ATTESTATION 0

# Windows cmd.exe
set AI_MEMORY_REQUIRE_AGENT_ATTESTATION=0

Service environments:

# systemd unit (Linux HTTP daemon)
[Service]
Environment=AI_MEMORY_REQUIRE_AGENT_ATTESTATION=0
<!-- launchd plist (macOS daemon) -->
<key>EnvironmentVariables</key>
<dict>
  <key>AI_MEMORY_REQUIRE_AGENT_ATTESTATION</key><string>0</string>
</dict>

MCP host: use the mcpServers.<name>.env JSON block shown in MCP configuration — a shell export does not reach an MCP server the host launches with its own environment.

iOS / Android: set AI_MEMORY_KEY_DIR (and, if opting out, AI_MEMORY_REQUIRE_AGENT_ATTESTATION) in your app’s process environment before the ai-memory library initializes.


See also: Agent identity · Governance · Encryption · v0.9.0 release notes