Agent 到底在循环什么:从手写有界 Agent Loop 看模型、工具与状态
从游戏变更验证 Agent 的真实工作流出发,解释 Agent Loop 中模型、状态、工具、观察结果与终止条件分别承担什么职责,以及为什么真正可控的 Agent 必须有边界。
在做 Game Change Verification Agent 之前,我对 Agent 的理解比较容易停留在一句话:
LLM 不只是回答问题,还可以调用工具。
例如模型说:
我要查询天气
系统执行天气工具,再把结果返回给模型。
模型继续思考。
这当然已经包含 Agent 的核心特征,但真正开始写一个可以反复运行、修改外部系统、根据结果继续行动的 Agent 后,我发现:
“会调用工具”只是 Agent Loop 中的一部分。
还需要回答:
模型现在知道什么?
下一步由谁决定?
工具执行以后结果保存在哪里?
什么时候继续循环?
什么时候必须停下来?
模型能直接修改什么?
模型输出错误怎么办?
循环最多跑多少次?
什么时候必须交给人?
这些问题决定了一个 Agent 是:
能够被工程系统控制的执行流程
还是:
一个可能不断调用模型和工具的 while true
Game Change Verification Agent 的主要场景是:
策划用自然语言描述一次弹幕 Boss 战修改,系统生成候选配置,在隔离的 Unity / UE5 环境中运行修改前后版本,采集 Telemetry,根据运行证据判断修改是否达标;如果没有达标,可以在有限动作空间中修复并重新测试。
整个过程不是让模型直接操作 Unity,更不是让模型随便修改游戏代码。
它是一条手写的、有预算、有状态、有确定性验证、有人工边界的 Agent Loop。
这篇文章从这条真实工作流出发,整理我对 Agent Loop 的理解。
项目地址:theHerta27/game-change-verification-agent
如果只有一次 LLM 调用,还算不算 Agent
假设策划说:
把第二阶段改成更密集的双向螺旋弹幕,
但是峰值存活子弹不能超过 350,
低分位 FPS 不能低于 55,
玩家固定轨迹下最多受击 3 次。
最简单的大模型程序可以这样:
Requirement
↓
LLM
↓
JSON Config
例如模型直接返回:
{
"phase_2": {
"pattern": "spiral",
"bullets_per_wave": 30
}
}
这更像:
一次结构化生成。
模型输入一次,输出一次,流程结束。
但如果候选配置拿去真实运行以后发现:
峰值存活子弹 = 430
FPS = 49
系统怎么办?
如果它只是:
生成一次
→ 结束
就没有后续行为。
真正的 Agent 场景更接近:
理解目标
↓
提出行动
↓
执行
↓
观察结果
↓
根据结果决定下一步
↓
再次行动
也就是:
Observe
→ Decide
→ Act
→ Observe
→ Decide
→ Act
...
循环真正出现以后,才会开始遇到 Agent 工程里最重要的问题:
这个循环由谁控制?
Game Change Agent 实际上有两个不同的 Agent
项目的弹幕验证主流程中,没有让一个模型既负责理解需求,又负责最后裁判一切。
而是明确区分:
Requirement Agent
和:
Quality Review Agent
两个角色。
Requirement Agent
负责:
自然语言需求
↓
结构化目标
+
完整候选配置
它的输出类型是严格定义的:
class BulletHellStructuredGoal(StrictAgentModel):
target_phase_id: str
requested_pattern: PatternType
increase_pressure: bool
preserve_visual_style: bool
constraints: SafetyConstraints
source_text: str
最终还必须同时输出:
class RequirementAgentOutput(StrictAgentModel):
structured_goal: BulletHellStructuredGoal
candidate_config: BulletHellContract
也就是说模型不能只说:
“我觉得可以增加一些子弹。”
它必须给出系统可以继续处理的结构化结果。
Quality Review Agent
它不再负责生成完整配置。
它看到的是:
原始需求
结构化目标
Config Diff
真实运行 Evaluation
修复历史
然后只能输出:
class QualityReviewOutput(StrictAgentModel):
decision: Literal[
"accept",
"repair",
"human_review",
]
repair_action: RepairAction | None
reason: str
evidence_refs: list[str]
它回答的是:
根据真实运行证据,下一步应该接受、修复,还是交给人?
所以两个 Agent 的职责不同:
Requirement Agent
→ 把自然语言目标变成候选方案
Quality Review Agent
→ 根据运行证据提出下一步决策
它们之间不是互相聊天。
而是由外部 Workflow 串起来。
真正控制 Agent Loop 的不是模型
这是这个项目里我觉得最重要的一点。
循环核心在 Python Workflow 中,大致结构是:
for iteration in range(1, max_runs + 1):
validate(candidate)
telemetry = run_engine(candidate)
evaluation = evaluate(telemetry)
review = quality_review_agent.review(
requirement,
structured_goal,
diff,
evaluation,
repair_history,
)
if review == "accept":
break
if review == "human_review":
break
candidate = apply_repair(
candidate,
action,
evaluation,
)
所以整个流程并不是:
LLM:
“我想再运行一次 Unity。”
系统:
“好的。”
LLM:
“我想改这个数字。”
系统:
“好的。”
LLM:
“再执行一次。”
系统:
“好的。”
真正决定:
什么时候可以运行
什么时候调用模型
什么时候执行修复
什么时候结束循环
的是确定性的 Workflow 代码。
模型只是被放在其中几个明确节点上。
因此我现在更愿意把 Agent 系统理解成:
LLM
不是整个 Agent
LLM
只是 Agent 中负责部分决策的组件
外面还必须存在:
State
Workflow
Tools
Validation
Budget
Termination
Human Boundary
一个 Agent Loop 到底由什么组成
用这个项目来拆,可以把 Agent Loop 简化成五个部分。
State
↓
Model / Policy
↓
Action
↓
Tool / Environment
↓
Observation
↓
重新进入 State
对应到项目:
当前 Candidate + 历史证据
↓
Quality Review Agent
↓
Repair Action
↓
确定性 Repair Tool + Unity
↓
Telemetry / Evaluation
↓
更新 Candidate 和 History
然后再循环。
所以循环的真正核心不是:
模型重复调用
而是:
系统状态经过一次行动以后发生变化,再把新的状态交给下一轮决策。
State 为什么这么重要
如果 Agent 每轮都只看到一句:
“刚才失败了,再试试。”
它其实并不知道之前发生了什么。
Game Change Workflow 会保存很多明确状态。
例如:
requirement.json
structured_goal.json
baseline_config.json
candidate_config.json
candidate_history.json
config_diff.json
validation_results.json
comparison_report.json
repair_history.json
quality_reviews.json
agent_runs.json
workflow_manifest.json
其中:
candidate_config
表示当前正在验证的候选。
repair_history
表示以前已经做过哪些修复。
comparison_report
记录当前候选与 Baseline 的运行比较。
workflow_manifest
保存:
当前状态
运行次数
模型调用次数
当前 iteration
授权信息
最终人工决策
所以 Agent 的“记忆”并不一定意味着:
给模型一个无限增长的聊天记录
很多时候更可靠的方式是:
把真正需要的状态显式存储成结构化数据。
下一轮只把需要的部分重新提供给模型。
为什么不能只依赖 LLM 的上下文窗口保存状态
如果整个 Workflow 都依赖:
messages = [
第一轮所有内容,
第二轮所有内容,
第三轮所有内容,
...
]
会产生几个问题。
第一:
状态越来越长
Token 成本不断增加。
第二:
真正重要的数据
混在自然语言历史里
程序很难准确知道当前事实是什么。
第三:
如果进程重启:
内存里的消息历史
可能直接丢失。
而这个项目把关键事实写成:
{
"current_iteration": 2,
"status": "repairing",
"budget": {
"max_unity_runs": 3,
"unity_runs_used": 2,
"max_model_calls": 4,
"model_calls_used": 3
}
}
程序可以明确读取:
现在已经跑了两轮。
而不是让模型自己从几千 Token 对话中推断:
我好像之前运行过两次。
所以:
Conversation History 和 Workflow State 并不是一回事。
Structured Output 为什么是 Agent 工程的基础能力
Requirement Agent 的 Prompt 明确要求:
Return exactly one JSON object
而且最终还要经过:
RequirementAgentOutput.model_validate(
json.loads(raw_output)
)
如果模型返回:
我认为第二阶段改成 spiral 比较合适。
下面是我的建议……
哪怕语言上完全正确,也不能进入后续 Workflow。
因为系统需要的是:
{
"structured_goal": {...},
"candidate_config": {...}
}
Quality Review Agent 同样必须返回:
{
"decision": "repair",
"repair_action": "REDUCE_BULLETS_PER_WAVE",
"reason": "...",
"evidence_refs": [...]
}
而不是:
“我觉得可以稍微减少一点子弹。”
这让我更容易理解 Structured Output 在 Agent 中的价值:
自然语言是给人理解的,结构化输出才方便程序继续执行。
如果模型的下一步结果要被代码消费,它最好尽可能进入一个明确 Contract。
extra="forbid" 在防什么
项目里的 Agent Schema 使用:
class StrictAgentModel(BaseModel):
model_config = ConfigDict(
extra="forbid"
)
也就是说模型如果偷偷多返回:
{
"decision": "repair",
"repair_action": "...",
"run_shell_command": "..."
}
这种 Schema 之外的字段:
不会被静默接受
而是验证失败。
这个细节很小,但它表达的是:
Agent 输出能力应该来自显式 Contract,而不是模型临时想到什么字段就接受什么字段。
在普通聊天里:
多说一句
通常没什么问题。
在自动执行系统里:
多出一个字段
可能意味着程序开始解释一个本来没有授权的动作。
Agent 给出的候选为什么不能直接运行
Requirement Agent 得到 Candidate 后,Workflow 不会马上:
启动 Unity
而是先执行四层确定性检查。
源码中明确写的是:
validate_bullet_hell_proposal(
baseline=baseline,
candidate=candidate,
structured_goal=goal,
)
主要检查:
Schema
Reference
Rule Engine
Safety Gate
Schema
首先验证:
字段有没有缺
类型对不对
数值范围是否合法
额外字段是不是出现了
例如:
bullets_per_wave: int = Field(
ge=1,
le=64,
)
所以模型不能输出:
bullets_per_wave = 100000
然后期待引擎帮它试试看。
Reference
检查模型引用的对象是否真的存在。
例如:
target_phase_id = phase_2
必须能在 Candidate 里找到。
Pattern 也必须属于系统支持的:
ring
aimed_fan
spiral
petal
否则模型可能提出一个听起来合理但系统根本没有实现的能力。
Rule Engine
即使单个字段都在合法范围内,组合起来仍然可能产生危险结果。
例如:
每波子弹数
×
发射频率
×
生命周期
可能让同时存活子弹远超安全边界。
所以程序还会根据配置估计:
estimated_peak_alive_bullets
并与:
max_alive_bullets
比较。
Safety Gate
还会限制模型能修改哪些内容。
例如阶段 ID 不允许:
新增
删除
重命名
模型的 Candidate 也不能随意越过允许的配置范围去修改其他东西。
这让我觉得:
LLM 的输出通过 Schema,只能证明“格式合法”,不能证明“行为安全”。
Structured Output 只是第一层。
为什么 Agent 不能直接决定修复数值
这是这个项目里我特别喜欢的一条边界。
Quality Review Agent 可以说:
REDUCE_BULLETS_PER_WAVE
但它不能直接说:
把 bullets_per_wave 从 32 改成 17。
Prompt 里明确要求:
Do not change configuration values and do not calculate replacement numbers.
也就是说模型只能选择:
修复意图。
真实数值由确定性函数计算:
def apply_repair(
contract,
action,
evaluation,
):
例如峰值存活子弹超标时:
actual = max(
float(check.get("actual") or 1),
1,
)
limit = candidate[
"runtime_targets"
][
"max_alive_bullets"
]
factor = min(
0.85,
max(
0.5,
limit / actual * 0.9,
),
)
pattern["bullets_per_wave"] = max(
1,
round(
pattern["bullets_per_wave"]
* factor
),
)
也就是说:
LLM
→ “应该降低单波子弹数”
确定性程序
→ “根据实际超标幅度计算应该降低多少”
这把:
策略判断
和:
精确数值执行
分开了。
为什么这种划分比“让 LLM 自己算”更稳
假设实际 Telemetry:
peak_alive_bullets = 430
目标:
<= 350
模型当然也可以被要求:
计算一个新的 bullets_per_wave。
但模型并不是为严格数值计算设计的。
不同调用还可能得到:
18
21
20
24
不同建议。
而确定性函数面对同样输入:
永远得到同样输出
因此项目的基本原则是:
LLM
负责模糊理解和策略选择
Deterministic Tool
负责精确计算和受约束修改
这也是我现在理解 Agent 工具设计时很重要的一点:
不是能让模型做的事情都应该让模型做。
能可靠写成程序的部分,通常更适合留给程序。
Quality Review Agent 甚至不能随便选择 Repair Action
项目允许的 Repair Action 是一个固定集合:
RepairAction = Literal[
"REDUCE_BULLETS_PER_WAVE",
"INCREASE_WAVE_INTERVAL",
"REDUCE_BULLET_SPEED",
"REDUCE_BULLET_LIFETIME",
"REDUCE_PATTERN_LAYERS",
"PRESERVE_VISUAL_STYLE",
"REQUEST_HUMAN",
"STOP",
]
但即使模型返回的是这个集合里的合法值,也不代表一定会执行。
因为后面还有:
Policy Gate
确定性程序会根据 Evaluation 自己计算:
expected_action = choose_repair_action(
evaluation
)
例如:
peak_alive_bullets failed
系统期望:
REDUCE_BULLETS_PER_WAVE
如果 Agent 却建议:
REDUCE_BULLET_SPEED
即使这也是合法 Action,Policy Gate 仍然会认为:
不匹配
最终:
转人工复核
而不是执行模型建议。
为什么 Structured Output 通过了还要 Policy Gate
因为这里有两个不同层面的正确性。
第一层:
Schema Correctness
回答:
模型输出格式是不是合法?
例如:
{
"decision": "repair",
"repair_action": "REDUCE_BULLET_SPEED"
}
完全符合 Schema。
第二层:
Decision Correctness
回答:
在当前证据下,这个动作是不是被系统策略允许?
如果真实失败项是:
peak_alive_bullets
确定性策略认为应该:
REDUCE_BULLETS_PER_WAVE
模型却选:
REDUCE_BULLET_SPEED
那:
格式合法
≠
决策可执行
所以 Agent 的输出必须再经过 Policy Gate。
这让我觉得 Agent 工程里很重要的一条原则是:
能 Parse,不等于能 Execute。
Tool Calling 在这个项目里其实是“受控的”
很多 Agent Framework 中,模型会直接返回类似:
tool_call:
name = run_unity
args = ...
然后框架自动执行。
这个项目的 Bullet Hell 主流程目前并不是这种设计。
模型不会直接获得:
run_unity()
或者:
apply_config()
这种无限工具权限。
而是 Workflow 自己决定:
现在该不该运行 Engine
现在是否允许 Apply Repair
下一步调用哪个确定性函数
例如:
Quality Review Agent
↓
repair_action
↓
Policy Gate
↓
apply_repair()
所以更准确地说:
模型提出 Action,Workflow 根据状态与 Policy 决定是否调用 Tool。
而不是:
模型想调用什么就调用什么。
这一点对于理解后面的 MCP 和 Tool Calling 会很重要。
Engine 本身也是一种 Tool
从 Agent 的角度看,Unity / UE5 可以看成一个非常重的外部工具。
输入:
candidate config
seed
run mode
执行:
真实游戏 Runtime
输出:
Telemetry
Screenshots
Logs
整个关系其实就是:
Agent Workflow
↓
EngineRunner
↓
Unity / UE5
↓
Observation
项目定义统一:
EngineRunner
接口,使 Workflow 不需要知道底层:
Unity 怎么启动
UE5 怎么启动
命令行参数长什么样
它只关心:
给定一个 Candidate
能不能运行
返回什么 Evidence
这正是 Tool Abstraction 的意义:
Agent 层关心能力,不应该和具体执行实现紧耦合。
Observation 为什么不能只是一句“运行成功”
运行 Candidate 后,系统不会只得到:
success
而是 Telemetry:
total_bullets_spawned
peak_alive_bullets
player_hits
player_survival_seconds
phase_results
average_fps
low_percentile_fps
minimum_fps
exception_log_count
然后再由:
evaluate_bullet_hell_telemetry(
candidate,
telemetry,
)
转成确定性 Evaluation。
因此下一轮 Quality Review Agent 看到的不是:
“游戏感觉有点卡。”
而是类似:
peak_alive_bullets
actual = 430
limit = 350
passed = false
low_percentile_fps
actual = 49
limit = 55
passed = false
这其实就是 Agent Loop 中:
Observation。
没有 Observation:
Action
→ Action
→ Action
只能叫连续执行。
只有外部环境执行后产生结果,并重新进入下一轮决策:
Action
→ Environment
→ Observation
→ Decision
才真正形成闭环。
为什么 Baseline 和 Candidate 要用同一个 Seed
如果 Baseline:
random_seed = A
Candidate:
random_seed = B
那么两次运行的变化可能来自:
配置变化
+
随机行为变化
就很难判断指标差异到底是谁造成的。
所以工作流运行:
self._run_automated(
baseline,
...,
20260727,
)
Candidate 同样:
self._run_automated(
candidate,
...,
20260727,
)
也就是:
同一 Player
同一 Seed
同一固定移动轨迹
同一运行时长
再比较修改前后。
这让我觉得一个 Agent 有工具还不够。
如果 Tool 输出本身高度随机:
Observation
就不稳定。
Agent 下一轮决策也会跟着漂移。
所以对可以控制的环境:
尽量让 Observation 可重复,也是一种 Agent 工程能力。
Loop 为什么一定要有 Termination Condition
最危险的 Agent Loop 写法大概是:
while True:
result = agent.run(state)
if result.needs_repair:
state = repair(state)
continue
如果 Agent 永远觉得:
还可以再优化一点
它就可以无限运行。
于是:
LLM 调用次数
Engine 运行次数
Token
时间
费用
都没有上限。
Game Change Workflow 从一开始就把预算写进:
manifest["budget"] = {
"max_unity_runs": 3,
"max_model_calls": 4,
...
}
其中:
Requirement Agent
最多消耗 1 次模型调用
Quality Review Agent
最多跟随 3 轮 Candidate
所以整个主要流程最多:
4 次 Model Call
3 次 Candidate Engine Run
这就是:
Bounded Agent Loop。
为什么预算不仅是控制费用
模型调用预算看起来首先是在控制:
API Cost
但它还有更重要的意义。
它定义:
Agent 最多拥有多少次自主修复机会。
例如:
Iteration 1
→ failed
→ repair
Iteration 2
→ failed
→ repair
Iteration 3
→ still failed
系统不会:
继续 Iteration 4
继续 Iteration 5
直到模型满意
而是进入:
budget_exhausted
也就是说:
预算本身就是安全边界的一部分。
项目还有“停滞”终止条件
即使还没有完全用完三轮,如果修复没有实际改善,也不应该无意义地继续。
Workflow 会计算 Evaluation Score。
如果连续修复:
没有改善
stagnant_rounds 会增加。
当:
stagnant_rounds >= 2
同样结束自动修复:
budget_exhausted
这避免了:
Agent
→ Repair A
→ 没改善
Agent
→ Repair B
→ 还是没改善
Agent
→ 继续换动作碰运气
所以终止条件不仅可以是:
次数到了
还可以是:
继续执行已经没有有效进展
Human-in-the-loop 不只是最后点一下“确认”
这个 Workflow 里至少有两个明显的人工边界。
第一个:执行引擎之前
Requirement Agent 生成 Candidate,并通过静态校验以后,状态是:
awaiting_authorization
用户必须显式调用:
authorize
才能变成:
authorized
授权信息还会记录:
{
"actor": "...",
"scope": "candidate_json_only",
"max_unity_runs": 3
}
也就是说:
模型生成了合法 Candidate,并不等于它自动获得运行外部环境的权限。
这是:
行动前授权。
第二个:最终接受结果
自动工作流最多只能进入:
evidence_ready
最终仍需要人做:
accept
revise
rollback
而且即使选择:
accept
当前实现也明确:
baseline_overwritten = false
不会因为人点击接受就自动覆盖正式仓库 Baseline。
这是:
结果后决策。
所以 HITL 不是简单的:
流程最后弹一个确认框
而是应该根据风险,把人工节点放在真正重要的权限边界上。
Agent 自己也可以主动要求 Human Review
Quality Review Agent 的输出允许:
human_review
而 Policy Gate 规定:
人工复核在任何阶段都是允许的保守决策。
如果证据:
缺失
冲突
超出允许修复动作
Agent 可以:
REQUEST_HUMAN
而不是必须硬猜下一步。
另外,如果 Agent 建议和确定性 Policy 不一致:
Agent:
REDUCE_BULLET_SPEED
Policy:
应该 REDUCE_BULLETS_PER_WAVE
系统也不会替模型找理由。
而是自动:
human_review
这让我觉得一个好的 Agent 系统应该允许:
“我不知道,交给人”也是一个合法动作。
如果 Agent 被迫任何时候都必须:
choose an action
反而容易把不确定性变成错误执行。
Agent Loop 不是越 Autonomous 越好
很多 Agent Demo 会强调:
完全自主
自动完成
无需人工
但这个游戏验证项目反而刻意限制:
模型不能直接修改正式 Baseline
模型不能直接运行任意程序
模型不能自己生成任意 Repair Tool
模型不能自行增加运行预算
模型不能绕过 Validation
模型不能在 Evidence 不足时强行 Accept
甚至:
第一版不允许 Agent 修改 C#
模型只在一个非常明确的空间里行动。
这其实让我重新理解了:
Agent Autonomy。
自主性不是:
给模型最多权限。
更准确地说是:
在一个定义清楚的边界里,允许它自己选择部分行动。
边界越清楚,系统反而越容易真的自动运行。
Agent 和 Workflow 到底有什么区别
如果把这套系统完全写死:
如果 peak bullets 超标
→ 减 bullets
如果 FPS 低
→ 增加 interval
整个过程甚至不需要 LLM。
它就是:
Workflow。
而加入 Agent 的地方在于:
自然语言需求理解
复杂上下文综合判断
基于证据选择下一步意图
这些不容易全部提前写成固定 if/else 的部分。
所以我的理解是:
Workflow
→ 定义允许怎样流转
Agent
→ 在某些节点处理难以完全规则化的决策
在这个项目中,两者是组合关系。
并不是:
用了 Agent
→ 就不需要 Workflow
恰恰相反:
真正需要执行外部动作的 Agent,通常更需要明确 Workflow。
为什么我没有直接用 LangGraph 编排这条主流程
这个项目的 Bullet Hell 主流程目前是:
手写 Python Workflow
并没有为了“像 Agent 项目”而强行加入 LangGraph。
因为当前状态转换其实非常明确:
awaiting_authorization
→ authorized
→ running_baseline
→ running_candidate
→ repairing
→ evidence_ready / blocked / budget_exhausted
修复循环最大也只有:
3 次
所以直接用普通 Python:
for
if
state
function call
已经能很清楚地表达控制关系。
这也让我觉得:
Agent Framework 是一种工程工具,不是 Agent 成立的必要条件。
真正需要先搞清楚的还是:
State 是什么?
Node 做什么?
Edge 怎么走?
谁决定下一条 Edge?
如何保存状态?
怎样退出?
这些关系搞清楚以后,再换成 StateGraph 之类的框架,主要是在改变:
编排表达方式
而不是突然让普通程序“变成 Agent”。
为什么这个项目也没有直接用 MCP
当前 Bullet Hell 主流程同样没有接 MCP。
EngineRunner、Validator、Repair Function 都是:
项目内部 Python 接口
而不是 MCP Server 暴露出来的 Tool。
这并不妨碍它构成 Agent Workflow。
因为:
MCP 解决的是工具和上下文怎样以标准协议暴露给 AI 应用。
Agent Loop 解决的是:
模型、状态、动作、工具结果怎样形成一个决策闭环。
两者是不同层次的问题。
关于:
Tool Calling
EngineRunner
MCP
三者到底是什么关系,我会单独写下一篇。
Error 为什么也必须成为状态的一部分
模型可能返回:
非法 JSON
或者:
JSON 合法
但不符合 Pydantic Schema
Requirement Agent 会抛出:
BulletHellAgentError
Workflow 会保存:
error_type
error_message
raw_model_output
provider
model
stage
进入:
badcases.json
Quality Review Agent 同样如此。
这意味着一次 Agent Failure 不是:
print(error)
然后结束
而是变成可检查 Evidence。
因为如果一个 Agent 系统以后要迭代 Prompt 或模型,一个很重要的问题就是:
它到底在哪些输入上失败?
没有保存:
Raw Output
Stage
Error Type
就很难真正分析。
Trace 为什么不能只记录聊天内容
项目中的:
agent_runs.json
会记录每一次 Agent:
agent_name
prompt_name
provider
model
latency_ms
usage
token_estimate
status
model_call
input_artifacts
output_artifacts
iteration
Workflow Manifest 还记录时间线:
Requirement structured
Feasibility Gate
Candidate engine run
Quality Review Agent
Bounded repair policy
Final human decision
因此 Trace 不只是:
User 说了什么
Assistant 回了什么
还应该回答:
哪一个 Agent 被调用?
用了哪个 Prompt?
读了哪些 Artifact?
输出到哪里?
第几轮?
耗时多少?
最终走向哪个状态?
这对于调试 Agent 比保存纯聊天记录更有意义。
一个完整循环实际发生了什么
假设需求是:
第二阶段改成更密集的螺旋弹幕,但不能突破性能和受击边界。
第 0 步:Requirement Agent
输入:
Requirement
Baseline Config
输出:
Structured Goal
Candidate Config
第 1 步:Deterministic Validation
检查:
Schema
Reference
Rules
Safety
如果失败:
blocked
不会运行引擎。
第 2 步:Human Authorization
Candidate 合法后:
awaiting_authorization
人明确授权:
最多三次隔离测试
第 3 步:Baseline
固定:
Seed
Trajectory
Duration
Engine
运行原始版本。
得到:
Baseline Telemetry
第 4 步:Candidate
运行 Candidate。
得到:
Candidate Telemetry
程序比较:
Baseline
vs
Candidate
并计算硬指标。
第 5 步:Quality Review Agent
输入:
Requirement
Structured Goal
Diff
Evaluation
Repair History
输出:
accept
repair
human_review
第 6 步:Policy Gate
确定性程序再检查 Agent Decision。
如果通过:
执行
如果不一致:
human_review
第 7 步:Repair Tool
如果需要:
Repair Intent
↓
Deterministic Calculator
↓
New Candidate
第 8 步:Loop
新 Candidate:
重新 Validate
重新运行 Engine
重新 Evaluate
重新 Review
最多三轮。
这才是这个项目里所谓的:
Agent Loop。
面试时我会怎样回答
如果面试官问:
你说你手写了 Agent Loop,它到底是什么?
我会回答:
我这个项目不是让 LLM 在 while true 里随便调用工具。主流程由 Python Workflow 显式控制。Requirement Agent 先把自然语言需求变成 Structured Goal 和完整 Candidate,Candidate 必须先过 Schema、引用、规则和安全四层确定性校验,再经过人工授权才能进入隔离引擎。
Engine 跑完以后会产生 Telemetry,程序先做确定性 Evaluation,再把原始需求、Diff、Evaluation 和 Repair History 交给 Quality Review Agent,让它只选择 accept、repair 或 human_review,以及一个受限 Repair Action。
Agent 的建议还不能直接执行,要再过 Policy Gate;真正的数值修改由确定性 Repair Function 计算。修改以后重新跑 Engine,形成 Observation → Decision → Action → Observation 的闭环。
整个循环最多 3 次 Candidate Run、4 次 Model Call,而且连续没有改善会提前停止,所以它是一个有预算和终止条件的 bounded Agent Loop。
如果继续问:
为什么不直接让模型修改数值?
我会回答:
因为 LLM 比较适合处理自然语言理解和策略判断,不适合承担需要稳定重复的精确计算。我的设计是让 Quality Review Agent 只选“降低单波子弹数”这种 Repair Intent,具体降低多少由确定性程序根据 Telemetry 和目标阈值计算,所以相同输入得到相同修改。
如果继续问:
Agent 和普通 Workflow 有什么区别?
我会回答:
Workflow 定义允许怎样流转,比如必须先 Validate、再授权、再运行、最多三轮;Agent 只放在难以完全规则化的节点,比如从自然语言形成 Candidate,以及综合需求和运行证据选择修复意图。我的理解不是用 Agent 替代 Workflow,而是在确定性 Workflow 里嵌入有限的模型决策。
如果继续问:
为什么叫有界 Agent?
我会回答:
因为模型的能力、动作空间、运行次数和权限都有边界。模型不能直接运行 Unity、不能任意改配置、不能自己计算修复数值、不能增加预算,也不能绕过 Policy Gate。最多三次候选运行、四次模型调用,证据不足或决策冲突时直接转人工,而不是继续自主尝试。
我从这个问题中理解到的事情
以前我比较容易把 Agent 理解成:
LLM
+
Tools
后来我觉得这只是一个非常粗略的公式。
一个真正能执行工作的 Agent 系统至少还要回答:
State 在哪里?
下一步 Decision 的输入是什么?
Action 有哪些?
哪些 Action 模型有权提出?
哪些 Tool 真正可以执行?
Tool Result 怎样变成 Observation?
Observation 怎样进入下一轮?
模型输出怎样验证?
什么时候需要人工?
循环什么时候结束?
失败以后留下什么 Trace?
在这个项目里,这些关系最终变成:
Structured State
→ 保存当前 Candidate、Evidence 和 History
Requirement Agent
→ 把模糊自然语言转成结构化候选
Deterministic Validation
→ 模型输出不能直接获得执行权
Human Authorization
→ 外部执行之前必须明确授权
EngineRunner
→ 在真实环境执行 Candidate
Telemetry
→ 把真实运行转成 Observation
Quality Review Agent
→ 根据证据选择有限的下一步意图
Policy Gate
→ 判断模型建议是否允许执行
Deterministic Repair
→ 模型决定方向,程序计算精确数值
Budget
→ 限制模型调用和引擎运行次数
Termination
→ 成功、人工复核、预算耗尽或停滞都可以结束循环
Trace
→ 每一轮输入、输出、状态和 Evidence 都可回放
所以我现在更愿意把 Agent 定义成:
一个让模型能够基于当前状态和外部观察选择行动,并通过工具影响环境,再把结果带回下一轮决策的受控执行系统。
而其中真正困难的部分不是:
怎么调用一次 LLM
也不是:
怎么让模型调用一个函数
而是:
如何决定模型可以控制什么,又必须把什么控制权留在确定性程序和人手里。
这也是我手写这条 Agent Loop 以后,觉得比“用了哪个 Agent Framework”更重要的基础问题。
延伸阅读
- OpenAI:Structured Outputs
- OpenAI:Function Calling / Tool Calling
- LangGraph:StateGraph Concepts
- Anthropic:Building Effective Agents