跳到正文

Agent 仿真台落地设计:场景、替身、运行与证据 ​

一句话:仿真台不是"再买一套评测工具",而是把 Agent 运行所依赖的外部世界做成可保存、可修改、可重放的软件环境——它交付的是证据材料,不是质量结论;边界由授权与后果划定,不由"请求有没有真的发出去"划定。

本篇对应白皮书第 16 章 Agent Simulation,是 24-白皮书对标与落地推演 认定的最大结构性缺口:平台 00–23 篇已经有生产可观测(14)、黄金集与发布门禁(14)、沙箱与状态(12/21)、任务账本与证据(22),但没有"上线前把一个 Agent 放进可控世界跑一遍"的对象链、执行引擎与证据契约。本篇把它补到可直接建表、建接口、接入现有组件的程度。

为什么需要 ​

现有平台能回答三个问题:线上这次为什么这样(14 的 Trace/事件)、这一版离线跑分是否更好(14 的黄金集)、谁能发布(14 的四级门禁)。它回答不了第四个问题:在没有人盯着、外部系统会超时、用户会改主意的情况下,这个 Agent 的行为是否仍然可接受。

缺口的根因不是评测集不够大,而是评测的输入是静态的:

  • 黄金集用例是"一条输入 + 一条期望",跑不出多轮追问、撤回、等待、故障恢复这些真正决定任务成败的路径;
  • 影子流量只观察、不施压,无法制造"首次查询超时、第二次恢复"这种条件;
  • 录制回放把历史调用重新喂给 Agent,但历史里的工具响应顺序、时延、失败都是固定脚本,测到的是"能不能按剧本走",不是"会不会处理没见过的状态"。

把这三类验证当成"仿真"会得到一个危险的结论:分数在涨,生产接管也在涨。白皮书第 16 章给出的判定很直接——模拟的价值是"用可控环境换可验证行为",它交付 Records → Observations → Evidence,质量结论由独立的 Evaluation 给出,发布授权由治理门禁给出。平台如果不把这三层分开,仿真就会退化成"又一个跑分平台"。

平台的现实约束也决定了仿真台必须独立建设,不能塞进现有任务链路:

  • 仿真不能改写被测 Agent 的决策,否则测的是脚本;SUT 必须作为黑盒从外部驱动;
  • 仿真需要独立命名空间、独立账户、独立数据,不能和生产共用租户与凭据;
  • 仿真的时间可以被加速、回拨、冻结,生产任务的 deadline 与租约不能;
  • 仿真 Run 可以一次包含多个业务 Task,而平台 rt.agent_task 是单任务账本,两者是包含关系,不是同一张表。

核心机制 ​

一、边界:SUT 不进仿真,边界由授权与后果划定 ​

先定三条不能破的规则:

  1. 被测系统(SUT)是仿真服务的对象,不是仿真的一部分。 推理、工具选择、参数构造、异常处理都属于 SUT;这些行为一旦被替身接管,被测对象就已经移出回路。
  2. 仿真边界只有两条:不触达未授权的生产账户、数据与网络;不产生不可控的真实业务副作用。满足这两条,即使连接的是隔离的真实数据库与测试账户,仍在仿真内;反过来,只要把请求路由到生产账户,即使所有组件都是替身,也不是仿真,是事故。
  3. 替换前先判断"这是内容还是条件"。 是本次要考察的内容,不能替换;是本次要设定的条件,必须替换,并写清替换成了什么。

这条判断直接决定场景设计。以"主管 Agent 调度一个协作 Agent 完成任务"为例:

考察目标协作 Agent 的角色处置原因
主管的任务分解与结果聚合能力内容保留真实协作 Agent 或其等价实现它的响应质量直接影响结论
主管对下游故障的容错能力条件替身为可注入超时/错误的状态机故障表现是实验设定,必须可控可复现
用户是否会补充订单号条件用户模拟器按披露规则控制用户行为是实验输入
Agent 自己的工具选择内容不替换替换后测的是脚本

SUT 边界必须写进场景规格,而不是靠口头约定。sut.boundary 至少声明四类:reasoning(推理)、tool-selection(工具选择)、parameters(参数构造)、recovery(异常恢复)。执行器在装载时校验:边界内出现替身即拒绝启动,返回 422 sut_boundary_replaced。

最常见的反模式是"把工具调用顺序固定成脚本"。这种 Run 看起来一切正常,也没有任何告警,但被测的已经不是 Agent 的决策能力,而是那段脚本。平台侧的防线是:环境替身只返回响应,不主动推进对话;任何"替身调用 SUT"的路径都必须经过 SUT 的公开入口与相同鉴权。

二、对象模型:Spec → Manifest → Run → Result ​

一次仿真执行涉及的对象必须各有定义和生命周期,否则"可复现"只是一句口号。

text
Scenario Spec(场景规格,声明式、可版本化)
  └── Manifest(启动时锁定:场景版本 + 全部资产版本 + 参数)
        └── Run(一次受控执行)
              ├── 1..N Task(业务任务,如"提交退款申请")
              │     └── 1..N Session(会话:用户对话 + 工具调用链)
              │           └── Event(已经发生的原子事实)
              └── Run Result
                    ├── Records(原始记录:消息、调用、状态变更)
                    ├── Artifacts(产物:文件、截图、浏览器状态)
                    ├── Observations(从 Records 提取的事实)
                    ├── Evidence(按评价项组织的证据引用)
                    └── Completeness(按采集契约判断的完整性)

四个对象的职责不能混:

对象回答什么可以变吗权威位置
Scenario Spec这次实验怎么设计改设计=新版本场景库(Nacos + 对象存储)
Manifest这次用了哪一组确定版本启动后不可变仿真账本 + 对象存储
Run这次执行实际发生了什么只追加仿真账本 + 事件存储
Run Result交出了哪些可引用的材料生成后不可变证据存储

计划与事实不能共用一个名字。 场景里写的是 triggers(例如"第 1 轮注入查询超时"),这是未来计划;命中之后产生的"查询超时已发生"才是 Event。把计划叫 Event,会让复现时无法区分"条件没生效"和"条件生效了但 Agent 没走到"。

Manifest 锁的是解析后的版本,不是标签。 场景写 refund-agent@stable,Manifest 必须落成 refund-agent@2.4.1 与 sha256:...;运行记录不能只记 stable,否则标签一移动,历史结论就无法解释。这一点与平台 08 篇的资产版本、19 篇的 label 回滚是同一个原则。

最小 schema ​

仿真数据放在独立 schema(示例 sim),与运行面 rt 分开,避免生产任务查询扫到仿真数据。

sql
CREATE SCHEMA IF NOT EXISTS sim;

-- 场景:逻辑资源
CREATE TABLE sim.scenario (
  scenario_id     text PRIMARY KEY,
  tenant_id       text NOT NULL,
  namespace       text NOT NULL,
  name            text NOT NULL,
  owner           text NOT NULL,
  risk_level      text NOT NULL CHECK (risk_level IN ('low','medium','high')),
  created_at      timestamptz NOT NULL DEFAULT now(),
  UNIQUE (tenant_id, namespace, name)
);

-- 场景版本:内容不可变
CREATE TABLE sim.scenario_version (
  scenario_id     text NOT NULL REFERENCES sim.scenario(scenario_id),
  version         text NOT NULL,              -- SemVer
  spec            jsonb NOT NULL,             -- 十个字段组
  spec_sha256     text NOT NULL,
  status          text NOT NULL CHECK (status IN ('draft','reviewing','reviewed','online','offline')),
  source          text,                       -- requirement / trace / incident / manual
  created_by      text NOT NULL,
  created_at      timestamptz NOT NULL DEFAULT now(),
  PRIMARY KEY (scenario_id, version)
);

-- 资产:用户策略、环境初始数据、工具契约、模拟器配置、评估规格
CREATE TABLE sim.asset (
  asset_id        text PRIMARY KEY,
  tenant_id       text NOT NULL,
  asset_type      text NOT NULL CHECK (asset_type IN
                    ('user_policy','env_data','tool_contract','simulator_config','evaluation_spec')),
  name            text NOT NULL,
  owner           text NOT NULL,
  UNIQUE (tenant_id, asset_type, name)
);

CREATE TABLE sim.asset_version (
  asset_id        text NOT NULL REFERENCES sim.asset(asset_id),
  version         text NOT NULL,
  content_ref     text NOT NULL,              -- oss://... 或 nacos://...
  content_sha256  text NOT NULL,
  status          text NOT NULL CHECK (status IN ('draft','reviewing','reviewed','online','offline')),
  created_at      timestamptz NOT NULL DEFAULT now(),
  PRIMARY KEY (asset_id, version)
);

-- Manifest:一次 Run 的冻结组合
CREATE TABLE sim.manifest (
  manifest_id     text PRIMARY KEY,
  tenant_id       text NOT NULL,
  scenario_id     text NOT NULL,
  scenario_version text NOT NULL,
  resolved        jsonb NOT NULL,             -- 全部资产的精确版本 + sha256
  manifest_sha256 text NOT NULL,
  seed            bigint,
  created_at      timestamptz NOT NULL DEFAULT now(),
  UNIQUE (manifest_sha256)
);

-- Run:一次受控执行,四种状态独立
CREATE TABLE sim.run (
  run_id            text PRIMARY KEY,
  tenant_id         text NOT NULL,
  manifest_id       text NOT NULL REFERENCES sim.manifest(manifest_id),
  mode              text NOT NULL CHECK (mode IN ('surrogate','hybrid','isolated_real')),
  execution_status  text NOT NULL CHECK (execution_status IN ('pending','running','completed','crashed','cancelled')),
  validity_status   text NOT NULL CHECK (validity_status IN ('unchecked','valid','violated')),
  evidence_status   text NOT NULL CHECK (evidence_status IN ('unchecked','complete','partial')),
  quality_ref       text,                     -- 指向 Evaluation Result,不由仿真写
  started_at        timestamptz,
  ended_at          timestamptz,
  terminal_reason   text,
  trace_id          text NOT NULL,
  created_at        timestamptz NOT NULL DEFAULT now()
);

CREATE INDEX ix_sim_run_manifest ON sim.run (tenant_id, manifest_id, created_at DESC);

-- Run 内的业务任务与会话(引用 SUT 自己的 task_id/session_id)
CREATE TABLE sim.run_task (
  run_id          text NOT NULL REFERENCES sim.run(run_id),
  task_key        text NOT NULL,              -- 场景内的逻辑任务名
  sut_task_id     text,                       -- 被测系统返回的任务 ID
  goal            jsonb NOT NULL,
  outcome         text CHECK (outcome IN ('submitted','accepted','root_complete','failed')),
  PRIMARY KEY (run_id, task_key)
);

CREATE TABLE sim.run_session (
  run_id          text NOT NULL,
  task_key        text NOT NULL,
  session_key     text NOT NULL,              -- 场景内的逻辑会话名
  sut_session_id  text,
  transport       text NOT NULL CHECK (transport IN ('ag-ui','a2a','chat-completions','internal')),
  PRIMARY KEY (run_id, task_key, session_key),
  FOREIGN KEY (run_id, task_key) REFERENCES sim.run_task(run_id, task_key)
);

-- Event:已经发生的原子事实,只追加
CREATE TABLE sim.event (
  run_id          text NOT NULL,
  seq             bigint NOT NULL,
  event_type      text NOT NULL,              -- user.message / tool.request / tool.response / fault.hit / state.change
  actor           text NOT NULL CHECK (actor IN ('user','agent','environment','harness')),
  payload         jsonb NOT NULL,
  logical_time    bigint,                     -- 仿真逻辑时间(毫秒)
  wall_time       timestamptz NOT NULL,
  trace_id        text,
  span_id         text,
  PRIMARY KEY (run_id, seq)
);

-- 原始记录:消息、调用、状态变更;不直接作为结论
CREATE TABLE sim.record (
  record_id       text PRIMARY KEY,
  run_id          text NOT NULL,
  record_type     text NOT NULL CHECK (record_type IN ('message','tool_call','state_change','control')),
  ref             text NOT NULL,              -- 对象存储 URI 或内联小对象
  sha256          text NOT NULL,
  redaction       text NOT NULL DEFAULT 'none',
  created_at      timestamptz NOT NULL DEFAULT now()
);

-- 观测:从 Records 提取的事实(可重算)
CREATE TABLE sim.observation (
  observation_id  text PRIMARY KEY,
  run_id          text NOT NULL,
  criterion_key   text,                       -- 对应评价项,可为空
  fact_key        text NOT NULL,              -- refund.count / refund.amount_minor / order.state
  fact_value      jsonb NOT NULL,
  derived_from    jsonb NOT NULL,             -- record_ids / event seqs
  extractor       text NOT NULL,              -- 提取规则或模型版本
  created_at      timestamptz NOT NULL DEFAULT now()
);

-- 证据引用:Observation → 评价项
CREATE TABLE sim.evidence (
  evidence_id     text PRIMARY KEY,
  run_id          text NOT NULL,
  criterion_key   text NOT NULL,
  observation_ids jsonb NOT NULL,
  evidence_refs   jsonb NOT NULL,             -- record/artifact 的稳定引用
  created_at      timestamptz NOT NULL DEFAULT now()
);

-- 完整性:按采集契约逐项核对
CREATE TABLE sim.completeness (
  run_id          text NOT NULL,
  check_key       text NOT NULL,              -- required_message / tool_result / state_snapshot / fault_recovery
  status          text NOT NULL CHECK (status IN ('present','missing','not_applicable')),
  detail          jsonb,
  PRIMARY KEY (run_id, check_key)
);

四个状态列不能合并。一次 Run 完全可能"执行完成、仿真有效、材料齐备、任务质量不合格";压成一个 success,归因时就分不清是环境坏了、材料缺了,还是 Agent 错了。

三、执行引擎:编排层做五件事,明确不做五件事 ​

仿真 Harness 的职责边界必须写进代码,而不是靠文档约束:

做不做
解析场景,把 Spec + 资产解析成不可变 Manifest任务生命周期管理(那是 SUT 或其编排层)
编排 Run 的装载、初始化、运行、终止、收敛、清理鉴权与权限决策(那是 Gateway 与安全框架)
管理替身与隔离,维持仿真边界遥测采集(那是 OTel,Harness 只关联引用)
汇集记录,把交互与控制关联到 Trace 与 Artifact质量评分与判定(那是 Evaluation)
清理环境,使世界回到初始状态发布决策(那是治理门禁)

"不做"的每一项都对应平台里已有的专职系统。把任何一项收进来,Harness 就会从执行引擎膨胀成总控制器:它会开始持有任务状态、开始自己发权限、开始自己下结论,最后与 22 的任务账本、15 的安全策略、14 的门禁产生第二权威。

数据流三线、协调两账 ​

  • 交互流:用户消息、工具请求、可见事件;
  • 控制流:装载、初始化、故障注入、终止、清理指令;
  • 证据流:Trace、状态、产物、控制记录的沉淀。

三线靠两张账协调:

事件联动账记录同一事件在用户侧、Agent 侧、环境侧的三个时点。以"撤回"为例:用户侧在 t1 本地撤回,Agent 在 t2 收到,在途事务在 t3 生效。界面已经撤回、Agent 却还在按原指令提交,是真实事故的常见形态。联动账让"撤回到底生效没有"可以被精确回答,而不是靠日志猜。

时间分离账记录四种时间,不可混用:

时间回答什么实现误用后果
业务日历时间业务规则的期限判断(是否在 7 天无理由期)场景指定起始日期 + 业务日历用虚拟加速会破坏业务规则
仿真逻辑时间虚拟事件调度(第 3 轮注入超时)Harness 调度器,可加速/暂停与墙钟混用会导致调度不确定
单调时钟真实耗时测量(推理、工具各花多久)CLOCK_MONOTONIC用业务时间测性能没有意义
墙钟时间与现实日期的关联(何时跑、报告何时生成)UTC回拨会破坏审计与保留期

生命周期与失败语义 ​

text
LOADING → INITIALIZING → RUNNING → DRAINING → COLLECTING → CLEANED
                │            │          │
                └ FAILED ←───┴──────────┘
  • LOADING 失败:Manifest 缺失、资产校验不通过、SUT 版本不可达 → crashed,terminal_reason=manifest_unresolved,不产生任何 Agent 调用;
  • INITIALIZING 失败:环境数据装载失败、用户模型加载失败 → crashed,validity_status=violated;
  • RUNNING 失败:SUT 崩溃、超时、预算耗尽 → crashed 或 cancelled,保留已产生的 Records,evidence_status=partial 并列出缺失项;
  • DRAINING:停止注入新事件,等待在途调用返回或超时;不能直接杀进程,否则最后一步的副作用与回执会丢失;
  • COLLECTING:生成 Observations、Evidence、Completeness;提取失败不阻断 Run,但必须把对应项标为 missing,不能用零值补齐;
  • CLEANED:销毁隔离命名空间、测试账户、临时凭据、工作区;清理失败写入告警,且该 Run 的结论标 validity_status=violated。

四、用户模拟器:三层模型 + 混合生成 + 独立质控 ​

用户模拟器承载的不是"一个会说人话的 LLM",而是三个可分别治理的部分:

层内容实现可复现性
Persona专业度、表达偏好、行为倾向结构化配置 + 提示模板高
认知状态已知事实、当前理解;允许与客观状态不同状态对象,区分真实状态与用户认知高
行为策略反应规则 + 状态机 + 时钟驱动的耐心状态机决定意图,模型生成措辞中

工程主流是混合:意图选择交给状态机("指出金额错误"是一个确定节点),自然语言表达交给模型(生成符合 Persona 的措辞),发送前过两道校验——事实校验(不能说出用户认知之外的金额与订单号)和披露规则校验(订单号只在被追问时提供)。探索运行放宽分支选择,让模拟用户走出多样路径;回归运行锁定关键行为约束,保证可复现。

用户模拟器的质量不能看 Agent 成功率。过度配合的模拟器会高估 Agent 能力,始终拒绝的模拟器会产生无意义失败。质控指标必须按维度和统计单位定义:

维度检查什么统计单位不合格处置
角色一致性行为是否贴合 Persona每条消息标记该消息,重跑或人工复核
反应规则符合率触发条件命中时是否执行反应每次触发该 Run 标 validity_status=violated
信息可见性遵守是否只披露用户认知内的信息每次披露该 Run 结论不可用于质量评价
目标终止规则符合率是否按规则继续、澄清或停止每个 Run重跑或判 unchecked
行为分布多样性分支选择是否符合目标分布每批次 Run调整场景或用户策略版本

生成与评分必须使用不同模型,降低共同偏差;再配合独立数据、受限上下文与人工校准,让"用户像不像"本身成为可治理对象。

五、环境模拟器:一个事实模型,三个超时位置,四步故障记录 ​

事实一致性优先于"像不像" ​

环境可信的第一要求不是接口长得像,而是事实一致:create_refund 返回"已受理"后,get_refund 必须查到对应状态与金额。用一张事实模型(订单、退款、账户、状态机)同时驱动 API 替身与数据库替身,禁止一个说受理、一个说不存在。

超时是最值得精雕的条件,按请求生命周期分三个位置:

超时位置系统状态检验什么注入方式
请求未发送调用未发生重试与退避策略工具适配层直接返回连接失败
已发送、未提交下游收到但未处理等待、轮询与查询确认替身挂起请求,超时后返回未知
已提交、响应丢失业务已生效重复提交防护与状态核实替身执行业务动作但不返回响应

第三种最关键:申请其实已经受理,Agent 却没收到回执——它会不会先核实状态,还是直接再提交一次?很多"重试导致重复退款"的事故就发生在这一格。

替身机制按确定性选择,不按难度阶梯 ​

机制开放度确定性状态复杂度维护成本典型对象
脚本低高低低固定流程的工具响应
录制回放低高中中,随接口版本漂移历史真实会话
规则低高低低幂等查询接口
状态机中高高中多阶段业务流程
模型生成高低中高开放用户行为
混合中中中中工程主流

被模拟对象越确定,越适合脚本与规则;越开放,越需要模型生成;状态越复杂,越偏向状态机与录制回放。不要因为"我们有模型"就把所有替身都做成生成式——每多一处生成,就多一处不可复现。

故障注入:ChaosBlade 只是执行后端 ​

边界要写死:ChaosBlade 执行实验,Harness 决定何时、对谁、施加什么故障,并负责同步、恢复验证与清理。 不允许业务代码直接调用 ChaosBlade,也不允许把故障脚本散落在场景 YAML 之外。

故障记录分四步,缺一步结论就不可信:

步骤记录什么失败含义
planned场景里写了什么故障、目标、时间窗计划本身不完整
armed执行器报告实验已启动注入没有发生
hit观测到故障确实落在目标调用上注入发生了,但没打中目标
recovered实验销毁后服务恢复正常环境未收敛,后续 Run 不可信
sql
CREATE TABLE sim.fault_plan (
  run_id        text NOT NULL,
  fault_id      text NOT NULL,
  target        text NOT NULL,              -- tool / dependency / pod
  fault_type    text NOT NULL,              -- delay / error / timeout / abort
  params        jsonb NOT NULL,
  at_logical_ms bigint NOT NULL,
  recover_after_ms bigint,
  PRIMARY KEY (run_id, fault_id)
);

CREATE TABLE sim.fault_record (
  run_id        text NOT NULL,
  fault_id      text NOT NULL,
  stage         text NOT NULL CHECK (stage IN ('planned','armed','hit','recovered','failed')),
  detail        jsonb NOT NULL,
  observed_at   timestamptz NOT NULL DEFAULT now(),
  PRIMARY KEY (run_id, fault_id, stage)
);

隔离与复现 ​

隔离覆盖计算、网络出口、数据、文件、缓存与记忆:每个 Run 使用独立的 Kubernetes 命名空间、独立测试账户、独立工作区与会话,Run 之间不共享任何可变状态。共享只允许发生在只读基础镜像与只读知识库。

复现分三个层次,回答三个不同问题:

层次做法回答
配置复现用 Manifest 重建场景与全部资产版本同样的实验能不能再来一次
事件复现重放输入、故障与调度同样的路径能不能再走一遍
统计复现同配置重复 N 次,比较分布结论稳不稳

六、场景与资产:十个字段组,五类版本,一张覆盖矩阵 ​

Scenario Spec 用十个字段组回答从"测什么"到"怎么评价"的完整设计:

字段组内容平台落点
identity场景 ID、版本、标签、负责人、风险等级sim.scenario / sim.scenario_version
sut被测边界、Agent 版本、模型与 Prompt 引用Manifest 解析;边界校验
userPersona、目标、私有事实、披露规则、行为策略用户模拟器 + sim.asset
environment初始数据、工具契约、后端模式、时钟策略环境模拟器 + 隔离命名空间
interaction实体绑定、可见渠道、动作入口、交互模式AG-UI / A2A / Chat Completions 适配
participants其他 Agent 的角色、接口与权限协作面替身或真实 Agent
events触发条件、故障动作、恢复策略sim.fault_plan
execution种子、并发、轮数、时间与资源预算Harness 调度与配额
evaluation_ref断言、Rubric、预期状态的版本引用Evaluation 系统(不在仿真内)
provenance原始需求、资产位置、脱敏会话、事故记录场景库审计

一个完整示例(退款超时场景):

yaml
schema_version: 1
identity:
  id: refund-timeout-retry
  version: 1.3.0
  owner: quality-team
  risk_level: high
sut:
  agent: refund-agent@2.4.1
  boundary: [reasoning, tool-selection, parameters, recovery]
user:
  persona: { expertise: novice, patience: low, tone: annoyed }
  goal: "拿回 199 元退款"
  private_facts: { order_no: "B2026-0901-7734", paid_amount_minor: 19900 }
  disclosure: { order_no: on-request }
  behavior:
    reaction_rules:
      - { when: agent.asks_order_no, times: 3, effect: disclose_order_no }
      - { when: wait_ms > 120000, effect: threaten_complaint }
environment:
  backend_mode: surrogate
  clock: { policy: virtual, start: "2026-09-01T10:00:00+08:00" }
  data_ref: env/ecom-seed@3.0.1
  tool_contracts: [refund-api@1.4.0]
interaction:
  entity_binding: { user.order_no: env.order.id, tool.order_no: env.order.id }
  channels: [ag-ui]
  action_entry: refund.create
participants: []
events:
  triggers:
    - { target: refund.query, effect: timeout, at: turn-1, recover: turn-2, position: committed }
execution:
  seed: 20260914
  budget: { max_turns: 12, wall_clock: 20m, max_tool_calls: 30 }
evaluation_ref:
  assertions: refund-assertions@1.1.0
  rubric: refund-rubric@2.0.0
provenance:
  source: "incident #7742 脱敏会话"
  reviewed_by: risk-team

两处细节决定场景是否成立:user.private_facts 与 environment.data_ref 里的订单必须是同一实体(联合建模);events.triggers 是未来计划,命中后才生成 Event(名字边界)。

联合建模的五个维度必须逐项校验,否则场景测的是假问题:

  1. 实体与事实绑定:用户说的订单、工具参数、数据库记录是同一张单子;
  2. 信息可见性:用户认知与环境状态允许不同,但两侧内部各自一致;
  3. 动作入口与状态归属:提交入口只有一个,状态变迁有唯一属主;
  4. 事件触发与时序:什么事件按什么顺序发生;
  5. 目标演化与结果要求:任务完成的判定随场景演化更新。

五类对象独立版本化:场景、数据、工具契约、模拟器配置、评估规格。一次 Run 锁定全部版本组合,结论只对这组组合负责。改用户耐心参数是新场景版本;改披露规则逻辑是新模拟器资产版本;两者不能混在一个版本号里。

覆盖矩阵按"业务阶段 × 用户行为 × 权限状态 × 依赖故障"建立。缺口决定下一批场景的优先级,而不是"谁声音大先做谁"。新出现的交互缺陷先核实事实与责任归属,再固化进回归集——把一次事故的表象直接抄成场景,等于把未确诊的病历直接开成处方。

七、证据交付:三层分离,三道可用性检查,一个置信上界 ​

Run Result 的三个层次不能混:

text
Records(原始记录)
  → Observations(事实性观测:退款申请数 = 1,金额 = 19900 分,状态 = accepted)
    → Evidence(按评价项组织的证据引用)

criterion → evidence_refs → verdict 的关系由 Evaluation Result 建立,不属于 Run Result。同一份 Records,质量团队用来重评分,合规团队检查金额口径,回归系统比对版本差异;如果原始材料与质量结论混在一层,后一种用途会被前一轮结论污染。

评估器一侧的可用性检查有三道,不是一道闸门:

检查回答不通过时
记录完整性材料在不在输出"不可判定"
运行有效性实验条件成立没有限定可评分范围或拒绝评价
证据充分性够不够支撑这个评价项该项标"不可判定",其余照常评价

"不可判定"是诚实结论,好过用不充分证据硬评。

零违规的回归结果要标注统计边界:n 次独立运行零违规,违规率的单侧 95% 置信上界是 1 − 0.05^(1/n)。300 次零违规意味着"违规率大概率不超过 1%",不是零。这个上界的前提是固定样本量、独立同分布、无非选择性排除。

零违规次数 n95% 置信上界(约)
1026%
309.5%
1003.0%
3001.0%
10000.3%

结论有有效期。 仿真用户不是真实用户,场景来自已知失败模式,工具契约与故障频次随业务漂移。校准由事件驱动——接口变更、故障复盘、用户结构变化时触发,不按固定周期例行执行。固定周期会在两次校准之间放过漂移。

八、与现有组件的合缝 ​

仿真台不新建一套平台,而是接在现有组件上。每条接缝都要有唯一权威与失败语义:

#接缝交界字段权威失败语义
G1仿真 → SUTsut.agent、sut.boundary、入口地址、测试身份控制面(07/08)边界被替换 → 拒绝启动 422
G2仿真 → Nacos场景/资产/Agent/Prompt 的精确版本与摘要Nacos Registry解析失败 → crashed,不降级到"任意可用版本"
G3仿真 → 沙箱独立 namespace、workspace、测试账户、临时凭据沙箱管理器(21)隔离创建失败 → 不进入 RUNNING
G4仿真 → ChaosBlade目标、故障类型、时间窗、实验 IDHarness(计划)/ ChaosBlade(执行)四步记录缺一 → 该 Run 结论不可信
G5仿真 → OTeltrace_id、run_id、manifest_id、span 引用OTel采集缺失 → evidence_status=partial,不补零
G6仿真 → 对象存储Records/Artifacts 的 URI 与 sha256对象存储上传失败 → Run 标 partial
G7仿真 → EvaluationRecords/Observations/Evidence/Completeness + 评价项引用Evaluation证据不足 → 该项"不可判定"
G8Evaluation → 发布门禁场景版本、评估器版本、阈值、置信区间治理门禁版本未冻结 → 不准入
G9仿真 → 任务账本SUT 自己的 task_id/session_id,只引用不复制rt.agent_task任务账本不可用 → 仿真不接管任务状态

三条责任边界要重复强调:

  • 仿真不鉴权:所有对 SUT 的调用都走真实入口与真实鉴权,仿真只持有测试身份;
  • 仿真不采集遥测:Trace 由 OTel 产生,仿真只在记录里保存引用;
  • 仿真不评分、不发布:Run Result 是证据,Evaluation 出判定,治理门禁出授权。

九、API 契约 ​

接口放在 Service Gateway 之后,与任务 API 同一套认证;所有写接口带 Idempotency-Key。

方法与路径作用关键请求成功响应主要错误
POST /v1/sim/scenarios创建场景namespace、name、owner、risk_level201 {scenario_id}409 重名、422 参数
POST /v1/sim/scenarios/{id}/versions提交新版本version、spec、source202 {status: reviewing}409 版本已存在、422 schema
POST /v1/sim/runs启动 Runscenario_id、version、mode、seed、budget202 {run_id, manifest_id}422 场景未上线、409 同幂等键
GET /v1/sim/runs/{id}读运行状态—200 {execution_status, validity_status, evidence_status, quality_ref}404
GET /v1/sim/runs/{id}/events?after_seq=拉事件after_seq200 {events, next_seq}410 游标过期
GET /v1/sim/runs/{id}/evidence取证据包criterion(可选)200 {records, observations, evidence, completeness}409 证据未收敛
POST /v1/sim/runs/{id}:cancel取消reason202 {execution_status: cancelled}409 已终态
GET /v1/sim/manifests/{id}取冻结组合—200 {resolved, manifest_sha256}404

错误语义与任务 API 一致:409 不重试先读状态,410 重读快照,422 修正后新建,429 按 Retry-After 退避,503 带同一幂等键重试。

Run 启动是异步的:202 只表示已受理并生成 Manifest,不表示环境已就绪。客户端通过 GET /v1/sim/runs/{id} 轮询或订阅事件,不能把 202 当"已开始执行"。

十、装配顺序 ​

仿真台作为平台第七个切片(S7),依赖 S1 身份、S2 状态与沙箱、S5 供给与路由,可与 S6 证据治理并行。

步动作依赖验收
1建 sim schema 与核心表族(场景、资产、Manifest、Run、事件、记录、观测、证据、完整性、故障)PostgreSQL能创建场景、版本、Manifest、Run
2场景库接 Nacos + 对象存储08/19场景与资产可版本化、按 label 回滚
3实现 Manifest 解析器Resolver标签解析为精确版本 + sha256,写入运行记录
4接独立命名空间与测试账户21 / K8s两个 Run 之间无共享可变状态
5实现用户模拟器(状态机 + LLM + 两道校验)模型网关角色一致性与披露规则可度量
6实现环境模拟器(事实模型 + 三处超时)工具契约create 后 get 状态一致;三种超时可注入
7接 ChaosBlade,落四步故障记录ChaosBlade注入→生效→命中→恢复四步可查
8接 OTel 与证据提取14任一 Run 能还原 span 树与证据链
9接 Evaluation 与门禁14证据不足输出"不可判定",不误判

最小上手 ​

先只做一条场景、一个业务任务、两种故障,跑通"证据交付":

text
1. 建 sim schema,只实现 scenario / scenario_version / manifest / run / event / record 六张表;
2. 写一条"查询超时后恢复"的场景,SUT 只声明 reasoning 与 tool-selection 两个边界;
3. 用户模拟器先只做状态机,不接 LLM:固定三轮,第三轮给订单号;
4. 环境模拟器用规则替身:第一次 query 返回超时,第二次返回已受理;
5. Run 完成后生成 observations(退款申请数、金额、状态),不写 verdict;
6. 把 evidence 包交给人工评审,确认"材料够不够支撑结论";
7. 故意让故障未命中,验证 Run 标 `validity_status=violated` 而不是"通过"。

最小验收:同一条场景用同一 Manifest 重跑两次,Records 可逐条对齐;故意把 sut.agent 指向不存在的版本,Run 在 LOADING 阶段失败且没有产生任何 Agent 调用;故障只注入未命中时,结论不被采纳。

生产动作 ​

  • 场景先评审再上线:场景与资产和代码一样走 review,高风险场景要求风险团队签字;
  • Manifest 不可变:启动后禁止修改;需要改条件就新建版本,不能改历史 Run 的解释;
  • 替身有负责人:每个环境替身登记 owner 与契约版本,接口变更触发场景校准;
  • 故障四步留痕:没有 hit 与 recovered 的 Run,不得进入回归结论;
  • 证据分层存储:Records 长期保留,Observations 可重算,Evidence 随评价项冻结;
  • 置信区间随报告发布:零违规必须附 1 − 0.05^(1/n) 上界,禁止写成"零风险";
  • 成本按段归因:区分 SUT 执行成本与仿真平台开销(模型生成、环境步进、存储)。

边界 ​

  • 仿真不是真实用户:用户模型是假设,仿真成功率上升不能替代真实业务指标;
  • 仿真覆盖不了未知风险:场景来自已知失败模式,训练分布之外的风险仍需生产可观测兜底;
  • 全替身通过不等于真实依赖通过:不同保真度验证的系统范围不同,结论不可混用;
  • 仿真不证明合规:它提供证据材料,合规判断仍由审计与治理节点给出;
  • 仿真不替代单元测试与生产观测:确定性逻辑用单测,真实分布用生产 Trace,三者互补;
  • 加速时间有边界:业务日历时间不能被虚拟加速替代,超时与租约仍按真实语义判断。

讲清楚 ​

仿真台的价值不是"多跑几条用例",而是把 Agent 的外部世界做成可保存、可修改、可重放的软件:用户会追问、会撤回,环境会超时、会丢响应,而这些都不会产生真实后果。

它的边界只有两条:不触达未授权的生产账户与数据,不产生不可控的真实副作用。被测 Agent 的决策不能被替身接管——工具调用顺序一旦写成脚本,测的就不是 Agent 了。

一次执行的对象链是 Spec → Manifest → Run → Result:Spec 是实验设计,Manifest 锁死版本组合,Run 记录实际发生的事实,Result 交出 Records、Observations、Evidence 和完整性判断。四维状态必须分开:执行完成、仿真有效、材料齐备、任务合格是四个独立事实。

仿真交付证据,不交付结论。评估器做三道检查(材料在不在、条件成立没有、证据够不够),不够就输出"不可判定";零违规要附置信上界,不能写成零风险。这条界限守不住,仿真就会变成新的"跑分表演"。

适用:AI-Agent-Handbook 第 16 章 / AgentScope Java 2.x / Kubernetes agent-sandbox / ChaosBlade / OTel GenAI 语义约定 | 初稿 2026-10

把 AI 工程讲到既懂也会用