Skip to main content

Building a Single Agent

A complete guide to building a production-ready agent with @agentdeploy/edge-sdk. Covers chat agents, tools, gateway integration, secrets, and scheduling.

Chat agent​

The most common pattern — a chat agent with streaming, tool calling, and message persistence:

import { createChatAgent, createHandler } from "@agentdeploy/edge-sdk";

export const SupportAgent = createChatAgent({
name: "support",
systemPrompt: `You are a helpful customer support agent.
Answer questions clearly and concisely.
If you don't know something, say so.`,
maxSteps: 10,
});

export default createHandler(SupportAgent);

That's it. The SDK handles:

  • WebSocket streaming via AIChatAgent (from @cloudflare/ai-chat)
  • Message persistence in embedded SQLite
  • LLM routing through the AgentDeploy gateway (billing + metering)
  • Health/provision endpoints auto-generated by createHandler

Adding tools​

Define tools with defineTool(), then pass them to the agent config:

import { createChatAgent, defineTool, createHandler } from "@agentdeploy/edge-sdk";
import { z } from "zod";

const checkOrderStatus = defineTool({
description: "Check the status of a customer's order",
inputSchema: z.object({
orderId: z.string().describe("The order ID to look up"),
}),
execute: async ({ orderId }, ctx) => {
// ctx.agent — the calling agent instance
// ctx.env — environment bindings (secrets, etc.)
// ctx.deploymentId — deployment ID

const response = await fetch(`https://api.myshop.com/orders/${orderId}`, {
headers: { Authorization: `Bearer ${ctx.env.SHOP_API_KEY}` },
});
const order = await response.json();
return { status: order.status, estimatedDelivery: order.eta };
},
});

const escalateTicket = defineTool({
description: "Escalate to a human agent",
inputSchema: z.object({
reason: z.string(),
priority: z.enum(["low", "medium", "high"]),
}),
needsApproval: true,
execute: async ({ reason, priority }, ctx) => {
await fetch(`${ctx.env.TICKETING_URL}/api/tickets`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ reason, priority, deploymentId: ctx.deploymentId }),
});
return { escalated: true, message: "A human agent will contact you shortly." };
},
});

export const SupportAgent = createChatAgent({
name: "support",
systemPrompt: `You are a customer support agent.
Use checkOrderStatus to look up orders.
Use escalateTicket only when the customer explicitly requests a human.`,
tools: { checkOrderStatus, escalateTicket },
maxSteps: 10,
});

export default createHandler(SupportAgent);

Using the LLM gateway​

createChatAgent automatically routes LLM calls through the gateway. For non-chat agents that need LLM calls (e.g., scheduled summarization), use useGateway() directly:

import { createAgent, useGateway, createHandler } from "@agentdeploy/edge-sdk";
import { generateText } from "ai";

export const ReportAgent = createAgent({
name: "reports",

async onSchedule(task) {
if (task.name === "generateSummary") {
const model = useGateway(); // Routes through AgentDeploy gateway

const result = await generateText({
model,
system: "Summarize the following metrics into a brief report.",
messages: [{ role: "user", content: JSON.stringify(task.payload) }],
});

// Token usage is automatically metered by the gateway
this.sql`
INSERT INTO reports (content, created_at)
VALUES (${result.text}, datetime('now'))
`;
}
},
});

export default createHandler(ReportAgent);

Accessing secrets​

Use useSecrets() for typed access to deployment secrets:

import { defineTool, useSecrets } from "@agentdeploy/edge-sdk";
import { z } from "zod";

interface MySecrets {
STRIPE_API_KEY: string;
DATABASE_URL: string;
}

const processPayment = defineTool({
description: "Process a payment via Stripe",
inputSchema: z.object({
amount: z.number().positive(),
currency: z.string().default("usd"),
}),
execute: async ({ amount, currency }, ctx) => {
const secrets = useSecrets<MySecrets>(ctx.env);

const response = await fetch("https://api.stripe.com/v1/payment_intents", {
method: "POST",
headers: {
Authorization: `Bearer ${secrets.STRIPE_API_KEY}`,
"Content-Type": "application/x-www-form-urlencoded",
},
body: new URLSearchParams({
amount: String(Math.round(amount * 100)),
currency,
}),
});

return await response.json();
},
});

Secrets are also accessible via ctx.env.KEY directly — useSecrets() adds typed error messages when a key is missing.

Scheduled tasks​

Use createAgent() with onStart to schedule recurring tasks:

import { createAgent, createHandler } from "@agentdeploy/edge-sdk";

export const MonitorAgent = createAgent({
name: "monitor",

onStart() {
// Schedule health check every 5 minutes
this.scheduleEvery("*/5 * * * *", "healthCheck");

// Schedule daily report at 9 AM
this.schedule("0 9 * * *", "dailyReport");
},

async onSchedule(task) {
if (task.name === "healthCheck") {
const services = ["api", "web", "worker"];
for (const svc of services) {
const res = await fetch(`https://${svc}.myapp.com/health`);
if (!res.ok) {
this.sql`
INSERT INTO incidents (service, status, detected_at)
VALUES (${svc}, ${res.status}, datetime('now'))
`;
}
}
this.setState({ lastCheck: new Date().toISOString() });
}

if (task.name === "dailyReport") {
const incidents = this.sql`
SELECT * FROM incidents WHERE detected_at > datetime('now', '-1 day')
`;
// Send report...
}
},

// HTTP endpoint for dashboard
onRequest(request) {
const url = new URL(request.url);
if (url.pathname === "/incidents") {
const rows = this.sql`SELECT * FROM incidents ORDER BY detected_at DESC LIMIT 50`;
return Response.json(rows);
}
return new Response("Monitor Agent. Use /incidents.", { status: 200 });
},
});

export default createHandler(MonitorAgent);

MCP integration​

Connect external MCP (Model Context Protocol) servers for additional tools:

import { createChatAgent, createHandler } from "@agentdeploy/edge-sdk";

export const ResearchAgent = createChatAgent({
name: "research",
systemPrompt: "You are a research assistant with web search and content analysis tools.",
mcpServers: [
{
name: "web-search",
transport: {
type: "sse",
url: "https://mcp.serper.dev/sse",
headers: { "X-API-KEY": "${SERPER_API_KEY}" },
},
},
],
maxSteps: 15,
});

export default createHandler(ResearchAgent);

MCP tools are merged with your locally-defined tools and passed to the LLM automatically.

Testing locally​

# Run with wrangler dev
ad dev

# Or directly
npx wrangler dev

The SDK's createHandler() generates /health and /info endpoints. Agent instances are accessible at:

ws://localhost:8787/agents/{agent-name}/{instance-id}

Use the AIChatAgent client or any WebSocket client to connect.

Secret manifest for this agent​

Declare your secrets in .agentdeploy/config.json:

{
"secrets": [
{ "key": "STRIPE_API_KEY", "required": true, "description": "Stripe API key" },
{ "key": "TICKETING_URL", "required": false, "description": "Ticketing system URL" }
]
}

When deploying with ad deploy, these are sent to the platform and shown to buyers during deployment setup.