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
Post-response quality checksWebhook signature verification
Agent-to-agent calling with type safetySimple CRUD operations
Lifecycle events (started, completed, errored)Streaming protocols (WebSocket, SSE)
Reusable processing you want to call from routes, jobs, or workersCron-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:

ServiceIn an agent (ctx)In a route (c.var)
Key-Value storagectx.kvc.var.kv
Vector searchctx.vectorc.var.vector
Durable streamsctx.streamc.var.stream
Loggerctx.loggerc.var.logger
Tracerctx.tracerc.var.tracer
Queuectx.queuec.var.queue
Emailctx.emailc.var.email
Sandboxctx.sandboxc.var.sandbox
Schedulectx.schedulec.var.schedule
Taskctx.taskc.var.task

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

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