[0.1.2] - 2026-05-25

Patch release — 对齐 cmdc 0.6.0 CMDC.Memory behaviour 升级, 加入 tags :: [String.t()] 一等公民字段 + tags_any / tags_all 高效 检索操作符 + schema_version/0 callback。零 breaking,无需停机

Added

  • tags 一等公民字段cmdc_episodic_memories 表新增 tags :: text[] 列 (NOT NULL default {}),与 cmdc 主库 v0.6+ CMDC.Memory.entry() 标准对齐
  • tags_any / tags_all 检索操作符search/3filters 接受:
    • {:tags_any, ["a", "b"]} — entry.tags 与列表有任一交集(走 PG &&)
    • {:tags_all, ["a", "b"]} — entry.tags 全部包含列表元素(走 PG @>)
    • 空列表 {:tags_any, []} / {:tags_all, []} 视为跳过过滤
  • GIN 索引cmdc_episodic_memories(tags) 上的 GIN 索引让 tag 检索高效
  • c:schema_version/0 callback — 实现 cmdc 0.6+ Memory behaviour 可选 callback, 返回 1(tags 字段 default {},老数据零 ETL,不算 schema breaking)
  • store/3 自动写入 tagsdata[:tags] / data["tags"] 自动落到 tags 列; 支持 String.t() | [String.t()] | [atom()] 多种入参形态,normalize 为 [string]
  • row_to_entry 同步带回 tags — 检索结果含 :tags 字段(老 entry 自动为 [])

Migration(0.1.1 → 0.1.2)

无破坏性变化,直接升 hex 版本号即可。运行新增 migration:

$ mix ecto.migrate

migration 文件 priv/repo/migrations/20260525_add_tags_to_cmdc_episodic_memories.exs 自动:

  • cmdc_episodic_memoriestags text[] NOT NULL DEFAULT '{}'
  • 创建 GIN 索引 cmdc_episodic_memories_tags_index

老数据全部自动获得 tags = '{}',行为与"未传 tags 过滤"一致。 不需要业务代码改动,新代码可逐步引入 tags 检索:

# 写入时按需要带 tags
backend.store(nil, "ep-1",
  content: "...", user_id: "u-1", tags: ["domain:finance", "topic:tax"])

# 检索时按 tags 过滤
backend.search(nil, "tax",
  filters: [user_id: "u-1", {:tags_any, ["topic:tax"]}])

Compatibility

  • 老代码零改动 — store/3 不传 :tags 时落 [];search/3 不传 tags_* filter 时返回行为不变
  • row_to_entry 一定带 :tags 字段(空 entry 也是 [])。如下游业务代码用 Map.fetch!(entry, :tags) 会触发(老 entry migration 后也有 []);若有 下游代码 key in Map.keys(entry) 类逻辑会感知新键

[0.1.1] - 2026-05-25

Patch release — 多租户隔离安全修复 + 集成方反馈采纳。 行为变化提示:默认行为从"未传 user_id 时返全表"修复为"返空列表"。

Fixed — 多租户数据隔离(安全)

  • EpisodicMemoryBackend.search/3 / similarity_search/3 — 未传 filters[:user_id] 时的行为修复:
    • v0.1.0 缺陷:代码 maybe_filter_user(query, nil), do: query 不过滤, 返回全表所有租户的 episodic 记录(注释声称"避免误泄"但实际未做)
    • v0.1.1 修复:新增 :on_missing_tenant opt 控制行为
      • :return_empty(默认)— 返回 {:ok, []} 强制空结果
      • :return_error — 返回 {:error, :missing_tenant_filter} 严格模式

Added

  • :on_missing_tenant optsearch/3 / similarity_search/3 的 opts 中接受

Migration(0.1.0 → 0.1.1)

强烈建议多租户集成方采取以下步骤:

  1. 代码 review:确保所有调用都显式传 filters[:user_id]
  2. 升级到 0.1.1 后短期可保持默认 :return_empty 行为(返空兼容,不报错)
  3. 验证业务流程后,逐步迁移到 opts[:on_missing_tenant] = :return_error, 让漏传 user_id 的代码路径在测试期就暴露

与 cmdc 主库的关系:cmdc 0.5.4 同步在 Plugin.Builtin.EpisodicMemory 新增 :require_user_id: true opt 形成"Plugin 层 + Backend 层"双重防护; 配合使用最佳实践:

{EpisodicMemory, memory_module: CMDCMemoryPg.EpisodicMemoryBackend,
 memory_store: nil, require_user_id: true}    # Plugin 层
# backend 调用时透传 on_missing_tenant: :return_error                # Backend 层

Compatibility

  • 默认行为变化:返"全表"→ 返"空列表";单租户场景(只有 1 个 user_id)无影响
  • 多租户场景:这是修复,先前行为是安全 bug,升级后应严格 review 代码

[0.1.0] - 2026-05-18

首发版本 — 与 cmdc 主库 0.5.0 协同发布。

Added

  • CMDCMemoryPg.Repo — Ecto Repo for cmdc_memory_pg
  • CMDCMemoryPg.CheckpointBackend — 实现 CMDC.Checkpoint.Backend 4 callback
    • snapshot 序列化走 :erlang.term_to_binary(snap, [:compressed]) 写入 bytea
    • 复用 cmdc 主库 CheckpointBackend.ETS 同套测试 suite
  • CMDCMemoryPg.EpisodicMemoryBackend — 实现 CMDC.Memory 5 callback
    • 与 cmdc 主库 Plugin.Builtin.EpisodicMemory 直接对接
    • user_id namespace 隔离多租户
    • v0.1 similarity_search/3 降级为 ILIKE 文本匹配(pgvector 留 v0.2)
  • Ecto migration 2 张表:cmdc_checkpoints + cmdc_episodic_memories
  • docker-compose.yml — Postgres 16 alpine 测试用
  • 完整 README + Cloak encryption 集成示例

v0.1 范围说明(明确不含)

  • ❌ pgvector 真语义检索(v0.2)
  • ❌ 3-tier Memory(Working / Semantic / Procedural)— 留 v0.2
  • ❌ Composite 路由 backend(cmdc 主库 Backend.Composite
  • ❌ KV jsonb backend(v0.2)
  • ❌ Cloak encryption 强制集成(提供 Snapshot.redact/2 hook 给集成方)

Migration

新引入,无 migration。配置 + mix ecto.migrate 即可使用。