跳到主要内容

Agent 记忆(agent-memory)

English

agent-memory 是 ANOLISA 的文件形态记忆 MCP 服务器,为 AI Agent 提供持久化、可搜索、受沙箱保护的记忆空间。Agent 像操作文件系统一样读写记忆,系统通过 BM25/向量混合检索、自动捕获与召回机制,把相关上下文注入后续对话,从而减少重复沟通并提升任务连贯性。

  • 文件形态记忆:通过 MCP 工具以文件系统语义读写记忆,支持命名空间隔离和路径沙箱。
  • 混合语义搜索:BM25 + 稠密向量 + RRF 融合,支持自动 fallback,召回相关记忆片段。
  • 自动捕获与召回:在对话结束时自动提取观察并去重,在下一轮构建提示时注入相关记忆。
  • 安全注入机制:对注入 LLM 提示的记忆内容做提示注入检测和转义包装,降低攻击面。
  • 版本化与快照:可选 git 自动提交 + tar.gz 快照,提供文件级与 mount 级回滚。

需要跨会话保留上下文时使用 Agent Memory。Tokenless 解决的是另一类问题:压缩 仍需进入当前上下文窗口的内容。


运行要求

  • Linux x86_64 或 aarch64
  • 支持 stdio MCP 服务器的 Agent 运行时

安装

通过 anolisa CLI(推荐)

anolisa install agent-memory

安装产物:agent-memory 二进制、默认配置、MCP 服务描述符、systemd user 模板、tmpfiles 规则、OpenClaw 适配器 bundle。

RPM 包(AnolisOS / RHEL 系)

sudo yum install agent-memory

RPM 安装到系统级 FHS 路径:

用途路径
服务二进制/usr/bin/agent-memory
默认配置/usr/share/anolisa/agent-memory/default.toml
MCP 服务描述符(自动发现)/usr/share/anolisa/mcp-servers/agent-memory.json
systemd user 模板/usr/lib/systemd/user/anolisa-memory@.service
tmpfiles 规则(创建 /run/anolisa/{,sessions}/usr/lib/tmpfiles.d/anolisa-memory.conf
OpenClaw 适配器 bundle/usr/share/anolisa/adapters/agent-memory/
文档/usr/share/doc/agent-memory/

源码构建(开发者)

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

make build # cargo build --release --locked
sudo make install # 安装到 /usr/local 下

构建依赖:Rust ≥ 1.85(edition 2024;CI 钉到 1.89 与 monorepo 共享 toolchain)、cmake(libgit2 vendored 构建)、systemd-devel(journald 审计 fan-out)。

跨平台开发

运行时仅支持 Linux(依赖 user_namespace、mount(2)、cgroup v2、inotify、journald)。macOS / Windows 请用远端构建:

make remote-build # push 分支并 ssh 到 Linux 主机执行 cargo build
make remote-test # 同上 + 跑测试 + clippy

集成配置

Claude Code / Cursor / Continue / 任意 stdio MCP 客户端

在 MCP 配置中添加:

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

/usr/share/anolisa/mcp-servers/agent-memory.json 描述符列出全部 37 个工具名,支持自动发现的客户端可直接识别。

OpenClaw

随包附带的插件把 4 个 memory contract 工具(memory_searchmemory_getmemory_observememory_get_context)转发到 agent-memory:

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

或通过 anolisa 适配器管理:

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

前置条件openclaw CLI 在 $PATH 上。脚本缺失时输出明确日志并以 0 退出,安装 OpenClaw 后重跑即可。yum remove agent-memory 时 spec 的 %preun 自动调用 uninstall 脚本,配置不残留孤立项。

插件 contract 名 ↔ agent-memory MCP 工具映射:

OpenClaw contractagent-memory MCP 工具
memory_searchmemory_search(BM25 默认;配置 embedding 后支持 mode=vector|hybrid
memory_getmem_read
memory_observememory_observe
memory_get_contextmemory_get_context

插件配置(通过 OpenClaw UI 或 openclaw.jsonplugins.entries["memory-anolisa"].config):

默认作用
binaryPath自动发现:$PATH/usr/bin/agent-memory/usr/local/bin/agent-memory~/.local/bin/agent-memory二进制绝对路径
userIdenv USER_ID → OS uid → env $USER命名空间 user_id;校验规则与 Rust 侧一致
profileadvancedprofile 门控,以 MEMORY_PROFILE env 启动子进程
maxReadBytes1048576(1 MiB)单次 mem_read 上限,以 MEMORY_MAX_READ_BYTES env 传入
maxWriteBytes16777216(16 MiB)单次 mem_write 上限,以 MEMORY_MAX_WRITE_BYTES env 传入
sessionIdenv MEMORY_SESSION_ID → 新生成 ses_<random>命名空间挂载会话,必须固定
sessionDirenv MEMORY_SESSION_DIR/run/anolisa/sessionssession scratch + log 根目录

插件给子进程传最小 env allowlist(PATHHOMEUSERUSER_IDLANG/LC_ALL/LC_CTYPETZTMPDIRXDG_RUNTIME_DIR 及所有 MEMORY_/RUST_ 前缀变量),其它 env 不泄漏。USER_ID 精确匹配,USER_IDX 等前缀变量不放行。


MCP 工具集(37 个)

所有工具通过 MCP tools/call 调用,参数为 JSON 对象。错误以 CallToolResult { isError: true } 返回,客户端可据此区分"成功但内容含 failed 字面"与真实错误。Profile 在 tools/listtools/call 两层校验。

Tier A — 文件操作(11 个)

工具必填可选返回
mem_readpathUTF-8 文件内容
mem_writepathcontentoverwritewrote N bytes to <path>
mem_appendpathcontentappended N bytes to <path>
mem_editpathold_strnew_stredited <path>old_str 须唯一命中)
mem_listdirrecursiveglob{name, type, size, mtime} 数组
mem_greppatterndirtypemaxcase_insensitive{path, line, text} 数组
mem_diffpath1path2unified diff
mem_mkdirpathcreated <path>
mem_removepathrecursiveremoved <path>
mem_promotesession_pathstore_path把会话 scratch 文件原子移入持久化仓
mem_session_log当前会话 JSONL

Tier B — 结构化检索(6 个)

工具必填可选返回
memory_searchquerytop_k(默认 5)、mode(bm25/vector/hybrid)、category{path, score, snippet, suspicious} 数组
memory_observecontenthinttypeobserved at notes/observed/<ulid>.md
memory_get_contextmax_tokens(默认 2048)最近修改文件的 markdown 预览,每条含 suspicious
memory_sessionslimit(默认 10)历史会话列表
memory_timelinesession_idlimit(默认 50)指定会话的工具调用时间线
mem_index_refresh强制重建 FTS5 索引

Tier C — 治理与版本(7 个)

工具必填可选返回
mem_snapshotname{id, name, created_at, size, backend}
mem_snapshot_listcreated_at 升序数组
mem_snapshot_restoreidrestored <id>
mem_loglimit(默认 20)、path{hash, summary, author, time} 数组(需启用 git)
mem_revertpathreverted <path> (commit <hash>)(需启用 git)
mem_consolidateconsolidation complete: N facts written
mem_compactcompacted N files to cold storage

主权与导入导出(13 个)

工具必填可选返回/说明
memory_abouttopiclimit(默认 10)按 topic 检索匹配记忆路径与 snippet
memory_auto_createdlimit(默认 20)自动提取事实列表(JSON 数组)
memory_consentaction(query/allow/deny)、scope(all/consolidation/capture)同意/撤回记忆操作
memory_forgettopicconfirm(默认 false=预览,true=删除)删除指定 topic 的记忆条目
mem_exportcategorysource导出记忆仓为 AMA JSON 字符串(不写文件)
mem_importjson_datastrategy(skip-existing/overwrite,默认 skip-existing)、dry_run(默认 false)从 AMA JSON 字符串导入记忆
memory_task_savetitlestatusprogressnext_stepsblockersfiles_modifieddecisionscontextid保存/更新任务,返回 task id(传 id 更新已有任务)
memory_task_liststatus(in-progress/blocked/done/cancelled)任务摘要 JSON 数组
memory_task_resumeid恢复任务上下文(格式化为新会话续作用)
memory_task_closeidreason关闭任务(标记 done)
memory_summaryrecent_limit(默认 10)记忆仓统计概览 JSON
memory_session_contextlimit会话启动上下文注入
mem_dream用户画像合成 JSON

错误码语义

MCP 错误码含义
-32601 METHOD_NOT_FOUND当前 profile 隐藏了该工具
-32602 INVALID_PARAMS缺参或类型错
-32603 INTERNAL_ERROR服务端故障
isError: true工具运行了但返回业务错误(路径不存在、被沙箱拒绝、大小超限等)

核心特性

文件形态记忆

Agent 用路径组织记忆,与人类文件系统模型一致:

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

命名空间内的目录结构:

~/.anolisa/memory/user-<uid>/ # mount root
├── README.md # 自动生成的概览
├── notes/ # 自由形态笔记
├── decisions/ # 用户自定义子目录
└── .anolisa/ # OS 管理,Agent 不可写
├── manifest.toml # 命名空间元数据
├── audit.log # JSONL 工具调用审计
├── index.db # FTS5 SQLite
├── snapshots/ # tar.gz 归档 + sidecar
├── trash/ # restore 时保留的旧条目
└── git/ # bare git 镜像(启用 git 后才有)

会话目录(tmpfs,权限 0700):

/run/anolisa/sessions/<sid>/
├── meta.toml
├── log.jsonl
└── scratch/ # 仅会话内草稿,通过 mem_promote 持久化

沙箱保护

每次文件打开通过内核级 openat2(RESOLVE_BENEATH \| RESOLVE_NO_SYMLINKS) 锚定 mount root:

  • 拒绝 .. 路径穿越
  • 拒绝符号链接(含调用中途的 symlink 替换;递归删除用 fdopendir + fstatat(AT_SYMLINK_NOFOLLOW) + unlinkat,让 swap 无法 race)
  • 拒绝访问元数据目录(.anolisa.git.gitignoreTargetIsReserved 拒绝)
  • mem_snapshot_restore 中 tar entry-type 过滤拒绝 Symlink/Hardlink/Device/Fifo
  • payload 超大按 max_*_bytes 配置拒绝

Mount 策略

策略适用行为
userland(默认)任意环境mount 仅普通目录,沙箱由 openat2 强制
usernsLinux ≥ 4.6 且允许 unprivileged user namespaceunshare 进入新 user+mount namespace,挂私有 tmpfs 再 bind-mount backing 目录;宿主侧进程看不到 /mnt/memory/<ns>/
auto运行时探测先尝试 userns,任何错误回退 userland

版本控制

可选自动 Git 提交(libgit2 vendored):

MEMORY_GIT_ENABLED=true MEMORY_GIT_AUTO_COMMIT=true agent-memory

启用后 mem_log 暴露变更历史,配合 mem_revert 给 Agent 一个真正的"撤销"按钮。mem_snapshot* 提供 mount 范围的 tar.gz 时间点备份,独立于 git。

全文搜索

SQLite FTS5 BM25 索引,亚毫秒级查询。后台 tokio 任务通过 inotify 监听 mount,事件经 200 ms debounce 聚合后在单事务中应用。分词器用 trigram(对 ≥3 字符的子串匹配友好)。trigram 分词器以 3 字符滑窗生成 token,因此少于 3 字符的查询词(如中文的"花名""小云")不产生任何 token,本会静默命中为空;memory_search 会识别这种情况并回退到 body LIKE '%term%' 子串扫描,确保短 CJK 查询仍能召回。IN_Q_OVERFLOW 时自动触发全量 rescan,不静默丢事件。

混合向量搜索

BM25 + 稠密向量混合检索,通过 RRF(Reciprocal Rank Fusion, k=60)融合排序。向量由可插拔 Embedding Provider 生成:

Provider配置方式说明
OpenAIMEMORY_EMBEDDING_BACKEND=openai + OPENAI_API_KEY调用 OpenAI Embeddings API
OllamaMEMORY_EMBEDDING_BACKEND=ollama + OLLAMA_BASE_URL本地 Ollama 实例

memory_search 支持 mode 参数:bm25(默认)/ vector(余弦相似度)/ hybrid(RRF 融合)。未配置 embedding 时 vector/hybrid 自动降级为 BM25,不报错。

自动 Consolidation

服务关闭时自动从会话审计日志中提取原子事实(mem_consolidate),使用 6 条启发式规则(零 LLM 调用)识别高频路径、搜索模式等行为特征并持久化为结构化记忆。也可通过 mem_consolidate 工具手动触发。含情景记忆提取与冲突检测(BM25 阈值)。

审计与可观测性

每次成功工具调用向 <mount>/.anolisa/audit.log 追加 JSONL,启用会话还写入 /run/anolisa/sessions/<sid>/log.jsonlaudit.journald=true 时 fan-out 到 systemd-journald,带结构化字段(MESSAGE_IDAGENT_MEMORY_TOOL 等),便于 journalctl --user-unit=anolisa-memory@<user> 过滤。


配置

配置文件

默认位置:~/.anolisa/memory.toml。该文件可选;文件不存在时,Agent Memory 使用内置默认值。所有 struct 启用 serde(deny_unknown_fields),配置项拼写错误 会直接导致加载失败。最小配置:

[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

环境变量

每个配置项都有对应 MEMORY_* 环境变量,优先级:env > config.toml > default

环境变量说明默认
USER_ID用户标识(校验;非法值 warn 后忽略)
MEMORY_PROFILE配置档位(basic/advanced/expert)advanced
MEMORY_BASE_DIR记忆仓根目录~/.anolisa/memory
MEMORY_SESSION_DIR会话根目录/run/anolisa/sessions
MEMORY_SESSION_ID固定当前会话 id(mem_promote 必须设)新生成 ses_<random>
MEMORY_SESSION_END会话结束动作(discard/keep)discard
MEMORY_MOUNT_STRATEGYmount 策略(auto/userland/userns)auto
MEMORY_MAX_READ_BYTES单次读取上限1 MiB
MEMORY_MAX_WRITE_BYTES单次写入上限16 MiB
MEMORY_MAX_APPEND_BYTES单次追加上限4 MiB
MEMORY_INDEX_ENABLED启用 FTS5 索引true
MEMORY_INDEX_TIME_DECAY_LAMBDA时间衰减系数(≥0)0.01
MEMORY_INDEX_TIME_DECAY_ALPHA时间权重占比(0–1)0.3
MEMORY_INDEX_COLD_AFTER_DAYS冷数据归档天数30
MEMORY_INDEX_EXCLUDE_COLD搜索排除冷数据true
MEMORY_AUDIT_JOURNALDfan-out 到 journaldfalse
MEMORY_CGROUP_ENABLED启用 cgroup 限制false
MEMORY_CGROUP_MEMORY_MAXcgroup 内存上限512M
MEMORY_GIT_ENABLED启用 git 版本控制false
MEMORY_GIT_AUTO_COMMIT自动提交true
MEMORY_EMBEDDING_BACKENDembedding 后端(none/openai/ollama)none
MEMORY_OPENAI_API_KEYOpenAI API key(空时回退 OPENAI_API_KEY
MEMORY_OPENAI_MODELOpenAI embedding 模型text-embedding-3-small
MEMORY_OLLAMA_MODELOllama embedding 模型nomic-embed-text
MEMORY_OLLAMA_BASE_URLOllama base URLhttp://localhost:11434
MEMORY_CONSOLIDATION_ENABLED启用自动 consolidationtrue
MEMORY_CONSOLIDATION_MAX_FACTS每次最多提取事实数20
MEMORY_CONSOLIDATION_MIN_CALLS最少调用次数门槛3
MEMORY_EPISODIC_ENABLED情景记忆提取true
MEMORY_MIN_EPISODE_STEPS情景最少步骤数3
MEMORY_MAX_EPISODES每会话最多情景数10
MEMORY_CONFLICT_DETECTION冲突检测true
MEMORY_CONFLICT_THRESHOLDBM25 冲突阈值-2.0

数据存储:~/.anolisa/memory/<namespace>/

Profile 含义

Profile 是 UX 提示而非安全边界,但在 tools/listtools/call 两层校验:

  • basic —— 37 个工具全部展示;弱模型也能用 Tier B 的结构化 API。
  • advanced(默认)—— 37 个工具全部展示;强模型应优先使用 Tier A 文件操作。
  • expert —— 隐藏 Tier B(memory_searchmemory_observememory_get_contextmem_consolidatememory_forgetmemory_consent),tools/call 调用会以 METHOD_NOT_FOUND 拒绝。熟练操作文件系统的前沿模型只需 Tier A 与 Tier C.

Embedding 配置

[memory.embedding]
backend = "openai" # 或 "ollama"
api_key = "" # 空时自动读 OPENAI_API_KEY 环境变量
model = "text-embedding-3-small"
# Ollama: backend = "ollama", model = "nomic-embed-text", base_url = "http://localhost:11434"

适用场景

  • Agent 跨会话持久化笔记和决策(Claude Code、Cursor、Continue、自研 rmcp 客户端等)。
  • 多 Agent 系统中 Agent A 写、Agent B 读的笔记跨进程共享。
  • 操作审计和状态恢复(mem_log、JSONL 审计、journald、mem_revertmem_snapshot_restore)。
  • "先草稿、决定后才持久化"的多回合模式(mem_promote 从 session scratch 原子移入持久化仓)。

SDK / 客户端开发指南

Python(官方 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 工作流(多回合模式)

  1. 每次 Agent 运行设 MEMORY_SESSION_ID=<sid>MEMORY_SESSION_DIR=/run/anolisa/sessions
  2. Agent 把草稿写到 /run/anolisa/sessions/<sid>/scratch/
  3. Agent 决定"这条值得保留"时调用 mem_promote 原子移入持久化仓。

功能测试与验证

自动化测试

cd src/agent-memory
cargo fmt --check
cargo clippy -- -D warnings
cargo test # 全部 suite
cargo test --test e2e_agent_test # 工具 E2E
cargo test --test mcp_integration_test # 协议层
cargo test --test linux_userns_test -- --ignored # 需 unprivileged userns
make smoke # 一键端到端冒烟

CI 跑 fmt --check + clippy -D warnings + cargo test,Rust 锁定 1.89。

交互式 mcp-harness

cargo run --example mcp-harness -- /tmp/mem-test
命令说明
list列出当前可见工具
call <tool> <json_args>调用工具
help帮助
quit退出

预置场景:--scenario full / git --git / promote / --verbose(打印 JSON-RPC)。

直发 JSON-RPC(协议级调试)

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

握手 + 工具调用:

{"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"}}}

沙箱越界验证

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

isError: true,消息 path outside mount root

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

isError: true,消息 target is reserved


故障排查

诊断工具

# 组件级诊断(按输出的 fix plan 手动处理)
anolisa doctor agent-memory

# 适配器状态
anolisa adapter status agent-memory

# 启动调试
RUST_LOG=agent_memory=debug agent-memory

常见问题

症状可能原因处理
启动报 unshare(NEWUSER|NEWNS): EPERMunprivileged user namespace 被禁sysctl kernel.unprivileged_userns_clone=1,或 MEMORY_MOUNT_STRATEGY=userland
tmpfs /mnt: EBUSY新 namespace 中 /mnt 已被占用重启进程
macOS / Windows cargo buildlibsystemd/nix宿主非 Linuxmake remote-build / remote-test
tools/call memory_searchMETHOD_NOT_FOUNDMEMORY_PROFILE=expert 隐藏 Tier B切回 advanced,或直接用 Tier A
配置项 typo 被悄悄忽略现已硬失败,看启动 stderr 报错并修正
mem_log 返回 [] 即使有写入git 版本控制未启用MEMORY_GIT_ENABLED=true MEMORY_GIT_AUTO_COMMIT=true
索引检索对刚写入的内容查不到还在 200 ms debounce 窗口内重试,或用 mem_grep(直接走文件系统正则,不依赖索引)
mem_promotesession not foundMEMORY_SESSION_ID/MEMORY_SESSION_DIR 未设或 scratch 不存在见 Promote 工作流
OpenClaw 插件未加载openclaw CLI 不在 PATH安装 OpenClaw 后重跑 install.sh
手动 dnf 操作后 system 状态不同步sudo anolisa --install-mode system repair agent-memory;仅在为仍存在的 RPM 重建记录时使用 system-scoped forget / adopt

深入排查:RUST_LOG=agent_memory=debug 启动,检查服务端 stderr 与 <mount>/.anolisa/audit.log


许可证:Apache-2.0 版本:0.2.1 文档版本:2.0(对齐 ANOLISA-design user-guide 结构)