CMDC 的设计就是 7 个互不重叠的抽象搭积木。读完本章你能在脑里画出整个调用图。
一、Agent — gen_statem 状态机
每个会话对应一个 OTP 进程,跑 :gen_statem 状态机,4 个状态:
idle ─prompt──▶ running ─stream chunk──▶ streaming ─tool calls──▶ executing_tools
▲ │
└────────── 工具批次完成 / abort / agent_end ───────────────────────┘外部代码不直接持 Agent struct,只通过 pid 或 session_id 字符串交互:
{:ok, session} = CMDC.create_agent(model: "anthropic:claude-sonnet-4-5")
CMDC.prompt(session, "...") # 异步 cast,立即返回
CMDC.steer(session, ref, "...") # 中段干预
CMDC.abort(session) # 中止
CMDC.status(session) # 状态快照
CMDC.stop(session) # 终止 Supervisor 树完整 API 矩阵见 CMDC,状态机详细行为见
Agent 状态机与事件。
二、Session — Supervisor Tree
create_agent/1 实际启动了一棵 Supervisor 树:
SessionServer (Supervisor :rest_for_one)
├── Agent (gen_statem)
└── SubAgent.Supervisor (DynamicSupervisor)
├── 子 Agent #1 (gen_statem)
└── 子 Agent #2 (gen_statem)
└── ...(孙 Agent 同样隔离)子 Agent crash 不影响父 Agent;父 Agent crash 由 SessionServer 决定是否 重启全树。Stop 一个 session = 停整棵树。
三、EventBus — 唯一的对外合约
Agent 内部所有"事情发生了"都通过 CMDC.Emitter 广播到
CMDC.EventBus,订阅者收到统一格式:
{:cmdc_event, session_id, event}事件类型分七大类(session / stream / tool / approval / subagent /
plugin_event / error),完整清单见 CMDC.Event。
消费方式:
| 场景 | API |
|---|---|
| 当前进程订阅 | CMDC.subscribe/2 |
| 重连补帧 | CMDC.subscribe(session, since: last_index)(需 event_buffer_size > 0) |
| Agent 崩溃监控 | CMDC.monitor/1 → {:cmdc_down, ref, sid, reason} |
四、Plugin — 切面拦截器
Plugin 实现 CMDC.Plugin behaviour,在 13 个 hook 中
返回 8 种 action 之一:
| 关键 hook | 用途 | 常用 action |
|---|---|---|
:session_start / :session_end | 资源初始化/清理 | :emit |
{:before_prompt, text} | 用户 prompt 进 Agent 前 | :intervene 改写、:abort 拦截 |
{:before_request, messages} | LLM 请求前 | :switch_model 路由模型 |
{:after_response, msg} | LLM 回复后 | :abort / :intervene |
{:before_tool, name, args} | 工具执行前 | :block_tool 拦截、:replace_tool_args 改参 |
{:on_tool_error, ...} | 工具失败 | :continue 重试、:abort 放弃 |
{:after_tool, name, call_id, result} | 单工具后 | :replace_tool_result 改 result |
{:before_compact, messages} | 压缩前 | 持久化关键事实 |
完整矩阵 + 5 个范例见 写一个 Plugin。
五、Tool — Agent 可调用的能力
Tool 是 LLM 视角的"函数",实现 CMDC.Tool behaviour:
@callback name() :: String.t()
@callback description() :: String.t()
@callback parameters() :: map() # JSON Schema
@callback execute(args :: map(), ctx :: CMDC.Context.t()) ::
{:ok, String.t()} | {:error, String.t()} | {:effect, term()}CMDC 内置 11 个:ReadFile / WriteFile / EditFile / Shell / Grep / ListDir / Glob / Task(子代理)/ WriteTodos / AskUser / CompactConversation。
文件类工具默认走 CMDC.Sandbox 代理,所有路径在
working_dir 内做边界校验。详见 写一个 Tool。
六、Backend / Sandbox — 文件与执行的抽象
CMDC.Backend 是文件 / 状态 / 远程存储的统一访问层
(10 callback:ls / read / write / edit / grep / glob / upload / download +
扩展的 execute / id),3 个内置实现:
| Backend | 用途 | 持久性 |
|---|---|---|
Backend.State | 内存 ETS,单测 / 短会话 | 进程级 |
Backend.Filesystem | 本地文件,CLI / 开发 | 跨进程 |
Backend.Composite | prefix 路由组合,生产推荐 | — |
CMDC.Sandbox 是 Backend 的子集 + 加 execute
(shell 命令)。Sandbox.Local 是默认实现。
典型生产配置(一个会话挂多套存储):
backend = CMDC.Backend.Composite.new(
default: CMDC.Backend.Filesystem.new(root_dir: "/tmp/work", virtual_mode: true),
routes: %{
"/memories/" => MyApp.PgBackend.new(), # 长期记忆
"/conversation_history/" => CMDC.Backend.State.new() # 短期 ETS
}
)
{:ok, session} = CMDC.create_agent(model: "...", backend: backend)七、Skill / Memory / Checkpoint — 三类持久化
CMDC 把"持久化"按时间尺度拆成三层,命名独立、互不重叠:
| 模块 | 时间尺度 | 用途 | 数据形态 |
|---|---|---|---|
CMDC.Skill | 永久 | 加载 SKILL.md 注入 system prompt | .md 文件 + frontmatter |
CMDC.Memory behaviour | 跨会话 | 语义记忆存储(store / search / similarity) | 由 backend 决定(ETS / Postgres + pgvector) |
CMDC.Checkpoint | 单会话快照 | BEAM 重启 / 跨设备恢复 | Snapshot struct 序列化 |
易混淆:
Backend是「文件接口」(read_file 之类)Memory是「语义记忆接口」(向量检索之类)Checkpoint是「会话快照」(save / load / list)
详见 CMDC.Backend / CMDC.Memory /
CMDC.Checkpoint 三个模块文档。
依赖关系一览
┌─────────────────┐
│ CMDC (facade) │
└────────┬────────┘
│
┌────────────┬───────┴────────┬────────────┐
▼ ▼ ▼ ▼
SessionServer EventBus Checkpoint Telemetry
│
┌────┴────┐
▼ ▼
Agent SubAgent.Supervisor
│
├── State / Stream / ToolRunner / Compactor ← @moduledoc false 私有
├── Plugin Pipeline
│ └── 16 内置 Plugin
├── Tool(11 内置)
│ └── Sandbox / Backend
├── Provider(req_llm 封装)
├── Skill / Memory / Blueprint
└── MCP(Bridge / Client / Supervisor)层级规则:只允许向下依赖,同层不互相依赖。
下一步
- 想看 Agent 状态机怎么转:Agent 状态机与事件
- 想写第一个切面:写一个 Plugin
- 想加个外部能力:写一个 Tool
- 想看常见组合:常见配方