第15章 可观测性与调试
2026.07.29调试自主 agent 不同于调试普通程序——LLM 的输出不可复现,错误的因果链可能跨越多步工具调用。
模式层
15.1 Agent 可观测性的独特挑战
传统软件的可观测性(日志、指标、追踪)在面对 agent 系统时暴露出三个不足:
第一,非确定性输出。 普通程序在相同输入下产生相同输出(确定性)。Agent 系统在相同输入下可能产生不同输出——LLM 的不确定性使得"复现 bug"成为难题。用户说"重构模块 A",agent 第一次做的方案和第二次做的方案可能完全不同。调试时无法通过"再跑一次"来确认问题是否修复。
第二,因果链跨越多步工具调用。 Agent 的一个决策可能涉及 5 次工具调用、3 次 LLM 往返、1 次子 agent 调用。如果最终输出有问题,根因可能在第一步——但第一步的推理过程已经被后续的上下文覆盖了。传统日志的"按时间戳搜索"模式在跨越多步的因果链面前效率很低。
第三,LLM 输出的内容不可解析。 传统日志的结构化事件("用户登录成功"、"数据库查询返回 0 行")可以被精确解析和过滤。LLM 的 Reasoning token 是自然语言——无法被程序结构化解析。你不能 grep "错误原因" 来找到 agent 决策出错的原因。
15.2 可观测性三要素
针对上述挑战,CodeCoder 的可观测性体系围绕三个要素设计:
结构化事件流。 所有 agent 事件(NewToken、ToolStarted、ToolFinished、MilestoneDone、StatusUpdate)都作为结构化 JSON 事件输出。每个事件包含时间戳、事件类型、相关上下文。结构化事件可以被程序解析、过滤、采样。
实时可观测。 Headless 模式下,事件流同时写入 stderr 和 .ccd.bg.ndjson。交互式模式下,事件流通过 daemon 的事件通道广播到所有连接的 client。不需要等待 run 完成就可以查看进展。
事后可追溯。 Session 文件保存完整的事件历史。BgObserver 写入的 ndjson 文件保留从上一次 truncate 到当前运行的完整事件流。事后分析时,不需要重新运行 agent——读取 ndjson 文件即可。
案例层
15.3 BgObserver + bg_ledger
BgObserver 是 headless 模式下的可观测性组件:
struct BgObserver {
ndjson_writer: BufWriter<File>,
events_seen: usize,
start_time: Instant,
}
impl BgObserver {
fn new(project_root: &Path) -> Result<Self> {
let path = project_root.join(".ccd.bg.ndjson");
// Truncate at start of each round
let file = fs::OpenOptions::new()
.write(true)
.truncate(true)
.create(true)
.open(&path)?;
Ok(Self {
ndjson_writer: BufWriter::new(file),
events_seen: 0,
start_time: Instant::now(),
})
}
fn observe(&mut self, event: &BgEvent) {
// Write to ndjson
let json = serde_json::to_string(event)?;
writeln!(self.ndjson_writer, "{}", json)?;
self.ndjson_writer.flush()?;
// Also write to stderr
eprintln!("{}", json);
self.events_seen += 1;
}
}
bg_ledger 是 BgObserver 的扩展——它记录每个里程碑的耗时和结果:
struct BgLedger {
milestone_times: Vec<MilestoneRecord>,
tool_counts: HashMap<String, usize>,
total_tokens: usize,
}
struct MilestoneRecord {
name: String,
started_at: Instant,
completed_at: Option<Instant>,
status: MilestoneStatus,
attempts: usize,
}
15.4 Accountability Chain
Accountability Chain 是 CodeCoder 可观测性体系中的高级功能——将 agent 的决策与其产生的具体输出链接起来。
当 agent 做出一个决定(如"修改文件 A 的接口"),Accountability Chain 记录:
- 决策的触发条件(用户输入)
- 决策的推理过程摘要(从 Reasoning 中提取的关键点)
- 决策执行的工具调用序列
- 执行结果(文件修改的 diff)
这些记录在事后分析时,可以帮助回答"为什么 agent 做了这个修改"——而不是"agent 做了什么修改"。
struct AccountabilityEntry {
timestamp: Instant,
trigger: String, // Input that triggered the decision
rationale: String, // Reasoning process summary
actions: Vec<ToolCall>, // Executed tool calls
outcome: String, // Execution result summary
diff: Option<String>, // File modification diff (if any)
}
15.5 调试方法论
调试自主 agent 的方法论可以总结为三个步骤:
第一步:隔离 LLM 输出。 使用 StubClient 或 ScriptedProvider 替代真实 LLM provider。StubClient 返回固定的响应,ScriptedProvider 从预先录制的响应序列中播放。这使得 agent 在调试时的行为是确定性的——每次运行产生相同的输出。
第二步:确定性回放。 从 Session 文件或 ndjson 文件中读取事件序列,在不连接 LLM provider 的情况下回放。回放时,agent 的输入来自录制的事件,而不是 LLM 的实时输出。可以快速定位"在哪个事件之后 agent 开始出现异常行为"。
fn replay(session: &Session, context: &Context) -> Result<()> {
for event in &session.events {
match event {
Event::NewToken(_) => {} // Ignore token output
Event::ToolStarted { tool, args } => {
// Check if tool call is reasonable
validate_tool_call(tool, args)?;
}
Event::ToolFinished { tool, result } => {
// Validate tool result
validate_tool_result(tool, result)?;
}
Event::MilestoneDone { name, .. } => {
// Validate milestone completion conditions
validate_milestone(name)?;
}
}
}
Ok(())
}
第三步:结构化事件追踪。 使用事件过滤工具(如 jq)从 ndjson 文件中提取特定类型的事件进行分析:
# Extract all tool call events
jq 'select(.event == "ToolStarted")' .ccd.bg.ndjson
# Extract all failed tool calls
jq 'select(.event == "ToolFinished" and .result.status == "error")' .ccd.bg.ndjson
# Extract all milestone completion events, sorted by time
jq 'select(.event == "MilestoneDone") | {name, timestamp}' .ccd.bg.ndjson
ADR 深度阅读
BgObserver 与 bg_ledger 的引入动机
ADR 0039 记录了 BgObserver 和 bg_ledger 的引入过程。
在 BgObserver 出现之前,headless 模式的输出只有退出码和 stderr 上的错误信息。如果 headless run 失败了(退出码 2 或 3),用户需要重新运行一次才能看到"哪里出错了"。重新运行可能产生不同的结果(LLM 的不确定性),使得"复现 bug"变得困难。
BgObserver 的引入将 headless 的运行过程从"黑盒"变为"透明盒"。每个事件都写入 ndjson 文件,用户可以 tail -f 实时观察,也可以在 run 结束后离线分析。ndjson 格式(每行一个 JSON 事件)确保文件可以被流式处理——不需要等到 run 结束才能开始分析。
bg_ledger 在 BgObserver 的基础上增加了里程碑级别的聚合统计。它不是记录每个事件,而是记录每个里程碑的耗时、工具调用次数、重试次数。这使得"这个 run 为什么慢"的根因分析变得可行——如果某个里程碑的次数远超平均值,可以针对性地查看该里程碑的详细事件。
Accountability Chain 是后来增加的功能。它的引入动机是:当 agent 做出一个错误的修改(如删除了不该删的代码),现有的日志系统只能告诉你"agent 删除了代码",不能告诉你"agent 为什么认为这段代码应该被删除"。Accountability Chain 把决策的推理过程摘要与决策的执行结果关联起来,使得事后审查时可以看到"动机"而非仅看到"行为"。
下一章,CodeCoder 的测试策略——三层测试金字塔、隔离测试、行为验证。