测试 Agent Core 设计
一句话定位: 这不是"一套写死的测试流程",而是一台双层反馈机,内环把单个测试任务收敛到事实,外环把跨项目的经验复利成先验。核心是状态 + 策略,不是流程。
1. 目标与非目标
目标
- 测试人员用自然语言下达测试意图,Agent 自行完成测试。
- 人员离线后,任务可无人值守持续运行并最终收敛。
- 面对不同类型的软件/项目/测试目标,不预设单一方法,而是由 Agent 主动摸索出正确的测试方向。
- 每个项目沉淀的经验可供后续项目复用与修正,形成正反馈闭环。
继承的成熟框架
- MAPE-K:Monitor–Analyze–Plan–Execute over shared Knowledge —— 本设计的骨架。
- OODA:观察→定向→决策→行动 —— 强调"定向(Orient)"即"找方向",多数 Agent 在此偷懒。
- CBR(案例推理)+ 主动学习:参考索引 = case base;"找方向"= 选信息增益最大的假设。
2. 核心洞察
2.1 核心是"状态 + 策略",不是"流程"
如果核心是一个提示词模板或固定步骤,那么一旦换项目类型就得改核心——这仍然是把方法写死了。 正确的核心是一组会演化的状态 + 一个转移函数:
Core = (TargetModel, HypothesisBoard, EvidenceLedger, Budget) + Transition
方法不是写死的,而是从目标模型推导出来的:
- 摸索(Recon) = 填充并细化 TargetModel。
- 实施(Execute) = 让 harness 去验证假设。
- 反馈(Feedback) = 用证据更新模型与假设后验。
2.2 方向选择必须有可计算的打分函数
否则大模型会挑"好玩的/好做的",而不是"该做的"。
priority(h) = 预期信息增益(h) × 风险覆盖(h) ÷ 成本(h)
这是整个设计里唯一不能省去的硬约束。
2.3 两种反馈的极性不能放反
| 环 | 范围 | 极性 | 作用 | 若放反的后果 |
|---|---|---|---|---|
| 内环 | 单任务(分钟~小时) | 负反馈 / 收敛 | 误差信号把系统推向不动点(可复现缺陷、被验证的假设) | 错误被放大,Agent 一路"论证"自己对(reward hacking / 确认偏误) |
| 外环 | 跨任务 / 跨项目 | 正反馈 / 放大(带阻尼) | 有效策略被强化复用,知识复利 | 纯正向放大 → 坏先验被反复强化 → 索引投毒 |
- 内外环的"负反馈"含义不同:内环的负反馈是误差信号(预测被证伪);外环的负反馈是证据门控的衰减(置信度下调)。
- 外环的正反馈必须带阻尼,否则复利会变成固化成见。
2.4 负结果不是没用,而是被用错了地方
负结果(失败方案 + 失败条件)属于知识层,它的角色是"过滤器"而非"阻尼器"。
- 作为过滤器:它收缩动作空间——"在这个架构形态下,这条路走不通",直接排除未来假设。
- 它不缩小知识:索引里记录失败,是为了让下一个项目少走弯路,而不是让系统对自己失去信心。
- 落到 schema 上:每条 case 必须同时存成功策略与失败方案及原因,否则索引会产生幸存者偏差。
3. 架构:双层反馈环
┌─ 外环 · 跨任务/跨项目 —— 正反馈:知识复利(带阻尼) ──────────────┐
│ │
│ ① 运行记录 ──▶ ② 模式提取 ──▶ ③ 参考索引 Case Base │
│ ▲ │ (负结果=过滤器) │(阻尼阀:证据门控衰减)│
│ │ ▼ ▼ │
│ │ ┌─ 内环 · 单任务 —— 负反馈:收敛 ─┐ │
│ │ │ ① 目标模型&假设看板 ──▶ ② 排序&执行 │ │
│ │ │ ▲ │ │ │
│ │ │ │ 误差信号 ▼ │ │
│ │ │ ④ 更新后验&收敛判定 ◀── ③ 证据/预言 │ │
│ │ └───────────┬────────────────┘ │
│ │ │(复用先验→假设种子) │
│ └──────────── ④ 检索先验 ◀───────────────────────────────┘
└────────────────────────────────────────────────────────────────┘
- 内环:单任务收敛循环,负反馈驱动,终点是"可复现的缺陷 + 被验证/证伪的假设"。
- 外环:跨项目知识复利,正反馈驱动,终点是"可复用、带适用条件、带置信度"的 case base。
- 复用通道:外环先验注入内环的 recon,作为假设种子(缩短冷启动)。
详见 feedback-loops.png。
4. 核心状态模型(Schema 草案)
原则:这是 v1 的全部"核心"。schema 必须从真实运行里长出来,不要预先设计过重。
4.1 TargetModel(目标模型)
{
"target": { "kind": "web-api | web-ui | mobile | cli | lib | data-pipeline", "entrypoints": [] },
"stack": { "lang": [], "frameworks": [], "build": "", "runtime": "" },
"architecture": { "shape": "monolith | service | distributed", "boundaries": [] },
"qualityGoals": [ { "attr": "functional | performance | security | compat | reliability",
"priority": 1, "rationale": "" } ],
"constraints": { "env": "", "data": "", "time": "", "access": "", "blastRadius": "" },
"observability": { "logs": [], "metrics": [], "traces": [], "testHooks": [] },
"unknowns": [ { "question": "", "cheapProbe": "" } ]
}
4.2 HypothesisBoard(假设看板)
{
"hypotheses": [
{
"id": "H1",
"claim": "分页参数 order 在负数时未做校验",
"goalAttr": "functional",
"risk": 0.7, // 影响
"infoGain": 0.9, // 预期信息增益
"cost": 1.0, // 成本
"score": 0.63, // priority 函数输出
"status": "open | probing | confirmed | refuted",
"oracle": "expected 400, observed 200",
"evidence": ["E3"],
"provenance": "recon | prior:CB-12 | user"
}
]
}
4.3 EvidenceLedger(证据账本)
{
"evidence": [
{
"id": "E3",
"type": "command | http | log | metric | trace | diff",
"reproducible": true,
"minimalRepro": "curl -X POST .../items?order=-1",
"observed": "...",
"confidence": 0.85,
"costSpent": { "steps": 3, "seconds": 42, "tokens": 8200 }
}
]
}
4.4 Budget(预算 / 停止条件)
{
"budget": { "maxSteps": 200, "maxSeconds": 7200, "maxCost": 5.0, "maxBlastRadius": "test-env-only" },
"definitionOfDone": [
"每个 high-risk goalAttr 至少有 1 条 confirmed 或 refuted 假设",
"无 open 假设的 score > 0.5",
"连续 N 步无新证据(漂移看门狗)"
],
"exploreExploit": { "exploreRatio": 0.3 } // 防止过早收敛到已知区
}
4.5 CaseBaseEntry(参考索引条目)
{
"id": "CB-12",
"structuralFeatures": {
"kind": "web-api", "architecture": "service",
"goalAttr": "functional", "constraint": ["no-auth-on-test-env"]
},
"applicability": "REST + 分页 + 无 schema 校验 的写接口", // 关键:没有它就等于靠玄学复用
"strategy": "对每个写接口做参数边界 + 类型混淆探测",
"adaptationPoints": ["分页参数名不同需先枚举"],
"outcome": { "found": 2, "falsePositives": 0, "cost": "低" },
"negativeResults": [ { "tried": "对读接口做同样探测", "reason": "读接口有 schema 校验,无收益" } ],
"confidence": 0.8,
"provenance": "project-A/2026-10",
"decay": { "lastValidated": "2026-10", "halfLifeDays": 180 }
}
5. 内环:单任务收敛循环
5.1 循环步骤(MAPE-K 实例化)
- Recon(摸索):扫描仓库/接口/文档/运行日志,填充 TargetModel,列出
unknowns,并为每个unknown选一个"最便宜的探测"。 - Hypothesize:把 unknowns 与先验(来自 case base)转成候选假设,写入 HypothesisBoard。
- Prioritize:用
priority(h)打分排序,取 top-K。 - Execute:harness 调用确定性工具(读文件 / 执行命令 / 调 HTTP / 解析输出)去验证最高分假设。
- Record:把结果写入 EvidenceLedger(含最小化复现与成本)。
- Update:更新假设后验(
confirmed/refuted),修正 TargetModel(新发现也可能推翻前期方向)。 - Decide:检查 Definition of Done 与预算;未达则回到 2,达到则产出报告 + case base 记录。
5.2 关键点
- 误差信号 = 预测被证伪 / 证据与模型冲突,这是内环唯一的驱动力,保证收敛。
- Orchestrator(编排) 与 Workers(执行) 分离:编排持有全局状态,worker 是无状态、可并行、可丢弃的执行单元。这让"长时运行"变得可恢复。
- 工具必须确定性:LLM 负责"决策",工具负责"事实"。所有事实来自工具,不来自模型记忆。
5.3 伪代码
state = load_checkpoint() or init_state(goal_nl, target)
while not done(state) and within_budget(state):
targets = recon(state) if state.needs_recon else []
state.model.update(targets)
state.board.extend(hypothesize(state, prior=casebase.retrieve(state.model)))
h = argmax(state.board.open, key=priority) # 增益 × 风险 ÷ 成本
result = harness.execute(h.probe) # 确定性工具
state.ledger.append(record(result, minimal_repro(result)))
update_posterior(state, h, result) # 负反馈:证伪 / 确认
checkpoint(state) # 可恢复
report = synthesize(state)
casebase.upsert(extract_pattern(state)) # 外环:正反馈
6. 外环:跨项目知识复利
6.1 外环职责
- 模式提取:从 EvidenceLedger 中抽象出"结构特征 → 策略 → 结果"的可复用模式。
- 索引维护:写入 case base,并持续修订置信度。
- 先验注入:为下一个项目的 recon 提供假设种子。
6.2 正反馈的阻尼机制(防索引投毒)
- 证据门控:只有当同一模式被独立复现(不同项目/不同环境)达到阈值时,置信度才上调。
- 衰减:长时间未被验证的模式,按
halfLifeDays下调置信度。 - 反例优先:一个明确的反例(失败条件)对置信度的下调幅度,必须大于一次成功的上调幅度——不对称才安全。
- 来源标注:用户确认 > 自动确证 > 单次观察。
6.3 检索必须按结构特征
- 用架构形态 / 技术栈 / 目标质量属性 / 约束 / 风险轮廓做检索键,而不是项目名或关键词。
- 没有 "applicability(适用条件)" 的索引 = 靠玄学复用先验,这是同类工具最常见的翻车点。
7. 失败模式与对策
| 失败模式 | 成因 | 对策 |
|---|---|---|
| Oracle 问题 | Agent 判断不了"真缺陷 vs 误报" | 多重预言三角验证 + 最小化复现 + 置信度分级;必要时人工兜底 |
| 确认偏误 / reward hacking | 内环误用正反馈 | 内环严格用负反馈;假设必须"可证伪",证据必须来自工具 |
| 探索坍缩 | 外环误用负反馈 / 无探索预算 | 外环用带阻尼的正反馈;固定探索比例 |
| 先验投毒 | 纯正反馈放大坏先验 | 证据门控 + 衰减 + 反例优先 |
| 假设抖动不收敛 | 打分函数不稳定 / 目标不清 | 稳定 priority 函数;DoD 明确;漂移看门狗 |
| 把环境噪声当信号 | flaky 环境 / 不可复现 | 幂等与快照;重复运行判定;隔离不稳定用例 |
| 成本无界 | 缺预算约束 | 步数/时间/成本三重上限,超限即归档并上报 |
8. 关键取舍与开放问题
- 收敛 vs 探索:DoD 太严会过早停止,太松会无限探索。需用数据调优,而非拍脑袋。
- 索引粒度:太细无法复用,太粗无法适用。建议以"结构特征 + 适用条件"为单位。
- 负结果的成本:记录失败方案需要额外结构化工作,但正是它让索引不产生幸存者偏差。
- 预言的可信度:自动化预言的置信度必须显式建模,否则"confirmed"会变成噪声。
- 何时必须人工:高危动作、真缺陷确认、方向性大改——应设计为"默认入队",而非默认阻断。
结语
- 核心是状态 + 策略,不是流程。
- 内环求收敛(负反馈),外环求复利(带阻尼的正反馈)。
- 负结果在知识层是过滤器:它修剪动作空间,不缩小知识。
- 唯一不可省的硬约束:方向选择必须有可计算的打分函数,否则"让大模型自己找方向"会退化成"让大模型自己找借口"。