---
title: "AI 工具链的转向：访问问题解决了，架构才是硬骨头"
description: "AI 工具的访问已经不是问题了。真正的难点在于：怎么编排三个智能体、三种记忆系统，以及信任边界。下面是我自己用下来的一套可扩展的架构。"
pubDate: 2026-07-21
tags: ["ai-agents", "memory-granularity", "toolchain-design", "governance"]
locale: zh-cn
canonical: https://mikeng.io/zh-cn/writing/inversion-of-ai-tooling-from-access-to-architecture/
---

六个月前，我写过一篇[香港的 AI 配置方案](https://mikeng.io/writing/hk-ai-tools-setup-2026/)，讲的是手机号、DNS 路由、支付绕路这些事。当时的瓶颈是地理限制——你甚至能不能**接触到**这些工具？

现在这个问题已经解决了。一台 5 美元的 VPS 加上 Shadowsocks，访问成本就降下来了。工具到处都是。但解决了访问问题，事情并没有变简单，反而暴露了下一个问题。当你能访问所有模型、运行所有智能体时，真正难的问题就不再是“能不能连上”，而是“什么该连什么，谁来定？”一个瓶颈消失了，取而代之的是十几个协调问题。

这就是转向。失败模式从外部环境转移到了内部。访问问题解决了，编排和信任才是真正的难点。就像我在[《智能体需要架构，而不是提示词》](https://mikeng.io/writing/agents-need-architecture-not-prompts/)里说的，真正的失败不是来自你写了什么提示词，而是来自你的架构允许了什么。下面要讲的这套架构，就是在我认真对待这个观点、并把它应用到自己的环境之后，慢慢长出来的。

这个转向是渐进的。每获得一个新能力，就移除一个外部约束，同时暴露一个内部约束。支付绕路解锁了模型访问，模型访问又暴露了一个智能体无法同时处理多个上下文的问题。拆分智能体又暴露了一个记忆系统无法满足三种不同回忆需求的问题。拆分记忆又暴露了工具链本身需要分类的问题。每个解决方案都带来下一个问题，架构就这样一步步长起来了。

驱动这个增长的有四个瓶颈，按顺序来：**上下文冲突**——一个系统提示词试图装下所有东西，不同领域互相渗透。**记忆碎片化**——一个嵌入存储无法回答三种不同类型的问题。**工具泛滥**——运行环境、模型、订阅越来越多，迟早要出分类错误。**信任模糊**——本地组件和托管组件混在一起，没有明确的边界。

下面就是最终长出来的架构。纸上看起来挺复杂：三个智能体角色、三个记忆系统、一个模型路由器、一个编排器、一个 ADE，还有跨运行环境、模型和订阅的工具链。没有哪一层是出于“收集癖”加的。每一层都存在，是因为更简单的方案先试过了，然后崩了。

## 一个智能体，三个角色

转折点出现在一个系统提示词试图装下所有东西的时候——运维指令、写作风格指南、社交媒体人设、营养追踪逻辑，全都在互相拉扯。

两种污染模式让单智能体方案变得不可行。第一，社交上下文泄漏到了私有运维决策里。前一次对话的闲聊语气会渗透到基础设施命令中。智能体并没有崩溃，它产出的结果看起来是对的，但实际上是错的——这是最难捕捉的失败，因为没有任何告警。你只能事后发现，当一个决策被错误的上下文“软化”了，你才意识到出了问题。

第二，营养追踪的专用逻辑和治理规则冲突了。卡路里追踪和部署策略放在同一个提示词里，两个领域都得不到充分关注。系统提示词变成了一个竞争性谈判，智能体在这场谈判中不断吃亏，而且只有事后才暴露出来。

```mermaid
flowchart TD
    S["单个系统提示词\n运维 + 写作 + 社交 + 营养"]
    S -->|"上下文冲突"| BREAK["污染：\n社交泄漏到运维\n营养与治理冲突"]
    BREAK --> SPLIT["拆分为三个角色"]
    SPLIT --> NORTY["Norty\n私有，完全访问\n治理 / 编码 / 运维 / 基础设施\nOmniRoute → GLM 5.2"]
    SPLIT --> NORA["Nora\n面向公众\n社区、朋友圈\nSearXNG + Firecrawl"]
    SPLIT --> BENTO["Bento\n单一用途\n营养追踪\nTelegram + SQLite"]
```

解决方案是结构性的：为不同的关注点设置独立的智能体。一个系统提示词不足以作为边界。跨领域共享一个提示词会让智能体的上下文变得多孔——无论你写得多仔细，领域之间还是会互相渗透。提示词可以说“不要混合这些上下文”，但结构性的拆分让混合变得不可能。

所以我把一个智能体拆成了三个角色，每个角色都是 **Hermes** 的一个配置——Hermes 是 Nous Research 开发的开源智能体框架，也是这套架构的编排器。不是三个独立的程序，而是三个不同的人格，拥有不同的工具访问权限，运行在同一个运行时上。

**Norty** 是私有的、完全访问的角色。治理、编码、运维、基础设施。默认模型是 GLM 5.2，通过 OmniRoute 路由，由 Ollama Cloud Pro 提供服务。Norty 拥有所有钥匙，因为 Norty 的职责范围是我构建和运营的一切。没有社交上下文，没有营养逻辑，只有工作。

**Nora** 是面向公众的角色：社区、朋友圈、有限的工具。SearXNG 用于搜索，Firecrawl 用于网页抓取。Nora 不是 Norty 的降权版——她是一个独立的人格，有明确的角色。工具限制就是全部意义。Nora 不需要基础设施访问权限，给了她反而会埋下范围泄漏的隐患。

**Bento** 是单一用途的：营养追踪。Telegram 界面，SQLite 数据库。没有模型路由的复杂性，没有治理规则，没有社交媒体。Bento 只知道卡路里和宏量营养素，其他一概不知。当 Bento 做决策时，绝不可能受到运维上下文或写作风格指南的影响——那些上下文在 Bento 的世界里根本不存在。Bento 不会泄漏它不包含的东西。

每个角色都有不同的工具集。一个角色能访问哪些工具，就定义了它能做什么，上面没有叠加另一层权限系统。工具列表**就是**边界。这是这套架构第一次撞上两个结构性的墙：**权限**——谁能做什么——和**范围**——防止上下文互相污染。拆分角色之所以存在，是因为一个试图无处不在的智能体，最终在哪里都不可靠。

## 三种粒度的记忆

拆分角色解决了上下文污染，但暴露了下一个瓶颈：记忆。

一个记忆系统无法满足三种不同的回忆需求。一个为对话相似度构建的平面嵌入存储，当你问“三个月前那个架构决策是为什么做的？”时，它会返回语义上最相似的对话——可能是一个完全不同的项目，只是恰好用了相似的词。代码库的结构和决策的时间线对它来说是不可见的。失败不在于它返回了空，而在于它返回了看似合理但错误的东西。

智能体需要三种粒度的回忆：跨会话的对话连续性、对代码库的结构性理解、以及对过去决策的时间推理。这是三个不同的问题。一个记忆系统无法覆盖全部。所以这套架构有三个记忆系统，每个针对不同的回忆需求。

**Honcho** 处理跨会话的对话记忆。通过 Docker 自托管，基于对等架构，带有辩证推理能力，它能回答“我们上周聊了什么？”它直接集成到 Hermes 中，让每个角色在会话之间保持连续性，同时不会在角色之间泄漏上下文。对话记忆无法告诉你某个函数在哪个模块里，因为它本来就不是干这个的。

**Graphify** 处理代码结构索引。它构建基于图的代码库知识表示，显示哪些函数调用了哪些，哪些模块依赖哪些，以及边界在哪里。它取代了现在已经废弃的 Codebase-Memory MCP。当编码智能体需要在修改之前理解架构时，Graphify 提供那张地图。但代码索引无法重建上周的推理过程；它知道结构，不知道历史。

**Graphiti** 处理时间知识。由 Zep 构建，是一个双时态知识图谱，后端用 Neo4j，同时追踪有效时间（某件事在什么时候是真的）和系统时间（我们什么时候知道的）。它解决了其他两个系统无法触及的问题：某个时间点的知识状态，包括当时已知的上下文下为什么做了那个决策。但时间知识图谱无法保存当前会话的上下文。它专为跨时间推理而建，而不是会话内的连续性。

这三个不是同一个问题的三个竞争方案。它们是三种不同回忆粒度的三个系统，类似于一个大脑中的短期、长期和程序性记忆。每个都存在，是因为一个记忆系统被要求覆盖所有三种时，返回的答案看似合理但错误。

## 工具链是拓扑，不是列表

角色拆分和记忆专业化之后，下一个瓶颈出现了：工具泛滥。组件列表越来越长，包括运行环境、模型、订阅、路由器、编排器、ADE，如果不理清它们之间的关系，就会产生分类混乱。模型不是运行环境，订阅不是路由器，编排器也不是其中任何一个。问题不再是“这套架构里有哪些工具？”，而是“什么连什么，什么**不连**？”

下面是所有智能体层的组件，按类别和职责分组。

| 组件 | 类别 | 职责 |
|-----------|----------|-----|
| Claude Code CLI | 运行环境/CLI | 主要开发，深度推理，多文件修改 |
| Kimi Code CLI | 运行环境/CLI | 次要，快速补丁，外部审查 |
| OpenCode CLI | 运行环境/CLI | 与提供商无关的开源编码智能体（MIT 协议，GitHub 约 16.5 万星） |
| Codex CLI | 运行环境/CLI | 仅用于验证和代码审查，审计路径 |
| GLM 5.2 | 模型 | Norty 的默认模型，通过 Ollama Cloud Pro 提供服务 |
| Kimi K3 | 模型 | 零样本编码模型，通过 Kimi Code CLI 在 Allegro 计划下访问 |
| MiniMax M3 | 模型 | 次要辅助，100 万 token 上下文，通过直接 API 和 OpenCode Go 使用 |
| OpenCode Go | 订阅 | 10 美元/月，14 个模型，与所有提供商签署了零数据保留协议 |
| Ollama Cloud Pro | 订阅 | 托管 Ollama 层，提供 GLM 5.2 和开放权重模型 |
| Claude Max 20x | 订阅 | 200 美元/月，解锁 Claude Code |
| Kimi Allegro | 订阅 | 99 美元/月，解锁 Kimi Code |
| OmniRoute | 模型路由器 | 自托管在 localhost:20128，按任务和成本路由 |
| Hermes | 编排器 | 管理角色、记忆、定时任务、多平台网关 |
| Orca | ADE | 在隔离的 git 工作树中并行运行智能体，自带智能体 CLI |
| Cortex | 编辑管线 | 第二个独立的 OmniRoute 消费者，与 Hermes 分开 |

这些分类防止了分类错误。OpenCode CLI 是一个免费、MIT 协议的运行环境。OpenCode Go 是一个付费订阅，同一个品牌，不同类别。OmniRoute 是路由器，不是运行环境。Orca 是 ADE，不是 IDE。模型不是运行环境，订阅不是模型。把它们扁平化成一个列表，就丢失了让拓扑可读的信息。

但分类之所以有趣，是因为它们之间的边。

```mermaid
flowchart TD
    NORTY["Norty"] --> HERMES
    NORA["Nora"] --> HERMES
    BENTO["Bento"] --> HERMES

    HERMES["Hermes"] -->|"通过"| OMNI["OmniRoute"]
    CORTEX["Cortex"] -->|"通过"| OMNI

    OMNI -->|"到"| MODELS["GLM 5.2, DeepSeek V4, Kimi K3, ..."]

    ORCA["Orca ADE"] -->|"启动"| CC["Claude Code\n主要"]
    ORCA -->|"启动"| KC["Kimi Code\n次要"]
    ORCA -->|"启动"| OC["OpenCode CLI"]

    CC -->|"直接计费"| ANTHROPIC["Anthropic"]
    KC -->|"直接计费"| KIMI["Kimi Allegro"]
    OC -->|"直接计费"| PROVIDERS["各提供商"]

    CODEX["Codex\n仅审查"] -.->|"MCP 内部"| CC
    CODEX -.->|"独立 CLI"| STANDALONE["直接审查"]
    CODEX -.->|"GitHub 应用"| GH["PR 触发"]
```

三个角色各自作为 Hermes 的配置运行。当某个角色需要模型时，Hermes 调用 OmniRoute，由 OmniRoute 根据任务和成本选择提供商和模型。Cortex 是驱动本站内容管线的编辑和雷达管线，是第二个独立的 OmniRoute 消费者。它不经过 Hermes。Hermes 和 Cortex 是兄弟关系，不是链条。

Orca 在隔离的 git 工作树中并行启动编码 CLI。Claude Code 是主要的：深度工作、多文件项目。Kimi Code 是次要的：快速补丁。OpenCode CLI 是与提供商无关的备用方案。每个编码 CLI 直接调用自己的提供商：Claude Code → Anthropic（Claude Max 20x），Kimi Code → Kimi Allegro。这些不经过 OmniRoute。它们是各自订阅下的厂商产品。

Codex 仅用于审查，有三个表面：Claude Code 内部的 Codex MCP 用于会话内审查，独立 CLI 用于直接审查，以及由 PR 事件触发的 GitHub 应用。第三个表面值得注意——它是由仓库事件触发的，而不是由架构中的任何组件调用的。一个没有任何组件调用的审查器，连接到仓库事件而不是调用图。

编码层级：Claude（主要）> Kimi（次要）> Codex（次要，仅审查）> MiniMax M3（次要，大上下文辅助，不是日常主力）。

然后是非连接边，它们和连接边一样重要，因为它们是拓扑设计要防止的错误。

**OmniRoute 不路由编码 CLI。** 它们是各自订阅下的厂商产品。OmniRoute 服务于我构建的东西——通过 Hermes 的角色和 Cortex——而不是我买的东西。

**没有任何东西调用 Orca。** 它是一个手动操作的环境，不是自动化链条中的服务。Orca 向外调用，启动 CLI；没有东西向内调用。

**不存在 OmniRoute → Orca → Codex 管线。** 这三个处于不同的层面：一个路由器、一个手动操作的环境、一个审查器。它们不构成链条。

这个拓扑不是因为我收集组件才存在的。而是因为工具泛滥迫使我要有一个结构性的答案。当运行环境、模型、订阅、路由器、编排器和 ADE 都在发挥作用时，唯一能理清它们的方法就是知道每个是什么、连什么、不连什么。**IDE 是给你用的。ADE 是给你和你的智能体用的。** 这就是 Orca 的区别。IDE 托管你的会话。ADE 并行托管多个智能体会话，每个在独立的隔离工作树中，每个有自己的运行环境和模型分配。

## 信任是边界，不是功能

第四个瓶颈是信任模糊。当本地组件和托管组件混在一起，没有明确边界时，默认假设要么是“全部私有”，要么是“全部暴露”，而两者都是错的。

这套架构横跨两个世界。有些组件运行在本地的一台 GMKtec NucBox M7 Ultra 上——一台 AMD Ryzen 7 PRO 6850U 迷你 PC，64GB DDR5 内存。其他的是托管服务，有自己的策略。边界不是一个可以开关的功能。它是每个组件运行在哪里、能接触到什么的结构性属性。

| 组件 | 运行位置 | 信任模型 |
|-----------|---------------|-------------|
| Ollama BGE-M3 | 本地 | 完全私有，仅嵌入，无互联网路由 |
| OmniRoute | 本地（路由器） | 桥接两个世界，根据任务路由到本地或托管 |
| OpenCode Go | 托管（第三方） | 取决于服务，与所有提供商签署了零数据保留协议 |
| Claude Code | 托管（Anthropic） | 取决于服务，受 Anthropic 数据政策约束 |
| Kimi Code | 托管（Moonshot） | 取决于服务，受 Kimi 数据政策约束 |

Ollama BGE-M3 真正在本地运行，处理语义搜索和代码索引的嵌入，没有互联网路由。那**就是**私有。OpenCode Go 是托管服务。模型推理通过他们的 API 进行，虽然他们与所有提供商签署了零数据保留协议，但信任模型与本地进程不同。OmniRoute 桥接两个世界：它可以路由到本地的 Ollama 进行嵌入，也可以路由到托管后端进行推理，取决于任务。整个架构没有统一的“零数据保留”声明。每个服务有自己的策略。重要的是知道哪个是哪个。

信任边界也在角色层面强制执行。每个角色的工具集定义了它能接触到什么，这个工具集**就是**它的信任边界。**Norty** 拥有完全访问权限，钥匙到整个王国，与其范围匹配。**Nora** 只有 SearXNG 和 Firecrawl，面向公众，没有基础设施访问权限。**Bento** 只有 Telegram 和 SQLite，最小的信任边界，无法接触到模型或基础设施。

对外访问控制层是一个集成的单点：Cloudflare Tunnel 加上 Google SSO，以 Google Workspace 作为身份提供商进行门控。`cloudflared` 建立一个出站连接到 Cloudflare 的边缘，没有入站端口，没有公共接口在监听。Cloudflare Access 挡在前面，只有通过 Google Workspace 认证的身份才能通过。免费层，最多 50 个用户。

信任不是一个一次配置好的设置。它是每一层都画好的边界：组件运行在哪里、角色能访问什么、网络允许什么进来。这套架构不做笼统的隐私声明。它做的是明确的边界，每个边界都有理由。

## 塑造它的三堵墙

这套架构看起来很复杂。三个角色、三个记忆系统、运行环境、路由器、订阅的拓扑，以及多层信任边界。自然会问：为什么需要这么复杂？

答案不是个人偏好。而是每个多智能体设置最终都会撞上的三堵结构性墙。

**权限**是第一堵墙。谁能做什么？当一个智能体拥有所有访问权限时，问题很简单。答案是“什么都能做”。但一旦你拆分成角色，权限就成了设计问题。Norty 可以部署基础设施。Nora 不行。Bento 甚至无法接触到模型。工具列表就是权限边界，而且必须通过结构来强制执行——不是通过提示词说“不要用这些工具”，而是通过配置让使用它们变得不可能。

**范围**是第二堵墙。每个智能体持有多少上下文？单提示词方案失败是因为范围是多孔的。社交上下文泄漏到运维，营养与治理冲突。拆分成角色在智能体层面解决了范围问题，但在记忆层面暴露了范围问题：一个记忆系统无法满足三种粒度的回忆。范围不仅仅是让智能体彼此隔离。它是让每个组件只持有它需要的上下文，不多不少。

**验证**是第三堵墙。你怎么知道输出是正确的？当一个智能体产生所有东西时，验证是临时的。你阅读输出，然后判断它看起来对不对。当多个智能体产生输出并互相输入时，验证需要结构。Codex 作为仅审查的路径存在，与主要的编码 CLI 分开。拓扑中的非连接边——OmniRoute 不路由编码 CLI、没有任何东西调用 Orca、不存在 OmniRoute → Orca → Codex 管线——就是验证边界。它们确保写代码的组件不是审查代码的组件。

这三堵墙直接对应四个瓶颈。上下文冲突是范围问题——领域互相渗透，因为边界是提示词而不是结构。记忆碎片化是不同粒度的范围问题——一个存储被要求服务三种回忆。工具泛滥是权限问题——当组件没有被分类、边不明确时，你不知道谁能接触到什么。信任模糊是权限和验证问题——没有本地和托管之间的明确边界，没有角色范围的工具访问，你只能猜测什么是私有的。

墙不是 bug。它们是无论你是否承认都存在的约束。这套架构就是当你不再绕开它们、而是开始与它们一起构建时，会发生的事情。

这篇文章描述了这套架构。下一篇会讲当你正面撞上每堵墙时会发生什么：失败模式、设计决策，以及当权限、范围和验证成为一等关注点而不是事后想法时，会出现的模式。
