ARTICLE · 1058583
按钮和 AI 调的是同一个函数:agent-native 把应用能力定义一次
共享 Action · 多端投影 · 智能体 UI 对等 · 可视化应用状态 —— BuilderIO/agent-native 是一个用 TypeScript 构建智能体应用的框架,它把「应用能做什么」定义成一次实现,让按钮和 AI 走同一条路径。
它不是在现有产品上挂一个聊天侧栏,也不是把按钮里的逻辑再抄一份给模型当工具。 框架的核心约定是:每个能力用一次 defineAction() 写好,之后同时成为智能体工具、 React 数据 Hook、HTTP 接口、MCP 工具、A2A 工具与命令行命令。
仓库 BuilderIO/agent-native 现有 5837 stars,主语言 TypeScript,采用 MIT 许可, 核心包 @agent-native/core 当前版本 0.183.0,仓库内自带 16 套可直接改造的应用模板。
两份实现,总会走散
按钮和 AI 要做的常常是同一件事:写一条记录、发一封信、改一个状态。传统分工里, 这件事被拆成两份实现——一份是前端调用的 API 层,只有浏览器能到达; 另一份是给模型准备的工具集成,参数、校验、权限各写一遍。
两份代码只要开始演化,就会漂移。权限校验加在界面上,模型那条路悄悄绕过; 参数约束松了一边,另一边开始拒绝合法输入。行为对不上时,你很难判断是模型理解错了, 还是两条实现本来就不一致。这也是不少「给产品加 AI」的项目在第二个月变得难维护的原因。
另一侧的痛点在交互层。编码智能体好用,不只是因为它会写代码,而是它的环境能承载工作: 工具、文件、测试、预览都在同一处可见。知识工作缺的正是这件外衣——纯聊天窗口里, 用户看不见 agent 能做什么,也没法检查、修改、审批、分享它的产出。
框架把这类需求归纳成三个共享:共享能力,同一份 action 既给界面也给 agent; 共享数据,agent 写进数据库的东西马上出现在界面;共享应用状态,agent 知道你正看着哪条记录。 缺少任何一项,协作就会退化成「agent 做完再向人汇报」。
目标用户是用 TypeScript 做产品的开发者与产品团队,他们需要智能体落进真实业务流程, 而不是停在演示视频里。他们的抱怨通常很具体:agent 在明明打开着一条记录时还问 「你要操作哪一条」;agent 改完数据,页面不刷新就看不到;权限规则只在界面上生效; 想让 Claude 或 Cursor 用上产品能力,又得从头写一遍工具定义。
上手:定义一次,到处可用
最短路径是官方脚手架。用下面的命令,从 chat 模板起一个可运行的应用,再连接模型, Builder.io 额度、Anthropic 或 OpenAI 的 key、本地 Ollama 都可以:
npx --yes @agent-native/core@latest create my-app --standalone --template chat cd my-app corepack enable pnpm install pnpm dev 在 actions/ 目录里放一个文件,能力就同时出现在所有界面上:
import { defineAction } from "@agent-native/core/action"; import { z } from "zod"; export default defineAction({ description: "Reply to an email thread in the user's voice.", schema: z.object({ emailId: z.string().describe("The id of the email to reply to."), body: z.string().describe("The reply body, in markdown."), }), run: async ({ emailId, body }) => { await db.insert(schema.replies).values({ emailId, body }); return { ok: true, emailId }; }, }); description 是模型判断何时调用的依据,schema 里每个字段的 describe 会流进 JSON Schema, run 是唯一的实现体。这段代码不用注册、不用写路由,框架启动时自动发现 actions/。
三个贴近真实需求的用例:
• 客服工单回复。输入是一条工单 id 和一段回复正文;在聊天里说「回复这个工单」, 或者在界面上点同一个按钮,两者最终执行同一个函数、写入同一张表。 结果一致,而这次调用来自 agent 还是来自点击,在调用上下文里是分开的。
• 把产品接进外部智能体。每个应用启动时自带一个 MCP 端点, Claude、Cursor、ChatGPT 这类宿主可以直接发现并调用同一批 action。 同一批能力也能从命令行跑,用于脚本与定时任务,例如按 JSON 参数执行一次回复动作。
• 人机接力的编辑。agent 通过 action 写入 SQL,界面通过 useActionQuery 读到同一份数据; 用户在界面上改完,agent 下一轮就能看到,不需要刷新,也不需要额外的同步操作。
常见的坑有两个。一个是只做界面不做 action,功能对人类可见、对 agent 不可见; 另一个反过来,给 agent 单独砌一套接口,于是又回到两份实现。 框架建议的顺序正好相反:先定义 action,再按需要补界面、技能文档与应用状态。
原理:一次定义的投影
defineAction 接收一个配置对象:description、schema、run 是必填,http、readOnly 等可选。 启动时框架扫描 actions/ 目录并挂载,HTTP 侧默认暴露在 /_agent-native/actions/
真正值得看的是调用来源的处理。框架把「谁在调」做成显式标签: tool、http、frontend、cli、mcp、webmcp、a2a、automation。 run 的第二个参数能直接读到 ctx.caller、userEmail、orgId、appId、appRoles、 appPermissions 以及本轮附件。这些信任信息由框架侧写入,不能用 action 输入伪造: 自动化的触发器血缘由触发器调度器注入,跨应用委派带着深度与已访问应用列表以阻断递归, 附件只在内置 agent 循环里存在。
数据层是 SQL,生产用 PostgreSQL,本地开发用 PGlite,部署面覆盖 Vercel、Netlify、 Cloudflare、Deno Deploy、AWS、Azure、Docker 等 Nitro 兼容主机。 写操作会让相关查询自动失效,同进程的变更通过 /_agent-native/events 实时推到界面, serverless 与跨进程写入用轻量轮询收敛。界面这侧把导航、URL、选中项写入应用状态表, 一个 view-screen 动作把它们水合成「用户眼前是什么」的快照;agent 也能反向下发导航指令。
创新性集中在这几处。第一,把 agent 写进应用契约而不是叠一层助手: 能力只有一份实现,MCP、A2A、命令行、浏览器内桥接都是同一注册表的投影, 接入外部生态不需要为每个宿主重写插件。第二,人机协作的状态是持久的: 主 agent 作为编排者把任务派给各自持有线程与工具集的子 agent, 子任务状态存在 SQL 的应用状态表里,中断沿 SQL 传播,serverless 冷启动不丢任务。 第三,长线程的成本控制做成了后台压缩,把较早的历史折叠成分层的观察记录与反思, 只保留最近若干轮原文,且在线程未越过阈值前完全不介入,避免破坏 prompt 缓存。 第四,连文档正确性都被纳入回归测试:仓库里有一个护栏测试扫描全部文档, 一旦被修正过的错误表述重新出现,测试就失败。
代价同样清楚。选择它意味着接受这套 action 与状态模型、pnpm 工作区与 SQL 后端; 把已上线的产品搬进来,需要按它的「界面 / action / 技能 / 应用状态」四区约定重构, 而不是加个依赖就能生效。能力全量暴露给 MCP 与 A2A 之后,权限边界必须靠角色与授权配置说清楚, 否则外部宿主能做的事,就等同于模型能做的事。
落地:把 AI 放进流程
第一个可立即落地的场景是内部运营台。用日历、邮件、CRM 中的一套模板起步, 把「查记录、改状态、发通知」定义成 action,再用自动化接上事件触发: 例如有预约创建时,先让一个轻量模型判断条件(发件域名是否属于本公司),满足才让 agent 执行正文。 agent 处理完的每条结果都落在 SQL 里,运营同事在同一页面复核、修改、驳回, 权限由内置的组织与角色体系控制,操作留在审计日志中。
第二个是把既有能力变成可被智能体驱动的产品。能力定义成 action 之后, 客户用自己的 Claude、Cursor 或 ChatGPT 通过 MCP 端点直接调用你的产品, 不需要你为每个宿主单独开发插件;另一个同类应用也能通过 A2A 把任务委派过来。
再往前看,这套模型适合长成工作台形态:主 agent 负责编排,专员子 agent 各自持有 线程、提示词与工具集,在同一个工作空间里并行处理不同工种; web、桌面、移动、VS Code 与浏览器扩展共用同一批 action,换壳不换能力。
它真正省掉的,是给 AI 再写一遍的那份维护成本。