Migrating from v1 to v2 — Agentuity Documentation

Migrating from v1 to v2

Migrate from v1 to v2 for explicit routing, Hono-native routers, and standard Vite config.

Upgrading from v1 to v2? v2 replaces the old generated entry flow with an explicit app.ts, supports Hono-native routers, and moves build config into standard vite.config.ts. This guide focuses on the changes the current SDK actually requires and what the migration tool will rewrite for you.

What's Changed in v2

v2 introduces seven fundamental architectural changes:

1. Agents are Declarative

v1: Agents were auto-discovered from src/agent/*/agent.ts files at runtime.

v2: Import agents explicitly into createApp(). This keeps the registered agents visible in one place and ensures they are included for runtime registration and agent-to-agent calls:

// app.ts
import { createApp } from '@agentuity/runtime';
import agents from './src/agent';
 
export default await createApp({
  agents,
});

Type safety for ctx.invoke() comes from direct imports. Listing agents in createApp() controls what the app registers and bundles.

2. No More setup/shutdown Hooks

v1: Lifecycle hooks in createApp():

createApp({
  setup: async (ctx) => { /* initialize */ },
  shutdown: async (ctx) => { /* cleanup */ }
});

v2: Use standard patterns:

  • Initialization: Module-level code for env-independent setup, agent setup() for env-dependent SDK clients
  • Cleanup: registerShutdownHook() or Bun process hooks

3. Explicit Router for Typed Mounts

v1: File-based auto-discovery — routes in src/api/*.ts were automatically mounted.

v2: If you want an exported router type for hc<ApiRouter>(), create a root router in src/api/index.ts and pass it to createApp():

// app.ts
import { createApp } from '@agentuity/runtime';
import api from './src/api/index';
import agents from './src/agent';
 
export default await createApp({
  router: { path: '/api', router: api },
  agents,
});

Compose route modules into a single exported router type when you want to import that type from a frontend or another app.

4. Use a Router Shape That Preserves Types

v1: createRouter() was a wrapper around Hono:

import { createRouter } from '@agentuity/runtime';
 
const router = createRouter();
router.get('/hello', async (c) => c.json({ msg: 'hi' }));

v2: Both createRouter() and new Hono<Env>() work. The key change is to keep the root router in a chained expression when you want to export its type for Hono RPC:

import { Hono } from 'hono';
import type { Env } from '@agentuity/runtime';
 
const router = new Hono<Env>()
  .get('/hello', async (c) => c.json({ msg: 'hi' }));

5. Legacy Transport Helpers Removed

v1: Older frontend code often relied on Agentuity-specific transport helpers and generated client types.

v2: Replace those legacy client patterns with Hono's native RPC client or your preferred data fetching library. If you're migrating older frontend code, look for imports from @agentuity/react that construct API clients or wrap route calls:

import { hc } from 'hono/client';
import type { ApiRouter } from '../api';
 
const client = hc<ApiRouter>('/api');
 
// Direct usage
const res = await client.hello.$get();
const data = await res.json();

6. Standard vite.config.ts

v1: Vite config lived inside agentuity.config.ts:

// agentuity.config.ts
export default {
  workbench: { route: '/workbench' },
  // Vite plugins were added here too
};

v2: Use standard vite.config.ts and move runtime settings to createApp():

// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
 
export default defineConfig({
  plugins: [react()], // Add plugins for your frontend framework
});
// app.ts
import { createApp } from '@agentuity/runtime';
 
export default await createApp({
  analytics: true, // or { trackClicks: false, ... }
});

7. Move Env-Dependent Agent Clients into setup()

v1: Agent files often constructed SDK clients at module scope.

v2: The build system imports agent files to read metadata. Keep clients that need environment variables inside agent setup() so build-time discovery does not require those variables:

import OpenAI from 'openai';
import { createAgent } from '@agentuity/runtime';
import { s } from '@agentuity/schema';
 
export default createAgent('chat', {
  schema: {
    input: s.object({ prompt: s.string() }),
    output: s.object({ answer: s.string() }),
  },
  setup: async () => ({
    client: new OpenAI(),
  }),
  handler: async (ctx, input) => {
    const completion = await ctx.config.client.chat.completions.create({
      model: 'gpt-5.4-nano',
      messages: [{ role: 'user', content: input.prompt }],
    });
 
    return {
      answer: completion.choices[0]?.message?.content ?? '',
    };
  },
});

Usage

npx @agentuity/migrate [project-dir] [options]

Run in your project root (or pass a path). The tool checks that your git worktree is clean before touching anything, so you can always git diff to review changes or git checkout . to roll back.

Options

FlagDescription
--yes, -ySkip interactive confirmation
--dry-runPrint the migration report without modifying files
--help, -hShow help

What it migrates

Auto-fixable (fully automated)

FindingAction
v1-generated files under src/generated/Deleted. Run agentuity build afterward so the current v2 build recreates any generated files it still needs
bootstrapRuntimeEnv() call in app.tsRemoved (createApp handles it)
v1 createRouter() + mutating .get()/.post() route filesRewritten to new Hono<Env>() chained style
Missing src/api/index.ts barrelGenerated from discovered route files
Missing src/agent/index.ts barrelGenerated from discovered agent files

Guided (applied with your review)

FindingWhat happens
setup in createApp()Migration comment added — move init to module level
shutdown in createApp()Guidance to move cleanup into registerShutdownHook() or Bun process hooks
No router/agents in createApp()Guidance shown — wire up the generated barrels
agentuity.config.ts has Vite keysGuidance to create vite.config.ts with plugins/define/render
agentuity.config.ts has runtime settingsRemove — keep them in createApp()
agentuity.config.ts emptyCan be deleted

Manual (instructions only, no auto-transform)

FindingGuidance
Frontend files using removed transport helpers or generated client registry typesReplace with hc<ApiRouter>() from hono/client or your preferred data fetching library

Step-by-Step Migration

Step 1: Run the Migration Tool

npx @agentuity/migrate

This will analyze your project and present a migration report. Use --dry-run first to preview changes without modifying files.

Step 2: Review Auto-fixes

The tool will automatically:

  • Delete old v1-generated files under src/generated/
  • Remove bootstrapRuntimeEnv() calls (handled by createApp() now)
  • Rewrite route files from mutating style to chained Hono style

After the migration finishes, run agentuity build so the current v2 build can recreate any generated files it still uses.

Before (v1 mutating style):

import { createRouter } from '@agentuity/runtime';
 
const router = createRouter();
 
router.get('/hello', async (c) => {
  return c.json({ message: 'Hello' });
});
 
router.post('/echo', async (c) => {
  const body = await c.req.json();
  return c.json(body);
});
 
export default router;

After (v2 chained style):

import { Hono } from 'hono';
import type { Env } from '@agentuity/runtime';
 
const router = new Hono<Env>()
  .get('/hello', async (c) => {
    return c.json({ message: 'Hello' });
  })
  .post('/echo', async (c) => {
    const body = await c.req.json();
    return c.json(body);
  });
 
export default router;

Step 3: Handle Guided Migrations

Review and apply the guided migrations:

Setup/Shutdown Lifecycle

Before (v1):

// app.ts
import { createApp } from '@agentuity/runtime';
 
export default createApp({
  setup: async (ctx) => {
    // Initialize database and connections
    ctx.app.db = await connectDB();
  },
  shutdown: async (ctx) => {
    // Cleanup
    await ctx.app.db.close();
  }
});

After (v2):

// db.ts - Initialize at module level
export const db = await connectDB();
 
// app.ts
import { createApp, registerShutdownHook } from '@agentuity/runtime';
import { db } from './db';
 
registerShutdownHook(async () => {
  await db.close();
});
 
export default await createApp({
  // No app-level setup/shutdown hooks
});

Configuration Consolidation

Move all configuration from agentuity.config.ts to either:

  • createApp() in app.ts for runtime config (analytics, cors, compression)
  • vite.config.ts for build config (plugins, define, render)

Before (v1):

// agentuity.config.ts
export default {
  workbench: { route: '/workbench' },
  // plugins and other vite config
};

After (v2):

// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
 
export default defineConfig({
  plugins: [react()],
  define: { 'process.env': {} }
});
 
// app.ts
import { createApp } from '@agentuity/runtime';
 
export default await createApp({
  analytics: true
});

Step 4: Update API Client

If your frontend still uses a v1-style client, remove the old Agentuity transport layer and switch to Hono's RPC client directly:

After (v2):

import { hc } from 'hono/client';
import type { ApiRouter } from '../api';
 
const client = hc<ApiRouter>('/api');
const result = await client.hello.$get();

Step 5: Wire Up Barrels

The migration tool generates barrel files:

src/api/index.ts:

import helloRouter from './hello/route';
import echoRouter from './echo/route';
import { Hono } from 'hono';
import type { Env } from '@agentuity/runtime';
 
const api = new Hono<Env>()
  .route('/hello', helloRouter)
  .route('/echo', echoRouter);
 
export default api;
export type ApiRouter = typeof api;

src/agent/index.ts:

import helloAgent from './hello/agent';
import echoAgent from './echo/agent';
 
export default [helloAgent, echoAgent];

Wire these into your app.ts:

typescriptapp.ts
import { createApp } from '@agentuity/runtime';
import api from './src/api/index';
import agents from './src/agent';
 
export default await createApp({
  router: { path: '/api', router: api },
  agents
});

Step 6: Update Cron Routes

v2 changes the cron() signature and introduces platform-managed scheduling.

Updated cron() Signature

The 2-argument form cron(schedule, handler) is deprecated. Use the 3-argument form with an explicit auth option:

Before (v1):

import { cron } from '@agentuity/runtime';
 
router.post('/daily-cleanup', cron('0 0 * * *', (c) => {
  c.var.logger.info('Running daily cleanup');
  return { status: 'done' };
}));

After (v2):

import { cron } from '@agentuity/runtime';
 
router.post('/daily-cleanup', cron('0 0 * * *', { auth: true }, (c) => {
  c.var.logger.info('Running daily cleanup');
  return { status: 'done' };
}));

The deprecated form still works but defaults to auth: false. Set auth: true to require signed requests (recommended for deployed apps). See Scheduling Cron Jobs for the full cron route API.

Platform-Managed Schedules

v2 also adds @agentuity/schedule for platform-managed scheduling with delivery tracking. See Schedules for the complete API:

import { ScheduleClient } from '@agentuity/schedule';
 
const client = new ScheduleClient();
 
await client.create({
  name: 'Hourly Sync',
  expression: '0 * * * *',
  destinations: [{ type: 'url', config: { url: 'https://example.com/sync' } }],
});

Troubleshooting

Issue 1: "Cannot find module '@agentuity/react' client exports"

Cause: v2 removed the old transport helpers from @agentuity/react.

Solution: Replace with hc() from hono/client:

import { hc } from 'hono/client';
import type { ApiRouter } from '../api';
 
const client = hc<ApiRouter>('/api');

Issue 2: Exporting a Router Type for hc<...>()

Cause: Hono RPC works best when your root router is composed in a single chained expression and exported from src/api/index.ts.

Solution: Export a single root router type. Both createRouter() and new Hono<Env>() work:

import { Hono } from 'hono';
import type { Env } from '@agentuity/runtime';
 
const router = new Hono<Env>();

Issue 3: "ctx.app is empty" or "ctx.app state not available"

Cause: v1 apps often populated shared dependencies through ctx.app. New v2 code should prefer module-level dependencies or explicit services instead of relying on that old pattern.

Solution: Initialize shared dependencies in modules and import them where needed:

// Before: setup() populated ctx.app
// createApp({ setup: async (ctx) => { ctx.app.db = await connectDB(); } })
 
// After: initialize at module level and import directly
// db.ts
export const db = await connectDB();
 
// agent handler
import { db } from '../db';
// use db directly instead of ctx.app.db

Getting Help

If you encounter issues not covered in this guide:

  1. Check the Documentation: Visit the SDK Reference for detailed API docs on agents, routes, storage, and context
  2. Review Examples: Browse the Cookbook for working patterns like chat with history, cron with storage, and webhook handlers
  3. Community Support: Join our Discord community for help
  4. Report Issues: Open an issue on GitHub if you find bugs