27 Jul 2026
This article covers a journey we took recently, turning an idea into a functional proof of concept software. It covers the journey from inception to delivery, in chronological order.
The idea was not ours. That is rather the point.
It arrived in conversation. A school leader told us their team was losing hours to a repetitive administrative task. The kind of work that does not demand much thought - but quietly drains the capacity for the work that does.
We asked ourselves one question: how can we help with that?
The ordering here matters. We did not build something first and then go looking for a problem it might solve. The problem walked in first, described by the person living with it. Real words, from a real professional. No market report can compete with that.
The pain was named for us. The problem decomposition was our contribution.
We took the workflow apart and rebuilt it as a chain of small, tractable steps. Then we asked of each step: is this a job for AI, or for boring, dependable, deterministic code?
The split follows a simple rule. Where a step must make sense of messy, human-made material, AI earns its keep. Where a step must produce the same correct result every time, plain code wins. We have seen too many teams reach for AI by default, on every step, and pay for it twice - in time and in compute. Knowing where the boundary sits is half the craft.
We also took a step back with our design team and asked a broader question: how should our software feel? The answers hardened into standing principles that every app we build must follow. Simplicity above all: ship the minimum that solves the stated problem, nothing speculative. Effortless interfaces: immediately usable by a non-technical person, with no training - one primary task per screen, the main action obvious, nothing on screen "just in case". Our users are experts in their own field and short on time. The software must respect both.
Finally, we checked the market. No off-the-shelf tool genuinely covered the need. We were clear to go.
Before the interesting work could start, the prototype needed a home. This was deliberately a short chapter of the journey, so it gets a short section.
Every prototype we build starts from the same application template and runs in its own isolated environment. It has exactly one point of contact with our core systems: a service that tells it who the current user is and what they may access. Nothing more. If an experiment ever misbehaves, there is very little it can reach.
We have written about this architecture before - the sandbox for our coding agents, and the sandbox for what they build. The point of laying these foundations up front is what they buy later: the freedom to move fast without losing sleep.
With the foundations in place, we did something that felt almost old-fashioned. We stopped and wrote things down. Extensively.
The toolchain was SpecKit, driven through Claude Code. Its workflow runs constitution → specify → clarify → plan → tasks, and we followed it in that order (coming back multiple times).
The constitution came first. It encodes our non-negotiables: test-first development, simplicity above all, think before coding, and the interface principles from our design work. Writing it before any feature spec has one purpose - every later decision gets a referee. When a debate comes up mid-build, we do not argue from taste. We check the constitution.
Then the specification itself. AI drafted essentially all of it. We would feed it a starting point - sometimes a one-line idea, sometimes a few hand-written paragraphs, sometimes exact technical requirements - let it analyse, and run structured question-and-answer sessions to close every open decision. The result is a substantial packet: user stories, acceptance scenarios, research notes recording each decision with its rationale and rejected alternatives, a data model, API contracts, an implementation plan.
And we reviewed every word. AI drafts; humans approve. That division held for the whole journey.
One thing worth stressing about those clarification sessions: the answers came from our engineering expertise, not from the customer. Data retention models, asynchronous processing, failure handling - these are not decisions a school should be asked to make. The customer's voice belongs to the parameters that are genuinely theirs (how long records are kept, say) and, above all, to testing the finished software.
Finally, the specification is not a write-once artefact. It lives alongside the code and is updated in the same working loop. For small changes, we edit the spec first and the software second. In a very real sense, the spec is the product.
Then came the surprise, though only a slight one: specifying took far longer than building.
Once the agents began writing code, the build was a sprint in every sense of the word. And they wrote effectively all of it. That is by design. Our goal is to write no code ourselves if we can help it. Humans set the architecture, steer, and review; agents produce the code. So much of modern software is boilerplate - API definitions, serialisers, plumbing - that humans are simply no longer the best tool for typing it out.
The work ran in increments: the core plumbing first, then the user-facing capabilities one at a time, then a polish pass. Every increment had to pass a gate before the next began. The gate had two layers: an automated audit hunting for over-engineering and other known AI issues, and a human review of the architecture - what was built, where it landed, how the modules were organised. Anything off was fixed on the spot, before moving on.
This was not fire-and-forget. The agents moved fast enough that supervising them was an engaged, hands-on job. The breaks were short.
What did the gates actually catch? Honestly: minor things. Some code repetition. The occasional unneeded abstraction. And one lesson worth recording: many of the audit's "unnecessary abstraction" flags were false positives. The audit could only judge the code as it stood at that moment; we knew the next increment would call that abstraction from ten places. An automated gate stays useful precisely because a human who knows what comes next stands behind it.
The one correction that needed genuine human taste was interface consistency - one screen wrapped its content in a card component, the next did not. Spotted, fixed, moved on.
Nothing went sideways. Not because agents never drift, but because a gate at every increment means small drifts never get the chance to grow.
With the build complete, we did not rush it in front of users. We used it ourselves. Properly.
There is a discipline to being your own first tester. Use the software, then step back and ask the questions experience has taught you to ask. Which failures are we not looking at? Things fail in more ways than anyone writes down - so we added machinery for stuck jobs to notice and recover on their own. How would a user find something they worked on a while ago? So search and sensible naming went in, unprompted.
The hardening fell into three buckets. Observability and trust: every AI interaction traceable end to end, with sensitive content properly redacted from the traces. Robustness: absorbing the failures of the outside world - rate limits, stuck jobs - without the user ever noticing. And user experience: the small refinements you only discover by standing in the user's shoes.
The best moment came from a colleague's simple question: what exactly happens to charts when a document is parsed? We checked. Images were being described well - but a chart came back as just "a chart". The numbers that existed only graphically were silently vanishing. The fix was an instruction: anything whose content carries meaning must have its values translated into text, with estimates where the page prints no figures. An approximate proportion beats a sentence that says "a bar chart". Evidence that lives only in a graph now reaches the output instead of disappearing.
Only after the wider team had reviewed the finished whole - and their feedback had driven the final touches - did we call it good enough to show the people who named the pain in the first place. That bigger loop comes next. The sequencing is deliberate: their time is precious, so we spend ours first.
One chapter remains, and it is still being written: evaluations.
Traditional software is tested pass-fail: does the feature match the spec? AI-powered software does not offer that luxury. It is probabilistic - mostly right, occasionally wrong - so quality becomes a statistical question. Working software is not the finish line. We must measure, continuously, whether it is right.
The evaluation strategy was built the same way as everything else. AI did the research and drafted the documents; a human reviewed them first, and a second AI review then caught a few statistical subtleties the human pass had missed. Reviewing the reviewer works in both directions.
Two decisions stand out. First, real data beats synthetic. Synthetic data has its place, but the structure of real documents is something no generator quite reproduces - so real data, handled under our safety principles, is the path. Second, the domain experts who will calibrate the system will never be pointed at raw engineering tools. They get a radically simplified interface, so that giving us their judgement takes minutes, not hours.
More on this once the work concludes. Progress, not perfection.
So what made it all work? Not the agents, or any other AI feature alone. Not the fabled Fable model.
The honest conclusion of the whole journey is that reliability is a property of the process. The agents wrote nearly everything, and nearly everything they wrote was right - but only because every step was anchored to a specification, gated by review, and supervised by a human who stayed interactively present. Take away the harness, and the same agents would happily drift.
Models keep improving - each generation raises the quality of the output noticeably. Our job is to keep raising the harness to match. The next step is to hand more of the human's share to agents too: code review, security review, adversarial testing, all built into the harness itself. It will never reach 100%, and it should not. But there is still quite a way to go until we reach the "optimal" split.
Author: Petr Klus