Both agents and routes live in your Agentuity project. Routes handle HTTP requests directly. Agents add schema validation, lifecycle hooks, and typed inter-agent calls.
Quick Decision Guide
| Create an agent when you need... | Handle directly in a route when... |
|---|---|
| Input and output schema validation | Health checks, status endpoints |
| Post-response quality checks | Webhook signature verification |
| Agent-to-agent calling with type safety | Simple CRUD operations |
Lifecycle events (started, completed, errored) | Streaming protocols (WebSocket, SSE) |
| Reusable processing you want to call from routes, jobs, or workers | Cron-triggered cleanup jobs |
What's the Same
Agents and routes share the same built-in platform services. These services are available through ctx.* in an agent and c.var.* in a route:
| Service | In an agent (ctx) | In a route (c.var) |
|---|---|---|
| Key-Value storage | ctx.kv | c.var.kv |
| Vector search | ctx.vector | c.var.vector |
| Durable streams | ctx.stream | c.var.stream |
| Logger | ctx.logger | c.var.logger |
| Tracer | ctx.tracer | c.var.tracer |
| Queue | ctx.queue | c.var.queue |
ctx.email | c.var.email | |
| Sandbox | ctx.sandbox | c.var.sandbox |
| Schedule | ctx.schedule | c.var.schedule |
| Task | ctx.task | c.var.task |
Database and object storage use Bun's native sql and s3 APIs instead of ctx.* or c.var.*.
ctx.kv.set('bucket', 'key', value) in an agent does the same thing as c.var.kv.set('bucket', 'key', value) in a route. If your logic only needs these services, a route works fine on its own.
What's Different
These capabilities are agent-only:
- Schema validation: Define input and output schemas. Use
agent.validator()as route middleware for automatic request validation - Quality checks: Add post-response validation when you need to score or review outputs
- Lifecycle events: Hook into
started,completed, anderroredevents for monitoring - Agent-to-agent calling: Call other agents with full type safety via
agent.run() - Reusable logic across entry points: Run the same processing flow from routes, background jobs, or standalone scripts
If you need any of these, create an agent.
Routes can still use thread and session context through c.var.thread and c.var.session when you need stateful request handling.
How They Work Together
Routes are the HTTP layer, agents are the processing layer. Most applications use both:
Request → Route (auth, validation) → Agent (processing) → Route (response formatting)
A route receives the HTTP request, applies middleware (authentication, rate limiting), then delegates to an agent for structured processing. The agent returns typed output, and the route formats the HTTP response.
Basic Usage
Route-Only: Health Check
A health endpoint doesn't need an agent:
import { Hono } from 'hono';
import type { Env } from '@agentuity/runtime';
const router = new Hono<Env>();
// Replace with your actual database health check
const checkDatabase = async () => true;
router.get('/health', async (c) => {
const dbHealthy = await checkDatabase();
return c.json({ status: dbHealthy ? 'healthy' : 'degraded' });
});
export default router;Route + Agent: Chat Endpoint
For structured processing, create an agent and call it from your route:
import { Hono } from 'hono';
import type { Env } from '@agentuity/runtime';
import chat from '@agent/chat/agent';
const router = new Hono<Env>()
.post('/chat', chat.validator(), async (c) => {
const data = c.req.valid('json'); // Fully typed from agent schema
const result = await chat.run(data);
return c.json(result);
});
export default router;The agent defines the schema, validates input, runs the handler, and fires lifecycle events. The route just wires it to HTTP.
Webhook: Route Validates, Agent Processes
Webhooks verify signatures in the route, then hand off to an agent in the background:
import { Hono } from 'hono';
import type { Env } from '@agentuity/runtime';
import paymentProcessor from '@agent/payment-processor/agent';
// Replace with your actual Stripe signature verification
const verifyStripeSignature = (body: string, sig: string | undefined) => true;
const router = new Hono<Env>()
.post('/webhooks/stripe', async (c) => {
const signature = c.req.header('stripe-signature');
const rawBody = await c.req.text();
if (!verifyStripeSignature(rawBody, signature)) {
return c.json({ error: 'Invalid signature' }, 401);
}
// Respond fast, process in background
c.waitUntil(async () => {
await paymentProcessor.run(JSON.parse(rawBody));
});
return c.json({ received: true });
});
export default router;c.waitUntil() lets you respond immediately while the agent processes in the background. Webhook providers expect responses under 3 seconds.
Agent-Only: Standalone Execution
Agents can also run outside of HTTP requests, triggered by other agents, cron jobs, or queue consumers:
import { createAgent } from '@agentuity/runtime';
import { s } from '@agentuity/schema';
export default createAgent('daily-report', {
schema: {
input: s.object({ date: s.string() }),
output: s.object({ report: s.string(), metrics: s.number() }),
},
handler: async (ctx, input) => {
const data = await ctx.kv.get('metrics', input.date);
const summary = `Report for ${input.date}: ${JSON.stringify(data)}`;
return { report: summary, metrics: 42 };
},
});See Standalone Execution for running agents from cron, queues, and other agents.
Best Practices
- Handle authentication and rate limiting in routes, not agents
- Use
agent.validator()as route middleware for type-safe request validation - Keep agents focused on processing logic, let routes handle HTTP concerns
- Use
c.waitUntil()in routes for background work when the client doesn't need the result - Prefer agents when you need schema validation, post-response quality checks, or agent-to-agent calling
Next Steps
- Creating Agents: Build agents with schemas and focused handlers
- HTTP Routes: Handle requests that don't need agent processing
- Calling Agents from Routes: Import and invoke agents with validation
- Middleware: Add auth and rate limiting in front of agents