控制平面与执行平面:Agent OS核心架构哲学
时间:2026-07-09 15:28
控制平面与执行平面分离是生产级Agent系统的核心原则。控制平面负责调度、权限、监控与审计,执行平面专注执行任务并回传状态。工具网关统一治理工具调用,接入层处理外部请求。这种架构确保系统安全可控、可运营。

为什么“管控与执行分离”是生产级 Agent 系统的核心原则?当你有 100 个 Agent 同时运行时,“能跑”和“能管”之间的距离,比“能写代码”和“能写生产级代码”之间的距离还要远。
LangGraph 跑通了,然后呢
上一篇我们讨论了四种 Harness 架构的选型。很多团队在选择了 Graph 架构(比如 LangGraph)之后,很快会遇到一个共同的瓶颈:**开发阶段跑得通,但部署到生产后问题接踵而至**。
具体来说,LangGraph 在开发阶段的优势非常明显——检查点、时间旅行调试、Human-in-the-Loop 节点,这些都是开箱即用的能力。但当你试图把它部署为一个服务化的产品,以下问题会逐一暴露:
- **并发控制缺失**:100 个用户同时发起任务,LangGraph 默认没有任何并发限制,所有任务在同一个进程里竞争资源。
- **审计盲区**:Agent 做了什么、调用了什么工具、消耗了多少 Token——这些数据散落在各个节点的日志里,没有统一视角。
- **多租户隔离**:团队 A 的 Agent 能否访问团队 B 的数据?租户之间如何隔离上下文和记忆?
- **资源配额**:单个 Agent 消耗了 50 万 Token,如何限制?如何防止某个用户的“坏任务”耗尽整个集群的预算?
- **模型热切换**:运行中的 Agent 能否在不中断任务的情况下切换到另一个模型?
这些问题的共同特征是:它们不是“某个 Agent 能不能完成某个任务”的问题,而是**“整个 Agent 系统作为一个基础设施,能不能被安全、可控、可审计地运营”**的问题。
这就是控制平面与执行平面分离的出发点。
控制平面:谁来管
控制平面到底管什么?一句话就能说清楚:**决定什么运行、怎么运行、运行得对不对**。它不负责具体执行,但所有执行都要经过它的许可和监控。
调度与生命周期管理
控制平面是 Agent 系统的“调度器”。它维护一个任务队列,决定哪个 Agent Worker 执行哪个任务,处理优先级、依赖关系和死锁检测。
但这里的调度比传统任务调度更复杂,因为 Agent 任务具有**不确定性**。一个后端 API 请求的处理时间通常在毫秒级,可以精确预测;但一个 Agent 任务可能耗时 5 分钟,也可能 2 小时——取决于模型推理的速度、工具调用的次数、错误重试的次数。控制平面必须在这种不确定性下做出调度决策。
IBM 在描述 Agent 控制平面时指出,它“作为 Agent 和其所依赖系统之间的中介,路由请求、执行权限检查、在动作执行前应用策略”。这一定义强调了控制平面的**中介属性**——它不替代 Agent 的推理,而是在 Agent 和外部世界之间插入一个管控层。
状态持久化与断点续跑
上一篇讲过 Graph 架构的检查点机制。控制平面负责**统一协调检查点的存储、恢复和清理**。具体来说:
- 检查点存到哪里?PostgreSQL?S3?Redis?——控制平面管理存储后端的选择和切换。
- 检查点保留多久?7 天还是 30 天?——控制平面管理生命周期策略。
- Worker 崩溃后,由哪个新的 Worker 接管未完成的任务?——控制平面负责任务的重新分配。
这类似于 Kubernetes 的 etcd 存储所有集群状态,Scheduler 决定 Pod 分配到哪个 Node,Controller Manager 负责 ReplicaSet 的期望状态与实际状态的调和。
安全护栏与权限控制
控制平面是安全策略的**唯一真源**(Single Source of Truth)。它定义了:
- 哪些 Agent 可以调用哪些工具(RBAC)。
- 哪些目录可以读写、哪些 API 可以访问(ABAC)。
- 敏感操作是否需要人工审批(Policy-as-Code)。
- Prompt Injection 的检测和拦截规则。
关键设计原则是:**权限检查在完全不同的代码路径上**,不能被 Agent 的推理逻辑绕过。即使 Agent 被提示注入攻击诱导,控制平面的权限检查仍然独立生效。这和后端开发中“业务逻辑层不负责鉴权,鉴权在网关层统一处理”的原则完全一致。
全链路监控与审计
控制平面收集所有 Agent 的运行数据:每次模型调用的输入输出、每个工具调用的参数和结果、每次状态转换的 before/after、每次人工介入的触发原因。
Khaled Zaky 在描述 Agent 控制平面的运营视角时指出,“stuck runs、异常行为、评估漂移、护栏触发、上下文长度膨胀、重复工具失败——这些是可观测性和评估停止作为独立仪表板、开始成为共享运营画面的时刻”。
控制平面不是给人看的 Dashboard,而是给整个系统看的**运营画面**。系统基于这个画面自动做出干预决策:暂停漂移的 Agent、回滚异常的任务、通知人类介入。
> **后端类比:** 控制平面 ≈ Kubernetes 的 API Server + Scheduler + Controller Manager + etcd。etcd 存储所有集群状态(对应 Agent 状态持久化);Scheduler 决定 Pod 分配到哪个 Node(对应 Agent 任务调度);Controller Manager 负责期望状态与实际状态的调和(对应生命周期管理)。Prometheus + Grafana 提供可观测性(对应全链路监控)。
执行平面:怎么跑
执行平面是 Agent 系统的“Worker Node”。它的职责更简单,也更专注:**接收控制平面分配的任务,执行到底,把结果和状态回传给控制平面**。
Agent Worker 被托管运行
执行平面中的每个 Agent Worker 都是“被托管的”——它的生命周期不由自己决定,而由控制平面管理。Worker 启动时从控制平面获取任务配置(可用工具列表、Token 预算、超时限制),运行过程中向控制平面汇报心跳和进度,结束后由控制平面决定是否销毁或回收。
这种“托管”模式带来了几个好处:
- **资源隔离**:每个 Worker 运行在独立的进程或容器中,一个 Worker 的崩溃不会影响其他 Worker。
- **弹性伸缩**:控制平面可以根据队列长度自动增删 Worker(水平扩展)。
- **模型可替换**:同一个控制平面可以调度使用 GPT-5 的 Worker,也可以调度使用 Claude 4 的 Worker——执行平面不绑定特定模型。
支持多种 Agent Loop
执行平面的另一个关键设计是**不假设 Agent 的具体实现**。一个 Worker 可以跑 ReAct Loop,另一个跑 LangGraph StateGraph,第三个跑自定义的 Plan-and-Execute 循环。控制平面不关心 Worker 内部怎么跑,只关心输入、输出和状态报告。
这和 Kubernetes 的 Pod 设计完全一致:kubelet 不管 Pod 里跑的是 Ja va 应用还是 Go 服务,它只负责创建、监控和销毁 Pod。具体业务逻辑在容器内部自行决定。
资源限制
执行平面负责实施控制平面下发的资源限制:Token 预算、运行超时、内存上限、并发工具调用数。当某个限制被触发时,执行平面必须优雅地终止任务,将当前状态保存为检查点,回传给控制平面。
这里的“优雅终止”非常重要。Agent 任务不像 HTTP 请求可以简单地返回 504——它可能已经修改了文件、更新了数据库、发送了外部请求。执行平面需要协调这些副作用的清理或回滚。
> **后端类比:** 控制平面 ≈ Kubernetes 的 API Server + Scheduler + Controller Manager;执行平面 ≈ Worker Node + Kubelet。Agent Worker ≈ Pod。控制平面决定“什么运行在哪里”,执行平面负责“让它跑起来”。这种分离让系统获得了两个独立演化的维度——控制平面的策略可以独立于执行平面的实现进行升级。
工具网关:所有工具的“海关”
在控制平面和执行平面之外,还有一个经常被忽视但至关重要的组件:**工具网关**(Tool Gateway)。
如果没有工具网关,每个 Agent Worker 会直接调用工具——这意味着工具调用的权限检查、参数校验、沙箱隔离、结果缓存,都必须在每个 Worker 内部重复实现。更严重的是,不同 Worker 可能对同一个工具有不同的理解(schema 版本不一致),导致行为不一致。
工具网关的三层职责
Stripe Minions 的 Toolshed 是工具网关的典型实现:集中化内部 MCP 服务器,托管近 500 个 MCP 工具,添加工具即自动对所有 Agent 可用。但更关键的是它的三层职责:
**第一层:参数校验与合规性检查。** 工具网关在所有工具调用前执行 JSON Schema 验证、取值范围检查、SQL Injection 检测。这相当于后端开发中的输入参数校验——不应该让每个 Service 自己实现,而应该在网关层统一处理。
**第二层:权限拦截与沙箱隔离。** 工具网关根据控制平面下发的权限策略,判断当前 Agent 是否有权调用目标工具。如果有权,它将工具调用路由到沙箱执行环境(如 bwrap、Docker、EC2 Devbox)。执行完成后,它检查返回结果是否包含敏感信息。
**第三层:结果缓存与监控。** 相同参数调用相同工具时,工具网关可以直接返回缓存结果,避免重复消耗 Token 和 API 配额。同时,它记录每次工具调用的耗时、成功率、错误类型,为控制平面的监控提供数据。
为什么工具网关不是“袋里层”
有人会把工具网关简单理解为 API Gateway 的袋里转发。但这两者有本质区别:
- API Gateway 袋里的是**确定性的 API 调用**——请求进来,转发出去,响应回来。
- 工具网关治理的是**不确定性的工具使用**——Agent 可能反复调用同一个工具、可能传错参数、可能在循环中疯狂调用工具耗尽配额。
工具网关的核心使命是**治理**,不是转发。它需要理解 Agent 的行为模式,检测异常(如 5 分钟内同一个工具被调用 100 次),并在必要时触发熔断或告警。
> **后端类比:** 工具网关 ≈ API Gateway + 服务网格 Sidecar + 风控规则引擎。API Gateway 做请求路由和参数校验;Sidecar 做服务间通信的治理(如 Istio 的 mTLS、流量镜像);风控引擎检测异常行为模式并触发熔断。三者的叠加,才能覆盖 Agent 工具调用的不确定性。
> arxiv 上一篇题为“Control Plane as a Tool”的论文提出了一个有趣的视角:将控制平面本身建模为一种工具,Agent 通过调用控制平面的工具来与其他 Agent 交互。这种设计让编排对 Agent 透明,支持复用、缓存、验证和动态组合。换句话说,**控制平面不只是“管控者”,也可以成为 Agent 的工具之一**。
接入层:Agent 系统的“门面”
接入层是 Agent 系统对外暴露的接口。它本身不处理 Agent 逻辑,但负责所有与外部世界的交互协议。
多渠道入口
一个生产级 Agent 系统需要支持多种接入方式:
- **HTTP API**:同步调用,适合短任务(<30 秒),返回结构化 JSON。
- **WebSocket**:流式响应,适合长任务,可以实时推送 Agent 的推理过程和中间结果。
- **消息队列**(如 Kafka、RabbitMQ):异步任务,适合批量处理、后台任务、定时任务。
接入层负责协议转换:将 HTTP 请求转换为内部任务格式,将 WebSocket 的流式输出转换为客户端可消费的增量数据,将 MQ 消息的消费 ack 与任务完成状态关联。
多租户与流量管控
接入层是**多租户隔离的第一道防线**。在请求进入控制平面之前,接入层需要:
- 认证:验证调用方的身份(API Key、OAuth Token、JWT)。
- 限流:按租户维度限制 QPS 和并发数。
- 路由:将请求路由到对应的控制平面实例(如果是多控制平面部署)。
- 配额检查:检查租户的 Token 预算是否已耗尽。
这对应后端开发中的 API Gateway + Ingress Controller 的角色。但有一个关键差异:传统 API Gateway 的限流基于请求数(QPS),而 Agent 系统的限流需要同时考虑**Token 消耗速率**——一个请求可能消耗 100 Token,也可能消耗 10 万 Token。
> **后端类比:** 接入层 ≈ Ingress Controller + API Gateway。Ingress 负责将外部流量路由到内部服务;API Gateway 负责认证、限流、配额检查。Agent 系统的特殊性在于:限流不只是按请求数,还要按 Token 数——这类似于云厂商按请求数和按数据传输量双重计费。
Agent OS 架构全景
到这里,我们已经拆解了 Agent 系统的四大组件:控制平面、执行平面、工具网关和接入层。但它们不是孤立存在的,而是作为一个整体系统协同工作。以下是一张抽象层面的 Agent OS 架构图,展示各组件之间的关系和数据流:
[图1:Agent OS 架构全景——四大组件的协作关系]
这张图揭示了两个关键设计原则:
- **所有外部请求必须经过接入层**,由接入层完成认证和初步限流后,才进入控制平面的调度队列。
- **所有工具调用必须经过工具网关**,Agent Worker 不直接访问外部工具,而是通过网关统一治理。
控制平面处于整个系统的中心位置——它向下调度执行平面的 Worker,向右管理工具网关的权限策略,向上通过接入层接收外部请求。这种“中心化但不执行”的设计,是 Agent OS 区别于传统应用架构的核心特征。
Stripe Minions:五层管道的生产实践
理论讲到这里,让我们看一个真实的大规模生产系统:Stripe Minions。每周超过 1,300 个 PR 完全由 Agent 生成,涉及数十个 Agent 并行工作。
五层管道架构
[图2:Stripe Minions 五层管道架构]
**调用层(Invocation Layer)**:接收任务,可以是 HTTP API 调用、CLI 命令、或定时触发。这一层对应我们说的“接入层”。
**Devbox(标准化执行环境)**:AWS EC2 实例,预装 Stripe 完整代码库和运行服务,10 秒内启动。Devbox 运行在 QA 环境中,无生产访问、无真实用户数据、无任意网络出口。关键洞察:**Devbox 的安全属性是现有基础设施设计的副产品,不是专门为 Agent 构建的**。这意味着如果你已经在使用不可变基础设施(Immutable Infrastructure),Agent 的安全隔离可以“免费”获得。
**Agent 核心(Agent Core)**:基于 Block(Square)的 Goose fork,针对 Stripe 基础设施定制。完全无人值守——没有中断性确认提示,没有“你确定要删除这个文件吗?”这类弹窗。
**蓝图(Blueprint)**:Stripe Minions 最核心的架构创新。Blueprint 是一个混合编排的状态机——确定性节点(矩形,如 “Run configured linters”)与 Agent 节点(云形,如 “Implement task”)交替执行。
Stripe 的实践中有一个深刻的认识:**“写代码来确定性地完成我们能预见的小决策——比如我们总是想在提交前运行 linter——比让 Agent 自行决定何时运行 linter 更可靠”**。这就是 Blueprint 的哲学:把确定性的部分硬编码,把不确定性的部分交给 Agent。
**CI 循环(CI Loop)**:本地 Lint(<5 秒)→ 提交到 CI → 最多 2 轮自动修复 → 人类 Review。标准 Blueprint 包含一次完整的 CI 运行,加一次修复机会。如果 2 轮后仍未通过,任务标记为失败,回传给控制平面处理。
“牛而非宠物”的深层含义
Stripe Minions 的每个 Agent 执行环境都是标准化的、一次性的、可替换的。这和传统运维中“宠物 vs 牛”(Pets vs Cattle)的比喻一致:你不给每头牛起名字,它生病了直接换掉,而不是精心照料。
但 Stripe 的做法比传统比喻更极端:每个 Devbox 只服务一个任务,任务完成后立即销毁。没有状态保留,没有渐进式配置——**每次都是全新启动**。这种设计消除了“环境漂移”问题:不存在“这个 Agent 上次运行时修改了某个配置文件,下次运行时状态不一致”的风险。
一位观察者总结 Stripe 的关键洞察时写道:**“关键洞察是系统控制 Agent,而不是反过来”**。在典型的 AI 编码工作流中,Agent 自己规划、实现、验证。问题是没有任何保证 Agent 会执行你真正想要的验证。Stripe 通过 Blueprint 解决了这个问题——不是相信 Agent 会“记得”运行 linter,而是让 linter 成为流程中的**强制节点**。
控制平面设计的三种模式
当系统规模扩大后,单一控制平面会成为瓶颈。Paul Serban 在 2026 年提出了三种控制平面架构模式,值得深入思考:
模式一:Hub(集中式)
所有 Agent 通过一个中心化的控制平面进行通信和协调。优点是简单、一致性强——所有策略在一个地方定义,所有监控在一个地方聚合。缺点是单点故障、扩展受限——当 Agent 数量增加到数千时,中心 Hub 的吞吐量和延迟会成为瓶颈。
适用场景:中小规模部署(<100 个 Agent)、对一致性要求高的场景(如金融审计)。
模式二:Mesh(分布式)
控制平面职责分布到每个 Agent 上,每个 Agent 内嵌路由、可观测性、策略执行能力,Agent 之间直接通信。优点是去中心化、扩展性好——新增 Agent 不会给中心节点增加压力。缺点是治理复杂——策略需要在所有 Agent 之间同步,版本不一致可能导致行为差异。
适用场景:大规模部署(1000+ Agent)、对延迟敏感的场景(如实时对话系统)。
模式三:Federated(联邦式)
每个团队或业务单元运行自己的控制平面实例,上层有一个聚合层负责跨团队的策略同步和全局审计。Guild.ai 在描述这种模式时指出,它的优势是自主性——团队可以按自己的节奏迭代,不用等待中央平台组的审批。代价是一致性——不同团队的策略可能不同,跨团队协作时需要额外的适配。
适用场景:大型企业、多团队共享平台、需要领域特定策略的场景。
| 模式 | 优点 | 缺点 | 适用规模 | 后端类比 |
| :--- | :--- | :--- | :--- | :--- |
| **Hub** | 简单、一致性强 | 单点故障、扩展受限 | <100 Agent | 单体 API Gateway |
| **Mesh** | 去中心化、低延迟 | 策略同步复杂 | 1000+ Agent | Service Mesh(Istio) |
| **Federated** | 团队自治、灵活 | 一致性挑战 | 多团队/企业级 | 多集群 K8s Federation |
深入反思:为什么管控比执行更难
停下来做一个反思。为什么“管控与执行分离”这个在后端领域已经习以为常的原则,在 Agent 领域却花了这么长时间才被广泛接受?
一个根本原因是:**Agent 的不确定性放大了管控的复杂性**。
在传统后端系统中,一个 HTTP 请求的处理路径是确定的——它经过哪些服务、调用哪些数据库、消耗多少资源,都是可预测的。即使引入微服务,每个服务的接口契约仍然是固定的。管控的目标很明确:限流、熔断、降级。
但在 Agent 系统中,**执行路径本身是不确定的**。同一个任务,这次 Agent 用了 5 步完成,下次可能因为推理差异用了 15 步。这次调用了 3 个工具,下次调用了 8 个。这种不确定性意味着:
- 你无法像后端限流那样简单地按“每秒请求数”来限制——一个“请求”的 Token 消耗可能是另一个的 100 倍。
- 你无法像熔断器那样按“错误率”来触发——Agent 的“错误”可能是暂时的推理偏差,也可能是致命的逻辑 bug。
- 你无法像超时那样简单地按“响应时间”来终止——Agent 的思考过程本身就是有价值的,过早终止会浪费已消耗的 Token。
这引出了一个更深层的反思:**我们正在用确定性基础设施(K8s、API Gateway、数据库)去管控一个本质上不确定的执行引擎(LLM)**。这种错配不是技术问题,而是**范式问题**。
解决方向可能是双向的:
**方向一:让基础设施更“Agent-aware”**。控制平面需要理解 Agent 的语义——不是“这个请求花了 30 秒”,而是“这个 Agent 在 30 秒内做了 12 次工具调用,其中 8 次是重复的,建议触发干预”。
**方向二:让 Agent 更“基础设施-aware”**。Agent 需要理解自己的资源约束——不是“我有无限的时间和 Token 来完成任务”,而是“我的 Token 预算还剩 20%,需要加速或请求额外配额”。
Paul Serban 指出,“对于延迟敏感的应用,通过 Hub 的额外跳转增加了开销”。这暗示了一个更激进的未来:控制平面和执行平面的边界可能不是固定的,而是根据任务特征动态调整的——简单任务走轻量路径,复杂任务走完整管控路径。
> **后端类比:** 控制平面 ≈ K8s Controller Manager + Scheduler;执行平面 ≈ Worker Node + Kubelet;Agent Worker ≈ Pod;工具网关 ≈ API Gateway + 服务网格 Sidecar;接入层 ≈ Ingress + 限流中间件;Stripe 管道 ≈ CI/CD Pipeline(Jenkins/GitLab CI 的 Stage 拆分)。这些类比不是修辞,而是架构的同构——我们正在将过去十年在后端领域沉淀的分布式系统设计模式,迁移到 AI Agent 这个新场景。
总结
回到开头提出的那个问题:LangGraph 跑通了,然后呢?控制平面与执行平面的分离,给出了系统性的回答。
- **并发控制缺失** → 控制平面的调度器统一分配任务,执行平面的 Worker 独立运行且资源隔离。
- **审计盲区** → 控制平面收集全链路数据,将可观测性从“独立仪表板”升级为“共享运营画面”。
- **多租户隔离** → 接入层在请求入口处完成认证和配额检查,工具网关在工具调用处执行权限拦截。
- **资源配额** → 控制平面下发 Token 预算和超时限制,执行平面实施并优雅终止。
- **模型热切换** → 执行平面不绑定特定模型,控制平面可以调度不同模型的 Worker。
这五个问题的共同答案是:**Agent 不是一个“更聪明的函数”,而是一个需要被管理的“智能进程”**。函数只需要输入输出正确,但进程需要生命周期管理、资源约束、安全隔离和审计追踪。
Stripe Minions 的实践给了我们一个清晰的参照:用 Blueprint 把确定性部分硬编码,把不确定性部分交给 Agent;用 Devbox 实现环境标准化和隔离;用 CI 循环强制验证。这套体系的底层逻辑就是——**永远不要相信 Agent 会“自觉”地做正确的事,而是把正确的事设计成无法绕过的流程**。
控制平面设计的三种模式(Hub / Mesh / Federated)没有绝对优劣,选择取决于规模、延迟敏感度和组织治理结构。但无论如何选择,核心原则不变:**管控能力必须独立于执行能力演化**。今天你的系统可能只有 5 个 Agent,控制平面看起来是过度设计;但当规模增长到 500 个时,没有控制平面的系统会在一夜之间崩溃。
**下一篇预告**
**第 8 篇:单 Agent vs 多 Agent —— 何时拆分,如何编排。** 控制平面管理好了,下一个问题是:一个控制平面下应该跑多少个 Agent?什么时候该把一个 Agent 拆成多个?多 Agent 之间的通信成本是多少?Token 经济学如何影响编排策略?我们将用具体的数学和数据来回答这些问题。
参考文献
1. IBM, “What is an agent control plane?” [https://www.ibm.com/think/topics/agent-control-plane](https://www.ibm.com/think/topics/agent-control-plane)
2. Khaled Zaky, “From Guardrails to Operating Model: The Agent Control Plane”. [https://www.khaledzaky.com/blog/from-guardrails-to-operating-model-the-agent-control-plane](https://www.khaledzaky.com/blog/from-guardrails-to-operating-model-the-agent-control-plane)
3. Alistair Gray, “Minions: Stripe's One-Shot End-to-End Coding Agents (Part 2)”, Stripe Engineering, 2026.02. [https://stripe.dev/blog/minions-stripes-one-shot-end-to-end-coding-agents-part-2.md](https://stripe.dev/blog/minions-stripes-one-shot-end-to-end-coding-agents-part-2.md)
4. arxiv, “Control Plane as a Tool: A Scalable Design Pattern for Agentic AI Systems”, 2025. [https://arxiv.org/pdf/2505.06817](https://arxiv.org/pdf/2505.06817)
5. ONA, “How Stripe and Ramp Built Self-Driving Codebases” White Paper. [https://ona.com/files/background_agents_white_paper.pdf](https://ona.com/files/background_agents_white_paper.pdf)
6. ice-ice-bear, “How Stripe Ships 1,300 PRs a Week — Coding Agents and Adversarial Development”. [https://ice-ice-bear.github.io/posts/2026-04-01-stripe-coding-agents/](https://ice-ice-bear.github.io/posts/2026-04-01-stripe-coding-agents/)
7. Paul Serban, “Architecting the AI Agent Control Plane: 3 Design Patterns for 2026”. [https://www.paulserban.eu/blog/post/architecting-the-ai-agent-control-plane-3-design-patterns-for-2026/](https://www.paulserban.eu/blog/post/architecting-the-ai-agent-control-plane-3-design-patterns-for-2026/)
8. Guild.ai, “Agent Control Plane”. [https://www.guild.ai/glossary/agent-control-plane](https://www.guild.ai/glossary/agent-control-plane)