Reference

Migrating from v0 to v1

Moving from v0? Here's everything you need to update.

v0 support is ending. We recommend migrating to v1 for new features and continued updates.

This guide covers the breaking changes and how to update your agents.

What's New in v1?

No More Manual Agent Registration

In v0, agents had to be created via the CLI (agentuity agent create) to register them with unique IDs in the cloud. In v1, just create a folder in src/agent/ with agent.ts — agents are auto-discovered and registered on deploy. No CLI commands or manual registration required.

Key improvements in v1:

  • Auto-Discovery: Create agents by adding folders, no CLI registration needed
  • Type-Safe Schemas: Built-in support for Zod, Valibot, ArkType, and StandardSchema libraries
  • Advanced Routing: Native WebSocket, SSE, email, cron, and SMS routes
  • Bun Runtime: Native S3 storage and SQL database support
  • Evaluations & Events: Test agent outputs and monitor lifecycle

Workbench: v0's "DevMode" is being rebranded to "Workbench", to better support agent development both locally and in production. The v0 welcome() function pattern for suggested prompts has been removed as part of this redesign.


Breaking Changes Overview

High Impact Changes

These changes require code modifications in all agents:

  1. Handler Pattern: Default export functions replaced with createAgent()
  2. Request/Response: New pattern using direct parameters and return values
  3. Context Properties: runId renamed to sessionId, new properties added
  4. Package Structure: SDK split into multiple packages (@agentuity/runtime, @agentuity/core, etc.)
  5. Language Support: v1 is TypeScript-only, optimized for Bun runtime
  6. Trigger Configuration: Cron schedules, email addresses, and SMS numbers are now configured in code (via router.cron(), router.email(), router.sms()) rather than in the cloud console UI

Step-by-Step Migration

Step 1: Create a Fresh v1 Project

v1 has a different project structure, so we recommend creating a new project and migrating your agent code.

Install the v1 CLI:

curl -sSL https://v1.agentuity.sh | sh

Create a new v1 project:

agentuity create

This sets up the correct project structure with all dependencies. You'll migrate your agent logic into this new project using the patterns in the following steps.


Step 2: Update Agent Handler Pattern

The most significant change is how agents are created and exported.

Basic Agent Handler

v0:

import { AgentHandler } from '@agentuity/sdk';
 
const handler: AgentHandler = async (request, response, context) => {
  const data = await request.data.json();
 
  context.logger.info('Processing request', data);
 
  return response.json({
    message: 'Hello from my agent!',
    data: data
  });
};
 
export default handler;

v1:

// src/agent/my-agent/agent.ts
import { createAgent } from '@agentuity/runtime';
 
const agent = createAgent('My Agent', {
  description: 'A simple agent',
  handler: async (ctx, input) => {
    ctx.logger.info('Processing request', input);
 
    return {
      message: 'Hello from my agent!',
      data: input
    };
  }
});
 
export default agent;

Key Changes:

  1. Import from @agentuity/runtime instead of @agentuity/sdk
  2. Use createAgent('Name', { ... }) with the name as the first argument
  3. Handler receives (ctx, input) instead of (request, response, context)
  4. Return values directly instead of using response.json()
  5. Export the agent (routes are defined separately in src/api/)

File Structure Change: In v0, agents were typically single files. In v1, agents and routes are separated:

  • src/agent/my-agent/agent.ts - Contains the createAgent() call with your handler logic
  • src/api/index.ts - Contains all HTTP routes that import and call agents

Routes import agents and call them via agent.run(input). This separation keeps HTTP routing concerns separate from agent core logic, making agents reusable across different routes.


Agent with Schema Validation

v1 introduces optional schema validation for type safety:

// src/agent/typed-agent/agent.ts
import { createAgent } from '@agentuity/runtime';
import { z } from 'zod';
 
const agent = createAgent('Typed Agent', {
  description: 'An agent with type-safe inputs and outputs',
  schema: {
    input: z.object({
      message: z.string(),
      count: z.number().optional()
    }),
    output: z.object({
      response: z.string(),
      timestamp: z.number()
    })
  },
  handler: async (ctx, input) => {
    // input is fully typed as { message: string, count?: number }
 
    return {
      response: `Received: ${input.message}`,
      timestamp: Date.now()
    };
    // Return type is validated automatically
  }
});
 
export default agent;
// src/api/index.ts - All routes are consolidated here
import { createRouter } from '@agentuity/runtime';
import typedAgent from '@agent/typed-agent';
 
const router = createRouter();
 
// Routes are mounted at /api/* automatically
router.post('/typed-agent', typedAgent.validator(), async (c) => {
  const data = c.req.valid('json');
  const result = await typedAgent.run(data);
  return c.json(result);
});
 
export default router;

Benefits:

  • Full TypeScript autocomplete
  • Runtime validation of inputs and outputs
  • Automatic error handling for invalid data
  • Self-documenting API contracts

Step 3: Update Context Usage

The context object has several changes and additions.

Context Property Changes

v0:

const handler: AgentHandler = async (request, response, context) => {
  // Access run ID
  const id = context.runId;
 
  // Access services
  await context.kv.set('cache', 'key', data);
 
  // Access logger
  context.logger.info('Message');
};

v1:

const agent = createAgent('My Agent', {
  handler: async (ctx, input) => {
    // runId renamed to sessionId
    const id = ctx.sessionId;
 
    // Services work the same way
    await ctx.kv.set('cache', 'key', data);
 
    // Logger unchanged
    ctx.logger.info('Message');
 
    // NEW: State management
    ctx.state.set('myKey', 'myValue');
 
    // NEW: Session and thread objects
    ctx.logger.info('Session:', ctx.session);
    ctx.logger.info('Thread:', ctx.thread);
 
    return { result: 'done' };
  }
});

Key Changes:

  • context.runIdctx.sessionId
  • New ctx.state for temporary state storage
  • New ctx.session and ctx.thread for conversation management
  • Agent-to-agent communication uses imports (see Step 7)

Step 4: Update Request Handling

Request handling is simplified in v1.

Accessing Request Data

v0:

const handler: AgentHandler = async (request, response, context) => {
  // Get JSON data
  const data = await request.data.json();
  
  // Get text data
  const text = await request.data.text();
  
  // Get binary data
  const binary = await request.data.binary();
  
  // Get metadata
  const userId = request.get('userId');
  const trigger = request.trigger;
};

v1 (HTTP Routes):

import { createRouter } from '@agentuity/runtime';
 
const router = createRouter();
 
router.post('/my-agent', async (c) => {
  // Get JSON body directly from Hono context
  const data = await c.req.json();
  
  // Get text body
  const text = await c.req.text();
  
  // Get headers
  const userId = c.req.header('x-user-id');
  
  // Get query params
  const param = c.req.query('param');
  
  return { processed: data };
});
 
export default router;

v1 (Agent with Schema):

const agent = createAgent('My Agent', {
  schema: {
    input: z.object({ message: z.string() })
  },
  handler: async (ctx, input) => {
    // Input is automatically parsed and validated
    // No need to call request.data.json()
    ctx.logger.info('Input message:', { message: input.message });
 
    return { response: 'ok' };
  }
});

Step 5: Update Response Handling

Responses are now returned directly instead of using a response builder.

Basic Responses

v0:

const handler: AgentHandler = async (request, response, context) => {
  // JSON response
  return response.json({ message: 'Hello' });
  
  // Text response
  return response.text('Hello');
  
  // Binary response
  return response.binary(buffer);
  
  // Empty response
  return response.empty();
};

v1:

const agent = createAgent('My Agent', {
  handler: async (ctx, input) => {
    // JSON response - just return an object
    return { message: 'Hello' };
 
    // Text response - return a string
    return 'Hello';
  }
});
 
// For more control over responses, use routes in src/api/index.ts:
router.get('/hello', async (c) => {
  const result = await myAgent.run({ name: 'World' });
  return c.json(result);      // JSON response
  return c.text('Hello');     // Text response
  return c.body(buffer);      // Binary response
});

Key Changes:

  1. No response object - return values directly from agent handler
  2. Objects are automatically JSON-serialized
  3. Use Hono context methods in routes for advanced responses
  4. For typed responses, define output schema

Step 6: Update Service Usage

KV and Vector storage APIs are unchanged.

Object Storage → Bun S3

Object storage has been replaced with Bun's native S3 APIs for better performance and simpler code:

v0/Early v1:

// Store file
await context.objectstore.put('uploads', 'hello.txt', data, {
  contentType: 'text/plain',
});
 
// Get file
const result = await context.objectstore.get('uploads', 'hello.txt');
if (result.exists) {
  const text = new TextDecoder().decode(result.data);
}
 
// Create public URL
const url = await context.objectstore.createPublicURL('uploads', 'file.pdf', {
  expiresDuration: 3600000,
});
 
// Delete file
await context.objectstore.delete('uploads', 'hello.txt');

v1 (Bun S3):

import { s3 } from "bun";
 
// Store file
const file = s3.file("uploads/hello.txt");
await file.write("Hello, World!", { type: "text/plain" });
 
// Get file
if (await file.exists()) {
  const text = await file.text();
}
 
// Presign URL (synchronous, no network request)
const url = s3.presign("uploads/file.pdf", {
  expiresIn: 3600, // seconds, not milliseconds
});
 
// Delete file
await file.delete();

Key Changes:

  1. Import s3 from "bun" instead of using ctx.objectstore
  2. Use s3.file(path) to create a lazy file reference
  3. presign() takes seconds, not milliseconds, and is synchronous
  4. File operations are methods on the file object: file.write(), file.text(), file.delete()
  5. Credentials are auto-injected via environment variables

Credential Auto-Injection

Agentuity automatically injects S3 credentials (S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY, S3_BUCKET, S3_ENDPOINT) during development and deployment.

Database Support

v1 adds SQL database support via Bun's native SQL APIs:

import { sql } from "bun";
 
// Query with automatic parameter escaping (prevents SQL injection)
const users = await sql`SELECT * FROM users WHERE active = ${true}`;
 
// Insert data
await sql`INSERT INTO users (name, email) VALUES (${"Alice"}, ${"alice@example.com"})`;
 
// Transactions
await sql.begin(async (tx) => {
  await tx`UPDATE accounts SET balance = balance - ${amount} WHERE id = ${fromId}`;
  await tx`UPDATE accounts SET balance = balance + ${amount} WHERE id = ${toId}`;
});

Credential Auto-Injection

Agentuity automatically injects DATABASE_URL during development and deployment.


Step 7: Update Agent-to-Agent Communication

Agent-to-agent communication uses direct imports in v1.

v0:

const handler: AgentHandler = async (request, response, context) => {
  // Get agent by ID
  const agent = await context.getAgent({ id: 'agent_123' });
 
  // Or by name
  const agent = await context.getAgent({
    name: 'other-agent',
    projectId: 'proj_123'
  });
 
  // Run the agent
  const result = await agent.run({
    data: JSON.stringify({ message: 'Hello' }),
    contentType: 'application/json'
  });
 
  const output = await result.data.json();
};

v1:

// src/agent/coordinator/agent.ts
import { createAgent } from '@agentuity/runtime';
import otherAgent from '@agent/other-agent';
 
const agent = createAgent('Coordinator', {
  handler: async (ctx, input) => {
    // Import and call agents directly
    const result = await otherAgent.run({
      message: 'Hello'
    });
 
    // Result is automatically typed if the agent has a schema
    ctx.logger.info('Agent response:', { response: result });
 
    return result;
  }
});
 
export default agent;

Key Changes:

  1. Import agents using import agent from '@agent/agent-name'
  2. Call directly with agent.run(input)
  3. No need to JSON-stringify data
  4. Type-safe when using schemas

Other v1 Features

These features are optional but can improve your agents.

Specialized Routes

v1 adds native email and cron routes. See Routes documentation for WebSocket and SSE.

Email:

router.email('support@example.com', async (email, ctx) => {
  ctx.logger.info('Email from:', email.fromEmail());
  return { status: 'processed' };
});

Cron:

router.cron('0 0 * * *', async (ctx) => {
  ctx.logger.info('Daily job running');
  return { status: 'completed' };
});

Evaluations

Test agent outputs automatically with agent.createEval(). See Evaluations.

Event Listeners

Monitor agent lifecycle with agent.addEventListener('started' | 'completed' | 'errored', ...). See Events.

Sessions and Threads

Manage conversational state with ctx.session and ctx.thread. See State Management.


Troubleshooting

Issue 1: "Cannot find module '@agentuity/sdk'"

Cause: You haven't updated your imports from v0 to v1.

Solution: Change all imports from:

import { ... } from '@agentuity/sdk';

To:

import { ... } from '@agentuity/runtime';

Issue 2: "Property 'runId' does not exist on type 'AgentContext'"

Cause: runId was renamed to sessionId in v1.

Solution: Replace all instances of context.runId with ctx.sessionId.


Issue 3: "Handler is not a function"

Cause: You're not exporting the agent correctly.

Solution: Export the agent from agent.ts and import it in your routes:

// src/agent/my-agent/agent.ts
const agent = createAgent('My Agent', { ... });
export default agent;
 
// src/api/index.ts
import myAgent from '@agent/my-agent';
router.post('/my-agent', async (c) => {
  const result = await myAgent.run(await c.req.json());
  return c.json(result);
});

Issue 4: "Input validation failed"

Cause: You defined an input schema but the incoming data doesn't match it.

Solution: Check your schema definition and ensure incoming data matches:

const agent = createAgent('My Agent', {
  schema: {
    input: z.object({
      message: z.string(),
      // Make optional fields explicit
      metadata: z.record(z.unknown()).optional()
    })
  },
  handler: async (ctx, input) => {
    // ...
  }
});

Issue 5: "Cannot find agent" or import errors

Cause: Agent import path is incorrect.

Solution: Use the @agent/ alias to import agents:

// Correct - use @agent/ alias
import myAgent from '@agent/my-agent';
 
// The alias maps to src/agent/
// So @agent/my-agent resolves to src/agent/my-agent/agent.ts

Getting Help

If you encounter issues not covered in this guide:

  1. Check the Documentation: Visit the API Reference for detailed information
  2. Review Examples: Browse the Cookbook for working patterns
  3. Community Support: Join our Discord community for help
  4. Report Issues: Open an issue on GitHub if you find bugs

Next Steps

After migrating your agents:

  1. Add Schema Validation: Improve type safety with Schema Validation
  2. Implement Evaluations: Ensure quality with Evaluations
  3. Use Advanced Routing: Explore WebSocket, SSE, and other routes
  4. Add Event Listeners: Monitor your agents with Events & Lifecycle

Need Help?

Join our DiscordCommunity for assistance or just to hang with other humans building agents.

Send us an email at hi@agentuity.com if you'd like to get in touch.

Please Follow us on

If you haven't already, please Signup for your free account now and start building your first agent!