Calling Agents from Routes — Agentuity Documentation

Calling Agents from Routes

Import and invoke agents from your routes

Routes import agents directly and call them using agent.run(). Use agent.validator() for type-safe request validation.

For guidance on when to use agents vs handling requests directly in routes, see Agents vs Routes.

Basic Agent Call

Import an agent and call it in your route:

import { createRouter } from '@agentuity/runtime';
import chat from '@agent/chat';
 
const router = createRouter();
 
router.post('/chat', async (c) => {
  const { message } = await c.req.json();
  const result = await chat.run({ message });
  return c.json(result);
});
 
export default router;

The agent receives validated input (if it has a schema) and returns typed output.

With Validation

Use agent.validator() for type-safe request validation:

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;

Multiple Agents

Import and use multiple agents in the same route file:

import { createRouter } from '@agentuity/runtime';
import chat from '@agent/chat';
import summarizer from '@agent/summarizer';
import teamMembers from '@agent/team/members';
 
const router = createRouter();
 
router.post('/process', async (c) => {
  const input = await c.req.json();
 
  // Call different agents
  const chatResult = await chat.run({ message: input.text });
  const summary = await summarizer.run({ content: input.text });
  const members = await teamMembers.run({ teamId: input.teamId });
 
  return c.json({ chatResult, summary, members });
});
 
export default router;

Parallel Agent Calls

Run multiple agents concurrently when they don't depend on each other:

import { createRouter } from '@agentuity/runtime';
import sentimentAnalyzer from '@agent/sentiment-analyzer';
import topicExtractor from '@agent/topic-extractor';
import summarizer from '@agent/summarizer';
 
const router = createRouter();
 
router.post('/analyze', async (c) => {
  const { content } = await c.req.json();
 
  // Run agents in parallel
  const [sentiment, topics, summary] = await Promise.all([
    sentimentAnalyzer.run({ text: content }),
    topicExtractor.run({ text: content }),
    summarizer.run({ content }),
  ]);
 
  return c.json({ sentiment, topics, summary });
});
 
export default router;

Background Agent Calls

Use c.waitUntil() to run agents after responding to the client:

import { createRouter } from '@agentuity/runtime';
import webhookProcessor from '@agent/webhook-processor';
 
const router = createRouter();
 
router.post('/webhook', async (c) => {
  const payload = await c.req.json();
 
  // Acknowledge immediately
  c.waitUntil(async () => {
    // Process in background
    await webhookProcessor.run(payload);
    c.var.logger.info('Webhook processed');
  });
 
  return c.json({ received: true });
});
 
export default router;

Error Handling

Wrap agent calls in try-catch for graceful error handling:

import { createRouter } from '@agentuity/runtime';
import chat from '@agent/chat';
 
const router = createRouter();
 
router.post('/safe-chat', async (c) => {
  const { message } = await c.req.json();
 
  try {
    const result = await chat.run({ message });
    return c.json({ success: true, result });
  } catch (error) {
    c.var.logger.error('Agent call failed', {
      agent: 'chat',
      error: error instanceof Error ? error.message : String(error),
    });
 
    return c.json(
      { success: false, error: 'Chat processing failed' },
      500
    );
  }
});
 
export default router;

Full Example: Multi-Endpoint API

Combine authentication, validation, and multiple agent calls:

import { createRouter } from '@agentuity/runtime';
import { createMiddleware } from 'hono/factory';
import { s } from '@agentuity/schema';
import chat from '@agent/chat';
import summarizer from '@agent/summarizer';
import sentimentAnalyzer from '@agent/sentiment-analyzer';
import entityExtractor from '@agent/entity-extractor';
 
const router = createRouter();
 
// Auth middleware
const authMiddleware = createMiddleware(async (c, next) => {
  const apiKey = c.req.header('X-API-Key');
  if (!apiKey) {
    return c.json({ error: 'API key required' }, 401);
  }
 
  const keyData = await c.var.kv.get('api-keys', apiKey);
  if (!keyData.exists) {
    return c.json({ error: 'Invalid API key' }, 401);
  }
 
  c.set('userId', keyData.data.userId);
  await next();
});
 
// Apply auth to all routes
router.use('/*', authMiddleware);
 
// Chat endpoint - uses chat agent's schema
router.post('/chat', chat.validator(), async (c) => {
  const userId = c.var.userId;
  const data = c.req.valid('json');
 
  const result = await chat.run({
    ...data,
    userId,
  });
 
  // Track usage in background
  c.waitUntil(async () => {
    await c.var.kv.set('usage', `${userId}:${Date.now()}`, {
      endpoint: 'chat',
      tokens: result.tokensUsed,
    });
  });
 
  return c.json(result);
});
 
// Summarization endpoint - uses summarizer's schema
router.post('/summarize', summarizer.validator(), async (c) => {
  const data = c.req.valid('json');
  const result = await summarizer.run(data);
  return c.json(result);
});
 
// Multi-agent analysis - uses custom schema
router.post('/analyze',
  sentimentAnalyzer.validator({
    input: s.object({ content: s.string() }),
  }),
  async (c) => {
    const { content } = c.req.valid('json');
 
    // Run multiple agents in parallel
    const [sentiment, entities, summary] = await Promise.all([
      sentimentAnalyzer.run({ text: content }),
      entityExtractor.run({ text: content }),
      summarizer.run({ content, maxLength: 100 }),
    ]);
 
    return c.json({
      sentiment: sentiment.score,
      entities: entities.items,
      summary: summary.text,
    });
  }
);
 
export default router;

Type Safety

If your agents have schemas, TypeScript provides full type checking. The route's chat.run() return type is inferred directly from the agent's output schema:

typescriptsrc/agent/chat/agent.ts
import { createAgent } from '@agentuity/runtime';
import { s } from '@agentuity/schema';
 
export default createAgent('Chat', {
  schema: {
    input: s.object({ message: s.string() }),
    output: s.object({ response: s.string(), tokensUsed: s.number() }),
  },
  handler: async (ctx, input) => { ... },
});
typescriptsrc/api/chat/route.ts
import { createRouter } from '@agentuity/runtime';
import chat from '@agent/chat';
 
const router = createRouter();
 
router.post('/chat', async (c) => {
  const result = await chat.run({ message: 'Hello' });
  // result is typed as { response: string, tokensUsed: number }
  return c.json({ text: result.response });
});
 
export default router;

Next Steps