# 23. 综合系统设计题:设计一个可恢复的企业级 Coding Agent Harness
# 23.1 题目
设计一个支持 Web/IDE/CLI、多租户、MCP、Skill、长期任务和子 Agent 的 Coding Agent。要求可暂停恢复、可审批、默认沙箱、全链路可观测,并能在上下文不足时压缩。
# 23.2 推荐回答骨架
- 先定实体与协议:Thread、Turn、Step、ToolCall、Approval、Task、Artifact、Event;所有流式事件都有稳定 ID 和递增事件序号。
- Runtime 主循环:构建 Context → 模型采样 → 收集 ToolCall → 校验/权限/沙箱 → 把结果加入下一轮 Context → 停止或继续。
- 状态与恢复:内存状态服务频繁访问的执行路径,Rollout 追加原始事件,SQLite 提供索引;副作用按 call id 去重,结果未知时不盲目重试。
- Context/Memory:稳定规则前缀、按 Step 召回代码和记忆、工具输出有界、接近阈值时压缩并重注入计划与未完成任务。
- Multi-Agent:根 Agent 维护任务 DAG;按能力与负载分发;Mailbox 传结构化消息;子 Agent 只拿必要上下文和最小权限 capability(能力凭证)。
- 安全:组织 Deny 优先;高风险动作审批;文件/网络/进程沙箱;MCP 与远程工具使用 audience-bound 凭证。
- 可观测与 Eval:Trace 连接模型、工具、审批、沙箱和子 Agent;Metric 监控 TTFT/TTFM/成功率/Token;Rollout 驱动回放与离线 Eval。
- 故障处理:限流退避、MCP 熔断、工具超时取消、孤儿进程清理、事件补发、写入队列 flush、计划局部重算。
# 23.3 主循环综合伪代码
function runTurn(threadId, input): // 定义企业级 Agent 处理一次用户目标的可恢复主循环
turn = beginDurableTurn(threadId, input) // 先持久化 TurnStarted 和用户输入,再开展任何模型或工具动作
while !turn.terminal: // 只要尚未完成、中止或失败,就继续执行采样—行动—观察循环
snapshot = captureWorldAndPermissionState(turn) // 固化当前文件、环境和权限版本,保证本 Step 判断基于一致视图
context = contextManager.build(turn, snapshot) // 按优先级、相关性和预算组装规则、历史、计划、代码与工具结果
if context.nearLimit: // 在达到模型硬上限前主动判断剩余安全 Token 是否不足
context = compactAndValidate(context) // 压缩旧历史并验证计划、约束和未完成任务没有被摘要丢失
response = tracedModelSample(context, toolRegistry.schemas) // 在 Trace 中调用模型,并提供当前允许工具的结构化 Schema
persistModelItems(response) // 将模型文本和工具提议先写入 Rollout,支持崩溃恢复与回放
if response.toolCalls.empty: // 没有工具提议意味着模型尝试直接回答或宣布完成
if verifier.accept(response, turn.goal): // 用验收标准、测试和证据验证最终回答是否真正满足目标
completeTurn(turn) // 验证通过后持久化完成终态,阻止主循环继续采样
else: // 验证失败说明模型声明与可观察事实不一致
injectVerificationFeedback(turn) // 把具体失败证据加入下一 Step,指导模型修复而非直接结束
continue // 返回循环起点,以最新状态重建上下文或结束终态 Turn
for batch in dependencyAwareBatches(response.toolCalls): // 按读写依赖分批,仅把相互独立的工具调用放入同一并发批次
results = parallelMap(batch, call => // 对当前无依赖批次并行执行,以降低多工具调用总延迟
authorizeApproveSandboxExecute(call, snapshot.permissions)) // 每个调用都独立经过授权、审批和沙箱,不能共享隐式放行
persistInOriginalCallOrder(results) // 即使并发完成顺序不同,也按模型原调用顺序持久化确定性结果
contextManager.append(results) // 将有界工具结果加入 Working Context,供下一轮模型观察
if shouldReplan(turn, results): // 根据失败证据、假设变化和剩余预算判断计划是否已经失效
updateVersionedTaskGraph(turn) // 局部修改任务图并保存新版本,保留旧计划和变更原因
flushRolloutAndEmitTerminalEvent(turn) // 退出循环前持久化全部事件,再向客户端发送唯一终态事件
面试结束前应主动补充三个 Trade-off:一致性与流式延迟、上下文质量与 Token 成本、自治程度与安全审批频率。能把这三组取舍讲清楚,通常比罗列更多框架名更有说服力。
# 23.4 用故障注入验证设计,而不是只画正常流程
完整设计应给每条关键边界安排可观测的故障实验:模型流在半个 ToolCall 时断开,断言不会执行残缺参数;Tool 产生副作用后、ToolResult 写入持久化存储前崩溃,断言恢复进入 unknown 而非自动重放;MCP 在 Schema 注入后切换连接版本,断言旧调用被拒绝并重新采样;压缩摘要遗漏未完成任务,断言结构化 Task 视图把它重新注入;子 Agent 租约过期后提交结果,断言防过期令牌(fencing token)拒绝旧 Worker;用户批准后符号链接目标发生变化,断言参数哈希和规范化对象校验失败。
验证结果要贯通四个面:Rollout 中存在正确状态迁移,Trace 能定位失败环节,Metric 记录稳定低基数错误类别,UI 显示可行动的恢复状态。安全实验还要在真实 OS backend 上执行越界读写、网络和进程树用例;仅 Mock Policy 返回 DENY 不能证明沙箱有效。通过这些实验,系统设计从“组件齐全”提升为“关键不变量可被证伪和持续回归”。