# Schema Libraries

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

Agentuity uses the [StandardSchema](https://github.com/standard-schema/standard-schema) interface for validation, which means you can use any compatible library.

## Choosing a Library

| Library | Syntax | Best For |
|---------|--------|----------|
| `@agentuity/schema` | `s.object({ name: s.string() })` | Zero dependencies, basic validation |
| [Zod](https://zod.dev) | `z.object({ name: z.string() })` | AI SDK integration (`.describe()` support) |
| [Valibot](https://valibot.dev) | `v.object({ name: v.string() })` | Smallest bundle size |
| [ArkType](https://arktype.io) | `type({ name: 'string' })` | TypeScript-native syntax, fast runtime |

**Recommendation:** Start with `@agentuity/schema` for simple cases. Use other libraries depending on your needs. For example, Zod is recommended when working with `generateObject()` (its `.describe()` helps guide LLM output).

> [!NOTE]
> **Built-in Schema Reference**
> For the complete `@agentuity/schema` API including coercion helpers and JSON Schema conversion, see [Schema Validation](/reference/sdk-reference/schema).

## Using @agentuity/schema

The built-in schema library provides simple validation with zero external dependencies.

### Basic Schema

```typescript
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

```typescript
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.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:

```typescript
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:

```typescript
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 });
```

> [!TIP]
> **Strict Mode for LLMs**
> Use `{ strict: true }` when generating schemas for LLM structured output (e.g., OpenAI's `response_format`). Strict mode ensures the schema is compatible with model constraints and produces more reliable outputs.

> [!NOTE]
> **When to Use**
> Use `@agentuity/schema` for simple validation needs. For advanced features like email validation, string length constraints, or complex transformations, consider Zod or Valibot.

## Using Valibot

### Installation

```bash
bun add valibot
```

### Basic Schema

```typescript
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

```typescript
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

```bash
bun add arktype
```

### Basic Schema

```typescript
import { createAgent } from '@agentuity/runtime';
import { type } from 'arktype';

const InputSchema = type({
  email: 'string.email',
  age: 'number>=0&<=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

```typescript
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('integer')                   // Integer only
type('number>=0&<=120')           // Range (0 to 120)

// Arrays and objects
type('string[]')                  // Array of strings
type({ name: 'string' })          // Object shape

// Optional and union
type('string?')                   // string | undefined
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:

```typescript
// 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

- [Creating Agents](/agents/creating-agents): Full guide to agent creation with Zod examples
- [Using the AI SDK](/agents/ai-sdk-integration): Add LLM capabilities to your agents