Calling App Code

Call shared workflow functions from routes, workers, scripts, and other server code

In framework-first apps, "calling another agent" usually means calling ordinary TypeScript. Put the reusable work in a function, import it from the route or worker that owns the trigger, and use Agentuity clients inside that function when it needs service state.

Shared Function

npm install @agentuity/keyvalue zod
typescriptsrc/workflows/enrich-message.ts
import { KeyValueClient } from '@agentuity/keyvalue';
import { z } from 'zod';
 
const inputSchema = z.object({
  userId: z.string(),
  message: z.string(),
});
 
const outputSchema = z.object({
  userId: z.string(),
  previousMessage: z.string().nullable(),
  message: z.string(),
});
 
type EnrichOutput = z.infer<typeof outputSchema>;
 
const kv = new KeyValueClient();
 
export async function enrichMessage(body: unknown): Promise<EnrichOutput> {
  const input = inputSchema.parse(body);
  const previous = await kv.get<string>('messages', input.userId);
 
  const output = {
    userId: input.userId,
    previousMessage: previous.exists ? previous.data : null,
    message: input.message,
  } satisfies EnrichOutput;
 
  await kv.set('messages', input.userId, input.message, {
    ttl: 60 * 60 * 24 * 30,
  });
 
  return outputSchema.parse(output);
}

The function owns validation and service calls. Any server entry point can call it without recreating a separate app registry.

Framework Route

typescriptapp/api/messages/route.ts
import { enrichMessage } from '@/workflows/enrich-message';
 
export async function POST(request: Request): Promise<Response> {
  const result = await enrichMessage(await request.json());
  return Response.json(result);
}

Use the same function from a server action, queue consumer, cron job, or script when that trigger should run the same work.

Hono Route

Use @agentuity/hono when you want Hono context variables for shared service clients.

npm install @agentuity/hono hono
typescriptsrc/index.ts
import { Hono } from 'hono';
import { agentuity } from '@agentuity/hono';
import { enrichMessage } from './workflows/enrich-message';
import type { Logger } from '@agentuity/hono';
 
interface Variables {
  readonly logger: Logger;
}
 
const app = new Hono<{ Variables: Variables }>();
 
app.use('*', agentuity());
 
app.post('/messages', async (c) => {
  const result = await enrichMessage(await c.req.json());
  c.var.logger.info('message enriched', { userId: result.userId });
 
  return c.json(result);
});
 
export default app;

Direct clients are still the portable default. Hono middleware is useful when a route should receive clients and telemetry through c.var.*.

Parallel Calls

Use Promise.all when independent workflows can run at the same time.

import { enrichMessage } from './enrich-message';
import { scoreMessage } from './score-message';
import { routeMessage } from './route-message';
 
export async function processMessage(body: unknown): Promise<{
  readonly enriched: Awaited<ReturnType<typeof enrichMessage>>;
  readonly score: Awaited<ReturnType<typeof scoreMessage>>;
  readonly route: Awaited<ReturnType<typeof routeMessage>>;
}> {
  const [enriched, score, route] = await Promise.all([
    enrichMessage(body),
    scoreMessage(body),
    routeMessage(body),
  ]);
 
  return { enriched, score, route };
}

Keep sequential await when the second call depends on the first result.

Typed Outputs

Export function return types from the implementation instead of duplicating output shapes in callers.

import { enrichMessage } from './enrich-message';
 
export type EnrichedMessage = Awaited<ReturnType<typeof enrichMessage>>;
 
export function formatEnrichedMessage(message: EnrichedMessage): string {
  return `${message.userId}: ${message.message}`;
}

Migration Note

If an older app still imports agents from @agentuity/runtime and calls .run(), treat that code as migration context. New v3 code should use framework routes, shared functions, and standalone service clients.