Aimform

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.

On this page