Validation and schemas
Every place where data crosses from one part of the platform to another carries a Zod
schema. Action input and output, provider config, entity data and searchInput, event
payloads, item data — each is described by a schema, and each is checked where the data
arrives. This is a deliberate, load-bearing choice, not boilerplate.
Schemas live at every boundary
Section titled “Schemas live at every boundary”The boundaries are exactly the places where you cannot trust the shape of what you receive, because it came from a model, a network request, a config file, or another package:
| Boundary | Schema |
|---|---|
| Action call | input / output |
| Provider registration | config |
| Entity search and fetch | searchInput / data |
| Event emission | payload |
| Item emission | data |
At each of these, the platform parses before it proceeds. An action’s execute receives
input only after it has passed the input schema, and its return value is checked against
output before the caller sees it. A provider’s config is parsed before setup runs. An
emitted item’s data is checked against its item type. The schema is the contract, and the
contract is enforced automatically — you declare it, you do not hand-write the checks.
What the schemas buy you
Section titled “What the schemas buy you”A single schema does four jobs at once:
- Runtime safety. Malformed data is rejected at the edge with a specific error, so it never reaches code that assumed it was well-formed. An LLM that hallucinates a field, a config file with a typo, a provider returning the wrong shape — all fail loudly and early.
- Static types. The schema is the type.
z.inferderives the TypeScript type from the schema, soexecutereceives fully typed input and there is no second, hand-maintained type declaration to drift out of sync. - Generated definitions. The same schemas are converted into the tool definitions the
agent gives a model, and into the OpenAPI documents the HTTP host serves. Describe a field
once with Zod
.describe()and that description flows into every generated surface. - Self-documenting contracts. A schema states precisely what a capability accepts and returns. It is the most reliable documentation a provider has, because it cannot lie — the runtime enforces it.
Because one declaration produces all four, the schema is the single source of truth for a boundary. There is nowhere else to look and nothing else to keep aligned.
Validate at boundaries, trust within
Section titled “Validate at boundaries, trust within”The discipline is to validate once, at the boundary, and then trust the parsed value
everywhere inside. Inside execute, input is already the right shape — there is no need to
re-check it, and doing so would only add noise. The parse happened at the door; within the
room, the data is known-good. This keeps validation concentrated where untrusted data
actually enters, and keeps the interior code clean and typed.
The corollary is that you should push validation to the boundary rather than scattering ad
hoc checks through your logic. If a value needs constraining, express the constraint in the
schema — z.string().min(1), z.number().int().positive() — so the boundary rejects bad
data before your code ever runs.
z comes from core
Section titled “z comes from core”Import z from the core package, not from zod directly:
import { z } from '@grundlag/core';Core re-exports Zod so every package uses one version and one import path, and the repo standardises on the zod v4 API. Using the re-export keeps providers consistent and avoids the subtle breakage of two Zod copies disagreeing about what a schema is.
Errors carry the issues
Section titled “Errors carry the issues”When a boundary rejects data, the error is specific and inspectable. The validation errors —
ActionInputValidationError, ActionOutputValidationError, and
ProviderConfigValidationError — each carry the offending id and the Zod issues array, so
a caller or a test can see exactly which field failed and why. All extend CoreError, so a
consumer can catch platform failures as a group. See
Define actions for these errors in the action flow and
Providers and registries for config validation at
registration.