为什么不能让 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