编程 Agent-Native 架构:一个 action 定义,如何被 7 类入口消费

2026-09-29 00:03:20

Agent-Native 架构:一个 action 定义,如何被 7 类入口消费

项目信息

npx --yes @agent-native/core@latest create my-agent --standalone --template chat

什么算 Agent-Native

AI 集成和 Agent-Native 的差别不在模型,而在状态归属。AI 集成是把 AI 叠加到既有系统上,AI 像外挂模块:调一次大模型、拿到返回、流程结束。Agent-Native 以 Agent 驱动业务流程为核心,Agent 有状态、有记忆、可持久化上下文,而不是一次性的 API 调用。

Anthropic 与 Dan Shipper 给出的 5 条原则:

  1. 平权 Parity:用户通过 UI 能做的任何事,Agent 必须能通过工具完成。
  2. 颗粒度 Granularity:工具是原子级原语,功能特性由 Agent 在循环中组合工具达成。
  3. 可组合性 Composability:有了原子工具和平权,仅靠写新 prompt 就能造新功能。
  4. 涌现能力 Emergent Capability:Agent 能完成你从未显式设计过的任务。
  5. 随时间进化 Improvement over time:靠积累上下文与 prompt 优化变好,不需要重新发版。

两个反模式值得单独记下来:

  • 把 Agent 当 API:给一个输入吐一个输出,丢掉了 Master Loop(Agent 主循环)的价值。Agent 追求的是结果,会尝试、失败、分析错误、再尝试。正确姿势是给目标,让它在循环里跑。
  • Workflow-shaped tool:例如造一个 analyze_and_organize_files(),把「如何做」的判断捆进工具,改组织方式就得重写。正确做法是提供 read / analyze / move 这些原语,让 Agent 决定怎么组织。

控制权也随之移交:传统软件工程用代码穷尽逻辑分支追求确定性;Agent-Native 里代码从「指挥官」变成「军火库」,只提供原语,How 的判断力交给 Agent。

框架定位:一次定义,多入口消费

BuilderIO 的 agent-native 是一个开源 TypeScript 框架,用来构建把自主工作与专用 UI 配对的 Agent。每个能力只定义一次为 action:Agent 把它当工具用,UI 从代码里调它。

三条共享:

  • Shared actions:Agent 以工具调用每个能力,UI 从代码调用,两条路径共用同一套校验、权限与实现。
  • Shared data:Agent 做的工作出现在 UI 里,UI 做的工作 Agent 也能拿到。
  • Shared application state:Agent 收到相关 UI 状态,如当前页面、选中记录、活动视图。

关键点是 Agent 不通过点击 UI 来操作界面,它走的是和 UI 同一个 action 层。

defineAction

import { defineAction } from "@agent-native/core/action";
import { z } from "zod";

// One action powers every app surface: UI, agent, HTTP, MCP, A2A, and CLI.
export default defineAction({
  description: "Return a friendly greeting.",
  schema: z.object({
    name: z.string().default("world").describe("Name to greet"),
  }),
  http: { method: "GET" },
  run: async ({ name }) => {
    return { message: `Hello, ${name}!` };
  },
});

Agent 收到 hello 作为工具;React 侧用 useActionQuery("hello", { name: "Alex" }) 调的是同一个函数;框架还把它通过 HTTP、MCP、A2A、CLI 暴露出去。actions/ 是扁平目录,每个文件一次 defineAction 默认导出,启动时扫描挂载,没有注册步骤。UI 钩子底层走 /_agent-native/actions/ 路由。

actions/ 之外的代码不应直接 import 某个 action 的 run,而要通过入口调用——Agent 工具、UI 钩子、HTTP、MCP、A2A、CLI——每条路径都会跑相同的 schema 校验、访问检查与审计追踪。可以用 http: { method: "GET" } 或 readOnly: true 标只读;parallelSafe: true 标注可在同轮工具调用中并发;toolCallable: false 留给高爆炸半径、不应从沙盒工具里触达的 action。

对比一下传统 server action:它绑定单一调用方(通常是 UI)。defineAction 绑定的是契约本身,一个 action 可被七类入口消费——UI 点击、Agent 对话、HTTP API、MCP Server、A2A 调用、CLI 命令、Scheduled Jobs。

三种产品形态

同一套原语,自由决定包多少 UI:

  • Headless:把 Agent 当 API / MCP / A2A 服务对外提供,UI 是后加的。原语:defineAction、auth、skills、memory、jobs。
  • Rich chat:独立或嵌入的 chat 界面,带原生表格、图表、审批流。原语:共享 chat runtime + BYO runtime adapter + action 声明的原生 renderer。
  • Whole app:完整 SaaS,Agent 居中但能「挪到侧边栏」,与 App 状态实时同步。原语:SQL state、actions、context awareness、live sync。

升级路径的建议是先 Headless 验证业务流程,再升 Rich chat,最后才考虑 Whole app。

后端:PostgreSQL 与 PGlite

生产用 PostgreSQL,本地用 PGlite(浏览器内运行的 SQLite),两者共享同一套 Drizzle schema 与迁移。

schema 变更必须增量:用 drizzle-kit generate 生成可审查的 SQL,Node 部署调 runDrizzleMigrations()。Cloudflare Workers / Pages 这类 edge runtime 没有可部署的文件系统,需要把生成的 SQL 嵌进 worker bundle 再传给 runMigrations()。持久应用数据属于 PostgreSQL / PGlite,不要另外加 data/ 目录。

内置能力包括 Agent chat、认证与权限、Skills 与 Memory、Automations(定时/事件)、Agent teams(同工作区或跨连接 Agent 委派)、PostgreSQL 后端。示例应用有 Clips / Design / Slides / Analytics / Calendar / Mail / Assets / Content / Plans,见 agent-native.com/apps。

协作层:人和 Agent 同编一份文档

这部分是五层互锁的设计。

  1. Yjs Y.Doc(CRDT):协同文档是含共享类型的 Y.Doc,富文本用 Y.XmlFragment(TipTap 读 ProseMirror 节点树),结构化数据用 Y.Map / Y.Array。无中心协调器,任意两个客户端交换状态结果一致。
  2. SQL 作为规范内容:Yjs 状态以 Base64 二进制存 _collab_docs 表,框架管理、provider 无关(PGlite 与 Postgres 同 schema)。每行有乐观并发版本列,防并发写竞争。当存储 blob 超过新编码状态的 4 倍时,机会性运行墓碑压缩,不需要后台任务。
  3. updatedAt 门控对账:Agent 的 action 不推进程内的 Yjs,而是改规范 SQL 内容列并 bump updatedAt。变更同步系统检测到 bump,打开的编辑器重新拉取记录,主客户端通过 setContent 应用到共享 Y.Doc。门控确保只采纳真正更新的内容,滞后的轮询响应无法回滚编辑。
  4. 主客户端选举(去重):开多个标签页时,只有一个标签页把权威 SQL 快照应用到共享 Y.Doc。领先者是当前可见 peer 中 Yjs clientID 最低的那个;Agent 的 awareness 条目用 AGENT_CLIENT_ID(max int),所以它永远不可能成为领先者——客户端独立编辑时始终是领先者。选举是确定性的,没有协调往返(isReconcileLeadClient)。
  5. SSE 快速路径 + 轮询回退:客户端订阅 /_agent-native/poll-events(与 useDbSync 同一个 EventSource),协作更新事件以推送方式到达,通常数十毫秒;SSE 健康时轮询放松到约 12 秒。SSE 不可用时回退到 /_agent-native/poll?since=N,每 2 秒轮询一次,使协作能在任意部署目标上工作,包括持久连接不可用的 serverless。本地 Yjs 更新做去抖并用 Y.mergeUpdates 合并(约 80ms)再发服务器,减少击键级网络流量;批次在 visibilitychange 或 pagehide 时立即 flush;状态向量差异(GET /:docId/state?stateVector=…)只在重连、环形缓冲溢出或每 15 个轮询周期时做。

边界与取舍

把协议适配放进框架核心层,收益是:MCP 规范更新或 A2A 新增能力时,升级一个依赖即可让所有 action 同步拿到新协议版本,避免「先做 chat、再补 MCP、再补 A2A,每个新协议都改一遍核心层」的维护路径。代价是团队需要接受框架对协议实现深度的判断,无法自行替换底层 client。

另一个更常见的障碍不在模型侧:真正拖住 Agent 落地的,往往是 UI 与 Agent 的状态脱节。

推荐文章

程序员茄子在线接单