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绕过, 程序员工具, 自动化任务, 自动化攻击, 自动化框架, 自动笔记, 虚拟沙箱, 运行时无关