Programming

Why our engineering process is now spec-first, not code-first

PRG

A year into routine agentic development, the highest-leverage work on our team has quietly shifted from writing code to writing specifications clear enough for an agent to execute correctly on the first attempt. That shift didn't arrive as an announced policy. It crept in gradually, project by project, until we noticed that the engineers spending the most time hunched over an editor typing implementation details were, on average, producing worse outcomes than the ones spending that same time thinking hard about what "correct" actually meant for a given feature. That was uncomfortable to admit, because it meant a lot of what we'd trained junior engineers to value — fast typing, broad framework familiarity, comfort improvising in the middle of a function — had quietly become less central to the job.

Why Specs Became The Bottleneck

Work that used to happen implicitly in a developer's head while writing code now has to happen explicitly, on paper, before any code exists. A developer writing a feature by hand can defer a decision about an edge case until they literally hit it in the implementation, glance at the surrounding code for context, and make a reasonable judgment call on the spot. An agent executing a task doesn't have that luxury in the same way. It either makes an assumption and runs with it, sometimes visibly wrong, sometimes wrong in a way that only surfaces in review, or it stops and asks a clarifying question that interrupts the whole workflow. Both outcomes are more expensive than the equivalent human judgment call, which means the cost of an ambiguous ticket has gone from mildly annoying to genuinely expensive, and that cost shows up immediately rather than three days later during code review.

  • Acceptance criteria that used to live only in a reviewer's head now need to be written down before work starts, not discovered during review.
  • Edge cases get enumerated explicitly rather than left to a developer's instinct for what "obviously" needs handling.
  • Non-goals are stated directly, since an agent has no social context to infer that a request is intentionally narrow in scope.
  • Rollback and failure-mode expectations get spelled out for anything touching production data.
  • Ambiguity that used to resolve itself quietly mid-implementation now surfaces as an explicit blocking question, often within minutes.

What A Spec Looks Like Now

The specs we write today look less like a paragraph of prose handed to a human and more like a structured brief with sections a reviewer can check against mechanically. A typical spec for a mid-sized feature includes the user-facing behavior in concrete terms, the acceptance criteria as a checklist rather than a narrative, explicit edge cases the implementation needs to handle, and a short list of things deliberately out of scope so an agent doesn't wander into adjacent territory nobody asked it to touch. We also started including a rollback plan as a standard section, something that used to live only in the head of whoever was on call, because agents executing changes to production systems benefit from the same explicit safety rails a careful human would want spelled out.

  • A concrete description of expected behavior, written as if explaining it to someone with zero context on the surrounding codebase.
  • Acceptance criteria as a checklist, each item independently verifiable rather than bundled into vague prose.
  • An explicit list of edge cases, drawn from the team's collective memory of what has broken similar features before.
  • A short non-goals section, since scope creep from an overly helpful agent is a real and recurring failure mode.
  • A rollback or failure plan for anything that touches production data or user-facing state.

Writing specs at this level of detail takes longer up front than it used to. Nobody on the team would claim otherwise. But the total time from ticket creation to a shipped, reviewed change has still gone down, because the time saved on the back end — fewer review round trips, fewer "wait, that's not what I meant" conversations, far less debugging of misunderstood requirements — consistently outweighs the additional time spent up front getting the spec right the first time.

The Failure Modes We Didn't Expect

The most interesting failures haven't been agents doing something incompetent. They've been agents doing exactly what the spec said, correctly and efficiently, when the spec itself had a gap nobody caught during review. This produces a specific and slightly uncomfortable postmortem category: the code matches its spec, the tests pass, the change ships, and it still causes an incident because the spec's author didn't anticipate a real-world condition. A human developer working from a similarly incomplete spec might have caught the gap instinctively, drawing on tacit knowledge about how the system actually behaves in production, in a way that isn't written down anywhere for an agent to draw on.

We've had to build muscle around treating a spec gap as a distinct and equally serious category of failure alongside an implementation bug, rather than quietly patching the code and moving on as if the code itself had been at fault. That reframing matters because it changes where the fix belongs. An implementation bug gets fixed in the code. A spec gap gets fixed by updating how the team writes specs for that class of feature going forward, which is a slower, more durable kind of fix, and one that pays off across every future feature of that type rather than just the one that broke.

Hiring And Team Structure

Senior engineers on our team now spend noticeably more time reviewing specs and final diffs, and noticeably less time reviewing the intermediate implementation choices an agent made along the way. That's a real shift in how review time gets allocated, and it means the skill we're now screening for in interviews looks different from a few years ago. We still care whether someone can write good code, but we care at least as much whether they can write a spec precise enough that a capable-but-literal executor won't misinterpret it, and whether they can spot the gap in someone else's spec before it turns into a production incident.

Onboarding has changed to match. New engineers now spend their first weeks reviewing real specs from shipped features, annotating where they think an agent might have made a wrong assumption, before they're asked to write one of their own. It's a different kind of ramp-up than reading a codebase and shadowing pull requests, and it took us a couple of tries to get the exercise right, but it produces engineers who are useful on spec review far sooner than the old approach did.

This isn't a smaller version of the same job with a faster keyboard attached. It's a genuinely different skill emphasis, and one we expect to keep leaning into rather than treat as a temporary adjustment while the tooling matures. The engineers who've adapted best are the ones who already thought of code as a byproduct of a decision rather than the decision itself. For everyone else, it's been a real, sometimes humbling adjustment, and we'd rather be honest about that than pretend the transition has been effortless.

Review Load Shifts Rather Than Shrinks

One assumption we had going in, that agentic execution would simply reduce total review burden, turned out to be wrong in an interesting way. Review time didn't shrink so much as move earlier in the process. A spec now gets the kind of scrutiny a pull request used to get: a second engineer reads it looking specifically for the ambiguous phrasing, the unstated assumption, the edge case that sounds like it's covered but actually isn't once you read it literally. That spec review is genuinely harder than it sounds, because catching an ambiguity in your own writing, or someone else's, requires actively imagining how a literal-minded executor might misread a sentence that reads perfectly clearly to a human who already knows what was meant.

We've started running a specific exercise during spec review that we didn't need before: someone reads the spec cold, with no context on the feature beyond what's written, and narrates out loud anywhere they'd have to guess. That exercise catches more gaps than a silent read-through ever did, because guessing silently and moving on is exactly the failure mode we're trying to eliminate, and narrating it out loud forces the guess into the open where a reviewer can actually address it.

A Concrete Example From Our Own Backlog

A recent feature request looked simple on its surface: add a "duplicate project" action to our internal tooling. The first draft spec described the happy path clearly enough, but said nothing about what should happen to project-specific integrations, webhook subscriptions, or scheduled jobs attached to the original project. An agent executing against that draft spec would have had to guess, and did, in an early test run, guess wrong: it duplicated the webhook subscriptions along with everything else, which meant the duplicate project immediately started firing webhooks meant for the original.

That single gap, caught in review before it ever reached production, became the reason we now maintain a running checklist of "commonly forgotten attachments" for any spec involving object duplication or cloning, a checklist that exists specifically because this failure mode has recurred across more than one feature. It's a small, unglamorous artifact, but it's exactly the kind of institutional memory that used to live only in a senior engineer's head and now lives somewhere a spec author can actually consult it before writing a duplication feature themselves.

← Back to the journal

Have a project in mind?
Let’s talk.

Tell us where you are and where you want to go. We'll map the fastest route between the two.

Currently accepting new clients