Router and Routes — Agentuity Documentation

Router and Routes

HTTP endpoints, middleware, WebSocket, SSE, and cron handlers

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.ts files for business logic (no HTTP access)
  • Router Context (Hono): Used in src/api/index.ts for 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:

FeatureRouter Context (Hono)Agent Context
TypeHono ContextAgentContext
Used insrc/api/index.tsagent.ts files
Request accessc.req (Hono Request)Direct input parameter (validated)
ResponseBuilder methods (.json(), .text())Direct returns
Servicesc.var.kv, c.var.logger, etc.ctx.kv, ctx.logger, etc.
Agent callingImport and call: agent.run()Import and call: agent.run()
State managementVia Hono middlewareBuilt-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

ServiceIn AgentsIn RoutesIn Standalone
Key-Valuectx.kvc.var.kvctx.kv
Vectorctx.vectorc.var.vectorctx.vector
Streamsctx.streamc.var.streamctx.stream
Queuectx.queuec.var.queuectx.queue
Tasksctx.taskc.var.taskctx.task
Emailctx.emailc.var.emailctx.email
Schedulectx.schedulec.var.schedulectx.schedule
Sandboxctx.sandboxc.var.sandboxctx.sandbox
Loggerctx.loggerc.var.loggerctx.logger
Tracerctx.tracerc.var.tracerctx.tracer
Statectx.stateN/Actx.state
Threadctx.threadc.var.threadctx.thread
Sessionctx.sessionc.var.sessionctx.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:

typescriptAgentuity route: src/api/sessions/route.ts
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;
typescriptNext.js: lib/agentuity.ts
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 });
});