返回博客2026年9月16日2 分钟阅读

fx:把 coding agent 做成一把瑞士军刀

摘要

fx 是一个用 Zig 写的原生 coding agent,二进制只有 6MB 出头。它能当终端 agent 用,也能作为 WASM 内核嵌进浏览器和 Node 应用。这篇文章拆解它的工具、权限、子 agent、上下文和嵌入设计,以及它和主流 coding agent 的差别。

主流的 coding agent 多数基于 Node 或 Python,安装后依赖通常有几十上百 MB,交互集中在一个全屏 TUI 里。fx 选了另一条路:用 Zig 写成,编译出来是一个 6MB 出头的原生二进制。

仓库在 vercel-labs/fx,Apache-2.0 开源,目前标注为实验状态。我翻它源码时的第一感觉是,它更像一把瑞士军刀 - 体积小巧,能力却很全。

fx 是什么

fx 对自己的描述是 “Tiny, open, embeddable, native coding agent”,这四个词基本概括了它的取舍。

技术底座是看起来偏小众的 Zig 0.16+,没有 Node 运行时,根目录也没有 package.json。当前源码版本 0.0.10,src/ 下有 591 个 Zig 文件、约 68 万行代码。这里的“小”指的是产物,不是代码量 - 大量代码花在接口约定、校验、权限和测试上。

它有两种用法:既是一个终端里的命令行工具,也是一个可以被别的系统嵌进去的 agent 运行时。

命令行与内核

先说命令行。fx 的顶层命令不是只有进入交互界面一个入口,而是一整套可以单独调用的子命令:

fx                    # 进入交互式 shell
fx ask "解释这个仓库最近的改动"
fx status --json      # 结构化状态,方便脚本消费
fx pr                 # 在 git 仓库里起草 PR
fx issue
fx replay bug.fxtape  # 回放录制的终端 tape

顶层命令还包括 login、setup、permissions、mcp、models、provider、doctor、session、sessions、resume、credits、usage、upgrade、workspace 等。进入交互界面后,还有一组斜杠命令:

  • /compact
  • /permissions
  • /model
  • /skills
  • /mcp
  • 等等

这种设计让 fx 可以被脚本和 CI 直接调用,而不是只能由人在终端里手动敲。

此外,fx 可以作为 ACP(Agent Client Protocol)服务器,也可以编译成 WebAssembly,作为内核被 JS 宿主使用:

import { createFxAgent } from "libfx";

const agent = await createFxAgent({
  apiKey: process.env.AI_GATEWAY_API_KEY,
  model: "google/gemini-2.5-flash-lite",
});

const turn = agent.prompt("Explain this project.");
for await (const event of turn) {
  if (event.type === "text_delta") process.stdout.write(event.delta);
}

libfx 的对外接口很精简:一个 agent 实例就是一次对话,全部状态都在内存中,没有独立的进程或服务,只提供 prompt、checkpoint、close 三个操作。宿主负责网络、凭证、会话存储和终端 I/O。导入 libfx 时不会连接 MCP、不会扫描 skill、不会起进程、不会读文件系统。

官方还配了 Node.js、浏览器、Next.js、Nuxt 四个可运行示例。主流 coding agent 的 SDK 大多是包一层子进程,fx 是把内核本身交出去。

内置工具集

fx 的内置工具集中在 src/builtins/tools.zig,一共十七个:

工具作用
shellrun / interact / stop,支持持久 TTY
read_file带行号的分段读取
write_file覆盖式写入
edit_file精确字符串替换
glob_files按路径模式查找,支持 count
grep_files精确文本匹配(不支持正则),支持分页和上下文行
web_fetch抓取指定 URL 的文本
web_search网络搜索,支持域名白名单 / 黑名单
capability_search按关键词检索可用能力
subagent委派临时或常驻 subagent
skill / install_skill加载和安装技能
mcp_select_tool / mcp_featuresMCP 工具选择和能力发现
ask_user_question结构化提问,带 2 到 6 个选项
vision图片输入
read_tool_result读取历史工具结果

shell 不是一次性执行

列表里最特别的是 shell。它不是“跑一条命令拿一次输出”,而是一套跨轮次的会话协议:

  • shell.run 执行命令。如果命令在 yield_time_ms 内没结束,会返回一个 session_id。
  • shell.interact 用这个 session_id 发送输入(chars)并观察输出,可以跨轮次反复调用。
  • shell.stop 终止会话。

也就是说,一个长期运行的进程可以被 agent 持续交互,而不是每条命令都重新起一个 shell。工具说明里还明确禁止用 &、nohup、setsid 或双重 fork 把进程放到后台。tty=true 会走真正的 PTY,底层是 src/core/terminal/ 里的终端引擎。

这一点和大多数 agent 的 bash 工具差别很大:后者通常只提供一次性执行,交互式程序基本没法用。

结果一直可读

上下文压缩之后,旧的工具结果不会直接消失。较近的完整工具调用会留在上下文里,更早的结果可以通过工具 read_tool_result 重新取出。这样模型需要回溯某个历史输出时,不需要重新跑一遍命令。

权限

fx 的权限基线有三档:ask、auto、full-access(yolo 作为别名保留)。

真正有意思的是 auto 模式下发生的事。每一个没有被配置规则或会话规则明确放行的操作,都会先经过一次针对性的安全审查。审查器拿到的输入包括:操作的原始文本和目标、来源和调用身份、可选的分支证据,以及当前轮次里较早工具结果的有限摘录。

它返回 caution 的唯一理由是发现了具体的 prompt injection 或者恶意行为。如果一个操作只是破坏性、有风险、涉及外部或远程,但不带恶意,它会放行。文本匹配到之前的工具输出只是“值得查看的证据”,不等于恶意。

而且 clear 只授权那一个未改变的操作。操作一旦变化,就要重新审查。caution、证据不足或审查不可用,都只会按住当前这一个操作,把判断交回给 agent,不会弹人工审批界面,也不会禁用工具或结束轮次。

审查还有预算控制:同一个操作在每一轮里最多消耗一次“审查不可用”的机会,caution 和确定性证据不足的结果只在当前轮次内复用。每次审查接受一个合法结构化决定,允许在原来的 30 秒期限内对格式不合规的响应重试一次。

这套设计的目标很具体:不是判断任务做得好不好,也不是替用户做授权决定,而是识别注入和恶意。它和主流的 allow / deny / always 加一个 yolo 开关,是两种不同的问题。

会话自己管理上下文

fx 会在模型可用输入容量的 80% 处自动压缩上下文,也可以手动 /compact。

压缩有几个约束值得注意:

  • 压缩完成后先校验上下文,只有 checkpoint 提交成功才切换。失败或取消的压缩会保留上一个已提交的上下文,已保存的会话可以从那个 checkpoint 恢复。
  • 已保存的会话里,较早的助手内容会被摘要,原始用户文本在放得下时保持不改动、保持时间顺序。
  • 达到容量时才会摘要较早的用户消息。

会话本身存在 ~/.fx/sessions/<session-id>/ 下,是全局的,可以跨工作区携带。每个会话记录自己的 workspace_root,在别的工作区恢复时更新。项目里的 .fx.json 只能放可以安全提交的默认值,像 provider、模型、权限模式、凭证这些属于个人配置的键会被忽略,不会从项目配置里被读进来。

能持续对话的 subagent

fx 的 subagent 有两种形态。

subagent.run 起一个临时 subagent,执行一个完整任务,不接受后续消息,可以单独指定模型和推理强度,不指定就继承父级。

subagent.message 走的是另一种模式:给它一个名字。第一次用这个名字会创建常驻 subagent,之后再调用就是继续对话。如果 subagent 正在工作,消息会排队作为反馈,不会打断它当前的工具调用。可选的 instructions 只替换 subagent 自己的系统覆盖层,而且不能替换 fx 的信任基础提示,也不能扩大权限。

父子关系上,父会话维护一份有界的 subagent/children.json 注册表,子会话只带一个不可变的归属标记。子会话不进入普通会话发现,也不能被直接恢复。

这套结构对应一种具体的工作方式:用前沿模型做规划和过程引导,把实现交给更便宜的模型,过程中还能插话调整。

不绑定任何一家模型

fx 支持内置的几种登录方式:fx login 走 Vercel AI Gateway,fx login codex 走 ChatGPT 订阅的 OAuth,fx login grok 走 xAI 的 OAuth,fx setup 配 Gateway 的 API key。

同时它也支持自定义的 OpenAI Chat Completions 端点,比如本地的 Ollama 或者 OpenRouter:

{
  "provider": "local",
  "providers": {
    "local": {
      "protocol": "openai-chat-completions",
      "base_url": "http://localhost:11434/v1",
      "auth": { "type": "none" }
    }
  },
  "models": {
    "local": "qwen2.5:7b"
  }
}

然后用 fx provider local 保存偏好,或者用环境变量单次覆盖:

FX_PROVIDER=openrouter FX_MODEL=openai/gpt-4.1 fx ask "review this change"

自定义连接有明确的边界:远程端点必须 HTTPS,本机回环地址可以用 HTTP;匿名连接不发 Authorization 头;bearer 连接只读它自己指定的环境变量。它也有一些还没做的能力,比如自定义适配器暂时不支持原生图片输入,也不支持 Responses API 和供应商特定的推理控制。自动权限审查会用同一个连接上的模型,也可以用 reviewer_model 指定另一个。

需要注意的是,fx 不会在模型审查失败时回退到云端审查器,也不会改动权限模式 - 无法审查就不放行。

把体积和启动耗时当成产品约束

这是 fx 和大多数 coding agent 最不一样的地方之一:它把性能和体积写进了 CI。

  • 启动延迟有预算。Linux CI 上每个命令的实测耗时预算是 2ms,由 benchmarks/startup.sh 和 bench.yml 执行。FX_BENCH=1 会在参数解析和 CLI 分发之后直接退出,不进 TTY 初始化。
  • 二进制体积有门禁。binary-size.yml 会在四个平台上对比 PR 和 base 的 stripped ReleaseSafe 二进制,报告精确字节和 section 变化。单个平台增长到 52,429 字节(0.05 MiB)会告警并保留该平台的二进制用于排查。macOS arm64 的 PGSO 发布门禁则守着 7.800 MiB 的上限。
  • 有 PGSO 语料分类。每个根级 tests/e2e/*.test.ts 都必须在 scripts/pgso/corpus.json 里归为 Training、Verification-only 或 Intentional exclusion 之一,PR CI 会拒绝缺失、重复、过期或未分类的文件。

渲染也按同样思路对待。fx 默认走 inline 渲染,只输出一个很小的 ANSI 子集,只有三类所有者可以占用备用屏:交互式权限审查、完整 transcript 界面、目录菜单。为了复现渲染问题,它提供 FX_DEBUG_RECORD=1 录制二进制 tape,再用 fx replay 在没有 TTY 的情况下确定性回放,甚至可以 --golden 生成回归基线。

一个 agent 项目把启动耗时、二进制体积和渲染可复现性都当成约束,这不算常见。

上手

安装脚本一行:

curl -fsSL https://fx.sh/setup.sh | bash

或者从源码构建(需要 Zig 0.16+):

git clone https://github.com/vercel-labs/fx.git
cd fx
zig build -Doptimize=ReleaseSafe
./zig-out/bin/fx

然后是登录和第一次运行:

cd your_project
fx                              # 交互式 shell
fx ask "explain this repository"  # 一次性问答

如果想把它嵌进自己的应用,可以先用 fx acp 接编辑器,或者在 Node / 浏览器项目里 npm install libfx。


相关文章

最近一封 · Sample

OpenRig 冲上 Trending:把 Claude Code 和 Codex 编成一支队伍

“OpenRig 在 GitHub Trending Today 获得 622 个 stars 的窗口信号。它用可恢复的队列、席位与权限记录,把 Claude Code 和 Codex 放进同一套多 Agent 工程流程。”

—— william

Letters

来信

里面装的是

  • 新文章 — 写完一篇就寄一封,不攒货
  • 这周读到的、看到的、好用的工具
  • 正在折腾的实验,附带翻车记录

约莫 1–2 周一封 · 随时退订

合作伙伴

CompeteMap — 英国及爱尔兰学生竞赛一站式搜索

数学、编程、科学、写作等各类竞赛信息汇总,支持按年龄和科目筛选,再也不错过报名截止日。

准备开始了吗?

先简单说明目标,我会给出最合适的沟通方式。