Skip to main content

guides/concepts.md

# 核心概念

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 字符串交互**
```elixir
{: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`](CMDC.html),状态机详细行为见
[Agent 状态机与事件](agent-loop.html)
---

## 二、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.EventBus.html),订阅者收到统一格式:

```
{:cmdc_event, session_id, event}
```

事件类型分七大类(`session` / `stream` / `tool` / `approval` / `subagent` /
`plugin_event` / `error`),完整清单见 [`CMDC.Event`](CMDC.Event.html)
**消费方式**
| 场景 | 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`](CMDC.Plugin.html) 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](plugins.html)
---

## 五、Tool — Agent 可调用的能力

Tool 是 LLM 视角的"函数",实现 [`CMDC.Tool`](CMDC.Tool.html) behaviour:

```elixir
@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`](CMDC.Sandbox.html) 代理,所有路径在
`working_dir` 内做边界校验。详见 [写一个 Tool](tools.html)
---

## 六、Backend / Sandbox — 文件与执行的抽象

[`CMDC.Backend`](CMDC.Backend.html) 是文件 / 状态 / 远程存储的统一访问层
(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`](CMDC.Sandbox.html) 是 Backend 的子集 + 加 `execute`
(shell 命令)。`Sandbox.Local` 是默认实现。

**典型生产配置**(一个会话挂多套存储):

```elixir
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`](CMDC.Skill.html) | 永久 | 加载 SKILL.md 注入 system prompt | `.md` 文件 + frontmatter |
| [`CMDC.Memory`](CMDC.Memory.html) behaviour | 跨会话 | 语义记忆存储(store / search / similarity) | 由 backend 决定(ETS / Postgres + pgvector) |
| [`CMDC.Checkpoint`](CMDC.Checkpoint.html) | 单会话快照 | BEAM 重启 / 跨设备恢复 | `Snapshot` struct 序列化 |

**易混淆**
- `Backend` 是「文件接口」(read_file 之类)
- `Memory` 是「语义记忆接口」(向量检索之类)
- `Checkpoint` 是「会话快照」(save / load / list)

详见 [`CMDC.Backend`](CMDC.Backend.html) / [`CMDC.Memory`](CMDC.Memory.html) /
[`CMDC.Checkpoint`](CMDC.Checkpoint.html) 三个模块文档。

### v0.5 新增 facade API

- **[`CMDC.checkpoint!/2`](CMDC.html#checkpoint!/2)** — 从运行中 session 抓快照
  (内部走 `Agent.get_state` + `Checkpoint.save`,序列化策略自动剥离运行期字段)
- **[`CMDC.resume_session!/2`](CMDC.html#resume_session!/2)** — 从 snapshot 重建 session,
  续 prompt 时 LLM 看到完整历史,无需自己拼装
- **[`CMDC.Checkpoint.Snapshot.redact/2`](CMDC.Checkpoint.Snapshot.html#redact/2)**  backend 写前预处理 hook(接 Cloak / KMS / 任意脱敏函数)
- **[`CMDC.Plugin.Builtin.AutoCheckpoint`](CMDC.Plugin.Builtin.AutoCheckpoint.html)**  内置 Plugin,按 turn / on_tools / on_events OR 触发自动存档 + max_checkpoints / ttl_seconds GC,
  `CMDC.AsyncTaskSupervisor` 异步执行不阻塞 gen_statem

### v0.5 新增 Hibernate 配置

[`CMDC.Options.hibernate_after_ms`](CMDC.Options.html)Agent 进程空闲 N 毫秒后走 OTP 原生 `:hibernate_after` 自动 hibernate,
单进程 heap 8KB → 1.5KB(节省 ~80% 内存)。长会话多租户场景必备。

---

## 依赖关系一览

```
                    ┌─────────────────┐
                    │   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 状态机与事件](agent-loop.html)
- 想写第一个切面:[写一个 Plugin](plugins.html)
- 想加个外部能力:[写一个 Tool](tools.html)
- 想看常见组合:[常见配方](cookbook.html)