本章只列用户实际会被坑的兼容边界——不是新增的可选字段,是写错代码会报错或行为变化的位置。 新增字段、新增可选参数、文档改写等"无影响"的变化请看 Changelog。
v0.5.x → v0.6.0
主要为加性升级,1 处 breaking 改动(Sandbox.Local virtual_mode 默认从
false → true,提供 opt-in 回退)。其他全部向后兼容。
v0.6.0 新增(零兼容影响)
CMDC.EventBus.subscribe_group/1—— 多 Agent 协调,按group_id一次性聚合订阅CMDC.Options.group_id—— 字段默认nil,不传完全兼容 v0.5CMDC.Provider.Registry.register/2:resolver_fnopt —— Vault / Cloak 懒解密注入点CMDC.Memory.entry():tags :: [String.t()]—— 一等公民字段(默认[])CMDC.Memory.ETS:tags_any/:tags_allfilter 操作符 —— 高效 tag 检索CMDC.Memoryc:schema_version/0—— 可选 callback,未实现的老 backend 默认认为是1CMDC.update_plugin_opts/3—— Plugin 长会话内热更(idle flush only)CMDC.AuditEvent.from_event/215 核心事件 +from_telemetry/312 事件
Memory backend 升级注意(v0.5 keyword filters 永久保留)
v0.5 时代 Memory.search/3 的 filters 接受 [{key, value}] 等值过滤。v0.6
完全保留该行为,语义恒为"AND 等值",没有引入 {:and, [...]} / {:or, [...]}
等结构化 DSL evaluator(让 backend 实现复杂度可控)。
新增的 {:tags_any, [...]} / {:tags_all, [...]} 与老 keyword filters 可同时
在同一调用混用,按 AND 组合:
# v0.5 写法(v0.6 完全等价)
CMDC.Memory.ETS.search(mem, "", filters: [{:user_id, "u-1"}])
# v0.6 推荐写法(结合 tags)
CMDC.Memory.ETS.search(mem, "",
filters: [{:user_id, "u-1"}, {:tags_any, ["domain:finance"]}])不需要的代码改动:
- ❌ v0.5 的
filters: [{:user_id, "u-1"}]在 v0.6 不需要改写 - ❌ 现存 entry 没有
:tags字段,v0.6 的 ETS backend 在search/3时tags_*filter 对其视为"无 tag",不会报错
需要的代码改动:
- ✅ 自定义 Memory backend 实现需要新加
def schema_version, do: 1(可选, 未实现走function_exported?回退路径) - ✅
cmdc_memory_pg集成方需运行新增 migrationmix ecto.migrate加tags列 + GIN 索引(升 0.1.1 → 0.1.2 唯一变动,详见 cmdc_memory_pg CHANGELOG)
Sandbox.Local virtual_mode 默认 true(唯一 breaking)
CMDC.Sandbox.Local 的所有 IO 函数(read_file/2 / write_file/3 / edit_file/4 /
list_dir/2 / file_exists?/2 / grep/3 / glob/3)v0.6 起接受 :virtual_mode opt,
默认值从隐式 false(v0.5 老行为)翻转为 true。
:virtual_mode true(默认,新行为)拦截:
- path 含
".."(如"../etc/passwd") - path 以
"~"开头(如"~/.ssh/id_rsa") - 绝对路径解析后不在
:working_dir内(如/etc/passwd,working_dir=/tmp)
违反返 {:error, "路径越界(virtual_mode 防护...)"} 字符串。file_exists?/2
在越界路径上返 false(不泄漏外部文件系统结构)。
为什么这么改:v0.5 行为对开发 / CLI / 单机 Agent 是合理的,但Web 服务器
/ 不可信负载 / 在线托管多用户 Agent 等场景下,LLM 一旦被 prompt injection
诱导生成 read_file("../../../etc/passwd") 就能拿到任意系统文件。
v0.6 默认翻转把"路径在 working_dir 内"作为安全基线,符合现代 Agent
runtime 的预期。
v0.5 → v0.6 影响判断:
| v0.5 代码场景 | v0.6 影响 | 推荐处理 |
|---|---|---|
| 始终用相对路径 + working_dir 内文件 | 零影响(推荐路径) | 不动 |
偶尔用 "../foo" / "~/cache" | {:error, ...} | 1 行 opt-out(见下)/ 重设 working_dir |
| 跨多个目录访问系统文件 / CLI 工具 | {:error, ...} | 显式传 virtual_mode: false |
| 完全自管路径解析(已自验证) | {:error, ...} | 显式传 virtual_mode: false(强烈不推荐) |
显式 opt-out(v0.5 兼容路径,1 行迁移):
# v0.5 老代码(隐式 virtual_mode=false 行为)
CMDC.Sandbox.Local.read_file("../parent.txt", working_dir: dir)
# v0.6 行为变化:默认拦截
{:error, "路径越界(virtual_mode 防护..."}
# v0.6 显式 opt-out 回退到 v0.5 行为
CMDC.Sandbox.Local.read_file("../parent.txt",
working_dir: dir,
virtual_mode: false # ← 1 行 opt-out
)
# 推荐:重设 working_dir 为公共祖先(不需要 opt-out)
CMDC.Sandbox.Local.read_file("parent.txt",
working_dir: Path.dirname(dir)
)virtual_mode: false 显式传入时,每个 Process 第一次会触发 Logger.warning/1,
提示用户考虑重设 working_dir 为公共祖先目录。后续调用静默。
v0.7+ 计划::virtual_mode opt 保留至少一个 minor cycle,不计划完全移除
(合理跨目录访问场景永远存在,如 CLI 开发工具 / 日志读取 / 系统监控)。
自定义 CMDC.Sandbox 实现:本变化仅影响默认 Sandbox.Local,自定义 behaviour
实现完全不受影响。Sandbox.Local 内部新增的 virtual_mode 防护不进 behaviour
contract(不强制其他实现支持)。
v0.5.x → v0.5.3(当前版本)
全加性升级,零代码 / 零行为破坏。所有 v0.5.x 代码不动即可跑通。
v0.5.3 新增
CMDC.Plugin.Builtin.SkillGuard—— Skillallowed_tools白名单 enforcer Pluginskills = CMDC.Skill.discover([{:project, "./.cmdc/skills"}]) CMDC.create_agent( model: "anthropic:claude-sonnet-4-5", tools: [CMDC.Tool.ReadFile, CMDC.Tool.Grep, CMDC.Tool.WriteFile, CMDC.Tool.Shell], plugins: [ {CMDC.Plugin.Builtin.SkillGuard, skills: skills, active: ["elixir-testing"], enforce_mode: :strict} # 或 :warn ] )并集语义:多 active Skill 的
allowed_tools取并集;任一未设白名单即不限制。 显式 opt-in,不挂载就零行为变化。3 份 observability 配方 (
docs/recipes/observability/):- Langfuse OTLP / LangSmith OTel / Grafana Tempo + Loki + PromEx
- 把 18 个
:telemetry事件接到主流可观测性栈
v0.5.2 新增
CMDC.Skill.t()加:source字段 ——:base | :user | :project | :team | :custom | nilSkill.discover/1多源标签重载:Skill.discover([ {:base, "priv/skills"}, {:user, "~/.cmdc/skills"}, {:project, "./.cmdc/skills"} ])老
discover([dir])调用形式零行为变化(:source = nil)。CMDC.Provider.Registry.Broadcaster.PG配套 handler 协议测@tag :distributed可选双节点 :pg 集群验收测
v0.5.1 新增
CMDC.Provider.Registry—— 多租户命名 Provider Profile 寻址中心:ok = CMDC.Provider.Registry.register("tenant-A", provider: "anthropic", opts: [api_key: "sk-...", base_url: "https://litellm.a.internal"]) CMDC.create_agent(model: "registry:tenant-A:claude-sonnet-4-5") # 等价于 CMDC.create_agent(model: "anthropic:claude-sonnet-4-5", # provider_opts: [api_key: "...", base_url: "..."])- hot-path
lookup/1≤ 1 µs (ETS read) - 跨节点同步通过
Broadcasterbehaviour 解耦(默认 PG / 可接 Phoenix.PubSub) - profile name 不能含
:(register/2返{:error, {:invalid_name, :contains_colon}}) resume_session!/2遇 profile 缺失返{:error, {:registry_profile_missing, name}}
- hot-path
v0.5 系列新增子库
cmdc_eval ~> 0.1(v0.5.3 同窗口)—— Agent benchmark harness- Suite behaviour + Internal/BFCL 内置 suite + Mix.Tasks.Cmdc.Eval CLI
defp deps, do: [{:cmdc, "~> 0.5.3"}, {:cmdc_eval, "~> 0.1"}]$ mix cmdc.eval --suite=internal --model="anthropic:claude-sonnet-4-5" --report=out.jsonl
v0.4.x → v0.5.0
全加性升级,零代码 / 零行为破坏。所有 v0.4.x 代码不动即可跑通。
新增公共 API(向后兼容,按需采用)
CMDC.Options.hibernate_after_ms— 进程空闲超时自动 hibernate,单进程 heap 8KB → 1.5KBCMDC.checkpoint!/2+CMDC.resume_session!/2— facade API 把运行中 session 抓快照 + 跨进程恢复CMDC.Checkpoint.Snapshot.redact/2— backend 写前预处理 hook(接 Cloak / KMS)CMDC.Plugin.Builtin.AutoCheckpoint— 内置 Plugin,按 turn / on_tools / on_events 自动存档 + 自动 GCCMDC.AsyncTaskSupervisor— application supervision tree 新增,供 plugin 异步任务用CMDC.Telemetry从 6 事件扩到 16 事件(10 个新事件覆盖 Plugin Pipeline / Compactor / Checkpoint / SubAgent / Hibernate)
长会话场景推荐配置
如果你的 Agent 是常驻多租户场景(>100 idle 会话同节点):
{:ok, session} = CMDC.create_agent(
model: "anthropic:claude-sonnet-4-5",
hibernate_after_ms: 60_000, # 新增
plugins: [
{CMDC.Plugin.Builtin.AutoCheckpoint, # 新增
backend: CMDCMemoryPg.CheckpointBackend,
every_n_turns: 10,
on_events: [:approval_required, :session_end]}
]
)持久化场景新增子库 cmdc_memory_pg 0.1.0
defp deps do
[
{:cmdc, "~> 0.5"},
{:cmdc_memory_pg, "~> 0.1"} # 可选 PG backend
]
end
# config/runtime.exs
config :cmdc, :checkpoint_backend, CMDCMemoryPg.CheckpointBackend提供 Checkpoint + EpisodicMemory 持久化 PG 后端,与主库 CMDC.Checkpoint.Backend / CMDC.Memory behaviour 完全对接。
测试场景新增子库 cmdc_test 0.1.0
defp deps do
[
{:cmdc, "~> 0.5"},
{:cmdc_test, "~> 0.1", only: :test}
]
end提供 4 大测试 helpers:
MockProviderBuilder APIPlugin.run_hook/3单元测 +Plugin.Spy集成路径 inject anonymous handlerEventCapture+Assertions.{assert_event_emitted, refute_event_emitted, assert_event_count}
无升级动作。直接改 mix.exs 依赖到 ~> 0.5 即可。
v0.4.0 → v0.4.1
纯文档 patch,零代码 / 零行为变更。
- 主库
mix.exsextras 重组、内核模块加@moduledoc false、清理 hex doc 内部字眼 - 1219 tests + 21 doctests 不变
无升级动作。
v0.3.x → v0.4.0
新增公共模块(向后兼容,纯增量)
CMDC.Backendbehaviour +Backend.{State, Filesystem, Composite}三个内置实现CMDC.Checkpoint+Checkpoint.Backendbehaviour + ETS / DETS 两个内置 backendCMDC.Telemetry标准:telemetry事件契约- 4 个新内置 Plugin:
LargeResultOffload/ContentPolicy/EpisodicMemory/MemoryFlush
旧代码不需要改任何东西就能拿到这些能力——按需挂载即可。
Plugin Pipeline 新 action :replace_tool_result
仅 :after_tool hook 接受。让 plugin 在 raw_result 写进 message history
之前替换它(LargeResultOffload 用这个 action 把 200KB 结果换成
preview)。
无破坏性影响——旧 plugin 完全不受影响。
HumanApproval 加 :approve_always 第三态
# v0.3:approve(session, id) 只放当次
CMDC.approve(session, id)
# v0.4:可加 :kind 走 session-scoped 永久白名单
CMDC.approve(session, id, kind: :approve_always)旧代码 CMDC.approve(session, id) 默认 :approve_once,行为不变。
Sandbox virtual_mode(推荐生产开启)
Backend.Filesystem.new/1 支持 :virtual_mode 选项:
# v0.3 行为(默认 false,0 安全保护)
backend = CMDC.Backend.Filesystem.new(root_dir: "/tmp/work")
# v0.4 推荐生产配置
backend = CMDC.Backend.Filesystem.new(
root_dir: "/tmp/work",
virtual_mode: true # 拦 .. / ~ traversal + O_NOFOLLOW symlink 防护
)virtual_mode: true 会拒绝路径逃逸 root_dir,有可能让原本依赖
绝对路径的代码失败。如果你的 Agent 之前依赖访问 root_dir 外的文件
(不推荐),开启前要先改造业务代码。
未来 v0.5 默认值会切换为 true,到时是 breaking change。
v0.2.x → v0.3.0(1 条 breaking change)
#1 公共 API 全部改 {:ok, _} | {:error, _} 返回(breaking)
v0.2 的 CMDC.monitor / abort / attach_tool / detach_tool /
status / messages / agent_pid / steer / stop / switch_model /
replace_tools / attach_tools / detach_tools 在传非法 session 时会 raise。
v0.3 起改为返回 {:error, :invalid_session | :not_alive}。
迁移:把所有 CMDC.xxx(session, ...) 改为 with 链或 case 匹配:
# v0.2 写法(v0.3 仍能跑,但只在 session 合法时工作)
CMDC.attach_tool(session, MyTool)
# v0.3 推荐写法
case CMDC.attach_tool(session, MyTool) do
{:ok, _name} -> :ok
{:error, :invalid_session} -> handle_dead_session()
{:error, {:validation_failed, failures}} -> handle_invalid_tool(failures)
end成功路径的返回值(:ok / map / Message struct 等)保持不变,所以多数业务
代码只需要在最外层套个 case 即可。
abort/2 :reason 接受 string
v0.2 只接受 atom,v0.3 起接受下列 6 个标准 string 自动归一为 atom,防止 前端通过 JSON 反序列化注入任意 atom 进 BEAM atom table:
"user_cancelled" / "timeout" / "shutdown" /
"budget_exceeded" / "permission_denied" / "provider_error"其他 string 一律归并为 :unknown 并 Logger.warning。Atom 入参保持原样
透传。
pending_tools 加 started_at_ms 字段
CMDC.status/1 返回的 pending_tools 列表每项新增 started_at_ms 字段
(System.system_time(:millisecond))。如果你之前对 pendingtools 做了
strict map match `%{name: , callid: , args: _}`,会失败:
# v0.2 strict match
%{name: name, call_id: id, args: args} = tool
# v0.3 兼容写法
%{name: name, call_id: id, args: args, started_at_ms: _} = tool
# 或更稳妥:
name = tool.namePlugin emit 自动注入 user_data
emit 出来的 {:plugin_event, name, payload} 当 payload 是 map 时,Pipeline
会自动 merge state.user_data 到 :user_data 字段。如果你的订阅方做了
strict map match 不期待 :user_data,要么改 match,要么在 plugin 给
payload 加 :_no_user_data opt out。
:after_turn 新 hook
新增 {:after_turn, payload} Plugin hook,每 turn 回 idle 前触发(finish
- abort 双路径)。比
:session_end触发更频繁,新 plugin 推荐用它写 审计 / 长期记忆 / 计费等。
旧 plugin 不受影响。
attach_tools / detach_tools / replace_tools 批量原子 API
新增三个批量 API。dry-run + 全回滚语义:任一失败全部不动。单次的
attach_tool/2 / detach_tool/2 行为完全不变,可继续用。
EventBus replay 加 :types 白名单
subscribe/2 加 :types 选项:
# v0.3 起:只 replay stream / agent_end 类事件
{:ok, _} = CMDC.subscribe(session, since: 100, types: [:message_delta, :agent_end]):since 一直存在;:types 是新增可选项。
v0.1.x → v0.2.0
{:agent_end, messages, token_usage} 第三参数改 struct
v0.1.x 的 token_usage 是 plain map(%{prompt_tokens, completion_tokens, ...}),
v0.2 起统一为 %CMDC.TokenUsage{} struct:
# v0.1.x 兼容写法
%{total_tokens: tt} = usage
# v0.2 推荐写法(也兼容 v0.1 字段名)
%CMDC.TokenUsage{total_tokens: tt, cost_usd: cost} = usage字段名保留 prompt_tokens / completion_tokens / total_tokens(OpenAI 行业
事实标准),同时归一化 Anthropic 风格的 input_tokens / output_tokens。
Steering 软中断(新功能)
新增 CMDC.steer/2 公开 API + 3 个新事件 :steering_received /
:steering_applied / :tool_skipped_for_steering。
SubAgent prompt_mode 默认改 :task
v0.1 子代理用完整 BasePrompt(同主 Agent)。v0.2 起 SubAgent 默认 prompt_mode: :task(精简),节省 30-50% system prompt token。
如果你的子代理依赖完整 BasePrompt 行为:
%CMDC.SubAgent{
name: "...",
prompt_mode: :full # 显式指定回 v0.1 行为
}MemoryFlush Plugin(新功能)
新增 CMDC.Plugin.Builtin.MemoryFlush,在压缩前把关键事实持久化到
MEMORY.md,下次会话由 MemoryLoader 自动加载回 system prompt——解决
长会话失忆问题。
Ring Buffer + replay(新功能)
Options.event_buffer_size > 0 启用 per-session 事件 ring buffer,
subscribe(session, since: idx) 重连补帧。默认 0 = 关闭,零内存开销。
完整变更摘要
各版本完整 changelog 见仓库 CHANGELOG.md。