Examples
Codex-style coding agent
This coding agent inspects a repository, changes files, runs checks, delegates reviews, keeps the complete thread, and reconnects after the client or Worker stops.
The important part is small:
const codingTools = app.tools({
workspace: app.workspace({ branch: "run" }),
shell: app.sandbox({ network: "restricted" }),
github: app.mcp("github"),
});
app.workspace() adds the file and Git tools. You do not write your own grep, read, edit, write, or
delete functions. app.sandbox() adds a Linux runtime for tests, builds, package installation, and
durable processes.
- 01Task“Fix the checkout tests.”
- 02SelectUse the known core list and load one selected schema.
- 03WorkGrep, read, edit, and run focused checks.
- 04ReviewA durable child agent checks the diff.
- 05SaveRecord messages, audits, usage, diff, and checkpoint.
- 06ResumeWebSocket, SSE, or HTTP continues from a cursor.
What tools are built in?
The registry name becomes the tool namespace. Flary puts the small core list in the agent instructions, so the agent already knows that grep, read, edit, write, delete, Git, and shell actions exist. It loads a full schema only when it selects one action:
| Source | Built-in tools | Default policy |
|---|---|---|
workspace |
list, stat, glob, grep, read, diff |
Read without approval |
workspace |
write, edit, batchEdit, move, delete |
Write with approval |
workspace |
git_status, git_log, git_diff, git_remote |
Read without approval |
workspace |
git_clone, git_add, git_rm, git_commit, git_branch, git_checkout, git_fetch, git_pull, git_push, git_init |
Write with approval |
shell |
exec, processStart, processAttach, processStdin, processSignal, processSleep, processWake |
Commands and controls are governed writes; attach is a read |
github |
Tools discovered from the connected MCP server | Uses MCP annotations plus Flary policy |
Workspace paths stay inside the authenticated thread workspace. Credentials do not enter tool input, generated code, or model context.
What does the executor do?
The model sees one execute tool, not every file, Git, shell, MCP, and OpenAPI schema. Flary runs
the model’s bounded TypeScript in a Cloudflare Dynamic Worker with direct network access disabled.
This is a hybrid tool surface:
- Always visible:
execute, plus the names and purpose of configured core workspace, shell, and browser actions. - Loaded when selected: the exact input schema for one core action.
- Fully lazy: MCP, OpenAPI, local functions, skills, and uncommon tools.
The agent does not need to search to learn that workspace.grep exists. It still calls
tools.describe({ id: "workspace.grep" }) before the first call so Flary can supply and validate
the exact schema. Search is most useful for a large or changing external catalog.
Conceptually, one turn can generate this code:
const grep = await tools.describe({ id: "workspace.grep" });
const found = await tools.call({
id: grep.id,
input: {
query: "rateLimit",
pattern: "src/**/*.ts",
maxMatches: 50,
},
});
const file = await tools.call({
id: "workspace.read",
input: { path: found.files[0].path, encoding: "utf8" },
});
await tools.call({
id: "workspace.edit",
input: {
path: file.file.path,
expectedSha256: file.file.sha256,
edits: [{ oldText: "old limit", newText: "new limit" }],
},
});
return tools.call({
id: "shell.exec",
input: { command: "npm test -- --runInBand", cwd: "/workspace" },
});
Search returns small descriptors without schemas. Describe loads one selected schema. Call validates the input, checks policy, pauses for approval when needed, records the audit, and journals the result for replay. A Dynamic Worker restart cannot make a completed write run twice.
The Dynamic Worker coordinates tool calls. It is not the Linux machine. Workspace Durable Objects own files and Git state. Cloudflare Sandbox runs Linux commands. Thread Control owns realtime commands, approvals, limits, and the public session projection. R2 stores durable files, checkpoints, and cold archives.
Complete tools file
This is the source used by flary create. The package consumer test builds it from a clean
installation.
import { z } from "flary";
import { app } from "./flary";
import { generated } from "./flary.generated";
export const searchDocs = app.fn({
description: "Search the product documentation",
input: z.object({ query: z.string().min(1) }),
output: z.array(
z.object({
title: z.string(),
url: z.string().url(),
excerpt: z.string(),
}),
),
run: ({ query }) => [
{
title: `Documentation result for ${query}`,
url: "https://example.com/docs",
excerpt: "Replace this function with your documentation search.",
},
],
});
const optionalTools = {
...(generated.features.mcp
? {
github: app.mcp({
namespace: "github",
connection: "github",
url: "https://api.githubcopilot.com/mcp/readonly",
}),
}
: {}),
...(generated.features.browser ? { browser: app.browser({ profile: "thread" }) } : {}),
...(generated.features.sandbox
? { shell: app.sandbox({ network: "restricted", sleepAfter: "10m" }) }
: {}),
};
/** Tools for finite support functions. */
export const supportTools = app.tools({ searchDocs });
/**
* A complete coding workspace.
*
* app.workspace() supplies durable list, stat, glob, grep, read, diff,
* write, edit, apply-patch, batch-edit, copy, move, delete, and Git tools. app.sandbox()
* supplies Linux commands and durable processes when that feature is enabled.
*/
export const codingTools = app.tools({
workspace: app.workspace({ branch: "run" }),
...optionalTools,
});
Complete agent and reviewer
The reviewer is a separate durable child thread. A deployment can assign it a different allowed provider or model. Parent and child messages use durable mailboxes, and their usage counts against the root limits.
import { app } from "./flary";
import { codingTools } from "./tools";
export const reviewer = app.agent({
name: "reviewer",
instructions: `
Review the current workspace diff.
Find concrete defects, missing tests, and unsafe changes.
Do not change files unless the parent asks you to.
`,
tools: codingTools,
});
export const coder = app.agent({
name: "coder",
instructions: `
Work like a careful coding agent:
1. Inspect the workspace before you change it.
2. Use grep and read to find the smallest correct change.
3. Edit files with workspace tools.
4. Run focused checks in the Sandbox when it is available.
5. Review the final diff and report the exact result.
`,
tools: codingTools,
subagents: { reviewer },
delegation: {
mode: "auto",
maxConcurrent: 2,
maxTotal: 4,
},
});
Serve the agents
app.serve() generates the thread, realtime, approval, audit, checkpoint, process, and
model-control routes. You do not write one API route per action.
import { Hono } from "hono";
import { cors } from "hono/cors";
import { app } from "./flary";
import { assistant } from "./assistant";
import { assistantConfig } from "./assistant.generated";
import { coder, reviewer } from "./coder";
import { generated } from "./flary.generated";
import { support } from "./support";
import { widgetDemo, widgetScript } from "./widget";
export const functions = { support, assistant, coder, reviewer };
const runtime = app.serve(functions);
const worker = new Hono();
worker.use(
"/apps/assistant/*",
cors({
origin: "*",
allowMethods: ["GET", "POST", "OPTIONS"],
allowHeaders: ["content-type", "x-flary-widget-session"],
}),
);
worker.get("/widget.js", (context) =>
generated.widget
? context.text(widgetScript(), 200, {
"content-type": "text/javascript; charset=utf-8",
"cache-control": "public, max-age=300",
"x-content-type-options": "nosniff",
})
: context.notFound(),
);
worker.get("/widget", (context) =>
generated.widget ? context.html(widgetDemo(assistantConfig.name)) : context.notFound(),
);
worker.route("/", runtime as never);
export default worker;
Start and reconnect from any UI
const thread = await api.coder.threads.create({
title: "Fix checkout",
});
await thread.send({
message: "Inspect the checkout failures, fix them, and run the checks.",
idempotencyKey: requestId,
});
for await (const event of thread.stream({ after: savedCursor })) {
render(event);
saveCursor(event.cursor);
}
The UI stores the thread ID and newest cursor. It does not resend the complete conversation. The same thread can continue from a web app, Telegram bot, Discord bot, mobile client, or another backend process.
After a completed turn, inspect or restore the immutable workspace checkpoint:
const { checkpoints } = await thread.checkpoints.list();
const latest = checkpoints[0];
const diff = await thread.checkpoints.diff({
headCommitId: latest.id,
});
await thread.checkpoints.restore(latest.id);
Git push, pull requests, destructive file changes, browser actions, and other sensitive operations remain subject to the application’s policy and approval UI.