Going Batman-active.

v0.7.0 ships 6 of 6 of Batman's write-time-investment forms + the 7th (substrate-authority at write). All IMPLEMENTED. None active by default. This page is the operator-facing recipe to take a fresh install from Batman-capable to Batman-active — and make it survive reboot.

7-step recipe launchd / systemd / Task Scheduler issue #800 audit PR #753 — all 7 forms closed
The thesis

Pay at write time, read for free.

Batman is the published 6-form write-time-investment framework. Memory quality is set at WRITE time, not retrieval time. No amount of rerank fixes a substrate that ingested duplicates, un-atomized blobs, untyped facts, no provenance, no confidence, and no governance. ai-memory v0.7.0 implements all 6 + adds a 7th (substrate-authority over agent-EXTERNAL actions). On a default install the cognitive forms in the write path are on the moment you launch with --tier autonomous; the governance + namespace-policy forms are opt-in and need this recipe.

FormWhat it doesDefaultActivation gate
Form 1 Online dedup-and-synthesis (one batch action-emitting LLM call BEFORE write — add/update/delete/no-op verb vocabulary) ACTIVE Autonomous tier; on by default in MCP write path
Form 2 Synchronous atomise-before-embed (source decomposed into atoms BEFORE embedding) ACTIVE On by default in MCP write path
Form 3 Multi-step ingest orchestrator (prompt-cache reuse + explicit-trust deterministic-then-LLM helpers) ACTIVE On by default in MCP write path
Form 4 Fact-provenance: citations field + source-as-URI + atom-grain span ACTIVE Schema-level; always on at v0.7.0
Form 5 Auto-confidence + shadow-mode calibration + freshness decay OPT-IN Process env vars — AI_MEMORY_AUTO_CONFIDENCE=1 / AI_MEMORY_CONFIDENCE_SHADOW=1 / AI_MEMORY_CONFIDENCE_DECAY=1 — + curator daemon
Form 6 MemoryKind vocabulary (Concept / Entity / Claim / Relation / Event / Conversation / Decision / Observation / Reflection / Persona) OPT-IN Per-namespace auto_classify_kind policy ("regex_then_llm" / "regex_only") + curator backfills
7th Substrate-authority at write — check_agent_action at Bash / Filesystem / Network / Spawn boundaries; refuses per signed operator rules OPT-IN Operator key + R001–R004 signed + enabled

Form 7 at v0.7.0 wires substrate-internal write paths (any caller of memory_store / memory_link / memory_delete / memory_archive / memory_consolidate / memory_replay). Agent-EXTERNAL Layer-4 (Bash / FilesystemWrite outside substrate / NetworkRequest / ProcessSpawn) is callable_now=false until the v0.8.0 harness-boundary wiring lands (#697). Operators can already enforce the same rules at the harness boundary themselves via a PreToolUse hook that shells out to ai-memory rules check.

Default state

Capable, not active.

A fresh v0.7.0 install with --tier autonomous --profile full running gives you Forms 1–4 the moment the MCP server boots. Everything else is dormant by design. The recipe below activates Forms 5, 6, 7 and makes the activation survive reboot.

already on

Forms 1–4

Online dedup, synchronous atomise-before-embed, multi-step ingest, fact-provenance. Six cognitive transforms before a row hits SQL.

step 5 + 7

Forms 5 + 6

Form 5 freshness decay + shadow-mode are process env vars on the MCP server + curator daemon; Form 6 auto-classify is a per-namespace policy. Default off so operators choose the LLM-call surface.

steps 1–4

7th-form

Operator-signed seed rules R001–R004 ship in the table at enabled=0, attest_level='unsigned'. Until the operator signs + enables them, the governance gate is dormant.

The recipe

Seven steps to Batman-active.

Set the DB path once and the rest of the commands inherit it:

# pick your DB; the same path your MCP server uses export AI_MEMORY_DB=~/.claude/ai-memory.db
Resolved wart (earlier v0.7.0 builds) ai-memory rules keygen wrote the operator key to <config-dir>/operator.key while the mutation verbs only looked under <config-dir>/keys/. Fixed (#800 Gap #6 / #822): load_operator_signing_key_from_dir now resolves the key across all three layouts (legacy keys/operator.priv+.pub, staged keys/operator.key+.pub, and the parent-dir singleton keygen writes). No mv workaround is needed.
Step 1 — Operator keypair

Generate the Ed25519 keypair that signs rule mutations.

Without it, rules enable / disable / remove / add all refuse with governance.no_operator_key. Read-only verbs (list, check) work unsigned.

ai-memory rules keygen
Step 2 — Sign the seed rules

Flip R001–R004 from unsigned → operator_signed.

R001–R004 ship in the governance_rules table with enabled=0 and attest_level='unsigned'. sign-seed populates signature_b64 and lifts attest_level to operator_signed. It does not enable them.

ai-memory rules sign-seed # → { "rules": [ { "id": "R001", "attest_level": "operator_signed", "signed_now": true }, { "id": "R002", "attest_level": "operator_signed", "signed_now": true }, { "id": "R003", "attest_level": "operator_signed", "signed_now": true }, { "id": "R004", "attest_level": "operator_signed", "signed_now": true } ], "signed_now": 4 }

Seed rules: R001 refuses filesystem_write under /tmp/**, R002 under /var/tmp/**, R003 under /private/tmp/** (macOS realpath), R004 refuses process_spawn of cargo when free disk is under 20 GiB.

Step 3 — Enable each rule

Flip enabled=0 → 1 per rule.

--sign is mandatory for mutation verbs.

for r in R001 R002 R003 R004; do ai-memory rules enable --id "$r" --sign done
Step 4 — Smoke-test enforcement

Confirm Form 7 is live.

# refuse under R001 ai-memory rules check --kind filesystem_write \ --payload '{"path":"/tmp/foo.txt"}' --agent-id smoke # → {"decision":"refuse","reason":"...no /tmp writes...","rule_id":"R001"} # allow (path outside refused set) ai-memory rules check --kind filesystem_write \ --payload "{\"path\":\"$HOME/.local-runs/foo.txt\"}" --agent-id smoke # → {"decision":"allow"}
Step 5 — Curator daemon

Autonomous upkeep loop for Forms 1, 5, 6.

The curator runs Form 1 background dedup sweeps on rows the in-process write-path already saw, Form 5 freshness-decay sweeps, Form 6 auto-classify backfills, and Rule-5 consolidation. --max-ops caps LLM-invoking operations per cycle.

mkdir -p ~/Library/Logs/ai-memory # macOS ai-memory curator --daemon \ --interval-secs 300 \ --max-ops 100 \ >> ~/Library/Logs/ai-memory/curator.log 2>&1 &

For permanence across reboot, skip this manual launch and use the OS service manager — see the next section.

Step 6 — (Optional) Reflection-pass curator

Synthesise Reflection memories from co-recalled Observation clusters.

ai-memory curator --reflect --all-namespaces \ --interval-secs 1800 \ --max-depth 3 \ >> ~/Library/Logs/ai-memory/curator-reflect.log 2>&1 &

Per-namespace max_reflection_depth is enforced on top — --max-depth refuses to propose reflections that would exceed an operator-supplied cap so the curator never burns an LLM round-trip on a doomed write.

Step 7 — Form 5 env vars + per-namespace Form 2/6 policies

Form 5 is process env vars; Forms 2 + 6 ride a namespace standard memory.

Form 5 lives on the MCP server + curator daemon process environment. Forms 2 + 6 live in a standard memory's metadata.governance, bound via memory_namespace_set_standard (MCP) or ai-memory namespace set-standard (CLI):

# Form 5 — set on BOTH the MCP server invocation and the curator daemon: export AI_MEMORY_AUTO_CONFIDENCE=1 export AI_MEMORY_CONFIDENCE_SHADOW=1 export AI_MEMORY_CONFIDENCE_DECAY=1 # Forms 2 + 6 — Option A: MCP. 1) memory_store a standard memory whose # metadata.governance carries the policy; 2) bind it: { "namespace": "main", "id": "<standard-memory-uuid>" } // memory_namespace_set_standard # governance keys: auto_atomise, auto_atomise_mode ("synchronous"), # auto_atomise_threshold_cl100k, auto_atomise_max_atom_tokens, # auto_classify_kind ("regex_then_llm" | "regex_only"), max_reflection_depth # Option B — CLI, merging the governance JSON in the same bind: ai-memory namespace set-standard --namespace main --id <standard-memory-uuid> \ --governance '{"auto_atomise":true,"auto_atomise_mode":"synchronous", "auto_classify_kind":"regex_then_llm","max_reflection_depth":3}'
Verification

Confirm Batman-active in one block.

# 1. Rules enabled + signed ai-memory rules list --json | jq '[.result[] | {id, enabled, attest_level}]' # Expect: all 4 enabled=true, attest_level="operator_signed" # 2. Curator processes alive pgrep -fl ai-memory # Expect: at least the curator --daemon line; reflection-pass if started # 3. Form 7 enforcement live ai-memory rules check --kind filesystem_write \ --payload '{"path":"/tmp/x"}' --agent-id verify # Expect: {"decision":"refuse","rule_id":"R001"} # 4. Namespace standards populated ai-memory namespace get-standard --namespace main --json # Expect: the bound standard memory whose metadata.governance carries # auto_atomise / auto_classify_kind / max_reflection_depth
Permanence

Making it survive reboot.

Operator key, signed/enabled rules, and namespace policies all live on disk or in SQLite and survive reboot. The curator daemon does not — it needs the OS service manager. The full plist / unit / scheduler XML lives in the markdown companion; here is the shape:

macOS

launchd LaunchAgent

~/Library/LaunchAgents/dev.alphaone.ai-memory.curator.plist with RunAtLoad=true, KeepAlive on Crashed=true, ProcessType=Background, Nice=5. Load with launchctl bootstrap gui/$(id -u) ....

Linux

systemd user unit

~/.config/systemd/user/ai-memory-curator.service, Restart=on-failure, RestartSec=30s, Nice=5. Pair with loginctl enable-linger $USER for headless hosts.

Windows

Task Scheduler

New-ScheduledTask -Trigger AtLogOn with -RestartCount 3, -RestartInterval 1m, -StartWhenAvailable.

Cloud-LLM key wiring. A service manager does not inherit your login-shell exports: launchd's GUI domain and systemd service units never see an OPENROUTER_API_KEY / XAI_API_KEY you export from ~/.zshrc. A curator daemon that can't resolve its key reports key_source=error and every cycle silently logs tagged=0. Prefer [llm].api_key_file = "…/openrouter-api.key" (mode 0400) in config.toml — env-independent and identical across launchd/systemd — or declare the key var inside the plist EnvironmentVariables dict / unit Environment= line.

Rollback

Going back to Batman-capable.

# Stop the daemons launchctl bootout "gui/$(id -u)/dev.alphaone.ai-memory.curator" 2>/dev/null # Linux: systemctl --user disable --now ai-memory-curator.service # Disable rules (keeps signatures intact for fast re-enable) for r in R001 R002 R003 R004; do ai-memory rules disable --id "$r" --sign done # (Optional) Drop the namespace policy binding + unset the Form 5 env vars ai-memory namespace clear-standard --namespace main unset AI_MEMORY_AUTO_CONFIDENCE AI_MEMORY_CONFIDENCE_SHADOW AI_MEMORY_CONFIDENCE_DECAY

The operator key on disk is left alone — keep it, rotate it via a fresh ai-memory rules keygen if you suspect compromise, or rm it if you genuinely want to retire the operator role on this node. Disabled rules remain in the database with signatures intact; enable flips them back on without re-signing.

Related

Where to read more.