When to Use Agents vs Routes — Agentuity Documentation

When to Use Agents vs Routes

When to create an agent vs handling requests directly in routes

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 validationHealth checks, status endpoints
Evaluations (quality checks after execution)Webhook signature verification
Agent-to-agent calling with type safetySimple CRUD operations
Lifecycle events (started, completed, error)Streaming protocols (WebSocket, SSE)
Thread/session state persistenceCron-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:

ServiceIn an agent (ctx)In a route (c.var)
Key-Value storagectx.kvc.var.kv
Vector searchctx.vectorc.var.vector
Object storagectx.storagec.var.storage
Loggerctx.loggerc.var.logger
Tracerctx.tracerc.var.tracer
Queuectx.queuec.var.queue
Emailctx.emailc.var.email
Sandboxctx.sandboxc.var.sandbox

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, and error events 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;

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