How OpenSEO Integrates with the Agents SDK for AI-Driven Workflows
OpenSEO leverages Cloudflare's Agents SDK through a three-layer architecture that routes HTTP requests to Durable Objects, implements conversational AI with tool calling, and provides React hooks for real-time streaming interfaces.
The every-app/open-seo repository demonstrates a production-ready integration between an SEO platform and Cloudflare's Agents SDK. This architecture enables persistent, stateful AI conversations that can call external tools and survive page reloads while maintaining project-specific context.
Three-Layer Architecture Overview
The integration follows a clear separation of concerns across three distinct layers:
- HTTP Routing Layer: Intercepts
/agents/*requests and forwards them to Durable Objects usingrouteAgentRequestwith custom authorization hooks - Durable Object Layer: Implements the conversational logic, LLM streaming, and tool execution within
OnboardingChatAgentwhile storing history in SQLite - Client-Side Layer: Provides React hooks (
useAgent,useAgentChat) that bind UI components to the persistent Durable Object instance keyed by project ID
HTTP Routing with routeAgentRequest
In src/server.ts, the Cloudflare Worker detects paths beginning with /agents/ and delegates to the SDK's routeAgentRequest function. This handler manages WebSocket upgrades, locates the appropriate Durable Object by name (using the project ID as the instance identifier), and establishes bidirectional streams.
The integration injects custom authorization hooks (onBeforeConnect and onBeforeRequest) that verify organization-level permissions before the connection reaches the Durable Object.
// Forward every /agents/* request to a Durable Object that implements the chat.
if (pathname.startsWith("/agents/")) {
return routeOnboardingChatAgent(publicRequest, env);
}
// ...
async function routeOnboardingChatAgent(
request: Request,
env: Env,
): Promise<Response> {
const response = await routeAgentRequest(request, env, {
// Authorize the user before any WS connection reaches the DO.
onBeforeConnect: (req, lobby) => authorizeOnboardingChat(req, lobby.name),
onBeforeRequest: (req, lobby) => authorizeOnboardingChat(req, lobby.name),
});
return response ?? new Response("Not found", { status: 404 });
}
The routing uses routeAgentRequest from the Agents SDK and injects custom auth hooks to protect the Durable Object endpoints.
Durable Object Agent Implementation
The OnboardingChatAgent class in src/server/features/onboarding/OnboardingChatAgent.ts extends AIChatAgent from the SDK. It defines the system prompt, registers a ToolSet containing read_website, get_seo_metrics, and research_keywords, and handles billing-gate checks through the Autumn subscription service.
The Durable Object stores chat history in its internal SQLite database, guaranteeing persistence across reloads. Before each turn, the agent checks the organization's credit balance; if the free-question cap is exceeded, it returns a static assistant message.
export class OnboardingChatAgent extends AIChatAgent {
maxPersistedMessages = 60; // cap history
async onChatMessage(
onFinish: StreamTextOnFinishCallback<ToolSet>,
options?: OnChatMessageOptions,
): Promise<Response | undefined> {
// ... billing checks omitted for brevity ...
const model = await getOnboardingModel();
const result = streamText({
model,
system: buildSystemPrompt(project.domain),
messages: await convertToModelMessages(this.messages),
abortSignal: options?.abortSignal,
maxOutputTokens: 1600,
stopWhen: stepCountIs(5),
onFinish: async (event) => {
// Meter LLM cost against Autumn credits.
if (creditCustomerId) {
const costUsd = event.steps.reduce(
(sum, step) => sum + openRouterCostUsd(step.providerMetadata),
0,
);
await trackUsageCreditSpend({
customer: billingCustomer,
customerId: creditCustomerId,
creditFeature: "onboarding",
costUsd,
monthlyRemaining,
properties: { provider: "openrouter" },
});
}
await onFinish(event); // persist turn in DO SQLite
},
tools: {
read_website: tool({ /* ... */ }),
get_seo_metrics: tool({ /* ... */ }),
research_keywords: tool({ /* ... */ }),
} as ToolSet,
});
return result.toUIMessageStreamResponse({
onError: (error) => "The assistant hit an error. Please try again.",
});
}
}
The Durable Object extends AIChatAgent, registers SEO-specific tools, streams LLM output through streamText, and records usage costs against the organization's credit balance.
Client-Side React Integration
On the frontend, src/client/features/onboarding/OnboardingChatConversation.tsx uses the useAgent hook to create a typed handle pointing at the Durable Object instance (name: projectId). The useAgentChat hook provides a streaming chat API that exposes messages, sendMessage, and status.
The component renders ToolBadge components to display progress for each tool call, keeping users informed while the Durable Object performs network calls (e.g., to DataForSEO).
export function OnboardingChatConversation({ projectId, domain }: {
projectId: string;
domain: string;
}) {
// Bind the React component to the Durable Object instance.
const agent = useAgent({ agent: "onboarding-chat", name: projectId });
const { messages, sendMessage, status } = useAgentChat({ agent });
// Send a user message to the DO.
const sendText = (text: string) => void sendMessage({ text });
// UI renders messages, tools, typing indicator, etc.
return (
<div className="flex min-h-0 flex-1">
{/* ... UI omitted for brevity ... */}
<ChatComposer busy={status === "submitted" || status === "streaming"}
onSend={sendText} />
</div>
);
}
The useAgent hook creates a handle to the Durable Object, while useAgentChat provides a streaming chat API that respects the Agents SDK's message format and connection state.
Extending AI Workflows Beyond Onboarding
Because the Durable Object follows the Agents SDK contract, OpenSEO exposes additional workflows using the same pattern. The src/server.ts file also exports SiteAuditWorkflow and RankCheckWorkflow as Agent-compatible Durable Objects. Any new workflow can be exposed by routing "/agents/<name>" to a new Durable Object class that extends the SDK's base classes, enabling rapid expansion of AI-driven SEO automation.
Summary
- HTTP Routing:
src/server.tsusesrouteAgentRequestto handle/agents/*paths, upgrade connections to WebSockets, and injectonBeforeConnectauthorization hooks before reaching the Durable Object - Stateful Agents:
OnboardingChatAgentextendsAIChatAgentto implement streaming LLM responses, tool calling (read_website,get_seo_metrics,research_keywords), and credit-based billing gates through Autumn - Persistence: Chat history survives reloads through the Durable Object's built-in SQLite storage, with the project ID serving as the unique instance name for isolation
- React Integration:
useAgentanduseAgentChathooks provide type-safe connections to Durable Objects, handling real-time message streaming and tool execution UI withToolBadgecomponents - Extensibility: The architecture supports multiple AI workflows (e.g.,
SiteAuditWorkflow,RankCheckWorkflow) through the same SDK primitives without reimplementing WebSocket or persistence logic
Frequently Asked Questions
What is the Agents SDK's role in OpenSEO?
The Agents SDK abstracts low-level WebSocket handling, Durable Object lifecycle management, and LLM streaming protocols. This allows OpenSEO to focus on domain-specific SEO prompts, tool implementations against DataForSEO, and billing logic rather than infrastructure plumbing.
How does authentication work with the Agents SDK integration?
OpenSEO implements onBeforeConnect and onBeforeRequest callbacks within the routeAgentRequest configuration in src/server.ts. These hooks call authorizeOnboardingChat to perform organization-level authorization before the WebSocket connection reaches the Durable Object, ensuring only authorized users access specific project chats.
How is chat history persisted across sessions?
The Durable Object stores conversation history in its internal SQLite database via the SDK's persistence layer. Because each project uses its project ID as the Durable Object instance name, returning users automatically reconnect to the same stateful agent instance with full message history intact.
Can developers add custom AI workflows beyond onboarding?
Yes. OpenSEO already exposes SiteAuditWorkflow and RankCheckWorkflow using the same pattern. Developers can create new Durable Object classes extending AIChatAgent, implement the onChatMessage method with custom tools, and route new /agents/<workflow-name> endpoints in src/server.ts to enable additional AI-driven workflows.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →