Zod is required for coded agents, Guild's most flexible agent type, written in TypeScript. Other agent types (LLM agents, Goose agents) declare their interface differently. See the agent types guide for the full breakdown.
Zod
Key Takeaways
- Zod is a TypeScript-first runtime schema validation library used to define, parse, and enforce the shape of data at the boundaries of your system.
- In the Guild SDK, Zod is the required tool for declaring an agent's inputSchema and outputSchema — the contracts every coded agent uses to describe what data it accepts and returns.
- Guild's coded-agent runtime supports exactly two npm packages: @guildai/agents-sdk and zod. Every other dependency is out of scope.
- Zod schemas double as TypeScript types via z.infer, so you write the contract once and get compile-time and runtime enforcement from the same source.
What Is Zod?
Zod is a TypeScript schema validation library that lets you describe the shape of any piece of data as a schema, then validate real values against that schema at runtime. It's widely used for parsing API responses, form input, environment variables, and — increasingly — the input and output of AI agents.
The core idea: you describe your data with expressions like z.object({ name: z.string(), age: z.number() }), and Zod turns that description into two things at once — a runtime validator and a TypeScript type. That means a Zod schema is the single source of truth for both the shape of your data and the type your compiler sees.
How Zod Works
Schemas as first-class values
A Zod schema is a normal JavaScript value you can pass around, compose, and extend. Every primitive (z.string(), z.number(), z.boolean()) and composite (z.object(), z.array(), z.union()) produces a schema object with methods like .parse(), .safeParse(), .optional(), and .describe().
Runtime validation with parse and safeParse
Calling .parse(value) on a schema validates the value and either returns a strongly-typed result or throws a rich error object with paths and messages for every failure. .safeParse(value) returns a discriminated union { success: true, data } | { success: false, error } for when you'd rather handle failures without a try/catch.
Types from schemas via z.infer
type MyInput = z.infer<typeof mySchema> gives you the exact TypeScript type the schema validates. You define the contract once. TypeScript, your IDE, and the runtime all agree on what the data looks like.
Schema descriptions carry into tools and LLMs
Every schema field can carry a .describe("...") string. In the Guild SDK, those descriptions get forwarded to the LLM as part of the tool schema, so the model knows what each input field is for. A schema is both documentation and enforcement.
Why Zod Matters for AI Agents
Agents live at a data boundary
An agent's job is to take unstructured input, decide what to do, and return structured output. That's a data boundary, and every data boundary needs validation. Without a schema, you're one malformed LLM response away from a runtime crash or a silently corrupted downstream call.
LLM tool schemas need a source of truth
LLM providers (OpenAI, Anthropic, and others) accept tool inputs as JSON Schema objects, and they require "type": "object" at the root. Zod produces exactly the JSON Schema shape these providers expect, which is why Guild's SDK enforces z.object({...}) as the root of every inputSchema. Using anything else — z.union, z.discriminatedUnion — will fail at runtime because the LLM can't process a non-object root.
One contract, three consumers
The same Zod schema serves three audiences:
- Your compiler gets a TypeScript type via z.infer.
- Your runtime gets a validator that catches malformed input before it reaches business logic.
- The LLM gets a machine-readable description of what fields exist and what they mean.
Without a shared source of truth, these three drift apart. Types describe one thing, docs describe another, and validation is best-effort. Zod collapses all three into one declaration.
Zod in the Guild SDK
Every coded agent in Guild declares its interface with two Zod schemas. Here is the pattern taken directly from the Guild coded-agents guide:
import { z } from "zod" const inputSchema = z.object({ repo: z.string().describe("The GitHub repository in 'owner/name' format"),})type Input = z.infer<typeof inputSchema> const outputSchema = z.object({ summary: z.string(), labels: z.array(z.string()),})type Output = z.infer<typeof outputSchema>
That block gives you:
- A runtime-validated inputSchema that Guild uses to check every invocation.
- A .describe() annotation the LLM sees when it decides how to call the agent.
- An Input and Output type you use throughout your handler.
Guild-specific constraints
Guild enforces two constraints on Zod schemas that are worth calling out:
- inputSchema must be z.object({...}) at the root. Not z.union(). Not z.discriminatedUnion(). LLM providers require "type": "object" at the top of tool input schemas, and Guild's runtime validates this on deploy.
- The runtime sandbox only allows two npm imports: @guildai/agents-sdk and zod. Agents run in a locked-down environment without access to arbitrary Node.js built-ins or external packages. Zod's zero-dependency footprint is a big reason it fits.
Key Considerations
Zod v3 vs. v4
Zod 4 is out and introduces some breaking changes around error formatting and type inference. If you're writing agents on Guild, check which version the SDK is pinned to before you rely on v4-only features.
Runtime cost is real but small
Every .parse() call walks the schema and inspects the value. For agent inputs and outputs this is negligible. If you're validating a hot loop with thousands of calls per second, cache the schema at module scope (not inside the handler) and prefer .safeParse over .parse to avoid throw overhead.
Descriptions are part of your prompt surface
Because Guild forwards .describe() strings to the LLM as part of the tool schema, they're effectively part of your prompt. Write them like you'd write a doc comment the model needs to understand: clear, concrete, and grounded in what the agent actually expects.
JSON Schema is a subset
Not every Zod feature translates cleanly to JSON Schema (custom refinements, transforms, brand types). If you use those inside an inputSchema, they still validate on the Guild side, but the LLM won't see them. Keep the schema declarative when it's user-facing to the model.
The Future We're Building at Guild
Guild is a control plane for AI agents, a place to build, deploy, and govern the agents your teams run in production. Zod is the foundation for how coded agents on Guild describe themselves to the runtime and to every LLM that calls them.
If you're building a coded agent on Guild:
- Start with the SDK introduction to see how inputSchema and outputSchema fit into the agent lifecycle.
- Read the coded agents guide for full examples.
- Explore the CLI and the Agent Hub once your agent is running.
FAQs
No. Guild's runtime only allows @guildai/agents-sdk and zod. Zod was chosen specifically because it produces the JSON Schema shape LLM providers expect and doubles as a TypeScript type source.
LLM providers (OpenAI, Anthropic, and others) require "type": "object" at the top of any tool schema. z.union and z.discriminatedUnion produce a root that's not an object, so the LLM rejects the call. Guild validates this constraint on deploy to catch it early.
No. Use type Input = z.infer<typeof inputSchema> to derive the TypeScript type directly from the schema. One declaration, two consumers.
For typical agent inputs and outputs, no. For very hot paths, define your schema once at module scope and use .safeParse to avoid the throw overhead of .parse.