Twenty One Media
aiJune 11, 2026

The Agent Writes JSON. The Code Writes the PDF.

The natural way to use an AI agent to produce a sales document is to ask it to write the document. Give it the business name, the research, the angle, and let it produce polished prose. That's how most people build this.

We didn't do that. The Claude Code skill that drafts our ops audits produces a JSON file. The document comes from code.

Why JSON, Not Prose

An ops audit has a specific structure. Each workflow entry has a name, an observation, a hours-per-week range, a dollars-per-year range, a required assumption field, and a description of what the automated version does. That structure maps directly to a TypeScript interface:

interface AuditWorkflow {
  name: string;
  observed: string;
  hoursPerWeek: [number, number];
  dollarsPerYear: [number, number];
  assumption: string;
  automated: string;
}

If the agent writes prose, there's no guarantee it includes an assumption. There's no guarantee the dollar range is a tuple of two numbers. There's no guarantee anything is present, specific, or consistent across audits.

If the agent writes JSON that matches that interface, every audit has the same structure. The Next.js page renders it correctly. The Playwright PDF script doesn't need to parse or interpret anything. The schema is the contract.

The Assumption Field

The assumption field is the most important constraint. The TypeScript type makes it required, but that's not the real point. The real point is that every estimate has to be justified in writing before it goes to a prospect.

"Assumes ~20 quotes/mo at 15-20 min of follow-up each, $25/hr admin cost."

That sentence came from the agent. It's not a number we made up, and it's not a vague claim about "significant time savings." It's a derivation. If the business owner tells us their quote volume is 50 per month, not 20, the estimate updates and the assumption should too. Showing your work invites a correction rather than a shrug.

When the agent writes JSON with a required assumption field, it can't produce a draft that skips the reasoning.

The Heuristics File

The skill has a heuristics.md file alongside the workflow. It contains industry-specific estimates: typical quote volumes for residential HVAC, scheduling minutes per job for field-service businesses, admin hourly rates for different business sizes.

This is encoded knowledge, not code. When the agent researches a prospect, it references that table to produce realistic ranges. A Hamilton County HVAC contractor probably sends 20-30 quotes a month. A landscaping crew probably spends 8-12 minutes per job on scheduling coordination. The agent uses those priors as a starting point, then adjusts based on what the research actually shows.

The heuristics live in the skill, not in the application. If we learn that we've been underestimating scheduling overhead for HVAC, we update heuristics.md. The agent gets the update. No code changes, no deployments.

The Human Gate

Before the JSON file gets committed, Isaac reviews it. The workflow is:

  1. Run /ops-audit <business-url> with any notes from the intake form
  2. Agent researches the business (website, job posts, reviews)
  3. Agent drafts the JSON and shows it inline
  4. Review. Edit if needed. Approve.
  5. Agent commits to content/audits/, pushes, Vercel deploys
  6. Run Playwright to render the PDF

The draft takes 15-20 minutes. Most of that is the agent reading the site and thinking through the workflows. The review is usually 5-10 minutes of checking the numbers and tightening the language.

The total time from "new prospect" to "live audit URL and PDF ready to send" is under 30 minutes. Before this pipeline, the same work took an afternoon.

The Pattern

The agent's job is narrow: produce valid structured data. The infrastructure's job is broader: render it, format it, generate a PDF, serve a URL. Keeping those two concerns separate means the agent output is testable, the presentation is maintainable, and neither depends on the other doing its job correctly.

A prose document that looks right is hard to validate programmatically. A JSON file that matches a TypeScript interface either compiles or it doesn't. The schema is the test.