AtomCode Agent 执行引擎与回合循环分析
持续深度分析 — Agent Loop 状态机、回合生命周期、执行控制 2026-06-23
1. Agent 回合循环状态机
1.1 状态定义
从二进制中提取的完整状态枚举:
| 状态 | 说明 |
|---|---|
Idle | 空闲状态 — 等待用户输入 |
CallingTool | 正在调用工具 |
WaitingApproval | 等待用户审批(权限请求) |
SkillCommand | 正在执行 Skill/命令 |
MCP | MCP 工具调用 |
LSP | LSP 语言服务器操作 |
PH | 钩子执行阶段 (PreHook/PostHook) |
1.2 回合生命周期
用户输入
│
▼
┌────────────────────────────────────────────────┐
│ run_turn() │
│ ┌─────────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Hook: │ │ LLM Call │ │ Tool │ │
│ │ on_turn_ │──▶│ + Stream │──▶│ Execute │ │
│ │ start │ │ │ │ │ │
│ └─────────────┘ └──────────┘ └──────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────┐ │
│ │ Hook: │ │
│ │ on_turn_end │ │
│ │ + Datalog │ │
│ └──────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────┐ │
│ │ Agent Loop: │ │
│ │ Idle → queue │ │
│ │ empty → │ │
│ │ redraw_idle │ │
│ └──────────────┘ │
└────────────────────────────────────────────────┘
│
▼
Idle, queue empty, redraw_idle1.3 工具调用流程
CallingTool
│
├──▶ 权限检查
│ ├── 已缓存 "always_allow" → 自动批准
│ ├── 敏感路径 → 提示用户审批
│ ├── 破坏性命令 → 提示用户确认
│ └── --dangerously-skip-permissions → 全部自动批准
│
├──▶ 前置钩子 (PreToolUse)
│ ├── 返回 Allow → 继续
│ ├── 返回 Block → 拒绝
│ └── 返回 Modify → 修改后继续
│
├──▶ 执行工具
│ ├── bash
│ ├── read_file / write_file / edit_file
│ ├── web_fetch / web_search
│ ├── MCP 工具 (mcp__<name>__<tool>)
│ └── ...
│
├──▶ 后置钩子 (PostToolUse)
│ └── 分析结果、触发通知
│
└──▶ 返回 LLM 进行下一轮1.4 重试逻辑
run_turn_with_retry 循环:
┌──────────────────┐
│ 最大回合数限制 │── NaturalTurnLimit
│ (LLM 生成轮次) │
├──────────────────┤
│ 最大步骤限制 │── StepLimit
│ (工具调用数) │
├──────────────────┤
│ 首 Token 超时 │── first token timeout
│ (断连/无响应) │
├──────────────────┤
│ 流超时 │── Stream timeout: 120s
│ (流中断) │
├──────────────────┤
│ 错误恢复 │── "run_turn_with_retry loop
│ │ must exit via the inner return"
└──────────────────┘
"internal error: entered unreachable code:
run_turn_with_retry loop must exit via the inner return"2. 回合结束处理
2.1 TurnCompleteContext
rust
struct TurnCompleteContext {
model_name: String,
provider_name: String,
result_type: String, // "success" | "error" | "interrupted"
tokens_used: u64,
truncated: bool, // 上下文窗口截断
edited_files: Vec<String>, // 本次编辑的文件
timestamp: u64,
tool_calls: Vec<ToolCallContext>,
}
struct ToolCallContext {
tool_name: String,
args: serde_json::Value,
result_type: String,
success: bool,
duration_ms: u64,
}2.2 回合结束处理链
1. 记录 Datalog (turn_{N}.md)
2. 触发 TurnComplete webhook
3. 更新 Telemetry 计数器
4. 更新会话状态
5. 检查是否需继续循环
6. 状态转为 Idle
7. 队列空 → redraw_idle3. 后台任务系统
3.1 /bg 子系统
/bg → 创建后台会话
/bg list → 列出活跃后台任务
/bg <N> → 切换到指定的后台任务
/bg drop <N> → 终止后台任务
后台会话特性:
- 隔离上下文(独立的会话状态)
- 只读工具子集(read-only-ish)
- 异步执行(不阻塞主会话)3.2 run_in_background 标志
rust
struct BashArgs {
command: String,
timeout: u64?, // 默认 60s
run_in_background: bool, // 后台运行标志
}后台 Bash 行为:
run_in_background=true:
- 分离到独立的进程组
- 立即返回
- 输出写入日志文件(可后续 cat/read_file 读取)
- 适用于长期运行任务(dev server, watcher, tunnel)4. 回合统计 (TurnStat)
rust
struct TurnStat {
input_tokens: u64,
output_tokens: u64,
cached_tokens: u64,
tool_calls: u64,
had_error: bool,
duration_ms: u64,
}每个回合记录到 Session 中,用于:
- Token 消耗统计
- 成本计算(显示于
/cost) - Telemetry 上报
- 上下文预算管理
5. 上下文预算管理
5.1 预算控制
/compact → 压缩对话历史(保留最近轮次)
/context → 显示上下文预算明细
/undo / /undo N → 撤回回合(内存回滚)
预算报告内容:
- system_tokens - 系统提示词 token
- tool_def_tokens - 工具定义 token
- tool_result_tokens - 工具结果 token
- message_tokens - 消息历史 token
- cached_tokens - 缓存 token
- messages_count - 消息条数5.2 上下文窗口超限处理
context_window 检测:
- 当响应包含 `context_window` 错误时触发
- 自动运行 /compact 压缩历史
- 重试最后一次 LLM 调用
- 如仍超限 → 提示用户6. 流式控制
6.1 流式参数
rust
struct ChatRequest {
// ...
stream: bool,
// ...
}6.2 首 Token 超时
"first token timeout"
一旦建立 SSE 连接,等待第一个数据块:
- 超时 → 断开连接
- 重试策略:由 run_turn_with_retry 处理6.3 流中断检测
stream_interrupted:
- 网络中断
- 提供商 API 中断
- 用户取消 (Ctrl+C)6.4 Agent 循环检测
loop_detected:
- 检测重复工具调用模式
- 相同工具 + 相同参数 X 次后触发
- 强制中断并提示用户7. Telemetry 事件与回合关联
| 事件 | 触发时机 | 关联数据 |
|---|---|---|
input_tokens | 每回合 | LLM 请求 token |
output_tokens | 每回合 | LLM 响应 token |
cached_tokens | 每回合 | 缓存命中 |
tool_call | 每次工具调用 | 工具名 |
tool_calls | 每回合 | 总调用次数 |
had_error | 每回合 | 错误类型 |
stream_interrupted | 流中断时 | — |
rate_limited | 被限流时 | — |
loop_detected | 检测到循环 | — |
session_duration_secs | 会话结束 | 总时长 |
turns_completed | 会话结束 | 总回合数 |
8. 新覆盖的盲区
| # | 之前未覆盖的领域 | 状态 | 发现 |
|---|---|---|---|
| 1 | Agent 回合循环状态机 | ✅ | 6 种状态 + 完整生命周期 |
| 2 | run_turn_with_retry 重试逻辑 | ✅ | 4 种终止条件 + 错误恢复 |
| 3 | TurnCompleteContext 完整字段 | ✅ | 6+ 字段 |
| 4 | 后台任务 (/bg) 机制 | ✅ | 隔离上下文 + 只读子集 |
| 5 | run_in_background Bash 行为 | ✅ | 分离进程 + 日志输出 |
| 6 | 上下文预算控制 | ✅ | /compact + /context + /undo |
| 7 | 流式控制参数 | ✅ | 首 Token 超时 + 流中断 + 循环检测 |
| 8 | Telemetry 与回合关联 | ✅ | 14 种关联事件 |
本报告由持续分析循环生成
逆向命令索引
bash
# 提取 agent 引擎相关
strings atomcode.bin.bak | grep -E "TurnStat|CountersSnapshot|NaturalTurnLimit|StepLimit|loop_detected" | sort -u
# 输出: struct TurnStat(6) CountersSnapshot(7) NaturalTurnLimit StepLimit loop_detected
# 提取 agent 源文件
strings atomcode.bin.bak | grep "^crates/atomcode-core/src/agent/" | sort -u
# 运行时查看 datalog
tail -3 ~/.atomcode/datalog/*/calls.log 2>/dev/null || echo "no datalog"