Rafe Blandford

Agentic product development, up close

What building RafeOS with AI taught me about compression, discipline and judgement.

Rafe Blandford
13 min read
RafeOS — What AI-native engineering actually looks like

RafeOS is my personal agentic layer, reading and (carefully) acting on my own data and services. I’ve written elsewhere about what it is, why I’m building it, and the product shape it is starting to take.

This piece is about what sits underneath it: a real, non-trivial codebase (a modular API, a command-line tool, a progressive web app, multiple agent integrations, and associated infrastructure) built and maintained over several months almost entirely through collaboration with AI.

I'd say 99% of the code has been generated by Claude Code or OpenAI's Codex, and I've leaned on AI almost as heavily for discovery, product strategy, specification, experience design, technical solution design, testing and deployment.

The interesting lesson is not “AI writes code”. We know that now. The more important lesson is that AI can compress the whole product-development lifecycle. You can move from a vague need to a working feature at a speed that still feels slightly unreal.

But that compression leaves gaps.

An agent can research, propose, design, implement and test against the world it can see. But it still needs you to know which use cases matter, which risks are unacceptable, and which features should not exist at all.

That is where the human still earns their place; not by typing every line of code, but by shaping the conditions under which good code is likely to be produced.

RafeOS wouldn't exist if I weren't building it this way, because I'm probably not a good enough developer to build it any other way. What's changed is the speed, the scope and the breadth of what I can now attempt. I can now build something genuinely personal, across a dozen domains, at a pace I couldn't otherwise manage.

The why and the what are still mine. The how is increasingly agentic. And the how is genuinely impressive.

The thesis: compression, and the gaps it leaves

When you build for yourself, with an agent, you can compress, or even skip, most of the stages a textbook process would tell you to do in full. Discovery becomes an interview. Specification becomes a working plan. Design becomes a set of generated options. Implementation happens in loops measured in hours and minutes. Testing is created alongside the code. Iteration starts almost immediately.

This is the gift.

The risk is that everything becomes plausible too quickly. Prototype is not production. Spike is not solution. The messy middle of any build is still there: the real behaviour nobody wrote down, the integration weirdness hidden behind a login flow, the edge case you forgot to mention, the assumption that seemed safe until it met reality.

It just arrives sooner. AI really doesn't know what it doesn't know.

Discipline context closes the gaps: the strategy explainer (useful context in PROJECT.md), the solution spec document (PLAN.md), the decision log (DECISIONS.md), the written conventions, lessons and architecture (in a CLAUDE.md), the tests, and (most powerfully) the decision not to build something (BACKLOG.md).

An .md files describing a plan for spike (exploring Haiku classification)

None of that is exotic AI tooling. It's ordinary product development rigour, restated and reframed for the AI-first world. Doing it well takes time.

Where that rigour used to keep a human team coherent, now it stops a fast, confident, slightly amnesiac collaborator from compressing your build into something that mostly works.

The clearest way to show this is one feature, end to end.

One feature, end to end: messaging

Messaging is the most complete worked example I have. I wanted RafeOS to help me with messages across email and chat, because that was where things were genuinely falling through the cracks: missed replies, fragmented context, and the small but accumulating cost of checking too many places.

Starting with the problem, not the implementation

I started by having the AI interview me: what are the pain points, what would good look like, where does the current mess hurt?

Out of that came a small set of jobs (triage; catching replies I'd missed, email especially; pulling context from previous conversations; occasionally getting a draft for a reply). Letting the agent run a discovery interview is a technique I used previously in knowledge work. It's a fast way of drawing out the problem space, but you need decent self-awareness and a goal in mind.

The most important decision was what to leave out

Fully automated replies were off the table from the start: anything going to an external person needs my explicit approval, however good the draft.

Choosing not to do something is consistently the more powerful choice, and it's the one a token-munching agent will rarely make for you. For me, this is the phase that is most human-centric.

Choosing a backbone, not a pile of integrations

With a clear value hypothesis (triage and surface what needs a response, unified across email and chat) I looked at a couple of solution routes.

Email was the easy half: I built on Google Workspace tools, wrapping their primitives into verbs that match the rest of the RafeOS API.

Chat was harder. I evaluated Matrix as the backbone, alongside a patchwork of thin clients, ran a few spikes to find the right homeserver and test other risky assumptions. I settled on Conduit, then built a thin RafeOS wrapper around it following the same conventions as everything else.

Claude Chat screenshot showing chat to get a high-level understanding of Matrix
Claude Chat as a way to get a high-level understanding of Matrix

The important choice was modularity. Matrix bridges made it possible to add new messaging services without turning RafeOS into a one-off monolith. That is exactly the sort of thing that is easy to miss when an agent can produce a working version in one session.

Designing the interface in parallel

Running in parallel to the discovery and specification work, I used Claude Design to think through the front-end and explore design options. For the Messaging feature this was relatively simple because it drew on conventions already established for Cockpit.

But, in general, the same coherence and consistency discipline applies (design system as a foundation, be clear and bounded in what you are exploring, start high-level and move to implementation spec).

From spec to implementation

Working collaboratively with Claude, I generated a high-level brief document drawing on the wider project context, research agents, the solution and design routes described above, plus third-party documentation. It's best to think of this as meta-context, with pointers to other artefacts and references.

This was passed into Claude Code's plan mode to validate, add detail, and create an execution plan. And then, for the most part, I let things run on auto mode, making decisions and granting permissions as needed.

I find this iterative stepped approach stops the agent racing ahead of a decision, and makes you feel like you are steering more.

Testing, quicker simulation, visual testing

Testing and deployment were almost entirely Claude Code's domain too — close enough to the build that, here, they felt like part of it. For Messaging, the best value came from testing against simulated states. This won't be novel to a QA engineer; it's the familiar world of test harnesses and simulated data. However, generating synthetic data for the states you actually care about and running through tests is something AI is particularly good at.

The right tooling and orchestration is important. I used to do a lot of the checking by eye. Giving Claude Code a headless browser via Playwright changed that for the routine checks — a reminder that a lot of what looks like a capability ceiling is just the agent not having been handed the right instrument.

The messy middle: LinkedIn

The gaps still appeared. I assumed I could bridge LinkedIn the way I'd bridged the others. WhatsApp, Signal and Telegram were straightforward; LinkedIn was not.

Its login and session-checking are restrictive enough that the bridge technically worked but wouldn't stay logged in, and after a fair amount of effort I concluded it wasn't possible.

That is a good example of the messy middle: the place where a plausible solution meets an awkward reality. The difference was that I could explore it and draw a conclusion in hours rather than days.

Living with the first version

The first version of the Messaging feature triaged messages with a model deciding what mattered. It didn't work well enough; the scoring was mushy and the real-world results mixed. So I both tweaked the classification prompt and added a salience layer of deterministic rules: plain logic, no model call, doing the part that doesn't need a model.

I could make that change quickly because the previous architecture and the reasoning behind it were already written down, in the CLAUDE.md files and the session records. The agent read what we'd decided and why, and built on it, instead of re-doing the design from cold. The discipline from the last build is what made the next change fast... and is, I think, one of the key advantages over one-shot or vibe engineering approaches (though I do like these for prototyping or rapid visualisation).

Shipping the foundation, then learning from use

The whole thing was five or six sessions: research, planning, design, a first implementation, polishing, then a couple of iterations once I'd lived with it. That last part is the other half of the truth about compression. You ship the foundation in v1, and then using it tells you what you actually needed (sometimes just config, Matrix has a lot of dials; sometimes a tweak to the wrapper).

Messaging in Cockpit
Messaging module in Cockpit

A stack chosen for the collaboration

For Cockpit the front end is React and Next.js, with shadcn/ui and Tailwind, all in TypeScript. I picked the popular, conventional stack not because it's well-trodden for humans but because it's well-trodden for the model. The more familiar the stack, the more often the agent gets it right first time, and the less of my day goes on the “wrote it slightly wrong, fix it, try again” round-trip tax. With a niche framework the agent guesses more often; with a well-established one it is operating in familiar territory. I was optimising for the collaboration, not just the code.

The same instinct produces the single best reliability move in the system. The API publishes an OpenAPI schema and the front-end’s types are generated straight from it — so the front-end can’t quietly invent an endpoint or field that isn’t in the schema: if it tries, the types don’t match and the build fails. It only binds the front-end to the contract, and only while I keep the types regenerated. But within that it turns a whole category of confidently-wrong output into an error the agent catches before I ever see it.

Version control plays the same enabling role. With an agentic builder, Git stops being the tax you pay for safety and becomes the thing that lets you move fast: reverts, branches and merges mean I'll happily let an agent attempt something ambitious, knowing I can cleanly undo it. When reverting is cheap, a clean undo button is what lets you use the speed.

The guardrails that make a flaky agent reliable

The generated-types pattern above is one example of a wider habit. The model is, by default, a brilliant junior who sometimes states a wrong answer with total confidence, and the skill is scaffolding its output into something dependable. Two more that have earned their keep for me in this project (there are many others):

  • Strict typing as a net the agent falls into. Tight types mean a class of mistakes fails at compile time instead of quietly at runtime. The type checker is an automated reviewer that never gets tired.
  • A CLAUDE.md per project, the single highest-leverage artefact in the system. I lean on tips from Karpathy and others here. It's the difference between an agent that drifts and one that stays on the rails.

None of this is exotic. It's contracts, types and documented conventions, applied to constrain a probabilistic collaborator. I find that quite reassuring about the future of the discipline: the rigour that was always good practice is now the thing that separates working from not.

Reviewing code you didn’t write

There’s a question lurking under all of this: how do I review code I couldn’t have written? I don’t read most of it line by line, and if I did, I couldn’t reliably spot a subtle bug. Given how long it's been since I worked with code day-to-day, and how fast engineering is changing, I can really only judge quality at a high level.

What I trust instead is the scaffolding around the code: types that won’t compile if a shape is wrong, an API contract the front-end can’t drift from, tests against the states I actually care about, and above all the behaviour — does it do the right thing when I throw the awkward cases at it?

Ultimately, I’m reviewing and directing the system that produces the code more than the code itself. That moves where the risk sits, and significant risk remains: on a personal project, I'm OK with this because a miss costs me an annoyance, not anything more serious. On something with sharper consequences, I'd want a review from an experienced senior developer (and, frankly, I wouldn't put me in the engineering seat).

The underplayed stages

Echoing back too "things becoming plausible too quickly", here are two areas where compression creates silent, structural vulnerabilities that require aggressive human reinforcement:

  • Security, compliance, scale, and trust as design-time concerns: decisions around robustness, rules about data storage and retention, and adherence to non-functional-requirements in general; plus balancing between them all feel particularly fragile under compression. This is not a new issue, but gaps here are particularly susceptible to human fallibility. This is not a new issue, but AI introduces a dangerous asymmetry here: an agent can generate outputs at blistering speed while remaining entirely blind to downstream compliance risks. For example, if security isn't explicitly hardcoded into the initial prompt substrate, the agent will optimise purely for feature completion, baking vulnerabilities directly into the foundation.
  • Maintenance and operations: the first version, ends at "it works", but the real life of a system is the months afterwards. AI compresses the build phase without reducing the long-term operating burden; in fact, it frequently inflates it. Human technical debt accumulates through time-pressure, shortcutting, or fatigue. AI technical debt, what I think of as hyper-debt, accumulates through frictionless velocity. Because the marginal cost of generation is near-zero, an agent will happily output thousands of lines of verbose, slightly off-pattern code without ever feeling the design strain. The system functions today, but a little while later, you realise you are maintaining a vast, automated house of cards. I can already see the early patterns of this debt in my own follow-up work and ongoing maintenance efforts.

Loops

Going through this end to end has also got me thinking about the wider shape of product development itself. Everything above was, in effect, a stack of loops — research, spec, design, the salience-layer fix, the iterations once I had lived with it — each run at a different blend of me and the agent and very much intersecting or bolting on to each other (note that agentic engineering loops, a hot top right now, are part of this, but here I'm thinking wider).

Product loops (various guises) should be a familiar concept. Good product development is rarely linear: the frameworks all say so, even if time-boxed phasing never quite vanishes in practice.

What changes with agents is twofold. First, they collapse phases together, or at least lets them run in parallel more easily — partly through the compressed loops, which I do have mixed feelings about; and partly by encouraging a real redesign of workflows, which I am much happier about.

Second, and more far-reaching: you can run more loops — human, hybrid, or agent — inside one wider product loop. That follows from the two threads this whole piece keeps circling: who does what (the autonomy continuum), and how much capacity you have (human and machine). It does not replace people; it reallocates them to higher-value, more interesting work, and makes things that were never worth doing suddenly feasible (Jevons’ paradox).

I sense this is good for the quality of outcomes, even if — like everyone — I am still working out the details. The diagram below is a first attempt to map it; I am developing the argument properly in a separate piece.

The agentic product loop: observe, plan and act as one continuous loop with reflect & learn, containing many smaller human/agent loops, on a human and machine substrate.
The product loop and the loops within it — observe · plan · act, each a blend of human and agent, on a shared substrate. Open the zoomable version ↗

An important field note

One caveat before I extrapolate too far: all of the narrative above comes from a project with a single creator, a single user, a blast radius of one, and a blank page to start from. That sidesteps most of what makes team software hard: shared ownership of code no one person wrote, brownfield complexity, compliance, security review, operational accountability, and the cost of a confident mistake landing on a customer rather than on me.

However, drawing on my wider experience, I do not think that changes the direction of the argument. If anything, it sharpens it. At scale, the guardrails I can treat as optional become mandatory. “I don’t read most of the code” is a luxury that does not survive shared ownership. The written context, contracts, tests, review habits and human/AI gates matter more in a team setting, not less.

Which is rather the point.

What this tells me about engineering teams

The value does not disappear. It migrates away from typing code and towards everything around it: the spec, the contracts, the written decisions, the conventions, the choice of stack, the tests, the review model, the choice of what not to build. The teams that thrive will be the disciplined ones, and the discipline compounds, because last build's written record is what makes this build's change fast. The senior person's job shifts from reviewing the output towards designing the system that produces reliable output in the first place.

You cannot lead that shift from the audience. You don't have to become a full-time engineer again, but a technology leader who hasn't run a real build this way, end to end, is now reasoning from a model of software development that's quietly going out of date.

The shift is not visible from demos, conference talks or vendor decks. It becomes visible when you have to decide what to trust, what to constrain, what to test, what to reject, and what to live with after the first version works.

The detail is where the lessons are.


On how this was made: RafeOS is built collaboratively with AI, and this post was drafted in conjunction with Claude and edited by me.

New writing by email

Occasional pieces on product, technology and AI — and how they actually play out in practice.

Thanks — check your inbox to confirm.