Memory behaviour 接口 — 可插拔的语义记忆存储。
定义 Agent 经验记忆的通用接口,支持多种存储后端:
CMDC.Memory.ETS— 内存存储(开发/测试)CMDCMemoryPg.EpisodicMemoryBackend— PostgreSQL 持久化(生产推荐)
Entry 结构
存入的 entry 为 map,必填字段:
:id :: String.t()— 调用方指定的唯一标识符:content :: String.t()— 主体文本(用于搜索匹配):inserted_at :: DateTime.t()— 插入时间戳(backend 自动注入):tags :: [String.t()]— 标签列表,v0.6+ 一等公民字段,默认[]
其余字段自由扩展(如 :task、:outcome、:user_id、:source)。
Backend 实现需在 store/3 时自动注入缺省字段(:id / :inserted_at / :tags)。
Tags 字段(v0.6+ 一等公民)
:tags 是后端一等公民索引列——backend 实现应为该字段建专用索引(PG GIN
/ ETS 二级索引),让 search/3 的 :tags_any / :tags_all filter 高效执行。
老 entry 写入时省略 :tags 字段,backend 应自动落 [],搜索时 tags_* filter
对其等同于"无任何 tag"。
典型用例:
- Anthropic Agent Skills 标准 frontmatter 字段对接(
tags: [data-analysis, sql]) - 多领域记忆隔离(
tags: ["domain:finance", "year:2026"]) - 检索时按场景筛选(
filters: [{:tags_any, ["domain:finance"]}])
Schema Version(v0.6+)
Backend 可选实现 schema_version/0 返回正整数,标识其 entry shape 版本。
Backend 引入新必填字段 / 改变字段语义时应升版本号。集成方可通过
resolve_schema_version/1 安全读取(未实现 callback 的老 backend 默认返 1)。
Filter 操作符(v0.6+)
search_opts.filters 支持两种形式(同一调用混用合法,按 AND 组合):
| 形式 | 语义 | 示例 |
|---|---|---|
{key :: atom(), value} | entry[key] == value(v0.5 行为,永久保留) | {:user_id, "u-1"} |
{:tags_any, [tag1, tag2, ...]} | entry.tags 与列表有任一交集 | {:tags_any, ["sql"]} |
{:tags_all, [tag1, tag2, ...]} | entry.tags 全部包含列表元素 | {:tags_all, ["sql", "v1"]} |
{:tags_any, []} / {:tags_all, []} 视为跳过该过滤(所有 entry 通过),
方便业务条件构造("如果有 tag 选项就传入,没有就不过滤")。
Summary
Callbacks
按 id 删除一条记忆 entry,id 不存在时也返回 :ok。
返回所有记忆 entry,按插入时间降序排列。
返回 backend 的 entry shape 版本号(v0.6+)。
按文本关键词搜索记忆 entry(大小写不敏感)。
按语义相似度检索(ETS 降级为关键词匹配)。
存储一条记忆 entry。
Functions
安全读取 backend 的 schema_version,未实现的老 backend 默认返 1。
Types
@type entry() :: %{ :id => entry_id(), :content => String.t(), :inserted_at => DateTime.t(), :tags => [tag()], optional(atom()) => term() }
记忆 entry 标准形态。
v0.6+ 起 :tags 是一等公民字段(默认 []),与 :id / :content /
:inserted_at 同等地位。其他字段自由扩展。
@type entry_id() :: String.t()
search_opts.filters 支持的过滤项。
{key :: atom(), value}— 等值匹配entry[key] == value(v0.5 行为){:tags_any, [...]}— entry.tags 与列表有任一交集(v0.6+){:tags_all, [...]}— entry.tags 全部包含列表元素(v0.6+)
@type search_opts() :: [ limit: pos_integer(), order: :desc | :asc, filters: [filter()] ]
@type store() :: term()
@type tag() :: String.t()
Callbacks
按 id 删除一条记忆 entry,id 不存在时也返回 :ok。
返回所有记忆 entry,按插入时间降序排列。
@callback schema_version() :: pos_integer()
返回 backend 的 entry shape 版本号(v0.6+)。
当 backend 引入新必填字段 / 改变字段语义时应升版本号, 让集成方在 schema migration 期间能据此判断是否需要 ETL。
实现为可选 callback;未实现的老 backend 默认认为是 1。
调用方应通过 CMDC.Memory.resolve_schema_version/1 安全读取。
@callback search(store(), String.t(), search_opts()) :: {:ok, [entry()]} | {:error, term()}
按文本关键词搜索记忆 entry(大小写不敏感)。
@callback similarity_search(store(), String.t(), search_opts()) :: {:ok, [map()]} | {:error, term()}
按语义相似度检索(ETS 降级为关键词匹配)。
存储一条记忆 entry。
id 为调用方指定的唯一标识符,data 必须包含 :content 字段。
Backend 实现应在缺省时自动注入 :tags(默认 [])和 :inserted_at。
Functions
@spec resolve_schema_version(module()) :: pos_integer()
安全读取 backend 的 schema_version,未实现的老 backend 默认返 1。
示例
iex> CMDC.Memory.resolve_schema_version(CMDC.Memory.ETS)
1