---
title: 'Decision records for a solo project'
canonical: 'https://www.dealmobile.co.uk/writing/decision-records-for-a-solo-project'
url: 'https://www.dealmobile.co.uk/writing/decision-records-for-a-solo-project'
type: Post
published: '2026-10-26'
summary: 'Mullet now keeps eleven short decision records beside its code, each a context, a decision and its consequences. Why a one-person project bothers, what they cover, and how they’re kept in step with the settings reference.'
---

# Decision records for a solo project

Published 2026-10-26

![Eleven decision records, listed by number. 0001, cost comes from the provider. 0002, one shared spending envelope. 0003, safe toolsets for other providers. 0004, a server token and Host and Origin checks. 0005, protected editor and shell files. 0006, a crashed gate pauses the job. 0007, the sandbox behind an interface. 0008, dependencies and one-way steps. 0009, reviewer findings and standards. 0010, an idle limit and a grace window. 0011, hardened throwaway containers.](https://www.dealmobile.co.uk/sites/default/files/styles/almanac_shot_md/public/content/images/posts/decision-records-for-a-solo-project/hero.png.webp?itok=W7RomGY9)

For most of Mullet’s life the reasons behind its design lived in two places: commit messages and my memory. Neither helps someone reading the code. A commit message explains a change, not a rule, and it drops out of view as soon as the next commit lands. My memory is worse. So Mullet 2.3.0 added `docs/adr/`, a folder of short decision records, and I wrote down the main choices, most of them already made.

## The format

Each record is one decision. The folder’s README gives the template:

```text
# NNNN. Title

Status: Accepted | Superseded by NNNN
Context: the problem and the forces on it.
Decision: what Mullet does, with the real names and defaults.
Consequences: what this makes easy, what it costs, what to watch.

```

Files are numbered in the order they’re written, and a number is never reused. When a decision changes, it gets a new record, and the old one’s status becomes “Superseded by” the new number. Nothing is edited out of the history.

The phrase that does the most work is in the Decision line: the real names and defaults. A record that says “spending is capped” is an opinion. One that says `budget.envelope.daily` and `.weekly` cap everything paid, in UTC windows, worked out in `mullet/jobs/budget.py`, can be checked against the code, and visibly goes wrong when the code changes.

## The eleven

| No. | Decision |
|:--|:--|
| 0001 | Cost comes from the provider’s own responses |
| 0002 | One shared spending envelope plus per-rung limits |
| 0003 | Toolsets for delegated models are a per-provider setting |
| 0004 | `mullet serve` needs a token and refuses foreign Host and Origin |
| 0005 | Editor, dev container, shell hook and package-manager files are protected |
| 0006 | A crashed gate or missing verdict pauses the job |
| 0007 | The sandbox sits behind a backend interface; bubblewrap is the default |
| 0008 | The planner states dependencies, one-way doors and open questions |
| 0009 | The reviewer gives findings and reads the repo’s own review standards |
| 0010 | Silent workers are stopped and finished ones are given a short grace window |
| 0011 | Docker and Podman run each command in a hardened, throwaway container |

The newer ones have posts of their own: [the sandbox interface](https://www.dealmobile.co.uk/writing/putting-the-sandbox-behind-an-interface.md) (0007), [the task plan](https://www.dealmobile.co.uk/writing/a-task-plan-that-knows-what-depends-on-what.md) (0008), [the reviewer](https://www.dealmobile.co.uk/writing/the-reviewer-that-can-only-say-no.md) (0009), [the silent worker](https://www.dealmobile.co.uk/writing/stop-the-silent-worker-but-let-a-finished-one-leave.md) (0010) and [the container backend](https://www.dealmobile.co.uk/writing/a-container-backend-stricter-than-the-usual-docker-recipe.md) (0011). Four of the older ones show what a record adds to a post.

0001 exists because [the bill didn’t match the ledger](https://www.dealmobile.co.uk/writing/we-checked-the-bill-and-our-ledger-was-wrong-by-half.md). The post tells the story. The record states the rule that came out of it: a cost the provider states is used as is, unpriced tokens get a fallback price set high on purpose, and an attempt’s recorded cost never falls below what the provider’s responses say. Someone changing the usage meter needs the rule, not the story.

0002 says why spending limits are one shared envelope and not only a cap per job: several jobs and providers can run in a day, and a cap on one job doesn’t stop their total from growing. It also says plainly that nothing is limited until a value is set.

0003 records why providers other than Claude’s own get a safe set of tools by default, and why giving one a terminal has to be a deliberate edit ([safe by default](https://www.dealmobile.co.uk/writing/safe-by-default-a-local-ai-server-and-a-cheap-model-with-a-shell.md)).

0006 records why a crashed gate pauses a job instead of judging it. A bug in my checks says nothing about the worker’s change. Treating a crash as a pass lets bad work through, and treating it as a fail wastes attempts and can bar a good model ([gates, not a model’s word](https://www.dealmobile.co.uk/writing/gates-not-a-models-word.md)).

## Consequences are the useful part

Context and Decision are easy to write. Consequences is where a record earns its place, because that is where the cost goes.

0008 admits that the planner’s new `after` lists are checked against the order steps run in but don’t change it: they don’t reorder steps or run them in parallel, and whether the new planner prompt reduces the failures it was written for needs new ledger figures to tell. 0011 admits that the sandbox image’s labels are not a signature, and a deliberately forged image would pass. Neither is the kind of thing that makes it into a README, and both are exactly what a contributor would trip over.

## Who reads them

Contributors, I hope. Me in six months, certainly. And the coding agents that work on the repo: `AGENTS.md`, the file written for them, points them at `docs/adr/`. An agent about to “simplify” the pause on a crashed gate into a plain failure is the reader I most want to stop, and a record that says why is a cheap way to try.

## Keeping them in step

The settings reference, `docs/CONFIGURATION.md`, says what each setting does. The records say why. The two point at each other: the reference’s Sandbox section names 0007 and 0011, its Jobs section names 0010, and it ends with a link to the folder. Each record names the settings it concerns, so a search for a setting’s name finds its record as well as its documentation.

That is all done by hand. No test checks that a record still matches the code or the reference, and I’ve already broken one of my own rules: the README says a record is 25 lines at most, and 0007 runs past that. The two sandbox records are by far the longest, because the guarantees they describe are long. I’d rather have a long, exact record than a short, vague one, but it’s a rule I wrote and haven’t kept.

## Worth it on your own?

Yes, because they cost so little. Most of Mullet’s records are under 250 words. A short one takes minutes, and it saves the conversation where someone, or something, asks why it works this way, or quietly undoes it.

The records are in `docs/adr/` in Mullet 2.3.0. The code is at <https://github.com/tomgould/mullet-public>.

- **Projects:** [Mullet](https://www.dealmobile.co.uk/projects/mullet.md)
- **Tags:** [Python](https://www.dealmobile.co.uk/writing/tag/python.md), [AI](https://www.dealmobile.co.uk/writing/tag/ai.md), [Mullet](https://www.dealmobile.co.uk/writing/tag/mullet.md)

---

Index of this site: [llms.txt](https://www.dealmobile.co.uk/llms.txt)
