CMDC.Agent.ToolRunner (cmdc v0.4.0)

Copy Markdown View Source

Agent 工具并发执行器 + 内建循环检测。

职责

  • 管理 tool call batch 的生命周期:spawn → collect → finalize
  • 工具执行崩溃隔离:单个工具 raise 不影响同批其他工具
  • 四个 Plugin Pipeline 嵌入点:
    • {:before_tool, name, args} — 工具执行前(可阻止或替换参数)
    • {:on_tool_error, name, call_id, error, attempt} — 工具执行失败、retry 前
    • {:after_tool, name, call_id, result} — 单个工具执行后
    • {:after_tool_batch, results} — 整批工具全部完成后

内建循环检测(吸收旧 LoopDetector Plugin)

三条检测路径,在 collect_result/4 中自动运行:

  1. 文件编辑频率 — 追踪 write_file/edit_file 对同一文件的编辑次数, 超过阈值先 intervene(注入提示词),再次超限则 abort
  2. 重复调用模式 — 滑动窗口检测连续相同工具调用
  3. 连续失败 — 连续 N 次工具调用失败时 intervene

执行流

execute_batch(tool_calls, state)
   for each tc:
       run_before_tool_pipeline  {:block_tool} | {:replace_args} | {:continue}
       spawn_tool_task(state, tc)
   (并发执行中...)
   工具执行失败  on_tool_error pipeline  retry / 放弃
   collect_result(ref, tc, result, state)
        run_after_tool_pipeline
        内建循环检测
        检查是否全批完成
        全批完成  run_after_tool_batch_pipeline  finalize_batch

返回约定

  • execute_batch/2 返回 {:next_state, :executing_tools, state}{:next_turn, state}(全部被 block/无效时直接跳过)
  • collect_result/4 返回 State.t() | {:next_turn, State.t()} | {:abort, State.t()} | {:intervene, String.t(), State.t()}

Summary

Functions

返回本轮可用的工具列表(已过滤 disabled_tools)。

取消所有进行中的工具任务(用于 abort)。

按 abort 模式取消进行中的工具任务(v0.2 RFC B6)。

收集单个工具任务的完成结果。

批量启动所有 tool call 的并发执行。

执行单个工具,崩溃隔离。

从工具列表中按名称查找工具模块。

获取循环检测配置,从 State.config 中读取。

获取工具 retry 配置,从 State.config 中读取。

Types

tool_result()

@type tool_result() :: {:ok, String.t()} | {:error, String.t()}

Functions

active_tools(state)

@spec active_tools(CMDC.Agent.State.t()) :: [module()]

返回本轮可用的工具列表(已过滤 disabled_tools)。

cancel_all(state)

@spec cancel_all(CMDC.Agent.State.t()) :: CMDC.Agent.State.t()

取消所有进行中的工具任务(用于 abort)。

cancel_for_abort(state, arg2, reason)

@spec cancel_for_abort(CMDC.Agent.State.t(), :all | :killable | :none, term()) ::
  {CMDC.Agent.State.t(), non_neg_integer()}

按 abort 模式取消进行中的工具任务(v0.2 RFC B6)。

Mode

  • :all — brutal_kill 所有任务(含 immune)
  • :killable — 仅杀非 interrupt_immune_tools 工具,与 Steering 行为一致
  • :none — 不杀任何任务,让其自然完成(state.pending_tool_tasks 不变)

每杀一个任务广播 {:tool_killed, %{name, call_id, reason}} 事件。 返回 {updated_state, killed_count}

collect_result(ref, tc, raw_result, state)

@spec collect_result(reference(), map(), term(), CMDC.Agent.State.t()) ::
  CMDC.Agent.State.t()
  | {:next_turn, CMDC.Agent.State.t()}
  | {:abort, CMDC.Agent.State.t()}
  | {:intervene, String.t(), CMDC.Agent.State.t()}

收集单个工具任务的完成结果。

自动执行:

  1. after_tool Pipeline
  2. Steering 检查(killable in-flight tool 立刻 brutal_kill;详见 steering-design.md
  3. 内建循环检测(文件编辑频率 + 调用模式 + 连续失败)
  4. 批次完成检查 → finalize_batch

返回:

  • State.t() — 还有任务未完成
  • {:next_turn, State.t()} — 整批完成(或 steering 已注入),进入下一 turn
  • {:abort, State.t()} — Pipeline/循环检测中止
  • {:intervene, String.t(), State.t()} — 循环检测注入提示词

execute_batch(tool_calls, state)

@spec execute_batch([map()], CMDC.Agent.State.t()) ::
  {:next_state, :executing_tools, CMDC.Agent.State.t()}
  | {:next_turn, CMDC.Agent.State.t()}
  | {:abort, CMDC.Agent.State.t()}

批量启动所有 tool call 的并发执行。

返回:

  • {:next_state, :executing_tools, state} — 有任务在运行
  • {:next_turn, state} — 没有可执行的工具(全部无效/被阻止),直接进入下一轮
  • {:abort, state} — Pipeline 中止

execute_tool(tool_mod, args, ctx)

@spec execute_tool(module() | nil, map(), map()) :: tool_result()

执行单个工具,崩溃隔离。

工具 raise 时捕获异常并返回 {:error, message}

find_tool(name, tools)

@spec find_tool(String.t(), [module()]) :: module() | nil

从工具列表中按名称查找工具模块。

loop_config(state)

@spec loop_config(CMDC.Agent.State.t()) :: %{
  max_edits_per_file: pos_integer(),
  min_repeat_pattern: pos_integer(),
  max_consecutive_failures: pos_integer()
}

获取循环检测配置,从 State.config 中读取。

支持的配置键:

  • :max_edits_per_file — 单文件最大编辑次数(默认 20)
  • :min_repeat_pattern — 触发重复模式检测的最小连续次数(默认 3)
  • :max_consecutive_failures — 触发连续失败警告的次数(默认 4)

tool_retry_config(state)

@spec tool_retry_config(CMDC.Agent.State.t()) :: %{
  max_retries: non_neg_integer(),
  delay_ms: non_neg_integer()
}

获取工具 retry 配置,从 State.config 中读取。

  • :tool_max_retries — 工具执行失败后最大重试次数(默认 0,0 = 不重试)
  • :tool_retry_delay_ms — 每次重试前的等待时间(默认 500ms)