withastro/flue

GitHub: withastro/flue

Flue 是一个运行时无关的 TypeScript Agent 引导框架,让开发者用极少的代码即可构建可部署到任意环境的自主 AI Agent。

Stars: 3212 | Forks: 166

# Flue Flue 是 **The Agent Harness Framework(Agent 引导框架)**。如果你知道如何使用 Claude Code(或 Codex、OpenCode、Pi 等)……那么你已经掌握了使用 Flue 构建 agent 的基础知识。 Flue 是一个用于构建下一代 agent 的 TypeScript 框架,围绕内置的 **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/sdk`](packages/sdk) | 核心 SDK:构建系统、会话、工具 | | [`@flue/cli`](packages/cli) | 用于构建和运行 agent 的 CLI | ## 示例 ### 快速入门 最简单的 agent——没有容器,没有工具,只有一个提示和一个类型化结果。 除非你选择初始化一个完整的容器沙箱,否则 Flue 会默认为每个 agent 使用一个由 [just-bash](https://github.com/vercel-labs/just-bash) 驱动的虚拟沙箱。虚拟沙箱在运行速度、成本和可扩展性上将大大超过为每个 agent 运行一个完整的容器,这使其非常适合构建高流量/高规模的 agent。 ``` // .flue/agents/hello-world.ts import type { FlueContext } from '@flue/sdk/client'; import * as v from 'valibot'; // Every agent needs a trigger. This agent is invoked as an API endpoint, via HTTP. export const triggers = { webhook: true }; // The agent handler. Where the orchestration of the agent lives. export default async function ({ init, payload }: FlueContext) { // `harness` -- Your initialized harness including sandbox, tools, skills, etc. const harness = await init({ model: 'anthropic/claude-sonnet-4-6' }); const session = await harness.session(); // prompt() sends a message in the session, triggering action. const { data } = await session.prompt(`Translate this to ${payload.language}: "${payload.text}"`, { // Pass a `schema` to get typed, schema-validated data back from your agent. schema: v.object({ translation: v.string(), confidence: v.picklist(['low', 'medium', 'high']), }), }); return data; } ``` ### 支持 Agent 支持 Agent 也可以在虚拟沙箱中运行,但我们现在使用 R2 存储桶添加了一个文件系统。知识库存储在 R2 中,并直接挂载到 harness 文件系统中——agent 使用其内置工具(grep、glob、read)来搜索它。存储桶中还定义了帮助 agent 执行其任务的 skills(技能)。 由于此 agent 部署在 Cloudflare 上,因此消息历史和会话状态会自动为你持久化。因此,你(或你的客户)可以在几天、几周甚至几年后重新访问此支持会话,并准确从上次中断的地方继续。 ``` // .flue/agents/support.ts import { getVirtualSandbox } from '@flue/sdk/cloudflare'; import type { FlueContext } from '@flue/sdk/client'; import * as v from 'valibot'; export const triggers = { webhook: true }; export default async function ({ init, payload, env }: FlueContext) { // Mount the R2 knowledge base bucket as the harness filesystem. // The agent can grep, glob, and read articles with bash, but // without needing to spin up an entire container sandbox. const sandbox = await getVirtualSandbox(env.KNOWLEDGE_BASE); const harness = await init({ sandbox, model: 'openrouter/moonshotai/kimi-k2.6' }); const session = await harness.session(); return await session.prompt( `You are a support agent. Search the knowledge base for articles relevant to this request, then write a helpful response. Customer: ${payload.message}`, { // Provide roles (aka subagents) to guide your agent. Defined in .flue/roles/ role: 'triager', }, ); } ``` ### Issue 分类 (CI) 一个在 GitHub 上打开 issue 时在 CI 中运行的分类 agent。`"local"` 沙箱赋予 agent 对主机文件系统和 shell 的直接访问权限——这非常适合 CI 运行器,因为 `gh`、`git` 和 `npm` 已经在 `$PATH` 上,并且运行器本身就是你的隔离边界。 ``` // .flue/agents/triage.ts import { type FlueContext } from '@flue/sdk/client'; import * as v from 'valibot'; // Because we are running this in CI, we don't need to expose this as an HTTP endpoint. // The CLI can run any agent from the command line, `flue run triage ...` export const triggers = {}; export default async function ({ init, payload }: FlueContext) { // 'local' gives the agent direct access to the host filesystem and // shell. The agent's bash tool can run `gh`, `git`, `npm` directly. // Skills and AGENTS.md are discovered from process.cwd(). // // `model` sets the default model for every prompt/skill call in this // agent. Override per-call with `{ model: '...' }` on prompt()/skill(). const harness = await init({ sandbox: 'local', model: 'anthropic/claude-opus-4-7', }); const session = await harness.session(); // Skills can be referenced either by their frontmatter `name:` (shown below) // or by a relative path under `.agents/skills/` — e.g. // `session.skill('triage/reproduce.md', ...)`. Path references are handy for // skill packs that group multiple stages under one directory. const { data } = await session.skill('triage', { // Pass arguments to any prompt or skill. args: { issueNumber: payload.issueNumber }, // Schemas are great for being able to act/orchestrate based on // the structured `data` returned from your prompt or skill call. schema: 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 | `(例如 `claude`、`opencode`、`codex`、`cursor-agent`)安装 Daytona 连接器。它会将一个精简的 `connectors/daytona.ts` 适配器写入你的项目,你可以直接导入它。 ``` // .flue/agents/code.ts import { Type, type FlueContext, type ToolDef } from '@flue/sdk/client'; import { Daytona } from '@daytona/sdk'; import { daytona } from '../connectors/daytona'; export const triggers = { webhook: true }; export default async function ({ init, payload, env }: FlueContext) { // Each agent gets a real container via Daytona. The container has // a full Linux environment with persistent filesystem and shell. // // For simplicity, we always create a new sandbox here. You could also // first check for an existing sandbox for the agent instance id, and reuse that // instead to best pick up where you last left off in the conversation. const client = new Daytona({ apiKey: env.DAYTONA_API_KEY }); const sandbox = await client.create(); const setupHarness = await init({ sandbox: daytona(sandbox), model: 'openai/gpt-5.5', }); const setup = await setupHarness.session(); // For simplicity, we clone the target repo into the sandbox here. // You could also bake these into the container image snapshot for a // faster / near-instant startup. await setup.shell(`git clone ${payload.repo} /workspace/project`); await setup.shell('npm install', { cwd: '/workspace/project' }); // Start a second harness in the cloned repo. It shares the same sandbox, but // discovers AGENTS.md and skills from /workspace/project. const projectHarness = await init({ name: 'project', sandbox: daytona(sandbox), cwd: '/workspace/project', model: 'openai/gpt-5.5', }); const session = await projectHarness.session(); // Coding agents don't hide the agent DX from the user, so no need to // wrap the user's prompt in anything. Just send it to the agent directly // and then stream back the progress and final results. return await session.prompt(payload.prompt); } ``` ### 远程 MCP 工具 MCP 可作为运行时工具适配器使用。在受信任的代码中连接到远程 MCP 服务器,将其工具传递给 `init()`,并将密钥保留在 `env` 中,而不是文件系统上下文或提示词中。 ``` // .flue/agents/assistant.ts import { connectMcpServer, type FlueContext } from '@flue/sdk/client'; export const triggers = { webhook: true }; export default async function ({ init, payload, env }: FlueContext) { const github = await connectMcpServer('github', { url: 'https://mcp.github.com/mcp', headers: { Authorization: `Bearer ${env.GITHUB_TOKEN}`, }, }); try { const harness = await init({ model: 'anthropic/claude-sonnet-4-6', tools: github.tools, }); 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、Harness 和 Session 一个 agent 是 `agents/.ts` 中的源文件。对于 HTTP agent,URL `` 片段标识了 agent 实例:即一个客户、代码库、对话空间或其他由调用方定义边界的持久运行时作用域。 ``` POST /agents// ``` 在一次运行(run)中,`init()` 会创建一个 harness:一个用于模型默认值、工具、沙箱、文件系统和会话的已配置句柄。默认的 harness 名为 `"default"`;当一次运行需要多个隔离的 harness 作用域时,请传递 `init({ name })`。 默认情况下,`harness.session()` 会为该 agent 实例打开默认 harness 内的默认会话。重用相同的 URL `` 即可继续同一个 agent 实例。使用新的 URL `` 即可重新开始。 ``` # 开启对话(端口 3583 是 `flue dev` 的默认端口) curl http://localhost:3583/agents/hello/session-abc \ -H "Content-Type: application/json" \ -d '{"name": "Alice"}' # 继续该对话 curl http://localhost:3583/agents/hello/session-abc \ -H "Content-Type: application/json" \ -d '{"name": "Alice"}' # 开启单独对话 curl http://localhost:3583/agents/hello/session-xyz \ -H "Content-Type: application/json" \ -d '{"name": "Alice"}' ``` Agent 实例拥有沙箱状态,例如运行期间写入的文件。Harness 在实例内对相关的会话状态进行分组。Session 在 harness 内部持久化消息历史和对话元数据。在 Cloudflare 上,会话数据由 Durable Objects 支持,并可在不同请求之间持久存在。在 Node.js 上,除非你提供自定义存储,否则会话默认存储在内存中。 在生产环境中,请为你希望保留的 agent 实例生成一个稳定的 URL ``。当你需要在同一个 harness 内进行多个对话时,请使用 `harness.session(threadName)`。 ### 任务 使用 `session.task()` 在独立的会话中运行一个专注的、一次性的子 agent。任务共享相同的沙箱/文件系统,但拥有自己的消息历史,并从其工作目录中发现 `AGENTS.md` 及 `.agents/skills/`。在 `prompt()` 和 `skill()` 调用期间,LLM 也可以使用相同的 `task` 工具,因此 agent 可以自行委派并行的研究或探索工作。 ``` const session = await harness.session(); const research = await session.task('Research the auth flow and summarize the key files.', { cwd: '/workspace/project', role: 'researcher', }); const answer = await session.prompt( `Use this research to draft the implementation plan:\n\n${research.text}`, ); ``` 角色可以在 harness、会话或调用级别进行设置。优先级为 `call role > session role > harness role`。角色指令作为调用范围的系统提示覆盖层应用,不会注入到持久化的用户消息历史记录中。 ``` const harness = await init({ model: 'anthropic/claude-sonnet-4-6', role: 'coder' }); const session = await harness.session('review-thread', { role: 'reviewer' }); await session.prompt('Review the latest changes.'); // uses reviewer await session.task('Research related issues.', { role: 'researcher' }); // uses researcher ``` ### Provider 设置 当模型流量需要特定于 provider 的运行时设置时使用 `providers`, 例如企业 API 网关、兼容 provider 的代理、自定义 endpoint 或特定于网关的凭据。这通常用于托管凭据、审计 日志记录、流量路由或自托管的 OpenAI 兼容 provider。 请在 `app.ts` 中配置这些设置,而不是修改全局模型状态。它们 将应用于每一个通过该 provider 解析模型的 harness 和会话。 ``` // .flue/app.ts import { configureProvider, flue } from '@flue/sdk/app'; export default { fetch(req, env, ctx) { configureProvider('anthropic', { baseUrl: env.ANTHROPIC_BASE_URL, headers: { 'X-Custom-Auth': env.GATEWAY_KEY }, // Use this when the proxy expects a synthetic or gateway-specific key. apiKey: 'dummy', }); return flue().fetch(req, env, ctx); }, }; ``` ### 自定义虚拟沙箱 对于大多数 agent,请使用内置的虚拟沙箱或 `sandbox: 'local'`。如果你需要直接自定义 just-bash,请传递一个 Bash 工厂。该工厂每次必须返回一个全新的类 Bash 运行时;在闭包中共享文件系统对象以跨会话和提示持久化文件。 ``` import { Bash, InMemoryFs } from 'just-bash'; const fs = new InMemoryFs(); const harness = await init({ sandbox: () => new Bash({ fs, cwd: '/workspace', python: true }), model: 'anthropic/claude-sonnet-4-6', }); const session = await harness.session(); ``` ## 连接器 连接器将第三方服务(沙箱提供商等)适配到 Flue 中。它们不是一个 npm 包——它们是托管在 `https://flueframework.com/cli/connectors/` 上的 markdown 安装说明,并由你的 AI 编码 agent 应用于你的项目。 ``` flue add # list available connectors flue add daytona | claude # pipe to your coding agent (claude, opencode, codex, cursor-agent, ...) flue add https://e2b.dev --category sandbox | claude # build one from scratch — pass the provider's docs URL as the agent's starting point ``` 当由 agent 运行(或使用 `--print` 参数)时,CLI 会获取命名连接器的 markdown 并将其打印到标准输出;当由人类在终端中运行时,则会显示一条简短可复制的 `flue add ... | ` 命令。你的 agent 读取该 markdown,并将一个轻量的 TypeScript 适配器写入 `./.flue/connectors/.ts`(对于根布局,则为 `./connectors/.ts`)。 ## 运行 Agent ### 本地开发 (`flue dev`) 长期运行的监视模式开发服务器。在文件更改时重新构建并重新加载——编辑 agent,重新运行 `curl`,即可看到你的更改。 ``` flue dev --target node # Node.js dev server flue dev --target cloudflare # Cloudflare Workers (via wrangler) dev server ``` 默认端口为 `3583`(电话键盘上的 "FLUE")。可使用 `--port` 覆盖。 `flue dev --target cloudflare` 需要在你的项目中将 `wrangler` 作为 peer dependency(`npm install --save-dev wrangler`)。 #### 加载环境变量 传递 `--env ` 以加载 `.env` 格式的文件。适用于两种 target: ``` flue dev --target node --env .env flue dev --target cloudflare --env .env ``` 可重复使用;当键发生冲突时,后加载的文件会覆盖先加载的文件。Shell 中设置的环境变量优先于文件中的值。对文件的修改会触发重新加载。同样的标志也适用于 `flue run`。 ### 从 CLI 触发 (`flue run`) 在本地构建并运行任何 agent,非常适合在 CI 中运行或用于一次性脚本调用。具有生产级形态——构建可部署的 artifact 并将其启动一次。 ``` flue run hello --target node --id test-1 \ --payload '{"text": "Hello world", "language": "French"}' ``` ### 从 HTTP Endpoint 触发 (`flue build`) 将你的 agent 构建并部署为 Web 服务器,非常适合托管 agent。 `flue build` 会构建出一个 `./dist` 目录,然后你可以对其进行部署。目前支持 Cloudflare 和任何 Node.js 主机,未来将支持更多。 ``` flue build --target node # Node.js server (single bundled .mjs) flue build --target cloudflare # Cloudflare Workers + Durable Objects ``` 对于 Cloudflare,`flue build` 会生成一个未打包的 TypeScript 入口文件,`wrangler deploy` 会自行将其打包——这与 `flue dev --target cloudflare` 使用的路径相同。开发和部署经过相同的打包器,因此在开发中可行的方案在生产环境中也同样可行。
标签:Agent框架, AI代理, AI编程助手, API端点, Cloudflare, DLL 劫持, GitHub Actions, Go语言工具, LLM应用开发, MITM代理, MITRE ATT&CK, OSV, TypeScript, Vercel, 云部署, 人工智能, 代码执行, 大语言模型, 威胁情报, 安全插件, 开发者工具, 无头代理, 沙箱环境, 用户模式Hook绕过, 程序员工具, 自动化任务, 自动化攻击, 自动化框架, 自动笔记, 虚拟沙箱, 运行时无关