Zod
TypeScript-first schema validation.
Goals
Validate the raw JSON output from axe agents (specifically inertia-decomposer) at the boundary between the LLM and the domain logic — catching malformed or non-compliant responses before they can corrupt task data.
Effectiveness
Excellent. Zod is exactly the right tool for this boundary. The schema is expressive enough to describe what the LLM should produce, coercive enough to handle what it actually produces, and integrates cleanly with TypeScript's type system so validated data flows through the rest of the code as typed values.
What made it effective
.or(z.string().transform(...))handles the case where the LLM returns plain strings instead of objects — a real compliance failure we actually hit. Without this, those tasks would fail every run; with it, they degrade gracefully..default('P3')on the priority field means a missing field produces a sensible default rather than a validation error.parseDecompositionas a pure exported function means the validation logic is independently testable without mocking axe or Todoist.
Bonus utility
The ZodError thrown on schema violations is structured enough to log the specific field that failed — useful for diagnosing which LLM output pattern is non-compliant.
Friction / pain points / surprises
Schema constraints drift from LLM agent prompts with no enforcement. Zod validates what the LLM produces; the LLM is constrained by the agent TOML. When the TOML was updated (essay-outliner: 5→7 min sections, 400→600 min word_quota) but the Zod schema wasn't (still max(7) sections, max(550) words), the pipeline entered a silent retry loop: axe exited 0, Zod threw, the checkpoint never advanced. The fix is mechanical (update both together) but there's no tooling that enforces the pairing. The TOML and the schema are a two-sided contract with no shared source of truth.