上一篇讲的是工具调用循环。
当工具结果可以作为 observation 回到模型,Agent Core 才真正形成了“决策 -> 行动 -> 观察 -> 再决策”的闭环。到这一步,系统已经不只是能调用工具,而是能在一次任务里连续行动,直到给出最终回答。
但 Agent Core 跑通以后,很快会遇到另一个问题:用户从哪里进入这个系统?
最开始用 CLI 很自然。
CLI 简单、可控、容易调试。用户输入一段文本,runtime 调模型,模型可能调用工具,工具结果回到上下文,最后输出答案。所有东西都在一个终端里,边界清楚,失败也容易看见。
所以第一版从 CLI 开始不是退而求其次。
它是验证 Agent Core 最合适的入口。
但 CLI 不应该成为所有入口的模板。
当 Agent 要进入 Telegram、Slack、Web、移动端、桌面端、定时任务,甚至以后更多设备和渠道时,问题就不只是“怎么把用户输入交给 Agent Core”。入口层开始有自己的复杂度:消息格式、会话身份、授权、命令、重试、错误兜底、响应投递、长期运行。
这时就需要一个 Gateway。
这一篇要讲的是:为什么 Agent 走出 CLI 以后,Gateway 应该成为一层正式架构,而不是一堆入口 handler 里的临时代码。
CLI 是很好的第一入口
CLI 的价值在于它足够薄。
它不需要处理平台协议,也不需要管理外部连接。它只是接收用户输入,把请求交给 Agent Core,然后把结果打印出来。
这让早期开发非常清楚。
模型有没有被调用,一眼能看见。
工具有没有被注册,一条命令能列出来。
memory 有没有写入,remember 和 recall 就能验证。
session history 有没有生效,连续对话很容易复现。
工具 observation 有没有回到模型,也可以在一次 CLI 请求里看出断点。
所以 CLI 很适合验证 Agent 系统里最重要的几条主线:
用户输入
-> Agent Core
-> 模型调用
-> 工具调用
-> observation
-> 最终回答
在这个阶段,入口越简单越好。
如果一开始就接复杂消息平台,很多问题会混在一起。模型没有调通,到底是 provider 配置错了,还是平台事件没解析对?工具没有执行,到底是 Agent Loop 断了,还是消息入口没把文本传进去?会话没有保存,到底是 memory 层的问题,还是平台用户 id 没映射好?
CLI 把这些噪声都拿掉了。
它让 Agent Core 先站稳。
但 CLI 不是长期形态
一个真正有用的 Agent,通常不会只活在终端里。
用户可能在手机上想到一件事,想直接发给它。
一个任务可能需要长期运行,不应该依赖一个临时终端窗口。
不同入口可能对应不同使用场景:CLI 适合开发调试,聊天入口适合日常触达,Web 入口适合可视化操作,cron 入口适合定时任务。
这些入口都应该复用同一个 Agent Core。
但它们不应该共享同一套入口逻辑。
因为入口层的问题和 Agent Core 的问题不一样。
Agent Core 关心的是:怎么构造上下文,怎么调用模型,怎么处理 tool calls,怎么把 observation 放回模型,怎么写入 session history,什么时候返回最终答案。
入口层关心的是另一组事情:这条消息来自哪个平台?是谁发的?能不能访问这个 Agent?应该归到哪个 session?这是不是一个控制命令?回复要怎么投递?平台 API 失败了怎么办?
这些问题如果没有单独成层,入口一多,系统就会开始分叉。
最容易犯的错:每个入口复制一套 Loop
接新入口时,最容易写出这样的结构:
Telegram handler -> build prompt -> call model -> dispatch tools -> write memory
Web handler -> build prompt -> call model -> dispatch tools -> write memory
Cron job -> build prompt -> call model -> dispatch tools -> write memory
一开始这很诱人。
Telegram 来了一条消息,就在 Telegram handler 里拼上下文,调用模型,执行工具,再把答案发回去。
Web 收到一个请求,也写一套类似流程。
定时任务要跑,就再写一套轻量版。
短期看,这样最快。
但很快会出现行为漂移。
CLI 支持原生 tool calls,Telegram 入口可能还只支持 JSON fallback。
CLI 会把工具失败作为 observation 回到模型,Web 入口可能直接把异常返回给用户。
Telegram 入口写了自己的 memory 逻辑,cron 入口又绕过了 session history。
一个入口更新了 prompt 组装方式,另一个入口忘了同步。
最后系统表面上有很多入口,实际上有很多个不完全一样的 Agent。
这不是多入口。
这是多份核心逻辑。
Agent 系统最怕这种分叉。因为用户感受到的是同一个 Agent,但工程上它已经变成了几套行为不同的程序。
所以新入口的第一原则应该是:入口不能复制 Agent Loop。
入口应该把请求交给同一个 Agent Core。
Gateway 是入口控制层
Gateway 的位置正好在平台和 Agent Core 之间。
它不是 Agent Core。
它也不是某个平台的 SDK wrapper。
它是一层入口控制层。
可以把它理解成:
Platform Adapter
-> Gateway Event
-> Gateway Runner
-> Agent Core
-> Gateway Response
-> Platform Adapter
Platform Adapter 负责和外部平台打交道。
Gateway Runner 负责把平台无关的入口规则跑完。
Agent Core 负责真正的 Agent Loop。
这个分层很重要。
如果 Telegram 来了一条消息,TelegramAdapter 可以负责解析 update、处理 update id、发送 sendMessage、发送 typing 状态、拆分过长回复。
但 TelegramAdapter 不应该自己决定 prompt 怎么组装,不应该自己 dispatch 工具,也不应该自己实现 memory 逻辑。
它应该把消息归一化成一个 Gateway Event。
Gateway Runner 再决定:这个用户是否被授权?这是不是命令?应该使用哪个 session id?如果是普通文本,是否交给 Agent Core?如果出错,应该给用户一个什么样的入口层错误回复?
Agent Core 只收到已经整理好的任务。
这样入口变多以后,系统不会跟着分裂。
Adapter 和 Runner 不是同一种东西
Platform Adapter 和 Gateway Runner 很容易被写在一起。
因为刚接一个平台时,所有代码看起来都和这个平台有关。
但它们关心的问题并不一样。
以 Telegram 为例。
TelegramAdapter 应该知道 Telegram Bot API 的细节。
它知道怎么 long polling,怎么读取 update id,怎么判断一条消息是不是私聊文本,怎么发送 sendMessage,怎么发送 sendChatAction,怎么把超长文本拆成几段。
这些知识都很平台化。
换成 Slack、Web 或其他入口,这些细节都不一样。
GatewayRunner 关心的则是更稳定的入口语义。
它关心一条入口事件来自哪个 platform,chat id 是什么,user id 是什么,文本是什么,是否已经授权,是否是控制命令,应该映射到哪个 session。
这些问题和 Telegram API 没有强绑定。
换一个平台,GatewayRunner 的很多规则仍然成立。
所以 Adapter 的输出应该尽量变成平台无关的事件。
比如:
platform: telegram
chat_id: 123
user_id: 456
text: hello
is_text: true
这不是模型上下文。
也不是工具调用。
它只是入口层的标准事件。
一旦事件被标准化,后面就可以走同一套 Gateway Runner。
这也是 Gateway 的价值:平台差异停在 Adapter,入口规则集中在 Runner,Agent Loop 留在 Core。
Session identity 是 Gateway 的契约
多入口以后,session id 不能再随便处理。
CLI 里默认用一个 default session 还可以接受。因为用户通常知道自己就在当前终端里和 Agent 对话。
但聊天入口不一样。
不同用户不能共享同一段历史。
同一个用户在 Telegram 里的历史,也不应该意外混进 CLI 的默认会话。
所以 Gateway 必须给 Agent Core 一个稳定、明确的 session id。
例如:
telegram:dm:<user_id>
这个 session id 表达了三件事。
第一,它来自 Telegram。
第二,它是一个私聊入口。
第三,它属于某个用户。
Agent Core 不需要知道 Telegram 的 update 长什么样,也不需要知道平台 API 怎么返回用户资料。它只需要拿到一个稳定 session id,然后按原来的方式读写 session history。
这就是边界的好处。
入口层负责身份映射。
Agent Core 负责会话语义。
如果以后再接 Web 或 cron,也可以用类似规则生成各自的 session id,而不是让每个入口临时决定历史应该写到哪里。
授权应该靠近入口
当 Agent 只在本地 CLI 里运行时,授权问题容易被忽略。
因为默认使用者就是本机用户。
但一旦 Agent 进入聊天入口,它就有了一个外部入口。别人可能能给它发消息,平台也可能把不同用户的消息送到同一个 bot。
这时授权不能等到 Agent Core 里再处理。
因为授权不是模型语义问题。
它是入口问题。
这个用户能不能进入系统?
这个 chat id 是否允许?
陌生用户应该被拒绝,还是进入 pairing 流程?
这些判断应该发生在消息进入 Agent Core 之前。
allowlist 是最直接的方式。
如果 user id 在允许列表里,就放行。
如果不在,就不要把这条消息交给模型。
pairing 则是更灵活的本地授权方式。
陌生用户第一次发消息时,Gateway 可以生成一个短 code,让本机用户在本地执行 approve 命令。授权完成以后,这个 user id 才能进入 Agent Core。
这个流程的重点不是 code 本身。
重点是授权边界的位置。
授权属于 Gateway policy。
它不应该散落在 Telegram handler 的角落里,也不应该交给模型去“判断这个人能不能用我”。
模型可以解释结果。
但入口能不能进,应该由系统规则决定。
命令是入口层的控制能力
聊天入口通常还需要一些稳定命令。
比如:
/help
/tools
/remember
/recall
/reset
这些命令不一定需要模型参与。
用户输入 /tools,系统应该列出当前工具,而不是让模型猜用户是不是想了解工具。
用户输入 /reset,系统应该清掉当前入口 session 的历史,而不是让模型生成一段解释。
用户输入 /remember project TalyOS,这是一个明确的控制动作,可以直接写入 memory。
这些命令放在 Gateway 层很合适。
因为它们是入口 affordance。
它们让用户在聊天界面里有稳定、可预测的控制方式。
普通自然语言仍然交给 Agent Core。
命令则由 Gateway Runner 处理。
这个分界能减少很多不必要的不确定性。
不是所有输入都需要模型解释。
有些输入就是系统控制。
错误兜底也属于入口体验
Gateway 还要处理另一类问题:入口层失败。
平台消息可能不是文本。
平台 API 可能发送失败。
一次 update 处理可能抛出异常。
回复可能太长,需要拆分。
这些失败不应该污染 Agent Core。
Agent Core 不应该知道 Telegram 单条消息有长度限制,也不应该知道某个平台需要先发 typing 状态。
同样,一个平台 update 处理失败,也不应该让整个 polling 进程停下来,然后下一次启动又重复处理同一条坏消息。
入口层要有自己的错误兜底。
能给用户短回复,就给一个短回复。
能记录日志,就记录日志。
能继续处理下一条消息,就不要让整个入口停掉。
这不是 Agent 智能的一部分。
这是长期运行入口必须具备的工程韧性。
Gateway 把这些问题留在入口层,Agent Core 就能继续保持干净。
Gateway 让 Agent Core 更小
系统越往后走,越容易把东西塞进 Agent Core。
入口多了,想把平台判断塞进去。
命令多了,想让 Core 直接识别。
权限多了,想让模型帮忙判断。
这些做法短期都能跑,但长期会让 Core 变得越来越不像 Core。
Agent Core 最应该稳定负责的是:
上下文构造
模型调用
工具调用
observation 回灌
session history
最终回答
Gateway 应该负责:
入口事件归一化
授权和 pairing
入口命令
session id 映射
入口错误兜底
响应投递
Platform Adapter 则负责:
外部平台 API
消息格式转换
平台特定限制
这三层边界清楚以后,新入口就不需要复制 Agent Loop。
接 Telegram,是写一个 TelegramAdapter。
接 Web,是写一个 Web adapter。
接定时任务,是写一个 scheduler adapter。
它们都可以走同一个 Gateway Runner,再交给同一个 Agent Core。
这样系统增长的是入口,而不是核心逻辑的副本。
这一篇的结论
CLI 是 Agent Core 最好的起点。
它让最小闭环足够清楚,也让工具调用、记忆、session 和 observation 这些核心语义容易验证。
但 Agent 不能永远只待在 CLI 里。
当它进入聊天入口、Web 入口、移动端或定时任务时,入口层会自然变复杂。这个复杂度不应该进入 Agent Core,也不应该散落在每个平台 handler 里。
Gateway 的作用,就是让 Agent 走出 CLI,同时不让系统分裂成多套 Agent Loop。
它把平台消息变成统一事件,把入口规则集中处理,把普通任务交还给 Agent Core。
这样,入口可以越来越多,Agent Core 仍然保持稳定。
到这里,系统已经有了更真实的形状:
入口
-> Gateway
-> Agent Core
-> Model / Tools / Memory
但这也把下一层问题推到了面前。
当入口可以长期在线,工具也可以连续行动以后,系统就不能只靠“风险等级”这个粗粒度标记了。
哪些行动可以自动执行?
哪些行动必须经过确认?
哪些行动即使确认了,也应该被限制在受控环境里?
Discussion