freedzhost.cloud

BMAD with small models: the model proposes, code decides

BMAD is a method for driving AI agents through a project: split the work into stories, give each story its acceptance scenarios, have agents implement them one by one. With a frontier model, a loose version works. With a 9B or 27B model on your own card, it drifts within the hour. Here is the strict version we run, where everything that can be checked is checked by code.

What you get. Small local models that deliver real projects, story after story, without drifting: because everything that can be checked is checked by code, and you keep the final word.

0. The BMAD method in one minute

BMAD (an open-source method for AI-driven agile development) gives each phase of a project to a specialised agent persona, each producing a document the next one reads:

Its strength is the chain of documents: each agent starts from written context, not from a vague chat. Its weakness with small models is that every hand-off is judged by another model. Below is how we kept the chain and replaced every judgement that has one right answer with code.

1. Splitmodel proposes stories2. Story lintcode: schema, files, deps3. You approvethe split and its file list4. One story aliveagent writes the code5. Scenariosdata, run by pytest6. MergeCI green, human readsmodelcodeyou
The model proposes, code judges, you decide. A model splits the work into stories; a linter checks each one against the schema; you approve the split. Then one story at a time goes to an agent, and its scenarios run as plain tests. No model ever delivers a verdict.

1. Stories with a schema

A story is not a paragraph. It is a record with fields: an identifier, a rationale, the files it may touch, the stories it depends on, its scenarios. We write the backlog in TOML and validate it with a small program, a story linter, that refuses by name:

The linter has no network and no model: run it three times, you get the same bytes three times.

2. The model proposes the split, you approve it

Splitting a project has many right answers, so it stays a model's job. But its output is a proposal: the linter checks it, then you approve it, file list included. A constraint written by a model and applied without review is only a suggestion with good manners.

3. One story alive at a time

The next story starts only when the previous one is merged. And the backlog's state is never stored: it is derived on every call from the runs that reference each story. A stored "in progress" survives a dead run and lies in silence; a derived one cannot.

4. Scenarios are data, run by pytest

No BDD framework, no matching a sentence to a step. A scenario is a small object, Given / When / Then, sitting beside the code that carries it out, and the suite runs them as parametrized pytest. The verdict is pytest's: passed or failed, with the failing line.

Scenario("S4", given="a story with a cyclic dependency",
         when="the linter reads the backlog",
         then="it refuses and names both stories")

5. The merge stays human

Automatic merging is an exception, off by default, with a list of forbidden zones: the test judge itself, the code that holds Git rights, the sandbox. A change there always waits for a human.

Traps

Re-read the rules at every task. An agent keeps freedom of implementation and loses freedom to drift only if the doctrine is in front of it each time, not once at the start.

A scenario that never fails proves nothing. Break the code on purpose and check it goes red (see tests validated by mutation).

Sources

Found this useful? Tell us what to measure next.