从“能跑”到“可运营”:Agent Harness 工程化建设指南
一个 Agent 能调用模型、执行工具并输出答案,只能说明 Demo 跑通了。真正进入生产环境后,我们需要面对上下文膨胀、任务中断、进程重启、重复执行、多实例竞争、成本失控和故障难以定位。解决这些问题的,不是继续堆 Prompt,而是在 Agent 外建立一层可靠的 Harness。
引言:Agent 为什么不能只是一段推理代码
很多 Agent 项目最初都有相似的结构:收到 HTTP 请求,创建一个可取消的 context.Context,调用模型或 ReAct Agent,然后把 token 通过 SSE 返回给浏览器。
func HandleChat(ctx context.Context, input string) <-chan Event {
runCtx, cancel := context.WithCancel(ctx)
runningTasks.Store(conversationID, cancel)
return agent.Run(runCtx, input)
}
这段代码很适合验证想法,但它把几件本应分离的事情绑在了一起:
- HTTP 连接的生命周期;
- Agent 执行的生命周期;
- 用户看到的流式输出;
- 任务状态与取消控制;
- 上下文、记忆和执行历史。
只要系统开始承载真实用户,问题就会接连出现:
- 浏览器断开后,Agent 是否应该停止?
- 用户点击“停止”,是永久取消,还是稍后可以继续?
- 服务重启后,执行到一半的搜索任务如何恢复?
- 多个实例部署后,
/stop请求如何找到真正执行任务的进程? - Worker 失联后被其他实例接管,旧 Worker 恢复时会不会重复写结果?
- 对话越来越长时,哪些历史应该进入模型上下文?
- Agent 为什么做出某个决定,花了多少 token,在哪一步失败?
这些问题共同指向一个结论:Agent 不应该直接拥有生产运行时。
我们需要在 Agent 外建立一层 Harness。Agent 负责推理,Harness 负责让推理过程可靠地运行。
一、什么是 Agent Harness
“Harness”原本有“线束”和“控制装置”的含义。在 Agent 工程里,它可以理解为包裹 Agent 的运行时外壳:向内为模型和工具提供稳定执行环境,向外为 API、用户和运维系统提供一致的生命周期协议。
一个企业级 Agent Harness 至少应负责:
- Run、Attempt、Step 的生命周期;
- 上下文装配和 token 预算;
- 短期工作记忆与长期用户画像;
- 暂停、继续、取消和超时;
- Checkpoint 与故障恢复;
- 重试、幂等和并发控制;
- 消息派发和多实例接管;
- 事件流、日志、指标、Trace 和审计;
- 成本、配额、测试与灰度发布。
这里最重要的设计原则是职责分离:
Agent 负责:
- ReAct 或其他推理循环;
- 选择模型和工具;
- 产生流式输出;
- 保存与恢复框架内部状态。
Harness 负责:
- 任务现在是什么状态;
- 哪个 Worker 有权执行;
- 用户要求暂停还是永久取消;
- 何时重试,最多重试多少次;
- 上下文里放什么,为什么被裁剪;
- 执行失败后从哪里恢复;
- 整个过程如何被观测、审计和治理。
总体架构可以抽象成下面这样:
这不是简单地多加几个中间件,而是把“执行一次请求”重构为“管理一个持久化 Run”。
二、第一步:让 Run 成为一等公民
2.1 Conversation 不是 Run
一个 Conversation 可以有多轮消息,而每次消息处理都可能形成一个独立 Run。一次 Run 还可能因为暂停、崩溃和恢复产生多个 Attempt。
因此至少要区分三个概念:
- Run:一次用户意图的完整生命周期;
- Attempt:某个 Worker 对该 Run 的一次执行或恢复尝试;
- Step:Run 内可识别的 Agent、子 Agent 或关键工具边界。
如果直接用 conversation_id 作为运行标识,会出现很多歧义:同一会话中的两个请求无法区分,暂停命令可能命中错误任务,Trace 和成本也无法正确归属。
2.2 API 创建 Run,而不是直接执行 Agent
推荐的 API 语义是:
POST /conversations/{conversation_id}/runs
Idempotency-Key: client-generated-key
HTTP/1.1 202 Accepted
{
"run_id": "0190...",
"state": "queued",
"events_url": "/agent/runs/0190.../events"
}
创建请求只负责:
- 校验用户与资源权限;
- 在 MySQL 中创建 Run;
- 同一事务写入 Outbox 事件;
- 返回稳定的
run_id。
真正的 Agent 执行由 Worker 异步完成。这样,API 实例可以随时扩容、重启或滚动发布,而不会直接拥有任务。
为了兼容已有 SSE 接口,可以保留一个适配层:内部先创建 Run,再立即订阅该 Run 的事件流。前端体验不必一次性重写,但底层生命周期已经完成解耦。
2.3 状态机必须显式
很多系统只有 running/success/error 三种状态,结果是暂停、超时、重试等待、Worker 丢失和永久失败全部被塞进 error。这样的状态无法驱动可靠恢复。
一个更完整的状态机如下:
API 发起的命令使用 state + state_version 做乐观锁。Worker 写状态、步骤、事件和 Checkpoint 时,还必须校验 fencing token。任何条件更新失败都意味着状态已被并发操作改变,不能继续凭本地内存做决定。
三、上下文管理:不是“把更多历史塞进去”
Agent 工程中最容易被低估的部分是上下文管理。
常见的第一版实现是保留最近 N 轮消息:
系统提示词 + 最近 10 轮 + 当前问题
稍好一些的版本会加入滚动摘要:
系统提示词 + 历史摘要 + 最近 10 轮 + 当前问题
这两种方案都能工作,但它们按时间远近决定信息是否重要。用户很早以前提出的关键约束可能被丢弃,最近的闲聊反而长期占用 token。摘要反复更新后还会发生事实漂移。
3.1 把上下文看成受预算约束的装配过程
更稳定的方式是建立 ContextAssembler。每次模型调用前,它根据模型能力、当前 Run 状态和候选信息生成一个 ContextEnvelope。
输入预算可以表示为:
input_budget = context_window
- reserved_output
- serialized_tool_schema
- safety_margin
上下文内容分为两类。
硬保留层:
- 系统与安全策略;
- 工具 Schema;
- 当前任务目标与工作状态;
- 当前用户请求。
软预算层:
- 与当前任务相关的用户画像;
- 版本化会话摘要;
- 相关旧历史;
- 最近几轮对话;
- 搜索或工具证据。
候选内容可以使用类似的评分:
score = relevance
+ importance
+ recency
+ task_continuity
- token_cost
- trust_risk
这里的关键不是找到一条完美公式,而是让选择过程可解释、可测试、可观测。
3.2 短期工作记忆必须结构化
对话摘要负责回答“之前聊过什么”,任务工作记忆负责回答“现在做到哪一步”。两者不能混在一起。
{
"goal": "完成用户问题的回答",
"current_step": "searching",
"constraints": [
"使用用户选择的资料",
"网络信息必须带来源"
],
"completed_steps": ["context_assembled"],
"pending_steps": ["search", "final_answer"],
"artifacts": [
{"id": "artifact-id", "kind": "search_results"}
]
}
工作记忆是 Run 状态的一部分,可以被持久化、审计和恢复。它不保存模型的隐藏思维链,只保存用户输入、工具结果和系统明确作出的结构化决定。
3.3 摘要必须有版本边界
滚动摘要至少要记录:
through_message_id:摘要覆盖到哪条消息;version:用于乐观锁;- 生成模型与 Prompt 版本;
- token 数和生成时间。
异步摘要任务更新时必须携带 expected version。否则较慢的旧任务可能在新任务之后完成,然后覆盖更完整的摘要。
3.4 工具产物不要永久塞进 Prompt
网页正文、完整搜索结果和大型文档应该保存为 Artifact。Prompt 中只放摘要、必要证据和 Artifact 引用,需要时再读取原文。
这同时解决三个问题:
- 控制 token;
- 保留可审计证据;
- 降低不可信网页文本对主 Prompt 的污染。
来自网页和工具的内容应被标记为 untrusted evidence,不能覆盖系统规则或用户已确认约束。
四、长期记忆:优先建设用户画像,而不是无限聊天仓库
长期记忆很容易被理解为“把所有聊天消息向量化,然后每次检索”。这种方式可以召回旧内容,却也会把临时情绪、错误推断、过期事实和敏感信息长期带回上下文。
更稳妥的第一步,是建设结构化用户画像。
适合长期保存的内容包括:
- 使用语言;
- 回答风格;
- 专业背景和熟悉程度;
- 常用输出格式;
- 稳定工作方式和明确偏好。
type ProfileFact struct {
UserID uint
Category string
Key string
NormalizedValue string
Confidence float64
SourceMessageID uint
Version int
Status string
UpdatedAt time.Time
}
4.1 混合写入策略
- 用户明确说“记住我……”时直接写入;
- 模型发现稳定偏好时生成候选;
- 候选必须属于白名单类别并达到置信度阈值;
- 冲突内容创建新版本,不静默覆盖;
- API Key、密码、Cookie、访问令牌和私钥永不进入长期记忆。
画像通常规模很小,首期没有必要立刻引入向量数据库。根据任务类型选择相关类别,再按置信度和更新时间排序即可。注入 Prompt 时还要设置比例上限,例如不超过输入预算的 5%。
用户必须能够查看、修改、单条删除、全部清空和关闭记忆。关闭后既不提取新画像,也不把已有画像注入上下文。
长期记忆的价值不在于“记得越多”,而在于只记稳定、相关、可追溯、可撤销的信息。
五、Checkpoint:把中断恢复建立在安全点上
“任务恢复”有三个不同等级:
- 从原始输入重新执行;
- 从最近安全点继续;
- 跨小时、人工审批和补偿事务的长工作流恢复。
多数通用 Agent Harness 的合理起点是第二级:模型或工具调用结束后保存框架内部状态,恢复时从该安全点继续。
以支持 Checkpoint 的 Agent 框架为例,运行时通常需要以下能力:
type CheckPointStore interface {
Get(ctx context.Context, checkpointID string) ([]byte, bool, error)
Set(ctx context.Context, checkpointID string, data []byte) error
}
type CheckPointDeleter interface {
Delete(ctx context.Context, checkpointID string) error
}
运行时为每个 Run 配置稳定的 checkpoint key:
runner := adk.NewRunner(ctx, adk.RunnerConfig{
Agent: mainAgent,
EnableStreaming: true,
CheckPointStore: checkpointStore,
})
cancelOpt, cancelAgent := adk.WithCancel()
iter := runner.Run(
runCtx,
messages,
cancelOpt,
adk.WithCheckPointID(checkpointKey),
)
5.1 正确的暂停流程
用户点击“停止”后,不能立即把 Run 标成 paused。正确流程是:
- API 持久化
pause_requested; - 当前 Worker 收到暂停意图;
- Worker 请求模型后或工具后安全点取消;
- 继续消费 Agent iterator;
- 框架生成取消事件并保存 checkpoint;
- Harness 校验 checkpoint 是否存在、校验和是否正确;
- 校验通过后才写入
paused。
handle, accepted := cancelAgent(
adk.WithAgentCancelMode(
adk.CancelAfterChatModel | adk.CancelAfterToolCalls,
),
adk.WithRecursive(),
adk.WithAgentCancelTimeout(cancelTimeout),
)
if accepted {
if err := handle.Wait(); err != nil {
// classify timeout or execution-ended outcome
}
}
一个常见错误是调用 cancel 后立刻停止读取 iterator。某些框架只有在事件流被持续消费时,才能完成取消传播和 checkpoint 落盘。因此 Harness 必须等待取消结果,而不是只发一个信号就返回。
5.2 Resume 必须重建兼容环境
Checkpoint 并不是脱离代码版本独立存在的魔法快照。恢复前至少要校验:
- Agent 定义版本;
- Prompt 版本;
- 工具 Schema 版本;
- 模型 Provider 与配置 Hash;
- Agent 框架和 Checkpoint 格式版本。
版本完全一致可以直接 Resume;明确声明向后兼容的版本可以通过兼容矩阵放行;其他情况应进入 suspended,由用户选择从原请求重新运行。盲目加载旧 Checkpoint,往往比安全失败更危险。
5.3 Checkpoint Store 不应只是一个覆盖写的 Blob
生产环境应保存版本化 Checkpoint:逻辑 key 对应多个不可变版本,写入新版本后再原子切换 current pointer。记录至少包括:
- Blob 或对象存储 URI;
- 字节数和 SHA-256;
- Run、Attempt 和 fencing token;
- Agent、Prompt、工具和模型版本;
- 创建与过期时间。
小 Checkpoint 可以放 MySQL,大 Checkpoint 放对象存储,数据库只保存元数据和指针。
六、断线、暂停、取消和关闭必须是四种语义
很多系统把所有停止行为都实现成 context.CancelFunc。这会让产品语义变得模糊:用户只是关闭页面,任务却永久消失;用户想暂停,后台工具却继续执行。
建议明确区分:
| 事件 | 语义 | Run 行为 |
|---|---|---|
| SSE 断线 | Detach | Run 继续执行 |
| “停止生成” | Pause | 安全点保存 Checkpoint,可继续 |
| “永久放弃” | Cancel | 进入终态,不可继续 |
| Worker 下线 | Drain | 尝试安全暂停,否则由其他实例接管 |
6.1 不要让 HTTP Context 成为 Run Context
推荐的 Context 层级是:
requestCtx 只服务 API 校验和响应
runCtx 由 Worker 创建,属于 Run 生命周期
attemptCtx 属于一次执行尝试
stepCtx 属于模型、子 Agent 或工具步骤
请求断开只关闭订阅,不触发 runCtx。业务暂停通过框架安全点协议完成;进程硬退出时才使用普通 Context 取消做资源清理。
同样要谨慎使用 context.WithoutCancel。它适合明确要脱离父请求的短后台操作,但如果被用来启动 SearchAgent 或生成任务,就可能绕过用户暂停、Run 超时和优雅下线。更好的方式不是“去掉取消”,而是让任务继承正确的 runCtx。
七、多实例接管:Lease 解决活性,Fencing 解决正确性
当系统只有一个进程时,用内存保存 run_id -> cancelFunc 似乎足够。部署多个实例后,这个方案立即失效:暂停请求可能到达 API-B,而任务实际运行在 Worker-A。
更重要的是,即便使用 Redis 锁,也不能自动获得正确的故障接管。
7.1 为什么只有 Lease 不够
假设 Worker A 获得 20 秒租约,然后发生长时间网络暂停。租约过期后 Worker B 接管任务。此时 A 恢复,它仍然可能继续把旧结果写入数据库。
因此需要同时使用:
- Lease:判断 Worker 是否还活着,以及何时可以尝试接管;
- Fencing Token:判断一个 Worker 是否仍有资格写入。
所有来自 Worker 的 Run、Step、Event 和 Checkpoint pointer 写入,都需要类似的条件:
UPDATE agent_runs
SET state = ?, state_version = state_version + 1
WHERE id = ?
AND owner_worker_id = ?
AND fencing_token = ?
AND state_version = ?;
Redis 提供低延迟租约和心跳,MySQL 保存 owner、lease expiry 和单调递增的 fencing token。Redis 丢失不应让任务事实消失。
八、MQ 负责传输,数据库负责事实
引入消息队列后,另一个常见误区是把 MQ 当作任务状态源。消息被 ACK、过期或重建队列后,系统就无法回答“这个 Run 到底处于什么状态”。
更稳妥的职责划分是:
- MySQL 决定任务是什么状态;
- MQ 决定任务由谁尽快处理;
- Redis 决定 Worker 当前是否仍持有租约;
- Checkpoint 决定恢复时从哪个内部安全点继续。
8.1 Transactional Outbox
数据库状态和 MQ 发布不能依赖普通“双写”:如果数据库提交成功、消息发布失败,Run 会永远停在队列中;如果先发布消息再回滚数据库,Worker 会收到不存在的任务。
Outbox 模式把两件事放在一个事务中:
Relay 可能在发布成功、标记 Outbox 前崩溃,因此消息仍可能重复。消费者需要通过 event_id Inbox、Run 状态 CAS 和幂等命令去重。
8.2 “Exactly-once”通常是一个危险承诺
Harness 内的状态转换可以做到有效幂等,但外部搜索 API 往往不支持幂等键。
如果搜索调用已经成功,进程却在结果持久化前崩溃,恢复时可能再次调用供应商。此时更诚实的语义是:
至少一次执行
+ 结果去重
+ 调用次数与费用预算
+ 重复外部调用指标
完成的 Search Step 应保存标准化 query hash、tool call ID 和结果 Artifact。恢复时优先复用持久化结果;只有找不到结果时才再次调用外部服务。
九、重试:需要统一预算,而不是每层各自“重试三次”
一个典型的重试风暴是:
- HTTP 客户端重试 2 次;
- Run 重试 3 次;
- SearchAgent 重试 3 次;
- 模型 SDK 再重试 2 次。
最终一次用户请求可能触发 36 次调用。
Harness 应集中管理重试预算:
- 模型层只重试明确的瞬时错误;
- SearchAgent Attempt 有独立上限;
- Worker 崩溃恢复有独立上限;
- 下层把实际重试次数上报给上层;
- 同时限制总 wall time、token、搜索次数和费用。
错误也必须分类:
| 错误类别 | 例子 | 动作 |
|---|---|---|
transient |
网络、429、可恢复 5xx | 退避重试 |
worker_lost |
租约过期、进程崩溃 | Checkpoint 接管 |
resource |
预算、配额、系统过载 | suspended |
permanent |
参数、权限、配置错误 | failed |
user_pause |
用户停止 | paused,不消耗重试 |
checkpoint_invalid |
损坏或版本不兼容 | suspended 并告警 |
重试不是错误处理的默认答案。对认证失败、参数错误和预算超限反复重试,只会增加延迟和成本。
十、可观测性:从 Run ID 还原整条执行链
生产环境最痛苦的问题之一是:日志很多,却无法解释一次 Agent Run 发生了什么。
OpenTelemetry 应作为统一协议,把 Run、Agent、模型、工具、Checkpoint 和状态转换串成一棵 Trace 树:
agent.run
├── context.assemble
├── main_agent.execute
│ ├── model.call
│ ├── search_agent.execute
│ │ ├── model.call
│ │ └── tool.web_search
│ └── model.call
├── checkpoint.save
└── run.transition
10.1 异步消息如何传播 Trace
HTTP 请求使用 W3C traceparent。Outbox 保存 Trace Context,发布到 MQ 时写入 Message Header。Worker 创建新的 Attempt Span,并通过 Span Link 关联原始请求和前一个 Attempt。
不要维持一个跨越数小时的父 Span。长时间异步任务更适合使用多个可结束的 Span 和 Link。
10.2 需要关注的指标
Run 指标:
- 队列等待时间;
- 各状态停留时间;
- 成功、失败、暂停和恢复比例;
- Worker 接管次数。
恢复指标:
- Pause 命令接受与完成延迟;
- Checkpoint 大小和读写耗时;
- Resume 成功率;
- Checkpoint 版本不兼容次数。
模型与上下文指标:
- token、延迟、重试和费用;
- 上下文预算利用率;
- 各层候选、入选和裁剪 token;
- 摘要和用户画像命中率。
基础设施指标:
- MQ consumer lag、redelivery 和 DLQ;
- Outbox backlog;
- Lease heartbeat 和过期;
- MySQL CAS 冲突与连接池。
Run ID 和 User ID 不应成为 Prometheus Label,否则会产生高基数灾难。它们适合进入 Trace 和结构化日志。指标 Label 应限制在 agent、provider、model family、state、error class 等有限枚举。
10.3 日志、Trace 和审计各有职责
- 日志用于排障,可以按策略清理;
- Trace用于还原调用链和性能;
- 审计事件记录谁在何时创建、暂停、继续或取消了任务。
普通日志不应保存完整 Prompt、网页正文、API Key 或用户画像值。如果首期不做执行回放,只记录 Prompt Hash、token 数和 Context Manifest 已经足够覆盖大量问题。
十一、测试 Harness,而不只是测试“模型回答对不对”
Agent Harness 的核心价值是故障条件下仍保持正确。因此测试必须包含状态、并发和故障注入。
11.1 单元测试
- 状态转换表和非法转换;
- 错误分类和重试预算;
- token 计算、评分和裁剪;
- 摘要版本冲突;
- 用户画像冲突、删除和总开关;
- 幂等键规范化。
11.2 集成测试
- MySQL Run/Attempt/Outbox 事务;
- MQ 重复投递、NAK、MaxDeliver 和 DLQ;
- Redis Lease、Heartbeat 和过期接管;
- Checkpoint Store 的 Get/Set/Delete;
- SSE
Last-Event-ID重放; - 对象存储中的大型 Checkpoint 与校验和。
11.3 必须做的故障注入
| 故障点 | 注入方式 | 期望结果 |
|---|---|---|
| SSE 断线 | 流式中关闭连接 | Run 继续,重连补发 |
| 模型调用中暂停 | 发送 Pause 命令 | 安全点后进入 paused |
| 子 Agent 中暂停 | 递归安全取消 | 主子 Checkpoint 可恢复 |
| Worker 硬崩溃 | kill -9 或强停容器 |
租约过期后接管 |
| 旧 Worker 复活 | 暂停网络后恢复 | 旧 Token 写入被拒绝 |
| MQ 重复投递 | 重发相同 event ID | 只产生一个有效 Claim |
| Outbox Relay 崩溃 | Publish 后、Mark 前终止 | 安全补发 |
| Checkpoint 损坏 | 删除对象或修改 Hash | 禁止 Resume,进入 suspended |
| 版本不兼容 | 升级工具后恢复旧 Run | 按兼容矩阵处理 |
还需要独立的 Agent 质量评测:历史约束召回率、摘要事实一致性、用户画像写入准确率、搜索结果完整性,以及 token、延迟和费用回归。
工程正确性和模型质量是两个维度,不能用一组测试互相替代。
十二、不要一步到位:推荐的七阶段路线
最终目标可以是多实例接管,但实施顺序应先证明单实例正确性,再引入分布式复杂度。
P0:固定现有行为
先为当前 Agent 调用链、流式输出、停止、摘要和历史加载建立契约测试。没有基线就开始重构,很难判断是 Harness 设计错误,还是旧行为本来就不稳定。
P1:上下文与用户画像
先以 Shadow 模式生成 Context Manifest,不改变模型输入。比较新旧上下文的 token、约束召回和回答质量,再灰度启用 ContextAssembler。
P2:持久化 Run
建立 Run、Attempt、Step、Event 和 Audit 数据模型。先使用单实例 Worker,让 SSE 与 Run 生命周期彻底解耦。
P3:Checkpoint Pause/Resume
接入框架 Checkpoint Store,把主 Agent 和搜索子 Agent 放入同一恢复协议。通过单实例重启和用户暂停测试证明恢复正确。
P4:MQ、Outbox 与事件重放
引入消息队列、Transactional Outbox、Consumer Inbox、DLQ 和 SSE 事件重放。验证消息重复和 Relay 崩溃不会产生重复有效执行。
P5:多实例接管
实现 Lease、Heartbeat、Recovery Scanner 和 Fencing。通过 Worker 硬崩溃、旧 Worker 复活和滚动发布测试。
P6:容量与生产硬化
根据目标规模进行并发 Run 压测,完成仪表盘、告警、Runbook、数据清理、混沌测试和灰度发布。
Feature Flag 应在 Run 创建时形成配置快照。不要在 Run 中途切换执行协议。回滚时停止新 Run 进入 Harness,已有 Run 继续完成或安全暂停,而不是删除运行状态。
十三、企业级 Agent Harness 检查清单
在宣布 Agent 已经“生产可用”之前,可以逐项检查:
生命周期
- HTTP/SSE 断线不会自动销毁 Run;
- Run、Attempt、Step 有独立标识;
- 状态转换由显式状态机约束;
- Pause 与 Cancel 是不同语义;
- 任务可以在进程重启后恢复。
上下文与记忆
- 有模型感知的 token 预算;
- 系统策略和当前任务约束不会被静默裁剪;
- 摘要有版本和覆盖消息边界;
- 工作记忆与对话摘要分离;
- 长期记忆可查看、修改和删除;
- 工具大结果使用 Artifact,而不是永久进入 Prompt。
分布式正确性
- 数据库是任务事实源;
- 数据库与 MQ 之间使用 Outbox;
- 消费者可以处理重复消息;
- Worker 使用 Lease 和 Fencing;
- 旧 Worker 恢复后不能写入;
- 外部副作用有幂等键或明确的至少一次语义。
恢复与重试
- Checkpoint 版本、校验和和保留期可管理;
- 只有 Checkpoint 验证成功才进入
paused; - 恢复时校验 Agent、Prompt、工具和模型版本;
- 错误被分类为可重试、资源、永久和用户行为;
- 重试受总时间、token、调用次数和费用预算约束。
可观测性与运维
- Run ID 可以关联 Trace、日志和审计;
- 异步消息传播 Trace Context;
- 指标没有 Run ID 级高基数 Label;
- 可以看到队列积压、Pause 延迟和恢复成功率;
- 有 DLQ、故障接管和 Checkpoint 清理 Runbook;
- 通过断线、崩溃、重复投递和旧 Worker 复活测试。
结语
Agent 工程化的分水岭,不是模型是否更聪明,也不是 Prompt 是否更长,而是系统能否回答这些朴素的问题:
- 任务现在在哪里?
- 谁拥有执行权?
- 用户停止后能否继续?
- 进程崩溃后从哪里恢复?
- 为什么选择了这些上下文?
- 是否重复调用了外部服务?
- 这次运行花了多少时间、token 和费用?
- 出错后能否被准确定位和安全重试?
一个成熟的 Harness 不会替 Agent 思考。它做的是更基础、也更重要的工作:为不确定的推理过程提供确定的运行边界。
从这个角度看,企业级 Agent 平台并不是“LLM 加几个工具”,而是一套围绕 Run 建立的可靠执行系统。先把生命周期、上下文、状态和恢复做对,再谈更复杂的多 Agent 协作,往往会走得更稳,也更快。
更多推荐



所有评论(0)