从一句话需求到可执行任务
用户说:“帮我把这个项目整理一下。”
人类听到这句话,会结合语气、当前目录和过往对话猜出大概意思。但 Agent 如果直接开始行动,可能会遇到一连串问题:整理是格式化代码,还是重构目录?允许修改多少文件?能不能删除废弃代码?需要运行测试吗?什么状态才算完成?
这就是 Harness 第一个核心部分存在的原因:任务入口与契约。
它的工作不是把自然语言改写得更漂亮,而是把一个可能含糊的请求,转换成一份能够执行、能够约束、也能够验收的任务说明。
如果把 Agent 团队比作装修队,模型像负责现场判断的工长,任务契约则是施工单:要改哪个房间、预算是多少、哪些墙不能动、做完按什么标准验收。没有施工单,能力越强的队伍反而可能拆得越多。
本文是《把 Harness 讲清楚:Agent 背后的运行系统》八个核心部分的第一篇。
任务入口不只是一个输入框
很多 Agent 原型把任务入口理解成聊天框:拿到一段字符串,原样交给模型。这适合问答,却不足以承载真实操作。
一个工程化任务入口至少要接收五类信息:
| 信息 | 要回答的问题 | 示例 |
|---|---|---|
| 目标 | 最终要得到什么 | 修复登录超时问题 |
| 范围 | 可以操作哪些对象 | 仅修改当前仓库 |
| 边界 | 明确禁止什么 | 不改数据库结构,不发送外部消息 |
| 完成标准 | 怎样才算完成 | 测试通过,说明根因和改动 |
| 证据 | 用什么证明完成 | 测试输出、文件路径、接口回执 |
此外还可能有截止时间、成本预算、目标环境、数据敏感级别和审批人。这些内容不一定都由用户手工填写,可以从当前项目、组织策略和会话上下文中补充,但最终应形成一份清楚的内部契约。
为什么不能把所有歧义都交给模型猜
合理推断是 Agent 提高效率的重要能力,但不是所有问题都适合猜。
可以采用一个简单判断:如果猜错后容易发现、容易撤销、影响范围小,就可以先做合理假设;如果猜错会造成外部影响、数据损失或昂贵返工,就必须询问或审批。
例如:
- 用户没指定 Markdown 文件名,选择语义清楚的文件名,通常可以合理假设;
- 用户说“清理无用数据”,但没有说明范围,不应该自行删除;
- 用户让分析邮件内容,可以读取已授权邮件;如果要代替用户回复,则是新的外部写入;
- 用户让修复代码,可以修改相关文件;是否顺便升级所有依赖,则通常超出任务范围。
所以,任务入口需要做的不只是“理解意图”,还要判断不确定性的风险。
一份任务契约应该长什么样
任务契约不必是一份复杂文档。对多数开发任务,一个结构化对象已经足够:
{
"objective": "修复登录页在会话过期后无限刷新问题",
"scope": {
"repositories": ["web-admin"],
"paths": ["src/auth", "tests/auth"]
},
"allowed_actions": ["read_files", "edit_files", "run_tests"],
"forbidden_actions": ["deploy", "change_database", "send_message"],
"completion_criteria": [
"复现并说明根因",
"新增回归测试",
"相关测试全部通过"
],
"required_evidence": ["changed_files", "test_result"],
"budgets": {
"max_minutes": 30,
"max_tool_calls": 80
}
}
这份对象不一定直接展示给用户,却应该成为后续上下文、工具权限、停止条件和结果验证的共同依据。
契约是怎样形成的
完整流程可以画成下面这样:
用户请求
↓
识别目标与对象
↓
合并项目规则和组织策略
↓
检查歧义与风险
├─ 低风险、可撤销 → 记录假设
└─ 高风险、不可逆 → 询问或审批
↓
生成任务契约
↓
交给上下文引擎、Agent Loop 和工具网关
这里有一个关键点:任务契约不是只给 Agent Loop 看。工具网关要根据它决定暴露哪些工具,策略层要根据它判断是否需要审批,评估模块也要依据同一份完成标准验收结果。
自主程度应该随动作变化
同一个用户对同一个 Agent 的授权,也不应该在整场任务里保持一个模糊的“全自动”开关。
更实用的方法是把动作分层:
| 动作等级 | 典型操作 | 默认策略 |
|---|---|---|
| 只读分析 | 搜索、读取、计算 | 可自动执行 |
| 本地可逆修改 | 编辑工作区文件 | 在明确范围内自动执行 |
| 外部写入 | 发消息、建工单、发布内容 | 需要明确任务授权 |
| 破坏性操作 | 删除、覆盖、生产变更 | 精确确认目标与后果 |
用户说“帮我看看为什么构建失败”,这授权了读取和诊断,并不自然包含提交代码、推送分支或发布生产环境。Harness 必须保留这种语义差别。
最小契约生成器
下面是一段接近 Python 的伪代码,展示任务入口的基本职责:
def build_contract(user_request, project_rules, identity):
intent = understand(user_request)
contract = {
"objective": intent.objective,
"scope": infer_scope(intent, project_rules),
"allowed_actions": allowed_by(identity, intent),
"forbidden_actions": project_rules.forbidden_actions,
"completion_criteria": infer_acceptance(intent),
"required_evidence": infer_evidence(intent),
}
risks = find_ambiguities(contract)
for risk in risks:
if risk.is_reversible and risk.impact == "low":
contract["assumptions"].append(risk.safe_default)
else:
contract["questions"].append(risk.question)
return contract
真实系统中,allowed_by 不应只依赖模型判断,而要读取经过认证的用户身份、令牌权限和组织策略。
契约在执行期间也会变化
任务开始后,Agent 可能发现实际问题比预期更大。例如用户让修改一个前端错误,调查后发现根因在数据库迁移。
此时不应该悄悄扩大范围。正确流程是:
发现新情况
↓
判断是否仍在原契约内
├─ 是 → 继续执行并记录证据
└─ 否 → 提出范围变更
↓
用户确认或策略批准
↓
生成新契约版本
契约最好带版本号。这样审计时能解释:某个写操作为什么在开始时不允许,后来却被执行——因为用户在第二版契约中明确扩大了范围。
常见的失败方式
只记录目标,不记录边界
“优化项目”只是目标,没有说明哪些东西不能动。Agent 很容易把重构、升级依赖和删除兼容代码都理解成优化。
把工具存在误认为用户授权
系统装有邮件发送工具,不代表每次任务都允许发邮件。能力和授权必须分开。
完成标准写成“尽力处理”
没有可检查的标准,Agent Loop 就不知道何时停止,评估模块也只能相信一段自我总结。
每次都追问所有细节
过度询问会让 Agent 失去价值。契约层应该识别安全默认值,只把真正会改变结果或风险的问题交给用户。
执行中静默扩大任务
用户授权修一个 Bug,Agent 顺便重构整个模块。即使结果看起来更好,也破坏了可预测性。
从原型开始怎么做
不需要一开始就建立复杂的任务 DSL。可以先做四件事:
- 每个任务都生成
objective、scope、allowed_actions和done_when; - 把外部写入和破坏性操作单独分类;
- 要求 Agent 在执行前记录关键假设;
- 最终回复逐条对应完成标准,并附上证据。
当任务类型逐渐稳定,再把常见契约做成模板,例如“代码修复”“内容发布”“数据分析”和“客户沟通”。
任务入口检查清单
- 目标是否描述了结果,而不只是动作?
- 操作对象和范围是否明确?
- 哪些动作被允许,哪些动作被禁止?
- 是否区分只读、本地修改、外部写入和破坏性操作?
- 高风险歧义是否会触发询问或审批?
- 是否记录了 Agent 自行采用的假设?
- 完成标准能否被程序或人工检查?
- 最终需要返回哪些证据?
- 任务范围变化时,是否生成新的契约版本?
最后理解任务契约
任务契约不是为了束缚 Agent,而是为了让自主执行变得可信。
没有契约时,系统只能在“每一步都问用户”和“什么都让模型自己决定”之间摇摆。有了契约,Harness 才能把安全的小决定交给 Agent,把真正重要的选择留给用户,并让后续的上下文、工具、安全和评估围绕同一个目标协作。
一句话总结:先定义什么叫完成,再让 Agent 开始行动。