开源项目

flue

无头式 Agent 编排框架,类似 Claude Code 的可编程版本——用 TypeScript 定义 agent 工作流,支持 HTTP/WebSocket 驱动、MCP 工具接入和远程沙箱(Daytona 等),可在 Node.js 或 Cloudflare Workers 上部署。亮点是 harness + session 抽象让多轮对话和状态管理开箱即用,且依赖标准 Markdown(AGENTS.md)而非复杂配置。实验性项目,API 可能变更。

README

Experimental(实验性) — Flue 正在积极开发中。API 可能发生变化。

正在寻找 v0.0.x?在此处查看。

Flue

Flue 是 Agent 控制框架(The Agent Harness Framework)。 如果你知道如何使用 Claude Code(或 Codex、OpenCode、Pi 等),那么你已经掌握了使用 Flue 构建 agent 的基础。

Flue 是一个 TypeScript 框架,用于构建下一代 agent,其设计围绕内置的 agent 控制(agent harness)。它就像 Claude Code,但 100% 无头且可编程。没有内置的假设,例如需要人类操作员来运行。没有 TUI。没有 GUI。只有 TypeScript。

但使用 Flue 的感觉就像使用 Claude Code。你构建的 agent 自主行动以解决问题和完成任务。它们运行所需的代码很少——大部分“逻辑”存在于 Markdown 中:技能(skills)、上下文(context)和 AGENTS.md。

Flue 不是另一个 AI SDK。它是一个真正的运行时无关框架——把它想象成 Astro 或 Next.js,但用于 agent。编写一次,构建,然后将你的 agent 部署到任何地方(Node.js、Cloudflare、GitHub Actions、GitLab CI/CD 等)。

包

包 描述
@flue/runtime 运行时:控制(harness)、会话、工具、沙箱
@flue/cli CLI + 构建/开发工具(flue 二进制文件)

示例

消息驱动的 agent 在 /agents/:name/:id 接收直接的 HTTP 或 WebSocket 消息;应用拥有的集成可以调用 dispatch(...) 将异步输入传递给 agent 会话。关于这些接口,请参见 消息驱动 Agent。可运行的 WebSocket 示例可在 Node 和 Cloudflare 中找到。

对于外部追踪、指标和错误报告,请参见 可观测性、官方 @flue/opentelemetry 适配器、基于公共 observe(...) 的 Braintrust 追踪示例 以及 Sentry 错误报告示例。

快速开始

最简单的 agent——没有容器,没有工具,只有一个提示词和一个类型化的结果。

除非你选择初始化一个完整的容器沙箱,否则 Flue 将为每个 agent 默认使用一个虚拟沙箱,该沙箱由 just-bash 驱动。与为每个 agent 运行完整容器相比,虚拟沙箱将显著更快、更便宜、更具可扩展性,这使其非常适合构建高流量/高规模的 agent。

// .flue/workflows/hello-world.ts
import { createAgent, type FlueContext, type WorkflowRouteHandler } from '@flue/runtime';
import * as v from 'valibot';

// 公共 HTTP 暴露由导出的 Hono 中间件启用。
export const route: WorkflowRouteHandler = async (_c, next) => next();

const translator = createAgent(() => ({ model: 'anthropic/claude-sonnet-4-6' }));

// 工作流处理程序。工作流编排所在处。
export async function run({ init, payload }: FlueContext) {
  // `harness` -- 你初始化的控制,包括沙箱、工具、技能等。
  const harness = await init(translator);
  const session = await harness.session();

  // prompt() 在会话中发送一条消息,触发动作。
  const { data } = await session.prompt(
    `将以下内容翻译成 ${payload.language}:"${payload.text}"`,
    {
      // 传递 `result` 以从 agent 获取类型化、经过 schema 验证的数据。
      result: v.object({
        translation: v.string(),
        confidence: v.picklist(['low', 'medium', 'high']),
      }),
    },
  );

  return data;
}

支持 Agent

一个支持 agent 也可以在 Cloudflare 上运行,无需容器,使用 Flue 的默认虚拟沙箱。将其文件系统填充 agent 所需的上下文,然后它可以使用内置的 grep、glob 和 read 工具搜索该内容。

由于此 agent 部署在 Cloudflare 上,消息历史和会话状态会自动为你持久化。因此,你(或你的客户)可以在数天、数周或数年后重新访问此支持会话,并精确地从上次中断的地方继续。

// .flue/workflows/support.ts
import { createAgent, type FlueContext, type WorkflowRouteHandler } from '@flue/runtime';

export const route: WorkflowRouteHandler = async (_c, next) => next();

const support = createAgent(() => ({ model: 'openrouter/moonshotai/kimi-k2.6' }));

export async function run({ init, payload }: FlueContext) {
  const harness = await init(support);
  const session = await harness.session();

  await session.fs.mkdir('/workspace/articles', { recursive: true });
  await session.fs.writeFile(
    '/workspace/articles/reset-password.md',
    '# 重置密码\n\n使用账户设置页面请求重置密码的电子邮件。',
  );

  return await session.prompt(
    `你是一个支持 agent。在工作区中搜索与此请求相关的文章,
然后编写一个有用的回复。\n\n客户:${payload.message}`,
  );
}

这使用了 Flue 内置的、由 just-bash 驱动的虚拟沙箱;无需连接器或容器。

问题分类(CI)

一个分类 agent,在 GitHub 上打开问题时在 CI 中运行。local() 沙箱使 agent 可以直接访问宿主文件系统和 shell——非常适合 CI 运行器,其中 gh、git 和 npm 已经在 $PATH 中,并且运行器本身是你的隔离边界。

// .flue/workflows/triage.ts
import { createAgent, type FlueContext } from '@flue/runtime';
import { local } from '@flue/runtime/node';
import * as v from 'valibot';

// 因为我们在 CI 中运行它,所以不需要将其暴露为 HTTP 端点。
// CLI 可以从命令行运行任何工作流:`flue run triage ...`
export async function run({ init, payload }: FlueContext) {
  // `local()` 使 agent 可以直接访问宿主文件系统和 shell。
  // agent 的 bash 工具可以直接运行 `gh`、`git`、`npm`。
  // 技能(Skills)和 AGENTS.md 从 process.cwd() 发现。
  //
  // 默认情况下,只有一小部分 shell 必需的环境变量(PATH、HOME、
  // locale 等)从 process.env 继承。传递 `env: { GH_TOKEN: process.env.GH_TOKEN }` 以暴露更多。
  //
  // `model` 设置此 agent 中每个 prompt/skill 调用的默认模型。
  // 通过 prompt()/skill() 上的 `{ model: '...' }` 覆盖每个调用。
  const agent = createAgent(() => ({
    sandbox: local({
      env: { GH_TOKEN: process.env.GH_TOKEN },
    }),
    model: 'anthropic/claude-opus-4-7',
  }));
  const harness = await init(agent);
  const session = await harness.session();

  // 工作区发现的技能通过其 frontmatter `name:` 激活。
  // 静态导入的打包技能也可以通过传递其引用来激活。
  const { data } = await session.skill('triage', {
    // 向任何 prompt 或 skill 传递参数。
    args: { issueNumber: payload.issueNumber },
    // 结果 schema 非常适合根据 prompt 或 skill 调用返回的
    // 结构化 `data` 进行行动/编排。
    result: v.object({
      severity: v.picklist(['low', 'medium', 'high', 'critical']),
      reproducible: v.boolean(),
      summary: v.string(),
      fix_applied: v.boolean(),
    }),
  });

  return data;
}

编码 Agent(远程沙箱)

上面的示例都在轻量级虚拟沙箱上运行——无需容器。但对于一个完整的编码 agent,你想要一个真正的 Linux 环境,包含 git、Node.js、浏览器和一个已克隆的仓库,随时可用。

Daytona 的声明式镜像构建器允许你用代码定义环境。镜像在第一次构建后缓存,因此后续会话可以立即启动。

使用 flue add daytona | <your-agent>(例如 claude、opencode、codex、cursor-agent)安装 Daytona 连接器。它会在你的项目中写入一个小的 connectors/daytona.ts 适配器,你可以直接导入。

// .flue/workflows/code.ts
import {
  Type,
  createAgent,
  defineTool,
  type FlueContext,
  type WorkflowRouteHandler,
} from '@flue/runtime';
import { Daytona } from '@daytona/sdk';
import { daytona } from '../connectors/daytona';

export const route: WorkflowRouteHandler = async (_c, next) => next();

export async function run({ init, payload, env }: FlueContext) {
  // 每个 agent 通过 Daytona 获得一个真实容器。该容器具有
  // 完整的 Linux 环境,包含持久化文件系统和 shell。
  //
  // 为简单起见,我们在此处总是创建一个新的沙箱。你也可以
  // 首先检查 agent 实例 ID 是否存在现有沙箱,并重用该沙箱
  // 以最好地继续上次对话中断的地方。
  const client = new Daytona({ apiKey: env.DAYTONA_API_KEY });
  const sandbox = await client.create();
  const setupAgent = createAgent(() => ({
    sandbox: daytona(sandbox),
    model: 'openai/gpt-5.5',
  }));
  const setupHarness = await init(setupAgent, { name: 'setup' });
  const setup = await setupHarness.session();

  // 为简单起见,我们在此处将目标仓库克隆到沙箱中。
  // 你也可以将它们烘焙到容器镜像快照中,以获得
  // 更快/接近即时的启动速度。
  await setup.shell(`git clone ${payload.repo} /workspace/project`);
  await setup.shell('npm install', { cwd: '/workspace/project' });

  // 在克隆的仓库中启动第二个控制。它共享相同的沙箱,但
  // 从 /workspace/project 发现 AGENTS.md 和技能(skills)。
  const projectAgent = createAgent(() => ({
    sandbox: daytona(sandbox),
    cwd: '/workspace/project',
    model: 'openai/gpt-5.5',
  }));
  const projectHarness = await init(projectAgent, { name: 'project' });
  const session = await projectHarness.session();

  // 编码 agent 不会向用户隐藏 agent DX,因此无需
  // 包装用户提示词。直接将其发送给 agent,
  // 然后流式返回进度和最终结果。
  return await session.prompt(payload.prompt);
}

远程 MCP 工具

MCP 作为运行时工具适配器可用。在可信代码中连接到远程 MCP 服务器,将其工具传递给 init(),并将秘密保存在 env 中,而不是文件系统上下文或提示词中。

// .flue/workflows/assistant.ts
import {
  connectMcpServer,
  createAgent,
  type FlueContext,
  type WorkflowRouteHandler,
} from '@flue/runtime';

export const route: WorkflowRouteHandler = async (_c, next) => next();

export async function run({ init, payload, env }: FlueContext) {
  const github = await connectMcpServer('github', {
    url: 'https://mcp.github.com/mcp',
    headers: {
      Authorization: `Bearer ${env.GITHUB_TOKEN}`,
    },
  });

  try {
    const agent = createAgent(() => ({
      model: 'anthropic/claude-sonnet-4-6',
      tools: github.tools,
    }));
    const harness = await init(agent);
    const session = await harness.session();
    return await session.prompt(payload.prompt);
  } finally {
    await github.close();
  }
}

connectMcpServer() 默认为现代可流式 HTTP。对于传统的 SSE 服务器,传递 transport: 'sse'。Flue 在此版本中不会自动检测传输、生成本地 stdio MCP 服务器或处理 OAuth 回调。

Agent、控制和会话

一个 agent 是 agents/<name>.ts 中的源文件。对于附加的 HTTP 或 WebSocket agent,URL 的 <id> 段标识 agent 实例:一个客户、一个仓库、一个对话空间或其他调用者定义边界的持久运行时范围。

POST /agents/<agent-name>/<id>
GET  /agents/<agent-name>/<id>  (Upgrade: websocket)

在 agent 模块中,导入 type AgentWebSocketHandler 并导出 const websocket: AgentWebSocketHandler = async (_c, next) => next(); 以打开与该稳定实例的长连接 SDK 连接;它可以在调用 next() 之前添加身份验证。一个 agent socket 可以发出顺序的 prompt 并为每个 prompt 选择一个会话;工作流 socket 是一个连接一次调用。

在工作流中,init(createdAgent) 创建一个控制(harness):一个用于模型默认值、工具、沙箱、文件系统和会话的配置化句柄。当一个工作流需要多个隔离的控制作用域时,传递 init(createdAgent, { name })。在 agent 模块中,运行时在消息到达时初始化模块的默认 createAgent(...) 导出。

默认情况下,harness.session() 打开该 agent 实例的默认控制内的默认会话。重用相同的 URL <id> 以继续相同的 agent 实例。使用新的 URL <id> 以重新开始。

运行(runs)仅属于工作流。直接 HTTP 和 WebSocket prompt 是与 agent 会话的附加交互。异步 agent 交付使用 dispatch(...),其 dispatchId 标识交付而不是运行。Agent 交互不会出现在 /runs 中,也不会使用 flue logs 检查。

# 开始对话(端口 3583 是 `flue dev` 的默认端口)
curl http://localhost:3583/agents/hello/session-abc \
  -H "Content-Type: application/json" \
  -d '{"message": "Hello, Alice"}'

# 继续那个对话
curl http://localhost:3583/agents/hello/session-abc \
  -H "Content-Type: application/json" \
  -d '{"message": "我刚才说了什么?"}'

# 开始另一个单独的对话
curl http://localhost:3583/agents/hello/session-xyz \
  -H "Content-Type: application/json" \
  -d '{"message": "来自另一个对话的问候"}'

Agent 实例拥有沙箱状态,例如跨 prompt 和分派输入交互写入的文件。控制在实例内对相关的会话状态进行分组。会话在控制内持久化消息历史和对话元数据。在 Cloudflare 上,会话数据由 Durable Objects 支持,并在请求之间持久化。在 Node.js 上,会话默认存储在内存中,除非你提供自定义存储。

在生产中,为你想要保留的 agent 实例生成一个稳定的 URL <id>。当你需要在同一个控制内进行多个对话时,使用 harness.session(threadName)。

任务

使用 session.task() 在一个分离的会话中运行一个聚焦的、一次性的子 agent。任务共享相同的沙箱/文件系统,但拥有自己的消息历史,并从其工作目录发现 AGENTS.md 和 .agents/skills/。相同的 task 工具在 prompt() 和 skill() 调用期间也可供 LLM 使用,因此 agent 可以自行委派并行研究或探索工作。

const session = await harness.session();

const research = await session.task('研究认证流程并总结关键文件。', {
  cwd: '/workspace/project',
  agent: 'researcher',
});

const answer = await session.prompt(
  `使用这些研究来草拟实施计划:\n\n${research.text}`,
);

使用 defineAgentProfile() 声明可重用的子 agent 配置,在 createAgent(...) 中配置它们,并使用 task({ agent }) 选择它们进行分离式委派。

const researcher = defineAgentProfile({ name: 'researcher', instructions: '仔细研究。' });
const workflowAgent = createAgent(() => ({
  model: 'anthropic/claude-sonnet-4-6',
  subagents: [researcher],
}));
const harness = await init(workflowAgent);
const session = await harness.session('review-thread');

await session.prompt('审查最近的更改。');
await session.task('研究相关的问题。', { agent: 'researcher' });

提供者设置

当模型流量需要提供者特定的运行时设置时,使用 configureProvider(...),例如企业 API 网关、兼容提供者的代理、自定义端点或网关特定的凭据。这对于托管凭据、审计日志、流量路由或自托管的 OpenAI 兼容提供者很常见。

在 app.ts 中使用来自模型值的提供者 ID 配置这些设置。它们适用于通过该提供者解析模型的每个控制和会话。

// .flue/app.ts
import { configureProvider } from '@flue/runtime';
import { flue } from '@flue/runtime/routing';

export default {
  fetch(req, env, ctx) {
    configureProvider('anthropic', {
      baseUrl: env.ANTHROPIC_BASE_URL,
      headers: { 'X-Custom-Auth': env.GATEWAY_KEY },
      // 当代理期望一个合成或网关特定的密钥时使用此选项。
      apiKey: 'dummy',
    });

    return flue().fetch(req, env, ctx);
  },
};

导入的 Agent 技能

位于 <cwd>/.agents/skills/<name>/SKILL.md 的工作区技能在运行时被发现,并通过 session.skill('name') 按名称激活。静态技能导入是打包的构建依赖项:

import review from '../skills/review/SKILL.md' with { type: 'skill' };

const agent = createAgent(() => ({ model: 'anthropic/claude-sonnet-4-6', skills: [review] }));
const harness = await init(agent);
const session = await harness.session();
await session.skill('review');
// 或者直接激活导入的引用:await session.skill(review);

静态导入暴露了一个轻量级的 SkillReference,而不是技能内容。Vite 会验证符合规范的 SKILL.md,并打包其技能目录中所有允许的支持文件,不仅仅是 scripts/、references/ 和 assets/。打包的文件对于直接引用激活和对在 skills 中注册了引用的 agent 进行的操作是可读的;仅导入未注册的引用不会将其内容暴露给普通提示词。可能包含密钥或私钥的文件(包括 .env*、.dev.vars*、凭据文件、密钥文件、.aws/、.ssh/ 和 .gnupg/)会拒绝构建而不会被部署。将凭据保留在技能目录之外。

自定义虚拟沙箱

对于大多数 agent,使用内置的虚拟沙箱或 sandbox: local()(仅限 Node 目标)。生成的默认沙箱由 @flue/runtime 提供;应用程序不需要声明 just-bash,除非编写的应用程序代码直接导入它。如果你需要直接自定义 just-bash,请添加 just-bash 作为应用程序依赖项并传递一个 Bash 工厂。工厂必须每次返回一个新鲜的类似 Bash 的运行时;在闭包中共享文件系统对象,以在会话和提示词之间持久化文件。

import { Bash, InMemoryFs } from 'just-bash';

const fs = new InMemoryFs();

const agent = createAgent(() => ({
  sandbox: () => new Bash({ fs, cwd: '/workspace', python: true }),
  model: 'anthropic/claude-sonnet-4-6',
}));
const harness = await init(agent);
const session = await harness.session();

连接器

连接器(Connectors)将第三方服务(沙箱提供商等)适配到 Flue 中。它们不是一个 npm 包——它们是托管在 https://flueframework.com/cli/connectors/ 的 markdown 安装说明,并由你的 AI 编码 agent 应用到你的项目中。

flue add                                            # 列出可用的连接器
flue add daytona | claude                           # 管道到你的编码 agent(claude、opencode、codex、cursor-agent 等)
flue add https://e2b.dev --category sandbox | claude   # 从头开始构建一个——将提供者的文档 URL 作为 agent 的起点

CLI 会获取指定连接器的 markdown 并将其打印到标准输出(当由 agent 运行时,或使用 --print),或者在人类在终端中运行时显示一个短的、可复制的 flue add ... | <agent> 配方。你的 agent 会读取 markdown,并在选定的源目录下写入一个小型的 TypeScript 适配器:首先是 .flue/,然后是 src/,最后是项目根目录。

运行和连接

本地开发(flue dev)

长期运行的监视模式开发服务器。在文件更改时重建并重新加载——编辑 agent,重新运行 curl,看到你的更改。

flue dev --target node          # Node.js 开发服务器
flue dev --target cloudflare    # Cloudflare Vite/workerd 开发服务器

默认端口为 3583(手机键盘上的 "FLUE")。使用 --port 覆盖。

import { createFlueClient } from '@flue/sdk';

const client = createFlueClient({ baseUrl: 'http://localhost:3583' });
const chat = client.agents.connect('chat', 'customer-123');
await chat.ready;
await chat.prompt('Hello', { session: 'support' });
chat.close();

const job = client.workflows.connect('summarize');
await job.ready;
await job.invoke({ text: 'Summarize me' });

导出的 websocket 中间件可以对其自己的 agent 或工作流 socket 端点进行身份验证。使用自定义 app.ts 进行集中式身份验证或挂载前缀,在 app.route('/api', flue()) 之前应用普通的 Hono 中间件;相同的路由模型适用于 Node 和 Cloudflare。在 baseUrl: 'https://example.com/api' 中包含自定义挂载点,并使用 websocketUrl: (url) => { url.searchParams.set('token', socketToken); return url; } 进行 URL 携带或签名的握手身份验证。HTTP token 和 headers 选项不会自动应用于 WebSocket 升级;浏览器应使用 cookie 或应用程序设计的 URL 身份验证,而需要实现特定标头的 Node 客户端可以提供自定义的 websocket 工厂。避免在 WebSocket 升级路由周围使用会修改标头的中间件。flue dev --target cloudflare 要求 wrangler 作为项目中的对等依赖项(npm install --save-dev wrangler)。

加载环境变量

flue build、flue dev、flue run 和 flue connect 在配置时加载项目根目录的 .env 文件(如果存在):

flue dev --target node

使用 --env <path> 选择一个替代文件;shell 设置的值优先。flue build 可能在评估配置时使用加载的值,但生成的产物在部署运行时不会加载 .env。对于 Cloudflare 开发,Worker 运行时绑定继续使用官方的 .dev.vars 或 .env 文件以及 CLOUDFLARE_ENV 约定。

从 CLI 触发(flue run)

构建并本地运行任何工作流,非常适合 CI 或一次性脚本调用。CLI 通过私有的子进程通信调用构建的 Node 产物,独立于公共工作流路由和中间件。

flue run hello --target node \
  --payload '{"text": "Hello world", "language": "French"}'

连接到 Agent 实例(flue connect)

打开一个与 agent 实例的交互式本地会话。构建的 Node 子进程在连接期间保持存活,因此重复的提示词可以共享内存中的会话状态。

flue connect chat customer-123 --target node --session support

从 HTTP 端点触发(flue build)

构建并将你的 agent 部署为 Web 服务器,非常适合托管的 agent。

flue build 构建到 ./dist 目录,然后你可以部署。目前支持 Cloudflare 和任何 Node.js 主机,未来将支持更多。

flue build --target node          # Node.js 服务器(单个捆绑的 .mjs 文件)
flue build --target cloudflare    # Cloudflare Workers + Durable Objects

对于 Cloudflare,flue build 和 flue dev 通过官方的 Cloudflare Vite/workerd 集成运行。生产构建会生成可由 wrangler deploy 消费的可部署输出。

开源项目withastro2026-06-06原文

相关内容