The recommended v3 migration is a framework port: choose the framework you want to keep, then move useful v2 handlers, service calls, and state boundaries into that app. Use the migrator when you want a mechanical starting diff, not as the final app shape.
The migrator may generate Hono routes and, for frontend apps, a Vite dev split because those are close mechanical replacements for v2 runtime pieces. Keep that output only when it matches your target app.
Start with a dry run. It tells you which v2 runtime patterns the migrator can rewrite and which parts need a person to choose the v3 shape.
npx @agentuity/migrate --v2-to-v3 --dry-runBefore You Start
Work from a branch with a clean git worktree. The migration tool checks this before writing files, because it rewrites source files and package metadata.
You can keep a v2 app on the v2 runtime model until you are ready to migrate. Keep the v2 CLI installed locally in that project. A global v3 CLI delegates to a different project-local CLI before handling commands. During deploy, v3 pins Agentuity-owned dependencies set to latest to versions resolved from bun.lock when possible, then hands v2 deploys to the local v2 CLI.
What Changes
| v2 runtime app | v3 framework app |
|---|---|
app.ts calls createApp() | the framework owns the entry point |
createAgent() wraps handlers | model-backed work becomes route code, server functions, queue handlers, or plain functions |
services arrive through ctx.* or c.var.* | service clients are imported directly, with Hono middleware as an option |
ctx.thread, ctx.session, ctx.sessionId, or c.var.thread | app-owned state in cookies, databases, KV records, durable streams, or platform inspection APIs |
agentuity.config.ts carries runtime settings | framework config handles framework concerns, agentuity.json handles project metadata |
src/agent/index.ts registers agents with the runtime | functions are imported where they are called |
The Runtime to Frameworks page shows the same shift with code.
Choose the Final App Shape
The migration tool gives you a starting point, then you choose the final framework shape.
The migrator may generate a Hono API and, for frontend projects, a Vite dev split because those are the closest mechanical replacements for v2 runtime pieces. Keep that output if it matches your target app. Otherwise, move the migrated functions and service clients into the framework you want to use.
Keep the useful diff: plain functions, direct service clients, and package cleanup. Change the app shell when another framework is the better home.
Run the Migration
Use the v3 migration command so the --v2-to-v3 flag is available.
npx @agentuity/migrate --v2-to-v3When you switch to the v3 CLI after migration, the examples below use agentuity ... for readability. If the CLI is only installed locally, run the same command through your package manager's exec wrapper, for example npx agentuity ... or bunx agentuity ....
The migrator can make these changes:
- generate
src/index.tswith a Hono app and@agentuity/hono - generate
src/services.tsfor detected service clients - convert simple
createAgent()handlers to plain exported functions - rewrite
ctx.kv,ctx.vector, and other detected service access to direct imports - rewrite some
c.var.*service access in Hono routes to direct imports - remove
@agentuity/runtime,@agentuity/evals,@agentuity/frontend, and@agentuity/workbenchfrompackage.json - remove the old
app.ts,agentuity.config.ts, and top-levelsrc/agent/index.tsbarrel when they match the v2 runtime shape - port detected
@agentuity/schemausage to Zod in files where that transform applies - run
bun installandtsc --noEmit --skipLibCheckafter writing changes
Review Manual Work
Some v2 patterns need a person to choose the replacement:
| Pattern | What to do |
|---|---|
setup() or shutdown() lifecycle hooks | move initialization and cleanup into normal framework or module-level code |
ctx.thread.state, ctx.session.state, or c.var.thread | choose the app-owned state boundary: cookie, database, KV, durable stream, or platform session lookup |
ctx.config or ctx.app | replace shared runtime state with explicit imports or framework state |
| event listeners on agents | move the behavior into your app's own event flow |
agentuity.config.ts runtime options | move build options to framework config and project metadata to agentuity.json |
| generated Hono or Vite scripts that don't match your target framework | replace them with that framework's normal dev, build, and start scripts |
files importing @agentuity/evals | rebuild that evaluation flow with Evals and Testing patterns |
| frontend code using v2 Agentuity React or Workbench helpers | replace it with your framework's normal client code, auth provider, or inspection UI |
The migration tool also strips v2 validator middleware from routes. Validate request bodies inside the framework route, usually with Zod or your existing validation library.
The migrator may leave stubs for thread and session APIs. Treat those as review markers, not as working conversation memory.
Before and After Patterns
Thread State
In v2, conversation state often lived behind the runtime thread API:
import { createAgent } from '@agentuity/runtime';
export const chat = createAgent('chat', async ({ ctx, input }) => {
const existing = await ctx.thread.state.get('history');
const history = Array.isArray(existing) ? existing : [];
const next = [...history, { role: 'user', content: input.message }];
await ctx.thread.state.set('history', next);
return { history: next };
});In v3, make the state boundary explicit. This example stores chat history in KV with an app-owned conversation ID:
import { KeyValueClient } from '@agentuity/keyvalue';
interface HistoryEntry {
readonly role: 'user' | 'assistant';
readonly content: string;
}
const kv = new KeyValueClient();
export async function appendHistory(
conversationId: string,
entry: HistoryEntry
): Promise<readonly HistoryEntry[]> {
const existing = await kv.get<readonly HistoryEntry[]>('chat-history', conversationId);
const history = existing.exists ? existing.data : [];
const next = [...history, entry];
await kv.set('chat-history', conversationId, next);
return next;
}Lifecycle Hooks
Move setup() and shutdown() work into framework or module-level code. The exact shape depends on the resource, but the migration decision should be explicit.
import { createAgent } from '@agentuity/runtime';
interface SummaryClient {
summarize(text: string): Promise<string>;
close(): Promise<void>;
}
async function createSummaryClient(): Promise<SummaryClient> {
return {
async summarize(text) {
return text.slice(0, 120);
},
async close() {},
};
}
export const summarize = createAgent(
'summarize',
async ({ ctx, input }) => {
return ctx.config.client.summarize(input.text);
},
{
setup: async () => {
const client = await createSummaryClient();
return { client };
},
shutdown: async ({ client }) => {
await client.close();
},
}
);interface SummaryClient {
summarize(text: string): Promise<string>;
close(): Promise<void>;
}
let client: SummaryClient | undefined;
async function createSummaryClient(): Promise<SummaryClient> {
return {
async summarize(text) {
return text.slice(0, 120);
},
async close() {},
};
}
export async function getSummaryClient(): Promise<SummaryClient> {
client ??= await createSummaryClient();
return client;
}
export async function closeSummaryClient(): Promise<void> {
await client?.close();
client = undefined;
}Call closeSummaryClient() from your framework's shutdown hook or from the script that owns the process. Do not hide app lifecycle work in a migrated agent wrapper.
Queue Producer
In v2, queue publishing usually sat behind ctx:
await ctx.queue.publish(
'email-reports',
{ userId: input.userId, report: 'daily' },
{ idempotencyKey: `daily-report:${input.userId}` }
);In v3, construct the queue client once and call it from the route, server function, or plain function that owns the request:
import { QueueClient } from '@agentuity/queue';
const queue = new QueueClient();
export async function enqueueDailyReport(userId: string): Promise<string> {
const message = await queue.publish(
'email-reports',
{ userId, report: 'daily' },
{
partitionKey: userId,
idempotencyKey: `daily-report:${userId}`,
}
);
return message.id;
}KV Access
In v2:
const preferences = await ctx.kv.get('preferences', userId);
await ctx.kv.set('preferences', userId, {
theme: 'dark',
notifications: true,
});In v3:
import { KeyValueClient } from '@agentuity/keyvalue';
interface Preferences {
readonly theme: 'light' | 'dark';
readonly notifications: boolean;
}
const kv = new KeyValueClient();
export async function savePreferences(userId: string, preferences: Preferences): Promise<void> {
await kv.set('preferences', userId, preferences);
}Dry Run Markers
Dry runs print the migration report and do not write files. Manual items are markers for design work, not errors from the tool.
━━━ Agentuity v2 → v3 Migration Report ━━━
Project: /path/to/app
Summary: 6 auto-fixable, 2 guided, 1 manual
Manual (requires human action)
[ manual ] Agent "chat" is complex
Thread or session state needs an app-owned replacement.
(dry-run mode, no files modified)Verify Locally
After the diff looks right, run the checks your app already uses. For a TypeScript app with a build script, that usually starts here:
npm run typecheck
npm run buildThen run the Agentuity packaging check:
agentuity buildIf the framework build passes but agentuity build fails, fix the packaging issue before deploying. The build output and --report-file option are usually the fastest way to see what the CLI detected:
agentuity build --report-file build-report.jsonAfter agentuity build, inspect .agentuity/launch.json. It is the exact process metadata Agentuity will use for deploy. For normal framework apps, prefer fixing the framework build and start script so the detector can infer the right command. If your app has a custom runtime or a start command the detector cannot infer, add a root launch.json and rerun the build. See Custom Launchers for the supported shape.
Register Before Deploy
Migrated apps usually need to be linked to Agentuity Cloud before their first v3 deploy. Validate the local project shape, then import it:
agentuity project import --validate-only
agentuity project importagentuity project import registers the project, writes agentuity.json, and creates or updates .env with AGENTUITY_SDK_KEY. After that, deploy from the app directory:
agentuity build
agentuity deployFramework projects should pass agentuity build before deploy. For Hono migrations, check the generated package.json scripts and .agentuity/launch.json before treating the app as deploy-ready.
Next Steps
- Migration CLI: see the full command flow and flags
- Deploying Framework Apps: register, build, and deploy framework projects
- Services: choose standalone service clients after the runtime migration