Cloudflare Deployment
Cloudflare is the recommended platform — Workers, Workflows, and Durable Objects provide the full stack natively.
Architecture
Browser ──WebSocket──▶ WorkflowChaperoneAgent (Durable Object)
▲
│ @callable() RPC
│
POST /messages ──▶ Worker ──▶ ChatWorkflow (Cloudflare Workflow)
│ │
│ ├─ DeepSeek/OpenAI API
│ ├─ D1 Database
│ └─ R2 Storage
│
└──▶ Response: { runId }
wrangler.toml
name = "aimform-identity"
[[durable_objects.bindings]]
name = "WORKFLOW_CHAPERONE_AGENT"
class_name = "WorkflowChaperoneAgent"
[[workflows]]
name = "CHAT_WORKFLOW"
class_name = "ChatWorkflow"
[[d1_databases]]
binding = "DB"
database_name = "aimform-db"
database_id = "xxx"
[[r2_buckets]]
binding = "FILES"
bucket_name = "aimform-files"
[vars]
DEEPSEEK_API_KEY = "" # set via wrangler secret
Worker Handler
import { AI, AgentsSDKTransportAdapter } from "@aimform/ai";
import { D1DatabaseAdapter } from "@aimform/core";
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const ai = new AI({
model: "deepseek-v4-flash",
apiKeys: { deepseekApiKey: env.DEEPSEEK_API_KEY },
db: new D1DatabaseAdapter(env.DB),
transport: new AgentsSDKTransportAdapter(
env.WORKFLOW_CHAPERONE_AGENT,
"https://api.aimform.com",
),
enableMemory: true,
plugins: {
files: new CloudflareR2Plugin({ bucket: env.FILES }),
ocr: new CloudflareOCRPlugin(env.AI),
webSearch: new TavilySearchPlugin(env.TAVILY_API_KEY),
},
});
// ... handle routes
},
};
WorkflowChaperoneAgent
import { Agent, callable } from "agents";
export class WorkflowChaperoneAgent extends Agent<Env> {
@callable()
async publishEvents(events: AnyChatEvent[]) {
// Buffer events and broadcast to WebSocket clients
}
@callable()
async getReplayState(runId: string) {
// Return accumulated state for late joiners
}
}
ChatWorkflow
import { WorkflowEntrypoint } from "cloudflare:workers";
import { createBroadcastFn } from "@aimform/ai";
export class ChatWorkflow extends WorkflowEntrypoint<Env, Params> {
async run(event, step) {
const transport = new AgentsSDKTransportAdapter(/* ... */);
const broadcast = createBroadcastFn(transport, runId);
await step.do("generate", async () => {
// Call LLM, execute tools, broadcast events
await broadcast({ type: "text_delta", delta: "Hello!", ... });
});
await step.do("finalize", async () => {
await transport.markComplete(runId, result);
});
}
}
Deploy
wrangler deploy
The Workflow runs in the background — even if the user closes their browser, the task continues and results are persisted to D1. Reopening the page replays the completed conversation.