Schema Libraries — Agentuity Documentation

Schema Libraries

Choose from built-in, Zod, Valibot, or ArkType for validation

Agentuity uses the StandardSchema interface for validation, which means you can use any compatible library.

Choosing a Library

LibrarySyntaxBest For
@agentuity/schemas.object({ name: s.string() })Lightweight validation without extra dependencies
Zodz.object({ name: z.string() })AI SDK integration (.describe() support)
Valibotv.object({ name: v.string() })Smallest bundle size
ArkTypetype({ name: 'string' })TypeScript-native syntax, fast runtime

Recommendation: Use @agentuity/schema when you want a lightweight schema library for straightforward validation. It is not trying to replace Zod. Reach for Zod, Valibot, or ArkType when you need their broader validation, transformation, metadata, or ecosystem features. For example, Zod is useful with AI SDK structured output because .describe() adds field hints for the model.

Using @agentuity/schema

The built-in schema library is a lightweight option for straightforward validation.

Basic Schema

import { createAgent } from '@agentuity/runtime';
import { s } from '@agentuity/schema';
 
const agent = createAgent('UserProcessor', {
  schema: {
    input: s.object({
      email: s.string(),
      age: s.number(),
      role: s.string(),
    }),
    output: s.object({
      success: s.boolean(),
      userId: s.string(),
    }),
  },
  handler: async (ctx, input) => {
    ctx.logger.info('Processing user', { email: input.email });
    return {
      success: true,
      userId: crypto.randomUUID(),
    };
  },
});
 
export default agent;

Common Patterns

import { s } from '@agentuity/schema';
 
// Primitives
s.string()                    // Any string
s.number()                    // Any number
s.boolean()                   // Boolean
s.null()                      // Null value
s.undefined()                 // Undefined value
s.any()                       // Any value (no validation)
s.unknown()                   // Unknown value (safer any)
 
// Objects and arrays
s.object({ name: s.string() })    // Object shape
s.array(s.string())               // Array of strings
s.record(s.string(), s.number())  // { [key: string]: number }
 
// Optional and nullable
s.optional(s.string())            // string | undefined
s.nullable(s.string())            // string | null
 
// Unions, literals, and enums
s.union(s.string(), s.number())   // string | number
s.literal('active')               // Exact value
s.enum(['admin', 'user', 'guest']) // One of these values
 
// Type coercion (useful for form inputs, query params)
s.coerce.string()             // Convert to string via String(value)
s.coerce.number()             // Convert to number via Number(value)
s.coerce.boolean()            // Convert to boolean via Boolean(value)
s.coerce.date()               // Convert to Date via new Date(value)

Type Inference

Extract TypeScript types from schemas:

import { s } from '@agentuity/schema';
 
const User = s.object({
  name: s.string(),
  age: s.number(),
  role: s.enum(['admin', 'user']),
});
 
// Extract the type
type User = s.infer<typeof User>;
// { name: string; age: number; role: 'admin' | 'user' }

JSON Schema Generation

Convert schemas to JSON Schema for use with LLM structured output:

import { s } from '@agentuity/schema';
 
const ResponseSchema = s.object({
  answer: s.string(),
  confidence: s.number(),
});
 
// Generate JSON Schema
const jsonSchema = s.toJSONSchema(ResponseSchema);
 
// Generate strict JSON Schema for LLM structured output
const strictSchema = s.toJSONSchema(ResponseSchema, { strict: true });

Using Valibot

Installation

bun add valibot

Basic Schema

import { createAgent } from '@agentuity/runtime';
import * as v from 'valibot';
 
const InputSchema = v.object({
  email: v.pipe(v.string(), v.email()),
  age: v.pipe(v.number(), v.minValue(0), v.maxValue(120)),
  role: v.picklist(['admin', 'user', 'guest']),
});
 
const OutputSchema = v.object({
  success: v.boolean(),
  userId: v.string(),
});
 
const agent = createAgent('UserProcessor', {
  schema: {
    input: InputSchema,
    output: OutputSchema,
  },
  handler: async (ctx, input) => {
    ctx.logger.info('Processing user', { email: input.email, role: input.role });
    return {
      success: true,
      userId: crypto.randomUUID(),
    };
  },
});
 
export default agent;

Common Patterns

import * as v from 'valibot';
 
// Strings
v.string()                              // Any string
v.pipe(v.string(), v.minLength(5))      // Minimum length
v.pipe(v.string(), v.maxLength(100))    // Maximum length
v.pipe(v.string(), v.email())           // Email format
v.pipe(v.string(), v.url())             // URL format
 
// Numbers
v.number()                              // Any number
v.pipe(v.number(), v.minValue(0))       // Minimum value
v.pipe(v.number(), v.maxValue(100))     // Maximum value
v.pipe(v.number(), v.integer())         // Integer only
 
// Arrays and objects
v.array(v.string())                     // Array of strings
v.object({ name: v.string() })          // Object shape
 
// Optional and nullable
v.optional(v.string())                  // string | undefined
v.nullable(v.string())                  // string | null
v.nullish(v.string())                   // string | null | undefined
 
// Enums
v.picklist(['a', 'b', 'c'])             // One of these values
v.literal('exact')                      // Exact value
 
// Defaults
v.optional(v.string(), 'default')       // With default value

Using ArkType

Installation

bun add arktype

Basic Schema

import { createAgent } from '@agentuity/runtime';
import { type } from 'arktype';
 
const InputSchema = type({
  email: 'string.email',
  age: '0 <= number <= 120',
  role: '"admin"|"user"|"guest"',
});
 
const OutputSchema = type({
  success: 'boolean',
  userId: 'string',
});
 
const agent = createAgent('UserProcessor', {
  schema: {
    input: InputSchema,
    output: OutputSchema,
  },
  handler: async (ctx, input) => {
    ctx.logger.info('Processing user', { email: input.email, role: input.role });
    return {
      success: true,
      userId: crypto.randomUUID(),
    };
  },
});
 
export default agent;

Common Patterns

import { type } from 'arktype';
 
// Strings
type('string')                    // Any string
type('string > 5')                // Minimum length (greater than 5 chars)
type('string < 100')              // Maximum length
type('string.email')              // Email format
type('string.url')                // URL format
 
// Numbers
type('number')                    // Any number
type('number > 0')                // Greater than 0
type('number <= 100')             // Less than or equal to 100
type('number.integer')            // Integer only
type('0 <= number <= 120')        // Range (0 to 120)
 
// Arrays and objects
type('string[]')                  // Array of strings
type({ name: 'string' })          // Object shape
 
// Optional properties and unions
type({ 'nickname?': 'string' })    // Optional object property
type('string | null')              // string | null
 
// Enums and literals
type('"a"|"b"|"c"')               // One of these values
type('"exact"')                   // Exact value
 
// Nested objects
type({
  user: {
    name: 'string',
    email: 'string.email',
  },
  tags: 'string[]?',
})

Migrating Between Libraries

Schemas are interchangeable in createAgent(). The same agent structure works with any StandardSchema library:

// With @agentuity/schema (built-in)
import { s } from '@agentuity/schema';
const schema = { input: s.object({ name: s.string() }) };
 
// With Zod
import { z } from 'zod';
const schema = { input: z.object({ name: z.string() }) };
 
// With Valibot
import * as v from 'valibot';
const schema = { input: v.object({ name: v.string() }) };
 
// With ArkType
import { type } from 'arktype';
const schema = { input: type({ name: 'string' }) };
 
// All work the same way in createAgent()
createAgent('MyAgent', { schema, handler: async (ctx, input) => { ... } });

Next Steps