chatbot 的世界有了统一客户端——Open WebUI、LobeChat、LibreChat,随便哪一个,都能接入各大模型厂商,给你一个熟悉的对话框。

agent 的世界没有。

这个问题最近在社区里开始被讨论。最值得关注的尝试是 AG-UI(Agent-User Interaction Protocol),2025 年中由 CopilotKit 团队发布,已有 LangGraph、CrewAI、Mastra、Microsoft Agent Framework 的官方支持。它定义了约 16 种标准事件类型,用事件流连接 agent 后端和前端 UI,解决"把 agent 嵌入 Web 应用"的问题。

AG-UI 是一个好的开始,但它没有解决全部问题。

我在想,如果从头设计这个协议,应该怎么做。以下是我的思考。


首先要想清楚:agent 和 chatbot 的根本差异

chatbot 是被动响应的。用户说话,它回答,结束。这个模型很简单:request → response。

agent 是自主运行的。它有记忆、有目标、会主动行动、会跨渠道推送消息、会在执行到一半时暂停等待人的确认。它的生命周期不以一次对话为边界,它的交互模式不是一来一往,它不总是等你先开口。

所以给 agent 设计统一客户端,不能只是"更好的聊天框"。需要从头定义一套抽象。


核心抽象概念

Agent

有身份、有记忆、有技能集、有当前状态(idle / thinking / acting / waiting)。不只是一个模型实例,而是一个持续运行的实体。两次对话之间,它还在。

Channel(信道)

agent 与外部世界交互的通道。飞书、Telegram、Web UI、Email 都是 channel,每个 channel 有自己的渲染能力——纯文本、富文本、交互卡片,能力不同,渲染层需要降级适配。

Session(会话)

一次有边界的交互上下文。一个 agent 可以同时在多个 channel 维护多个 session,session 之间共享 memory 但隔离对话历史。

Session 有两种发起方式:用户发起(pull 模型)和 agent 发起(push 模型,比如 cron 触发的主动通知)。这两种看起来不同,本质上可以统一——agent 发起的 push,就是一个"由 agent 先说第一句话"的 session,用户可以选择接着回复,session 就延续了。不需要在协议层把它们割裂成两条路。

Event(事件)

信息流的基本单位,分两个方向:

下行(agent → 用户):TextChunk、ToolCall、ToolResult、Interrupt、StateUpdate、Artifact

上行(用户 → agent):UserMessage、Approval、Rejection、ContextUpdate

Message(消息)

Event 的子集,专指"有完整语义、面向人类可读"的那类。用户发的一句话是一条 Message,对应一个 UserMessage Event,天然原子。agent 流式输出的一段话也是一条 Message,但在底层是多个 TextChunk Event 聚合的结果。

Event 和 Message 的层次不同:底层传输用 Event(细粒度、实时),UI 渲染用 Message(聚合后展示给用户)。把两者混用,要么传输粒度太粗丢失实时性,要么 UI 直接渲染 Event 导致界面碎片化。

Memory(记忆)

分两层:Short-term(当前 session 上下文)和 Long-term(跨 session 持久化事实)。Memory 是 agent 的内部状态,client 只读展示,不直接编辑——用户通过对话来影响 memory,而不是直接操作它。

Skill(技能)Tool(工具)

这两个概念需要分开。Tool 是原子能力:调一个 API、执行一段代码、读一个文件。Skill 是 Tool 的组合加上使用策略,是更高层的能力封装。client 需要感知 ToolCall 的入参和结果(才能正确渲染执行过程),也需要知道当前激活了哪些 Skill(才能向用户展示 agent 的能力边界)。

Interrupt(中断)

agent 执行中途需要人工介入的时刻——确认、补充信息、拒绝操作。这是 agent 交互区别于 chatbot 最关键的机制,也是最容易被设计忽视的地方。Interrupt 不是一个异常,是一个一等公民。

Identity / Auth(身份与授权)

谁在和 agent 说话?同一个人的飞书账号和 Telegram 账号,agent 应该认出是同一个人,memory 才能跨 channel 共享。这是跨渠道 agent 的核心价值所在。另外 agent 执行某些操作时(发邮件、调外部 API),用的是谁的凭证?Identity 和 Credential 需要一起设计。

Context(上下文注入)

用户和 agent 交互时,往往有隐式的环境信息需要传递:当前时间、当前位置、粘贴的截图、打开的文档。这些不是 Message,也不是 Memory,是每次交互时动态注入的运行时上下文。需要作为独立概念设计,否则会被随意塞进 Message 里,破坏协议边界。

Artifact(产出物)

agent 经常产出有结构的东西——生成的代码、写好的文档、分析报告。这些不是聊天消息,是可以被引用、存储、版本管理的持久化对象。没有 Artifact 概念,这些东西只能作为文本 dump 在对话里,失去了结构性。

Trace(执行轨迹)

区别于 Event stream。Trace 是面向调试和可观测性的——agent 为什么做了这个决策,调用链是什么,哪一步耗时最长,哪里出错了。Event 是给用户看的,Trace 是给开发者看的。两者数据来源重叠,但目的不同,需要独立建模。

Policy(行为策略)

哪些操作需要人工审批,哪些可以自动执行,哪些被禁止。Interrupt 是一个事件,但触发 Interrupt 的规则需要有地方来定义,就是 Policy。没有 Policy,Interrupt 就是一个没有规则的随机打断。


协议分层

┌─────────────────────────────────────────────┐
│              Rendering Layer                │
│   Web UI / Telegram / 飞书 / Email / ...    │
│   (各 channel 自己渲染,按能力降级)         │
├─────────────────────────────────────────────┤
│            Agent Client Protocol            │
│   连接任意前端 ↔ 任意 agent 后端            │
│   统一事件流 + 状态同步                      │
├─────────────────────────────────────────────┤
│              Agent Runtime                  │
│   memory · skills · tools · scheduler      │
│   identity · policy · session              │
└─────────────────────────────────────────────┘

事件流

一次典型的用户发起 session:

用户输入 [UserMessage]
    │
    ▼
Agent Runtime
    │
    ├──► [TextChunk × N]          文本流式输出
    │
    ├──► [ToolCall]               工具调用开始
    │         │
    │    [ToolResult]             工具返回结果
    │         │
    │    [Artifact]               如果产出了文件/报告
    │
    ├──► 需要人工介入?
    │         ├── 是 → [Interrupt] → 用户 Approve/Reject → 继续/终止
    │         └── 否 → [RunEnd]
    │
    └──► [StateUpdate]            memory 或状态变更通知

agent 发起的 session(push 模型):

Scheduler / Trigger
    │
    ▼
[SessionStart](agent 发起)
    │
    ▼
[TextChunk × N] 或 [Artifact]
    │
    ▼
[RunEnd]
    │
    ▼
用户可选择回复 → session 延续

完整关系图

                    ┌──────────────────────────────────────┐
                    │            Agent Runtime             │
                    │                                      │
                    │  Identity ──► Memory                 │
                    │      │        (short / long)         │
                    │      │                               │
                    │      │        Skills ──► Tools       │
                    │      │                               │
                    │      └──► Policy                     │
                    │            │                         │
                    │            └──► Interrupt 触发规则   │
                    │                                      │
                    │  Session ──► Event Stream            │
                    │               ├── Message            │
                    │               ├── ToolCall/Result    │
                    │               ├── Artifact           │
                    │               ├── Context            │
                    │               └── Interrupt          │
                    │                                      │
                    │  Trace(可观测,独立于 Event Stream)  │
                    └──────────────────┬───────────────────┘
                                       │
                         Agent Client Protocol
                         (统一事件流 + 状态同步)
                                       │
                         Channel Router
                         (身份映射 + 渲染降级)
                                       │
              ┌────────────────────────┼────────────────────────┐
              ▼                        ▼                         ▼
           Web UI                    飞书                    Telegram
         ┌─────────┐             (卡片降级)               (文本降级)
         │Timeline │  ← 对话流(主视图)
         │ Status  │  ← agent 当前状态(idle/thinking/acting)
         │  Tools  │  ← 工具调用可折叠展示
         │Interrupt│  ← 待审批的中断队列
         │ Memory  │  ← memory 快照只读面板
         │ Skills  │  ← 当前激活 skill 列表
         │Artifact │  ← 产出物管理
         │  Trace  │  ← 执行轨迹(开发者模式)
         └─────────┘

和 AG-UI 的差异

AG-UI 解决的是"把 agent 嵌入 Web 应用"的问题:用户打开一个网页,网页里有 agent 的对话区域。它的设计前提是 pull 模型,单一 Web UI。

我这里想解决的是"agent 作为中心,各种界面来接入它"的问题:agent 持续运行,有自己的身份和记忆,通过不同 channel 和用户交互,有时主动找你说话,有时等你发起。主客关系反过来了。

维度 AG-UI 这里的设计
交互模型 纯 pull(用户发起) pull + push 统一为 Session
Channel 单一 Web UI 多 channel 路由 + 渲染降级
Identity 不涉及 跨 channel 身份映射
Memory 不涉及 一等公民,前端只读可见
Skills/Tools 不涉及 动态感知,区分 Skill 和 Tool
Interrupt 支持 支持,由 Policy 驱动
Artifact 不涉及 独立产出物模型
Trace 不涉及 独立可观测层
Policy 不涉及 行为策略,Interrupt 的规则来源

两者并不是竞争关系——AG-UI 专注于 Web 嵌入场景,我这里的设计更接近一个 personal agent 的完整客户端协议。如果 AG-UI 继续演进,覆盖多 channel 和 push 模型,两者会逐渐收敛。


最后

这个设计里,Identity 和 Policy 是最值得重视的两个遗漏概念

Identity 决定了 memory 能不能跨 channel 共享。如果你在飞书和 Telegram 用的是同一个 agent,但 agent 认不出你是同一个人,那"个人 agent"的核心价值——了解你这个人——就打了折扣。

Policy 决定了 agent 的可信边界。没有 Policy,Interrupt 就是一个没有规则的随机打断,人机协作就变成了随机审批。只有当 agent 清楚地知道哪些事情自己可以自主决定、哪些需要你确认,它才真正成为一个可靠的合作者。

nanobot 和 openclaw 目前对这两个问题的处理都还很粗糙。这可能是这类 personal agent 框架下一步值得认真解决的问题。