返回文章列表

为什么不能让 Agent 想改什么就改什么:从游戏验证项目讲 Human-in-the-loop 与安全边界

从游戏变更验证 Agent 的实际工作流出发,解释 Human-in-the-loop、确定性校验、Policy Gate、有限动作空间和运行预算分别解决什么问题,以及为什么模型输出合法并不等于可以执行。

前面写 Agent Loop 时,我提到一个基本原则:

LLM 只是 Agent 系统中的决策组件,不应该自动拥有整个系统的控制权。

但这句话如果只停留在概念上,其实还是很空。

真正实现一个能够修改配置、启动 Unity / UE5、运行测试并继续修复的 Agent 后,会遇到更具体的问题:

模型已经返回了合法 JSON,能不能直接执行?

模型认为应该修改某个参数,程序是不是就应该照做?

Candidate 已经通过静态检查,为什么还要人再授权一次?

自动运行已经全部通过,为什么 Agent 不能直接把修改正式接受?

如果 Agent 的判断和确定性规则不一致,到底听谁的?

如果连续修复都没效果,为什么不能让模型继续试?

Game Change Verification Agent 当前的回答很明确:

不能把“模型提出一个动作”直接等价成“系统批准执行这个动作”。

项目的完整主线更接近:

自然语言需求
    ↓
Requirement Agent
    ↓
候选配置
    ↓
四层确定性校验
    ↓
人工授权
    ↓
隔离引擎运行
    ↓
Telemetry
    ↓
确定性 Evaluation
    ↓
Quality Review Agent
    ↓
Policy Gate
    ↓
有限修复 / 人工复核
    ↓
最终人工接受 / 修订 / 回滚

这篇文章想讲的,就是这些边界分别在防什么。

项目地址:theHerta27/game-change-verification-agent

Human-in-the-loop 不只是“最后点一下确认”

HITL,也就是 Human-in-the-loop,很容易被理解成:

Agent 做完
    ↓
弹一个确认框
    ↓
用户点“确定”

但在真正有外部执行能力的 Agent 中,人应该出现在哪里,取决于:

哪里存在真正不可轻易撤销或需要额外授权的动作。

Game Change Agent 当前有两个非常明确的人工边界。

第一个在:

真正启动隔离引擎测试之前

第二个在:

自动证据生成完成之后

也就是说,人不是只出现在最后。

而是分别控制:

“能不能开始执行”

和:

“这个结果最终算不算被接受”

两种完全不同的权限。

第一道人工边界:Agent 生成 Candidate,不代表它可以运行

Workflow 创建以后,如果 Requirement Agent 成功生成 Candidate,并且静态校验全部通过,状态并不会直接变成:

running_candidate

而是:

awaiting_authorization

源码中:

"status": (
    "awaiting_authorization"
    if gate["decision"] == "accepted"
    else gate["decision"]
)

也就是说:

模型提出的方案即使看起来合法,系统也不会自动执行它。

真正启动引擎之前,必须显式调用:

authorize(...)

授权函数首先检查:

if manifest["status"] != "awaiting_authorization":
    raise ValueError(...)

只有当前 Workflow 正处于等待授权状态,才可以继续。

同时要求:

actor

不能为空。

授权结果也不是只保存一个:

true

而是记录:

{
  "actor": "designer",
  "note": "允许最多三轮隔离测试",
  "authorized_at": "...",
  "scope": "candidate_json_only",
  "max_unity_runs": 3
}

这里我觉得很重要的是两个字段:

scope
max_unity_runs

它表达的不是:

“我授权这个 Agent 随便做事。”

而是:

我授权它在一个明确范围和明确预算内执行。

授权应该是一份能力范围,而不是一个万能开关

当前:

scope = candidate_json_only

说明自动工作流的授权对象只是:

候选配置验证。

并不是:

修改 C#
修改 Scene
修改 Prefab
修改项目设置
运行任意程序
覆盖正式基线

Milestone 的设计也明确要求:

Agent 只生成完整 Candidate JSON,不修改 C#、Scene、Prefab 或项目设置。

所以这份授权更接近:

允许:
对候选 JSON 进行隔离运行

不允许:
任意代码变更
正式配置覆盖
任意外部程序执行

这让我觉得 Agent 权限设计里一个很重要的原则是:

不要只问“这个 Agent 有没有权限”,而要问“它具体拥有什么权限”。

权限最好能被表达成明确的 Scope。

为什么 Static Validation 通过以后还需要 Human Authorization

一个很自然的问题是:

Candidate 都已经通过程序校验了,为什么还不能直接跑?

因为:

“配置合法”

和:

“我允许你消耗真实执行资源”

不是同一个问题。

比如 Candidate 经过 Schema 可以证明:

字段类型正确
数字在允许范围内

Rule Engine 可以证明:

估算峰值没有越过明确安全上限

Safety Gate 可以证明:

没有修改授权范围之外的字段

但真正启动 Unity / UE5 仍然意味着:

启动外部进程
消耗运行时间
占用机器资源
产生 Artifact
执行真实游戏逻辑

这些是实际动作。

所以:

Validation

回答:

这个 Candidate 从规则上允许进入下一阶段吗?

Authorization

回答:

人是否真的允许这次 Workflow 开始执行?

两者不能互相替代。

模型输出合法,也不代表 Candidate 合法

Requirement Agent 使用严格 Structured Output。

模型输出最终会经过 Pydantic 校验。

例如:

class RequirementAgentOutput(StrictAgentModel):
    structured_goal: BulletHellStructuredGoal
    candidate_config: BulletHellContract

并且:

extra="forbid"

所以额外字段会被拒绝。

这已经能过滤大量:

JSON 不合法
字段缺失
类型错误
模型胡乱多输出字段

的问题。

但 Workflow 仍然继续调用:

validate_bullet_hell_proposal(
    baseline=baseline,
    candidate=candidate,
    structured_goal=goal,
)

这个函数又拆成四层:

Schema
Reference
Rule Engine
Safety Gate

为什么?

因为:

结构正确只是最浅的一层正确。

第一层:Schema 只能证明“长得像正确数据”

例如:

bullets_per_wave

应该是整数。

如果模型输出:

{
  "bullets_per_wave": "很多"
}

Schema 可以直接拒绝。

或者数字必须:

1 <= bullets_per_wave <= 64

模型返回:

100000

也会被挡住。

但假设模型返回:

bullets_per_wave = 64
wave_interval_ms = 100
bullet_lifetime_seconds = 10
bidirectional = true
layer_count = 4

每一个字段单独都可能合法。

组合起来却可能造成非常高的弹幕规模。

所以:

字段合法

不能推出:

整个配置安全

Schema 解决的是:

数据形状和局部约束。

不是完整业务安全。

第二层:Reference 防止模型引用不存在的东西

Requirement Agent 会输出:

target_phase_id

例如:

phase_2

但模型理论上也可能生成:

phase_99

字符串本身完全符合 Schema。

它仍然是:

合法字符串

但当前 Baseline 根本没有这个 Phase。

所以 Reference Layer 会检查:

模型引用的目标是否真实存在。

测试里就专门把:

goal["target_phase_id"] = "phase_missing"

再执行 Proposal Validation。

最终:

reference layer = failed

这说明:

Schema Correct

和:

Reference Valid

也是两个问题。

第三层:Rule Engine 处理字段组合产生的问题

项目中有一个:

estimate_peak_alive(...)

用于估算一个 Pattern 同时可能存活多少子弹。

大致考虑:

子弹生命周期
发射间隔
单波子弹数量
层数
是否双向

例如:

waves_alive = ceil(
    bullet_lifetime_seconds * 1000
    / wave_interval_ms
)

direction_factor = (
    2 if bidirectional else 1
)

peak =
    waves_alive
    * bullets_per_wave
    * layer_count
    * direction_factor

也就是说,Rule Engine 关注:

单个字段虽然没有越界,但它们组合后的结果是不是已经危险。

这和后端系统里:

每个参数都合法

却不代表:

整个业务请求一定合法

是同一种问题。

第四层:Safety Gate 控制“哪些东西允许 Agent 改”

Safety Gate 和前面几层最大的区别是:

它不是在问数值是不是合理,而是在问这个字段是不是属于 Agent 的权限范围。

例如测试中会主动:

candidate["boss"]["max_health"] = 7200

Requirement 本来只是:

修改弹幕 Pattern

但 Candidate 顺手把:

Boss 最大生命

也改了。

这个值从类型上完全合法。

也可能不违反普通数值范围。

但 Safety Gate 会拒绝它。

测试明确验证:

boss.max_health

出现在:

safety_errors

中。

这里的规则其实是:

模型不应该因为“我能生成这个字段”,就自动获得修改它的权限。

这也是 Guardrail 和 Schema 最明显的区别之一。

“能生成”不等于“能修改”

LLM 很容易生成:

{
  "boss": {
    "max_health": 7200
  }
}

这在生成能力上毫无难度。

但系统真正应该问的是:

本次需求允许修改 Boss Health 吗?

如果答案是:

不允许

那模型输出再合理也不能接受。

所以:

Model Capability

和:

Agent Permission

必须分开。

模型的能力往往很广。

Agent 的权限应该故意更窄。

危险需求为什么在 Unity 启动之前就必须被挡住

项目测试里有一个非常直观的需求:

每 0.02 秒发射 200 颗高速弹,越密越好。

Mock Requirement Logic 会识别这个请求超过安全边界。

最终状态:

blocked

测试还专门检查:

assert not called

也就是说:

Runner 根本没有被调用。

这点非常重要。

如果系统设计是:

先运行
    ↓
看看会不会出问题

那 Safety Gate 就失去了意义。

真正的安全检查应该尽量发生在:

产生昂贵或危险副作用之前

对于这个项目,就是:

Unity / UE5 启动之前

而不是运行崩了以后再说:

看起来这个 Candidate 不安全。

Guardrail 不应该只存在于 Prompt 里

当然也可以在 System Prompt 中写:

不要生成危险配置。
不要修改 Boss Health。
不要超过 350 个存活子弹。

这些提示有价值。

但项目没有把它们当成最终安全保障。

原因很简单:

Prompt 是给模型看的规则,不是系统强制执行的规则。

模型可能:

理解错
忘记
输出和规则冲突
模型版本变化
Prompt 被其他上下文干扰

如果一条规则真的必须满足,它最好同时存在于确定性代码中。

所以:

Prompt
→ 引导模型产生更好的输出

Validator / Safety Gate
→ 强制系统只接受合法输出

这是两个不同层次。

可以把它理解成:

Prompt 是软约束,程序校验才是硬约束。

Quality Review Agent 也不能直接控制修复

Candidate 运行以后,会得到真实 Telemetry。

然后程序做:

evaluation =
    evaluate_bullet_hell_telemetry(
        candidate,
        telemetry,
    )

这个 Evaluation 完全是确定性的。

比如:

peak_alive_bullets
actual = 420
limit = 350
passed = false

Quality Review Agent 再根据:

Requirement
Structured Goal
Config Diff
Evaluation
Repair History

输出:

accept
repair
human_review

但它的结果仍然不能直接执行。

中间还有:

Policy Gate

Policy Gate 在检查什么

Workflow 会提前用确定性规则计算:

expected_action = (
    None
    if evaluation["passed"]
    else choose_repair_action(evaluation)
)

例如:

peak_alive_bullets failed

得到:

expected_action =
REDUCE_BULLETS_PER_WAVE

如果 Agent 也输出:

decision = repair
repair_action =
REDUCE_BULLETS_PER_WAVE

Policy Gate 才允许:

effective_decision = repair

但如果模型说:

REDUCE_BULLET_SPEED

虽然:

REDUCE_BULLET_SPEED

本身也是合法的 RepairAction。

Policy Gate 仍然拒绝自动执行。

最终:

effective_decision =
human_review

原因是:

合法动作不等于当前证据下被授权的动作。

为什么 Agent Decision 不能比 Deterministic Evaluation 权限更高

假设程序已经计算:

全部硬指标通过

也就是:

evaluation["passed"] = true

这时 Agent 正确输出应该是:

accept

或者配合:

repair_action = None / STOP

如果模型反而说:

repair
REDUCE_BULLETS_PER_WAVE

Policy Gate 不会说:

模型可能发现了一些程序没发现的问题,那就继续改吧。

而是:

human_review

反过来,如果硬指标明确没有通过:

evaluation["passed"] = false

模型却说:

accept

也不能生效。

这说明项目里存在一个明确权力关系:

LLM
可以解释和建议

Deterministic Hard Checks
定义自动执行边界

Agent 不能推翻硬规则。

为什么 human_review 在任何阶段都是合法动作

Policy Gate 有一个很有意思的规则。

如果 Quality Review Agent 输出:

human_review

直接:

passed = true
effective_decision = human_review

代码里的理由是:

人工复核是任何阶段都允许的保守决策。

这其实非常重要。

一个 Agent 如果面对不确定性,却被迫:

accept 或 repair

总要二选一。

它就可能在证据不足时硬猜。

而项目允许:

我不确定
→ 交给人

作为正式动作。

所以 Human Review 不是:

系统失败以后不得已兜底

而是:

Agent Action Space 中一个正常的保守分支。

为什么“不会做决定”有时比错误决定更好

假设 Evaluation 中出现一个项目当前没有自动修复策略的失败:

phase_coverage failed

choose_repair_action() 最终会:

return "REQUEST_HUMAN"

而不是:

随便挑一个看起来可能有用的 Repair

这其实是在承认:

当前自动化系统能力范围到这里结束。

很多 Agent 系统最危险的问题并不是:

模型完全不会

而是:

模型在没有可靠依据时仍然会给一个看起来很合理的答案

在聊天场景里,这可能只是回答不准确。

在执行场景里,它可能变成真实动作。

所以:

REQUEST_HUMAN

本身就是一种安全能力。

Human-in-the-loop 和 Human-on-the-loop 有什么区别

从概念上可以粗略区分两种情况。

Human-in-the-loop

人真正处在流程路径里。

没有人的动作,Workflow 不能继续。

项目中的:

awaiting_authorization
    ↓
authorize()
    ↓
authorized

就是非常典型的 Human-in-the-loop。

Human-on-the-loop

系统可以自动工作。

人主要负责:

监督
观察
必要时介入

例如自动修复的三轮 Loop,本身不需要人每一轮都点击确认。

人在外面监督这段自动化。

Game Change Agent 实际上同时有这两种味道:

执行前
→ Human-in-the-loop

有限修复循环
→ Agent 自动执行,人主要监督边界

最终结论
→ 再次 Human-in-the-loop

所以真实系统并不一定是:

全自动

或者:

每一步都人工确认

二选一。

更合理的是根据不同风险设计不同权限。

为什么每次 Repair 不需要重新人工授权

既然人工控制很重要,另一个极端可能是:

Agent 每改一个数字
都必须等用户点击一次确认

这样当然最保守。

但自动修复能力几乎就不存在了。

Game Change Agent 选择:

用户一次授权一个有限预算。

例如:

max_unity_runs = 3

于是:

Candidate 1
    ↓
失败
    ↓
受限 Repair
    ↓
Candidate 2
    ↓
失败
    ↓
受限 Repair
    ↓
Candidate 3

在这个预算内,不需要每轮重新授权。

因为用户授权的其实不是:

某一个精确 Candidate

而是:

在已定义安全范围内
最多进行三轮候选验证

这是比:

每一步确认

更实用的 HITL 设计。

Budget 本身就是权限

Workflow Manifest 中:

{
  "budget": {
    "max_unity_runs": 3,
    "max_model_calls": 4,
    "unity_runs_used": 0,
    "model_calls_used": 1
  }
}

表面看这些像:

成本限制

但实际上它们也是:

权限限制。

Agent 不仅要知道:

我能不能运行 Unity

还要知道:

我还能运行几次

两者区别很大。

比如:

Engine Tool = allowed

并不意味着:

无限调用 Engine Tool

所以可以把权限理解成:

Capability
+
Scope
+
Budget

而不是只有一个:

allow = true

为什么 Repair Loop 到三轮必须停

Workflow:

max_runs =
    manifest["budget"]["max_unity_runs"]

for iteration in range(
    1,
    max_runs + 1,
):

所以最多三轮 Candidate Run。

如果已经:

iteration >= max_runs

还没有达到可接受状态:

budget_exhausted

自动流程结束。

系统不会:

再让模型试一轮
也许第四次就好了

因为一旦允许这种例外:

再试一次

Budget 就不再是真正的边界。

而只是一个建议。

安全边界要有意义,必须满足:

到了边界真的会停。

连续没改善也会提前停止

Workflow 还记录:

previous_score
stagnant_rounds

如果新一轮 Evaluation 并没有变得更好:

stagnant_rounds = (
    stagnant_rounds + 1
    if previous_score is not None
    and score >= previous_score
    else 0
)

当:

stagnant_rounds >= 2

也会:

budget_exhausted

也就是说即使还有形式上的剩余空间:

连续修复没有进展,也不值得继续自动探索。

这比只写:

最多三次

多了一层“有效进展”判断。

为什么实际修复数值不能由模型直接决定

即使 Policy Gate 允许:

REDUCE_BULLETS_PER_WAVE

模型也只是决定:

应该往哪个方向修。

真正修改:

apply_repair(
    candidate,
    action,
    evaluation,
)

仍然由确定性工具完成。

例如当前峰值:

actual = 420

目标:

limit = 350

程序计算:

factor = min(
    0.85,
    max(
        0.5,
        limit / actual * 0.9,
    ),
)

然后调整:

bullets_per_wave

模型不能自己写:

“我觉得改成 17 比较好。”

这再次缩小了 Agent 的权限:

模型拥有:
选择修复意图的权限

模型没有:
计算和直接写入任意数值的权限

所以 Action Space 的限制不仅是:

允许哪些动作

还可以细到:

动作的哪些参数由谁决定

为什么执行外部环境也必须是隔离的

Workflow 的 automation boundary 明确描述为:

isolated_validation_with_final_human_decision

也就是说:

Agent 自动做的是隔离验证。

不是:

直接把修改发布到真实项目

运行产生的 Evidence 都保存在:

runtime-artifacts/bullet-hell-workflows/

而正式:

configs/bullet-hell/baseline.json

保持不变。

项目测试也专门验证:

baseline_before =
    service.baseline_path.read_bytes()

...

assert (
    service.baseline_path.read_bytes()
    == baseline_before
)

这说明即使整个 Workflow 跑完:

Baseline
仍然不会被自动改写

自动化工作的对象只是:

候选副本

而不是正式状态。

为什么 accept 也不会覆盖 Baseline

这是另一个很容易忽视的细节。

Workflow 最终证据全部生成以后:

status = evidence_ready

然后人可以:

service.decide(
    workflow_id,
    decision="accept",
    actor="designer",
    note="固定轨迹证据达到当前目标",
)

状态变成:

accepted

但 Final Decision 明确记录:

{
  "baseline_overwritten": false
}

也就是说:

当前版本中,“接受结果”仍然只是记录一个人工结论。

不会自动:

candidate
    ↓
覆盖正式 baseline

为什么这么保守?

因为:

自动验证通过

并不等于:

应该修改 Git 仓库中的正式基线

后者是另一层更高权限的操作。

项目当前故意把它留在自动化边界之外。

最终 Human Decision 为什么有三种

人最终可以选择:

accept
revise
rollback

对应状态:

accepted
revision_requested
rolled_back

它们并不是让 Agent 自己决定。

这是因为自动证据回答的主要是:

固定种子
固定轨迹
固定环境

下:

性能
受击
弹幕数量
阶段覆盖
运行错误

是否满足目标。

项目自己也明确承认:

固定轨迹结果
不代表所有玩家体验

所以最终:

“这个修改是否真的适合游戏”

仍然需要人判断。

这就是 Evidence 和 Decision 的区别。

自动 Evidence 不能替代人的价值判断

例如 Candidate 可能做到:

peak_alive_bullets <= 350
low_percentile_fps >= 55
player_hits <= 3
exception_log_count = 0

所有自动指标全部通过。

程序可以说:

当前定义的硬指标通过

但它不能严格证明:

弹幕很好看
玩起来有趣
难度合理
符合策划审美
玩家会喜欢

这些问题不属于当前 Telemetry 的测量范围。

所以项目在自动化后仍然保留:

manual_play

以及:

Final Human Decision

这不是因为 Agent:

还不够先进

而是因为:

有些决策本来就不是当前自动证据能够证明的。

Guardrail 最重要的是知道自己没验证什么

项目的 Evaluation 返回中专门写:

evidence_scope =
固定种子与固定轨迹下的可重复运行证据,
不代表所有玩家体验。

我觉得这个字段其实非常重要。

因为一个系统最危险的状态之一就是:

测了一个指标
    ↓
却把结论扩大到没有测过的范围

例如:

固定轨迹没有被击中

不能推出:

所有玩家都能躲开
低分位 FPS >= 55

也不能推出:

所有机器都能稳定运行

所以 Guardrail 不只是:

阻止危险动作

还有一部分是:

限制系统能够声称的结论范围。

Error 也应该 Fail Closed,而不是让 Agent 猜

如果 Requirement Agent 返回非法输出:

JSON parse error
Schema validation error
Provider error

Workflow 不会说:

先用一个默认 Candidate 继续跑

而是:

blocked

并保存:

error_type
error_message
raw_model_output
provider
model
stage

Quality Review Agent 出错也是一样。

Workflow:

blocked

而不是:

“Agent 挂了,那系统替它猜一个 Repair Action。”

这是一种 Fail Closed 设计。

因为在这条 Workflow 里:

“不知道模型真正想表达什么”

不能被解释成:

“那就继续执行一个近似动作”

Agent 和 Policy 不一致时为什么不能自动纠正 Agent

假设:

Expected:
REDUCE_BULLETS_PER_WAVE

Agent:
REDUCE_BULLET_SPEED

一种设计是:

Agent 说错了
那程序自动替它改成 Expected Action
然后继续

项目没有这么做。

而是:

human_review

为什么?

因为如果程序最终总会无条件覆盖模型:

那这一轮 Agent Decision 本身就没有实际意义。

更重要的是,Agent 与 Policy 出现冲突:

本身就是值得调查的信号

可能是:

Prompt 理解有问题
Evidence 表达不清
模型能力不足
规则策略过于死板

直接偷偷纠正,会把这个问题隐藏掉。

转人工反而能保留:

“模型和确定性策略产生分歧”

这一条 Evidence。

Guardrail 不只是输入过滤器

很多时候说 Guardrail,会先想到:

敏感词过滤
Prompt Injection 检测
内容审核

但在 Agent 工程里,我觉得 Guardrail 范围更广。

它可以出现在整个 Action Lifecycle 中。

Before Decision

限制:

模型看到哪些工具
模型允许输出哪些 Action

After Model Output

检查:

Schema
Reference
Policy
Safety Scope

Before Tool Execution

检查:

是否已经授权
预算还有没有
当前状态能不能执行
参数是否合法

After Tool Execution

检查:

Telemetry 是否完整
Evidence 是否对应当前 Candidate
指标是否通过

Before Final Commit

检查:

是否需要最终人工决定

所以 Guardrail 不是一个:

Agent 前面的 Filter

而更像:

贯穿整个执行链路的边界集合。

状态机本身也是安全机制

Workflow 不能从任意状态调用任意动作。

例如:

authorize()

只接受:

awaiting_authorization

start() 只接受:

authorized

accept 只允许:

evidence_ready

如果尝试:

create
    ↓
直接 start

测试要求:

ValueError

因此:

状态机

不只是为了前端显示:

当前跑到哪一步

它也在限制:

什么动作在当前阶段是合法的。

这和订单系统里的:

未支付
→ 已发货

不应该直接跳转,是同样的思想。

为什么 Agent 权限最好和 Workflow State 绑定

比如:

awaiting_authorization

时:

run_engine

不应该可用。

authorized

以后才可以执行。

evidence_ready

以后:

accept

才合法。

所以从 Agent Tool 的角度,可以理解成:

不同 State
→ 不同 Tool Availability

这比:

Agent 从头到尾永远拥有全部工具

更可控。

如果未来真的把 EngineRunner 做成 MCP Tool,我仍然会希望:

Workflow State

决定这个 Tool 当前能不能执行。

而不是 MCP Tool 一注册,任何阶段都可以调用。

怎样验证这些边界真的存在

项目的测试并不只验证:

正常 Workflow 最后成功

而是专门覆盖很多“不能发生”的路径。

未授权不能运行

Workflow 创建后:

status =
awaiting_authorization

直接:

service.start(...)

必须抛错。

危险 Candidate 不启动 Runner

输入:

每0.02秒发射200颗高速弹

Workflow:

blocked

并验证:

runner never called

Baseline 永远不被自动修改

测试前保存:

baseline bytes

Workflow 创建、运行后重新比较。

必须完全一致。

自动修复必须有上限

首次 Candidate 故意:

peak_alive_bullets = 420

Workflow 自动选择:

REDUCE_BULLETS_PER_WAVE

再运行下一轮。

最后测试:

unity_runs_used = 2

并检查 Repair History。

Agent Decision 必须经过 Policy Gate

最终 Quality Review 中保存:

policy_gate

测试会检查:

effective_decision

而不是只看:

agent_output

Accept 不能覆盖正式基线

最后:

accepted

但:

baseline_overwritten = false

这些测试让我觉得:

安全边界真正值得测试的不是“系统能做什么”,而是“系统明确不能做什么”。

面试时我会怎样回答

如果面试官问:

你这个 Agent 的 Human-in-the-loop 是怎么做的?

我会回答:

我把人工节点放在两个真正的权限边界上。Requirement Agent 生成 Candidate 并通过 Schema、引用、规则和 Safety Gate 后,Workflow 仍然只进入 awaiting_authorization,必须由人显式授权一次最多三轮的隔离验证预算,才能启动 Unity 或 UE5。

自动验证和有限修复完成后,系统最多进入 evidence_ready,不会自动覆盖正式 Baseline。最终还要由人选择 accept、revise 或 rollback,而且当前即使 accept,也只记录人工结论,baseline_overwritten 仍然是 false。

如果继续问:

为什么模型输出已经符合 Schema,还要做 Safety Gate?

我会回答:

Schema 只能保证数据结构和字段局部范围正确,不能保证模型修改的是被授权的业务范围。比如项目里如果需求只是修改第二阶段弹幕,模型却顺手修改 boss.max_health,这个字段类型完全合法,但 Safety Gate 会拒绝,因为它超出了本次 Agent 的修改权限。

如果继续问:

Quality Review Agent 为什么不能直接执行 Repair?

我会回答:

Agent 只能输出受限的 Repair Action,后面还有确定性 Policy Gate。程序会根据真实 Evaluation 计算 Expected Action,只有模型建议与这个失败路由一致才自动执行,否则直接转 human_review。真正修改多少数值也不是 LLM 算,而是确定性 Repair Function 计算。

如果继续问:

为什么不让 Agent 多修几轮,直到成功?

我会回答:

因为自主执行必须有预算。当前一次人工授权最多三次 Candidate Engine Run、四次 Model Call,而且连续两轮没有改善会提前停止。预算不仅控制成本,也是权限边界,避免模型在没有进展时无限试错。

如果继续问:

Guardrail 是不是就是 Prompt 里写“不要做某些事情”?

我会回答:

我不会把 Prompt 当成硬安全边界。Prompt 可以引导模型,但真正必须满足的约束都应该由程序再次校验。这个项目里从 Structured Output、四层 Proposal Validation、Workflow 状态、Authorization、Policy Gate、Budget 到最终 Human Decision,都属于不同阶段的 Guardrail。

我从这个问题中理解到的事情

刚开始接触 Agent 时,我比较容易把:

Human-in-the-loop

理解成:

模型做完以后让人检查一下。

但真正把 Agent 接到外部执行环境以后,我觉得更准确的问题其实是:

哪些控制权可以交给模型,哪些控制权不能交?

Game Change Agent 当前给模型的能力包括:

理解自然语言需求
生成受限 Candidate
根据真实 Evidence 提出 Accept / Repair / Human Review
选择有限 Repair Intent

但没有交给模型:

绕过静态校验
自行扩大修改范围
自己增加运行预算
任意启动程序
直接决定精确修复数值
推翻确定性硬指标
自动覆盖正式 Baseline

整个权限关系可以概括成:

Structured Output
→ 限制模型输出形状

Schema
→ 保证字段和局部范围合法

Reference Check
→ 保证引用对象真实存在

Rule Engine
→ 检查组合后的业务风险

Safety Gate
→ 限制模型允许修改的范围

Workflow State
→ 限制每个阶段可以执行的动作

Human Authorization
→ 外部执行之前授予有限权限

Budget
→ 限制自主执行次数

Deterministic Evaluation
→ 用真实运行证据判断硬指标

Policy Gate
→ 模型建议不能越过确定性策略

Deterministic Repair
→ 模型选方向,程序算精确数值

Human Review
→ 不确定时允许主动退出自动化

Final Human Decision
→ 自动证据不能直接变成正式业务决定

所以我现在觉得一个 Agent 的“自主性”并不是:

它能自己做多少事情

这么简单。

更重要的是:

系统是否能明确说出:它在哪些边界以内可以自己做决定,越过哪些边界以后必须停下来。

如果没有这些边界:

Tool Calling
Agent Loop
自动修复

能力越强,错误执行的影响范围反而越大。

而 Human-in-the-loop 真正解决的也不是:

“人类比模型聪明,所以最后让人看一下。”

它更像是一种权限设计:

把高风险、不可完全由自动证据证明、或者自动策略已经失去确定性的决策,明确留给人。

这也是我在这个项目里对 Agent Guardrail 最直观的理解:

一个可控的 Agent,不是永远知道下一步该做什么,而是知道什么时候它已经没有资格继续自动做决定。

延伸阅读

  • Anthropic:Building Effective Agents
  • OpenAI:Structured Outputs
  • NIST AI Risk Management Framework
  • OWASP:Agentic AI Threats and Mitigations