Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions apps/docs/src/content/docs/ecosystem/channels/stripe.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,9 +165,10 @@ export function retrieveCustomer(customerId: string) {
async run() {
const customer = await client.customers.retrieve(customerId);
return {
output: 'deleted' in customer
? { id: customer.id, deleted: true }
: { id: customer.id, name: customer.name, email: customer.email },
output:
'deleted' in customer
? { id: customer.id, deleted: true }
: { id: customer.id, name: customer.name, email: customer.email },
};
},
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ The blueprint creates the adapter at `<source-root>/sandboxes/cloudflare-compute

```jsonc title="wrangler.jsonc"
{
"compatibility_flags": ["nodejs_compat", "experimental"],
"worker_loaders": [{ "binding": "LOADER" }]
"compatibility_flags": ["nodejs_compat", "experimental"],
"worker_loaders": [{ "binding": "LOADER" }],
}
```

Expand All @@ -39,27 +39,27 @@ import { extend, getDurableObjectIdentity } from '@flue/runtime/cloudflare';

/** Re-export from each agent module: `export { workspaceHost as cloudflare } ...` */
export const workspaceHost = extend({
base: (Base) =>
class extends Base {
/* ... captures the Durable Object state; exposes the workspace stub ... */
},
base: (Base) =>
class extends Base {
/* ... captures the Durable Object state; exposes the workspace stub ... */
},
});

/** One durable Workspace per agent instance, shared with the sandbox. */
export function getComputerWorkspace(options: GetComputerWorkspaceOptions): Workspace {
/* ... memoized construction: DO storage + git client + WorkerShellBackend ... */
/* ... memoized construction: DO storage + git client + WorkerShellBackend ... */
}

export function getComputerSandbox(options: GetComputerWorkspaceOptions): SandboxFactory {
return {
async createSandbox(): Promise<ComputerSandboxEnv> {
const workspace = getComputerWorkspace(options);
await workspace.fs.mkdir('/workspace', { recursive: true });
return { ...createWorkspaceSandbox(workspace, '/workspace'), workspace };
},
// No `tools` override: exec() works here, so the framework's standard
// set (bash/grep/glob/read/write/edit) applies as-is.
};
return {
async createSandbox(): Promise<ComputerSandboxEnv> {
const workspace = getComputerWorkspace(options);
await workspace.fs.mkdir('/workspace', { recursive: true });
return { ...createWorkspaceSandbox(workspace, '/workspace'), workspace };
},
// No `tools` override: exec() works here, so the framework's standard
// set (bash/grep/glob/read/write/edit) applies as-is.
};
}
```

Expand All @@ -74,9 +74,9 @@ import { getComputerSandbox } from '../sandboxes/cloudflare-computer';
export { workspaceHost as cloudflare } from '../sandboxes/cloudflare-computer';

export function Assistant() {
useModel('cloudflare/@cf/moonshotai/kimi-k2.6');
useSandbox(getComputerSandbox({ loader: env.LOADER }));
return 'You explore and edit your durable workspace with the standard file and shell tools.';
useModel('cloudflare/@cf/moonshotai/kimi-k2.6');
useSandbox(getComputerSandbox({ loader: env.LOADER }));
return 'You explore and edit your durable workspace with the standard file and shell tools.';
}
```

Expand Down
5 changes: 4 additions & 1 deletion apps/docs/src/content/docs/reference/sandbox-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,10 @@ interface BashLike {
## `SandboxToolFactory`

```ts
type SandboxToolFactory = (sandbox: Sandbox, options: SandboxToolFactoryOptions) => AgentTool<any>[];
type SandboxToolFactory = (
sandbox: Sandbox,
options: SandboxToolFactoryOptions,
) => AgentTool<any>[];

interface SandboxToolFactoryOptions {
subagents: Record<string, SubagentDefinition>;
Expand Down
1 change: 1 addition & 0 deletions apps/docs/src/styles/global.css
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,7 @@
overflow-x: auto;
border: 1px solid #e2e5eb;
border-radius: 0.3rem;
/* biome-ignore lint/complexity/noImportantStyles: Shiki sets the theme background inline. */
background: #f6f8fa !important;
box-shadow: 0 1px 1px rgb(0 0 0 / 0.04);
font-family: var(--font-mono);
Expand Down
122 changes: 61 additions & 61 deletions apps/www/src/pages/blog/flue-2.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,16 @@ import CopyPrompt from '../../components/CopyPrompt.astro';

Flue 2.0 is available today. We rebuilt our agent framework around a new hooks-based API, unlocking a new kind of dynamic agent that can evolve its capabilities over time.

**Agent Hooks** are the new foundation in Flue 2.0. Hooks let you build dynamic agents that can manage their own state, listen to agent lifecycle events, and even attach different resources and capabilities dynamically to enhance themselves at runtime.
**Agent Hooks** are the new foundation in Flue 2.0. Hooks let you build dynamic agents that can manage their own state, listen to agent lifecycle events, and even attach different resources and capabilities dynamically to enhance themselves at runtime.

Hooks are authored in TypeScript and presented in a familiar API:

```ts
export function Assistant() {
const [count, setCount] = usePersistentState('count', 0);
useAgentStart(() => setCount((n) => n + 1));
useModel('moonshot/kimi-k2');
return `You are a helpful assistant. This conversation has ${count} messages.`;
const [count, setCount] = usePersistentState('count', 0);
useAgentStart(() => setCount((n) => n + 1));
useModel('moonshot/kimi-k2');
return `You are a helpful assistant. This conversation has ${count} messages.`;
}
```

Expand Down Expand Up @@ -50,29 +50,29 @@ In the original Flue 1.0 API I took the same static approach to agent architectu
```ts
// Flue 1.0 API
export default defineAgent(() => ({
model: 'moonshot/kimi-k2',
tools: [replyToIssue],
skills: [triage, verify],
sandbox: local(),
instructions,
model: 'moonshot/kimi-k2',
tools: [replyToIssue],
skills: [triage, verify],
sandbox: local(),
instructions,
}));
```

We dogfooded this API with real developers during the Flue 1.0 Beta. What we found was surprising: The static agent approach worked well for simple use-cases, but started to break down for more complex, non-trivial agents and multi-step workflows.

We began to wonder: if Flue 1.0 had this problem, then how many other popular agent frameworks and SDKs had this problem as well?
We began to wonder: if Flue 1.0 had this problem, then how many other popular agent frameworks and SDKs had this problem as well?

We decided that this was a problem worth solving, and that the timing was right to make the breaking change to Flue now, while we were still early. We experimented with a bunch of different approaches, but eventually a familiar design pattern started to come into focus:

```ts
// Flue 2.0 API
export function IssueTriageAgent() {
useModel('moonshot/kimi-k2');
useTool(replyToIssue);
useSkill(triage);
useSkill(verify);
useSandbox(local());
return instructions;
useModel('moonshot/kimi-k2');
useTool(replyToIssue);
useSkill(triage);
useSkill(verify);
useSandbox(local());
return instructions;
}
```

Expand All @@ -84,22 +84,22 @@ Let's say you want your agent to be able to upgrade its initial model or sandbox

```ts
export function CompanySlackAgent() {
// Attach persistent data to each agent, stored in your DB.
const [isEnhanced, setEnhanced] = usePersistentState("isEnhanced", false);
// Give your agent the ability to upgrade itself.
useTool({ name: "enhance", description: "...", run: () => setEnhanced(true) });
// Attach new capabilities, on-demand.
if (isEnhanced) {
useModel('anthropic/fable-5-0');
useSandbox(daytona());
useTool(/* ... */);
useSkill(/* ... */);
useSubagent(/* ... */);
} else {
useModel('moonshot/kimi-k2');
}
// Return your agent instructions. Flue handles the rest.
return `You are a helpful assistant.`;
// Attach persistent data to each agent, stored in your DB.
const [isEnhanced, setEnhanced] = usePersistentState('isEnhanced', false);
// Give your agent the ability to upgrade itself.
useTool({ name: 'enhance', description: '...', run: () => setEnhanced(true) });
// Attach new capabilities, on-demand.
if (isEnhanced) {
useModel('anthropic/fable-5-0');
useSandbox(daytona());
useTool(/* ... */);
useSkill(/* ... */);
useSubagent(/* ... */);
} else {
useModel('moonshot/kimi-k2');
}
// Return your agent instructions. Flue handles the rest.
return `You are a helpful assistant.`;
}
```

Expand All @@ -109,25 +109,25 @@ In Flue 2.0, a workflow is persistent state plus conditional tools. Give each st

```ts
export function IssueTriageAgent({ id }) {
useSandbox(local());
// Persist which step of the workflow the agent is on.
const [step, setStep] = usePersistentState('step', 'reproduce');
// Each step attaches its own tools and skills.
if (step === 'reproduce') {
useModel('anthropic/sonnet-5-0');
useSkill(reproChecklist);
useTool({ name: 'submit_repro', description: '...', run: () => setStep('diagnose') });
}
if (step === 'diagnose') {
useModel('anthropic/fable-5-0');
useSkill(debuggingGuide);
useTool({ name: 'submit_diagnosis', description: '...', run: () => setStep('report') });
}
if (step === 'report') {
useModel('anthropic/sonnet-5-0');
useTool(postGitHubComment);
}
return `Follow the workflow to triage GitHub issue ${id}: reproduce -> diagnose -> report.`;
useSandbox(local());
// Persist which step of the workflow the agent is on.
const [step, setStep] = usePersistentState('step', 'reproduce');
// Each step attaches its own tools and skills.
if (step === 'reproduce') {
useModel('anthropic/sonnet-5-0');
useSkill(reproChecklist);
useTool({ name: 'submit_repro', description: '...', run: () => setStep('diagnose') });
}
if (step === 'diagnose') {
useModel('anthropic/fable-5-0');
useSkill(debuggingGuide);
useTool({ name: 'submit_diagnosis', description: '...', run: () => setStep('report') });
}
if (step === 'report') {
useModel('anthropic/sonnet-5-0');
useTool(postGitHubComment);
}
return `Follow the workflow to triage GitHub issue ${id}: reproduce -> diagnose -> report.`;
}
```

Expand All @@ -137,18 +137,18 @@ And just like React hooks, they compose together nicely. You can build your own

```ts
export function useLinear(apiKey) {
useMcpConnection({
name: 'linear',
url: 'https://mcp.linear.app/mcp',
auth: apiKey,
});
useMcpConnection({
name: 'linear',
url: 'https://mcp.linear.app/mcp',
auth: apiKey,
});
}

export function ProjectAssistant() {
useLinear(process.env.LINEAR_API_KEY);
useGitHub(process.env.GITHUB_API_KEY);
useBrowser();
return 'Help your team manage their work.';
useLinear(process.env.LINEAR_API_KEY);
useGitHub(process.env.GITHUB_API_KEY);
useBrowser();
return 'Help your team manage their work.';
}
```

Expand Down
2 changes: 1 addition & 1 deletion biome.jsonc
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"$schema": "https://biomejs.dev/schemas/2.5.4/schema.json",
"$schema": "https://biomejs.dev/schemas/2.5.6/schema.json",
"assist": { "actions": { "source": { "organizeImports": "on" } } },
"css": {
"parser": {
Expand Down
8 changes: 4 additions & 4 deletions examples/cloudflare/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ mounts each agent's routes explicitly.

## Agents

| Agent | Demonstrates |
| ---------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| `with-cloudflare-binding.ts` | Routing model traffic through the Workers AI binding (no API keys). |
| Agent | Demonstrates |
| ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| `with-cloudflare-binding.ts` | Routing model traffic through the Workers AI binding (no API keys). |
| `skills-from-r2.ts` | Hydrating a cloudflare-computer `Workspace` from an R2 bucket and using a discovered skill (via a model-callable `check_spam` action). |
| `skills-from-git.ts` | Hydrating a cloudflare-computer `Workspace` from a git repo via the built-in `workspace.git` client. |
| `skills-from-git.ts` | Hydrating a cloudflare-computer `Workspace` from a git repo via the built-in `workspace.git` client. |

## Setup

Expand Down
1 change: 0 additions & 1 deletion examples/discord-channel/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
"valibot": "^1.0.0"
},
"devDependencies": {
"@cloudflare/vitest-pool-workers": "0.19.1",
"@flue/cli": "workspace:*",
"@flue/vite": "workspace:*",
"typescript": "^7.0.2",
Expand Down
1 change: 0 additions & 1 deletion examples/github-channel/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
"valibot": "^1.0.0"
},
"devDependencies": {
"@cloudflare/vitest-pool-workers": "0.19.1",
"@flue/cli": "workspace:*",
"@flue/vite": "workspace:*",
"typescript": "^7.0.2",
Expand Down
1 change: 0 additions & 1 deletion examples/google-chat-channel/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
"valibot": "^1.0.0"
},
"devDependencies": {
"@cloudflare/vitest-pool-workers": "0.19.1",
"@flue/cli": "workspace:*",
"@flue/vite": "workspace:*",
"typescript": "^7.0.2",
Expand Down
4 changes: 2 additions & 2 deletions examples/hello-world/src/sandboxes/daytona.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@
*/

import type { Sandbox as DaytonaSandbox } from '@daytona/sdk';
import type { FileStat, SandboxDriver, SandboxFactory, Sandbox } from '@flue/runtime';
import { sandboxFromDriver, SandboxOperationUnsupportedError } from '@flue/runtime';
import type { FileStat, Sandbox, SandboxDriver, SandboxFactory } from '@flue/runtime';
import { SandboxOperationUnsupportedError, sandboxFromDriver } from '@flue/runtime';

// ─── DaytonaSandboxDriver ──────────────────────────────────────────────────────

Expand Down
1 change: 0 additions & 1 deletion examples/intercom-channel/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
},
"devDependencies": {
"@cloudflare/vite-plugin": "^1.39.2",
"@cloudflare/vitest-pool-workers": "0.19.1",
"@flue/cli": "workspace:*",
"@flue/vite": "workspace:*",
"@types/node": "^26.1.1",
Expand Down
1 change: 0 additions & 1 deletion examples/linear-channel/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
"valibot": "^1.0.0"
},
"devDependencies": {
"@cloudflare/vitest-pool-workers": "0.19.1",
"@flue/cli": "workspace:*",
"@flue/vite": "workspace:*",
"typescript": "^7.0.2",
Expand Down
1 change: 0 additions & 1 deletion examples/messenger-channel/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
"valibot": "^1.0.0"
},
"devDependencies": {
"@cloudflare/vitest-pool-workers": "0.19.1",
"@flue/cli": "workspace:*",
"@flue/vite": "workspace:*",
"typescript": "^7.0.2",
Expand Down
1 change: 0 additions & 1 deletion examples/notion-channel/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
},
"devDependencies": {
"@cloudflare/vite-plugin": "^1.39.2",
"@cloudflare/vitest-pool-workers": "0.19.1",
"@flue/cli": "workspace:*",
"@flue/vite": "workspace:*",
"@types/node": "^26.1.1",
Expand Down
1 change: 0 additions & 1 deletion examples/resend-channel/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
},
"devDependencies": {
"@cloudflare/vite-plugin": "^1.39.2",
"@cloudflare/vitest-pool-workers": "0.19.1",
"@flue/cli": "workspace:*",
"@flue/vite": "workspace:*",
"@types/node": "^26.1.1",
Expand Down
1 change: 0 additions & 1 deletion examples/salesforce-marketing-cloud-channel/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
},
"devDependencies": {
"@cloudflare/vite-plugin": "^1.39.2",
"@cloudflare/vitest-pool-workers": "0.19.1",
"@flue/cli": "workspace:*",
"@flue/vite": "workspace:*",
"@types/node": "^26.1.1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export function createSalesforceMarketingCloudClient({
};
}

export function salesforceMarketingCloudRestOrigin(restBaseUrl: string): string {
function salesforceMarketingCloudRestOrigin(restBaseUrl: string): string {
let url: URL;
try {
url = new URL(restBaseUrl);
Expand Down
Loading