[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-74820":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":10,"language":11,"languages":10,"totalLinesOfCode":10,"stars":12,"forks":13,"watchers":14,"openIssues":15,"contributorsCount":16,"subscribersCount":16,"size":16,"stars1d":17,"stars7d":18,"stars30d":19,"stars90d":16,"forks30d":16,"starsTrendScore":20,"compositeScore":21,"rankGlobal":10,"rankLanguage":10,"license":22,"archived":23,"fork":23,"defaultBranch":24,"hasWiki":23,"hasPages":23,"topics":25,"createdAt":10,"pushedAt":10,"updatedAt":26,"readmeContent":27,"aiSummary":28,"trendingCount":16,"starSnapshotCount":16,"syncStatus":15,"lastSyncTime":29,"discoverSource":30},74820,"flue","withastro\u002Fflue","withastro","The sandbox agent framework.","https:\u002F\u002Fwww.flueframework.com",null,"TypeScript",4889,260,14,2,0,195,1063,1629,585,109.25,"Apache License 2.0",false,"main",[],"2026-06-12 04:01:16","> **Experimental** — Flue is under active development. APIs may change.\n>\n> Looking for `v0.0.x`? [See here.](https:\u002F\u002Fgithub.com\u002Fwithastro\u002Fflue\u002Ftree\u002Fv0.0.x)\n\n# Flue\n\nFlue is **The Agent Harness Framework.** If you know how to use Claude Code (or Codex, OpenCode, Pi, etc)... then you already know the basics of how to build agents with Flue.\n\nFlue is a TypeScript framework for building the next generation of agents, designed around a built-in **agent harness**. It's like Claude Code, but 100% headless and programmable. There's no baked-in assumption like requiring a human operator to function. No TUI. No GUI. Just TypeScript.\n\nBut using Flue feels like using Claude Code. The agents you build act autonomously to solve problems and complete tasks. They require very little code to run — most of the \"logic\" lives in Markdown: skills, context, and `AGENTS.md`.\n\nFlue isn't another AI SDK. It's a proper runtime-agnostic framework — think Astro or Next.js, but for agents. Write once, build, and deploy your agents anywhere (Node.js, Cloudflare, GitHub Actions, GitLab CI\u002FCD, etc).\n\n## Packages\n\n| Package                       | Description                             |\n| ----------------------------- | --------------------------------------- |\n| [`@flue\u002Fruntime`](packages\u002Fruntime) | Runtime: harness, sessions, tools, sandbox |\n| [`@flue\u002Fcli`](packages\u002Fcli)   | CLI + build\u002Fdev tooling (`flue` binary)    |\n\n## Examples\n\n### Quickstart\n\nThe simplest agent — no container, no tools, just a prompt and a typed result.\n\nUnless you opt-in to initializing a full container sandbox, Flue will default to a virtual sandbox for every agent, powered by [just-bash](https:\u002F\u002Fgithub.com\u002Fvercel-labs\u002Fjust-bash). A virtual sandbox is going to be dramatically faster, cheaper, and more scalable than running a full container for every agent, which makes it perfect for building high-traffic\u002Fhigh-scale agents.\n\n```ts\n\u002F\u002F .flue\u002Fagents\u002Fhello-world.ts\nimport type { FlueContext } from '@flue\u002Fruntime';\nimport * as v from 'valibot';\n\n\u002F\u002F Every agent needs a trigger. This agent is invoked as an API endpoint, via HTTP.\nexport const triggers = { webhook: true };\n\n\u002F\u002F The agent handler. Where the orchestration of the agent lives.\nexport default async function ({ init, payload }: FlueContext) {\n  \u002F\u002F `harness` -- Your initialized harness including sandbox, tools, skills, etc.\n  const harness = await init({ model: 'anthropic\u002Fclaude-sonnet-4-6' });\n  const session = await harness.session();\n\n  \u002F\u002F prompt() sends a message in the session, triggering action.\n  const { data } = await session.prompt(`Translate this to ${payload.language}: \"${payload.text}\"`, {\n    \u002F\u002F Pass `result` to get typed, schema-validated data back from your agent.\n    result: v.object({\n      translation: v.string(),\n      confidence: v.picklist(['low', 'medium', 'high']),\n    }),\n  });\n\n  return data;\n}\n```\n\n### Support Agent\n\nA support agent can also run on Cloudflare without a container by using a cf-shell Workspace. The Workspace is a durable SQLite-indexed filesystem; R2 is an optional hydration source (and large-file spillover), not a live bucket mount. Copy the R2 objects you want into the Workspace before calling `init()`, then the agent operates on that structured filesystem through the `code` tool and `state.*` API.\n\nBecause this agent is deployed to Cloudflare, message history and session state are automatically persisted for you. So you (or your customer) can revisit this support session days, weeks, or years later and pick up exactly where you left off.\n\n```ts\n\u002F\u002F .flue\u002Fagents\u002Fsupport.ts\nimport type { FlueContext } from '@flue\u002Fruntime';\nimport {\n  getDefaultWorkspace,\n  getShellSandbox,\n  hydrateFromBucket,\n} from '@flue\u002Fruntime\u002Fcloudflare';\nimport * as v from 'valibot';\n\nexport const triggers = { webhook: true };\n\nexport default async function ({ init, payload, env }: FlueContext) {\n  const workspace = getDefaultWorkspace();\n\n  \u002F\u002F Hydrate once per agent instance. R2 is a source, not a live mount.\n  if (!(await workspace.exists('\u002F.hydrated'))) {\n    await hydrateFromBucket(workspace, env.KNOWLEDGE_BASE);\n    await workspace.writeFile('\u002F.hydrated', new Date().toISOString());\n  }\n\n  const harness = await init({\n    sandbox: getShellSandbox({ workspace, loader: env.LOADER }),\n    model: 'openrouter\u002Fmoonshotai\u002Fkimi-k2.6',\n  });\n  const session = await harness.session();\n\n  return await session.prompt(\n    `You are a support agent. Use the code tool to search the hydrated\n    workspace for articles relevant to this request, then write a helpful response.\n\n    Customer: ${payload.message}`,\n    {\n      \u002F\u002F Provide roles (aka subagents) to guide your agent. Defined in .flue\u002Froles\u002F\n      role: 'triager',\n    },\n  );\n}\n```\n\nThis requires a `worker_loaders` binding (`{ \"worker_loaders\": [{ \"binding\": \"LOADER\" }] }`) in your `wrangler.jsonc`. If you need true bucket-keys-as-filesystem-paths semantics or Linux shell commands, use `@cloudflare\u002Fsandbox` Containers with `mountBucket` instead. See [Cloudflare Shell Sandbox](https:\u002F\u002Fgithub.com\u002Fwithastro\u002Fflue\u002Fblob\u002Fmain\u002Fdocs\u002Fcloudflare-shell.md) for the full migration and trade-offs.\n\n### Issue Triage (CI)\n\nA triage agent that runs in CI whenever an issue is opened on GitHub. The `local()` sandbox gives the agent direct access to the host filesystem and shell — perfect for CI runners, where `gh`, `git`, and `npm` are already on `$PATH` and the runner itself is your isolation boundary.\n\n```ts\n\u002F\u002F .flue\u002Fagents\u002Ftriage.ts\nimport { type FlueContext } from '@flue\u002Fruntime';\nimport { local } from '@flue\u002Fruntime\u002Fnode';\nimport * as v from 'valibot';\n\n\u002F\u002F Because we are running this in CI, we don't need to expose this as an HTTP endpoint.\n\u002F\u002F The CLI can run any agent from the command line, `flue run triage ...`\nexport const triggers = {};\n\nexport default async function ({ init, payload }: FlueContext) {\n  \u002F\u002F `local()` gives the agent direct access to the host filesystem and\n  \u002F\u002F shell. The agent's bash tool can run `gh`, `git`, `npm` directly.\n  \u002F\u002F Skills and AGENTS.md are discovered from process.cwd().\n  \u002F\u002F\n  \u002F\u002F Only a small allowlist of shell-essential env vars (PATH, HOME,\n  \u002F\u002F locale, etc.) is inherited from process.env by default. Pass\n  \u002F\u002F `env: { GH_TOKEN: process.env.GH_TOKEN }` to expose more.\n  \u002F\u002F\n  \u002F\u002F `model` sets the default model for every prompt\u002Fskill call in this\n  \u002F\u002F agent. Override per-call with `{ model: '...' }` on prompt()\u002Fskill().\n  const harness = await init({\n    sandbox: local({\n      env: { GH_TOKEN: process.env.GH_TOKEN },\n    }),\n    model: 'anthropic\u002Fclaude-opus-4-7',\n  });\n  const session = await harness.session();\n\n  \u002F\u002F Skills can be referenced either by their frontmatter `name:` (shown below)\n  \u002F\u002F or by a relative path under `.agents\u002Fskills\u002F` — e.g.\n  \u002F\u002F `session.skill('triage\u002Freproduce.md', ...)`. Path references are handy for\n  \u002F\u002F skill packs that group multiple stages under one directory.\n  const { data } = await session.skill('triage', {\n    \u002F\u002F Pass arguments to any prompt or skill.\n    args: { issueNumber: payload.issueNumber },\n    \u002F\u002F Result schemas are great for being able to act\u002Forchestrate based on\n    \u002F\u002F the structured `data` returned from your prompt or skill call.\n    result: v.object({\n      severity: v.picklist(['low', 'medium', 'high', 'critical']),\n      reproducible: v.boolean(),\n      summary: v.string(),\n      fix_applied: v.boolean(),\n    }),\n  });\n\n  return data;\n}\n```\n\n### Coding Agent (Remote Sandbox)\n\nThe examples above all run on a lightweight virtual sandbox — no container needed. But for a full coding agent, you want a real Linux environment with git, Node.js, a browser, and a cloned repo ready to go.\n\nDaytona's declarative image builder lets you define the environment in code. The image is cached after the first build, so subsequent sessions start instantly.\n\nInstall the Daytona connector with `flue add daytona | \u003Cyour-agent>` (e.g. `claude`, `opencode`, `codex`, `cursor-agent`). It writes a small `connectors\u002Fdaytona.ts` adapter into your project that you import directly.\n\n```ts\n\u002F\u002F .flue\u002Fagents\u002Fcode.ts\nimport { Type, type FlueContext, type ToolDef } from '@flue\u002Fruntime';\nimport { Daytona } from '@daytona\u002Fsdk';\nimport { daytona } from '..\u002Fconnectors\u002Fdaytona';\n\nexport const triggers = { webhook: true };\n\nexport default async function ({ init, payload, env }: FlueContext) {\n  \u002F\u002F Each agent gets a real container via Daytona. The container has\n  \u002F\u002F a full Linux environment with persistent filesystem and shell.\n  \u002F\u002F\n  \u002F\u002F For simplicity, we always create a new sandbox here. You could also\n  \u002F\u002F first check for an existing sandbox for the agent instance id, and reuse that\n  \u002F\u002F instead to best pick up where you last left off in the conversation.\n  const client = new Daytona({ apiKey: env.DAYTONA_API_KEY });\n  const sandbox = await client.create();\n  const setupHarness = await init({\n    sandbox: daytona(sandbox),\n    model: 'openai\u002Fgpt-5.5',\n  });\n  const setup = await setupHarness.session();\n\n  \u002F\u002F For simplicity, we clone the target repo into the sandbox here.\n  \u002F\u002F You could also bake these into the container image snapshot for a\n  \u002F\u002F faster \u002F near-instant startup.\n  await setup.shell(`git clone ${payload.repo} \u002Fworkspace\u002Fproject`);\n  await setup.shell('npm install', { cwd: '\u002Fworkspace\u002Fproject' });\n\n  \u002F\u002F Start a second harness in the cloned repo. It shares the same sandbox, but\n  \u002F\u002F discovers AGENTS.md and skills from \u002Fworkspace\u002Fproject.\n  const projectHarness = await init({\n    name: 'project',\n    sandbox: daytona(sandbox),\n    cwd: '\u002Fworkspace\u002Fproject',\n    model: 'openai\u002Fgpt-5.5',\n  });\n  const session = await projectHarness.session();\n\n  \u002F\u002F Coding agents don't hide the agent DX from the user, so no need to\n  \u002F\u002F wrap the user's prompt in anything. Just send it to the agent directly\n  \u002F\u002F and then stream back the progress and final results.\n  return await session.prompt(payload.prompt);\n}\n```\n\n### Remote MCP Tools\n\nMCP is available as a runtime tool adapter. Connect to a remote MCP server in trusted code, pass its tools to `init()`, and keep secrets in `env` instead of filesystem context or prompts.\n\n```ts\n\u002F\u002F .flue\u002Fagents\u002Fassistant.ts\nimport { connectMcpServer, type FlueContext } from '@flue\u002Fruntime';\n\nexport const triggers = { webhook: true };\n\nexport default async function ({ init, payload, env }: FlueContext) {\n  const github = await connectMcpServer('github', {\n    url: 'https:\u002F\u002Fmcp.github.com\u002Fmcp',\n    headers: {\n      Authorization: `Bearer ${env.GITHUB_TOKEN}`,\n    },\n  });\n\n  try {\n    const harness = await init({\n      model: 'anthropic\u002Fclaude-sonnet-4-6',\n      tools: github.tools,\n    });\n    const session = await harness.session();\n    return await session.prompt(payload.prompt);\n  } finally {\n    await github.close();\n  }\n}\n```\n\n`connectMcpServer()` defaults to modern streamable HTTP. For legacy SSE servers, pass `transport: 'sse'`. Flue does not auto-detect transports, spawn local stdio MCP servers, or handle OAuth callbacks in this first version.\n\n## Agents, Harnesses, And Sessions\n\nAn agent is the source file in `agents\u002F\u003Cname>.ts`. For HTTP agents, the URL `\u003Cid>` segment identifies the agent instance: the durable runtime scope for one customer, repo, conversation space, or other caller-defined boundary.\n\n```txt\nPOST \u002Fagents\u002F\u003Cagent-name>\u002F\u003Cid>\n```\n\nInside a run, `init()` creates a harness: a configured handle for model defaults, tools, sandbox, filesystem, and sessions. The default harness is named `\"default\"`; pass `init({ name })` when one run needs multiple isolated harness scopes.\n\nBy default, `harness.session()` opens the default session inside the default harness for that agent instance. Reuse the same URL `\u003Cid>` to continue the same agent instance. Use a new URL `\u003Cid>` to start fresh.\n\n```bash\n# Start a conversation (port 3583 is `flue dev`'s default)\ncurl http:\u002F\u002Flocalhost:3583\u002Fagents\u002Fhello\u002Fsession-abc \\\n  -H \"Content-Type: application\u002Fjson\" \\\n  -d '{\"name\": \"Alice\"}'\n\n# Continue that conversation\ncurl http:\u002F\u002Flocalhost:3583\u002Fagents\u002Fhello\u002Fsession-abc \\\n  -H \"Content-Type: application\u002Fjson\" \\\n  -d '{\"name\": \"Alice\"}'\n\n# Start a separate conversation\ncurl http:\u002F\u002Flocalhost:3583\u002Fagents\u002Fhello\u002Fsession-xyz \\\n  -H \"Content-Type: application\u002Fjson\" \\\n  -d '{\"name\": \"Alice\"}'\n```\n\nAgent instances own sandbox state such as files written during a run. Harnesses group related session state within an instance. Sessions persist message history and conversation metadata inside a harness. On Cloudflare, session data is backed by Durable Objects and survives across requests. On Node.js, sessions are stored in memory by default unless you provide a custom store.\n\nIn production, generate a stable URL `\u003Cid>` for the agent instance you want to preserve. Use `harness.session(threadName)` when you need multiple conversations inside the same harness.\n\n### Tasks\n\nUse `session.task()` to run a focused, one-shot child agent in a detached session. Tasks share the same sandbox\u002Ffilesystem, but get their own message history and discover `AGENTS.md` plus `.agents\u002Fskills\u002F` from their working directory. The same `task` tool is also available to the LLM during `prompt()` and `skill()` calls, so the agent can delegate parallel research or exploration work itself.\n\n```ts\nconst session = await harness.session();\n\nconst research = await session.task('Research the auth flow and summarize the key files.', {\n  cwd: '\u002Fworkspace\u002Fproject',\n  role: 'researcher',\n});\n\nconst answer = await session.prompt(\n  `Use this research to draft the implementation plan:\\n\\n${research.text}`,\n);\n```\n\nRoles can be set at the harness, session, or call level. Precedence is `call role > session role > harness role`. Role instructions are applied as call-scoped system prompt overlays, not injected into the persisted user message history.\n\n```ts\nconst harness = await init({ model: 'anthropic\u002Fclaude-sonnet-4-6', role: 'coder' });\nconst session = await harness.session('review-thread', { role: 'reviewer' });\n\nawait session.prompt('Review the latest changes.'); \u002F\u002F uses reviewer\nawait session.task('Research related issues.', { role: 'researcher' }); \u002F\u002F uses researcher\n```\n\n### Provider Settings\n\nUse `providers` when model traffic needs provider-specific runtime settings,\nsuch as an enterprise API gateway, provider-compatible proxy, custom endpoint,\nor gateway-specific credentials. This is common for managed credentials, audit\nlogging, traffic routing, or self-hosted OpenAI-compatible providers.\n\nConfigure these settings in `app.ts` instead of mutating global model state. They\napply to every harness and session that resolves models through that provider.\n\n```ts\n\u002F\u002F .flue\u002Fapp.ts\nimport { configureProvider, flue } from '@flue\u002Fruntime\u002Fapp';\n\nexport default {\n  fetch(req, env, ctx) {\n    configureProvider('anthropic', {\n      baseUrl: env.ANTHROPIC_BASE_URL,\n      headers: { 'X-Custom-Auth': env.GATEWAY_KEY },\n      \u002F\u002F Use this when the proxy expects a synthetic or gateway-specific key.\n      apiKey: 'dummy',\n    });\n\n    return flue().fetch(req, env, ctx);\n  },\n};\n```\n\n### Custom Virtual Sandboxes\n\nFor most agents, use the built-in virtual sandbox or `sandbox: local()` (Node target only). If you need to customize just-bash directly, pass a Bash factory. The factory must return a fresh Bash-like runtime each time; share the filesystem object in the closure to persist files across sessions and prompts.\n\n```ts\nimport { Bash, InMemoryFs } from 'just-bash';\n\nconst fs = new InMemoryFs();\n\nconst harness = await init({\n  sandbox: () => new Bash({ fs, cwd: '\u002Fworkspace', python: true }),\n  model: 'anthropic\u002Fclaude-sonnet-4-6',\n});\nconst session = await harness.session();\n```\n\n## Connectors\n\nConnectors adapt third-party services (sandbox providers, etc.) into Flue. They are not an npm package — they are markdown installation instructions hosted at `https:\u002F\u002Fflueframework.com\u002Fcli\u002Fconnectors\u002F` and applied to your project by your AI coding agent.\n\n```bash\nflue add                                            # list available connectors\nflue add daytona | claude                           # pipe to your coding agent (claude, opencode, codex, cursor-agent, ...)\nflue add https:\u002F\u002Fe2b.dev --category sandbox | claude   # build one from scratch — pass the provider's docs URL as the agent's starting point\n```\n\nThe CLI fetches the markdown for the named connector and prints it to stdout when run by an agent (or with `--print`), or shows a short copyable `flue add ... | \u003Cagent>` recipe when run by a human in a terminal. Your agent reads the markdown and writes a small TypeScript adapter into `.\u002F.flue\u002Fconnectors\u002F\u003Cname>.ts` (or `.\u002Fconnectors\u002F\u003Cname>.ts` for the root layout).\n\n## Running Agents\n\n### Local Development (`flue dev`)\n\nLong-running watch-mode dev server. Rebuilds and reloads on file changes — edit an agent, re-run `curl`, see your change.\n\n```bash\nflue dev --target node          # Node.js dev server\nflue dev --target cloudflare    # Cloudflare Workers (via wrangler) dev server\n```\n\nDefaults to port `3583` (\"FLUE\" on a phone keypad). Override with `--port`.\n\n`flue dev --target cloudflare` requires `wrangler` as a peer dependency in your project (`npm install --save-dev wrangler`).\n\n#### Loading environment variables\n\nPass `--env \u003Cpath>` to load a `.env`-format file. Works for both targets:\n\n```bash\nflue dev --target node --env .env\nflue dev --target cloudflare --env .env\n```\n\nRepeatable; later files override earlier ones on key collision. Shell-set env vars win over file values. Edits to the file trigger a reload. Same flag works for `flue run`.\n\n### Trigger From the CLI (`flue run`)\n\nBuild and run any agent locally, perfect for running in CI or for one-shot scripted invocations. Production-shaped — builds the deployable artifact and starts it once.\n\n```bash\nflue run hello --target node --id test-1 \\\n  --payload '{\"text\": \"Hello world\", \"language\": \"French\"}'\n```\n\n### Trigger From HTTP Endpoint (`flue build`)\n\nBuild and deploy your agents as a web server, perfect for hosted agents.\n\n`flue build` builds to a `.\u002Fdist` directory, which you can then deploy. Cloudflare and any Node.js host are supported today, with more coming in the future.\n\n```\nflue build --target node          # Node.js server (single bundled .mjs)\nflue build --target cloudflare    # Cloudflare Workers + Durable Objects\n```\n\nFor Cloudflare, `flue build` produces an unbundled TypeScript entry that `wrangler deploy` bundles itself — the same path `flue dev --target cloudflare` uses. Dev and deploy go through the same bundler, so what works in dev will work in production.\n","Flue 是一个用于构建下一代代理的 TypeScript 框架，它围绕内置的代理框架设计。其核心功能包括提供了一个完全无头且可编程的环境，允许开发者创建能够自主解决问题和完成任务的代理。技术特点上，Flue 采用 Markdown 来定义技能、上下文等逻辑，并通过简洁的 TypeScript 代码实现复杂的行为。此外，Flue 支持跨平台部署，无论是 Node.js、Cloudflare 还是 GitHub Actions 等都能轻松集成。由于其轻量级虚拟沙箱机制，特别适合于开发需要处理高流量或大规模并发请求的应用场景中使用。","2026-06-11 03:50:59","high_star"]