freeCodeCamp.org

How to Separate Operational Intent from the Executor

7.1内容质量

TL;DR · AI 摘要

How to Separate Operational Intent from the Executor October 4, 2026 / software architecture Hugo Teijiz In my previous...

核心要点

  • 主题聚焦:How to Separate Operational Intent from the Exec
  • 来源:freeCodeCamp.org,建议结合原文判断细节。
  • AI 分析暂不可用,本条为保底评分与摘要。
#AI#编程#前端#云计算#安全
打开原文

如何将操作意图与执行器分离

2026年10月4日

/

#软件架构

Hugo Teijiz

在上一篇文章中,我提出软件自动化需要在操作意图与执行之间增加一个中间层。

原因很简单:规范应该描述成功意味着什么,而执行器应该决定如何实现它。

这种分离创造了有用的可能。

如果操作规范与执行机制独立,那么应该有多个执行器能够满足相同的规范。

这听起来很直接。但在实践中,它会引发一些困难的问题:

code
两个不同的执行器能否实现相同的操作结果?
我们如何比较它们?
当执行器改变时,什么必须保持稳定?
哪些差异是可以接受的?
每个执行器应该生成什么证据?
我们如何判断执行器独立性是真实存在的还是仅是理论上的?

这些问题很重要,因为现代软件系统很少会永远使用一种执行机制。

团队会变化:

code
CI/CD平台
云提供商
部署系统
基础设施工具
编排引擎
事件自动化
AI代理

如果更换执行器也会改变操作的含义,那么系统实际上并不是规范驱动的。执行器仍然拥有太多意图的控制权。

在本文中,我将向你展示如何将操作规范与其执行器分离,通过两种不同实现运行相同规范,收集证据,比较结果,并识别执行器独立性失效的位置。

目标不是证明两个执行器在内部行为完全一致。

目标是确定它们是否能够满足相同的操作契约。

先决条件

你应该熟悉以下内容:

  • 软件架构
  • 接口和依赖倒置
  • TypeScript或类似语言
  • CI/CD和部署概念
  • 可观测性
  • 基础测试
  • 操作规范

你不需要了解Kubernetes、Terraform或任何特定云平台。示例特意设计得小巧且基于内存,以保持架构的可见性。

目录

  • 先决条件
  • 执行器独立性的实际含义
  • 为什么执行器独立性很重要
  • 从一个稳定的操作规范开始
  • 定义执行器契约
  • 构建第一个执行器
  • 构建第二个执行器
  • 通过两个执行器运行相同规范
  • 比较证据而非内部步骤
  • 规范化执行器特定的证据
  • 将执行证据与符合性分离
  • 什么构成等效执行?
  • 执行器独立性通常失效的位置
  • 如何测试执行器可移植性
  • 为什么这对AI代理很重要
  • 一个小型端到端示例
  • 一个实用工作流程
  • 执行器独立性不意味着什么
  • 从可替换工具到稳定操作意图
  • 结论

执行器独立性的实际含义

执行器独立性意味着即使执行工作的机制发生变化,操作规范仍然有效。

例如,假设规范说明:

code
部署Orders版本v42。

约束条件:
- 至少有3个可用副本
- 错误率 <= 1%
- p95延迟 <= 400毫秒
- 必须保留回滚可能性

一个执行器可能使用:

code
Kubernetes滚动部署

另一个可能使用:

code
蓝/绿部署
code
托管云部署服务

稍后,AI代理可能会生成自己的执行计划。

如果每个执行器都可以根据相同的规范进行评估,那么该规范就完成了它的职责。

概念上:

code
┌── 执行器 A
操作规范 ───┼── 执行器 B
            ├── 执行器 C
            └── AI 代理

规范保持稳定,而执行机制发生变化。

这就是执行器独立性。

为什么执行器独立性很重要

软件团队经常更换工具。一个部署工作流可能会从:

code
Jenkins
↓
GitHub Actions
↓
Argo CD
↓
Kubernetes operator

如果每次迁移都需要重新发现:

code
成功意味着什么
哪些约束条件重要
需要哪些证据
何时允许回滚

那么操作含义从未真正独立于旧工具。

这会带来几个风险。

  • 工具锁定:系统可能在技术上可移植,但操作规则却无法移植。
  • 隐藏的行为变化:新执行器可能保留相同的部署步骤,但丢失了重要的约束条件。
  • 重新实现漂移:团队可能近似而非精确地重新创建旧行为。
  • 审计缺口:难以证明新执行器保持了相同的操作契约。

执行器独立性为您提供了一个更强的迁移目标:保留规范,替换机制。

从一个稳定的运行规范开始

要测试执行器独立性,首先定义一个稳定的内容。

例如:

code
type DeploymentSpec = {
  service: string;
  version: string;
  minReplicas: number;
  maxErrorRate: number;
  maxP95LatencyMs: number;
};

然后:

code
const spec: DeploymentSpec = {
  service: "orders",
  version: "v42",
  minReplicas: 3,
  maxErrorRate: 0.01,
  maxP95LatencyMs: 400,
};

这个规范不应该包含:

code
kubectl
helm
terraform
AWS
Azure
Argo
GitHub Actions

这些属于执行器。

规范描述了操作契约。

定义执行器契约

现在定义执行器必须满足的最小接口。

code
type ExecutionEvidence = {
  deployedVersion: string;
  availableReplicas: number;
  errorRate: number;
  p95LatencyMs: number;
};

interface DeploymentExecutor {
  execute(
    spec: DeploymentSpec
  ): Promise<ExecutionEvidence>;
}

这个接口没有说明部署是如何发生的。

它只说明:

code
给定一个规范,
执行操作,
返回证据

在本文中,证据指的是执行后生成或收集的可观测事实,这些事实让我们能够评估实际发生了什么。对于部署来说,这可能包括正在运行的版本、可用副本数、测量的错误率和p95延迟。

证据不是执行器认为操作成功的观点。它是我们可以与规范进行比较的数据。

这一点很重要。如果执行器接口包含特定于工具的概念,可移植性就会开始泄露。

例如,这会更加耦合:

code
interface DeploymentExecutor {
  executeKubectlCommand(
    namespace: string,
    manifestPath: string
  ): Promise<void>;
}

现在接口已经假定了Kubernetes。

这并不符合执行器独立性。

构建第一个执行器

让我们创建一个简单的内存执行器。

code
class RollingDeploymentExecutor
  implements DeploymentExecutor {
  async execute(
    spec: DeploymentSpec
  ): Promise<ExecutionEvidence> {
    return {
      deployedVersion: spec.version,
      availableReplicas:
        spec.minReplicas,
      errorRate: 0.004,
      p95LatencyMs: 280,
    };
  }
}

此执行器模拟滚动部署过程。

在内部机制上,可以想象它执行以下操作:

code
启动新副本
等待健康检查
逐步替换旧副本

但这些细节并未体现在规范中。执行器拥有自身的实现机制。

构建第二个执行器

现在创建不同的策略实现。

code
class BlueGreenExecutor
  implements DeploymentExecutor {
  async execute(
    spec: DeploymentSpec
  ): Promise<ExecutionEvidence> {
    return {
      deployedVersion: spec.version,
      availableReplicas:
        spec.minReplicas + 2,
      errorRate: 0.003,
      p95LatencyMs: 260,
    };
  }
}

该执行器可能在概念上执行以下操作:

code
创建并行环境
验证环境
切换流量
保留旧环境可用

其内部流程与滚动部署不同,但返回的证据结构保持一致。这意味着两者都可以基于相同的运营规范进行评估。

通过两种执行器运行相同规范

现在同时执行两种策略:

code
const rolling =
  new RollingDeploymentExecutor();

const blueGreen =
  new BlueGreenExecutor();

const rollingEvidence =
  await rolling.execute(spec);

const blueGreenEvidence =
  await blueGreen.execute(spec);

此时我们得到:

code
相同规范
不同执行器
不同内部行为
不同证据值

关键问题不在于它们是否执行了相同的操作步骤(实际上没有)。真正需要关注的是两者是否都满足相同的运营契约。

对比证据而非内部步骤

执行器独立性依赖于比较结果而非实现细节。

假设:

code
滚动部署:
副本数 = 3
错误率 = 0.4%
p95 = 280 ms

蓝绿部署:
副本数 = 5
错误率 = 0.3%
p95 = 260 ms

这些输出不完全相同,但都可能符合规范要求。这非常重要。

执行器独立性不要求:

code
相同命令
相同步骤数量
相同拓扑结构
相同时间安排
相同基础设施

它要求:

code
相同运营意图
满足约束条件
提供必要证据
产出可接受结果

这与基于接口的编程理念相似。两种实现可以采用不同的内部机制,但都能满足相同的契约要求。

统一执行器特定的证据格式

真实执行器通常返回不同格式的证据。

假设执行器A返回:

code
{
  "readyReplicas": 3,
  "image": "orders:v42",
  "latencyP95": 280
}

执行器B返回:

code
{
  "instancesHealthy": 5,
  "releaseVersion": "v42",
  "p95Ms": 260
}

这些证据无法直接比较,需要适配器转换。

code
type CanonicalEvidence = {
  version: string;
  availableReplicas: number;
  p95LatencyMs: number;
};

适配器A:

code
function normalizeRolling(
  raw: {
    readyReplicas: number;
    image: string;
    latencyP95: number;
  }
): CanonicalEvidence {
  return {
    version:
      raw.image.split(":")[1],
    availableReplicas:
      raw.readyReplicas,
    p95LatencyMs:
      raw.latencyP95,
  };
}

该适配器将滚动执行器的原生输出转换为规范证据模型。它从镜像标签中提取版本信息,将readyReplicas映射到availableReplicas,并将latencyP95重命名为p95LatencyMs。

关键点在于适配器不会改变操作语义。它仅将执行器特定的字段名称和格式转换为规范层预期的共享表示形式。

适配器B:

code
function normalizeBlueGreen(
  raw: {
    instancesHealthy: number;
    releaseVersion: string;
    p95Ms: number;
  }
): CanonicalEvidence {
  return {
    version:
      raw.releaseVersion,
    availableReplicas:
      raw.instancesHealthy,
    p95LatencyMs:
      raw.p95Ms,
  };
}

该适配器为蓝/绿执行器执行相同的功能。其原始输出使用不同的字段名称,但这些值代表相同的操作概念:版本、可用容量和p95延迟。

当两个适配器都就位后,系统其余部分不再需要理解执行器特定的结构。它可以使用相同的规范模型评估两种结果。

现在两个执行器都生成可以使用相同模型评估的证据。

这是一个重要的架构边界。执行器特定的证据保留在执行器附近,而规范证据属于规范层。

将执行证据与符合性分离

执行器应生成证据。它不应从规范的角度决定操作是否成功。

这种区分防止了另一种形式的耦合。

例如,避免:

code
return {
  success: true,
};

一个通用的成功标志告诉你的信息非常有限。

相反,返回证据:

code
return {
  deployedVersion: "v42",
  availableReplicas: 3,
  errorRate: 0.004,
  p95LatencyMs: 280,
};

然后单独进行评估。

code
type ConformanceResult = {
  conformant: boolean;
  failures: string[];
};

function evaluateConformance(
  spec: DeploymentSpec,
  evidence: ExecutionEvidence
): ConformanceResult {
  const failures: string[] = [];

  if (
    evidence.deployedVersion !==
    spec.version
  ) {
    failures.push(
      "wrong-version"
    );
  }

  if (
    evidence.availableReplicas <
    spec.minReplicas
  ) {
    failures.push(
      "insufficient-replicas"
    );
  }

  if (
    evidence.errorRate >
    spec.maxErrorRate
  ) {
    failures.push(
      "error-rate-too-high"
    );
  }

  if (
    evidence.p95LatencyMs >
    spec.maxP95LatencyMs
  ) {
    failures.push(
      "latency-too-high"
    );
  }

  return {
    conformant:
      failures.length === 0,
    failures,
  };
}

评估器接收两部分内容:定义应为真的规范,以及描述实际观察到情况的证据。

它独立检查每个约束条件。版本错误会添加"wrong-version",副本数不足会添加"insufficient-replicas",错误率和延迟检查的处理方式相同。

最后,只有在未记录任何失败时,conformant才会为true。返回单独的失败名称很有用,因为它解释了执行为何不符合规范,而不是将所有情况合并为一个通用的false。

这就是为什么执行器应返回事实而非最终裁决。如果规范变更、需要审计重建决策,或想使用完全相同的规则比较多个执行器,相同的证据可以重新评估。

现在:

code
执行器 → 证据

规范 + 证据 → 符合性

这种分离在后续会变得非常重要。

什么情况属于等效执行?

两个执行器不需要生成完全相同的证据。它们需要生成满足相同规范的证据。

code
执行器 A
副本数: 3
错误率: 0.4%
延迟: 280 毫秒

执行器 B
副本数: 5
错误率: 0.3%
延迟: 260 毫秒

两者都可能通过验证。

现在假设执行器 B 生成:

code
副本数: 2
错误率: 0.3%
延迟: 260 毫秒

它未能满足一个约束条件。

这意味着:

code
执行器 A:
符合规范

执行器 B:
不符合规范

执行器仍然是独立的实现,但只有其中一个满足此次执行的规范。

这种区分非常重要。

执行器独立性并不能保证执行器正确性。它仅能为您提供一个稳定的契约,用于评估正确性。

执行器独立性通常失效的场景

存在几种常见的失效模式。

规范中的工具特定字段

code
kubernetesNamespace: production
helmChart: orders

如果这些字段确实是实现细节,就不应该出现在操作规范中。

执行器自定义的成功规则

如果每个执行器都定义自己的阈值,规范将不再具有权威性。

缺少证据规范化

不同的执行器可能会暴露不同的概念,这些概念从未被映射到通用模型中。

隐藏的前提条件

执行器 A 可能需要审批。执行器 B 可能不需要。

如果审批是操作意图的一部分,这条规则不应该只存在于一个执行器内部。

隐藏的恢复行为

一个执行器可能会自动回滚,而另一个可能会让失败状态继续运行。

如果恢复行为很重要,规范应该明确表达这种预期。

语义不匹配

两种工具可能用相同词汇表示不同含义。

code
healthy
ready
available
running

这些概念需要明确的定义。否则执行器的可移植性只是表面的。

如何测试执行器可移植性

你可以显式地测试执行器独立性。

从一组规范开始。

code
const cases: DeploymentSpec[] = [
  {
    service: "orders",
    version: "v42",
    minReplicas: 3,
    maxErrorRate: 0.01,
    maxP95LatencyMs: 400,
  },
  {
    service: "payments",
    version: "v18",
    minReplicas: 5,
    maxErrorRate: 0.005,
    maxP95LatencyMs: 250,
  },
];

然后将每个规范应用于每个执行器。

code
const executors:
  DeploymentExecutor[] = [
    new RollingDeploymentExecutor(),
    new BlueGreenExecutor(),
  ];

for (const spec of cases) {
  for (const executor of executors) {
    const evidence =
      await executor.execute(spec);

    const result =
      evaluateConformance(
        spec,
        evidence
      );

    console.log({
      spec: spec.service,
      executor:
        executor.constructor.name,
      result,
    });
  }
}

这将生成有用的矩阵:

code
Rolling    BlueGreen
Orders v42     PASS       PASS
Payments v18   PASS       FAIL

现在你有了关于执行器可移植性的证据。

这比假设两种工具都支持部署因此等效要强得多。

为什么这对AI代理很重要

AI代理使执行器独立性更加有趣。

AI代理可能每次都会生成新的计划。

code
执行 1:
扩展
部署
验证
路由

执行 2:
创建并行环境
验证
切换流量

执行步骤 3: 部署金丝雀 观察 扩展发布

code

这些计划各不相同,执行器的行为是动态的。这使得逐步等价变得不切实际。

但规范仍然可以保持稳定。

部署订单 v42。

约束条件: 副本数 ≥ 3 错误率 ≤ 1% 延迟 ≤ 400 毫秒

证据: 运行版本 副本数 错误率 延迟

code

AI 代理可以选择任何可接受的计划,其结果仍通过相同的契约进行评估。

这创建了一个强大的边界:

代理自主性 内部 操作约束

code

代理可以优化执行。但它不能静默地重新定义成功。

## 一个小型端到端示例

让我们将这些组件组合在一起。

从规范开始:

创建两个执行器:

const executors: DeploymentExecutor[] = [ new RollingDeploymentExecutor(), new BlueGreenExecutor(), ];

code

运行它们:

for (const executor of executors) { const evidence = await executor.execute(spec);

const conformance = evaluateConformance( spec, evidence );

console.log( executor.constructor.name, evidence, conformance ); }

code

可能的结果:

RollingDeploymentExecutor

证据: 版本 = v42 副本数 = 3 错误率 = 0.004 p95 = 280

符合性: 通过

code

以及:

BlueGreenExecutor

证据: 版本 = v42 副本数 = 5 错误率 = 0.003 p95 = 260

符合性: 通过

code

执行器的行为并不完全相同,但它们不需要完全相同。它们满足了相同的操作规范。

现在假设第二个执行器报告:

副本数 = 2

code

结果变为:

BlueGreenExecutor

符合性: 失败

原因: 副本数不足

code

这正是我们想要的:规范保持稳定,执行器可以变化。证据揭示了执行是否符合契约。

## 实际工作流程

如果你想在真实系统中测试执行器独立性,我会使用这个流程。

### 1. 选择一个操作

部署服务 恢复备份 轮换证书 扩展工作池

code

### 2. 提取操作规范

定义:

目标 约束条件 证据要求 恢复预期

code

### 3. 移除工具特定语言

查找:

kubectl Terraform GitHub Actions AWS CLI 特定资源 ID

code

只保留真正属于操作意图的内容。

### 4. 定义执行器接口

让执行器负责:

执行操作 生成证据

code

### 5. 构建第一个适配器

包装现有实现,不要重写它。

### 6. 构建第二个执行器

使用:

另一个工具 另一种部署策略 模拟器 测试桩 AI 代理

code

### 7. 规范化证据

将执行器特定的观察结果映射到规范化的证据模型。

### 8. 分离评估符合性

不要让执行器决定自己是否通过。

### 9. 比较可移植性

让相同的规范通过多个执行器运行。

### 10. 调查差异

询问:

执行器是错误的吗?

规范是否不完整?

证据是否缺失?

这些概念是否真正等价?

code

这些差异是有用的。它们揭示了隐藏的耦合。

## 执行器独立性不意味着什么

执行器独立性并不意味着所有工具都可以互换。

不同的执行器可能有不同的:

能力 成本 延迟 故障模式 安全模型 操作复杂性

code

一个规范可能还要求某种能力,而某个执行器可能根本无法提供。

零停机时间部署

code

在一种平台上可能可行,在另一种平台上却可能无法实现。

这不是规范的失败,而是有用的信息。执行器无法满足合同要求。

执行器独立性并不意味着忽略实现细节。实现细节仍然影响以下方面:

性能 安全性 成本 可靠性 可维护性

code

重点更狭窄:操作含义不应依赖于某种特定的执行机制。

## 从可替换工具到稳定的操作意图

软件基础设施不断变化。

工具来来往往,执行策略不断演变,云平台发生变化,代理变得更加强大。

如果操作意图嵌入在每个执行器中,每次变更都可能引发语义迁移。

但如果意图是独立表示的:

操作规范 ↓ 稳定合同 ↓ 可替换执行器

code

那么系统将更容易演进。

这还给了我们另一项优势:

来自多个执行器的证据 根据相同规范进行评估

code

到那时,我们可以停止问:

> 工具是否完成?

而开始问:

> 这次执行在多大程度上符合规范?

这就是下一个问题。

## 结论

执行器独立性并不是假装每个工具都相同。它关乎保护操作意图免受实现细节的波动。

规范描述了:

应该发生什么 必须满足的约束条件 所需的证据

code

执行器决定:

如何实现它

code

然后执行生成证据。而证据可以独立评估。

这给了我们:

规范 ↓ 执行器A ──→ 证据A 执行器B ──→ 证据B ↓ 评估

code

两个执行器可以使用完全不同的策略,但仍能满足相同的操作合同。

这对于普通自动化来说是一个有用的特性。

当执行器是自主代理,其内部计划可能在每次运行中发生变化时,这一点变得更加重要。

但一旦多个执行器可以基于相同规范运行,一个新问题就不可避免了:

> 我们应该如何衡量规范与实际发生事件之间的一致性?

这就是我下一步想探讨的问题。

拥有25年以上经验的技术和产品领导者,曾在美国拉丁地区构建和扩展B2B平台、运营系统、云架构和技术团队。我撰写关于软件架构、技术债务、遗留系统现代化、人工智能、工程领导力和技术决策——尤其是团队投入时间、金钱和代码之前应做出的决策。OrkestCloud和Collabai的创始人。兼职CTO和技术顾问。我的核心原则:构建成本低廉;糟糕的决策代价高昂。

如果你读到这里,请感谢作者以表明你对他们的支持。说声谢谢

免费学习编程。freeCodeCamp的开源课程已帮助超过40,000人成为开发者。立即开始

ADVERTISEMENT