- 人工智能
- AI 应用
- 交互助手
- AI Agent
【免费下载链接】ironclaw
IronClaw is an Agent OS focused on privacy, security and extensibility
导读
本文围绕 IronClaw 开源仓库中的crates/extensions/packages/memory-native包展开,该包是 IronClaw(一款聚焦隐私、安全与可扩展性的 Agent OS)内置的默认[memory]提供者,以文件系统为后端完整实现了跨会话的持久记忆能力。读完本文,你将掌握 memory-native 的整体架构(契约层与实现层的边界)、/memory虚拟路径与四元组作用域规则、读写/搜索/树形浏览/档案设置五个模型工具的配置语义、混合搜索与提示词写入安全引擎的实现原理,以及如何用cargo test -p ironclaw_memory_native验证这套内存系统的契约一致性。
一、包定位:为什么需要内置的 memory 提供者
IronClaw 把"记忆"抽象为 provider-neutral 的契约ironclaw_memory::MemoryService,而memory-native是随二进制默认捆绑、默认激活的实现(扩展 ID 为ironclaw.memory),保证记忆能力开箱即用,无需任何安装/启用步骤。
从 crates/extensions/packages/memory-native/README.md 可以看到它的关键定位:
- Surfaces:
[memory]提供者 + 5 个模型工具(ironclaw.memory.read/.write/.search/.tree/.profile_set); - Vendor(凭据权威):无——不声明任何
[auth.*]配方,因为它是 first-party 原生实现,不需要第三方凭据; - Runtime:
first_party,与保留的ironclaw.*ID 一起,只有在宿主注册了对应的native_memory_provider服务时才被接受(见 manifest.toml 的注释); - 部署约束:每个部署同时只激活一个
[memory]提供者,备选实现是../mem0包;切换后端不需要改动任何工具。
值得一提的依赖反转例外:crates/kernel/ironclaw_host_runtime/src/memory_native_extension.rs持有一个常规依赖(其捆绑内存包的构建器),README 明确记录这是 §8.2"只有提供者包与二进制可以命名 memory 提供者"规则的例外,并被 PROPOSAL §6.8.4 记录为待迁移的端口反转而非迁移本身。
二、分层架构:契约在 ironclaw_memory,实现在 memory-native
AGENTS.md(crates/extensions/packages/memory-native/AGENTS.md)是本文档的核心骨架,它首先强调"provider-neutral 契约"的归属:MemoryServicetrait、DTO、scope/path/context 值类型、prompt-safety 词汇、审计/事件契约都住在 crates/domains/ironclaw_memory,本 crate 只负责实现并再导出。
这种"契约下沉、实现外置"的结构带来两个直接好处:
- 提供者可替换:mem0 包同样实现同一套
MemoryService契约,共享的契约一致性测试套件同时跑两个提供者,契约一旦变动,两边都必须保持通过(AGENTS.mdValidation一节); - 依赖面收窄:本 crate 只依赖
ironclaw_memory、ironclaw_filesystem、ironclaw_safety、ironclaw_host_api四个包(见 Cargo.toml),明确禁止依赖 mem0 包、任何 HTTP 客户端、ironclaw_extension_host、ironclaw_extension_manager以及任何 kernel/product crate——这是 AGENTS.mdGuardrails的硬性边界。
从 src/lib.rs 的公开导出可以看到本 crate 真正"拥有"的组件面:
| 领域 | 关键类型 |
|---|---|
| 文档仓库 | MemoryDocumentRepository、FilesystemMemoryDocumentRepository、InMemoryMemoryDocumentRepository |
| 后端适配 | MemoryBackend、RepositoryMemoryBackend、MemoryBackendCapabilities |
| 分块与哈希 | ChunkConfig、chunk_document、MemoryChunkWrite |
| 索引器 | MemoryDocumentIndexer、ChunkingMemoryDocumentIndexer、MemoryChunkReplaceOutcome |
| 搜索 | MemorySearchRequest、MemorySearchResult、FusionStrategy |
| 安全 | DefaultPromptWriteSafetyPolicy |
| 服务门面 | NativeMemoryService、MEMORY_GUIDANCE、MEMORY_CURATION_PASS_PROMPT等资产常量 |
三、/memory 虚拟路径语法与四元组作用域
memory-native 在宿主解析好的作用域之上工作。/memory是 crates/extensions/packages/memory-native/src/path.rs 中注册的VIRTUAL_ROOT,路径语法为:
/memory/tenants/{tenant}/users/{user}/agents/{agent}/projects/{project}/{path}解析逻辑ParsedMemoryPath::from_virtual_path要求至少 7 段,支持两种合法形态(见 src/path.rs):
- 带 agent:
.../agents/{agent}/projects/{project}/{path...}; - 不带 agent(agent 为 None):
.../projects/{project}/{path...}。
关键语义点:
_none是虚拟路径层哨兵:路径段中的_none会被转换为None(agent_id/project_id),但它永远不被持久化存储——存储层用空字符串作为 agent/project 的"缺席哨兵",因为MemoryDocumentScope会拒绝空字符串 ID,所以空串作为存储哨兵是安全的(AGENTS.mdGuardrails);- 保留后缀命名空间:用户文档路径不得以
.meta、.chunks、.versions结尾,避免与仓库的 sidecar 后缀命名空间冲突(见 src/path.rs 的rejects_path_segments_ending_in_reserved_sidecar_suffixes测试); - 作用域过滤不可绕过:每一次 read/list/search/write/version/chunk 操作都按完整
(tenant_id, user_id, agent_id, project_id)四元组过滤,禁止从路径前缀推断项目作用域;文档唯一性约束为UNIQUE (tenant_id, user_id, agent_id, project_id, path)。
此外,path.rs 中还有一层防泄漏设计sanitize_memory_backend_reason:当后端错误信息包含 SQL 关键字(sql、sqlite、libsql)、主机端口(host=、port=)、绝对路径(/tmp/、/home/)等敏感标记时,统一替换为 "memory backend operation failed",避免把后端内部细节泄露到外部错误面。
四、文档仓库与后端:插件化存储,fail-closed 能力声明
4.1 双层抽象:Repository 与 Backend
MemoryDocumentRepository(src/repo/mod.rs)定义文档的读写/原子追加/原子替换/元数据/列表/搜索原语,并提供默认的 fail-closed 实现(不支持的操作返回memory backend does not support ...错误);RepositoryMemoryBackend(src/backend.rs)把仓库包装成宿主可调用的MemoryBackend,在其上叠加作用域守卫、prompt-write 安全、元数据解析、schema 校验、事件上报与索引器联动。
MemoryBackendCapabilities是一组布尔能力声明(file_documents、metadata、versioning、prompt_write_safety、full_text_search、vector_search、embeddings、graph_memory、delete、transactions),AGENTS.md 强调它是强制输入:不支持的 file/search 行为在产生后端副作用之前就 fail closed。例如RepositoryMemoryBackend::search在请求 full-text 而能力未声明、请求 vector 而能力未声明时,都会先返回错误再触碰仓库(见 src/backend.rs)。
4.2 持久化策略:Filesystem 是唯一部署目标
AGENTS.md 明确:持久化是单一的FilesystemMemoryDocumentRepository,叠在RootFilesystem之上;InMemoryMemoryDocumentRepository只是测试支撑,绝不是部署目标。libSQL/Postgres 等后端特有的行为覆盖属于ironclaw_filesystem的后端契约测试,本 crate 的测试只针对内存后端来验证内存文档语义(版本化、chunk 替换、元数据级联、混合搜索融合)。
4.3 原子追加与乐观并发
写入路径上有多层并发控制:
compare_and_append_document_with_options/compare_and_write_document_with_options返回Appended/Conflict、Written/Conflict结果;append_document_with_backend_options采用"读-算哈希-比较追加"的乐观循环,最多重试 8 次,超限报 "memory document changed during append; retry limit exceeded";profile_set与patch_document同样在最多MAX_MEMORY_PATCH_RETRIES = 8次内做 read-modify-write 的哈希比对。
这保证了对同一文档的并发写不会静默覆盖:冲突会显式暴露并由调用方重试。
五、分块、内容哈希与索引器
5.1 分块配置
src/chunking.rs 中的ChunkConfig移植自工作区现有 chunker,以保证记忆索引保持既有搜索召回行为:
| 参数 | 默认值 | 说明 |
|---|---|---|
chunk_size | 800 | 以词为单位的块大小,with_chunk_size强制>= 1 |
overlap_percent | 0.15 | 相邻块重叠比例,with_overlap收敛到[0.0, 0.5] |
min_chunk_size | 50 | 尾块不足此词数时与上一块合并 |
chunk_document按空白切词,不足chunk_size时整体返回;超过时按步长chunk_size - overlap滑动切块,末尾不足min_chunk_size的残块会与前一块合并,避免产生无意义碎片。
5.2 内容哈希
content_sha256/content_bytes_sha256从ironclaw_memory再导出,是原子追加/写入的乐观锁基础,也是版本化与"chunk 替换"的比对依据。
5.3 向量搜索:诚实的能力边界
AGENTS.md 特别强调:本 crate 不存在 embedding-provider 端口,向量搜索只在预先提供的 embeddings下激活。RepositoryMemoryBackend::search保留了 fail-closed 信息:
- 请求向量搜索但后端未声明
vector_search→ "memory backend does not support vector search"; - 声明了
vector_search但请求未携带query_embedding→ 若embeddings能力为 false 则 "memory backend does not support embedding generation",否则 "memory backend cannot generate query embeddings"。
也就是说:搜索路径是full-text only(原生后端的build_native_backend只声明full_text_search = true),任何向量请求都会在触碰仓库前失败。这是源码注释明确记录的"端口曾有零实现而被删除"的事实。
六、混合搜索:FTS + 向量经 RRF 融合
src/search.rs 定义了搜索请求/结果与融合逻辑:
MemorySearchRequest关键参数与默认值:limit = 20(上限MAX_LIMIT = 1000)、pre_fusion_limit = 50(上限MAX_PRE_FUSION_LIMIT = 5000)、full_text = true、vector = true、fusion_strategy = Rrf、rrf_k = 60、min_score = 0.0、full_text_weight = 0.5、vector_weight = 0.5。with_limit会联动重夹取pre_fusion_limit,保证不变式pre_fusion_limit >= limit;FusionStrategy:Rrf(倒数排名融合,工作区默认)与WeightedScore(加权排名分融合);- 融合细节:同一文档路径的 FTS 命中与向量命中会合并为一个结果槽(
full_text_rank/vector_rank各取最小排名);先归一化再过滤——原始 RRF 分很小(k=60 时榜首约 0.016),若直接拿调用方给的min_score = 0.5过滤会把所有结果清空,因此实现先除以最大分归一化到 [0,1] 再按min_score保留,测试rrf_min_score_filters_normalized_scores_not_raw_rrf_values专门锁定了这个契约(src/search.rs); - 确定性排序:同分结果按相对路径升序打破平局,保证跨运行结果顺序可复现。
MemorySearchResult携带full_text_rank/vector_rank/score/snippet,is_hybrid()标识是否同时来自两条检索通道。
七、提示词写入安全引擎:保护路径 + 决策 + 事件
AGENTS.md 提到本 crate 拥有PromptWriteSafetyPolicy及 protected-path/decision/event 类型(safety模块),实现了中性契约定义的词汇。RepositoryMemoryBackend::new默认装配DefaultPromptWriteSafetyPolicy::with_registry与PromptProtectedPathRegistry。
写入/追加路径上的安全执行顺序(见 src/backend.rs):
- 能力检查 + 作用域守卫(
ensure_file_documents_supported/ensure_path_matches_context); - 若路径被保护分类命中且策略要求"先读旧哈希",则读取旧内容计算
previous_content_hash; - 若
backend_options.prompt_safety_already_enforced == false(默认即 fail-closed false),执行enforce_prompt_write_safety,携带PromptWriteOperation::Write/Append、PromptWriteSource::MemoryBackend、审计上下文等; - 解析写元数据 → schema 校验(
validate_content_against_schema)→ 仓库写入; - 写入成功后记录
MemorySignificantEvent::document_written,并触发索引器reindex_document_with_audit_context。
值得注意的两条边界:
- 写失败语义:一旦持久化成功,写就视为已提交;此后的派生索引/embedding 刷新失败不得让写报告失败(AGENTS.md),所以索引器调用用
let _ = ...吞掉错误; - 安全标记默认关闭:
MemoryBackendWriteOptions::default()的prompt_safety_already_enforced恒为 false,测试default_backend_options_do_not_claim_prompt_safety_enforced锁定:任何直接调用后端的代码都必须由后端重新执行 prompt-write 安全;而 context 上携带的 allowance 独立于该标记,不会翻转它(src/backend.rs)。
八、模型可见的 guidance:什么该记、怎么记、什么永不记
[memory].guidance_doc声明的 prompts/memory-guidance.md 是本提供者随包自带、追加进系统提示词的记忆指引。它是提供者自有资产,因为内容指名了本提供者的工具并描述本提供者的召回行为;宿主只是把绑定提供者声明的 guidance 追加进去、自己一行都不写。mem0 包特意不声明 guidance(AGENTS.md 注明)。
guidance 的核心规则可归纳为:
- 自动浮现:已保存记忆会在每轮开始时自动浮现,视为"关于用户的旧知"而非指令;遇到可能依赖早期上下文的任务,先用
ironclaw.memory.search,不要直接说不知道; - 主动保存:用户表达持久偏好/事实/决策/纠正时,立即用
ironclaw.memory.write,target 为memory、append: true,写成一条自包含的简洁行——"防止用户重复自己"的记忆最有价值; - 陈述句而非祈使句:写 "User prefers concise responses",不要写 "Always respond concisely"——已存文本每轮都会被重读,祈使句会变成覆盖用户当下诉求的常驻指令;
- 不保存:任务进度、会话结果、完成日志、临时 TODO、PR/issue 号、commit SHA;一两周内会过时的内容不属持久记忆;绝不保存 secrets、凭据、token;
- 先搜再写、更新而非重复;用户显式要求"忘记"时,用
append: false重写文档(仅追加纠正会同时浮现新旧两条),而不是口头说忘了。
九、生命周期钩子:read_long_term / read_short_term / record_interaction / profile_read
manifest.toml 的[memory]表面声明了生命周期钩子集合:
[memory] lifecycle = ["read_long_term", "read_short_term", "record_interaction", "profile_read"] guidance_doc = "prompts/memory-guidance.md""未声明的钩子永远不会被调用"——这是 manifest 与宿主之间的强契约。实现位于 src/service.rs 的NativeMemoryService:
9.1 read_long_term:常驻 MEMORY.md 前缀 + FTS 命中
AGENTS.md 用专门小节强调"始终开启的read_long_term精选前缀"(#7185):本提供者把常驻的MEMORY.md文档放在自己长期通道的最前面,先于全文检索命中,且与当前轮查询无关。原因在于全文搜索只在当前消息与已存事实共享词汇时才能命中——新开一个无关话题的会话,已存偏好就不可见了。
实现细节(src/service.rs):
- 预算
MAX_CURATED_SNIPPETS = 4,防止常驻文档吃掉调用方全部max_snippets额度、饿死后面的搜索命中; - 每个精选块原始字节上限
CURATED_CHUNK_RAW_BYTES = 400,因为宿主把模型可见 snippet 上限设为 512 字节并跑 prompt 黑名单,块内切分保证模型看到的每个字节都经过与搜索命中相同的检查——携带黑名单秘密的行单独被丢弃,而不是连累整个文档; - 截断标记为纯文字
" (truncated)"(括号与分隔符会被宿主的 safe-summary 规则拒绝);块内行用"; "连接(原始换行是控制字符,会被宿主净化剥掉); - 精选前缀之后,FTS 命中会排除
threads/子树(保持两通道不相交)与MEMORY.md自身(它已在通道头部,避免同一文档占用第二个槽位)。
9.2 read_short_term:线程作用域的 run-local 通道
短程通道只检索活动线程的记忆子树:thread_id由可信宿主运行上下文携带在 invocation scope 上,永远不由模型提供;没有活动线程就降级为空。检索用ranked_in_scope_results先过量抓取(fetch_limit = max_snippets * 8且至少 64)再按线程前缀过滤,防止全局 top-N 挤掉线程局部命中。
9.3 record_interaction:逐轮转录的幂等记录
after-turn 记录器把完整轮次历史逐字写入threads/<thread_id>/<turn_run_id>.md(overwrite 模式),路径以turn_run_id(provenance)命名,使重跑已Completed的 run 覆盖同一文件而非无限追加——幂等。threads/命名空间是保留的:公开write工具拒绝任何threads/前缀目标(否则会成为"长期通道不可见 + 非活动线程的短程通道也不可见"的检索黑洞),只有受信任的记录器通过write_reserved_document写入。
9.4 profile_read / profile_set:私密本地档案
档案键控在人类用户上(agent=None, project=None),落在context/profile.json。profile_set只接受timezone(IANA 名)、locale(BCP-47)、location(自由标签)三个字符串字段,且是私有本地写入,与builtin.trace_commons.profile_set无关(见 manifest.toml 的工具描述)。
十、工具表面与 origin 门控
manifest 声明 5 个模型工具,输入/输出 schema 内联服务自捆绑资产文件(单一事实源),origin_gate_matrix保留了它们作为builtin.memory_*时的门控:
| 工具 | effects | default_permission | loop_run 门控 | 说明 |
|---|---|---|---|---|
ironclaw.memory.read | read_filesystem | allow | ungated | 读当前作用域记忆文档 |
ironclaw.memory.write | read_filesystem,write_filesystem | allow | gated_unless_granted | 任意路径写入,需门控授权 |
ironclaw.memory.search | read_filesystem | allow | ungated | 仅搜内部持久记忆 |
ironclaw.memory.tree | read_filesystem | allow | ungated | 树形列出记忆文档 |
ironclaw.memory.profile_set | read_filesystem,write_filesystem | allow | ungated | 记录时区/语言/位置 |
关键安全语义:缺失矩阵不等于"无门控"——S4 authorize fold 对每个带 origin 标记的调用 fail-closed 为 Forbidden;product与automation对全部工具默认forbidden。也就是说这些工具只对 LoopRun(模型驱动回合)开放,产品/自动化通道默认拒绝。
十一、自声明的定期整理:memory curation pass
manifest 中还有一个值得展开的设计(#7664):提供者为自己声明 recurring upkeep——每完成 10 轮(interval_turns = 10)按所有者执行一次整理:
[[memory.scheduled_ops]] trigger = "after_turn" interval_turns = 10 pass = { prompt = "prompts/memory_curation.md", tools = ["ironclaw.memory.read", "ironclaw.memory.search", "ironclaw.memory.write"], max_model_calls = 10 }整理工作流为:重读常驻文档 → 合并表达相同内容的条目 → 解决已被取代的条目 → 输出报告。它声明在 manifest 里,是因为"这份工作属于本提供者":prompt 描述的是本提供者的文档形态、选用的是本提供者的工具。宿主只拥有时钟、调用信封与权威;工具 ID 是从本 manifest 的[[tools]]中选择的,且每次调用仍走正常的能力授权。max_model_calls = 10是本 pass 自身预算(低于宿主上限)——#7770 实测显示真实模型会额外消耗调用(一次失误的读、一次多余的写),需要余量才能发出报告。
十二、测试与验证:共享契约套件
AGENTS.md 的Validation给出三层验证路径:
- 快速本地检查:
cargo test -p ironclaw_memory_native——契约套件位于tests/(memory_service/memory_backend/memory_filesystem/repo_*),实际文件为 tests/memory_service_contract.rs、tests/memory_backend_contract.rs、tests/memory_filesystem_contract.rs、tests/repo_filesystem_contract.rs、tests/repo_in_memory_contract.rs; - 边界检查(依赖/API 变更后):
cargo test -p ironclaw_architecture_tests; - 共享一致性:
MemoryService一致性套件同时也跑在 mem0 包上——契约变化时两个提供者必须同时保持通过。
Cargo.toml 中test-supportfeature 的设计细节值得注意:契约测试脚手架(src/contract_tests.rs)含有.expect/.unwrap/assert*!调用,属于有意的测试代码,必须通过 feature 门控关闭,避免出现在生产构建里触发 scripts/check_no_panics.py 扫描器;本 crate 的集成测试通过自 dev-dependency 开启该 feature。同时rt-multi-threadfeature 是竞态安全测试#[tokio::test(flavor = "multi_thread", worker_threads = 2)]所必需的——没有它,宏会静默回退到 current-thread 调度器,tokio::join!协作式轮询将掩盖对replace_document_chunks_if_current的真实抢占式竞争(PR #3180 invariant 6)。
十三、开发边界与契约变更流程
AGENTS.md 最后给出协作规则,对想为该项目贡献的读者有直接指导意义:
- 编辑边界:保持编辑在本 crate 内,除非契约明确要求改动相邻 crate;
- 测试偏好:当 helper 门控 dispatch、持久化、网络、secrets、审批、资源、事件或进程副作用时,优先写调用方级测试;
- 契约冲突处理:如果契约与代码不一致,停下来,把任务当作"契约变更请求"处理,而不是静默改变所有权——这条规则同时保护了契约层的权威性和实现层的自由度。
结语
memory-native 是一个"小而克制"的参考实现:契约全部下沉到ironclaw_memory,本 crate 只保留文件系统文档系统、路径语法、分块/索引、混合搜索、提示词写入安全与模型 guidance 这些真正属于自己的部分;能力声明 fail-closed、作用域四元组全过滤、错误信息脱敏、写提交与索引刷新解耦,共同构成了它的安全与一致性底座。对于想理解 IronClaw 记忆体系(或移植一个自研记忆后端)的读者,从 AGENTS.md 出发,对照 manifest.toml 的工具表面与 src/service.rs 的生命周期实现,再跑一遍 tests/ 下的契约套件,即可完整还原这套持久记忆架构的运作全貌。
- 人工智能
- AI 应用
- 交互助手
- AI Agent
【免费下载链接】ironclaw
IronClaw is an Agent OS focused on privacy, security and extensibility
相关推荐
IronClaw memory-native 详解:默认 `[memory]` 提供者的文件系统记忆后端实现
IronClaw memory native 详解:默认 memory 提供者的文件系统记忆后端实现 导读 memory native 是 IronClaw(A
人工智能AI 应用交互助手AI Agentomo-senpi Memory 组件深度解析:Letta-Code 风格持久化 Agent 记忆的架构、配置与实现
omo senpi Memory 组件深度解析:Letta Code 风格持久化 Agent 记忆的架构、配置与实现 导读 本文以 omo senpi( pac
人工智能AI Agent代码智能体多智能体MCP ClientsAgent 编排IronClaw 记忆契约层深度解析:provider-neutral 的 MemoryService 接口、`/memory` 路径语法与 Prompt 写安全边界
IronClaw 记忆契约层深度解析:provider neutral 的 MemoryService 接口、 /memory 路径语法与 Prompt 写安全
人工智能AI 应用交互助手AI Agent
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考