Why we started using Spec Kit before letting agents touch the codebase

AI coding agents are fast, but fast is not the same as aligned. Spec Kit helped us move the important thinking before implementation — where it belongs.

AI coding agents are very good at giving you code before you have fully decided what you wanted.

That sounds like a feature, and for small things it is. You describe a function, the agent writes it, you tweak a few lines, and ten minutes later you have something working. The loop feels almost unfair.

But the same speed becomes a problem once the task is bigger than a function. A missing edge case becomes an implementation choice. An unclear requirement becomes a default behavior. A sentence like "users should be able to upload documents" quietly turns into decisions about file size, storage, retries, permissions, virus scanning, metadata, and failure states — even if nobody explicitly discussed any of them.

The agent is not being stupid. It is doing what we asked: filling in the gaps.

The problem is that in a production codebase, those gaps are usually where the important decisions live.

This is why we started using Spec Kit. Not because we wanted more process, and definitely not because writing documents is fun. We started using it because our AI-assisted development needed a place where requirements, constraints, and implementation decisions could be made explicit before the agent started editing files.

The problem was not code generation

The agents we were using — Copilot, Claude, Gemini — were all competent at writing code. That was never the issue. The issue was alignment. Not alignment in the AI safety sense, but in the boring engineering sense: does the code do what the team actually intended?

For small changes, the answer was almost always yes. Rename a field, add a validation check, write a utility function — the agent gets it right because the scope is narrow enough that the prompt contains everything it needs.

For larger features, the answer was often "sort of." The code would compile. It would even pass the tests the agent wrote for itself. But it would make choices nobody discussed. A data model that was subtly wrong. An authorization check in the wrong layer. A retry strategy that conflicted with the one we already had. Each choice individually reasonable, all of them together forming an implementation that nobody on the team would have designed.

The pattern was always the same: the agent interpreted ambiguity as permission to decide.

The workflow we were replacing

Before Spec Kit, the workflow looked something like this:

  1. Write a prompt describing the feature.
  2. Let the agent generate a first implementation.
  3. Notice that it made a few reasonable but wrong assumptions.
  4. Correct those assumptions in chat.
  5. Watch it patch the code it had just written.
  6. Repeat until the diff looked acceptable.

This works, but it has a weird failure mode: the important thinking happens after the code exists.

By the time you realize the agent misunderstood the data model, or skipped an authorization check, or picked the wrong abstraction, you are no longer discussing the feature. You are negotiating with an implementation.

That last sentence is the one that made us look for a different approach.

What Spec Kit changed

Spec Kit gave us a different shape for the work. Instead of starting with "implement this," we start with a spec. What should the feature do? Who is it for? What are the acceptance criteria? What should explicitly not happen?

Then comes the plan. This is where the technical choices live: which service changes, which data model changes, what gets migrated, what gets tested, what can stay out of scope.

Then tasks. Small enough that an agent can work through them without inventing half the system as it goes.

The workflow maps to a sequence of slash commands — /speckit.constitution for project-level principles and coding standards, /speckit.specify for the what and why, /speckit.plan for the technical blueprint, /speckit.tasks to break it into units of work, and /speckit.implement to execute. There are also optional quality gates like /speckit.clarify and /speckit.analyze that you can run between phases to catch inconsistencies before they become code.

The important part is not the tool itself. The important part is that the agent is no longer working from a single prompt and a pile of repository context. It is working from a reviewed artifact — a markdown spec that a human read, challenged, and signed off on before any code was generated.

The spec became the prompt

This is the thing I did not expect. Once the spec exists, it replaces the ad-hoc prompts we were writing before. Instead of a paragraph in a chat window, the agent gets a structured document with requirements, constraints, acceptance criteria, and explicit scope boundaries.

The quality of the generated code improved — not because the agent got smarter, but because the input got better. The same way a well-written function signature tells you more than a comment, a well-written spec tells the agent more than a conversational prompt.

And because the spec is a file in the repository, it is reviewable. You can diff it. You can comment on it in a PR. You can point at line 47 and say "this acceptance criterion is wrong" before any code exists. That is a fundamentally different conversation than reviewing a 300-line diff and trying to reconstruct the assumptions that produced it.

What got better

Fewer hidden assumptions

The biggest win. Before, assumptions were buried inside generated code. After, they were surfaced during the spec phase — either because we wrote them down explicitly, or because the agent asked about them through /speckit.clarify.

A concrete example: we were adding a document-processing pipeline. The prompt version would have said something like "process uploaded documents and extract metadata." The spec version forced us to answer: what document types? What happens on parse failure? Is processing synchronous or queued? What metadata fields are required vs. optional? What is the maximum file size and who enforces it?

Every one of those questions would have become a silent implementation decision. Instead, they became explicit design choices in a document that the whole team could read.

Better review before code existed

Code review is a terrible time to discover that the feature was mis-scoped. But that is exactly what happens when the first artifact a reviewer sees is a PR. They are reviewing the implementation and the specification simultaneously, and they conflate the two.

With a spec, the review happens in two stages. First: does the spec describe the right thing? Second: does the code implement the spec correctly? Those are different questions with different reviewers. The product person cares about the first. The engineer cares about the second. Combining them into one PR review was always a compromise.

More consistent tasks for the agent

When the agent works from a task list generated from a plan generated from a spec, each task is small and well-bounded. "Add the DocumentProcessor service with methods for PDF and DOCX extraction, following the interface defined in the plan" is a much better prompt than "build the document processing feature."

The tasks also reference the spec, so the agent has context about why it is building what it is building. That sounds like a minor detail, but it means the agent is less likely to introduce something clever that contradicts a constraint defined three paragraphs above.

Easier handoff between people

A spec is a better handoff artifact than a Slack conversation. When someone new picks up the feature — because the original developer is on vacation, or because the task crosses team boundaries — they can read the spec instead of reconstructing intent from git history and PR comments.

This is the same argument people have always made for documentation, except the spec is not documentation written after the fact. It is the input to the implementation, so it exists by the time the code does.

What did not magically improve

The spec does not write itself. Someone still has to do the thinking, and that thinking takes time. For a well-understood feature, writing the spec can feel like busy work — you are writing down things everyone already knows. The temptation to skip the spec and just prompt the agent directly is always there, and sometimes it is the right call.

The quality of the generated spec also varies. If you let the agent draft the spec from a vague description, you sometimes get a spec that sounds thorough but is actually just the vague description with more words. The human review step is load-bearing. Skipping it defeats the purpose.

Version drift is a real problem too. If the spec says one thing and the implementation evolves during development, you can end up with a spec that no longer matches the code. Spec Kit's artifacts are markdown files in your repo — they do not auto-update when you change the implementation. Keeping them in sync is a discipline, not a guarantee.

And there is a learning curve. The first time through the full workflow — constitution, specify, plan, tasks, implement — it feels like a lot of ceremony. It gets faster once you have a feel for which phases need depth and which can be lightweight, but the first few runs are slower than you expect.

Where I would not use it

I would not reach for Spec Kit for every change.

If I am renaming a field, fixing a small bug, adding one validation check, or writing a short internal script, a full spec-driven workflow is too much ceremony. The overhead is real. You have to write the spec, review it, refine it, generate tasks, and keep the artifacts aligned with the code.

For small changes, that is slower than just making the change.

The break-even point, for me, is when the feature has enough ambiguity that two reasonable engineers might implement it differently. If the task is obvious — everyone on the team would do it the same way — just do it. If the task has judgment calls, edge cases, or cross-cutting concerns, the spec earns its keep.

I would also skip it for rapid prototyping. If you are exploring an idea and plan to throw away the code, the whole point is speed and discovery. Specifying the behavior of a throwaway prototype is optimizing the wrong thing.

The bottom line

Spec Kit did not make the agent smarter. It changed where the thinking happened.

Before, the agent would start coding and we would discover missing requirements by reading the diff. After, we had to make those requirements explicit before the diff existed.

That is a slower loop at the beginning, but a faster loop overall. Fewer rewrites. Fewer "that's not what I meant" prompts. Fewer architectural decisions hidden inside generated code.

For tiny changes, I still skip it. For ambiguous features in a real codebase, I want the spec first.

Not because specs are elegant.

Because code is a very expensive place to discover that nobody agreed on the behavior.