一个没有证据支撑的批准按钮并不是一种控制机制。人机协同自动化将五个要素绑定到一个可审计的记录中:你观察到的证据、你提议的操作、做出决策的人、他们拥有的截止时间,以及实际执行的内容。
在本文中,你将使用 Elastic Workflows 构建这个关卡,并复用配套文章使用 Agent Builder 和 Workflows 构建 SRE 控制平面中的控制平面。这一次,事件的范围足够精确,可以采取行动:一个单一工作节点上的过时定价缓存,并在推荐与修复之间设置一个结构化的审批步骤。这里不会接触生产环境,因此你可以同时运行批准分支和拒绝分支,并对比两者在执行记录中留下的内容。这份记录正是事件响应自动化逐步赢得自主权的方式——一次一个事件类别。
本文建立在使用 Agent Builder 和 Workflows 构建 SRE 控制平面的基础上,该文定义了本文使用的控制平面模式:遥测、调查上下文、策略以及一组已知操作,它们被串联在一起。Agent Builder 负责对日志、链路追踪、指标、告警、运行手册和过往案例进行推理。Elastic Workflows 则使用明确的输入和权限执行定义好的序列。人机协同(HITL)正是在证据转化为行动的那个关键点上,将这两项职责连接起来。

推荐和修复有着截然不同的失败模式。一个弱的推荐会浪费工程师的时间。一个弱的修复则会改变生产环境。这就是为什么审批关卡应该属于执行模型内部。
一个有用的关卡需要保留五个属性。缺少任何一个,关卡都会变弱。
形式仍然重要,但形式只是更大控制机制中面向人类的部分。审批设计就是可靠性工程。
将自主性视为一系列操作状态,而不是一个产品开关。
自主性阶段 | 系统行为 | 人类职责 | 推进证据 |
|---|---|---|---|
0, 观察 | 搜索日志并汇总上下文。 | 手动调查并操作。 | 查询能够找到正确的事件证据。 |
1, 推荐 | 提出一个有界的下一个步骤并附带理由。 | 在工作流外部做出决策并执行。 | 推荐的准确度足够高,可以快速审查。 |
2, 审批 | 在操作前暂停,仅在有结构化输入时恢复。 | 批准或拒绝确切的提议。 | 审批质量、执行成功率和回滚行为被度量。 |
3, 窄范围自动化 | 针对已证实的事件类别,自动执行相同操作。 | 审查异常并审计样本。 | 范围、权限、超时、验证和回滚仍被强制执行。 |
重要的过渡是从阶段 1 到阶段 2。这是系统不再是顾问,而是获得执行路径的时刻。即使 AI Agent 参与了调查,该路径也应该是确定性的:Agent 总结证据并推荐一个操作,而工作流则负责暂停、结构化决策、分支以及执行记录。
Elastic Workflows 为此提供了 waitForInput 步骤。当执行到达该步骤时,工作流会进入 WAITING_FOR_INPUT 状态并等待人员操作。审查者在 Kibana 执行视图(或通过恢复 API)中填写一个小表单,他们提交的任何内容都会在后续步骤中通过 steps.<step_name>.output 可用。
工作流会拉取最近 30 分钟的 checkout-api 失败日志,将其交给 Agent Builder 进行根因分析,并将该分析写入一个可观测性案例中。然后停止并询问一个问题:是否应该清除 checkout-worker-07 上的定价缓存?如果批准,它会记录模拟操作,运行一个验证查询,并将结果追加到案例中。如果拒绝,它会记录决策,不执行任何操作。无论哪种方式,生产环境都不会被触及。

name: obs-labs-checkout-control-plane-hitl
description: 基于 OpenTelemetry 的 checkout 控制平面的人机审批关卡。
enabled: false
tags:
- sre-control-plane
- agent-builder
- human-in-the-loop
- workflows
- opentelemetry
settings:
timeout: "30m"
triggers:
- type: manual
consts:
incident_id: "obs-labs-checkout-hitl-20260719"
steps:
- name: collect_evidence
type: elasticsearch.search
with:
index: "logs-*"
size: 10
query:
bool:
filter:
- range:
"@timestamp":
gte: "now-30m"
- term:
"service.name": "checkout-api"
- term:
"attributes.incident.id": "{{ consts.incident_id }}"
- match_phrase:
"body.text":
query: "checkout failed: stale pricing cache"
- name: rca_analysis
type: ai.agent
agent-id: elastic-ai-agent
create-conversation: true
with:
message: |
调查由 {{ consts.incident_id }} 标识的 checkout-api 事件。
工作流证据查询在最近 30 分钟内找到了 {{ steps.collect_evidence.output.hits.total.value }} 个匹配的 OpenTelemetry 日志事件。
搜索 service.name 为 checkout-api 且 incident.id 为 {{ consts.incident_id }} 的日志、链路追踪和指标。
确定受影响的 Worker、部署版本、错误类型、HTTP 状态码和延迟证据。
返回可能的原因、支持证据、置信度,以及以下有界模拟操作是否与证据一致。
提议的模拟操作:模拟清除 checkout-worker-07 上的定价缓存。
此工作流不得更改生产环境。
- name: case_title
type: ai.agent
agent-id: elastic-ai-agent
with:
conversation_id: "{{ steps.rca_analysis.output.conversation_id }}"
message: "为这个 checkout 事件生成一个简短的案例标题。只输出标题。"
- name: case_description
type: ai.agent
agent-id: elastic-ai-agent
with:
conversation_id: "{{ steps.rca_analysis.output.conversation_id }}"
message: "生成一个基于 OpenTelemetry 证据的简洁案例描述。包含事件 ID,并说明修复需要人工审批。只输出描述。"
- name: create_case
type: cases.createCase
with:
title: "{{ steps.case_title.output.message }}"
description: "{{ steps.case_description.output.message }}"
owner: "observability"
severity: "medium"
tags:
- sre-control-plane
- agent-builder
- human-in-the-loop
- opentelemetry
- name: add_agent_analysis
type: cases.addComment
with:
case_id: "{{ steps.create_case.output.case.id }}"
comment: |
## Agent Builder RCA 及提议操作
证据查询匹配数:{{ steps.collect_evidence.output.hits.total.value }}
{{ steps.rca_analysis.output.message }}
提议的模拟操作:模拟清除 checkout-worker-07 上的定价缓存。
尚未执行任何操作。
Agent 对话:{{ kibanaUrl }}/app/agent_builder/conversations/{{ steps.rca_analysis.output.conversation_id }}
- name: review
type: waitForInput
with:
message: |
批准有界的 checkout 响应?
事件:{{ consts.incident_id }}
证据:{{ steps.collect_evidence.output.hits.total.value }} 条匹配的 checkout 失败日志(最近 30 分钟内)。
目标:checkout-worker-07
操作:仅在此 Worker 上模拟清除定价缓存。
预期效果:后续 checkout 请求不再使用过时缓存。
影响范围:一个 Worker。此模拟步骤不会更改生产环境。
在决策前,请审查案例 {{ steps.create_case.output.case.id }} 中的 Agent Builder 分析。
schema:
type: object
properties:
approved:
type: boolean
title: "批准模拟缓存清除"
notes:
type: string
title: "审查者备注"
required:
- approved
- name: approved_action
type: console
if: "steps.review.output.approved : true"
with:
message: "已批准。已为 checkout-worker-07 记录模拟缓存清除。审查者备注:{{ steps.review.output.notes }}"
- name: verify_after_approval
type: elasticsearch.search
if: "steps.review.output.approved : true"
with:
index: "logs-*"
size: 0
query:
bool:
filter:
- range:
"@timestamp":
gte: "now-5m"
- term:
"service.name": "checkout-api"
- term:
"attributes.incident.id": "{{ consts.incident_id }}"
- match_phrase:
"body.text":
query: "checkout failed: stale pricing cache"
- name: record_approved
type: cases.addComment
if: "steps.review.output.approved : true"
with:
case_id: "{{ steps.create_case.output.case.id }}"
comment: |
## 人工决策:已批准
审查者备注:{{ steps.review.output.notes }}
模拟操作目标:checkout-worker-07
最近 5 分钟内的验证查询匹配数:{{ steps.verify_after_approval.output.hits.total.value }}
此演练未更改生产环境。
- name: record_declined
type: cases.addComment
if: "steps.review.output.approved : false"
with:
case_id: "{{ steps.create_case.output.case.id }}"
comment: |
## 人工决策:已拒绝
审查者备注:{{ steps.review.output.notes }}
未执行任何操作。
- name: declined_console
type: console
if: "steps.review.output.approved : false"
with:
message: "已拒绝。未执行任何操作。审查者备注:{{ steps.review.output.notes }}"collect_evidence 和 rca_analysis 步骤保持了与第一篇文章相同的“证据-推理”顺序,工作流在到达 waitForInput 边界之前就将分析写入案例。当人类被要求做出决策时,推理过程已经是持久且可链接的。
审批表单要求一个布尔值,并接受可选的备注。
只有批准分支会记录模拟缓存清除,运行一个有界的验证查询,并将结果追加到案例中。拒绝分支记录决策,不执行任何操作。
审批请求将结果计数带入决策,而链接的案例则保留完整的 Agent Builder 分析。将计数视为审查者的证据,而非根因声明或性能度量。
演练中将批准分支保留为 console 步骤,以便你可以安全地运行两个分支。在生产环境中,你只需将该步骤替换为范围狭窄的外部操作,并保持案例、超时、验证和拒绝路径完全不变。
根据目标系统的访问方式,你有两种选择。
在 Kibana 中配置一个 HTTP 连接器,包含基础 URL、认证和任何加密的标头,然后通过 connector-id 引用它。密钥保留在连接器中,永远不会出现在工作流 YAML 中。
- name: clear_pricing_cache
type: http
connector-id: "checkout-remediation-api"
if: "steps.review.output.approved : true"
with:
path: "/v1/cache/pricing/purge"
method: "POST"
body:
worker: "checkout-worker-07"
incident_id: "{{ consts.incident_id }}"Kibana 连接器可作为工作流步骤使用,因此批准分支可以为变更管理操作打开一个 jira 工单,向值班渠道发布 slack 消息,或通过 PagerDuty 寻呼——所有这些都使用你团队已集中管理的凭据。
- name: notify_oncall
type: slack
connector-id: "sre-oncall-channel"
if: "steps.review.output.approved : true"
with:
message: "由 {{ steps.review.output.notes }} 批准:已为 {{ consts.incident_id }} 清除 checkout-worker-07 上的定价缓存。"无论你选择哪种方式,都要保持操作的有界性。
上面的工作流展示了具体机制。正确设计关卡是一个设计问题:暂停放在哪里,请求告诉审查者什么,无人应答时会发生什么,以及执行记录需要保留什么。每一点都映射到五个绑定之一。
waitForInput 的最佳位置是紧接在第一个会增加影响的步骤之前。不要在收集证据前暂停,因为工作流通常可以在不接触受影响服务的情况下搜索、丰富、分类和打开草稿案例。也不要在修复后暂停,因为那只是要求一个人批准已经发生的事情。
在任何人启用工作流之前,审查者可以检查证据查询、表单架构、超时、分支条件和操作本身。
值班工程师不应从五个其他屏幕中重建调查。一上来就给出决策,并仅包含做出决策所需的证据。一个强有力的审批请求按以下顺序回答这些问题:
一个必须的决策加上可选的备注通常就足够了。如果审查者需要输入服务名称、主机标识符、环境、操作参数,那么提议在暂停之前就不够具体。

暂停的执行在历史记录中仍然是可发现的,并且可以由任何授权审查者恢复,这引出了一个队列需求。一旦一个团队拥有超过几个暂停的执行,审查者就需要一个收件箱或等效的筛选视图,显示待处理的决策、年龄、所有者、严重性和目标。否则,一个安全的暂停就会悄然变成一个不可见的积压。
waitForInput 没有默认超时;执行会无限期等待。settings.timeout 字段限制了整个执行的时间,包括等待输入的时间,在此工作流中,30分钟的值限制了提议在收集证据后的可操作时间。
根据事件和操作选择该值:在活跃故障期间,流量切换可能需要几分钟内做出决策。维护审批可能有效数小时。确认你操作的确切 Elastic 版本上的超时行为,并如果你观察到的情况不满足你的“失败关闭”要求,则添加一个外部升级或取消路径。
无论如何,都不要将沉默转换为批准。
执行历史记录应能回答四个问题,而无需任何人翻阅聊天记录:


一个暂停的执行显示证据步骤和仍在等待的决策。一个批准的执行会在同一视图中添加操作分支和验证输出。一个拒绝的执行保留相同的证据和相同的决策,同时跳过所有操作步骤。保持演练操作模拟,可以让你在检查两个分支的同时不更改生产环境。
对于更长生命周期的事件上下文,将其推送到案例中。将证据摘要、审查者备注、操作结果和验证结果作为评论添加,案例就成为比执行本身更持久的记录。
不要因为少数几次运行成功就移除审批关卡。审查足够多的执行,以同时理解正常和异常路径,并至少度量以下结果:
度量项 | 它回答的问题 |
|---|---|
提议接受率 | 工作流是否识别了正确的事件类别? |
审查者编辑或拒绝 | 哪些证据或操作参数仍然不正确? |
审批年龄 | 团队能否在证据变得过时之前做出响应? |
操作成功率 | 有界操作是否可靠地执行? |
验证成功率 | 操作后服务是否有所改善? |
回滚率 | 响应多久会带来一个新问题? |
仅当事件类别、目标选择、操作、验证、权限、超时和回滚路径都狭窄且可重复时,自主执行才是合理的。即便如此,也保持相同的工作流结构。自动化应该绕过已证明路径上的人类等待,而不是绕过证据收集、授权、验证或审计记录。将任何不确定的东西路由回人工审查。
审批关卡只是一小段 YAML,但它改变了自动化的本质。waitForInput 将决策转化为一个结构化输入,分支逻辑依赖于它,因此操作、验证和案例评论之所以存在,是因为一个具名的人在特定时间回答了一个特定问题。将暂停放在紧接在第一个会增加影响的步骤之前,并用 settings.timeout 进行限制,这就是它成为可靠性控制机制而非确认对话框的原因。
将其投入生产环境的变化比看起来要小:模拟的 console 步骤变成 http、slack 或 jira 连接器步骤,其他一切保持不变。从那里开始,你一次一个事件类别地赢得自主权,让接受率、审批年龄、操作成功率、验证成功率和回滚率告诉你,什么时候一个路径已足够证明,可以无需等待运行。
从一个重复出现的操作信号和一个可逆的响应开始。首先构建证据查询,然后添加一个指明目标和预期效果的推荐,接着在紧接操作之前插入 waitForInput,并在实验室或仅案例模式下运行工作流。与 SRE、开发人员、支持人员、安全团队以及受影响服务的产品负责人一起审查执行历史。
审批关卡不是终点。它是让团队能够向窄范围自主性迈进,同时不放弃证据、问责制或控制的机制。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。