Declare the shape, delegate the procedure

The single change that improved my results with coding agents more than any prompt technique: stop describing what the code should do, and hand over a structure it has to satisfy instead. Write the types yourself. Let the agent write the procedure.

Prose is a lossy spec. “Fetch the videos, parse the subtitles, and return a beat sheet per video” has a dozen readings, and the agent will pick one, confidently, and you find out which one after you read 200 lines. A type has exactly one reading, and the compiler checks the agent’s work before you do.

The shape first

type Beat = {
  startSeconds: number;
  endSeconds: number;
  kind: "hook" | "setup" | "turn" | "payoff";
  transcript: string;
};

type Analysis = {
  videoId: string;
  beats: Beat[];
  /* null when the video had no subtitle track, which is
     not the same as an empty array. */
  outlierScore: number | null;
};

Twenty lines of type do work that a page of instructions can’t. kind being a union means the agent cannot invent a fifth beat category, which it will otherwise do the moment it meets a video that doesn’t fit. outlierScore being nullable states, in a way that cannot be skimmed past, that “no subtitles” and “scored zero” are different facts. If the generated code conflates them it stops compiling, and it stops compiling in my editor rather than in production.

Why this works better than asking nicely

An agent producing code against a type has a verifier in the loop that isn’t me. It can attempt, fail, read the error, and correct, without a round trip through a human who has to notice the mistake first. My review then only has to answer “is this the right shape”, which I already decided, and “does it do the thing”, rather than the much harder “are there four subtly different notions of missing data buried in here”.

It also puts the irreversible decisions on the side of the line where I want them. Type changes ripple through everything downstream; the body of one function does not. So the cheap-to-change part is delegated and the expensive-to-change part stays hand-written, which is roughly the opposite of the default workflow where you let the model scaffold the data model and then spend a week living with its guesses.

Where it stops helping

The types have to be load-bearing to be worth it. Record<string, any> at the boundary buys nothing, and a codebase that stringly-types its states gives the agent nothing to fail against. This is the same reason the technique transfers badly to code whose hard part is a sequence rather than a structure: retries, ordering, what happens when the third of four steps fails. A type says nothing about time.

For those I write the state machine by hand and describe the transitions, because the shape that needs pinning down is the set of legal moves, not the set of legal values. Which is the same principle applied honestly: find the declarative core of the problem, own that, and delegate what follows from it.