The SDK provides a Hono-based routing system for creating HTTP endpoints. Routes are defined in src/api/index.ts and provide full control over HTTP request handling.
Creating Routes
Routes are created using the createRouter() function from @agentuity/server.
Basic Setup:
// src/api/index.ts
import { createRouter } from '@agentuity/runtime';
const router = createRouter();
// Define routes
router.get('/', (c) => {
return c.json({ message: 'Hello from route' });
});
export default router;Router Context
Router handlers receive a context parameter (typically c) that provides access to the request, response helpers, and Agentuity services. This Hono Context is distinct from the AgentContext type used in agent handlers.
Understanding Context Types
Agentuity uses two distinct context types based on where you're writing code:
- AgentContext: Used in
agent.tsfiles for business logic (no HTTP access) - Router Context (Hono): Used in
src/api/index.tsfor HTTP handling (has HTTP + agent services)
Both commonly use c as the variable name in SDK examples. The distinction is type-based, not name-based.
Both contexts provide access to Agentuity services, but through different access patterns.
Router Context Interface:
interface RouterContext {
// Request
req: Request; // Hono request object with .param(), .query(), .header(), .json()
// Agentuity Services (via c.var)
var: {
kv: KeyValueStorage; // Key-value storage
vector: VectorStorage; // Vector storage
stream: StreamStorage; // Stream storage
queue: QueueService; // Message queues
task: TaskStorage; // Task tracking
email: EmailService; // Email sending and receiving
schedule: ScheduleService; // Scheduled tasks
sandbox: SandboxService; // Isolated code execution
logger: Logger; // Structured logging
tracer: Tracer; // OpenTelemetry tracing
};
// Response Helpers
json(data: any, status?: number): Response;
text(text: string, status?: number): Response;
html(html: string, status?: number): Response;
redirect(url: string, status?: number): Response;
// ... other Hono response methods
}Key Differences Between Context Types:
| Feature | Router Context (Hono) | Agent Context |
|---|---|---|
| Type | Hono Context | AgentContext |
| Used in | src/api/index.ts | agent.ts files |
| Request access | c.req (Hono Request) | Direct input parameter (validated) |
| Response | Builder methods (.json(), .text()) | Direct returns |
| Services | c.var.kv, c.var.logger, etc. | ctx.kv, ctx.logger, etc. |
| Agent calling | Import and call: agent.run() | Import and call: agent.run() |
| State management | Via Hono middleware | Built-in (.state, .session, .thread) |
Example Usage:
import processor from '@agent/processor';
router.post('/process', processor.validator(), async (c) => {
// Access request
const body = c.req.valid('json');
const authHeader = c.req.header('Authorization');
// Use Agentuity services
c.var.logger.info('Processing request', { body });
// Call an agent
const result = await processor.run({ data: body.data });
// Store result
await c.var.kv.set('results', body.id, result);
// Return response
return c.json({ success: true, result });
});Accessing Services
Agentuity services (storage, logging, tracing) are available in multiple contexts. The API is identical; only the access pattern differs.
Quick Reference
| Service | In Agents | In Routes | In Standalone |
|---|---|---|---|
| Key-Value | ctx.kv | c.var.kv | ctx.kv |
| Vector | ctx.vector | c.var.vector | ctx.vector |
| Streams | ctx.stream | c.var.stream | ctx.stream |
| Queue | ctx.queue | c.var.queue | ctx.queue |
| Tasks | ctx.task | c.var.task | ctx.task |
ctx.email | c.var.email | ctx.email | |
| Schedule | ctx.schedule | c.var.schedule | ctx.schedule |
| Sandbox | ctx.sandbox | c.var.sandbox | ctx.sandbox |
| Logger | ctx.logger | c.var.logger | ctx.logger |
| Tracer | ctx.tracer | c.var.tracer | ctx.tracer |
| State | ctx.state | N/A | ctx.state |
| Thread | ctx.thread | c.var.thread | ctx.thread |
| Session | ctx.session | c.var.session | ctx.session |
From Agents
import { createAgent } from '@agentuity/runtime';
export default createAgent('cache-manager', {
handler: async (ctx, input) => {
await ctx.kv.set('cache', 'key', { data: 'value' });
ctx.logger.info('Data cached');
return { success: true };
},
});From Routes
import { createRouter } from '@agentuity/runtime';
const router = createRouter();
router.post('/cache', async (c) => {
await c.var.kv.set('cache', 'key', { data: 'value' });
c.var.logger.info('Data cached');
return c.json({ success: true });
});
export default router;From Standalone Code
For Discord bots, CLI tools, or queue workers running within the Agentuity runtime:
import { createApp, createAgentContext } from '@agentuity/runtime';
await createApp();
const ctx = createAgentContext();
await ctx.invoke(async () => {
await ctx.kv.set('cache', 'key', { data: 'value' });
ctx.logger.info('Data cached from standalone context');
});See Running Agents Without HTTP for Discord bots, CLI tools, and queue worker patterns.
From External Backends (Next.js, Express)
External backends cannot access Agentuity services directly. Create authenticated routes that expose storage operations, then call them via HTTP:
import { createRouter } from '@agentuity/runtime';
const router = createRouter();
router.get('/:id', async (c) => {
const result = await c.var.kv.get('sessions', c.req.param('id'));
return result.exists ? c.json(result.data) : c.json({ error: 'Not found' }, 404);
});
export default router;const AGENTUITY_URL = process.env.AGENTUITY_URL!;
const API_KEY = process.env.STORAGE_API_KEY!;
export async function getSession(id: string) {
const res = await fetch(`${AGENTUITY_URL}/api/sessions/${id}`, {
headers: { 'x-api-key': API_KEY },
});
if (!res.ok) return null;
return res.json();
}See SDK Utilities for External Apps for the complete pattern with authentication.
From Frontend
Frontend code accesses services through routes using useAPI or fetch:
import { useAPI } from '@agentuity/react';
function SessionView({ id }: { id: string }) {
const { data, isLoading } = useAPI(`GET /api/sessions/${id}`);
if (isLoading) return <div>Loading...</div>;
return <div>{data?.message}</div>;
}See React Hooks for useAPI, useWebsocket, and useEventStream.
HTTP Methods
The router supports all standard HTTP methods.
GET Requests:
router.get('/users', (c) => {
return c.json({ users: [] });
});
router.get('/users/:id', (c) => {
const id = c.req.param('id');
return c.json({ userId: id });
});POST Requests:
router.post('/users', async (c) => {
const body = await c.req.json();
return c.json({ created: true, user: body });
});PUT, PATCH, DELETE:
router.put('/users/:id', async (c) => {
const id = c.req.param('id');
const body = await c.req.json();
return c.json({ updated: true, id, data: body });
});
router.patch('/users/:id', async (c) => {
const id = c.req.param('id');
const updates = await c.req.json();
return c.json({ patched: true, id, updates });
});
router.delete('/users/:id', (c) => {
const id = c.req.param('id');
return c.json({ deleted: true, id });
});Calling Agents from Routes:
Import agents and call them directly:
import processorAgent from '@agent/processor';
router.post('/process', processorAgent.validator(), async (c) => {
const input = c.req.valid('json');
// Call the agent
const result = await processorAgent.run({
data: input.data,
});
return c.json(result);
});Specialized Routes
The router provides specialized route handlers for non-HTTP triggers like WebSockets, scheduled jobs, and real-time communication.
WebSocket Routes
Create a WebSocket endpoint for real-time bidirectional communication using the websocket middleware.
Import:
import { websocket } from '@agentuity/runtime';Handler Signature:
type WebSocketHandler = (c: Context, ws: WebSocketConnection) => void | Promise<void>;
interface WebSocketConnection {
onOpen(handler: (event: any) => void | Promise<void>): void;
onMessage(handler: (event: any) => void | Promise<void>): void;
onClose(handler: (event: any) => void | Promise<void>): void;
send(data: string | ArrayBuffer | Uint8Array): void;
}Example:
import { createRouter, websocket } from '@agentuity/runtime';
import chatAgent from '@agent/chat';
const router = createRouter();
router.get('/chat', websocket((c, ws) => {
ws.onOpen((event) => {
c.var.logger.info('WebSocket connected');
ws.send(JSON.stringify({ type: 'connected' }));
});
ws.onMessage(async (event) => {
const message = JSON.parse(event.data);
// Process message with agent
const response = await chatAgent.run({
message: message.text,
});
ws.send(JSON.stringify({ type: 'response', data: response }));
});
ws.onClose((event) => {
c.var.logger.info('WebSocket disconnected');
});
}));
export default router;Server-Sent Events (SSE)
Create a Server-Sent Events endpoint for server-to-client streaming using the sse middleware.
Import:
import { sse } from '@agentuity/runtime';Handler Signature:
type SSEHandler = (c: Context, stream: SSEStream) => void | Promise<void>;
interface SSEStream {
write(data: string | number | boolean | object): Promise<void>;
writeSSE(message: { data?: string; event?: string; id?: string }): Promise<void>;
onAbort(handler: () => void): void;
close?(): void;
}Example:
import { createRouter, sse } from '@agentuity/runtime';
import longRunningAgent from '@agent/long-running';
const router = createRouter();
router.get('/updates', sse(async (c, stream) => {
// Send initial connection message
await stream.write({ type: 'connected' });
// Stream agent progress updates
const updates = await longRunningAgent.run({ task: 'process' });
for (const update of updates) {
await stream.write({
type: 'progress',
data: update,
});
}
// Clean up on client disconnect
stream.onAbort(() => {
c.var.logger.info('Client disconnected');
});
}));
export default router;Stream Routes
Create an HTTP streaming endpoint for piping data streams using the stream middleware.
Import:
import { stream } from '@agentuity/runtime';Handler Signature:
type StreamHandler = (c: Context) => ReadableStream<any> | Promise<ReadableStream<any>>;Example:
import { createRouter, stream } from '@agentuity/runtime';
import dataGenerator from '@agent/data-generator';
const router = createRouter();
router.post('/data', stream(async (c) => {
// Create a readable stream
const readableStream = new ReadableStream({
async start(controller) {
// Stream data chunks
const data = await dataGenerator.run({ query: 'all' });
for (const chunk of data) {
controller.enqueue(new TextEncoder().encode(JSON.stringify(chunk) + '\n'));
}
controller.close();
},
});
return readableStream;
}));
export default router;For streaming agent outputs, see Streaming Responses.
Cron Routes
Schedule recurring jobs using cron syntax with the cron middleware.
Import:
import { cron } from '@agentuity/runtime';Handler Signature:
type CronHandler = (c: Context) => any | Promise<any>;Cron Schedule Format:
┌───────────── minute (0 - 59)
│ ┌───────────── hour (0 - 23)
│ │ ┌───────────── day of month (1 - 31)
│ │ │ ┌───────────── month (1 - 12)
│ │ │ │ ┌───────────── day of week (0 - 6) (Sunday = 0)
│ │ │ │ │
* * * * *
Example:
import { createRouter, cron } from '@agentuity/runtime';
import reportGenerator from '@agent/report-generator';
import healthCheck from '@agent/health-check';
const router = createRouter();
// Run daily at 9am
router.post('/daily-report', cron('0 9 * * *', async (c) => {
c.var.logger.info('Running daily report');
const report = await reportGenerator.run({
type: 'daily',
date: new Date().toISOString(),
});
// Store report in KV
await c.var.kv.set('reports', `daily-${Date.now()}`, report);
return c.json({ success: true });
}));
// Run every 5 minutes
router.post('/health-check', cron('*/5 * * * *', async (c) => {
await healthCheck.run({});
return c.json({ checked: true });
}));
export default router;For platform-managed schedules with delivery tracking and retry, see Schedules.
Route Parameters
Access route parameters and query strings through the request object.
Path Parameters:
router.get('/posts/:postId/comments/:commentId', (c) => {
const postId = c.req.param('postId');
const commentId = c.req.param('commentId');
return c.json({ postId, commentId });
});Query Parameters:
router.get('/search', (c) => {
const query = c.req.query('q');
const limit = c.req.query('limit') || '10';
const page = c.req.query('page') || '1';
return c.json({
query,
limit: parseInt(limit),
page: parseInt(page)
});
});Request Headers:
router.get('/protected', (c) => {
const authHeader = c.req.header('Authorization');
if (!authHeader) {
return c.json({ error: 'Unauthorized' }, 401);
}
return c.json({ authorized: true });
});