cmdc_memory_pg

CMDC PostgreSQL backend — Checkpoint + EpisodicMemory 持久化。

让 cmdc Agent 在 BEAM 节点重启 / 跨设备 / 跨进程的场景下,完整保留对话上下文 + 情景记忆

v0.1 范围(严控二件套

模块实现 behaviour用途
CMDCMemoryPg.CheckpointBackendCMDC.Checkpoint.BackendAgent 会话快照持久化(CMDC.checkpoint!/2 后端)
CMDCMemoryPg.EpisodicMemoryBackendCMDC.Memory情景记忆 few-shot 持久化(与 Plugin.Builtin.EpisodicMemory 对接)

v0.1 明示不含

安装

defp deps do
[
{:cmdc, "~> 0.5"},
{:cmdc_memory_pg, "~> 0.1"}
]
end

配置

# config/runtime.exs
config :cmdc_memory_pg, CMDCMemoryPg.Repo,
database: "cmdc_prod",
username: System.fetch_env!("PGUSER"),
password: System.fetch_env!("PGPASSWORD"),
hostname: System.get_env("PGHOST", "localhost"),
port: String.to_integer(System.get_env("PGPORT", "5432")),
pool_size: 10
# 设为 CMDC.Checkpoint 默认 backend
config :cmdc, :checkpoint_backend, CMDCMemoryPg.CheckpointBackend

启动

defmodule MyApp.Application do
def start(_type, _args) do
children = [
CMDCMemoryPg.Repo,
# ... 其他子进程
]
Supervisor.start_link(children, strategy: :one_for_one)
end
end

Migration

$ mix ecto.create
$ mix ecto.migrate

migration 创建 2 张表:

用途
cmdc_checkpointsSnapshot bytea 存储(:erlang.term_to_binary([:compressed]))+ 索引 (session_id, checkpoint_id)
cmdc_episodic_memories情景记忆(按 user_id namespace 隔离)+ 索引 (user_id, episode_id)

使用

1. Checkpoint 持久化

# 抓快照
{:ok, snap} = CMDC.checkpoint!(session)
# 跨 BEAM 恢复
{:ok, snap} = CMDC.Checkpoint.load("sess-prod-001")
{:ok, new_session} = CMDC.resume_session!(snap)

无需指定 backend — 配置 :cmdc, :checkpoint_backend 后默认走 PG。

2. 情景记忆 few-shot

# 配置 EpisodicMemory Plugin 用 PG backend
{:ok, session} =
CMDC.create_agent(
model: "anthropic:claude-sonnet-4-5",
user_data: %{user_id: "alice"},
plugins: [
{CMDC.Plugin.Builtin.EpisodicMemory,
memory_store: :ignored,
memory_module: CMDCMemoryPg.EpisodicMemoryBackend}
]
)
# 成功对话自动写入;下次同用户类似 query 自动 few-shot 加载

与 Cloak 集成(可选 encryption at rest)

cmdc 主库提供 CMDC.Checkpoint.Snapshot.redact/2 helper — 集成方在 wrapper backend 层接 Cloak:

defmodule MyApp.EncryptedCheckpointBackend do
@behaviour CMDC.Checkpoint.Backend
@impl true
def save(sid, snap, opts) do
sanitized = CMDC.Checkpoint.Snapshot.redact(snap, &MyApp.Vault.encrypt/1)
CMDCMemoryPg.CheckpointBackend.save(sid, sanitized, opts)
end
@impl true
def load(sid, opts) do
case CMDCMemoryPg.CheckpointBackend.load(sid, opts) do
{:ok, snap} ->
decrypted = CMDC.Checkpoint.Snapshot.redact(snap, &MyApp.Vault.decrypt/1)
{:ok, decrypted}
other -> other
end
end
# list / delete 透传
defdelegate list(sid, opts), to: CMDCMemoryPg.CheckpointBackend
defdelegate delete(sid, opts), to: CMDCMemoryPg.CheckpointBackend
end

测试

测试套件分两层:

$ docker compose up -d
$ mix ecto.setup
$ mix test --include pg

不带 --include pg 时跳过 PG 集成测,便于纯单元 CI。

v0.2 路线图

优先级说明
pgvector embedding 检索P0替换 ILIKE,让 similarity_search/3 真实语义匹配
Working Memory backendP1session 短期 KV 存储(区别于 Episodic 长期)
Composite 路由配方P1在 cmdc 主库 Backend.Composite 之上提供推荐配置模板
Cloak ecto_field encryptionP2字段级加密预设(不强制)

License

Apache 2.0