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 |
| Evaluations (quality checks after execution) | Webhook signature verification |
| Agent-to-agent calling with type safety | Simple CRUD operations |
| Lifecycle events (started, completed, error) | Streaming protocols (WebSocket, SSE) |
| Thread/session state persistence | Cron-triggered cleanup jobs |
What's the Same
Agents and routes share the same platform services. Everything available through ctx.* in an agent is also available through 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 |
| Object storage | ctx.storage | c.var.storage |
| 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 |
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 - Evaluations: Attach quality checks that run after every execution
- Lifecycle events: Hook into
started,completed, anderrorevents for monitoring - Agent-to-agent calling: Call other agents with full type safety via
agent.run() - Thread state: Persist conversation context across multiple calls
If you need any of these, create an agent.
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 { createRouter } from '@agentuity/runtime';
const router = createRouter();
// 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 { createRouter } from '@agentuity/runtime';
import chat from '@agent/chat';
const router = createRouter();
router.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 { createRouter } from '@agentuity/runtime';
import paymentProcessor from '@agent/payment-processor';
const router = createRouter();
// Replace with your actual Stripe signature verification
const verifyStripeSignature = (body: string, sig: string | undefined) => true;
router.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 agent work when the client doesn't need the result - Prefer agents when you need schema validation, evals, or agent-to-agent calling
Next Steps
- Creating Agents: Build agents with schemas, handlers, and evals
- 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