Build What You Need: How Two Side Projects Became One Pipeline
By Dan McAulay
Third post in the series on how Shipwright actually came together — the first one covered the cloud review pipeline, the second covered OpenClaw, Bodhi, and the original todo list. This one is where Dave’s thread — the shipwright plugin — and mine — the Bodhi service — actually meet.
Dave and I try to meet in person once a week. He lives in Truckee; I’m about thirty minutes away on the lake in Tahoe, so Truckee’s where we go. In a meeting room at Lift Workspace, Dave told me he wanted to build a product. I didn’t — I wasn’t there to make a bet on a product, I wanted to build things that made our own business actually run better. I was already sold on 37signals by then, so I made the case: build what you need, for yourselves, first. See later whether it’s useful to anyone else. Dave came around. VitalsOS was born in that room, before either of us had written a line of code — and by the end of that same day, it already had its first commits, with Bodhi’s workspace synced in as a shared repo.
We each drove home and started building right away. I didn’t even wait to sit down at a laptop — I started on my side walking my dog through the forest, talking to Bodhi in Slack. Neither of us sat down and decided to merge our tools. What actually happened was simpler, and looking back, more obviously inevitable: we’d both just agreed to follow the same instinct 37signals has been preaching for twenty years — build what you need — and that instinct is what did the merging for us.
We Needed Real Things, Not Scripts
Bodhi’s billing automation already worked by then. Tested, linted, real money running through it every month — the P&L calculator, the compensation math, invoice and payment checkers, the mailer, the PDF generator. But only one agent could touch any of it, and only through chat. If I wanted another agent to check an invoice, or wanted an actual web page instead of a Slack reply, there was nowhere to point it. It lived entirely inside Bodhi’s own workspace, with no interface beyond talking to Bodhi directly. It was cool, and it was fast to build that way — and it also wasn’t real, not in the sense we needed it to be.
So it got copied wholesale into VitalsOS on day one, hardcoded paths to my own workspace and all — the build plan we wrote that same week has a task that says it plainly: decouple from Bodhi workspace, remove the hardcoded paths, make it stand on its own. We weren’t rewriting anything. We were taking something that already worked and giving it a real API, and eventually a web interface, so it could be more than one agent’s private tool.
That same week we went further than just freeing the billing code — we decided VitalsOS wasn’t going to be a script collection at all. It was going to replace two SaaS products we were actively paying for. Not “fix what’s broken” in each one separately — unify the whole loop into a single thing: schedule an event with a client’s email address, it routes to the correct calendar automatically, and from there we get one continuous thread — time tracking against that engagement, invoicing off the actual hours worked (including drafting the invoice emails themselves), and tracking whether payment came in, with reminders if it didn’t. Toggl and Cal.com each handled one slice of that and didn’t talk to each other, so the gaps between them were exactly where things kept breaking. Cal.com’s specific failure was calendar routing — a known bug, not a missing feature, and we were paying thirty dollars a month for it. Dave’s other complaint was simpler: he hated having a separate browser tab open for every calendar just to check availability. He wanted one surface, not five tabs.
Jason Fried and DHH put the whole philosophy as plainly as it gets in REWORK: “The easiest, most straightforward way to create a great product or service is to make something you want to use.” Basecamp exists because 37signals needed something to manage their own client work. VitalsOS exists for the same reason, and the very first README we wrote says so outright: built for us first, customers come later, naturally. We’re not claiming the comparison to be clever. It’s just the actual shape of the decision.
Building Real Things Meant We Needed a Real Way To Build Them
Here’s the part that actually surprised me putting this together. The moment we committed to building real, shared software instead of personal scripts, ad hoc chat with an agent stopped being enough — and it turned out we’d each already half-solved that problem, separately, without knowing it.
Dave had been building plan-session and dev-task since November 2025 — it didn’t land in our shared marketplace until March 19, eight days before VitalsOS existed at all, out of a problem that had nothing to do with our company. He’s told that story himself. By the time it landed anywhere near VitalsOS, it already had a nine-phase planning flow with dependency tracking and a twelve-step execution flow with its own dependency checks. I had the opposite kind of tool: a todo list, a cron that picked items off it and ran them with hard stops, and a review habit I’d built to keep my own business from quietly losing money. Neither of us built any of it with the other person’s tool in mind.
Once we actually needed to build real things together, they just started working, because they were now pointed at the same problem. Dave’s plan-session would produce a task breakdown. Those tasks got queued into my todo list. My execute cron picked them up and ran Dave’s dev-task to actually build them. Two systems that had never spoken to each other, snapped together — not because we designed it that way, but because building what we needed forced the question, and we both already had half the answer. CLAUDE.md declared this process “mandatory” on day two, which sounds like a bigger moment than it was. We weren’t waiting on anything; the plugin was already sitting in our shared marketplace, so the policy just caught up to what we were already doing.
My review habit followed the exact same pattern, on a smaller scale. It had its own separate start, built before any of this and running on its own for a while, and it folded into the shared pipeline the same way the planning and execution halves did — not because it existed first, but because it matched how review actually needed to work once real trust was on the line.
Fast Wasn’t The Same As Good
With both halves running together, we moved fast — 628 merged pull requests in the first 25 days, all under our two GitHub accounts, on real cloud infrastructure almost from the start (Terraform and a CD pipeline by day two, three production services on Helm plus Cloud SQL migration prep by day three). We weren’t prototyping locally and deploying later. Production existed almost from the beginning, running in parallel with the actual product getting built on top of it.
Some of that volume was the pipeline doing what it was supposed to do. Some of it was just us being genuinely stoked and putting in the hours. And all of it happened part time — we were still delivering client work the entire way through this. VitalsOS got built in whatever time was left over, not instead of the job that was actually paying the bills.
But speed exposed the gap immediately. There were real, repeated moments where we’d be talking to an agent in Slack, casually, about something we wanted built — and it would just go ahead and ship it without confirming first. The output was noticeably worse than when the same work went through the actual pipeline. That’s not mysterious once you look at what the pipeline was actually checking for by then: dependency pre-flight, spec compliance, requirements verification, pre-ship checks. A casual Slack request skips every one of those, because there was never a defined “done” to check against — just a conversation that felt finished. Declaring the process “mandatory” in a markdown file hadn’t changed behavior on its own. What actually made the discipline stick was watching that gap happen enough times to trust it wasn’t a fluke.
The tool matured in response. By April 20, it had gone through a real shape change, not just feature accumulation. It had become queue-driven — the plugin itself had absorbed the pattern my todo list and execute cron used to run externally. It had dependency checks, spec compliance, test enforcement, a CI gate that could fix its own failures. It had metrics flowing out to PostHog so we could actually see what it was doing instead of trusting it blind. Review had been rewritten to be conservative and staged instead of aggressive and automatic. It had gone from version 1.1 to version 4 in under a month. It still wasn’t a product — one plugin, living in our shared marketplace, keeping its work queue in a todo list rather than anything resembling a real task store — but we’d dogfooded it hard enough on our own company to trust it with something we hadn’t tried yet.
Keanu
Every client-specific agent we’d built before that point — including one that went live earlier that same afternoon — was still something we operated ourselves. Scoped to a particular engagement, but still living in our own Slack workspace, still us at the keyboard, doing work on a client’s behalf rather than handing them anything directly.
Keanu was the first one built for someone else to actually use. It lived in a Slack workspace that wasn’t ours, deployed for a real person at a real client to talk to directly, the same way I’d been talking to Bodhi — and it did that work using the same pipeline we’d just spent a month combining and hardening. That’s also where the per-client architecture stopped being a design choice and started being a requirement. Splitting agents per engagement wasn’t about looking clever with multi-agent systems — it was about making sure one client’s context, history, and decisions never showed up in another client’s agent. The moment someone outside the company was going to actually use one of these, that stopped being optional.
I spent the days before that call working around the clock to make it real. We had everything provisioned in the client’s own workspace ahead of time, an onboarding call on the calendar, and then we just waited for the client to say the first word. When he did, Keanu opened with the same ritual OpenClaw had run on Bodhi back in February, rebuilt on our own infrastructure: “Hey. I just came online. Who am I? Who are you?” It’s supposed to be a back-and-forth from there — name, role, team or solo, filled in collaboratively. He skipped the back-and-forth entirely and pasted in an entire markdown file he’d already built himself in Claude, describing his business in one shot. Keanu consumed it whole, and he was working with it the same day.
Build what you need. That’s what got the billing code its own API, what decided Toggl and Cal.com had to go, what forced two separate personal tools into one pipeline, and what made that pipeline good enough to hand to someone who wasn’t us. Five months later, we still run every piece of it — the billing math, the calendar routing, the todo list turned task queue, the review habit — every day, for ourselves, whether or not anyone else is using it. That’s not a coincidence. It’s the same instinct we started with, still running.
Next up: what we were calling a task store back then was still a todo list with an interface bolted on, and it was already starting to crack under multiple agents working the same queue. How that actually became the shared task store the whole pipeline runs on now — including the false start in the middle — is its own story, for another post.
Want to accelerate your engineering team?
Book a 30-minute discovery call to discuss your team's AI adoption strategy.
Get in Touch