Agentuity uses the StandardSchema 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() }) | Lightweight validation without extra dependencies |
| Zod | z.object({ name: z.string() }) | AI SDK integration (.describe() support) |
| Valibot | v.object({ name: v.string() }) | Smallest bundle size |
| ArkType | type({ 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.
For the complete @agentuity/schema API including coercion helpers and JSON Schema conversion, see Schema Validation.
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 });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.
Use @agentuity/schema when you want a small validation layer for common object, array, union, enum, literal, and coercion cases. Use Zod, Valibot, or ArkType when you need deeper validation rules, transformations, metadata, or ecosystem integrations.
Using Valibot
Installation
bun add valibotBasic 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 valueUsing ArkType
Installation
bun add arktypeBasic 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
- Creating Agents: Full guide to agent creation with Zod examples
- Using the AI SDK: Add LLM capabilities to your agents