Agent Memory (agent-memory)

September 20, 2026 · View on GitHub

中文版

agent-memory is ANOLISA's file-form memory MCP server, providing AI agents with a persistent, searchable, sandboxed memory space. Agents read and write memory like a filesystem; the system injects relevant context into subsequent turns via BM25/vector hybrid retrieval and automatic capture/recall, reducing repeated communication and improving task continuity.

  • File-form memory: read/write memory with filesystem semantics via MCP tools; namespace isolation and path sandboxing.
  • Hybrid semantic search: BM25 + dense vector + RRF fusion with automatic fallback.
  • Auto capture & recall: automatically extracts observations at conversation end (deduped) and injects relevant memory when building the next prompt.
  • Safe injection: prompt-injection detection and escaping for memory content injected into LLM prompts.
  • Versioning & snapshots: optional auto git commit + tar.gz snapshots for file-level and mount-level rollback.

Use Agent Memory when context must persist across sessions. Tokenless addresses a different part of the workflow by compressing content that still needs to enter the current context window.


Requirements

  • Linux on x86_64 or aarch64
  • An Agent runtime that supports stdio MCP servers

Installation

anolisa install agent-memory

Produces: agent-memory binary, default config, MCP service descriptor, systemd user template, tmpfiles rule, OpenClaw adapter bundle.

RPM package (AnolisOS / RHEL)

sudo yum install agent-memory

RPM installs to system-level FHS paths:

PurposePath
Service binary/usr/bin/agent-memory
Default config/usr/share/anolisa/agent-memory/default.toml
MCP service descriptor (auto-discovery)/usr/share/anolisa/mcp-servers/agent-memory.json
systemd user template/usr/lib/systemd/user/anolisa-memory@.service
tmpfiles rule (creates /run/anolisa/{,sessions})/usr/lib/tmpfiles.d/anolisa-memory.conf
OpenClaw adapter bundle/usr/share/anolisa/adapters/agent-memory/
Docs/usr/share/doc/agent-memory/

Source build (developers)

git clone https://github.com/alibaba/anolisa.git
cd anolisa/src/agent-memory

make build         # cargo build --release --locked
sudo make install  # install to /usr/local

Build deps: Rust ≥ 1.85 (edition 2024; CI pins 1.89 to share the monorepo toolchain), cmake (libgit2 vendored), systemd-devel (journald audit fan-out).

Cross-platform development

Runtime is Linux-only (depends on user_namespace, mount(2), cgroup v2, inotify, journald). On macOS / Windows use the remote flow:

make remote-build   # push branch and ssh to a Linux host for cargo build
make remote-test    # same + tests + clippy

Integration

Claude Code / Cursor / Continue / any stdio MCP client

Add to your MCP config:

{
  "mcpServers": {
    "agent-memory": {
      "command": "/usr/bin/agent-memory",
      "args": [],
      "env": {
        "USER_ID": "alice",
        "MEMORY_PROFILE": "advanced"
      }
    }
  }
}

/usr/share/anolisa/mcp-servers/agent-memory.json lists all 37 tool names for auto-discovering clients.

OpenClaw

The bundled plugin forwards 4 memory-contract tools (anolisa_memory_search, anolisa_memory_get, memory_observe, memory_get_context) to agent-memory:

bash /usr/share/anolisa/adapters/agent-memory/openclaw/scripts/install.sh
openclaw gateway restart

Or via anolisa adapter management:

anolisa adapter enable agent-memory openclaw
anolisa adapter status agent-memory

Prerequisite: openclaw CLI on $PATH. The script logs clearly and exits 0 if missing — rerun after installing OpenClaw. yum remove agent-memory triggers %preun to call the uninstall script, leaving no orphaned config.

Running anolisa adapter enable agent-memory openclaw or the agent-memory OpenClaw install.sh accepts the plugin's declared capabilities. Both entry points pass --accept-capabilities only when plugins install --help advertises that exact option, so older hosts keep working. Set AGENT_MEMORY_ACCEPT_CAPABILITIES=0 when running install.sh to withhold consent — gating hosts then reject the install until you grant it yourself, e.g. via an interactive openclaw plugins install.

Install-time environment variables (runtime MEMORY_* variables are listed separately under Environment variables):

VariableDefaultEffect
AGENT_MEMORY_ACCEPT_CAPABILITIESaccept1/true/yes/on grant consent when the host advertises the flag; 0/false/no/off withhold it, so gating hosts reject the install; any other value aborts with an error (exit code 2) before install
AGENT_MEMORY_SAFE_INSTALLunset1 declines --dangerously-force-unsafe-install on hosts that would still receive it; hosts advertising it as a deprecated no-op omit it either way
OPENCLAW_BINopenclawopenclaw CLI binary to invoke
OPENCLAW_STATE_DIR~/.openclawstate directory passed to every openclaw CLI invocation
OPENCLAW_HOME~/.openclawdefault for OPENCLAW_STATE_DIR only; never passed to the CLI (unset for every call)

The standalone install.sh negotiates the unsafe-install bypass the same way: it passes --dangerously-force-unsafe-install only while the installer advertises that option as effective. OpenClaw 2026.6.1 and earlier run an install-time safety scan that blocks child_process plugins non-interactively — this plugin spawns the agent-memory MCP server over stdio — so those hosts receive the bypass. OpenClaw 2026.6.5 and later dropped install-time dangerous-code blocking and list the option as a deprecated no-op, so they never receive it; there install-time safety is decided by the operator-owned security.installPolicy, which no script flag can override. AGENT_MEMORY_SAFE_INSTALL=1 declines the bypass on hosts where it still has effect and changes nothing on current ones — the install log states which case applied. If the plugins install --help probe itself fails, the host cannot be classified: the script keeps the bypass so pre-2026.6.2 hosts still install, logs a WARNING, and AGENT_MEMORY_SAFE_INSTALL=1 declines it there too.

When an install fails, the script reports only what it can verify. An unwritable ${OPENCLAW_STATE_DIR}/extensions is named as the filesystem-permission failure that on its own would break the install — fix that directory, and do not touch the policy for it. Otherwise the openclaw output above the script's note is the evidence, and security.installPolicy appears only as a conditional to confirm there, never as an asserted cause: a host that advertises the bypass as a deprecated no-op says nothing about why an install failed.

The OpenClaw plugin exposes anolisa_memory_search and anolisa_memory_get for ANOLISA memories. OpenClaw's memory_search and memory_get remain host tools; the namespaced tools do not compete for those names. Both install.sh and anolisa adapter enable agent-memory openclaw install the same plugin bundle. The scripts no longer explicitly disable or re-enable memory-core. OpenClaw still manages its memory slot and plugin loading according to its own configuration.

When upgrading, update prompts, skills, tool allowlists and direct callers that used the plugin's old memory_search / memory_get names. There are no old-name aliases; restart the gateway and start a new conversation so its tool list and memory instructions use the new names. Internal MCP names and stored memories are unchanged.

The plugin declares all four contract tools for the coding profile through its manifest's toolMetadata. OpenClaw 2026.9.2 conversation tool resolution honors this declaration, so both installation paths expose search, read, observe, and context tools without modifying your tool policy. Explicit allow/deny restrictions still apply. group:memory expands only to OpenClaw's memory_search and memory_get; it does not include the ANOLISA names.

Profile metadata support is not an installation requirement: older hosts can use explicit tool grants. OpenClaw 2026.5.7 does not honor toolMetadata.profiles; automatic profile contributions are verified separately on 2026.9.2. For a host or tool surface that does not honor this metadata, add anolisa_memory_search, anolisa_memory_get, memory_observe, and memory_get_context to the effective tools.alsoAllow (or the corresponding agent/provider policy). Merge these entries into your existing list rather than replacing it. For example, when no list exists:

{
  "tools": {
    "profile": "coding",
    "alsoAllow": [
      "anolisa_memory_search",
      "anolisa_memory_get",
      "memory_observe",
      "memory_get_context"
    ]
  }
}

Sandbox sessions have an additional policy: the default sandbox does not allow memory tools, including the old names. If you intend to allow ANOLISA search/read there, also add the two new names to tools.sandbox.tools.alsoAllow (or the agent's sandbox policy). An existing sandbox allow: ["group:memory"] needs this addition too. Add memory_observe and memory_get_context there only if those capabilities are intended as well. Keep explicit deny rules and other agent/provider restrictions; alsoAllow does not override a deny. Installers do not grant these permissions. Restart the gateway and start a new conversation after editing the policy.

If an earlier installer left ${OPENCLAW_STATE_DIR}/.anolisa-memory-anolisa-disabled-memory-core, the scripts warn and retain it for manual recovery. Inspect plugins.slots.memory and plugins.entries.memory-core.enabled first. To allow the bundled sidecar while keeping your current slot, set plugins.entries.memory-core.enabled to true with openclaw config set, then restart the gateway; host policy and version still determine whether the sidecar loads. To select memory-core as the active backend after removing this plugin, use openclaw plugins enable memory-core. That command changes the memory slot, so do not use it if you want to keep memory-anolisa, another backend, or none. Remove the marker only after you have confirmed the desired state, including an intentional choice to keep memory-core disabled. The new tools work without restoring it.

Plugin contract ↔ agent-memory MCP tool mapping:

OpenClaw contractagent-memory MCP tool
anolisa_memory_searchmemory_search (BM25 default; mode=vector|hybrid with embedding)
anolisa_memory_getmem_read
memory_observememory_observe
memory_get_contextmemory_get_context

Plugin config (via OpenClaw UI or openclaw.json plugins.entries["memory-anolisa"].config):

KeyDefaultPurpose
binaryPathauto-discovery: $PATH/usr/bin/agent-memory/usr/local/bin/agent-memory~/.local/bin/agent-memoryabsolute binary path
userIdenv USER_ID → OS uid → env $USERnamespace user_id; same validation as Rust side
profileadvancedprofile gate, passed as MEMORY_PROFILE env; basic or advanced only — the plugin rejects expert at load (see Profiles below)
maxReadBytes1048576 (1 MiB)mem_read cap, passed as MEMORY_MAX_READ_BYTES
maxWriteBytes16777216 (16 MiB)mem_write cap, passed as MEMORY_MAX_WRITE_BYTES
sessionIdenv MEMORY_SESSION_ID → new ses_<random>namespace session; must be fixed
sessionDirenv MEMORY_SESSION_DIR/run/anolisa/sessionssession scratch + log root

The plugin passes a minimal env allowlist to the subprocess (PATH, HOME, USER, USER_ID, LANG/LC_ALL/LC_CTYPE, TZ, TMPDIR, XDG_RUNTIME_DIR, and all MEMORY_/RUST_-prefixed vars); other env does not leak. USER_ID matches exactly — USER_IDX is not allowed.


MCP tool set (37 tools)

All tools are invoked via MCP tools/call with JSON object arguments. Errors return CallToolResult { isError: true } so clients can distinguish business errors from "successful but content contains 'failed'". Profile is enforced at both tools/list and tools/call.

Tier A — file operations (11)

ToolRequiredOptionalReturns
mem_readpathUTF-8 file content
mem_writepath, contentoverwritewrote N bytes to <path>
mem_appendpath, contentappended N bytes to <path>
mem_editpath, old_str, new_stredited <path> (old_str must match exactly once)
mem_listdir, recursive, glob{name, type, size, mtime} array
mem_greppatterndir, type, max, case_insensitive{path, line, text} array
mem_diffpath1, path2unified diff
mem_mkdirpathcreated <path>
mem_removepathrecursiveremoved <path>
mem_promotesession_path, store_pathatomically move session scratch file into the persistent store
mem_session_logcurrent session JSONL

Tier B — structured retrieval (6)

ToolRequiredOptionalReturns
memory_searchquerytop_k (default 5), mode (bm25/vector/hybrid), category{path, score, snippet, suspicious} array
memory_observecontenthint, typeobserved at notes/observed/<ulid>.md
memory_get_contextmax_tokens (default 2048)markdown preview of recently modified files; each entry has suspicious
memory_sessionslimit (default 10)historical session list
memory_timelinesession_idlimit (default 50)tool-call timeline for a specific session
mem_index_refreshforce-rebuild the FTS5 index

Tier C — governance & versioning (7)

ToolRequiredOptionalReturns
mem_snapshotname{id, name, created_at, size, backend}
mem_snapshot_listarray sorted by created_at
mem_snapshot_restoreidrestored <id>
mem_loglimit (default 20), path{hash, summary, author, time} array (requires git)
mem_revertpathreverted <path> (commit <hash>) (requires git)
mem_consolidateconsolidation complete: N facts written
mem_compactcompacted N files to cold storage

Sovereignty & import/export (13)

ToolRequiredOptionalReturns / notes
memory_abouttopiclimit (default 10)matching memory paths and snippets for a topic
memory_auto_createdlimit (default 20)JSON array of auto-extracted facts
memory_consentaction (query/allow/deny), scope (all/consolidation/capture)grant/revoke memory operations
memory_forgettopicconfirm (default false=preview, true=delete)delete memory entries about a topic
mem_exportcategory, sourceexport the store as an AMA JSON string (does not write a file)
mem_importjson_datastrategy (skip-existing/overwrite, default skip-existing), dry_run (default false)import memory from an AMA JSON string
memory_task_savetitlestatus, progress, next_steps, blockers, files_modified, decisions, context, idsave/update a task; returns the task id (pass id to update an existing task)
memory_task_liststatus (in-progress/blocked/done/cancelled)JSON array of task summaries
memory_task_resumeidresume task context (formatted for continuing in a new session)
memory_task_closeidreasonclose a task (mark done)
memory_summaryrecent_limit (default 10)memory store statistics overview JSON
memory_session_contextlimitsession-start context injection
mem_dreamuser profile synthesis JSON

Error code semantics

MCP codeMeaning
-32601 METHOD_NOT_FOUNDtool hidden by current profile
-32602 INVALID_PARAMSmissing or wrong-type param
-32603 INTERNAL_ERRORserver fault
isError: truetool ran but returned a business error (path missing, sandbox rejection, size limit, etc.)

Core features

File-form memory

Agents organize memory by path, matching the human filesystem model:

notes/day1.md
decisions/2026-05/db-pick.md
context/project-overview.md

Namespace layout:

~/.anolisa/memory/user-<uid>/        # mount root
├── README.md                        # auto-generated overview
├── notes/                           # free-form notes
├── decisions/                       # user-defined subdirs
└── .anolisa/                        # OS-managed, not writable by agents
    ├── manifest.toml                # namespace metadata
    ├── audit.log                    # JSONL tool-call audit
    ├── index.db                     # FTS5 SQLite
    ├── snapshots/                   # tar.gz archives + sidecar
    ├── trash/                       # entries retained on restore
    └── git/                         # bare git mirror (when git enabled)

Session dir (tmpfs, 0700):

/run/anolisa/sessions/<sid>/
├── meta.toml
├── log.jsonl
└── scratch/                         # session-only drafts; promoted via mem_promote

Sandbox protection

Every file open is anchored at the mount root via kernel openat2(RESOLVE_BENEATH | RESOLVE_NO_SYMLINKS):

  • Rejects .. traversal
  • Rejects symlinks (including mid-call replacement; recursive deletes use fdopendir + fstatat(AT_SYMLINK_NOFOLLOW) + unlinkat so swaps can't race)
  • Rejects access to metadata dirs (.anolisa, .git, .gitignore via TargetIsReserved)
  • mem_snapshot_restore filters tar entry types — rejects Symlink/Hardlink/Device/Fifo
  • Oversized payloads rejected per max_*_bytes

Mount strategies:

StrategyWhenBehavior
userland (default)any environmentmount is just a directory; sandbox enforced by openat2
usernsLinux ≥ 4.6 with unprivileged user namespaceunshare into a new user+mount namespace, mount a private tmpfs, then bind-mount the backing dir; host-side processes can't see /mnt/memory/<ns>/
autoruntime probetry userns; fall back to userland on any error

Version control

Optional auto git commit (libgit2 vendored):

MEMORY_GIT_ENABLED=true MEMORY_GIT_AUTO_COMMIT=true agent-memory

With git on, mem_log exposes change history and mem_revert gives the agent a real "undo" button. mem_snapshot* provides mount-wide tar.gz point-in-time backups independent of git.

SQLite FTS5 BM25 index, sub-millisecond queries. A background tokio task watches the mount via inotify; events are debounced 200 ms and applied in a single transaction. Tokenizer is trigram (substring matching for ≥3-char terms). The trigram tokenizer emits one token per 3-character window, so a query term shorter than 3 characters (common for CJK words like "花名" / "小云") produces no tokens and would silently match nothing; memory_search detects this case and falls back to a body LIKE '%term%' substring scan so short CJK queries still recall. IN_Q_OVERFLOW triggers a full rescan — events are never silently dropped.

BM25 + dense vector hybrid retrieval, fused via RRF (Reciprocal Rank Fusion, k=60). Vectors come from a pluggable Embedding Provider:

ProviderConfigurationNotes
OpenAIMEMORY_EMBEDDING_BACKEND=openai + OPENAI_API_KEYcalls OpenAI Embeddings API
OllamaMEMORY_EMBEDDING_BACKEND=ollama + OLLAMA_BASE_URLlocal Ollama instance

memory_search supports mode: bm25 (default) / vector (cosine similarity) / hybrid (RRF fusion). Without embedding config, vector/hybrid auto-degrade to BM25 — no error.

Auto consolidation

On shutdown, automatically extracts atomic facts from the session audit log (mem_consolidate) using 6 heuristic rules (zero LLM calls) — identifies high-frequency paths, search patterns, etc., and persists them as structured memory. Also manually triggerable via the mem_consolidate tool. Includes episodic memory extraction and conflict detection (BM25 threshold).

Audit & observability

Every successful tool call appends a JSONL line to <mount>/.anolisa/audit.log; with sessions enabled, also to /run/anolisa/sessions/<sid>/log.jsonl. audit.journald=true fans out to systemd-journald with structured fields (MESSAGE_ID, AGENT_MEMORY_TOOL, etc.) for journalctl --user-unit=anolisa-memory@<user> filtering.


Configuration

Config file

Default location: ~/.anolisa/memory.toml. The file is optional; Agent Memory uses built-in defaults when it is absent. All structs enable serde(deny_unknown_fields) — typos hard-fail at load. Minimal config:

[global]
user_id = "alice"

[memory]
profile = "advanced"           # basic | advanced | expert
max_read_bytes = 1048576       # 1 MiB
max_write_bytes = 16777216     # 16 MiB
max_append_bytes = 4194304     # 4 MiB

[memory.paths]
base_dir = "~/.anolisa/memory"

[memory.session]
base_dir = "/run/anolisa/sessions"
end_action = "discard"         # discard | keep

[memory.mount]
strategy = "auto"              # auto | userland | userns

[memory.index]
enabled = true
time_decay_lambda = 0.01
time_decay_alpha = 0.3
cold_after_days = 30
exclude_cold_on_search = true

[memory.audit]
journald = false

[memory.cgroup]
enabled = false
memory_max = "512M"

[memory.git]
enabled = false
auto_commit = true

[memory.consolidation]
enabled = true
max_facts = 20
min_tool_calls = 3
episodic_enabled = true
min_episode_steps = 3
max_episodes_per_session = 10
conflict_detection = true
conflict_bm25_threshold = -2.0

Environment variables

Every config key has a matching MEMORY_* env var. Priority: env > config.toml > default.

VariableDescriptionDefault
USER_IDuser identity (validated; invalid values warn-and-ignore)
MEMORY_PROFILEprofile (basic/advanced/expert)advanced
MEMORY_BASE_DIRmemory store root~/.anolisa/memory
MEMORY_SESSION_DIRsession root/run/anolisa/sessions
MEMORY_SESSION_IDfixed session id (required for mem_promote)new ses_<random>
MEMORY_SESSION_ENDsession end action (discard/keep)discard
MEMORY_MOUNT_STRATEGYmount strategy (auto/userland/userns)auto
MEMORY_MAX_READ_BYTESper-read cap1 MiB
MEMORY_MAX_WRITE_BYTESper-write cap16 MiB
MEMORY_MAX_APPEND_BYTESper-append cap4 MiB
MEMORY_INDEX_ENABLEDenable FTS5 indextrue
MEMORY_INDEX_TIME_DECAY_LAMBDAtime decay (≥0)0.01
MEMORY_INDEX_TIME_DECAY_ALPHAtime weight ratio (0–1)0.3
MEMORY_INDEX_COLD_AFTER_DAYScold archive days30
MEMORY_INDEX_EXCLUDE_COLDexclude cold from searchtrue
MEMORY_AUDIT_JOURNALDfan out to journaldfalse
MEMORY_CGROUP_ENABLEDenable cgroup limitsfalse
MEMORY_CGROUP_MEMORY_MAXcgroup memory cap512M
MEMORY_GIT_ENABLEDenable git versioningfalse
MEMORY_GIT_AUTO_COMMITauto committrue
MEMORY_EMBEDDING_BACKENDembedding backend (none/openai/ollama)none
MEMORY_OPENAI_API_KEYOpenAI API key (falls back to OPENAI_API_KEY)
MEMORY_OPENAI_MODELOpenAI embedding modeltext-embedding-3-small
MEMORY_OLLAMA_MODELOllama embedding modelnomic-embed-text
MEMORY_OLLAMA_BASE_URLOllama base URLhttp://localhost:11434
MEMORY_CONSOLIDATION_ENABLEDenable auto consolidationtrue
MEMORY_CONSOLIDATION_MAX_FACTSmax facts per run20
MEMORY_CONSOLIDATION_MIN_CALLSmin tool-call threshold3
MEMORY_EPISODIC_ENABLEDepisodic extractiontrue
MEMORY_MIN_EPISODE_STEPSmin episode steps3
MEMORY_MAX_EPISODESmax episodes per session10
MEMORY_CONFLICT_DETECTIONconflict detectiontrue
MEMORY_CONFLICT_THRESHOLDBM25 conflict threshold-2.0

Data storage: ~/.anolisa/memory/<namespace>/.

Profiles

Profiles are UX hints, not security boundaries, but enforced at both tools/list and tools/call:

  • basic — all 37 tools shown; weaker models can use the Tier B structured API.
  • advanced (default) — all 37 tools shown; stronger models should prefer Tier A file ops.
  • expert — hides Tier B (memory_search, memory_observe, memory_get_context, mem_consolidate, memory_forget, memory_consent); tools/call returns METHOD_NOT_FOUND. For proficient models that only need Tier A and Tier C.

expert is a setting for direct MCP clients, which drive the Tier A file tools themselves. The OpenClaw adapter rejects plugins.entries["memory-anolisa"].config.profile = "expert" when the plugin loads: three of the four tools it registers for the host's memory contract (anolisa_memory_searchmemory_search, memory_observe, memory_get_context) use Tier B MCP methods, and so do the two paths that call memory_search on the agent's behalf — auto-recall before each prompt and the corpus=all supplement. Forwarding the profile would leave the memory slot loaded while every one of those calls came back METHOD_NOT_FOUND, so the adapter fails at boot and says why instead.

Embedding config

[memory.embedding]
backend = "openai"                # or "ollama"
api_key = ""                      # empty: auto-read OPENAI_API_KEY
model = "text-embedding-3-small"
# Ollama: backend = "ollama", model = "nomic-embed-text", base_url = "http://localhost:11434"

Use cases

  • Cross-session persistence of notes and decisions (Claude Code, Cursor, Continue, custom rmcp clients).
  • Multi-agent systems where Agent A writes and Agent B reads shared notes.
  • Operation audit and state recovery (mem_log, JSONL audit, journald, mem_revert, mem_snapshot_restore).
  • Multi-turn "draft first, persist when decided" pattern (mem_promote atomically moves files from session scratch into the persistent store).

SDK / client integration

Python (official mcp SDK)

import asyncio
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client

async def main():
    server = StdioServerParameters(
        command="/usr/bin/agent-memory", args=[],
        env={"USER_ID": "alice"},
    )
    async with stdio_client(server) as (read, write):
        async with ClientSession(read, write) as session:
            await session.initialize()
            tools = await session.list_tools()
            print([t.name for t in tools.tools])
            result = await session.call_tool(
                "mem_write",
                {"path": "notes/from-python.md", "content": "hello"},
            )
            assert not result.isError

asyncio.run(main())

TypeScript (@modelcontextprotocol/sdk)

import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";

const transport = new StdioClientTransport({
  command: "/usr/bin/agent-memory", args: [],
  env: { USER_ID: "alice" },
});
const client = new Client({ name: "my-app", version: "1.0.0" }, {});
await client.connect(transport);
const result = await client.callTool({
  name: "mem_grep",
  arguments: { pattern: "TODO", recursive: true, max: 50 },
});

Rust (rmcp)

use rmcp::transport::child_process::ChildProcessTransport;
use rmcp::ServiceExt;

let transport = ChildProcessTransport::new(
    tokio::process::Command::new("/usr/bin/agent-memory"),
).await?;
let client = ().serve(transport).await?;
let tools = client.list_tools(Default::default()).await?;

Promote workflow (multi-turn)

  1. Set MEMORY_SESSION_ID=<sid> and MEMORY_SESSION_DIR=/run/anolisa/sessions for each agent run.
  2. Agent writes drafts to /run/anolisa/sessions/<sid>/scratch/.
  3. When a draft is worth keeping, the agent calls mem_promote to atomically move it into the persistent store.

Testing & verification

Automated tests

cd src/agent-memory
cargo fmt --check
cargo clippy -- -D warnings
cargo test                              # full suite
cargo test --test e2e_agent_test        # tool E2E
cargo test --test mcp_integration_test  # protocol layer
cargo test --test linux_userns_test -- --ignored  # needs unprivileged userns
make smoke                              # one-shot end-to-end smoke

CI runs fmt --check + clippy -D warnings + cargo test on Rust 1.89.

Interactive mcp-harness

cargo run --example mcp-harness -- /tmp/mem-test
CommandDescription
listlist visible tools
call <tool> <json_args>invoke a tool
helphelp
quitquit

Scenarios: --scenario full / git --git / promote / --verbose (prints JSON-RPC).

Raw JSON-RPC (protocol-level debugging)

mkdir -p /tmp/mem-test/__sessions__
MEMORY_BASE_DIR=/tmp/mem-test \
MEMORY_SESSION_DIR=/tmp/mem-test/__sessions__ \
MEMORY_MOUNT_STRATEGY=userland \
USER_ID=tester \
agent-memory

Handshake + tool call:

{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"manual","version":"1.0"}}}
{"jsonrpc":"2.0","method":"notifications/initialized"}
{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"mem_write","arguments":{"path":"test.md","content":"hello"}}}

Sandbox escape verification

{"name":"mem_read","arguments":{"path":"../../etc/passwd"}}

isError: true, message path outside mount root.

{"name":"mem_write","arguments":{"path":".anolisa/audit.log","content":"x"}}

isError: true, message target is reserved.


Troubleshooting

Diagnostic tools

# Component-level diagnosis (follow the reported fix plan manually)
anolisa doctor agent-memory

# Adapter status
anolisa adapter status agent-memory

# Debug startup
RUST_LOG=agent_memory=debug agent-memory

Common issues

SymptomLikely causeFix
startup unshare(NEWUSER|NEWNS): EPERMunprivileged user namespace disabledsysctl kernel.unprivileged_userns_clone=1, or MEMORY_MOUNT_STRATEGY=userland
tmpfs /mnt: EBUSY/mnt occupied in new namespacerestart the process
macOS / Windows cargo build fails on libsystemd/nixnon-Linux hostmake remote-build / remote-test
tools/call memory_search returns METHOD_NOT_FOUNDMEMORY_PROFILE=expert hides Tier Bswitch to advanced, or use Tier A directly
config typos silently ignorednow hard-fail; check startup stderr
mem_log returns [] despite writesgit versioning not enabledMEMORY_GIT_ENABLED=true MEMORY_GIT_AUTO_COMMIT=true
search misses just-written contentinside the 200 ms debounce windowretry, or use mem_grep (regex on the filesystem, no index)
mem_promote reports session not foundMEMORY_SESSION_ID/MEMORY_SESSION_DIR unset or scratch missingsee Promote workflow
OpenClaw plugin not loadedopenclaw CLI not on PATHrerun install.sh after installing OpenClaw
OpenClaw calls the host memory backend or reports plugin tool name conflictOld plugin bundle, stale gateway/session, or old tool names in promptsUpdate the plugin, restart the gateway, start a new session, and use anolisa_memory_search / anolisa_memory_get for ANOLISA memories
install.sh reports Plugin "memory-anolisa" requires capability consentOpenClaw >= 2026.8.1 consent gate; installer-options probe failed, AGENT_MEMORY_ACCEPT_CAPABILITIES=0 is set, or script predates the fixcheck install output for the probe WARNING or opt-out refusal line; update agent-memory, unset the opt-out, or run openclaw plugins install <plugin-dir> --force --accept-capabilities manually. A withheld install rejected by the gate exits with code 3; if OpenClaw rewords the rejection message, the script falls back to exit 1 with the opt-out note
install.sh reports the install target is not writable${OPENCLAW_STATE_DIR}/extensions (or its nearest existing parent) is not writable by the user running the script, so OpenClaw's mkdir extensions/memory-anolisa fails with EACCESfix that directory's ownership/permissions — or point OPENCLAW_STATE_DIR at a writable state directory — and re-run. This is a filesystem failure, not a policy refusal: do not relax security.installPolicy for it
install.sh fails on a host that lists --dangerously-force-unsafe-install as a deprecated no-opOpenClaw 2026.6.5+ runs no install-time scan, so the script sent no bypass and cannot shape install-time safety there; the cause is in the openclaw outputread the CLI output above the script's note. Only if it names security.installPolicy is that operator-owned policy what to relax — re-running the script or setting AGENT_MEMORY_SAFE_INSTALL cannot override it
install.sh reports the safety scan blocked the pluginOpenClaw 2026.6.1 or earlier scans plugin sources at install time and flags the plugin's child_process.spawn MCP transportunset AGENT_MEMORY_SAFE_INSTALL so the script passes the bypass it declined, or upgrade OpenClaw
system state out of sync after manual dnfsudo anolisa --install-mode system repair agent-memory; use system-scoped forget / adopt only when intentionally rebuilding the record for a present RPM

For deeper investigation: start with RUST_LOG=agent_memory=debug and inspect both stderr and <mount>/.anolisa/audit.log.


License: Apache-2.0 Version: 0.2.1 Document version: 2.0 (aligned with ANOLISA-design user-guide structure)