Within a few days of coming back, the collectors were running. The pipeline ran end to end. The November bug that had nearly killed the project was fixed.
I sat down to write more code, and realised I didn’t know what to write next.
The harder problem
The audit after my break had found a specific bug: five collectors that weren’t being invoked at runtime. Finding the bug took less than a week. Fixing it took a day.
The bug wasn’t the real problem. The real problem was that I had never written down what the system was supposed to do. There was no specification and no contract between what I was building and what it was being built for. The collectors had stopped running silently in November, and I hadn’t noticed for nearly two months, because no document said that they should be running or what they should produce.
I’d been working for nearly half a year without a written description of what success looked like. In 2025 I would have called a specification bureaucracy. Specifications were what enterprise teams wrote because they had to coordinate with each other; a solo engineer could hold the spec in their head. Except the spec hadn’t been in my head. It had been scattered across half a year of decisions, none of them written down and all of them subject to drift when I changed my mind. The codebase had grown beyond what I could hold, and the spec had grown beyond what I could remember.
I opened a fresh document instead of the code.
I started with the mobile app specification. It described what the app needed to do for the user, what it needed to look like, and what constraints it had to live inside: model size, inference latency, battery cost, privacy, offline behaviour. It also defined what a “scan” was, end to end, from the user opening the camera to the result appearing on the screen.
In 2025 I had built the model and assumed the app would figure out how to use it. Now I did the opposite. I wrote what the app needed before I wrote what the model would do. The constraints came first, and the engineering had to answer them.
So why did that help? Because a specification is useful when it removes ambiguity. It turns “make the model good enough” into “hit this latency, this size, this accuracy, and this behaviour when confidence is low”. That is a much clearer problem.
The ML specification then had to answer the mobile specification’s questions. What was the maximum acceptable inference latency? Could the model run quantised without losing accuracy? What confidence labels would the UI display, and how were they calibrated? When the model wasn’t sure, what did it return? Where the answers conflicted, I went back and forth between the two documents until they were consistent.
For the first time, the system had defined edges. Before that, it had mostly been code.
Most of the back-and-forth surfaced things I hadn’t realised I was assuming. The mobile side wanted four confidence tiers; the ML side had been computing scalar probabilities with no calibration. The mobile side wanted a 100ms p95 inference target; the ML side had been training a model that ran at 280ms on the development machine. The two documents didn’t agree because the work hadn’t been designed for them to agree. Each iteration narrowed the gap. By the second week the documents could be read in sequence and made sense.

Reading help
I gave both documents to ChatGPT and to Claude, and asked them what they were missing.
This was the first time I had asked an AI to do anything on this project. The rule I had set for myself, don’t use agents and build it alone, had not been replaced when it broke. It had just stopped being absolute. I wasn’t ready to hand the work over, but I was ready to have it read.
They asked the kinds of questions a careful reviewer would ask. What happens when the model is wrong, and how do you know? What is the contingency if the accuracy target isn’t reached? What does the system do at the edges, with species it has never seen, a photo of nothing, or a hand half-blocking the leaf?
Most of the questions were ones I would have got to eventually. Some of them I would not have. The contingency question was one I had not written down anywhere. When the reviewers asked, I did.
The collaboration was modest. I wrote the documents, and the AI tools reviewed them. The documents were stronger for the reading, and that was enough to break the habit of refusing those tools entirely.
Four roadmaps in one day
With the specifications in shape, I wrote a roadmap.
Then I wrote it again.
Then I wrote it again.
Each round was reviewed with ChatGPT and Claude before the next.
By the end of 25 January, version 1.3.0 of the Project Roadmap included resource requirements, an ASCII Gantt chart, version-control rules, success metrics, backup and disaster-recovery notes, a security audit, and phase testing gates with exit criteria for Weeks 4, 8, and 12.
There was no team and no stakeholder meeting waiting for these documents. There was just me, writing down enough structure that I could no longer pretend the vague parts were clear.
I knew, while I was doing it, that this level of detail was more than a solo project normally required. I wrote it anyway. In 2025 I would have called it bureaucratic overhead. In January 2026 it was the first thing that had made the project feel possible again.
The slightly irritating truth is that boring process becomes useful when intuition stops scaling. One person can hold a small project in their head. A model, a mobile app, a data pipeline, a launch plan, and a set of failure criteria is no longer a small project, even if the team size still says one.
The Task structure
The roadmap was followed by a formal task breakdown. The work was sequential, numbered, and tied to acceptance criteria. There were thirteen tasks in the first phase, and I knew what each of them was, what order they came in, and what done meant for each. None of that had been true for any week of 2025.
The tasks were not exciting. They were environment configuration, PostgreSQL database setup, API key configuration, and monitoring infrastructure. It was the kind of work I would have skipped over in 2025 to get to the more interesting parts, and later paid for skipping. I did them in order this time, and I closed each one when its acceptance criteria were met.
There is a useful discipline in that, once you stop resenting it. A task with acceptance criteria tells you what done means before you start. For a project I had been steering by instinct, that was a bigger change than the tooling.
The kill switches
Among the rules I wrote during those two weeks, several were designed to tell me when to stop. If the application crashed more than 2% of the time for seven consecutive days, I would halt feature work and fix stability. If day-7 retention fell below 25% by Month 2 of operation, I would stop adding features and investigate why people weren’t coming back. There were others.
One of them was the species accuracy stage gate. Written in formal language in the spec, it had six pass criteria: accuracy thresholds, model size budgets, mobile hardware targets, ANN search latency, ONNX quantisation requirements, and hardware acceleration coverage. In my head, it was simpler. If the model didn’t reach 75% species accuracy on five thousand species by a specific date, I would stop pushing for the ML model and pivot the app to something else.
It would also mean the hypothesis had failed. The architectural bet was that composed AI components could work on device, without the cloud. If the model missed that gate, the bet had lost the test I’d set for it.
I didn’t tell anyone about the rule. I didn’t put it anywhere outside the spec it lived in, which nobody else was reading. The other rules were for the project. This one was for me.
The project would live or die depending on whether it made that stage gate.
The other rules checked external failures: a crash rate too high, a retention rate too low, an accuracy too poor for the product to be honest. The stage gate checked an internal one. I knew that without a written rule, I would push through a failure I should have stopped at and pay for it later. The rule was there to stop me when I wouldn’t stop myself.
I wrote it. I dated it. I moved on.
The work begins
Task 1.1 landed on the evening of 25 January. Task 1.2 landed the next day. Task 1.3 followed a few days later.
I had a plan now. I had a way to know whether the project was succeeding, and a way to know when I was lying to myself. None of that had been true a month earlier.
I did not know whether the plan was right. I knew I had one. For the first few weeks back from my Christmas holidays, that was enough.
To be continued.