id: ADR-005 title: 内存存储与后端无关,以 SQLite 作为默认实现 date: 2026-07-14 status: accepted relates-to:
- ADR-002
- docs/book/src/foundations/fnd-001-intentional-architecture.md
- docs/book/src/foundations/fnd-002-documentation-standards.md
- https://github.com/zeroclaw-labs/zeroclaw/issues/6850
- crates/zeroclaw-api/src/memory_traits.rs
- crates/zeroclaw-memory
- crates/zeroclaw-config/src/schema.rs
ADR-005:内存存储采用后端中立设计,默认使用 SQLite
这是对一种在正式 ADR 流程建立之前逐步演变形成的架构的追溯记录。本记录中没有确切的原始决策日期;上方日期是将此 ADR 添加到架构文档中的日期。
FND-002 最初将此决策描述为选择 SQLite 和 Markdown 作为两个记忆后端。该描述已不再能概括更广泛的契约。本记录描述的是持久架构,而不是固化后端数量。
上下文
ZeroClaw 需要在具有不同运行约束的多个安装环境中保持持久内存。本地单进程代理适合使用无需服务依赖的嵌入式存储。运维人员可能还需要人类可读的文件系统存储、共享数据库、向量数据库,或与其他内存系统的集成。
这些存储并不具有完全相同的模式或操作特性。它们仍然需要为记忆操作和作用域管理提供统一的运行时接口。各项操作可能具有特定于后端的能力语义;例如,Markdown 记忆只能追加,不能删除条目。轮次处理不得依赖具体的数据库类型,添加后端也不得要求将提示组装、整合、清理或代理授权策略复制到该后端中。
当前仓库识别 SQLite、Lucid、PostgreSQL、Qdrant 和 Markdown 存储,以及用于禁用持久化内存的 none。SQLite 是默认值。FND-001 单独将 SQLite 和 Markdown 标识为最终最小运行时所需的基线存储;该打包目标并不将存储契约限制为两种实现。
决策
内存持久化通过与后端无关的契约进行选择,默认后端为 SQLite。
存储契约
具体的存储实现了 zeroclaw-api 中的 Memory trait。该 trait 负责与后端无关的持久化操作和条目语义。调用方使用 Memory 句柄,而不是在轮次处理代码中逐一针对 SQLite、Markdown、PostgreSQL、Qdrant 或 Lucid 进行分支判断。
后端构造目前会查询两个存在重叠的配置层级。agents.<alias>.memory.backend 仅直接路由 Markdown 和 none 构造路径,并提供用于同后端共享验证的 kind。所有其他按代理配置的值都会通过安装范围的工厂处理,其中 memory.backend 选择具体类型的 storage.<kind>.<alias> 条目;旧版裸名称会解析为 default 别名。本 ADR 记录了这种交互,但并不将这种重叠视为理想的最终状态。运行时组件必须遵循当前工厂及验证职责的归属,而不是推断不受支持的按代理选择,或创建另一个持久化选择器。
SQLite 仍为默认选项,因为它提供持久化本地存储、混合检索能力,且无需外部服务。当其他后端的存储方式、部署方式、可读性或集成特性更符合需求时,可选用相应后端。none 是显式禁用持久化内存的请求,而非隐式回退。
更改任一选择器都不会迁移现有数据。在不同后端类型之间移动数据需要显式的迁移路径,而不是将一种存储静默地重新解释为另一种。
生命周期策略
存储后端不拥有提示词构建或轮次策略。轮次引擎拥有记忆上下文的选择与渲染。MemoryStrategy 是 Memory 句柄之上用于整合与治理的指定边界,但向该边界的迁移尚未完成:部分路径仍直接调用底层生命周期函数。Issue #6850 跟踪剩余的对齐工作。后端实现提供存储与检索行为,但不会成为这些生命周期规则的长期所有者。
代理范围
记忆独立于具体存储,按智能体身份进行作用域划分。基于 SQL 的存储可能使用内部 UUID,而非 SQL 存储则可能直接使用智能体别名。智能体作用域适配器会将后端绑定到智能体;只有通过配置的允许列表,并且仅当这些智能体使用同一后端时,才允许跨智能体检索。调用方不得绕过这些适配器,也不得推断特定后端的标识符具有相同的表示形式。
此 ADR 不决定哪些后端实现会随特定二进制文件一同发布,也不决定未来的后端是原生实现、通过 feature flag 控制还是由插件提供。这些属于打包和插件生命周期方面的决策。稳定的约束是:每个受支持的后端都必须遵守通用的存储和 agent 作用域契约。
后果
积极后果:
- Agent、channel、gateway 和工具代码可以依赖同一个内存接口。
- SQLite 提供了实用的本地默认选项,同时又不会让嵌入式存储成为唯一的部署模式。
- 运营商可以选择嵌入式、文件支持、共享数据库或向量存储,而无需更改轮次处理的调用方。
- 提示词的构建、整合和清理可以不断演进,而无需向每个存储实现添加生命周期策略方法。
- 无论后端采用何种特定的标识符和存储模型,Agent 隔离对调用方都提供统一的可见契约。
负面后果:
- 后端实现必须保留共享的条目和作用域契约,即使其存储和查询模型不同。
- 调用方必须考虑功能差异,例如仅追加存储以及报告不受支持或无操作行为的特征操作。
- 后端类型之间的迁移需要显式的数据迁移;更改已配置的后端不会使现有数据出现在新的存储中。
- 可选服务和功能会扩大验证矩阵,尽管大多数调用方只会看到共享 trait。
后续决定:
- Issue #6850 跟踪存储与更高层内存生命周期策略之间的边界。
- WASM 内存适配器目前还不是可配置的守护进程后端;其运行时构建和打包仍属于独立的插件工作。
- 对跨后端迁移、共享代理召回或存储标识的更改,都需要进行明确的兼容性评审。
参考文献
- ADR-002:基于 trait 的可扩展性
- FND-001: Intentional architecture
- FND-002:文档规范
- 运行时状态与持久化
- Issue #6850
crates/zeroclaw-api/src/memory_traits.rscrates/zeroclaw-config/src/schema.rscrates/zeroclaw-memory/src/backend.rscrates/zeroclaw-memory/src/lib.rscrates/zeroclaw-runtime/src/agent/memory_strategy.rs