Build timeline (12 milestones across 64 days)
  1. Day 19d2c818
    initial commit
  2. Day 1f9bd94b
    Yelp removed
  3. Day 3
    Microsoft SSO, renamed CODA
  4. Day 4
    contact cascade reordered
  5. Day 5
    hardening: circuit breakers, logging
  6. Day 14
    call scoring pipeline
  7. Day 30
    rate-card fix + audit script
  8. Day 36
    Performances tab
  9. Day 39f710d12
    Deal Assist prompt overhaul
  10. Day 58
    pipeline health audit
  11. Day 61
    cron duplicate fix
  12. Day 64
    classification bug fix

01The setup

3 years ago I was contracted to a company for outbound email sequencing. The scope evolved to M365 administration, sales enablement, training, data analytics, strategic advisory, and eventually the question of how to build the software this company actually needed.

In February 2026, I decided to answer that question by building it.

The company sells compliance-based licensing to physical commercial venues. Roughly 20 field reps, selling against a published annual fee schedule, in a business where the same venue can require different treatment depending on which classification it falls under and whether the rep quotes it correctly. The tool they had been using to find new prospects was a Google Apps Script I had written for them a year earlier, during my first experiments with ChatGPT automation. It called GPT to pull venue candidates, ran rough fee estimates that were usually wrong, returned duplicates every run, had no deduplication, no contact enrichment, and no pipeline tracking. It did what it needed to at the time. I decided to replace it entirely.

What followed took 64 days. I worked with 2 instances of Claude, one acting as strategy and architecture, one acting as implementation, to build a 10-integration production system. The result is called CODA. The client's business is rooted in music, so the name is a musical one: a coda is the closing, resolving section of a composition. Reps close deals, CODA is the tool that helps them get there.

CODA discovers venues across all 50 U.S. states, classifies them against the client's license types using Claude AI, estimates fees from the official rate schedules, enriches them with owner contact information through a 4-stage cascade, delivers curated lead batches to each rep's Google Sheet, pushes qualified leads to Pipedrive, scores sales calls against a 12-point rubric, and runs an admin dashboard with full analytics, adoption tracking, and pipeline health monitoring. It's deployed on Railway, serves roughly 20 reps, and processes about 1,000 new leads a week.

CODA is still being built. That's the point of owning the software your team runs on rather than renting someone else's. When a rep flags a pattern I didn't anticipate, I ship the change that week. When leadership asks a new question about pipeline health, I build the query. And when the sales motion shifts, the tool shifts with it, instead of sitting there like a subscription the team stops opening after 6 months. It started as a lead-discovery tool. It's grown into the sales intelligence system the team runs on, and it keeps growing.

No engineering hires. No agency. The commit log is a single developer's name from start to finish.

This is the story of how that happened, what went right, what went wrong, and what it looks like when someone without an engineering background uses AI to build production software instead of output a team has to clean up.

12,652lines in the initial commit

02What the commit log shows

Before any narrative, the raw evidence.

The repository opens on February 16, 2026. The first commit is 12,652 lines across 51 files. It contains the orchestrator that coordinates every pipeline step, 9 backend services, 4 frontend views, the initial database migration, a 613-line project brief, the Railway deployment configuration, and the package.json with all core dependencies. It's the whole working application.

The tip of the main branch, as of this writing, is April 20, 2026. That's 64 days after the opening commit. In between are 442 commits, 43 database migrations, and roughly 15 distinct product eras, each with its own character. Some last a single day and produce a complete new feature, frontend to database, in one pass. Others span a week of infrastructure hardening, and a few exist because one bug got caught in production and demanded a multi-commit investigation.

Claude wrote a lot of the code here. Claude also fabricated data, proposed wrong solutions, followed outdated instructions, and occasionally hallucinated confidently enough that production bugs would have shipped if nothing had been checking. So the claim this document makes is narrower than "Claude built CODA." The story is the loop that caught those failures and turned them into correct work, and the human judgment that sat at the center of the loop and made the decisions Claude couldn't.

The three layers:

Strategy. A Claude.ai instance used for architecture, specification writing, and the "what are we building and why" work. Holds the full business context. Proposes solutions. Reviews plans. Writes prompts.

Implementation. A Claude Code instance used for all actual code work. Reads the existing codebase. Runs audits against production data. Implements changes. Catches when the strategy layer is wrong.

Human. Me. I supply the business rules the models can't know. I arbitrate when the layers disagree. I hold the field reality. I approve changes before they ship. I own the outcomes.

The loop isn't symmetric. Each layer has a different relationship to truth and different failure modes. The examples below show what that means in practice.

15commits on day 1

03Era 1: the one-commit foundation

February 16, 2026. 6 hours of work. 15 commits, starting with 9d2c818, the initial commit, and ending with f9bd94b, which removed Yelp from the discovery pipeline and prioritized live-entertainment venues.

The code itself was written earlier: Claude Code produced the initial commit in a longer session, working from a specification document that Claude.ai and I had built over several preceding conversations. That specification, a 17-section architecture document referenced throughout the repo, established the inviolable business rules that still govern CODA today.

Among them: never charge the wrong fee. Never drop a lead silently. Never re-present a duplicate. Never burn enrichment credits unnecessarily. Never classify a venue toward a higher fee when ambiguous. Always show the rep the classification reasoning so they can sanity-check it before a sales call.

These rules became the skeleton that every subsequent change had to honor. 64 days later, after 43 migrations and hundreds of commits, they're still enforced at the prompt level, at the orchestrator level, and at the database level. When the implementation layer writes 4,000 lines of code in a single commit, those lines hold together because they were written against a specification. When the next 60 days of iteration touched every service file, the iteration stayed disciplined because the rules didn't move.

Non-technical operators tend to assume AI-assisted development means prompting your way through problems one at a time. CODA's commit log says otherwise. The working system appeared in one pass because the specification was complete enough to build from in one pass, and the specification was complete because the strategy layer had been worked hard before any code existed.

The same-day follow-ups tell you something too. Within hours of the initial commit, I was catching problems the specification hadn't anticipated: white-on-white text on certain sheet rows, chain venues slipping through the initial chain-detection regex, junk email domains contaminating the contact enrichment output. Each got fixed in its own commit, none required rearchitecture.

Yelp got removed in Era 1 too. The initial specification included Yelp Fusion as a secondary discovery source. Within hours of shipping, Yelp's results turned out redundant with Google Places, and the API cost wasn't worth the marginal signal. Removing it took one commit. Specifications are predictions. The first few hours of production use are the cheapest time to correct the ones that missed.

04Era 2 through Era 4: velocity and correction

Days 2, 3, and 4 of the project produced roughly 110 commits. Pipedrive integration landed on day 2. Microsoft SSO replaced the initial PIN-based auth on day 3. The contact enrichment cascade was inverted on day 4, originally spec'd as Hunter first, Facebook second, Seamless as the last-resort paid option. In production, Seamless was returning better contacts for the specific venue-owner use case, and the order got reversed.

The cascade inversion is a small moment, but it shows the loop correcting its own output. The strategy layer had produced a cascade order based on cost reasoning: Hunter is cheapest, Facebook is free, Seamless costs money, therefore run Seamless last. That reasoning was fine in isolation. In production, the contacts Seamless returned were higher quality for the specific type of business the client sells to, and the credit cost was justified by the conversion lift.

The strategy layer couldn't have known this. It had no access to production contact-quality data. The implementation layer couldn't have proposed it unilaterally because the change altered business logic, not just code. I made the decision after seeing the enrichment results on real leads. That decision took one chat and one commit to implement. The specification got updated to reflect the new order. The inviolable rules, never burn credits unnecessarily, never re-present a duplicate, were still honored, because the credit cap logic was preserved. Only the order of the cascade changed.

This is the first place in the build where the three layers each contributed something the others couldn't. The strategy layer had the cost model. The implementation layer had the clean ability to rewire the cascade without disrupting the credit cap or the dedup logic. I had the ground truth about which contacts actually closed deals.

Era 3 is also where the product's identity changed for the first time. The original specification had reps reading their leads from Google Sheets. Only the admin logged into the web application. 43 hours after the initial commit, that constraint died. Microsoft SSO was installed and reps got their own logins. The PIN-based admin auth was ripped out in the same commit. The application went from single-operator to multi-user in one change.

2 weeks later, the product's second identity change happened: the name. It was rebranded from its working title to "CODA" in a single frontend commit. It stuck. Every subsequent reference in the repo uses it.

Both identity changes are visible in the commit log as single-commit posture changes. The specification was strong enough to ship from and loose enough that a commit from a week later could overrule it.

05Era 5: hardening

Days 5 through 7 were spent on infrastructure. The commits have titles like "Harden pipeline: fix silent drops, add diagnostics, prevent data loss," and "Harden security: auth scoping, rate limiting, XSS, webhook verification." Pino came in for structured logging, Helmet and express-rate-limit for HTTP hardening, a locking rule in the scheduler so 2 jobs can't grab the same work at the same time, and a circuit breaker abstraction that would later wrap every external API call.

This is the era that's hardest to explain to someone without an engineering background, and it's the reason experienced engineers treat infrastructure work as real work. From the outside, hardening looks like "the system was already working, why are we still doing things to it." From the inside, hardening decides whether a system survives contact with production.

2 patterns from this era saved real pain later.

The circuit breaker pattern: every external API call got wrapped in a shared abstraction that tracks consecutive failures, opens the breaker after a threshold, and transitions through a half-open state before re-enabling. Named breakers were registered globally, one for Claude (threshold: 3 failures, 30-second reset), one for Hunter (threshold: 5, 60-second reset), one for Seamless (threshold: 5, 120-second reset). A health endpoint exposed all breaker states. When Hunter rate-limited the system 2 weeks later, the breaker opened, the pipeline fell back to Facebook scraping, and nothing shipped a partial batch.

The structured logging pattern: every service got a Pino child logger with its module name and context fields. No more console.log. Production logs became searchable, filterable, and reproducible. When the cron duplicate bug surfaced in April, the diagnosis took minutes rather than hours because the logs were structured enough to correlate across replicas.

Neither pattern was expensive to install. Pino took 1 commit. Circuit breakers took 2. The value shows up much later, in eras that wouldn't have been survivable without them.

320calls per rep per week

06Era 6 through Era 8: product depth

The next 2 weeks added call scoring, city-event tracking, batch enrichment, rep sheet edits syncing back into the system, weekly intelligence digests, AI talk-track generation inside each lead's detail view, and dozens of smaller improvements. The system stopped being a lead discovery tool and became a full sales operating layer.

The call scoring pipeline is the flagship example of the strategy layer doing what it does best: catching a second-order requirement that changes the build.

The context: the reps make about 320 calls per week. Their dialer vendor offered AI-powered call scorecards as a premium add-on. I decided to build an equivalent internally. The strategy layer designed a 3-stage pipeline: fetch the calls from the dialer, transcribe them, score the transcripts against a 12-point rubric using Claude. Estimated all-in cost at our volume: roughly $20 per month.

The first version of the spec used Whisper for transcription. Whisper is the default choice. It's accurate, cheap, and well-documented. It would have worked.

During spec review, the strategy layer caught something. The 12-point rubric included criteria like "talk-to-listen balance" and "objection handling under pressure." Those criteria can't be scored from a monolithic transcript. They require knowing who said what. That's speaker diarization, the technical term for labeling which voice belongs to which speaker, and Whisper didn't offer it at the time.

The transcription service was switched from OpenAI Whisper to AssemblyAI Universal-2 specifically because AssemblyAI provides speaker diarization (labeling rep vs. prospect turns), which meaningfully improves scoring accuracy on criteria like talk-to-listen balance and objection handling. The primary goal was transcription, but the scoring quality depended on a feature only one vendor offered.

This is the strategy layer doing its job. The primary requirement (transcription) was clear. The secondary requirement (speaker labeling) only became visible when the transcription step got mentally connected to the scoring step. A less rigorous planning process would have shipped Whisper, produced transcripts without speaker labels, and discovered the problem only when the scorecard output was unusable. Catching it at spec time added zero cost.

A second catch in the same spec: the 12-point rubric originally counted "license closed on call" toward the composite score. During review, the strategy layer flagged this. Most sales calls don't close on the call. Including "license closed" in the composite would penalize reps for normal outcomes. The fix was to score the criterion but exclude it from the weighted composite. That decision happened before any code was written. If it had shipped the other way, every rep's composite would have read artificially low, the scorecards would have lost credibility, and the feature would have been abandoned.

Neither catch looks like much. Together they saved weeks of rework, and they're exactly the judgment a specification layer exists to provide.

The pipeline itself landed in a burst: routes, services, migration, and frontend view, all shipped over 4 days in early March.

07Era 9: the fabrication

March 17 through March 20. The era of the rate-card fix. This is the clearest example in the build of the implementation layer catching the strategy layer confabulating, so I'll tell it in full.

The context: a routine investigation into 4 garbage leads that had reached a rep surfaced test failures in the fee calculation module. Specifically, the live-event classification fee tests were failing. I asked the strategy layer to diagnose.

I presented a guess as ground truth and recommended you revert production code to numbers I'd derived circularly.

The strategy layer pulled up the live rate card for the live-event classification. The rate card is published as a PNG image on the client's website. The strategy layer couldn't read the image directly. Instead of stopping and saying "I cannot see the rate card, please read me the values," the strategy layer produced a table of factor values and presented them as authoritative.

The values were wrong. They had been derived circularly: the strategy layer had noticed the in-code values were roughly 5% higher than expected, hypothesized that a prior commit had applied a 5% inflation, and divided the code values by 1.05 to produce "the real values." Then it presented those derived numbers as the live rate card truth and recommended reverting the code to match them.

The implementation layer caught it. Rather than acting on unverified numbers, it flagged the contradiction. The strategy layer had claimed it couldn't read the image, then produced values from that same image. It insisted on grounding against the PDF of the rate schedule that lived in the repository. When I supplied the actual rate card image as a direct upload, the 5% theory turned out to be correct. But the correct values had come from a real source, not from the strategy layer's circular derivation.

The underlying bug had a separate root cause, and it's revealing on its own. A prior Claude Code session, months earlier, had drafted a planning document that applied a speculative 5% CPI escalation to the already-published current-year rates. The current-year rate card already included the CPI adjustment. Applying 5% on top produced a speculative next-year rate card 8 months early. That document had been used as the source for a commit that shipped into production, and live-event fees had been running 5% high for 30 days before the test failures surfaced the problem.

The fix took 2 commits. The first reverted the live-event rates to match the PDF and the live rate card. The second added a new audit script, auditFeesAgainstPdfs.js, which parses every fee schedule PDF in the repo and compares the extracted values against the hardcoded rates in the code. It runs manually, report-only, not CI-gated. It exists specifically to catch this class of drift without relying on coincidental discovery.

This is what Claude sent me in the chat after Claude Code surfaced the contradiction:

I presented a guess as ground truth and recommended you revert production code to numbers I'd derived circularly. If you'd just run my prompt without Claude Code's epistemic check, you'd have shipped my guesses. Two lessons: I should have stopped when the PNG didn't load and said "I need you to read the rate card values to me" rather than invent them. And Claude Code's pushback pattern is exactly the behavior you want more of. It spotted a premise contradiction, flagged it explicitly, and proposed grounding against the in-repo PDF before taking destructive action. That's senior-engineer behavior.

This is the loop working as designed. The strategy layer fabricated, the implementation layer caught it, and I supplied the authoritative source. A bad commit got stopped, and a permanent audit script now guards the whole class of bug.

The implication for anyone considering this pattern: any single layer of AI output is unreliable in specific, predictable ways. The strategy layer hallucinates confidently when it's under pressure to produce a numerical answer. The implementation layer is literal-minded, which is exactly what made it stop when the premises contradicted each other. The human supplies the thing neither layer has: a direct connection to reality.

The loop's real advantage is the catch rate: three layers together catch a class of AI failure that a single prompt-and-accept workflow ships straight to production. Every model output gets a check. Every proposed change gets grounded. Every destructive action gets approved before it ships.

08Era 10: the Performances tab

March 23 through March 26. An aside about what vertical slices look like in this kind of build.

The CEO sent me a screenshot. It showed a social media post announcing a live performance at a venue, featuring a performer whose work the client represents. The question was whether we could track these, whether we could know when represented performers appeared at specific venues, so the data could be used in licensing conversations.

The request arrived on a Monday. By Monday evening, the commit log shows a single commit introducing a fully-formed Performances tab. Frontend view, backend route, 3 services, a new database migration. Screenshot ingestion via Claude vision extraction. Google Sheets mirroring. 4 database fields per performance. The whole vertical slice landed in one commit.

Within the same week, follow-ups added: full lead enrichment pipeline integration (each extracted venue now runs through the real contact cascade), Instagram scraping via Apify for automatic tour-date extraction, national concert promoter affiliation detection with a full subsidiary tree, setlist.fm integration for setlist data, municipality targeting, promoter tracking, and a second Google Sheet for the extracted data.

The pattern is different from Analytics, which grew incrementally over 8 distinct thematic phases across 2 months. Performances was a vertical slice: everything needed to make one feature work, shipped in a single cohesive unit, then refined in the days that followed. Both patterns are legitimate, some features want to land whole and others want to accrete.

The CEO's feedback on the initial Performances tab was substantial. 6 features were batched into a single commit 3 days later based on his review: multi-day event support (a music festival is one performance across 3 dates, not 3 performances), separating represented performers from other performers on the bill, promoter tracking, municipality flagging, setlist integration, and a significant schema expansion to support all of it. The commit touches everything: route, all 3 services, both frontend views, a new migration.

The CEO described how the feature needed to work to be useful to him, in his own language, with zero code proposals in it. The strategy layer translated that feedback into a concrete specification. The implementation layer turned the specification into the coordinated 6-file change. I reviewed and approved.

f710d12Deal Assist overhaul commit

09Era 11: Deal Assist and the dangerous prompt

March 26 through March 27. Deal Assist is a feature that takes a Pipedrive deal URL and produces a drafted reply email that incorporates the deal history, the prospect's objections, and the client's licensing arguments. It was called Deal Triage in its first commit and got renamed the same day.

3 weeks after launch, 2 reps flagged problems independently. One rep reported that a generated draft had offered the prospect a monthly payment plan that the client doesn't offer. The client's licenses are annual. There's no installment option. The second rep reported that a specific legal argument was appearing in every draft regardless of whether it was the right argument for the objection in front of him.

The strategy layer's first instinct was to investigate what the model was doing wrong. The implementation layer audited the Deal Assist prompt and found something worse. The prompt wasn't failing to restrain the model. The prompt was explicitly instructing it to offer payment plans.

Claude Code identified that the existing system prompt contained positive instructions directly contradicting the intended constraints, including explicit directives to offer payment plans and lead with multi-location discounts. The prompt was a fossil of a prior policy discussion. Someone had written "offer multi-year or payment plans" at line 240 at some point believing it was correct policy. The model wasn't hallucinating; it was following instructions.

This is one of the subtler failure modes of AI-assisted development. The prompt had been written during an earlier conversation, under assumptions that had since changed. Nobody revisited it. The assumptions survived only as text inside a file nobody read unless they were debugging. The model followed the instructions because the model is supposed to follow instructions. The instructions were wrong.

The fix was a 3-phase prompt overhaul. A "NEVER INCLUDE" constraint block that enumerates things the draft must not propose. An objection classification step that routes the draft-generation logic based on what the prospect actually said, rather than defaulting to a one-size argument. A 3-check self-review at the end that verifies the draft against the constraints before returning it. A Commercial Terms knowledge base entry establishing that the client's licenses are annual only, with no installments and no rep-level discounts, and any non-standard requests escalate to the licensing operations team.

A 4-test regression suite was written to lock the new behavior in. All 4 tests passed after fixing a bug where the draft's self-review notes were leaking into the reply itself. The commit shipped as f710d12 to main and auto-deployed to Railway.

The loop-level lesson: the implementation layer's audit was the critical step. The strategy layer would have proposed a fix without ever checking whether the existing prompt was the source of the problem. I wouldn't have known to look inside a prompt file for positive instructions that contradicted the desired behavior. Only the implementation layer, grounded in the actual code, surfaced the real bug.

One more detail from this era: both reps who flagged the problem got a direct follow-up when the fix shipped. Each was told their report triggered the change. If reps see their reports produce changes, they keep reporting. If the reports disappear into a void, they stop.

10Era 12 through Era 14: the final weeks

April opened with an email-lifecycle automation layer. Auto-detect bounced emails on Pipedrive deals. Auto-detect prospect replies to sequence emails. A "Replies Waiting on You" dashboard card surfacing deals where a prospect had responded and the rep hadn't followed up.

The loop handles diagnostic work too. One engagement inside this era produced a pipeline health audit where the system-of-record's reported pipeline size turned out to be materially different from the workable pipeline, a finding that changed downstream forecasting math. The finding itself is the client's to share. What the case study can share is the methodology.

I built a series of analysis scripts that queried Pipedrive directly, classified deals by activity recency and health signals (bounced email status, do-not-contact flags, last-touch dates), and classified prospect replies into a 6-category taxonomy: objection, logistical, question, refusal, interest, unclear. The "interest" category is the one that matters most in this kind of audit. It surfaces deals where a prospect explicitly asked to talk or asked a substantive question about the product, and the rep didn't reply. Those are warm-pipeline losses that look like nothing in the CRM. The taxonomy makes them visible.

The output was a formatted Word document summarizing the findings by segment, cross-tabbed by rep, with recommended remediations. The deliverable took a day. The code was trivial: a few SQL-shaped queries against the Pipedrive API, a categorization pass run through Claude, a Word template.

This is strategy work. The value sat in the framing: segmenting zombie deals from workable pipeline, surfacing the subset of unanswered replies that represented real interest, and presenting the findings so leadership could correct the reporting without it reading as an accusation. The strategy layer produced that framing. The implementation layer handled the plumbing. I made the calls about which cuts of the data would land with which audience.

The finding changed the company's reporting practices for the next planning cycle. Not every engagement is a system build. Some are a day of audits that change how the business sees itself.

April also produced the 2 flagship bugs of the final weeks.

The cron duplicate bug. On April 17, a rep received 10 leads in her sheet when she was expecting 5. Investigation found that her pipeline had run twice, 6 seconds apart, and written duplicate rows for every lead. The root cause was Railway's rolling deploy mechanism: during a deploy, the old replica is kept alive until the new replica's health check passes. If a scheduled run (a cron tick) fires during the overlap window, both replicas execute it. Neither replica knows about the other.

The fix was layered. A new database migration added a partial unique index on the run log table, specifically, a unique constraint on the combination of rep ID and run date, but only for rows with status 'running'. This created a database-level mutex. Only one replica could claim a run slot. The other would fail its insert and bail. A shared run-lock service was added to enforce the claim atomically. An in-memory list of already-started runs was added to the scheduler as a same-instance safeguard.

Belt and suspenders. The database lock catches cross-instance duplicates. The in-memory list catches hypothetical same-instance double-firing. Each covers a failure mode the other can't reach.

During the investigation, the implementation layer found a latent bug in the Pipedrive integration. A function was updating deal URLs using a case-insensitive name match rather than a primary-key match. That mismatch explained why one rep's duplicate rows both received the same Pipedrive URL while another rep's duplicates had the URL on only one row. The latent bug got filed to a deferred-issues document rather than folded into the current commit. Separate atomic commits are easier to revert in isolation.

The verification discipline around this fix is the part I'd point at. The commit didn't get pushed immediately. Deploy timing was checked against the next cron tick. Railway's health check and graceful shutdown window can cross cron boundaries, and pushing during that window would have defeated the fix. The commit was held until a clean window arrived, deployed, and verified at the next 21:00 UTC tick to confirm only one replica fired. Data cleanup for the affected reps was deferred until after that verification. A reminder was set for the following morning to verify the 6 AM PT scheduled cron worked correctly.

Don't clean the symptom until the cause is fixed, otherwise a deploy at xx:55 could re-double the data.

The classification bug. 3 days later. A rep flagged that country clubs on his sheet were having their names truncated: "Ridgemont Country Club" was appearing as "Ridgemont Country," losing the "Club" suffix. That fix was straightforward: an audit of 1,182 venue names already in the database showed which name-stripping rules fired destructively, a narrowed rule list was deployed, and 34 affected rows were backfilled with a one-shot script.

During the same investigation, the rep also mentioned that some country clubs were being classified under the venue classification when they should have been under the membership classification. The venue classification carries lower fees, so misclassification meant the venues were being quoted incorrect prices.

The strategy layer's initial proposal was a name-based hard rule: if a venue's name contains "Country Club," force-classify it as a membership venue. The implementation layer ran the audit before implementing and found the hard rule would misclassify real cases in the other direction: "The Country Club" at one Chicago address is actually a bar, Harbor Yacht Club is a bar, Renegade's Country Club is a bar. 15 of the 25 club-named leads in the database were correctly classified under the venue classification because they were themed bars that happened to have "Country Club" in the name.

The implementation layer pulled the classification reasoning for the 10 real misclassified cases, the ones that were actual membership clubs being labeled as venue-classification, and found the actual bug. The reasoning strings were startling:

"This meets the membership-classification suppression criterion." "Private membership clubs are excluded from independent licensing under this classification rule, as members' dues typically include blanket licensing coverage through their membership organization's group license."

The model was correctly identifying the venues as membership clubs. Then it was suppressing that classification, believing that membership clubs were exempt from licensing. This is an artifact of training data. The model had learned, generically, that some industries treat certain membership clubs differently. For the client's business model, this is wrong. The membership classification is a paid license type, not a suppression category. The prompt wasn't explicit enough to override the model's general-knowledge assumption, so the model's prior won.

The fix rewrote the membership-classification rule in the classification prompt explicitly, framing it as a license type rather than a suppression and rejecting the specific reasoning patterns the model had been using. A spot-check against 4 real inputs before the prompt change deployed returned correct classifications, with the prompt's rule language quoted back in the reasoning strings instead of the model's training-data priors. That's the signature of prompt anchoring working: the model is executing the rule it was given.

10 affected rows were backfilled with a script that flipped their classifications, nulled their fee estimates (because the membership classification requires member count and operating expense data that the system doesn't have automated access to), and added notes to the open Pipedrive deals indicating that the fees needed manual re-pricing. Zero production urgency, an activity audit confirmed none of the 10 deals had yet had a rep conversation attached to them, but the fix still shipped inside 2 days of the original flag.

11What the loop actually looks like

64 days. 442 commits. 43 migrations. 10 external API integrations. One operator.

Patterns that show up across every era:

Audit before fix. The classification fix produced an initial proposal that a deeper audit invalidated. The rate-card fix produced a fabricated rate table that grounding against the PDF invalidated. Every flagship moment in the build history has an audit in the middle of it, and in every case the audit changed the fix.

This is the most replicable piece of the pattern for operators considering similar work. The strategy layer is good at proposing solutions quickly and bad at knowing when its proposal is wrong. The implementation layer is good at grounding proposals against production data. A workflow that moves from "propose" directly to "implement" will ship wrong solutions regularly. Insert "audit" between the two and the wrong solutions get caught before they become code.

Ground against authoritative sources. When a numerical or classification decision has an authoritative source (a PDF fee schedule, a database row, a reasoning string from a prior model output), the implementation layer grounds against that source rather than accepting the strategy layer's claim. The rate-card fix caught a fabricated table this way. The classification fix found the actual model reasoning by pulling real examples from the database. The cron duplicate bug was diagnosed by reading Railway's deploy logs rather than theorizing about the failure mode.

Separate commits for cause and tooling. The rate-card fix shipped as 2 commits: the correction and the audit script. The cron fix filed a latent bug to the deferred list rather than folding it into the same commit. The name-stripping fix added a raw name column as a separate commit from the stripping rule change. Atomic commits can be reverted independently. Coupled commits cannot.

Verification gates everywhere. Dry-run before apply. Health check before backfill. Manual cron-run verification before data cleanup. Log-watch at the first real execution after a scheduler change. Spot-check against production inputs before a prompt change ships. No exception has broken this pattern in the build history.

Close the loop with the humans who flagged problems. Reps who flag bugs get follow-up messages when the fix ships. Rep feedback that reshuffles a fix mid-flight gets explicitly acknowledged in the revised approach. This is operational, not technical, but it's the reason the feedback keeps coming. The loop includes the humans who use the system, not just the ones who build it.

12The productization question

This document is published by Outblox, so the obvious question is whether the pattern can be replicated for other companies. The honest answer has 3 parts.

The pattern generalizes, but it isn't domain-free. The three-layer loop isn't specific to licensing. It works in any domain where a non-technical operator has deep business context, access to production data, and the judgment to know when a proposed fix is wrong. What doesn't generalize is the operator. CODA got built because I had spent 3 years embedded in the client's business before I wrote the first line of the specification. I knew how the reps talked about their prospects, which classifications they fought about, which objections came up in which order, and which parts of the workflow nobody would defend if challenged. That context is what made the specification complete enough to support a one-pass initial commit. Without it, the initial commit would have been the first of many re-specifications, each of which would have drifted further from what the business actually needed.

The operator matters more than the tools. When I read other write-ups of AI-assisted development, the thing that usually gets overstated is the AI. The thing that usually gets understated is the person in the middle. CODA works because I made thousands of small calls that neither Claude instance could have made: which features shipped first, which bugs got filed to a deferred list versus fixed immediately, which rep feedback got absorbed and which got politely deferred, which prompt changes were safe to deploy and which needed a regression suite first. None of those calls are hard individually. In aggregate they are the entire system. Any productized version of this pattern needs to pair with a senior operator inside the client company who can make them. If the client doesn't have that person, the engagement produces worse outcomes, regardless of how good the tools are.

The ideal client profile is narrower than it looks. The client whose engagement produced CODA had: a well-defined sales motion, a large prospect universe that benefits from automated discovery, a CRM already in place, and operational leadership that could make decisions quickly when the loop surfaced them. The rough translation: field sales companies, 50 to 500 employees, no internal engineering team, selling a defined product to a large universe of commercial locations, with an operator or senior operations role available to embed in the build. Distributors fit. So do commercial service providers, equipment lessors, and regional franchise operators. Enterprise software sales to a small number of very large accounts doesn't, the universe is too small for the discovery layer to matter. Consumer e-commerce doesn't, there's no account to enrich. Agency account management doesn't, the relationships are the product.

If the fit is right, what Outblox delivers is what CODA delivered for this client. A full operational layer, designed for one business, built in weeks rather than quarters, at a fraction of the cost of a traditional engineering hire. If the fit is wrong, the pattern doesn't help, and I'll tell you so on a first call rather than take an engagement that will disappoint both of us.

13What I want you to take from this

The failures in this document are there on purpose. The fabricated rate table, the prompt fossil instructing the model to offer non-existent payment plans, the classification model suppressing its own correct answer. I didn't include them reluctantly. They're the reason the case study is worth reading. Any AI-assisted build at this scale produces failures of that shape. The difference between a working system and a broken one is whether those failures get caught before they ship.

The three-layer loop catches these failures because each layer has a different relationship to truth. Strategy proposes. Implementation grounds what gets proposed against the code in the repository and the rows in the database. I arbitrate between them with domain knowledge neither layer has. Remove any one of the three and the system collapses into something with a single author's failure modes, which is what most AI-assisted builds are.

The difference between a working system and a broken one is whether those failures get caught before they ship.

If you run a sales operation that would benefit from software nobody has built for you yet, skip the question of whether AI is good enough to build it. It is. The question that matters is whether you have someone who can sit in the middle of a loop like this and make the calls that neither AI instance can. If you do, the engagement works. If you don't, no amount of tooling will substitute.

Outblox takes engagements where the fit is right and declines the ones where it isn't. If you think your business might fit the profile above, the first conversation is 30 minutes and it costs nothing. I'll read your sales motion, ask 3 or 4 questions about your operator layer, and tell you whether the pattern applies. If the answer is no, I'll say so on that call and point you at what would actually help. The case study you just read is the longer version of that same posture.

demo.outblox.com shows what the front end of a live system can look like when it's built for the shape of one business. If you want to see the pattern before you talk to me, start there.


This case study is based on the commit history of the CODA repository, conversation logs from the build, and my working notes across the February to April 2026 engagement. Client-identifying details have been abstracted. Technical details have not been simplified. The chat excerpts are verbatim structurally, with identifying specifics removed. Commit hashes are real.