从建议到修复的 4 个阶段:使用 Elastic Workflows 实现人在回路中的自动化

作者:来自 Elastic Jeffrey Rengifo

一个审批关卡会在执行操作之前暂停故障事件响应自动化,并为审核人员提供足够的证据,让其能够在几秒钟内做出决定。无论后续结果是批准还是拒绝,都会记录在一个可审计的记录中。

没有证据支撑的 "批准" 按钮并不能算作一种控制手段。人在回路中自动化会将五件事情绑定到一个可检查的记录中:你观察到的证据、你提出的操作、做出决定的人、他们拥有的截止时间,以及实际执行的操作。

在本文中,你将使用 Elastic Workflows 构建这个审批关卡,并复用配套文章 使用 Agent Builder 和 Workflows 构建 SRE 控制平面 中的控制平面。这一次,故障事件足够明确,可以采取行动:单个 worker 上存在过期的定价缓存,并在建议和修复之间设置一个结构化的审批步骤。这里的任何操作都不会触及生产环境,因此你可以分别运行批准分支和拒绝分支,然后比较每个分支在执行记录中留下的内容。正是通过这份记录,故障事件响应自动化才能一次针对一个故障事件类别,逐步获得自主性。

前提条件

  • Elastic Stack 9.4+

在添加审批之前,SRE 控制平面需要什么?

本文基于使用 Agent Builder 和 Workflows 构建 SRE 控制平面,该文章定义了这里使用的控制平面模式:将遥测数据、调查上下文、策略以及一组已知操作连接在一起。Agent Builder 对日志、追踪、指标、警报、运行手册和之前的故障事件案例进行 推理 。Elastic Workflows 按照定义好的序列执行操作,并使用明确的输入和权限。人在回路中,即 HITL,则在证据转化为操作的关键节点,将这两项职责连接起来。

建议和修复具有完全不同的故障模式。糟糕的建议只是浪费工程师的时间,而糟糕的修复则会改变生产环境。这就是为什么审批关卡应该属于执行模型的一部分。

使审批关卡成为可靠性控制机制的五种绑定

一个有用的审批关卡需要保留五种属性。缺少其中任何一种,审批关卡都会变得更弱。

  • **证据绑定:**审核人员能够看到生成该建议所依据的日志、警报详细信息、补充信息或 agent 推理过程。没有证据的"批准"按钮,只是在要求一个人接受自动化系统的判断。

  • **操作绑定:**请求需要明确说明具体且范围受限的操作、目标、参数和预期效果。没有明确目标的请求,可能会授权执行远超审核人员预期的操作。

  • **身份绑定:**记录会显示谁批准或拒绝了该请求,以及做出决定的时间。没有这些信息,你只能知道结果,却没有责任归属。

  • **时间绑定:**决策具有截止时间,过期的请求会默认失败,而不是在失去上下文后继续执行。没有超时机制的审批请求,其生命周期可能超过当初证明该操作合理的故障事件证据。

  • **结果绑定:**执行记录会显示实际执行了什么、跳过了什么,以及操作后的验证是否通过。

表单仍然很重要,但表单只是一个更大控制机制中面向人工的部分。审批设计本身就是可靠性工程。

从 AI 建议到自动修复的四个阶段

应将自主性视为一系列运营状态,而不是一个产品开关。

自主性阶段 系统行为 人工职责 晋级依据
0. 观察 搜索日志并收集上下文。 人工调查并采取操作。 查询能够找到正确的故障事件证据。
1. 建议 提出一个范围受限的下一步操作,并提供理由。 决定是否执行,并在 workflow 外部执行。 建议足够准确,可以快速审核。
2. 审批 在执行操作前暂停,并且只有获得结构化输入后才继续执行。 批准或拒绝明确的操作建议。 对审批质量、执行成功率和回滚行为进行衡量。
3. 有限范围自动化 针对经过验证的故障事件类别,自动执行相同的操作。 审核异常情况并审计样本。 仍然强制执行范围限制、权限、超时、验证和回滚。

最重要的转换是从阶段 1 到阶段 2。正是在这里,系统不再只是一个顾问,而是获得了执行路径。即使 AI agent 参与了调查,这条路径也应该是确定性的:agent 负责总结证据并建议操作,而 workflow 负责暂停、结构化决策、分支处理和执行记录。

在 Elastic Workflows 中构建人在回路中的自动化审批关卡

Elastic Workflows 为此提供了 waitForInput step。当执行到达该步骤时,workflow 会停止在 WAITING_FOR_INPUT 状态并等待人工输入。审核人员可以在 Kibana execution view 中填写一个简单的表单(或通过 resume API 提交),提交的内容随后可以通过 steps.<step_name>.output 供后续步骤使用。

该 workflow 会获取过去 30 分钟 checkout-api 的失败日志,将这些日志交给 Agent Builder 进行根本原因分析,并将分析结果写入一个 Observability case。然后它会暂停,并提出一个问题:是否应该清除 checkout-worker-07 上的定价缓存?

如果你批准,workflow 会记录模拟操作,运行验证查询,并将结果追加到 case 中。如果你拒绝,它会记录该决定,并且不会执行任何操作。无论哪种情况,都不会触及生产环境。

yaml 复制代码
`

1.  name: obs-labs-checkout-control-plane-hitl
2.  description: Human approval gate for the OpenTelemetry-grounded checkout control plane.
3.  enabled: false
4.  tags:
5.    - sre-control-plane
6.    - agent-builder
7.    - human-in-the-loop
8.    - workflows
9.    - opentelemetry

11.  settings:
12.    timeout: "30m"

14.  triggers:
15.    - type: manual

17.  consts:
18.    incident_id: "obs-labs-checkout-hitl-20260719"

20.  steps:
21.    - name: collect_evidence
22.      type: elasticsearch.search
23.      with:
24.        index: "logs-*"
25.        size: 10
26.        query:
27.          bool:
28.            filter:
29.              - range:
30.                  "@timestamp":
31.                    gte: "now-30m"
32.              - term:
33.                  "service.name": "checkout-api"
34.              - term:
35.                  "attributes.incident.id": "{{ consts.incident_id }}"
36.              - match_phrase:
37.                  "body.text":
38.                    query: "checkout failed: stale pricing cache"

40.    - name: rca_analysis
41.      type: ai.agent
42.      agent-id: elastic-ai-agent
43.      create-conversation: true
44.      with:
45.        message: |
46.          Investigate the checkout-api incident identified by {{ consts.incident_id }}.
47.          The workflow evidence query found {{ steps.collect_evidence.output.hits.total.value }} matching OpenTelemetry log events in the last 30 minutes.
48.          Search logs, traces, and metrics for service.name checkout-api and incident.id {{ consts.incident_id }}.
49.          Identify the affected worker, deployment version, error type, HTTP status, and latency evidence.
50.          Return the likely cause, supporting evidence, confidence, and whether the bounded simulated action below is consistent with the evidence.
51.          Proposed simulated action: simulate clearing the pricing cache on checkout-worker-07.
52.          This workflow must not change production.

54.    - name: case_title
55.      type: ai.agent
56.      agent-id: elastic-ai-agent
57.      with:
58.        conversation_id: "{{ steps.rca_analysis.output.conversation_id }}"
59.        message: "Produce a short case title for this checkout incident. Output only the title."

61.    - name: case_description
62.      type: ai.agent
63.      agent-id: elastic-ai-agent
64.      with:
65.        conversation_id: "{{ steps.rca_analysis.output.conversation_id }}"
66.        message: "Produce a concise case description grounded in the OpenTelemetry evidence. Include the incident ID and say that remediation requires human approval. Output only the description."

68.    - name: create_case
69.      type: cases.createCase
70.      with:
71.        title: "{{ steps.case_title.output.message }}"
72.        description: "{{ steps.case_description.output.message }}"
73.        owner: "observability"
74.        severity: "medium"
75.        tags:
76.          - sre-control-plane
77.          - agent-builder
78.          - human-in-the-loop
79.          - opentelemetry

81.    - name: add_agent_analysis
82.      type: cases.addComment
83.      with:
84.        case_id: "{{ steps.create_case.output.case.id }}"
85.        comment: |
86.          ## Agent Builder RCA and proposed action

88.          Evidence query matches: {{ steps.collect_evidence.output.hits.total.value }}

90.          {{ steps.rca_analysis.output.message }}

92.          Proposed simulated action: simulate clearing the pricing cache on checkout-worker-07.
93.          No action has run yet.

95.          Agent conversation: {{ kibanaUrl }}/app/agent_builder/conversations/{{ steps.rca_analysis.output.conversation_id }}

97.    - name: review
98.      type: waitForInput
99.      with:
100.        message: |
101.          Approve the bounded checkout response?

103.          Incident: {{ consts.incident_id }}
104.          Evidence: {{ steps.collect_evidence.output.hits.total.value }} matching checkout failure logs in the last 30 minutes.
105.          Target: checkout-worker-07
106.          Action: simulate clearing the pricing cache on this worker only.
107.          Expected effect: subsequent checkout requests no longer use the stale cache.
108.          Blast radius: one worker. This simulated step does not change production.

110.          Review the Agent Builder analysis in case {{ steps.create_case.output.case.id }} before deciding.
111.        schema:
112.          type: object
113.          properties:
114.            approved:
115.              type: boolean
116.              title: "Approve the simulated cache clear"
117.            notes:
118.              type: string
119.              title: "Reviewer notes"
120.          required:
121.            - approved

123.    - name: approved_action
124.      type: console
125.      if: "steps.review.output.approved : true"
126.      with:
127.        message: "Approved. Simulated cache clear recorded for checkout-worker-07. Reviewer notes: {{ steps.review.output.notes }}"

129.    - name: verify_after_approval
130.      type: elasticsearch.search
131.      if: "steps.review.output.approved : true"
132.      with:
133.        index: "logs-*"
134.        size: 0
135.        query:
136.          bool:
137.            filter:
138.              - range:
139.                  "@timestamp":
140.                    gte: "now-5m"
141.              - term:
142.                  "service.name": "checkout-api"
143.              - term:
144.                  "attributes.incident.id": "{{ consts.incident_id }}"
145.              - match_phrase:
146.                  "body.text":
147.                    query: "checkout failed: stale pricing cache"

149.    - name: record_approved
150.      type: cases.addComment
151.      if: "steps.review.output.approved : true"
152.      with:
153.        case_id: "{{ steps.create_case.output.case.id }}"
154.        comment: |
155.          ## Human decision: approved

157.          Reviewer notes: {{ steps.review.output.notes }}
158.          Simulated action target: checkout-worker-07
159.          Verification query matches in the last 5 minutes: {{ steps.verify_after_approval.output.hits.total.value }}

161.          This walkthrough did not change production.

163.    - name: record_declined
164.      type: cases.addComment
165.      if: "steps.review.output.approved : false"
166.      with:
167.        case_id: "{{ steps.create_case.output.case.id }}"
168.        comment: |
169.          ## Human decision: declined

171.          Reviewer notes: {{ steps.review.output.notes }}
172.          No action ran.

174.    - name: declined_console
175.      type: console
176.      if: "steps.review.output.approved : false"
177.      with:
178.        message: "Declined. No action executed. Reviewer notes: {{ steps.review.output.notes }}"

`Lobster AI![](https://csdnimg.cn/release/blogv2/dist/pc/img/runCode/icon-arrowwhite.png)收起代码块![](https://csdnimg.cn/release/blogv2/dist/pc/img/arrowup-line-top-White.png)

collect_evidencerca_analysis 步骤保持了第一篇文章中相同的"先获取证据、再进行推理"的顺序,并且 workflow 会在到达 waitForInput 边界之前,将分析结果写入 case。这样,当需要人工做出决定时,推理结果已经被持久化,并且可以进行关联。

审批表单要求提供一个布尔值,并接受可选的备注。

只有批准分支会记录模拟的缓存清除操作、运行一个范围受限的验证查询,并将结果追加到 case 中。拒绝分支只记录决定,不执行任何操作。

审批请求会将最终的计数带入决策,而关联的 case 则保留完整的 Agent Builder 分析结果。应将这个计数视为提供给审核人员的证据,而不是根本原因结论或性能指标。

将模拟操作替换为实际的自动修复

本实践指南将批准分支保留为一个 console 步骤,这样你可以安全地运行两个分支。在生产环境中,你只需要将_这一个_步骤替换为范围严格受限的外部操作,而 case、超时、验证和拒绝路径则完全保持不变。

根据如何访问目标系统,你有两个选择。

使用 HTTP connector 调用内部修复 API

在 Kibana 中配置一个 HTTP connector,设置基础 URL、身份验证信息以及任何加密 headers,然后通过 connector-id 引用它。密钥保留在 connector 中,永远不会出现在 workflow YAML 中。

yaml 复制代码
 `1.    - name: clear_pricing_cache
2.      type: http
3.      connector-id: "checkout-remediation-api"
4.      if: "steps.review.output.approved : true"
5.      with:
6.        path: "/v1/cache/pricing/purge"
7.        method: "POST"
8.        body:
9.          worker: "checkout-worker-07"
10.          incident_id: "{{ consts.incident_id }}"`Lobster AI![](https://csdnimg.cn/release/blogv2/dist/pc/img/runCode/icon-arrowwhite.png)

交给 Jira、Slack 或 PagerDuty

Kibana connectors 可以作为 workflow 步骤使用,因此批准分支可以为需要变更管理的操作创建 jira 工单,向值班 channel 发布消息到 slack,或者通过 PagerDuty 发送页面通知,所有这些操作都使用团队已经集中管理的凭证。

yaml 复制代码
 `1.    - name: notify_oncall
2.      type: slack
3.      connector-id: "sre-oncall-channel"
4.      if: "steps.review.output.approved : true"
5.      with:
6.        message: "Approved by {{ steps.review.output.notes }}: pricing cache cleared on checkout-worker-07 for {{ consts.incident_id }}."`Lobster AI

无论你选择哪一种方式,都要确保操作与明确的目标绑定。

如何设计人在回路中的自动化审批关卡

上面的 workflow 展示了具体实现方式。要正确设计这个审批关卡,则是一个设计问题:暂停应该放在哪里、请求应该向审核人员提供哪些信息、无人响应时应该发生什么,以及执行记录必须保留哪些内容。这些问题分别对应前面提到的五种绑定。

在故障事件响应 workflow 中,审批步骤应该放在哪里?

waitForInput 最适合放在第一个会增加影响范围的步骤之前。不要在收集证据之前暂停,因为 workflow 通常可以在不触及受影响服务的情况下完成搜索、补充信息、分类以及创建草稿 case。也不要在修复之后暂停,因为那只是让人工去确认一个已经发生的操作。

在任何人启用 workflow 之前,审核人员都可以检查证据查询、表单 schema、超时设置、分支条件以及具体操作。

一个好的审批请求应该告诉审核人员什么?

值班工程师不应该需要打开另外五个界面,重新还原整个调查过程。应该首先明确需要做出的决定,并且只提供做出该决定所需的证据。一个好的审批请求应该按照以下顺序回答这些问题:

  1. 我究竟要决定什么?

  2. 哪些遥测数据支持这个建议?

  3. 操作将使用什么目标和参数?

  4. 预期效果和影响范围是什么?

  5. 如果我拒绝或什么都不做,会发生什么?

一个必填的决定加上可选的备注通常就足够了。如果审核人员还必须输入服务名称、主机标识符、环境以及操作参数,那么在暂停之前,这个建议就还不够具体。

暂停的执行会一直保存在历史记录中,并且任何获得授权的审核人员都可以恢复执行,这就带来了一个排队管理的要求。当团队中存在超过几个暂停的执行时,审核人员需要一个收件箱或类似的筛选视图,用于显示待处理的决定、等待时长、负责人、严重性和目标。否则,一个安全的暂停机制很容易悄悄变成一个无人关注的积压队列。

如果没有人在规定时间内批准,会发生什么?

waitForInput 没有默认的超时设置;执行会无限期地等待。settings.timeout 字段 会限制整个执行过程,包括等待输入所花费的时间。在这个 workflow 中,30 分钟的设置限制了证据收集完成后,该建议保持可执行状态的最长时间。

应根据故障事件和操作本身来选择这个值;在正在发生的服务中断期间进行流量切换,可能需要在几分钟内做出决定。而维护审批可能在数小时内都保持有效。请在你实际运行的 Elastic 版本上确认超时行为,如果实际观察到的行为不符合你的"默认拒绝"要求,请增加外部升级或取消路径。

无论如何,都不要将沉默视为批准。

审计记录必须记录什么?

执行历史应该能够回答以下四个问题,而无需任何人翻阅聊天记录:

  • workflow 收集了哪些证据?

  • 审核人员提交了什么确切的输入?

  • 哪个分支被执行了?

  • 操作和验证步骤返回了什么结果?

暂停的执行会显示证据步骤,而决定仍处于待处理状态。获得批准的执行会在同一个视图中增加操作分支和验证输出。被拒绝的执行会保留相同的证据和相同的决定,同时跳过所有操作步骤。让实践指南中的操作保持为模拟操作,可以让你检查两个分支,而不会改变生产环境。

对于生命周期更长的故障事件上下文,可以将其写入 case。将证据摘要、审核人员备注、操作结果和验证结果作为评论添加进去,这样 case 就会成为一个比执行本身生命周期更长的持久记录。

如何判断 workflow 已经可以在无需审批的情况下运行?

不要因为少数几次运行成功就移除审批关卡。你应该检查足够多的执行,以了解正常路径和异常路径,并至少衡量以下结果:

衡量指标 它回答的问题
建议接受率 workflow 能否识别正确的故障事件类别?
审核人员修改或拒绝的比例 哪些证据或操作参数仍然存在问题?
审批等待时间 团队能否在证据过期之前做出响应?
操作成功率 范围受限的操作能否可靠执行?
验证成功率 操作之后服务是否得到改善?
回滚率 响应操作有多少次导致了新的问题?

只有当故障事件类别、目标选择、操作、验证、权限、超时和回滚路径都足够明确且可重复时,才适合进行自主执行。即使如此,也应该保留相同的 workflow 结构。自动化应该绕过经过验证路径中的人工等待,而不是绕过证据收集、授权、验证或审计记录。任何不确定的情况都应该重新交给人工审核。

总结

审批关卡只需要少量 YAML,但它会改变自动化的性质。waitForInput 将决定转换为分支逻辑所依赖的结构化输入,因此操作、验证和 case 评论之所以存在,都是因为一个明确的人在特定时间回答了一个具体的问题。在第一个会增加影响范围的步骤之前立即暂停,并通过 settings.timeout 设置上限,这才使它成为一种可靠性控制机制,而不仅仅是一个确认对话框。

将其投入生产环境的改动比看起来要小:将模拟的 console 步骤替换为 httpslackjira connector 步骤,其余部分保持不变。从这里开始,你可以一次针对一个故障事件类别逐步获得自主性,让接受率、审批等待时间、操作成功率、验证成功率和回滚率告诉你某条路径何时已经经过充分验证,可以在无需等待人工审批的情况下运行。

从哪里开始使用人在回路中的自动化?

从一个重复出现的运营信号和一个可逆的响应操作开始。首先构建证据查询,然后添加一个明确指定目标和预期效果的建议,接着在操作之前立即插入 waitForInput,并在实验环境或仅操作 case 的模式下运行 workflow。与 SRE、开发人员、支持团队、安全团队以及受影响服务的产品负责人一起检查执行历史。

审批关卡不是终点。它是一种机制,让团队能够在不放弃证据、责任追踪或控制权的情况下,逐步迈向范围受限的自主性。

原文:Human-in-the-loop automation for SRE incident response | Elastic Observability Labs

相关推荐
鲨鱼辣钊2 小时前
FastAPI筑基_Day15_Alembic数据库迁移实战
数据库·elasticsearch·fastapi
Elasticsearch3 小时前
Agent 上岗做日志检测,日志赛道降本的叙事快讲不下去了
elasticsearch
Elastic 中国社区官方博客4 小时前
从告警到根因仅需 3 分钟:使用 Elastic Agent Builder 实现自动化根因分析
运维·数据库·人工智能·elasticsearch·自动化·可用性测试
Elastic 中国社区官方博客4 小时前
驯服 PUNKs:ES|QL 如何查询 Elasticsearch 从未被告知的字段
大数据·sql·elasticsearch·搜索引擎·全文检索
Elasticsearch5 小时前
了解你的事实:Elasticsearch AI Indices 让 agents 无需通读内容,就能保留答案
elasticsearch
Elasticsearch6 小时前
机构如何统一智慧城市数据以改善公共服务?
elasticsearch
Elastic 中国社区官方博客15 小时前
搜索倍增器:推动收入、生产力和 AI 实现规模化
大数据·数据库·人工智能·elasticsearch·搜索引擎·ai·全文检索
Elasticsearch18 小时前
Microsoft Foundry 的 AI agent 可观测性:两个环境变量,无需采集器
elasticsearch
Elastic 中国社区官方博客20 小时前
OpenTelemetry Java 扩展:无需分叉 agent 即可自定义追踪
java·大数据·运维·开发语言·数据库·人工智能·elasticsearch