把 CMDC 当作一个会话级 Agent 引擎:调一次 create_agent/1 拿到 pid, 之后所有交互都通过这个 pid 异步推进,事件经 EventBus 广播给订阅者。


安装

def deps do
  [
    {:cmdc, "~> 0.4"}
  ]
end

获取依赖:

mix deps.get

Hello World

最少 4 个调用就能完成一次完整对话——创建 → 提问 → 收回复 → 关闭:

{:ok, session} = CMDC.create_agent(
  model: "anthropic:claude-sonnet-4-5",
  api_key: System.get_env("ANTHROPIC_API_KEY")
)

CMDC.prompt(session, "你好,请用一句话介绍 Elixir 的优势。")

{:ok, reply} = CMDC.collect_reply(session)
IO.puts(reply)

CMDC.stop(session)

prompt/2 是非阻塞的——它返回后 LLM 调用才开始;collect_reply/2 同步等 拿到本轮所有 assistant 文本拼接的字符串。需要边收边渲染请改用 subscribe/1


流式订阅

订阅当前进程接收 {:cmdc_event, session_id, event} 消息:

{:ok, session} = CMDC.create_agent(model: "anthropic:claude-sonnet-4-5")

CMDC.subscribe(session)
CMDC.prompt(session, "数到 5。")

stream_loop = fn loop ->
  receive do
    {:cmdc_event, _sid, {:message_delta, %{delta: text}}} ->
      IO.write(text)
      loop.(loop)

    {:cmdc_event, _sid, {:agent_end, _msgs, _usage}} ->
      :done
  after
    30_000 -> {:error, :timeout}
  end
end

stream_loop.(stream_loop)
CMDC.stop(session)

完整事件清单见 CMDC.Event。订阅断网想补帧请用 Options.event_buffer_size 开启 ring buffer。


带工具的 Agent

CMDC 内置 11 个工具(读写文件 / shell / grep / glob / ls / 子代理派发 / todo 跟踪 / 用户提问 / 上下文压缩 / 大结果分页)。声明 :tools 即可让 Agent 自主调用:

{:ok, session} = CMDC.create_agent(
  model: "anthropic:claude-sonnet-4-5",
  tools: [
    CMDC.Tool.ReadFile,
    CMDC.Tool.WriteFile,
    CMDC.Tool.Shell,
    CMDC.Tool.Grep
  ],
  working_dir: "/tmp/agent-workspace"
)

CMDC.prompt(session, "读取 /tmp/agent-workspace/notes.md,把 TODO 抽出来写到 todos.md")
{:ok, reply} = CMDC.collect_reply(session)

Shell / WriteFile 默认走 Sandbox.Local,所有文件路径限制在 working_dir 内(见 Sandbox)。生产环境强烈建议接 Backend.Filesystem

  • virtual_mode: true

用 Plugin 加切面

Plugin 是把"切面逻辑"插入 Agent 13 个生命周期 hook 的标准方式:

defmodule MyApp.AuditPlugin do
  @behaviour CMDC.Plugin

  @impl true
  def init(_opts), do: {:ok, %{count: 0}}

  @impl true
  def priority, do: 100

  @impl true
  def handle_event({:before_tool, name, args}, state, _ctx) do
    IO.puts("[审计] 调用工具: #{name}, 参数: #{inspect(args)}")
    {:continue, %{state | count: state.count + 1}}
  end

  def handle_event(_event, state, _ctx), do: {:continue, state}
end

{:ok, session} = CMDC.create_agent(
  model: "anthropic:claude-sonnet-4-5",
  tools: [CMDC.Tool.Shell],
  plugins: [{MyApp.AuditPlugin, []}]
)

CMDC 自带 16 个内置 Plugin(见 Plugins(安全与控制) 和 Plugins(优化与记忆)分组),覆盖审批 / 安全 / 内容拦截 / 长会话记忆 / 模型路由 / 大结果 offload / 反思 / 规划等典型场景。


中段干预(Steering)

发现方向不对想换条路?不需要 abort 重来——直接 steer/2 软中断:

{:ok, session} = CMDC.create_agent(
  model: "anthropic:claude-sonnet-4-5",
  tools: [CMDC.Tool.Shell]
)

CMDC.prompt(session, "帮我用 100 行 Python 实现快排。")

# 中途改主意
ref = make_ref()
CMDC.steer(session, ref, "停,改成用 Elixir 实现,别用 Python。")

{:ok, reply} = CMDC.collect_reply(session)

正在执行的不可中断工具会跑完,可中断的工具被 brutal_kill,下一轮 LLM 调用看到合并后的新 prompt。完整选项见 CMDCsteer/2CMDC.Agent 的状态行为表。


HITL 审批

让人类在 Agent 调危险工具前点头:

{:ok, session} = CMDC.create_agent(
  model: "anthropic:claude-sonnet-4-5",
  tools: [CMDC.Tool.Shell],
  plugins: [
    {CMDC.Plugin.Builtin.HumanApproval, [tools: ["shell"]]}
  ]
)

CMDC.subscribe(session)
CMDC.prompt(session, "在 /tmp 下创建一个 hello.txt 文件。")

receive do
  {:cmdc_event, _sid, {:approval_required, %{id: id, tool: "shell", args: args}}} ->
    IO.inspect(args, label: "Agent 想跑")
    CMDC.approve(session, id)  # 或 CMDC.reject(session, id)
end

{:ok, reply} = CMDC.collect_reply(session)

approve_always 可把同类命令加入 session 白名单,详见 CMDC.Plugin.Builtin.HumanApproval


下一步

跑通上面五段你已经覆盖了 80% 的日常用法。继续深挖请看: