Harness engineering & SDD... the basics that work

girorme 14 min read August 30, 2026 2830 words
Callback hell? Forget that, the trend now is: prompt and markdown hell!!! xD

sdd-llm

One good spec in hand or 18 agents hallucinating? That was what I thought when I saw half the internet full of “experts” promising an entire company made of agents…

TL;DR

Harness Engineering + SDD is basically about creating an environment where agents work with more context, less improvisation, and more verification. A simple flow: SPEC → PLAN → CODE → VERIFY. The spec defines what needs to be true, the plan decides how to do it, the code implements it, and verify checks whether everything actually matches what was defined. Ready-to-use SPEC, PLAN, and CODE examples: https://github.com/girorme/artigo-sdd-harness (mentioned at the end of the article), don’t be lazy, go read it!!! xD

A snapshot of where we are

If, like me, you are not living in a cave right now, work with / like technology, you are certainly going through the current “cognitive friction” of hundreds of new repositories and topics showing up every minute (and some people thought nothing could beat JS libraries while you were reading this sentence another JS lib was created). There is so much information and so much to study that it might be worth following the “roadmap to roadmaps” xD. Just to save you some time: this article is going toward the coding side of things.

Current behavior of the “coders”

I’ll spare you all that usual speech: “We’re entering a new era”, “LLMs bring productivity”, etc, etc. The goal here is to contribute at least a little to you who, besides receiving a 550-file PR from a coworker, still drops that “looks good to me” before approving it for production.

We have already reached a point where the community itself is working on ways to reduce the craziness LLMs can generate. There has been enough time to notice and feel that code generated with no structure simply adds complexity and makes maintenance much harder.

So, in the reality show of tech life, if we had to group the biggest crowds, we would have:

1 - the team that generates code with LLMs using vague prompts, ships it to prod and keeps fixing things with no history, docs or “methods”

2 - the team that “suffers less”, using some way to track LLM-generated code and following at least a minimal process to reduce friction

Harness engineering

For those who play poker (and this applies to other games too…) and study it a little more seriously, you probably know the term “GTO”, which is always around professional tables and big profits. It stands for Game Theory Optimal and aims to create a strategy that is less exploitable by opponents. It is the “perfect” way to play, a formula that uses statistics and mathematics to find the best way to win.

Just like GTO, harness engineering is a “profitable” strategy that can be translated into: the perfect environment for AI (LLMs) to work in.

And why is it important to keep this concept in mind? Basically, what will dictate the quality of the code generated by LLMs is the environment where they are running. That means it is not enough to write a prompt and expect magic to happen, because most of the time code generated without some kind of “armor” is hard to maintain and tends to age in the worst possible way.

This optimized environment is the harness.

SDD, loop engineering and other shenanigans

When we start using AI agents to develop software, one thing becomes pretty clear: telling the agent what to do is not the same as telling it how to know when it is done. A prompt may be enough for a simple task, but as complexity grows, we need something more stable to represent the intention behind what is being built. So -> SDD.

The idea is “simple”: before starting the implementation, we define a specification describing what we expect from the feature / bug fix / a bunch of other things. That specification becomes the reference throughout the development process, reducing the dependency on context that only exists inside the conversation with the agent.

The most important point here is that the spec should not be seen as a static document that we write once and forget about. It participates in the development process itself. In some companies I already see teams putting a lot of focus on reviewing these specs: the spec becomes the source of truth.

The loop

And when the spec becomes the source of truth, a natural consequence appears: we can stop thinking about the agent as something that receives a prompt, executes a task and waits for the next command. We can start thinking in terms of a loop.

This idea has been gaining a lot of space in the agentic development community. Instead of constantly telling the agent what the next step is, we build a process that can understand the work, execute part of it, verify the result and decide what needs to happen next. The focus moves away from the prompt and toward the system that drives the agent.

In the context of SDD, the spec works as one of the reference points for this loop. The agent does not just receive an instruction saying “implement this feature”. It receives a definition of what needs to be true when the work is finished.

We can imagine something relatively simple:

Spec → Plan → Implement → Verify → Feedback → …

The idea is not to create some magical pipeline that runs by itself. The point is that each step leaves enough information for the next one, and verification can tell us whether there is still work to be done.

That last part is probably the most important one. A loop should not simply ask the same agent whether it “thinks” it is done. We need external mechanisms that can verify the result: tests, acceptance criteria, lint, type checking, static analysis, review by another agent and other forms of validation.

This also changes the role of the Harness a little. The Harness provides the environment, context, tools and rules the agent uses to work. The loop uses that environment to keep the work moving continuously.

In other words: the Harness defines the environment where the agent works; the loop defines how the work progresses until it reaches a verifiable state.

And this is exactly where the quality of our spec starts to make a huge difference. The better we can transform intention into clear and verifiable criteria, the more autonomy we can give the loop without simply increasing the amount of prompts or human supervision.

SPEC → PLAN → CODE → VERIFY

Before getting into the details, I like to think about this process in four stages:

SPEC defines what needs to be true.

PLAN defines how we are going to do that inside the existing system.

CODE turns the plan into implementation.

VERIFY tries to prove that the result actually matches what was defined.

It sounds obvious, but the separation matters. If we mix everything into a single prompt, the agent starts making architecture decisions while it is still trying to understand the requirement. By separating the stages, we can review the intention before implementation and review the technical strategy before writing code.

This also does not mean that every stage needs a different agent. What matters is separating responsibilities, not necessarily the tools.

What makes a good Spec?

A good spec is not simply a bigger prompt asking the LLM to implement something. It needs to provide enough context to understand the problem, the constraints and, most importantly, what it means to finish the work correctly.

In my case, the process starts in a pretty simple way. Using Copilot, for example, I can run /spec and provide a task, a business need or describe a bug. From there, the agent reads the available context and produces the specification before any implementation begins.

Context

The first point is giving context to the LLM. An isolated spec may describe a feature, but it will hardly capture the particularities of an existing system.

So before generating the spec, the agent can consult project instructions, architecture documentation, business rules, technical decisions and other existing artifacts.

With MCPs, this context can go beyond local files: we can connect the agent to Jira, Notion, databases, internal tools, repositories or other knowledge bases.

The idea is simple: the closer the agent is to the reality of the system, the less we need to depend on assumptions.

Intention and analysis

Another important point is defining how we want the agent to analyze the problem. Before writing the spec, it needs to understand the business problem, evaluate architectural impact, look for ambiguity and think about possible failures.

We do not need to turn this into an attempt to reproduce the LLM’s internal reasoning. What we want is to define an analysis protocol: which questions the agent should consider before producing the specification.

In my case, that includes identifying assumptions, separating what is inside and outside the scope and paying special attention to edge cases.

What will be built

After understanding the context and the problem, the spec needs to make clear what will be built.

The objective explains the why: what problem are we solving and what value are we delivering? The functional description explains the what: what behavior do we expect to observe when the feature is ready?

This separation prevents the spec from getting tied to a specific implementation. We want to define the expected outcome before deciding exactly which modules, classes or providers will be created.

Flow and contracts

The spec also needs to make the flow understandable. For an API, we can describe something like:

Request → Validation → Data → Business Rule → Persistence → Response

This is not the detailed solution design yet. It is a way to make clear how information enters the system, what we expect to happen and what the result should be.

Contracts and constraints that should not remain implicit also belong here: endpoints, events, data models, security, observability and what is outside the scope.

Acceptance criteria

This is probably one of the most important parts: turning a description into something that can actually be verified.

This is where acceptance criteria come in, describing success scenarios, error scenarios and expected behavior. In my case, I use Gherkin to structure these scenarios in a way that stays relatively close to the business language.

The idea is to move away from “implement X” and get closer to:

Given a certain context → When a certain action happens → Then a certain result should happen.

From there, these criteria can be connected to tests and used by the rest of the loop as a verification reference.

Definition of Done

Finally, we need to define what done actually means.

Working code does not necessarily mean the task is complete. We may require tests, lint, migrations, documentation, integration or other project-specific requirements.

The Definition of Done closes an important part of the loop: it provides objective conditions for deciding whether the work is really finished.

And after the SPEC?

The SPEC does not need to carry everything. In fact, the more we try to put into it, the greater the chance of turning the document into an implementation plan in disguise.

After the SPEC comes the PLAN.

Now the question changes:

How are we going to build this inside the system that already exists?

obs: Here I am talking about a scenario where we are working on an existing system, whether it is complex or not.

The PLAN receives the SPEC and the codebase as inputs. This is the stage where the agent analyzes the current architecture, looks for alternatives and decides the best way to implement what was specified.

The same spec can have several possible implementations. We can create a new service or extend an existing one, process something synchronously or asynchronously, reuse an abstraction or create a new one.

The PLAN exists precisely to make those decisions before they become code.

What makes a good PLAN?

The PLAN (that famous /plan that exists in Copilot / Claude etc, but now customized with our own requirements) starts by analyzing technical alternatives. The agent needs to understand what already exists, consider different approaches and choose one based on simplicity, performance, maintainability and architectural consistency.

Then comes the solution design.

Here we can detail components, call sequences, persistence, events, integrations and dependencies. This is where diagrams, flowcharts and other system design elements start to make more sense.

The difference becomes clearer when we put them side by side:

SPEC: “When X happens, the system should produce Y.”

PLAN: “To do that in this codebase, we are going to change A, create B, call C and persist D.”

The PLAN also breaks the implementation into smaller tasks. Each task should be small enough to be executed and validated in a single coding step.

This helps us get closer to atomic commits, reduces the amount of context needed for each stage and makes it much easier to identify where something went wrong.

This is also where more implementation-specific details come in: framework modules and providers (language behavior and best practices too), migrations, files that will be created or changed, the sequence of changes and validation commands.

BDD and TDD can also show up again at this stage, but now with a more technical role. The SPEC defines the behaviors that must be satisfied; the PLAN can define how those behaviors will be covered by unit tests, integration tests and other checks.

In the end, the PLAN turns intention into a concrete implementation path.

CODE: executing the plan without reinventing the feature

After the SPEC and PLAN, it is time for /code.

Here the intention changes again: the agent is no longer trying to understand the problem and it also should not be redesigning the solution. It receives an already defined plan and starts acting as a technical executor.

The main point of this prompt is to reduce improvisation. Instead of asking something generic like “implement this feature”, the agent receives smaller tasks, project context, acceptance criteria and an execution order.

This is also where TDD starts to appear in a more concrete way. For each part of the plan, the agent can first turn the expected behavior into tests, then write the minimum code necessary to make them pass and finally refactor.

In a simple form:

Test → Implement → Refactor → Validate

Another important point is making it explicit that the agent should reuse what already exists before creating new abstractions. In large codebases, one of the fastest ways for an LLM to increase complexity is to locally solve a problem that the system has already solved somewhere else.

The /code step can also control the level of autonomy. Small changes can move continuously, while more sensitive changes can be executed task by task, allowing human review between stages.

In the end, the role of this stage is relatively straightforward: execute the PLAN while respecting the contracts defined in the SPEC and produce code that already comes with verification mechanisms.

With that, we close a pretty simple sequence:

SPEC → PLAN → CODE → VERIFY

Each stage reduces a different kind of uncertainty. The SPEC reduces uncertainty about what we want, the PLAN about how we are going to do it, and CODE about how to turn that decision into a verifiable implementation.

Putting it all together

From here, the picture starts to get more interesting:

sdd-llm

The point is not to create more bureaucracy around developing a feature. It is to create memory and verifiability for a process that, without this, depends far too much on the conversation with the LLM.

At the end of the day, maybe the biggest gain from SDD is not writing more documentation. It is being able to transform an intention into something that another agent, another person or even the system itself can understand, execute and verify.

And then we go back to the beginning: maybe we do not need 18 hallucinating agents.

Maybe one good spec already solves a big part of the problem.

That’s it…

It is possible to generate features and systems with exceptional quality using LLMs, you just need to stay in control from the beginning!

I’ll leave a repository in the references below with examples of spec, plan and code files so you can use them in your own projects. Each tool has its own usage pattern. In Copilot, for example, we can create any markdown file inside project/.github/prompts/(spec/plan/code.prompt.md), while in Claude you can have skills with the same content

Leaving a shout-out here to Mr. Zéé Hudson, who I had the honor of working with, and who one day created and spread the skeleton I mentioned in this article. Since then, my view of how to use LLMs more efficiently has completely changed :)

References