<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[listenrightmeow]]></title><description><![CDATA[I've spent 15 years building software for domains where getting it wrong has consequences — healthcare, finance, clinical trials, compliance. This blog is about what rigorous architecture actually looks like, written by someone still in the middle of it.]]></description><link>https://listenrightmeow.hashnode.dev</link><image><url>https://cdn.hashnode.com/uploads/logos/69ae573286766ac3a67a2e78/6c5087a6-aa56-437b-8d2e-beb951a3d337.png</url><title>listenrightmeow</title><link>https://listenrightmeow.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Thu, 17 Sep 2026 02:18:25 GMT</lastBuildDate><atom:link href="https://listenrightmeow.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Harnesses, Playbooks, and Proof]]></title><description><![CDATA[In February, OpenAI published Harness engineering: leveraging Codex in an agent-first world. Ryan Lopopolo and his team spent five months building a product where no human wrote a line of code, and sh]]></description><link>https://listenrightmeow.hashnode.dev/harnesses-playbooks-and-proof</link><guid isPermaLink="true">https://listenrightmeow.hashnode.dev/harnesses-playbooks-and-proof</guid><category><![CDATA[#ai-tools]]></category><category><![CDATA[software development]]></category><category><![CDATA[DDD]]></category><category><![CDATA[event-driven-architecture]]></category><dc:creator><![CDATA[Mike Dyer]]></dc:creator><pubDate>Wed, 16 Sep 2026 05:05:12 GMT</pubDate><content:encoded><![CDATA[<p>In February, OpenAI published <a href="https://openai.com/index/harness-engineering/">Harness engineering: leveraging Codex in an agent-first world</a>. Ryan Lopopolo and his team spent five months building a product where no human wrote a line of code, and shared what it took. Six months later, in August, Anthropic published <a href="https://claude.com/blog/the-ai-native-sdlc-playbook">The AI-Native SDLC playbook</a>, a stage-by-stage guide to rebuilding the whole development lifecycle around agents, from planning all the way through maintenance.</p>
<p>Different companies, different months, same purpose. Both are about making agentic development faster and more accurate.</p>
<p>That's been my purpose too. I've spent over 15 years building software and leading the teams that build it, and the last two years building a delivery stack for AI agents. When I read both posts, I recognized conclusions I had already written into my own methodology, in my own words. The tools that carry that methodology are recent, and their history is on record in my decision streams, my documentation, and my git history. The discipline behind them came from years of watching where software actually breaks.</p>
<p>We're all moving along the same path. We're just not following in each other's footsteps. This post is the view from 10,000 feet, and each section below gets its own deep dive in the series that follows.</p>
<h2>Where all three of us agree</h2>
<p>OpenAI's <a href="https://openai.com/index/harness-engineering/#redefining-the-role-of-the-engineer">early progress</a> was slow. The agent was capable. What it lacked was an environment that told it enough. When something failed, their engineers asked "what capability is missing, and how do we make it both legible and enforceable" for the agent.</p>
<p>Anthropic's playbook starts from a related observation. Code is no longer the bottleneck. The stages around it still move at human speed, and the old controls weren't built for agents writing most of the diff.</p>
<p>I agree with both. My own numbers tell the same story. Without structure, I was correcting roughly 40% of what an agent produced. With the full methodology in place, that dropped to around 5%. Same models. Better inputs.</p>
<p>We also agree on a lot of the mechanics. Knowledge belongs where the agent can read it. Each stage should leave behind an artifact the next stage can use. Guidance is helpful, but anything that must always hold needs a deterministic check behind it. And the agent that wrote the code should never be the one that approves it.</p>
<p>So this isn't a rebuttal of either post. It's a look at where our paths split, and why.</p>
<h2>Where the paths split</h2>
<p>OpenAI was clear about what they were optimizing for. They wanted engineering velocity by orders of magnitude, and the scarce resource they were protecting was human time and attention. Anthropic's playbook is aimed at the stages that still run at human speed, so that the whole lifecycle can keep up with the build.</p>
<p>Both are about throughput, and both do it well.</p>
<p>My goal is different. I build domain tooling so agents can work accurately and quickly, sure. But the real reason is that what we deliver has to be measurable, provable, and still true to the vision that was agreed on before any engineering started.</p>
<p>That changes who the process is for. Business decides what matters. Product defines the outcomes. Design shapes the experience. Engineering builds it. Testing proves it. Data depends on the shapes that move through it. Sales has to explain it to someone who writes a check. When the work is agent-driven, every one of those groups still needs to trust that what shipped is what they signed off on.</p>
<p>A lot of my work also lives in regulated industries like healthcare and financial services. That's a factual, ledgered world. Everything has to be proven. Most of the differences below come from those two facts.</p>
<h2>Where the work starts</h2>
<p>OpenAI's story begins with an empty git repository. Anthropic's begins with an <code>intent.md</code>, an idea written up with Claude and then turned into requirements and a design in a single working session.</p>
<p>Mine begins with an empty domain, and with the people who run the business in the room.</p>
<p>We start with domain-driven design and event storming, run as narratives that walk through what happens and when. From there I use <a href="https://github.com/listenrightmeow/signal-driven-development">Signal-Driven Development</a>, or SiDD, an open methodology I wrote to help architects bring a domain model to convergence. SiDD takes a candidate model, produces a report of everything the model can't answer yet, resolves those gaps one at a time, and repeats until nothing is left. Across nine products, that process has resolved 275 gaps before a single line of code was written.</p>
<p>Then I test the design itself. <a href="https://moment.mmmnt.ai/">Moment</a> is an open source language I built for describing a domain model. Most modeling tools capture structure: the contexts, aggregates, commands, and events. Moment also captures time, meaning how events move from one part of the business to another and in what order.</p>
<p><a href="https://facet.mmmnt.dev">Facet</a> is a free visual simulator for Moment models. It plays the flows through the design and checks them as they go. Do events reach the contexts they should, in the right order? Do the contracts at each boundary hold? Do long-running processes move through their states the way they claim to? Do the reactions the model promises actually fire? Because Facet is visual, the people who approved the design can watch it run instead of reading about it.</p>
<p>OpenAI <a href="https://openai.com/index/harness-engineering/#what-were-still-learning">closes their post</a> by saying they don't yet know how architectural coherence holds up over years in an agent-generated system. My answer is that coherence can't be something you hope survives. It has to be designed, agreed on, and tested before the agents start typing.</p>
<h2>What a specification is</h2>
<p>At OpenAI, <a href="https://openai.com/index/harness-engineering/#what-agent-generated-actually-means">humans prioritize work</a>, turn user feedback into acceptance criteria, and validate outcomes. In Anthropic's playbook, the specification is a <code>spec.md</code> and a <code>plan.md</code>, readable markdown that both people and agents work from, and the agent generates the tests alongside the code.</p>
<p>This is where I hold my hardest opinion, and I'll be direct about it. An agent should never write the tests for its own work. It will agree with itself every time. That's the biggest flaw I see in spec-driven development in the AI space right now, and it's the one I've worked hardest to remove.</p>
<p>My specifications are written in <a href="https://feature.mmmnt.ai/">Feature</a>, an open source specification language I built for agent-driven delivery. A Feature spec has two parts, and they're treated very differently.</p>
<p>The first part is instruction. It's written in plain language and tells the agent what to build: where the handler lives, what it's allowed to touch, what constraints it has to respect. This part guides the agent, and the agent has room to decide how to meet it.</p>
<p>The second part is measurement. It defines the contracts and predicts every observable effect the feature will have: the response, the events, the data written, the messages sent. None of that is inferred. A person authors those predictions, a person agrees to them, and the compiler turns them into a complete test suite. The agent building the feature never writes a single assertion it will be graded against.</p>
<p>And here's the best part, if you've read this far. Those measurements aren't invented at the spec layer. They map directly to what the Moment model already produced and Facet already tested. The contracts at each boundary become the input shapes. The events a flow emits become the predicted events. The branches where a flow refuses become the rejection cases. The points where a flow ends become the final state the spec expects. The design that was agreed on and proven is the same design the tests measure.</p>
<p>The rule underneath it is simple. The spec declares everything that should happen. Anything that happens and wasn't predicted is a violation. Anything predicted that didn't happen is a violation too.</p>
<p>The scenarios come straight out of the narratives the business approved, so what stakeholders signed off on during design is exactly what the pipeline measures during delivery.</p>
<p>I won't sugarcoat the cost. This is the part of the process that demands the most human investment. Deciding exactly what a feature should produce, down to the last write, is real work, and it can't be handed off. If a product team lets its discipline slip here, the agent's output will slip right along with it. The quality of what gets built is capped by the quality of what gets predicted.</p>
<h2>Who decides the work is right</h2>
<p>This is the sharpest difference, so I want to be fair about it.</p>
<p>OpenAI has pushed almost all review to agents reviewing agents, and agents often merge their own pull requests. Anthropic's playbook uses layers of agent review, continuous evals, and in its most autonomous stage, an adversarial reviewing agent that can decide whether work moves forward. It still keeps human review for regulated and critical code, and a human has to authorize anything that reaches production.</p>
<p>None of that is wrong. For a lot of teams, it's exactly the right call. It just doesn't work for me.</p>
<p>In a ledgered world, inference isn't a luxury I get to spend at the point where something is judged correct. It isn't a trust I can place in any model, no matter how capable it is. So in my stack, inference is removed everywhere we can remove it. A model can help shape the instructions in a specification, because those instructions translate a design that was already tested. A model never writes the measurements, and it never decides whether the result passes. The compiled tests do, and the ledger records it.</p>
<p>Having to see things that way pushed me down different paths than the ones these posts describe. The upside surprised me. The feedback loop got tighter, and the product that came out the other side got better.</p>
<h2>Keeping things from drifting</h2>
<p>OpenAI is <a href="https://openai.com/index/harness-engineering/#entropy-and-garbage-collection">candid about drift</a>. Agents copy whatever patterns already exist in the repo. Their answer is to encode principles and run background agents that find deviations and open cleanup pull requests, which they compare to garbage collection. Anthropic's answer is to keep a short <code>CLAUDE.md</code> that the team updates whenever the agent repeats a mistake, back important policies with hooks, and keep the plan in sync with the code.</p>
<p>I'd rather not produce the garbage in the first place.</p>
<p>In my stack, generated tests are never committed. The pipeline compiles them from the current spec on every single run. A committed test suite is a place where a stale file can hide, and where a reviewer or a signature check can miss it. Take the file out of the repository and there's nothing left to go stale.</p>
<p>That idea goes past tests. Every boundary has to be enforced inside the system it protects. A rule that lives in a document and nowhere else isn't a boundary. It's a suggestion.</p>
<h2>Memory that knows what's no longer true</h2>
<p>OpenAI <a href="https://openai.com/index/harness-engineering/#we-made-repository-knowledge-the-system-of-record">tried the one giant instruction file</a> and watched it rot. They describe it filling up with outdated rules that agents could no longer tell apart from current ones. Their fix is a structured docs directory plus a recurring agent that looks for stale documentation. Anthropic keeps <code>CLAUDE.md</code> under a page and treats the chain of committed artifacts as the record.</p>
<p>Both named the right problem. Staleness is the whole problem. A stale test fails. A stale type throws an error. A stale instruction file produces a confident agent with no warning at all.</p>
<p>My answer is <a href="https://flmnt.ai">flmnt</a>, a memory layer for agents that took me months and hundreds of revisions to get right. In flmnt, decisions aren't just stored. They're authored, and when a decision changes, the new one explicitly supersedes the old one with a typed link. History is kept. Nothing is deleted. But an agent reading that memory can tell which decision is current and which one was replaced, even when the replacement was recorded somewhere else entirely.</p>
<p>I call this decision currency. Recall asks what was said. Currency asks what's still true. It's the piece I'm proudest of, because it's what keeps the original vision intact while the details keep changing underneath it.</p>
<h2>Gates and proof</h2>
<p>OpenAI <a href="https://openai.com/index/harness-engineering/#throughput-changes-the-merge-philosophy">runs with minimal blocking merge gates</a>, and flaky tests often get rerun instead of blocking progress. When agents produce more than humans can watch, fixing a mistake later costs them less than holding work in a queue. Anthropic's playbook uses hooks as approval gates, and the agent can act all the way up to the production gate but never past it.</p>
<p>In regulated systems, a correction isn't cheap. It's an incident, an audit finding, or a bug a patient sees. So my gates block. A red build is a blocker. There are no skip flags.</p>
<p>Every pipeline run also produces an evidence bundle tied to the exact commit it came from. When Feature is wired into CI, those results are signed and written to an append-only ledger in the Feature dashboard, tracked per specification and per environment. Nothing in that ledger can be edited after the fact. An auditor doesn't have to take anyone's word for how the system behaves. They get a front row seat to the system proving itself, run after run, before anything reaches production.</p>
<p>That changes what a young company can say out loud. Certifications like SOC 2 and HITRUST need months of evidence that controls are working in production before an auditor will sign off. Most teams build first, start that clock later, and spend a year or more fixing what the audit finds. When compliance controls are part of the architecture from the first deploy, the clock starts on day one.</p>
<p>So the pitch stops being "we take security seriously." Two products I've owned this year show what it becomes instead. One is a health application that passed a third-party HIPAA audit within 90 days of its first line of code. The other is a financial intelligence platform that opened its SOC 2 observation window six months after the business opened its doors. An early-stage company with certifications in hand, months or years before its competitors can get there, is having a completely different conversation with buyers. Leadership and sales aren't describing what they hope the product does. They're pointing at proof.</p>
<p>We prove delivery. We don't hope for it.</p>
<h2>Agents still get room</h2>
<p>None of this means agents are on a short leash. They choose how to build, and they get creative about it. But every freedom an agent has sits inside a boundary the design process already measured.</p>
<p>The agent decides how. The design already decided what, and the ledger proves it.</p>
<h2>This is leadership work</h2>
<p>OpenAI makes a <a href="https://openai.com/index/harness-engineering/#enforcing-architecture-and-taste">comparison</a> I appreciated. They describe their approach as something like leading a large platform organization. Hold the boundaries firmly, and give teams room to decide how they work inside them. Anthropic ends their playbook in the same spirit: "The loop keeps running. Human judgement stays above it."</p>
<p>I'd take it one step further. Harness engineering is leadership, and leadership means extreme ownership.</p>
<p>When an agent ships something wrong, the agent isn't accountable. I am. That's why I front-load the design, make the specification something a person agrees to, keep decisions current, and block the build when something doesn't hold. Every one of those is a choice to own the outcome before it happens instead of explaining it afterward.</p>
<p>Ownership also means being honest about what tooling can't do. None of my tools can tell you whether your design matches what your business actually needs. That's a people problem. Traditional domain modeling sessions put business decision makers in the room, and I expect the same when we converge a design. If it's just an architect and an AI, the architect carries that risk. At a minimum, get buy-in from the wider organization before you put the design into motion.</p>
<h2>Why I'm writing this</h2>
<p>I've been heads down for almost two straight years building domain tooling for exactly this problem. Early on, I kept running into the same gaps in AI-driven development. Agents guessed where they should have asked. Boundaries lived in documents instead of in the system. Teams found out what was wrong after it was built.</p>
<p>So I went after those gaps one at a time, working alongside Claude the whole way. How do we limit what an agent is allowed to infer? How do we tighten boundaries so they enforce themselves? How much can we prove before we build anything at all?</p>
<p>That work has paid off in more ways than I expected, and one of them is watching the industry arrive at the same place from different directions. The market is converging fast on one idea. Speed isn't the hard part anymore. Accuracy is. I'm very much embedded in that part of the industry, and I plan to keep sharing what I learn from inside it.</p>
<h2>What the rigor costs, and what it buys</h2>
<p>I won't pretend this is the easy road. My way of working asks for a lot more discipline than either post describes. It takes more thought, more leadership, and more time before the first line of code. On a typical project, planning is around 70% of my investment. The remaining 30% goes to building and validation.</p>
<p>The harness and the playbook look like friendlier investments up front. They ask for enough discipline to lay a solid foundation for a long running project, and they let a team start moving quickly.</p>
<p>In my experience, the difference evens out over time, and then it flips. Every bit of rigor built into the environment before we build gives the agent more room to operate inside those boundaries. And because the boundaries were measured before the work started, far less human attention is needed to confirm what the agent did inside them. That's where the velocity catches up. Past that point, an agent working inside a proven design can keep pace with, and sometimes outrun, one working in an environment that asked for less up front.</p>
<p>You pay for the rigor once. You get the speed back on every feature after it.</p>
<h2>What's next</h2>
<p>Both posts confirmed something I've believed for a long time. The discipline didn't go away when agents started writing code. It just lives in different places now. Where you put it depends on where you're trying to go.</p>
<p>Over the next few posts I'll go deeper into each piece, starting with a closer, constructive look at OpenAI's harness engineering post. After that: drift, memory and supersession, specifications and prediction, domain design with SiDD, Moment, and Facet, and evidence you can hand to an auditor.</p>
<p>All of these tools fit together into one delivery chain called <a href="https://complai.mmmnt.ai/">Complai</a>, and most of it is open for you to use today. Feature, Moment, and SiDD are open source. Facet is free.</p>
<p>If you've landed on the same conclusions, or different ones, I want to hear about it.</p>
]]></content:encoded></item><item><title><![CDATA[Your tests can't see the waves]]></title><description><![CDATA[Ten years ago, if you wanted an event-sourced system with properly separated bounded contexts, sagas coordinating across service boundaries, projections rebuilding from an immutable log, and a real ub]]></description><link>https://listenrightmeow.hashnode.dev/your-tests-cant-see-the-waves</link><guid isPermaLink="true">https://listenrightmeow.hashnode.dev/your-tests-cant-see-the-waves</guid><category><![CDATA[DDD]]></category><category><![CDATA[#ai-tools]]></category><category><![CDATA[QA]]></category><category><![CDATA[agentic AI]]></category><dc:creator><![CDATA[Mike Dyer]]></dc:creator><pubDate>Wed, 02 Sep 2026 01:25:59 GMT</pubDate><content:encoded><![CDATA[<hr />
<p>Ten years ago, if you wanted an event-sourced system with properly separated bounded contexts, sagas coordinating across service boundaries, projections rebuilding from an immutable log, and a real ubiquitous language holding it together — you needed a team. A big one. You needed an architect who'd done it before, several engineers who could be taught, a year, and an organization willing to fund all of that before anything shipped.</p>
<p>That was the actual barrier. Not the ideas. Evans published in 2003. The ideas have been sitting there, freely available, for twenty years. What stopped most people from using them was that the labor cost of doing it properly was enormous, and the payoff arrived far too late to survive a budget conversation.</p>
<p>That barrier is gone.</p>
<p>I'm one person. My current system has multiple bounded contexts, an event-sourced core, sagas with timeouts, projections, and a domain model that was converged over several formal passes before a line of implementation existed. Agents did nearly all of the typing. A meaningful number of people reading this have built something of comparable structural complexity in the past year, alone or nearly alone, and are quietly aware that it's more machinery than they've ever personally been responsible for before.</p>
<p>The complexity of what we can build went up by an order of magnitude. The way we verify it did not move at all.</p>
<p>We're still writing unit tests that assert return values, integration tests that assert a couple of collaborators got called, and end-to-end tests that assert the page says the right thing. That toolkit was designed for systems where the interesting behavior <em>was</em> the return value. We are no longer building those systems.</p>
<hr />
<h2>The splash and the waves</h2>
<p>Every test you've ever written asserts the splash.</p>
<p>A function is called. It returns something. You check the something. Maybe you check that one collaborator got invoked with the right arguments. The assertion surface is shaped exactly like the thing you were thinking about when you wrote the code, because you wrote the test from the same mental model, usually minutes later, often in the same sitting.</p>
<p>That's fine as far as it goes. Return values matter. Response codes matter. I'm not here to tell you to stop checking them.</p>
<p>But in a modern system, the return value is the least interesting thing a command does.</p>
<p>Call one handler in an event-sourced system and the actual footprint is: rows written, events appended, projections updated, messages queued, webhooks dispatched, caches invalidated, analytics fired, audit rows inserted, a saga timer armed. Those are the waves. They travel outward, they hit other contexts, they arrive at other people's inboxes, and they persist long after the response has been serialized and forgotten.</p>
<p>Almost none of them are asserted.</p>
<p>And I want to be careful here, because a certain kind of reader is already typing. Yes, your framework can check some of this. Strict mocks exist. <code>verifyNoMoreInteractions</code> exists. Spies exist. You <em>can</em> assert that an unexpected call didn't happen.</p>
<p>But look at what that requires. It's opt-in. It's per-test. It's per-collaborator. And it only fires if someone already imagined the specific interaction worth forbidding. You have to have thought of the wave in advance in order to check that it didn't happen.</p>
<p><strong>The default in every mainstream testing framework is permissive: anything you didn't assert against is allowed.</strong></p>
<p>That default was harmless when a unit was a function and its effects were its return value. It's structurally dangerous when a unit is a command handler sitting on top of five services, and it's how we're all testing the most complex software any of us has ever personally shipped.</p>
<hr />
<h2>What this looks like when it bites</h2>
<p>Take refunds. Everyone has built one. The domain is obvious to any reader and the stakes are obvious to any customer.</p>
<p>Here's the spec, in the language I use for this — a <code>.feat</code> file. The syntax is real; you can read it cold.</p>
<pre><code class="language-plaintext">spec SPEC-BIL-014 "RefundPayment"
context   Billing
aggregate Payment
type      command
status    verified

touches handlers/refund-payment.ts

construct:
  handler at handlers/refund-payment.ts
  imports PaymentRepository  from repositories/payment-repository
  imports RefundGateway      from gateways/refund-gateway
  Refundable balance is folded from the aggregate's own PaymentRefunded
  history. There is no refunded boolean and no denormalised total to trust.

enforce:
  validate input against RefundPaymentInput
  load Payment by paymentId, at the version carried on the request
  fold refundedTotal from prior PaymentRefunded events on the aggregate
  compute refundableBalance as capturedAmount minus refundedTotal

  rejects PAYMENT_NOT_FOUND        when no Payment exists for paymentId
  rejects PAYMENT_NOT_CAPTURED     when the Payment has never reached captured
  rejects ALREADY_REFUNDED         when refundedTotal equals capturedAmount
  rejects AMOUNT_EXCEEDS_BALANCE   when amount is greater than refundableBalance
  rejects VERSION_CONFLICT         when the aggregate version has advanced

  replay the stored outcome when idempotencyKey matches a settled refund
  issue the refund through RefundGateway before appending any event
  append PaymentRefunded carrying refundId, amount, and gateway reference

contract:
  input     RefundPaymentInput from "schemas/refund-input"
  response  RefundResponse     from "schemas/refund-response"
  error     ErrorResponse      from "schemas/error"

scenario "refunds a captured payment in full":
  given: payment "pay_8812" captured at 4200, version 3, with no prior refund
  when: RefundPayment {
          paymentId: "pay_8812", amount: 4200,
          idempotencyKey: "idem_9f21", expectedVersion: 3
        }
  predict success:
    response 200 RefundResponse { refundId: "rfnd_5107", replayed: false }
    paymentGateway has [{ action: "REFUND", reference: "pay_8812", amount: 4200 }]
    eventStore     has [ PaymentRefunded { amount: 4200, version: 4 } ]
    database       has [{ action: "UPDATE", table: "payments", refundedTotal: 4200 }]
    email          has [{ template: "refund_confirmation", amount: 4200 }]
    analytics      has [{ event: "payment.refunded" }]

scenario "rejects a payment already refunded in full":
  given: payment "pay_8812" captured at 4200, version 4, carrying a settled
         PaymentRefunded of 4200 as "rfnd_5107" at "2026-08-14T09:12:04Z"
  when: RefundPayment {
          paymentId: "pay_8812", amount: 4200,
          idempotencyKey: "idem_c40b", expectedVersion: 4
        }
  predict rejection ALREADY_REFUNDED:
    response 409 ErrorResponse {
               refundedTotal:    4200,
               existingRefundId: "rfnd_5107",
               refundedAt:       "2026-08-14T09:12:04Z"
             }
    paymentGateway has []
    eventStore     has []
    database       has []
    email          has []
    analytics      has []

scenario "replays a settled refund under the same idempotency key":
  given: payment "pay_8812" refunded 4200 as "rfnd_5107" under key "idem_9f21"
  when: RefundPayment {
          paymentId: "pay_8812", amount: 4200,
          idempotencyKey: "idem_9f21", expectedVersion: 4
        }
  predict success:
    response 200 RefundResponse {
               refundId:   "rfnd_5107",
               refundedAt: "2026-08-14T09:12:04Z",
               replayed:   true
             }
    paymentGateway has []
    eventStore     has []
    database       has []
    email          has []
    analytics      has []
</code></pre>
<p>The language is called Feature. The toolchain is at <a href="https://github.com/mmmnt/feature">github.com/mmmnt/feature</a>, and there's a browser playground at <a href="https://feature.mmmnt.ai/">feature.mmmnt.ai</a> if you want to break the grammar yourself while you read the rest of this.</p>
<p>Now look at what those three scenarios pin down that a conventional suite would not.</p>
<p><strong>Refunded-ness is derived, and the spec says where from.</strong> There is no <code>refunded</code> boolean. <code>refundedTotal</code> is a fold over prior <code>PaymentRefunded</code> events on the aggregate, and the <code>construct</code> block says so in a sentence the implementer has to read. That single line eliminates the most common refund bug in existence — a denormalised flag that disagrees with the event history after a partial refund, a replayed webhook, or a projection rebuild. The enforcement isn't "check if it's refunded." It's a stated derivation with a stated source.</p>
<p><strong>Every rejection has its own condition, in the enforce block, individually.</strong> Not-found, not-captured, already-refunded, exceeds-balance, and version-conflict are five distinct refusals with five distinct triggers. <code>feat audit</code> refuses the spec if any declared rejection has no scenario demonstrating it, so you cannot claim a refusal you never proved.</p>
<p><strong>The 409 is asserted on its contents, not its status.</strong> It echoes the total, the <em>existing</em> refund id, and the original timestamp — <code>2026-08-14T09:12:04Z</code>, a literal from the given. If the handler stamps <code>now()</code> into that field, the test fails. That's the tell that it minted something instead of reading something. A status-code assertion cannot see the difference between a handler that correctly refused and a handler that created a duplicate refund record and then errored on the way out.</p>
<p><strong>And the replay scenario asserts a 200 with zero effects everywhere.</strong> Success, and nothing happened. That combination is unrepresentable in most test suites — success is implicitly assumed to mean work occurred — and it's precisely how you prove idempotency rather than hoping for it.</p>
<hr />
<p>Now the bug those specs catch, which I'd bet money exists in production somewhere right now.</p>
<p>Someone implements the refund. The double-refund guard is correct — it returns a 409 exactly when it should. But the notification dispatch was wired slightly upstream of the guard, or into middleware, or a policy subscribed to the wrong trigger. So on the rejection path, the customer receives an email that says <em>your refund of $42.00 is on its way.</em></p>
<p>The refund did not happen. It is never going to happen. The response code was correct. The state was correct. <strong>Every conventional test on that handler passes</strong>, because every conventional test asserts the 409 and the 409 is right.</p>
<p>The waves went out anyway. Somebody is waiting for money that isn't coming, and the first person to learn about it will be a support agent, weeks later, with no idea why.</p>
<hr />
<h2>Queries are worse</h2>
<p>Here's the second one, and it's the one that surprises people.</p>
<pre><code class="language-plaintext">spec SPEC-BIL-021 "GetInvoice"
context Billing
type    query
status  verified

contract:
  input     GetInvoiceInput from "schemas/invoice-input"
  response  InvoiceResponse from "schemas/invoice-response"

scenario "returns an issued invoice by id":
  given: an issued invoice "inv_5501"
  when: GetInvoice { invoiceId: "inv_5501" }
  predict success:
    response 200 InvoiceResponse
</code></pre>
<p>There's a rule in the grammar: <strong>a spec declared as</strong> <code>type query</code> <strong>cannot express a write.</strong> Not "shouldn't." It's a parse error. You cannot author the file.</p>
<p>Which means declaring something a query silently predicts zero effects everywhere, permanently, without anyone writing a single assertion about it.</p>
<p>Now think about what actually accumulates on read endpoints in a real system over a couple of years. A <code>last_viewed_at</code> timestamp. A rate-limit counter increment. A cache warm. An analytics event. An access audit row — often added deliberately, for compliance, by someone who never told the original author.</p>
<p>Every one of those is a write on a path everybody in the building believes is read-only. They're the reason a report endpoint takes a row lock under load. They're the reason a "read" shows up in a GDPR export. They're the reason your replica lag spikes when someone opens a dashboard.</p>
<p>And no test anywhere fails, because the invoice body came back correct and the invoice body is the only thing anyone thought to check.</p>
<p>Side-effect freedom is treated as a convention that people honor. It should be something the grammar refuses to let you violate.</p>
<hr />
<h2>Inverting the assertion surface</h2>
<p>The mechanism that makes all of this tractable is one inversion, and it's worth stating precisely because it sounds impossible until you see the trick.</p>
<p>You cannot enumerate everything that shouldn't happen. That list is infinite. Every test suite that approaches this from the "assert the absence" direction dies on that fact.</p>
<p>So you don't. <strong>You enumerate what should happen — which is finite and small — and everything outside it is a violation by construction.</strong></p>
<p>That's the whole move. A scenario declares the complete observable footprint. Predicted-and-captured passes. Captured-but-not-predicted fails. Predicted-but-not-captured fails. Right event, wrong shape, fails. Nobody had to imagine the audit row in advance. Nobody had to know the email existed. The absence of prediction <em>is</em> the assertion.</p>
<p>A failure reads like this:</p>
<pre><code class="language-plaintext">UNPREDICTED record PaymentAuditLogged in eventStore
  predicted  eventStore has [ PaymentRefunded { amount: 4200, version: 4 } ]
  captured   [0] PaymentRefunded    ✓ predicted
             [1] PaymentAuditLogged ✕ unpredicted
  anchor     refund-payment.feat › "refunds a captured payment in full" › eventStore[1]
</code></pre>
<p>You didn't write that assertion. You didn't have to.</p>
<p>Two supporting rules make it hold up in real use. Every configured service must appear in every prediction — omitting one is a validation error, and zero is written down explicitly as <code>has []</code>. You cannot quietly decline to look at a service, which is exactly how effects hide. And predictions compile deterministically into the suite with no model and no glue code in between, then get byte-compared in CI, so the spec you agreed to is provably the spec that ran.</p>
<p>We tested that claim adversarially: one planted defect per scenario, every tool configured the way its own docs recommend, every lane required to pass a clean implementation first, one binary question per cell. An unpredicted audit-row write and a leaky rejection both came back fully green in every conventional lane — not because those suites were bad, but because nothing in them was ever pointed at the thing that broke. Ten generations of the same spec produced one byte-identical suite; the agent lanes produced ten unique suites out of ten and none of them caught the planted write. Where conventional approaches win, they're published too. It's version 0.1.4, two lanes are still pending, and a benchmark I built is a benchmark I built — the harness regenerates from scripts, so go break it.</p>
<p>One limitation no inversion fixes: a spec can predict the wrong footprint, an implementation can match it perfectly, everything passes, and the software is wrong. Predictions being reviewable data in one file rather than assertions scattered across a suite makes that visible at review. That's a mitigation, not a guarantee, and anyone selling you a guarantee at that layer is lying.</p>
<hr />
<h2>The surface moved</h2>
<p>Something changed in what we're capable of building, and it changed fast enough that most of us haven't stopped to account for it.</p>
<p>The architectural patterns that used to require a funded team and a year of runway are now reachable by one person with a converged model and an agent doing the typing. Event sourcing, bounded contexts, sagas, projections — the whole vocabulary that was enterprise-only a decade ago is now a weekend away for anyone who wants it. That's a genuine expansion, and I don't think it's reversible.</p>
<p>What expanded with it is the effect surface. Every one of those patterns multiplies the number of observable things a single command does. A handler that once returned a value now writes rows, appends events, updates projections, queues messages, notifies humans, and arms timers — six or eight waves per splash, across contexts that don't know about each other.</p>
<p>The verification did not expand. It is the same shape it was when a unit was a function: assert the return, assert a call or two, move on. We are measuring a system with eight effects using an instrument built to see one.</p>
<p>That gap isn't negligence. It's what happens when capability jumps a generation and the tooling around it doesn't. The assertion model most of us are using was correct for the software we were writing when we learned it, and it quietly stopped being sufficient somewhere in the last two years, without an announcement.</p>
<p>Go pick your highest-stakes command handler — the one that moves money, or provisions access, or sends something a customer will read. Write down every observable thing it does. Then count how many are asserted anywhere in your suite. Then do the same for its rejection path, which is the one nobody ever writes down.</p>
<p>The number is lower than you expect. It was in my system too, before I started predicting the footprint instead of checking the return.</p>
<p>Your code is making waves. Something should be watching them.</p>
]]></content:encoded></item><item><title><![CDATA[Slop Is Upstream]]></title><description><![CDATA[My feed has decided the machines are bad at this.
The word is "slop." It gets applied to code, to prose, to pull requests, to entire products. The implied argument is always the same: the model produc]]></description><link>https://listenrightmeow.hashnode.dev/slop-is-upstream</link><guid isPermaLink="true">https://listenrightmeow.hashnode.dev/slop-is-upstream</guid><category><![CDATA[DDD]]></category><category><![CDATA[Spec-Driven-Development]]></category><category><![CDATA[software architecture]]></category><category><![CDATA[ai agents]]></category><dc:creator><![CDATA[Mike Dyer]]></dc:creator><pubDate>Fri, 28 Aug 2026 13:29:49 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/69ae573286766ac3a67a2e78/97dc77b9-24b4-4d1b-a288-c486dc018c7a.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>My feed has decided the machines are bad at this.</p>
<p>The word is "slop." It gets applied to code, to prose, to pull requests, to entire products. The implied argument is always the same: the model produced garbage, therefore the model is garbage, therefore anyone shipping with it is a fraud. It's a satisfying take. It costs nothing to hold, and it flatters everyone who holds it.</p>
<p>I'm not going to tell you the models are flawless. I run an AI scheduling assistant in production that was built almost entirely by agents, and it has been wrong in ways that reached real people. That isn't the argument.</p>
<p>The argument is that almost every slop post I read is describing a <strong>loop</strong>, not a tool. And the loop belongs to the person posting.</p>
<hr />
<h2>What the complaint is actually describing</h2>
<p>There are two failures stacked on top of each other, and neither one happens inside the model.</p>
<p><strong>The first is the prompt.</strong> People love letting the model infer. They use natural language understanding to the absolute limit of its <em>capacity</em> and none of its <em>strengths</em>. A paragraph of intent. No inputs. No outputs. No contracts. No acceptance criteria. No domain vocabulary. No statement of what must be true when the thing is finished. And then they expect the model to be psychic — to reconstruct an entire unstated system from a sentence and land precisely on the picture in their head.</p>
<p>NLU is extraordinary at parsing an ambiguous instruction. That is not the same as the instruction being complete. Parseable and specified are different words.</p>
<p>Nobody would hand a contractor a single paragraph and expect back the building they were imagining. We understand this instinctively when the work takes six weeks and arrives from a person. We abandon it entirely when the work takes nine seconds and arrives from a machine — as though speed of delivery somehow implies completeness of instruction.</p>
<p><strong>The second is the workflow.</strong> That loose prompt doesn't stay in a chat window. It goes into a real pipeline. The model produces work. The work gets a smell test — a glance, a nod, "looks okay" — and moves toward a customer-facing environment on the strength of that nod. There is no contract it was measured against, because nobody wrote one. There is nothing measurable at all, because nothing measurable was ever defined. The only quality signal in the entire chain is one person's aesthetic reaction to a diff they skimmed.</p>
<p>Then it breaks in front of a customer, and the model takes the blame.</p>
<p>That is not a model failure. That is a system with no specification, no acceptance criteria, and no evidence standard producing an unpredictable result — which is the only thing such a system has ever been capable of producing. It would behave identically with a junior engineer, an agency, or you at two in the morning.</p>
<hr />
<h2>The number that ends the argument</h2>
<p>I've been keeping records on this for a while, across nine products taken from innovation to production.</p>
<p>Ungoverned, AI-mediated delivery in my own work ran roughly a <strong>40% correction rate</strong>. Under the methodology I'll describe below, it runs <strong>95%+ accuracy</strong>, and in its current version the correction rate is trending under 3% — because a whole class of former corrections has become a parse-time or audit-time refusal instead.</p>
<p>Same models. Same person. Two orders of difference in outcome.</p>
<p><strong>The variable is input precision, not AI capability.</strong> That's not a slogan; it's the only conclusion the numbers support. If the difference between slop and rigor were a property of the model, that gap would not move when the only thing I changed was what I handed it.</p>
<p>There's a second finding underneath it that took me longer to accept. The two most expensive production failure classes in everything I've built were not <em>wrong</em> decisions. They were <strong>silent</strong> ones. Something proceeded plausibly where it should have stopped and asked.</p>
<p>That is a precise definition of slop, and notice where it lives. Slop isn't the model being stupid. Slop is a system continuing confidently past the point where it had enough information to continue. Every layer I'm about to describe exists to make that impossible — to force a halt where precision runs out, instead of letting plausibility carry the work forward.</p>
<hr />
<h2>The chain</h2>
<p>The thing I'd most like people arguing about prompts to understand is that the prompt is not the top of the stack. It's near the bottom, and it's load-bearing only if everything above it is sound.</p>
<p>Seven layers, each reading only from the layers above it.</p>
<p><strong>L0 — The palette.</strong> Domain-driven design and Event Storming as a binding grammar: commands, aggregate roots, domain events, policies, sagas, projections, invariants, value objects, bounded contexts, context relationships. Evans and Brandolini, extended with reactive concepts. Every layer speaks this vocabulary, every term has exactly one meaning inside its boundary, and the classification rules are enforced rather than suggested. A command targets exactly one aggregate. An aggregate emits events after a successful transition, or rejects with none. Events are past tense. State without rules is not an aggregate. Violating one is a defect.</p>
<p><strong>L1 — Regulatory framing,</strong> when formal compliance is in scope. Skipped otherwise.</p>
<p><strong>L2 — The converged domain model.</strong> The product's actual structure and behavior over time.</p>
<p><strong>L3 — System architecture.</strong> Prescriptive defaults plus a corpus of architecture decision records.</p>
<p><strong>L4 — Specification.</strong> Executable specs and schema contracts.</p>
<p><strong>L5 — Execution.</strong> The build index, the agent, the closed boundary it works inside.</p>
<p><strong>L6 — Verification.</strong> Automated comparison of what execution actually produced against what the specification said it would.</p>
<p>Architecture constrains specification. Specification constrains execution. Verification measures the distance between the two. Skip a layer, hand down an incomplete artifact, or let an ambiguity pass unchecked, and it doesn't stay local — it cascades into execution errors that look, from the bottom, exactly like the model being bad at its job.</p>
<p>Almost everyone posting about slop is operating at L5 with nothing above it and nothing below it. They're tuning the prompt because the prompt is the only layer they have.</p>
<hr />
<h2>L2: Converge the model before you write anything</h2>
<p>This is where the work actually is, and it's the layer nobody builds.</p>
<p>I use <strong>Signal-Driven Development</strong> — domain-driven design with a feedback loop. Model, diagnose, resolve, repeat. It replaces the traditional multi-day workshop full of stakeholders with an AI-mediated convergence loop, which is the thing that makes rigorous DDD available to one person instead of a department.</p>
<p>Each pass produces a candidate domain specification, then a <strong>gap report</strong> — an enumeration of everything the model cannot answer about itself. Gaps come in four kinds. Structural ones are mechanical: an orphaned command, an aggregate that emits no events, an event nothing consumes. Heuristic ones are quality signals: an aggregate carrying too many commands, a context with too little policy coverage. Language ones are ubiquitous-language violations: synonyms, homonyms, undefined terms, events that aren't past tense.</p>
<p>The fourth kind is the important one. <strong>Decision gaps</strong> are places where multiple valid modeling choices exist and only the architect can choose. They're classified as errors, and they block convergence entirely. The system is not permitted to pick one and move on. That is the halt-loudly rule made structural at the modeling layer, and it is the single highest-leverage rule in the whole stack — because a decision gap silently resolved in pass one is a defect that reaches production in month four wearing a completely different costume.</p>
<p>Convergence is done at <strong>zero unresolved gaps</strong>, never at a pass count. And there's an invariant on the loop itself: the gap count must decrease across passes. If a pass finds more gaps than the previous one resolved, the resolutions are generating more uncertainty than they eliminate — stop, and go reassess the foundations rather than iterating harder.</p>
<p>Here's what that produces in practice, across nine products:</p>
<ul>
<li><p>Pass one surfaces 16 to 34 gaps.</p>
</li>
<li><p>Pass two brings it to 6 to 9.</p>
</li>
<li><p>Pass three lands at 0 to 2.</p>
</li>
<li><p>Average across all nine: <strong>3.2 passes, 275 total resolutions, and zero production-discovered modeling errors.</strong></p>
</li>
</ul>
<p>That last number is the one I'd put in front of anyone who thinks this is ceremony. Not zero bugs — I have plenty of bugs. Zero cases where production revealed that the <em>domain model itself</em> was wrong. Every structural surprise got caught while it was still cheap, by a process built to go looking for them.</p>
<h3>Moment: the model as a language, not a whiteboard</h3>
<p>The convergence loop needs somewhere to live, and sticky notes decay the moment the session ends.</p>
<p><strong>Moment</strong> is an open-source domain specification language and toolchain. A single <code>.moment</code> file captures two things at once.</p>
<p>The <strong>spatial</strong> model is what you'd expect from any DDD tool: bounded contexts, aggregates, commands, events, value objects, invariants, policies, sagas, projections, and the relationships between contexts.</p>
<p>The <strong>temporal</strong> model is the part that doesn't exist anywhere else. Cross-context event flows. Moments ordered along a timeline. Context crossings with contract obligations attached to each one. The existing domain-modeling languages — ContextMapper, LEMMA, DomainLang — are spatial only. They describe what your system <em>is</em>. None of them describe what it <em>does over time</em>, which is where every interesting failure in a distributed system actually lives.</p>
<p>And all of it is executable as integration tests.</p>
<p>The toolchain runs a fixed pipeline: parse, derive, generate, emit. Parse enforces the grammar and roughly twenty structural validators. Derive walks every branch point and enumerates the paths. Generate emits Gherkin, specification documents, and an AsyncAPI description. Emit produces TypeScript types, aggregates, and test scaffolds.</p>
<p>On my scheduling product, that pipeline turned sixteen user journeys into 106 scenarios and over 1,300 events — exactly one happy path per flow, with everything else a variant, a failure, a negative, or a timeout. Plus the topology, an event catalog, an impact analysis, and the saga state machines. All of it derived, none of it hand-written, none of it free to drift.</p>
<p>Two properties matter more than the feature list.</p>
<p><strong>The contract space is closed.</strong> Every schema name used in a scenario must be declared. An undeclared one is a parse error, not a warning. You cannot reference something that doesn't exist and find out at runtime.</p>
<p><strong>There is no LLM anywhere in it.</strong> Moment is a local toolchain and a pure transformation pipeline. No API keys, no inference, nothing probabilistic. Given the same model, it produces byte-identical output forever. That is deliberate: the artifact your agent is held to cannot itself be something that might have hallucinated.</p>
<p>Before any agent was asked to build anything, I had a machine-verified enumeration of every path a user can take through my product, every point at which the system can refuse, and every contract crossing a context boundary. I could not confidently specify anything without that, and I don't believe anyone can.</p>
<hr />
<h2>L4: The spec is a translation, not an invention</h2>
<p>Once the domain is converged, <strong>Feature</strong> takes over. One executable spec per command or query handler.</p>
<p>The scenarios aren't imagined. Happy-path moments become success scenarios. Branch conditions become rejection scenarios on the command that owns them. The invariants become rejection rules, corroborated in both directions against those scenarios. The spec's type <em>is</em> the palette classification, made executable.</p>
<p>The spec is not where I decide what the system does. It's where I formalize a decision the model already made and already tested, and bind it to something that runs.</p>
<p>That's the whole point, and it's the line I'd defend hardest: <strong>without a converged model, specs have nothing to translate. Without specs, the model leaves the decisions to the agent.</strong></p>
<p>A <code>.feat</code> file has two zones, and the split is the reason the format works. The <strong>agent zone</strong> is freeform natural language — what to construct, what to enforce — where a parse error is impossible by design. The <strong>compiler zone</strong> is a strict typed grammar where malformation is a parse error and never a silent failure. One file, two audiences, no ambiguity about which half is prose and which half is law.</p>
<p>Four reference spaces are closed and validated at parse time: service keys, schema names, command names, actor names. An unknown name doesn't compile, and the error lists the valid ones.</p>
<hr />
<h2>L5: What the agent is actually allowed to do</h2>
<p>Now, finally, the prompt.</p>
<p>Execution runs as four subtasks with checkpoints between them. <strong>Context review</strong>: read the spec, its contract registry, and the shared contracts it references — never another spec's internals. <strong>Test generation</strong>: a mechanical compiler step. Zero human- or agent-authored test code, byte-deterministic output, committed next to the spec. <strong>Implementation</strong>: make the generated suite green, touching only the file paths the spec declares. <strong>Verification</strong>: every scenario green, zero unpredicted, zero missing, zero schema violations.</p>
<p>Read that second step again, because it's the one people find surprising. <strong>The agent does not write its own tests.</strong> It cannot. Tests are compiled from the specification by a deterministic tool. The single most common way AI-generated code launders its own defects — writing an implementation, then writing a test that agrees with it — is structurally unavailable.</p>
<p>The boundary around execution is machine-enforced rather than requested. Only agreed specs get built. Only declared paths get touched. No architectural decisions that aren't already in the spec.</p>
<p>And two protocols do the heavy lifting:</p>
<p><strong>Ambiguity.</strong> On an ambiguous directive, the agent halts that spec and asks. It does not guess, does not annotate the file with its interpretation, does not quietly downgrade the status and move on. Resolution is a spec edit made by me, after I answer.</p>
<p><strong>Failure.</strong> If a generated test fails, the spec is correct and the code is wrong. If the agent believes the <em>spec</em> is wrong, it halts and flags it. It does not edit the spec. It does not edit the generated test. Those two sentences eliminate an entire genre of AI-assisted disaster, and they cost nothing except the discipline to mean them.</p>
<hr />
<h2>L6: Measuring the delta between what was specified and what was built</h2>
<p>This is the layer that makes the rest of it verifiable rather than aspirational, and it rests on a single inversion.</p>
<p><strong>A specification does not enumerate what shouldn't happen — that list is infinite. It declares exactly what should happen, which is finite. Everything else is a violation by default.</strong></p>
<p>Every scenario predicts its complete footprint: the response, and the exact effects across every configured service. Then execution runs, effects are captured, and the two are diffed. Three verdicts:</p>
<ul>
<li><p><strong>Unpredicted</strong> — something happened that the spec never declared. Violation.</p>
</li>
<li><p><strong>Missing</strong> — the spec declared it and it didn't happen. Violation.</p>
</li>
<li><p><strong>Schema mismatch</strong> — the right event, in the wrong shape. Violation.</p>
</li>
</ul>
<p>A rejection scenario predicts an error response <em>and zero effects across every service</em>. Which means a command that correctly refuses but still writes something is caught by construction. Not by a test somebody remembered to write — by the shape of the language. Every configured service must appear in every prediction; omitting one is a validation error, and explicit zero is a thing you write down.</p>
<p>The inversion runs at three levels. At <strong>run time</strong>, captured effects are diffed against predictions. At <strong>build time</strong>, the files actually touched are diffed against the paths the spec declared it would touch. At <strong>CI time</strong>, the committed test files are regenerated and byte-compared — any drift at all, whether a spec was edited without regenerating or a generated file was hand-edited, fails the build. Every CI run files a digest-anchored evidence bundle with per-scenario violation rows at spec coordinates.</p>
<p>And one law I'd tattoo on the pipeline: <strong>a lane that ran and filed nothing is a red gate, not a green run.</strong> Absence of evidence is treated as failure, because the most expensive class of defect I've ever shipped was a guard that had quietly stopped guarding anything while continuing to report success.</p>
<h3>What this actually catches</h3>
<p>Abstractions are easy to nod along with, so here is what the inversion has actually found in my own work — cases where the specification caught something neither I nor the agent knew was there.</p>
<p><strong>An effect that was absent rather than wrong.</strong> A messaging policy recorded its outbound send into module-level state, but the handler and the adapter were importing that module by different specifiers — so the policy was writing into an array nothing read. The prediction inversion caught it immediately as a <em>missing</em> record. Note what makes this valuable: the effect wasn't incorrect, it was simply not there. In a conventional test suite that reads as a feature nobody built yet. In an inverted one it's a hard failure at a precise coordinate. The same run also caught the policy baking its own pre-rendered copy into a refusal notice, which put that wording out of the renderer's reach and out of translation entirely.</p>
<p><strong>A closed schema silently dropping a fact.</strong> A send directive gained a new field, and the contract's closed schema quietly stripped it in transit. The value never arrived, no error was raised, and the message rendered without it. That defect is invisible to any system where schemas are advisory. It's a build failure where the reference space is closed.</p>
<p><strong>A refusal a spec claimed but never declared.</strong> An audit run refused a spec because a predicted rejection had no matching rule in the enforce block — the law had been written with the wrong verb, so the audit couldn't read it. The scenarios were correct and ran green locally. The spec was asserting an enforcement it had never actually declared, and the two-way lint between rejections and rules is the only thing that could see it.</p>
<p><strong>An event nobody emitted and a precondition with no law.</strong> The traceability sweep caught a projection spec consuming an event that no scenario anywhere ever produced, alongside a command precondition that cited no law. Both are the kind of quiet structural hole that ships happily and fails months later. The sweep, incidentally, caught its own author twice during the week it was being written.</p>
<p><strong>And the one that justifies the entire approach.</strong> In a delivery product built on the same stack, a milestone that no contract had ever purchased could be demonstrated, accepted, and then invoiced — a money path that began with a name typed into a box. Closing that hole immediately turned eight scenarios red across three other specifications, because every one of them had been demonstrating milestones no instrument ever named.</p>
<p>Sit with that one. Those eight scenarios had been passing for weeks, and they had been passing <em>because</em> the hole existed. The specs encoded exactly the same wrong assumption as the code, so neither could catch the other. Nothing in a conventional test suite finds that, ever — the tests agree with the implementation, which is precisely what tests are usually for. It surfaced because a rule was tightened at the specification layer and the whole plane was forced to re-prove itself against the stricter rule.</p>
<p>That is what a specification layer buys that a test suite cannot: not confirmation that the code does what the code does, but a place to change your mind about what should be true and immediately learn everywhere that belief was already wrong.</p>
<hr />
<h2>The part that surprises people</h2>
<p>There is no AI inference anywhere in this approach.</p>
<p>Not in the palette, which is a grammar. Not in Moment, which is a deterministic transformation pipeline with no model in it. Not in test generation, which is a compiler step. Not in verification, which is a diff. The specifications are authored and ratified. The decisions are documented, with causal chains linking each one to what caused it and explicit supersessions when a ruling replaces an earlier one. Nothing important in this system is arrived at by asking a model what it thinks.</p>
<p>We don't infer to create.</p>
<p>We infer in <em>response</em> — and almost always in response to a failure in the process. When something breaks, that's when the model earns its keep: reading the evidence, walking the causal chain, proposing where the fault lives. But by the time we get there, we already have the violation, the coordinate, the spec that predicted it, and the decision record that explains why the rule exists. The inference isn't generating truth from nothing. It's interpreting a body of evidence that already isolates the answer to a handful of possibilities.</p>
<p>That's the inversion of how most people are using these tools. The common pattern is to infer the design and verify by eye. Mine is to specify the design and infer only when the verification screams.</p>
<hr />
<h2>And it still fails</h2>
<p>All of the above, and my system still produces defects that reach production. Convergence doesn't make you omniscient. Specs encode what you thought of. Gates catch the classes you've already learned to catch.</p>
<p>The hardest failure mode is one no toolchain detects: a spec that declares the wrong behavior, an implementation that matches it perfectly, and every test passing. Tests green, software wrong. The only defenses are that predictions are reviewable data sitting in one file, that every rejection must be justified in prose a human reads, and that changes to predictions surface explicitly at review. Review the contract, not just the code.</p>
<p>So the difference isn't that things stop breaking. It's what happens in the twenty minutes after they do.</p>
<p>When something lands wrong, I'm not standing in front of an opaque box wondering what the AI did. The question is narrow and answerable: <strong>which layer let this through?</strong> The domain model didn't capture the case. The spec didn't translate a branch the model had. The scenario set covered the transitions but missed a state-and-input combination. The prediction was complete but wrong. Or the thing was correct and simply never reachable.</p>
<p>Every one of those is a specific, findable, fixable defect in a specific artifact — and most of them are <strong>mine</strong>. Upstream of the prompt. In the modeling, the specification, or the rigor around the evidence.</p>
<p>That's what governance actually buys. Not the absence of failure — the ability to say precisely where failure entered, fix it at that layer, and encode the lesson so the class doesn't recur. A loop with a diagnosable failure mode gets better every week. A loop with a smell test in it has no failure mode at all, just an unbounded supply of surprise.</p>
<hr />
<h2>Where the critics have a point</h2>
<p>I'd be doing the lazy version of this if I stopped here.</p>
<p><strong>This is expensive.</strong> Everything I described is real infrastructure. A domain-specification language, a compiler, a derivation engine, a spec toolchain, an evidence discipline, a durable record. I built most of it. Telling a solo developer on a deadline that they "just need governance" is not far from telling an exhausted person they should exercise more. True, unhelpful, and a little smug.</p>
<p><strong>Nobody sold them governance.</strong> The pitch is <em>describe it and it appears</em>. The demos are greenfield toys where nothing can be built-but-unreachable, because nothing is deployed and no journey has a fourth branch. The distance between that pitch and a real production system is enormous, and it isn't the user's fault that nobody mentioned it. A great deal of slop frustration is anger correctly aimed at marketing and misfiled as a verdict on capability.</p>
<p><strong>And some slop is just slop.</strong> There's a real volume of low-effort output being pushed into the world by people who don't care about the result. Being annoyed by that is reasonable. I'm annoyed by it too.</p>
<p>So my position isn't "skill issue." It's narrower, and I think harder to argue with:</p>
<p><strong>The slop you're seeing is real, and it is evidence about the loop that produced it — not about the ceiling of the tool.</strong> Generalizing from someone else's ungoverned loop to a claim about what's achievable is the same reasoning error I'd flag in any postmortem. A rule that explains most of the evidence is not a diagnosis.</p>
<p>If you want the tool to stop producing slop, the work is upstream of the prompt. It's in knowing your domain well enough to converge a model of it, converging it well enough to specify it, and specifying it well enough that inference was never required in the first place.</p>
<p>The design you agreed to should be the design that ships. Right now, for most people, there isn't a design — there's a paragraph, and a hope.</p>
<hr />
<h2>About this article</h2>
<p>This article was generated by AI. I'm going to say that plainly, because the alternative is worse and because the entire point collapses if I'm cagey about it.</p>
<p>But understand what that actually involved.</p>
<p>I decided what this was going to argue, and I planned it section by section. I chose which layers led and which followed, and when an early draft opened with the wrong one, I said so and it was restructured. I chose the context it was allowed to draw from, and I pointed it at specific bodies of work rather than letting it improvise. I told it what to cut — the examples that read as blame, the jargon a reader wouldn't know, the passages that had drifted into somebody else's voice.</p>
<p>And the material underneath it exists because I built the infrastructure to record it. The decisions, the supersessions, the failures, the causal chains between them, the measured correction rates across nine products — those are queryable today because I decided a long time ago that they should be, and then did the work to make it so. My agent reviewed my recent work and cited it back to me because I made my recent work reviewable.</p>
<p>That is the whole argument, demonstrated instead of asserted. The output is exactly as good as the governance around it.</p>
<p>The governance is the part that was mine.</p>
]]></content:encoded></item><item><title><![CDATA[Drift Is Not a Bug]]></title><description><![CDATA[It is not a bug in anyone's implementation. It is what the architecture predicts.
Go and look at where the conversation actually is. In the largest spec-driven toolkit on GitHub, the open issues carry]]></description><link>https://listenrightmeow.hashnode.dev/drift-is-not-a-bug</link><guid isPermaLink="true">https://listenrightmeow.hashnode.dev/drift-is-not-a-bug</guid><dc:creator><![CDATA[Mike Dyer]]></dc:creator><pubDate>Sun, 02 Aug 2026 19:46:21 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/69ae573286766ac3a67a2e78/8a18aabe-592c-4c5d-8731-249c9b9866ca.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>It is not a bug in anyone's implementation. It is what the architecture predicts.</p>
<p>Go and look at where the conversation actually is. In the largest spec-driven toolkit on GitHub, the open issues carrying the most engagement are the drift issues. On two separate Hacker News threads about spec-driven development, both north of nine hundred points, the same question surfaces and goes unanswered: <em>how do you address spec drift?</em> Not "is drift real." How do you address it? The premise is conceded. Nobody has the answer.</p>
<p>Meanwhile, Tessl, funded at $125M explicitly to build spec-centric development, moved away from it toward skills governance. I won't characterize their reasoning; I don't have it. But when the best-capitalized attempt at the thesis redirects, that is market information.</p>
<p>I think the category has a structural problem, and I think it is worth naming plainly.</p>
<h2>What the category actually built</h2>
<p>Spec, plan, tasks, code. Every serious entrant runs some version of that pipeline, and it works — it produces better output than prompting cold, and the market's convergence on it is not a mistake. AI-native development is permanent now. Nobody is going back.</p>
<p>But look at what the specification <em>is</em> in these systems. It is markdown. A model reads it, forms an interpretation, and emits code. That is the entire relationship. The spec has no grammar, no type system, no compiler, and — decisively — <strong>no failure mode</strong>. It cannot reject anything. A document that cannot fail to parse cannot enforce anything.</p>
<p>The category built better prompts and called it engineering.</p>
<p>That is a real contribution, and I am not sneering at it. Structured intent beats unstructured intent, reliably. But it means the specification's authority ends at generation. Once code exists, the spec has spent itself. And two things follow that nobody in the category has answered.</p>
<p><strong>The first is that tests are derived from the wrong source.</strong> Tests get written after the code exists — by a person reading the implementation, or increasingly by a model reading the implementation. Either way, the assertion is derived from observed behavior rather than stated intent. So the suite measures what got built. It cannot measure what was asked for, because it was never shown what was asked for. That circle closes green, forever, around a system that may have stopped being the thing you specified several releases ago.</p>
<p><strong>The second is non-determinism.</strong> Where a tool does generate tests from specs, it generates them with a model. Run it twice, get two suites. Which means you cannot ask the only question that matters at release time — <em>do these tests still represent this specification?</em> — because there is no stable answer to compare against. If a model derives your tests, you have relocated the interpretation problem, not removed it.</p>
<p>And this is exactly the moment those two failures get expensive. In an AI-native SDLC, code stops being the artifact humans maintain by hand. The spec becomes the only thing a person authors on purpose. Which makes it load-bearing in a way it has never been before — and the category is shipping it as a document.</p>
<p>We are industrializing the production of code that no human specified, and calling the specification a prompt.</p>
<h2>Drift is a release-time phenomenon</h2>
<p>Here is the part I think is genuinely misdiagnosed.</p>
<p>Drift does not begin when the code is written. At the instant of generation, spec and code agree — that is the one moment they are guaranteed to agree. Drift begins at the first release, and at every release after it, because a released system enters a lifecycle. It gets patched under incident pressure. It gets refactored. It gets a hotfix at 2 am that nobody writes down. It gets a second feature that quietly changes the first one's behavior. At no point in that lifecycle does anything go back and ask whether the system still does what the specification said it would.</p>
<p>The spec was never a contract. It was a prompt with better formatting.</p>
<p>This is why drift cannot be patched. You cannot ship a linter that fixes it or a smarter model that avoids it, because the divergence is not a defect in generation — it accumulates in the space between releases, where the specification has no standing to object. As long as the spec is consumed at generation and discarded, drift is not a failure mode. It is the design.</p>
<p>The fix is not a better document. The fix is giving the specification the authority to refuse.</p>
<h2>What we built instead</h2>
<p>A <code>.feat</code> file has two halves, and the split is the whole idea.</p>
<p>One half is natural language for the agent doing the work — where files go, what to import, the sequence of validation, the constraints it must honor. Freeform. Any directive is valid. <strong>The compiler never reads it.</strong></p>
<p>The other half is strict grammar. For every scenario, you declare the complete observable footprint: what the response is, what lands in the event store, what lands in the projection store, what goes on the broker — and where nothing should land, you declare that too. An empty declaration is an assertion.</p>
<p>That inversion is the mechanism. You are not enumerating the things you thought to check. You are declaring the finite set of everything that should happen, and everything outside that set is a violation by default. Captured but not predicted: violation; predicted but not captured: violation; and captured with the wrong shape: violation.</p>
<p>Four things fall out of that, and they are why I think this is the right shape for teams in the future.</p>
<p><strong>The compiler emits the tests, not a model.</strong> Because the declaration is complete and quantified, there is nothing left to infer. No step definitions. No glue code. No human-authored test file anywhere in the loop — which removes the layer where meaning has always leaked.</p>
<p><strong>Emission is deterministic.</strong> Same spec, byte-identical output. That is not a performance detail; it is what makes <code>feat verify</code> possible. Verification is a byte comparison. If the committed tests no longer match the current spec, the build fails. Not a warning. A failure.</p>
<p><strong>Verification covers infrastructure, not just responses.</strong> Most contract testing stops at the API boundary. Predictions are quantified per configured service, so an unexpected write to a projection store, a stray event on a broker, a silent extra record — these are violations, caught at PR, by a spec that named the surface it cared about.</p>
<p><strong>The prose half is future-proof by construction.</strong> Models will keep improving, and the agent-facing directives will keep getting better at eliciting good implementations. None of that changes your test suite, because the compiler never reads that half. You get to adopt every improvement in the agent layer without your verification surface moving underneath you.</p>
<p>The spec stops being a document you consulted once. It becomes a gate every release has to pass through — which is also what stops it being outlived. A specification that gates delivery cannot quietly become historical.</p>
<h2>The compiler told me no</h2>
<p>Two weeks ago I had a bug in our own dashboard. Invitations sending, emails arriving, memberships never appearing. I traced it, found the seam, and put the claim logic in the <code>me</code> resolver in our account subgraph. Two lines. Obvious.</p>
<p>The compiler refused to build it. That spec is declared <code>type query</code>, and the language forbids a query from predicting a write. <code>QUERY_SIDE_EFFECT</code>.</p>
<p>I was annoyed for about a minute, and then I realized the refusal was correct — and not on a technicality. What I had written was a write hidden inside a read. The kind of thing that passes review, ships, and lives in a codebase for four years until someone puts a cache in front of the read and cannot work out why data stops persisting.</p>
<p>The language caught a design mistake made by the person who designed the language. Enforcement that only ever agrees with its author is not enforcement.</p>
<p>We also settled the obvious objection before shipping — that a machine deriving tests from a spec will derive <em>different</em> tests than a competent engineer would. Working from the specs alone, I hand-derived the suites by mechanically applying the derivation rules, before the generator existed. Twenty-seven cases. Then we built the generator, pointed it at the same specs, and diffed—zero semantic assertion disagreements. One error in the exercise, mine, caught while deriving.</p>
<h2>For teams operating at scale: evidence, not attestation</h2>
<p>There is a second thing that falls out of release-time verification, and for regulated organizations I think it is the more valuable one.</p>
<p>Every verification run produces a record of what your software actually did, in a real environment, at a known commit, under a known CI identity. Sign those records per environment with an asymmetric key, publish the public keys at a well-known URL, chain them with sequence numbers per specification per environment, and write them once to storage that cannot be altered afterward.</p>
<p>That produces two properties that ordinary compliance evidence does not have.</p>
<p><strong>Completeness is provable.</strong> The records are sequenced and chained. A missing record leaves a detectable gap. You cannot quietly drop the run that failed, and more to the point, you can <em>demonstrate</em> that you did not.</p>
<p><strong>Sampling is verifiable.</strong> The sample is derived from the chain digest rather than selected by the party being audited. The organization under examination does not choose which evidence gets examined.</p>
<p>This matters because of what auditors call information produced by the entity. Most of the evidence in an audit is IPE — you produced it, so its reliability has to be tested before it can be relied upon. And most IPE is a CSV export or a dashboard screenshot that somebody could have edited. Chained, signed, independently verifiable records answer that reliability question directly, and a third party can verify them without our involvement at all. That is the point of publishing the keys.</p>
<p>The distinction from compliance automation is clean, and I want to be precise about it. Those platforms attest that your <em>organization</em> operates its controls — onboarding, access reviews, vendor management. This attests that your <em>software behaves as specified</em>. Different claim, different evidence, narrow overlap around change management and system operations. And because verification can run on a schedule against live environments, the evidence describes operation across a period rather than a point in time.</p>
<p>To be explicit: we do not issue opinions, and no software vendor can. Your auditor tests information you produced. We produce it in a form that survives being tested.</p>
<h2>What I am not claiming</h2>
<p>The adapter shelf is thin — first-party adapters for a handful of surfaces, and if your stack sits outside them you will be writing one against a published conformance kit. That is a deliberate bet, not a solved problem.</p>
<p>And this is one team dogfooding its own tool. Twenty-seven cases is a real result and a small number, with no independent benchmark yet. Demonstrated, not proven. Read every figure above with that attached.</p>
<h2>Where it is</h2>
<p>The language and toolchain are open source and free, and the local half is complete—parse, generate, verify, run —in your repo, on your CI, no account, nothing phoning home. There is a hosted ledger for teams that want the history kept and chained per environment.</p>
<p>The free half is not a trial. If the argument above is right, enforcement has to live in your build — and a build gate you have to pay to keep running is not a gate.</p>
]]></content:encoded></item><item><title><![CDATA[Companies Don't Remember in Conversations]]></title><description><![CDATA[In the first post, I described the failure that sent me down this path: an agent confidently re-arguing a decision my team had already reversed. It recalled plenty. What it couldn't tell was what's st]]></description><link>https://listenrightmeow.hashnode.dev/companies-dont-remember-in-conversations</link><guid isPermaLink="true">https://listenrightmeow.hashnode.dev/companies-dont-remember-in-conversations</guid><category><![CDATA[agentic AI]]></category><category><![CDATA[llm]]></category><category><![CDATA[ai agents]]></category><category><![CDATA[AI]]></category><dc:creator><![CDATA[Mike Dyer]]></dc:creator><pubDate>Mon, 27 Jul 2026 16:58:04 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/69ae573286766ac3a67a2e78/81a99b4e-1349-463b-b573-ce166010c1e8.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In the <a href="https://blog.coada.dev/your-agents-don-t-have-a-memory-problem">first post,</a> I described the failure that sent me down this path: an agent confidently re-arguing a decision my team had already reversed. It recalled plenty. What it couldn't tell was what's still true.</p>
<p>Knowing the problem and knowing the fix are different things. This post is about the wrong turns I took first, and the observation that finally reframed the whole build.</p>
<h2>The fixes that don't fix it</h2>
<p>Everyone who meets this problem reaches for the same three fixes. I reached for all of them.</p>
<p><strong>Give the agent everything.</strong> If it can see the whole history, surely it can work out what's current. Except now it holds the original decision <em>and</em> the reversal, side by side, both looking equally authoritative — and it has to notice the conflict and guess the winner. On every task. Forever. Sometimes it guesses right. You don't get to know when.</p>
<p><strong>Give it the summary.</strong> Compress the history to get the gist. This one fails quietly. Summaries preserve conclusions and shed <em>reasons</em> — and the reasons are the part that matters. "We use this vendor" survives summarization. "We chose them over the cheaper option because of what happened during the outage in March" does not. Lose the why, and when conditions change, nothing can tell your agent whether the decision should change too.</p>
<p><strong>Trust the newest thing.</strong> Prefer recent information — surely the latest word is the current truth? Except the most recent mention of a decision is often someone <em>discussing the old one</em>. A retro that quotes the abandoned approach. A doc explaining the new pattern by describing the deprecated one. Newest and current agree just often enough to be dangerous.</p>
<p>Three fixes, one shared assumption: that memory is a pile of things that were said, and the job is to sort the pile better. That assumption turned out to be the actual bug.</p>
<h2>Watch how a company actually remembers.</h2>
<p>The reframe didn't come from a paper. It came from noticing something I'd watched happen a hundred times and never really looked at.</p>
<p>A new engineer joins and asks the oldest question in software: <em>"Why is it built this way?"</em> Nobody answers by handing them meeting transcripts. Nobody says "read every Slack thread from Q1." Someone — usually the most senior person in the room — tells them the <em>story of the decision</em>: here's what we tried, here's what broke, here's what we picked and what we walked back, and here's why the walked-back thing keeps looking tempting to people who weren't there.</p>
<p>That's how companies remember, not in conversations — in <strong>decisions</strong>. The conversations are just the raw material; the institution keeps the verdicts. And the best organizations learned, usually the hard way, to write the verdicts down — decision records, postmortems — because the alternative is that the verdicts live in people, and people leave.</p>
<p>Then look at what we handed the agents: the transcripts: all raw material, no verdicts. Every pile-sorting fix I'd tried was an attempt to make an agent <em>reconstruct</em> the verdicts from the raw material — reconstruct, on the fly, under time pressure, the thing your organization spent months deciding.</p>
<p>That was the discovery, and I wrote it in a note to myself in May — preserved, fittingly, in the system it describes. Roughly: stop handing the agent what was said. Hand it what was decided, what that replaced, and what replaced it.</p>
<p>You can't bolt that on afterward. The connections between decisions — <em>this reversed that, this exists because that failed</em> — were never in the transcripts to begin with. They lived in the heads of the people in the room. If you want memory that knows what still holds, the life of a decision — made, challenged, revised, replaced — has to be recorded <em>as it happens</em>, the way the organization itself would remember it. A decision book, kept current. Not a longer transcript, searched harder.</p>
<h2>The day it proved itself on me.</h2>
<p>One story from June, because a thesis should have to survive its own author.</p>
<p>Weeks after making a significant product decision — removing something, deliberately — I hit a new problem where reversing that call looked attractive. The removed piece would have papered right over it. My in-the-moment reasoning was drifting toward "just put it back."</p>
<p>Old me would have put it back. Momentum plus a plausible reason is how most bad reversals happen — in people and in agents alike.</p>
<p>But by now I was testing this thesis on the thing I was building. So I asked it: why did we make this call? It gave me back the decision — the reasons, what we'd observed, what the removal was protecting. Every reason still held. Nothing about the new problem touched them. I solved the new problem another way, and the decision stood.</p>
<p>That's the whole thesis in one moment: the system didn't remember <em>more</em> than me. It remembered <em>better</em> than me — it knew the decision's standing when my own judgment was about to slip. Now give that slip machine speed and twenty agents, and you have the risk from Post 1. Give the <em>save</em> machine speed instead, and you have the reason I kept building.</p>
<h2>What this changes</h2>
<p>The discovery, compressed: what was said is a transcript. What still holds is a ledger. Transcripts only ever get longer; a ledger gets reconciled — and reconciliation, not recall, is what keeps agents from confidently rebuilding your past.</p>
<p>Next post: what all of this does to your budget — because the transcript model doesn't just fail on correctness, it fails on economics, and the token market is making that failure more expensive every quarter.</p>
<hr />
<p><em>The product that came out of these six months is</em> <a href="https://flmnt.ai">flmnt</a> <em>— a memory layer for AI agents built around decision currency: what's still true, not just what was said. It's in private beta now. Code</em> <em><strong>BLOG30</strong></em> <em>gets you a free month.</em></p>
]]></content:encoded></item><item><title><![CDATA[Your Agents Don't Have a Memory Problem]]></title><description><![CDATA[The moment I started paying attention wasn't dramatic. No outage, no incident review. An agent I was working with — one of several I run in parallel, every day — suggested an approach we had abandoned]]></description><link>https://listenrightmeow.hashnode.dev/your-agents-don-t-have-a-memory-problem</link><guid isPermaLink="true">https://listenrightmeow.hashnode.dev/your-agents-don-t-have-a-memory-problem</guid><category><![CDATA[llm]]></category><category><![CDATA[agentic AI]]></category><category><![CDATA[ai agents]]></category><category><![CDATA[AI]]></category><dc:creator><![CDATA[Mike Dyer]]></dc:creator><pubDate>Thu, 16 Jul 2026 05:55:29 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/69ae573286766ac3a67a2e78/51e3948d-96a3-4b6c-8858-43f108fb5624.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The moment I started paying attention wasn't dramatic. No outage, no incident review. An agent I was working with — one of several I run in parallel, every day — suggested an approach we had abandoned weeks earlier. And it argued for it well. Confidently. With reasons.</p>
<p>The reasons were good. They were just old.</p>
<p>We had already tried that path. We'd found the problem with it, made a call, and moved on. The agent didn't know any of that, because the record of <em>why we moved on</em> lived in a session that had long since evaporated. So it did what any capable reasoner does with incomplete history: it re-derived the wrong answer, beautifully.</p>
<p>I lead product and engineering, and I build with AI agents daily. That moment bothered me more than it probably should have — not because the agent was wrong, but because of <em>how</em> it was wrong. It wasn't a hallucination. It wasn't forgetting. It was a confident, well-reasoned recommendation of a decision my own team had already reversed.</p>
<p>That's a different failure mode than the one the industry is racing to fix. And I couldn't stop pulling on the thread.</p>
<h2>Where everyone is looking</h2>
<p>If you follow the agent-memory space — and if you're responsible for an engineering org adopting agents, you should — the energy is overwhelmingly pointed in one direction: <strong>remember more</strong>. Bigger context windows. Better retrieval. Higher recall scores. Every benchmark, every launch post, every roadmap converges on the same implicit definition of memory: <em>the ability to bring back what was said</em>.</p>
<p>It's a reasonable definition. It's also the wrong one, and the gap between the two is where the real risk lives.</p>
<p>Here's the thing recall can't tell you: <strong>whether what it retrieved is still true.</strong></p>
<p>A perfect recall system retrieves your March architecture decision flawlessly. It also retrieves the April decision that reversed it flawlessly. What it cannot do is tell the agent which one is <em>current</em> — because currency isn't a property of the text. Nothing in the March decision says "I've been replaced." It sits in memory looking exactly as authoritative as the day it was written.</p>
<p>Humans handle this without noticing. Ask a senior engineer about the March decision and they don't just recall it — they wince and say, "yeah, we walked that back in April, here's why." That wince is institutional memory. Not the file server. Not the wiki. The living knowledge of <em>what still holds</em>.</p>
<p>We have spent decades learning that companies lose that knowledge when the senior people leave. Now we're deploying agents that never had it in the first place.</p>
<h2>Machine speed changes the stakes</h2>
<p>A human who missed the April reversal propagates the stale decision at human speed — a comment in a meeting, a paragraph in a doc. Annoying. Correctable. Someone catches it.</p>
<p>An agent propagates it at machine speed. It writes the code. It updates the docs. It opens the tickets. And if you're running agents the way organizations are starting to — several in parallel, handing work to each other — the stale decision doesn't just move fast. It <em>compounds</em>. One agent's confident wrong answer becomes the next agent's trusted input.</p>
<p>This is the part I'd put in front of every engineering leader evaluating agent adoption: the risk profile of AI agents isn't primarily that they make things up. Modern models are better at that than their reputation suggests. The risk is that they <strong>inherit and amplify whatever your organization believes</strong> — including the parts your organization has already stopped believing, without leaving a machine-readable trace.</p>
<p>Your decisions have a lifecycle. Made, revised, superseded, sometimes reversed outright. Your memory systems treat them as immortal.</p>
<h2>Six months down the thread</h2>
<p>I went looking for the system that solved this and didn't find one. What I found instead was a market competing hard on a question — <em>how much can you recall?</em> — that quietly assumes the harder question is already answered. It isn't. <strong>What's still true?</strong> is a different problem, with different architecture underneath it, and almost nobody was building for it.</p>
<p>So I did what product and engineering people do with a problem they can't put down: I spent the next six months building, measuring, being wrong, correcting, and building again. Some of what I learned surprised me. Some of it contradicted things I'd said out loud with confidence — and there's a post coming in this series about exactly that, because the way we caught our own errors turned out to be the most convincing evidence for the whole thesis.</p>
<p>This series is that six months, written for the people who own the decision I now think every engineering org will face: not <em>whether</em> your agents get memory, but <em>what kind</em> — and whether that memory knows the difference between what was said and what still holds.</p>
<p>Next up: the reframe everything else followed from — why recall and currency are different problems, and why solving the second one changed how I think about the first.</p>
<hr />
<p><em>The product that came out of those six months is</em> <a href="https://flmnt.ai"><em>flmnt</em></a> <em>— a memory layer for AI agents built around decision currency: what's still true, not just what was said. It's in private beta now. If this problem sounds like your problem, use code</em> <em><strong>BLOG30</strong></em> <em>for a free month and go see whether your agents are re-deriving old answers too.</em></p>
]]></content:encoded></item><item><title><![CDATA[Steal this blog]]></title><description><![CDATA[This blog — the one you're reading right now — took two hours to build. Not two days. Not two sprints. Two hours, a conversation with an AI, and a spec.
No hand-written CSS. No framework. No template ]]></description><link>https://listenrightmeow.hashnode.dev/steal-this-blog</link><guid isPermaLink="true">https://listenrightmeow.hashnode.dev/steal-this-blog</guid><category><![CDATA[Astro]]></category><category><![CDATA[gen ai]]></category><category><![CDATA[Vercel]]></category><dc:creator><![CDATA[Mike Dyer]]></dc:creator><pubDate>Fri, 01 May 2026 01:23:16 GMT</pubDate><content:encoded><![CDATA[<p>This blog — the one you're reading right now — took two hours to build. Not two days. Not two sprints. Two hours, a conversation with an AI, and a spec.</p>
<p>No hand-written CSS. No framework. No template I found on GitHub and spent the weekend customizing. I wrote a specification, handed it to Claude, and deployed the result to Vercel. Lighthouse came back 100/100/100/100.</p>
<p>I'm going to show you exactly how I did it so you can do the same thing tonight. Not a version of it. This. The same stack, the same workflow, the same architecture. Steal it. I want you to.</p>
<h2>The Setup: What You Need Before You Start</h2>
<p>Here's what I walked in with:</p>
<ul>
<li><p>A <a href="https://hashnode.com">Hashnode</a> account with some blog posts in it. If you don't have posts yet, write two or three. You need something for the site to pull.</p>
</li>
<li><p>A <a href="https://claude.ai">Claude</a> account. The free tier works for the spec-writing phase. <a href="https://claude.ai/design">Claude Design</a> — which handled the structured design and code handoff — is available on Pro, Max, Team, and Enterprise plans.</p>
</li>
<li><p>A <a href="https://vercel.com">Vercel</a> account (free tier is fine).</p>
</li>
<li><p>A domain or subdomain you want to deploy to. Mine is <code>blog.coada.dev</code>. Yours can be whatever you want.</p>
</li>
</ul>
<p>That's it. No local dev environment required to start. No dependencies to install upfront. You begin by writing, not coding.</p>
<h2>Step 1: Write the Spec, Not the Code</h2>
<p>This is the part that feels counterintuitive. You're going to spend the first hour not building anything.</p>
<p>Open Claude and start describing what you want. Not "build me a blog" — that's too vague to produce anything worth deploying. Describe the constraints:</p>
<ul>
<li><p>What should it look like? I wanted something minimal and typography-first, inspired by <a href="https://mitchellh.com/writing">mitchellh.com</a>. You might want something different. Describe it.</p>
</li>
<li><p>Where does the content come from? Hashnode, in headless mode. You write there, the blog pulls from there at build time via GraphQL.</p>
</li>
<li><p>Where does it deploy? Pick your platform. I used Vercel.</p>
</li>
<li><p>What features do you need? I wanted a newsletter subscribe form, series support for multi-part posts, and an RSS feed. You might want more. You might want less. Say so.</p>
</li>
</ul>
<p>Here's what's going to happen: Claude is going to push back on you. When I said I wanted to consume Hashnode's raw markdown, Claude pointed out that Hashnode's custom embed syntax (<code>&lt;a class="embed-card" href="url"&gt;url&lt;/a&gt;</code> for YouTube, Twitter, GitHub) would silently break in a standard remark pipeline. We documented the trade-off and chose to consume the pre-rendered HTML instead. That kind of decision is exactly what a spec is for — you make it once, clearly, before you're debugging a build error at midnight.</p>
<p>By the end of this step, you should have a document that covers your design requirements, your tech stack, your GraphQL queries, your project structure, your environment variables, and your deployment instructions. Mine ended up with eight Architecture Decision Records across five revisions. Yours might be simpler. That's fine. The point is that every decision is made before you write a line of code.</p>
<h2>Step 2: From Spec to Design to Code</h2>
<p>This is where <a href="https://claude.ai/design">Claude Design</a> changed my workflow.</p>
<p>My old process used Replit for design. I'd prototype the UI there, export to a repository, and then bring in Claude Code to optimize and rebuild the codebase — because what Replit exported was never production-ready. That handoff was lossy. Claude Code had no idea what I'd been doing in Replit. It had to reverse-engineer my intent from exported code, figure out what I actually wanted, and rebuild accordingly. That loop typically took a day or two from start to finish.</p>
<p>Claude Design replaced Replit entirely. The spec I wrote in Claude Chat carried forward into Design, and Design's output carried forward into Code — same ecosystem, same context, zero re-explanation. There's no export step, no "here's a codebase I prototyped somewhere else, please figure out what I meant." Claude Code already knew the project because it came from the same conversation. The code it produced didn't need an optimization pass because the decisions were already made in the spec, not improvised during prototyping.</p>
<p>That's how two days became two hours. Not because the AI is faster at typing. Because the handoff disappeared.</p>
<p>Take your spec and push it into Claude Design. Design takes the conversational document and gives it structure — the project scaffold, the component hierarchy, the styling approach. Then hand it off to Claude Code.</p>
<p>Claude Code will build it. Here's what mine produced:</p>
<ul>
<li><p>An Astro static site with dynamic routes for posts and series.</p>
</li>
<li><p>A GraphQL client that queries Hashnode's API at build time, paginating through all published posts.</p>
</li>
<li><p>Series landing pages with prev/next navigation in the author-defined sort order.</p>
</li>
<li><p>A single Vercel serverless function (<code>/api/subscribe</code>) that proxies newsletter subscriptions to Hashnode's API, keeping the access token server-side.</p>
</li>
<li><p>An RSS feed with full post HTML.</p>
</li>
<li><p>Vanilla CSS in one file. Zero client-side JavaScript except fifteen lines for the subscribe form.</p>
</li>
</ul>
<p>Five npm dependencies total. The whole thing.</p>
<h2>Step 3: Deploy and Wire It Up</h2>
<p>This part takes fifteen minutes.</p>
<p><strong>Vercel:</strong> Import your repo. Vercel auto-detects Astro and pre-fills the build config. Add your environment variables — your Hashnode publication host and access token, your site URL. Deploy.</p>
<p><strong>DNS:</strong> Point your subdomain at Vercel. One CNAME record. Vercel provisions SSL automatically.</p>
<p><strong>Webhook:</strong> Hashnode's built-in webhooks are unreliable at the time of writing, so here's the workaround I use. Connect your Hashnode blog to GitHub — Hashnode can back up every post as a commit to your repo. Then in your GitHub repo settings, add a webhook that fires on push events to your Vercel deploy hook URL (create one in Vercel under Settings → Git → Deploy Hooks). The flow becomes: publish on Hashnode → Hashnode commits to GitHub → GitHub webhook fires → Vercel rebuilds. One extra hop, but it's rock solid.</p>
<p><strong>Headless mode:</strong> In Hashnode, go to your blog dashboard → Domain → Headless mode. Enter your blog's URL. Save. Hashnode stops rendering its own frontend and redirects readers to yours.</p>
<p>That's it. You're live.</p>
<h2>Step 4: Publish Something and Watch It Work</h2>
<p>Write a post on Hashnode. Hit publish. Hashnode commits to GitHub, the GitHub webhook fires to Vercel, and your new post appears on your custom blog within a minute or two. You didn't deploy anything. You didn't run a build command. You published on Hashnode and the rest happened automatically.</p>
<p>This is the part that makes headless mode worth it. You get the authoring experience of a polished CMS — rich editor, drafts, scheduling, series, newsletter management — with a frontend that's entirely yours. Best of both worlds.</p>
<h2>Why This Worked in Two Hours</h2>
<p>I want to be honest about the secret here, because it's not the tools. The tools are great — Claude Design genuinely replaced an entire step in my process. I used to design in Replit, export, then spend a day or two having Claude Code reverse-engineer and rebuild what I'd prototyped. Now the design and the code share the same context from the start. But the real reason this took two hours instead of two days is that I didn't start coding.</p>
<p>The spec forced every decision upfront. Vanilla CSS or Tailwind? Decided in the spec (vanilla — the site's styles fit in one file, a framework would add build tooling for no reason). Hashnode HTML or markdown? Decided in the spec (HTML — embed support would break otherwise). Server-side subscribe endpoint or client-side API call? Decided in the spec (server-side — the access token can't be in the browser).</p>
<p>When Claude Code picked up the spec, there were no ambiguities left. No "what should this look like?" No "where does this deploy?" No "how does the newsletter work?" All of that was answered. Implementation was execution.</p>
<p>If I'd started by writing code, I would have made those decisions under pressure — in the middle of a build error, or staring at a blank CSS file, or realizing at deploy time that my API key was in the client bundle. That's how a two-hour project becomes a weekend project.</p>
<h2>The Lighthouse Scores (and Why They're Free)</h2>
<p>100/100/100/100. Performance, Accessibility, Best Practices, SEO. No optimization passes. No performance tuning. No "let me run Lighthouse one more time and fix the orange ones."</p>
<p>This isn't a flex. It's a natural consequence of not shipping things you don't need. Astro outputs static HTML with zero JavaScript by default. Vanilla CSS is a single small file with no framework overhead. Images come from Hashnode's CDN, which already handles optimization. There's nothing to render-block on because there's nothing to render client-side.</p>
<p>The architecture decisions that produced these scores weren't performance decisions. They were simplicity decisions. The performance came free.</p>
<h2>Now Steal It</h2>
<p>I built this blog in two hours. You can do it faster because I've already made the decisions for you.</p>
<p>The entire specification — every design requirement, every architecture decision, every GraphQL query, every deployment instruction — is public. You can read it, fork it, and use it as the foundation for your own blog tonight:</p>
<p><a href="https://github.com/coada-dev/blog/blob/main/PROJECT_SPEC.md"><strong>github.com/coada-dev/blog/blob/main/PROJECT_SPEC.md</strong></a></p>
<p>Or, if you want Claude to build from the spec with full context, paste the URL of this blog post into a new Claude conversation and tell it to build you a blog based on what I described. It'll pick up the architecture, the stack, the design philosophy, and the deployment target. You just fill in your own Hashnode publication and domain name.</p>
<p>Here's the path:</p>
<ol>
<li><p><strong>Start a conversation at</strong> <a href="https://claude.ai"><strong>claude.ai</strong></a><strong>.</strong> The free tier works for spec authoring. Paste this post's URL or the spec link above and say "I want to build this." Claude will have full context on the architecture and can help you customize it for your setup.</p>
</li>
<li><p><strong>Push into</strong> <a href="https://claude.ai/design"><strong>Claude Design</strong></a><strong>.</strong> This is where the spec becomes a real project — structured, scaffolded, and ready for implementation. Design is available on Pro, Max, Team, and Enterprise plans. The handoff to Claude Code carries all the context forward. No re-explaining, no re-uploading, no lost context.</p>
</li>
<li><p><strong>Let Claude Code build it.</strong> The spec is detailed enough that implementation is execution. You'll have a deployable Astro project in minutes.</p>
</li>
<li><p><strong>Deploy to Vercel.</strong> Import the repo, add three environment variables, deploy. Connect Hashnode to GitHub, add a GitHub webhook to trigger Vercel rebuilds. Enable headless mode. You're live.</p>
</li>
</ol>
<h2>Here's What You Get</h2>
<p>I want to be specific about what "live" means here, because the value proposition goes deeper than a pretty blog.</p>
<p><strong>A global CDN for free.</strong> Vercel serves your static site from edge nodes worldwide. Your blog loads fast in São Paulo, Singapore, and San Francisco. You didn't configure this. You didn't pay for this. It's included.</p>
<p><strong>A complete authoring pipeline without writing a single line of code.</strong> Hashnode gives you a rich editor, draft management, scheduled publishing, series for multi-part content, and subscriber management. You write there. Your blog pulls from there. Publish a post and it flows through GitHub to Vercel — your site rebuilds automatically. You never touch a terminal to publish a post.</p>
<p><strong>Newsletter management through Kit.</strong> Your subscriber list, email campaigns, automation sequences, and analytics — all managed through Kit's purpose-built platform. The subscribe form on your blog feeds directly into this pipeline.</p>
<p><strong>Analytics and performance monitoring built in.</strong> Vercel Web Analytics gives you privacy-friendly visitor tracking — pageviews, unique visitors, top pages, referrers, device breakdown — without cookies, without consent banners, and without a third-party script. Vercel Speed Insights monitors your real-user Core Web Vitals (LCP, INP, CLS) from actual visitor devices, not synthetic lab tests. Both are included on the free tier. You enable them with a toggle, not a configuration file.</p>
<p><strong>A first-class web experience — 100/100/100/100 on Lighthouse.</strong> Performance, Accessibility, Best Practices, SEO — all perfect. Not after three optimization passes. Out of the box. Claude Design produced code that scores at the ceiling because the architecture decisions in the spec — static HTML, vanilla CSS, zero client-side JavaScript — don't leave room for performance problems. There's nothing to optimize when you don't ship anything unnecessary.</p>
<p><strong>And the cost of all of this:</strong></p>
<table>
<thead>
<tr>
<th>What</th>
<th>Cost</th>
</tr>
</thead>
<tbody><tr>
<td>Hashnode (CMS, authoring, series, scheduling)</td>
<td>Free</td>
</tr>
<tr>
<td>Vercel (hosting, CDN, analytics, speed insights, SSL)</td>
<td>Free</td>
</tr>
<tr>
<td>Kit (newsletter management)</td>
<td>Free tier available</td>
</tr>
<tr>
<td>Claude (spec authoring on free tier)</td>
<td>Free</td>
</tr>
<tr>
<td>Claude Design → Code (full pipeline)</td>
<td>$20/month Pro plan</td>
</tr>
</tbody></table>
<p>You're looking at a professional blog with a global CDN, automated publishing pipeline, newsletter management, analytics, performance monitoring, and a perfect Lighthouse score — for \(20/month or less. And most of that \)20 is paying for Claude, which you're probably already using for other things.</p>
<p>This isn't a side project. This is a production content platform that happens to be run by one person with no ops burden.</p>
<p>Go steal it. I mean it.</p>
]]></content:encoded></item><item><title><![CDATA[The Jobs Aren't Back. They're Different Jobs.]]></title><description><![CDATA[The headlines are warm again.
Software engineering roles have doubled since mid-2023. Sixty-seven thousand open positions. Listings up 11% year-over-year. Three openings for every qualified candidate.]]></description><link>https://listenrightmeow.hashnode.dev/the-jobs-aren-t-back-they-re-different-jobs</link><guid isPermaLink="true">https://listenrightmeow.hashnode.dev/the-jobs-aren-t-back-they-re-different-jobs</guid><category><![CDATA[Software Engineering]]></category><category><![CDATA[AI]]></category><category><![CDATA[Career]]></category><category><![CDATA[software architecture]]></category><dc:creator><![CDATA[Mike Dyer]]></dc:creator><pubDate>Thu, 09 Apr 2026 23:27:50 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/69ae573286766ac3a67a2e78/3b1fcbd8-9d46-49c9-bb4a-cb7d0cfd7ea3.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The headlines are warm again.</p>
<p>Software engineering roles have doubled since mid-2023. Sixty-seven thousand open positions. Listings up 11% year-over-year. Three openings for every qualified candidate. Salary growth averaging 4.2%.</p>
<p>If you're reading those numbers from a hiring desk, the crisis is over. If you're reading them from a couch where you've been sitting for fourteen months sending applications into a void, the crisis hasn't even started being honest with you yet.</p>
<hr />
<h2>The Counting Problem</h2>
<p>The Bureau of Labor Statistics counts an opening. It doesn't ask whether the person who just lost their job at that same company can fill it.</p>
<p>Indeed counts a listing. It doesn't track how many of those listings sit open for nine months because the company would rather leave a seat empty than hire someone who needs to be retrained.</p>
<p>When a company says "we have three openings for every qualified candidate," that's not a labor shortage. That's a skills mismatch being reported as a market recovery.</p>
<p>Here's how the math actually works. A Fortune 500 cuts 400 mid-level engineers in January. Posts 200 "AI-native platform engineer" roles in March. The net looks like growth. The headlines report recovery. And 400 people who built production systems for a decade are still unemployed, watching job boards full of openings they don't qualify for.</p>
<p>That's not recovery. That's replacement.</p>
<hr />
<h2>What Actually Changed in Three Years</h2>
<p>The job that got eliminated didn't get reinstated. It got restructured into something the person who held it might not recognize.</p>
<p>Three years ago, the market valued the engineer who could write React components and ship features on a two-week sprint cadence. That job is compressed now. AI handles the output layer. What's left is the judgment layer — system design, domain modeling, integration architecture, failure mode analysis, trade-off reasoning under ambiguity.</p>
<p>The people who thrived writing code are not automatically the people who thrive deciding what code should exist.</p>
<p>That's not a semantic distinction. It's a career-ending one for a lot of people who were very good at their jobs.</p>
<hr />
<h2>The Cross-Disciplinary Divide</h2>
<p>Watch who's actually landing in the new market. It's not the deepest specialists. It's the widest thinkers.</p>
<p>The developer who also understood product. The backend engineer who also did infrastructure. The architect who'd sat in sales calls and heard what the customer actually needed versus what the ticket said.</p>
<p>They didn't just learn to code. They learned to think across boundaries. And now, paired with AI, those people are producing output that would have required a team of six three years ago.</p>
<p>Meanwhile, the engineer who spent a decade getting exceptionally good at one framework, one layer of the stack, one narrow slice of the pipeline — that expertise was valuable when the floor existed.</p>
<p>The floor is gone.</p>
<hr />
<h2>The Retraining Lie</h2>
<p>Every time a market shifts like this, the same reassurance shows up: "People just need to reskill." It sounds reasonable. It's almost never true at the pace the market demands.</p>
<p>Architecture isn't a certification. Lateral thinking isn't a bootcamp. The judgment that makes someone valuable in the current market was built over years of cross-disciplinary work — product decisions, infrastructure trade-offs, customer conversations, failed projects, recovered projects.</p>
<p>You can't speedrun that.</p>
<p>A six-month AI engineering course doesn't give you the instinct to know when an architecture will collapse under scale. Watching a tutorial on system design doesn't teach you how to make trade-offs between consistency and availability while a product manager is staring at you waiting for a yes. Those are skills you earn by being wrong enough times to recognize the shape of the next mistake before you make it.</p>
<p>The people telling displaced engineers to "just learn AI" are offering the same advice as telling a taxi driver to "just become a software engineer" in 2014. Technically possible. Statistically rare. And the people giving the advice are never the ones who have to take it.</p>
<hr />
<h2>Who the Numbers Are Actually Counting</h2>
<p>Let's be precise about what "67,000 open positions" means.</p>
<p>It means companies are building AI products and need engineers to build them. It means the demand is real. None of that is in dispute.</p>
<p>What's in dispute is who those jobs are for.</p>
<p>They're for engineers who already think in systems. Who already model domains. Who already design for failure. Who already understand distributed architecture, event-driven patterns, and the difference between structural correctness and semantic correctness.</p>
<p>They're not for the mid-level developer who shipped features reliably for eight years and suddenly finds that "reliable feature shipping" is exactly the job AI compresses first.</p>
<p>The cruelty of this market is that the people it displaced are often genuinely good engineers. They didn't fail. The definition of the job changed underneath them. And the new definition requires a fundamentally different kind of thinking that takes years to develop — years they may not have, because the market moved in months.</p>
<hr />
<h2>The Uncomfortable Part</h2>
<p>When someone says "AI won't replace developers, it'll just change what developers do" — that's a comforting lie dressed up as optimism.</p>
<p>AI is already replacing developers. Entire categories of engineering output that required human labor eighteen months ago are now handled by tools that didn't exist then. The companies doing the replacing are the same ones posting those 67,000 job openings. They're not contradicting themselves. They're replacing one kind of engineer with a different kind.</p>
<p>The honest framing isn't "the jobs are back." It's: "new jobs exist, and they require capabilities that a large portion of the displaced workforce doesn't have."</p>
<p>That's not a recovery. That's a restructuring. And a restructuring without an honest accounting of who's left behind isn't optimism. It's negligence.</p>
<hr />
<h2>What the Numbers Won't Tell You</h2>
<p>They won't tell you about the senior engineer with fifteen years of experience who's been interviewing for eleven months and keeps getting rejected for roles that didn't exist when they were laid off.</p>
<p>They won't tell you about the teams that post openings they never intend to fill — headcount budget theater for the board while the actual work gets absorbed by three people and an AI tool.</p>
<p>They won't tell you about the companies lowballing experienced engineers 50 to 70 percent below market rate, because a flooded market means someone desperate enough will take it.</p>
<p>They won't tell you that "three openings for every qualified candidate" is an indictment, not a celebration. It means the market broke the pipeline. It spent three years laying off the mid-career engineers who would have been next in line, and now it's shocked that there's nobody to fill the senior roles.</p>
<p>You don't get to gut the middle of the ladder and then complain about a talent shortage at the top.</p>
<hr />
<h2>The Real Question</h2>
<p>The real question isn't whether engineering jobs are "back." It's whether the industry is willing to be honest about what it did.</p>
<p>It overhired during a bubble. It cut aggressively to protect stock prices. It blamed AI for decisions that were really about margins. And now it's celebrating "recovery" while a generation of mid-career engineers — people who built the systems these companies run on — sit on the other side of a skills gap that didn't exist three years ago.</p>
<p>The jobs aren't back. Different jobs showed up. And the people who lost the old ones are being told the market is fine while staring at a wall of postings they can't qualify for.</p>
<p>That's not recovery. That's denial with a press release.</p>
<hr />
<p><em>Stop counting openings. Start counting placements. The gap between those two numbers is the actual state of the engineering market — and nobody wants to talk about it.</em></p>
]]></content:encoded></item><item><title><![CDATA[I Stopped Correcting 40% of My AI's Work.]]></title><description><![CDATA[Here's What Changed.
I run product and engineering for regulated healthcare software. Over the past year, I've shipped three products using AI as a core member of the delivery team — not for code sugg]]></description><link>https://listenrightmeow.hashnode.dev/i-stopped-correcting-40-of-my-ai-s-work</link><guid isPermaLink="true">https://listenrightmeow.hashnode.dev/i-stopped-correcting-40-of-my-ai-s-work</guid><dc:creator><![CDATA[Mike Dyer]]></dc:creator><pubDate>Wed, 08 Apr 2026 07:40:08 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/69ae573286766ac3a67a2e78/0df4bdf7-85b1-433c-830f-e0bf7eb16de6.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Here's What Changed.</p>
<p>I run product and engineering for regulated healthcare software. Over the past year, I've shipped three products using AI as a core member of the delivery team — not for code suggestions or autocomplete, but for full execution: architecture, domain modeling, compliance documentation, sprint planning, pull requests.</p>
<p>Early on, I was correcting roughly 40% of what the AI delivered. Not hallucinations or obvious errors — structural misalignment. The code was clean, the documents were well-written, and the logic was sound. But 4 out of 10 deliverables didn't match the intent. A story would be implemented against the wrong architectural assumption. A document would reference a pattern we'd discussed but never decided on. A pull request would make a reasonable judgment call that happened to be the wrong one.</p>
<p>That 40% correction rate was consistent across projects, across domains, across complexity levels. It wasn't a capability problem. It was a structural one.</p>
<p>Today, across three shipped products, that number is roughly 5%.</p>
<p>The difference isn't a better model. It's a methodology.</p>
<hr />
<h2>The Problem Isn't Intelligence. It's Ambiguity.</h2>
<p>When you hand an AI a story that says "implement the patient authorization flow," the AI will deliver something. It will make decisions about state management, error handling, API boundaries, security constraints, and data persistence. It will make those decisions confidently, because that's what large language models do — they produce plausible outputs.</p>
<p>The problem is that "plausible" and "correct" diverge exactly at the decision points that matter most. The AI doesn't know that your team decided last month to isolate token storage in a dedicated service. It doesn't know that your compliance framework requires audit events at the command level, not the API level. It doesn't know that the domain expert deferred the acuity algorithm to a later phase, so the placeholder invariant in the spec is intentional, not an oversight.</p>
<p>Every ambiguous decision point in a ticket is a coin flip. The AI will resolve it, but it won't tell you it's guessing. And the resolution will be internally consistent, well-documented, and wrong in ways that require deep domain knowledge to catch.</p>
<p>This is why smarter models don't fix the problem. The bottleneck was never reasoning capability. It was the absence of explicit, locked decisions upstream of execution.</p>
<hr />
<h2>Three Layers That Eliminated Guessing</h2>
<p>I built a methodology — iteratively, over the course of these three products — that moves every material decision upstream of execution. It has three layers, and they must be completed in sequence.</p>
<h3>Layer 1: Regulatory Foundation</h3>
<p>Before any product design work begins, the compliance scaffolding gets built. For healthcare, that means a complete Quality Management System: policies, standard operating procedures, forms, evidence records, and a control mapping to whatever regulatory framework applies.</p>
<p>This isn't checkbox compliance. Every SOP becomes a governing document that execution references. When a story says "per SOP-014," that SOP exists, it's specific to the organization's architecture and tooling, and it defines exactly what evidence the story must produce. The AI doesn't interpret regulatory requirements at implementation time — the interpretation was done during the compliance authoring phase and locked in a controlled document.</p>
<p>On the first product, a two-person team (me and the AI) produced 50 controlled documents in roughly 30 hours. Traditional timeline for that scope is 4–6 weeks with a dedicated compliance team.</p>
<h3>Layer 2: Signal-Driven Design</h3>
<p>This is where the product's architecture gets defined — not as a set of diagrams or a PRD that engineering interprets, but as a formal domain model that converges to zero ambiguity through iterative adversarial passes. I call this methodology <a href="https://sdd.mmmnt.dev/">Signal-Driven Design (SDD)</a>.</p>
<p>SDD draws from domain-driven design, event storming, and user journey mapping, but its core mechanic is adversarial convergence. The process works like this:</p>
<ol>
<li><p>Extract a domain specification from the PRD and architectural decision records. This produces bounded contexts, aggregates, commands, events, invariants, policies, and sagas — the full vocabulary of what the system does.</p>
</li>
<li><p>Run an adversarial gap analysis against the specification you just produced. The same session that wrote the spec tries to break it: structural gaps (missing aggregates, orphaned events), heuristic gaps (oversized aggregates, thin contexts), language gaps (inconsistent terminology), and decision gaps (unresolved architectural questions).</p>
</li>
<li><p>Resolve every gap collaboratively, one at a time. Some are mechanical fixes the AI handles as an interpreter. Others require architectural judgment — those escalate to me. The role separation is explicit: the AI proposes, I decide, and the decision gets recorded with rationale.</p>
</li>
<li><p>Regenerate the full specification incorporating all resolutions and run the adversarial analysis again. Repeat until gaps hit zero.</p>
</li>
</ol>
<p>On every product, this converges in three passes. The gap trajectory is predictable by pass two — you can see whether the model is tightening or oscillating. What matters is that when it converges, every command, every event, every invariant has been examined adversarially and either confirmed or corrected. There are no assumptions left in the specification.</p>
<p>The items that can't be resolved — because a domain expert hasn't made the call yet, or a technical evaluation hasn't happened — go into a deferred resolutions tracker with an explicit owner. They don't get guessed at. They get flagged as implementation-blocking and carried forward visibly.</p>
<h3>Layer 3: Execution Planning With Closed Boundaries and Enforced Quality Gates</h3>
<p>The converged domain specification feeds into an execution plan manifest — a single document that maps every milestone, epic, and story to the domain model, the governing ADRs, and the regulatory SOPs. Every story has:</p>
<ul>
<li><p>A closed boundary defining exactly which files it creates, modifies, or reads</p>
</li>
<li><p>Machine-verifiable exit criteria tied to the domain specification</p>
</li>
<li><p>References to the specific governing documents that define how it's built</p>
</li>
<li><p>Parallelization tags based on file-level conflict analysis between stories</p>
</li>
</ul>
<p>But the manifest alone is just a document. What makes it enforceable is the CI pipeline.</p>
<p>Every quality gate from the methodology gets encoded into continuous integration. Coverage thresholds, test evidence requirements, traceability checks, linting rules that enforce architectural boundaries — these run on every pull request, automatically, without human intervention. The pipeline doesn't care who wrote the code. It validates that the output conforms to the specification and the governing documents.</p>
<p>This is what keeps execution, tracking, and the AI in sync. You can cut corners, but the quality gates will catch you. A story that skips its test specification fails CI. A pull request that modifies files outside its closed boundary fails CI. Code that doesn't meet coverage thresholds fails CI. The methodology's discipline isn't enforced by willpower or code review — it's enforced mechanically, on every commit.</p>
<p>The manifest becomes the contract between planning and execution. When the AI picks up a story, it doesn't need to make architectural decisions — every decision was already made during layer 2, documented in an ADR, and referenced on the ticket. The AI's job is translation: take the locked specification and produce code that matches it. And CI verifies that the translation is faithful.</p>
<p>This is the key insight: <strong>the framework doesn't make the executor smarter. It makes the executor's judgment irrelevant to the outcome.</strong></p>
<hr />
<h2>What the Numbers Actually Mean</h2>
<p>The 95% accuracy rate isn't the AI getting things right 95% of the time through better reasoning. It's the AI having nothing to get wrong. When every decision is locked in a governing document, every story has a closed boundary, and every invariant has been adversarially validated, the implementation is a mechanical translation exercise. The 5% that still requires correction is edge cases where the specification was ambiguous in ways the gap analysis didn't catch — and those corrections feed back into the next pass.</p>
<p>The 40% correction rate without the framework isn't the AI being bad at coding. It's the AI being confident at guessing. Remove the guessing, and the number drops to the floor.</p>
<p>My time split now is roughly 60% planning (working through the three layers with the AI as a collaborator) and 40% delivery oversight. That 40% isn't debugging code line by line. It's reviewing pull requests for shape, reading CI reports, checking test coverage summaries, and confirming that the pipeline's quality gates passed cleanly. When enough quality is locked into CI, I defer my trust to the reporting of the pipeline jobs rather than having to validate implementation details myself. The planning investment is front-loaded and significant. But the delivery phase is fast, predictable, and the reviews are architectural confirmations rather than defect hunts.</p>
<hr />
<h2>What This Changes About AI-Augmented Teams</h2>
<p>Yes, code is cheap. AI can produce it faster than any human team, and the quality floor keeps rising with every model generation. But producing code was never the hard part.</p>
<p>The hard part is knowing what to build and why. It's tying together domain-driven design, event storming, user journey mapping, product management, architecture, engineering, testing, and compliance into a coherent system where every decision reinforces every other decision. That cross-discipline integration doesn't come from a tool. It comes from years of building things, shipping things, breaking things, and learning which decisions cascade and which ones don't.</p>
<p>There's no bootcamp for this. No certificate course. No fast path. It's the accumulated judgment of someone who has been a product owner, an architect, a quality manager, and a compliance officer — often simultaneously — and who understands how those roles constrain and inform each other. The methodology I've described isn't a process anyone can follow mechanically. It requires someone who can see the gap between a domain specification and a regulatory requirement, between an architectural decision and its downstream impact on sales cycles, between a testing strategy and its evidence value during an audit.</p>
<p>AI is contextually aware of the now. It can reason about the specification in front of it with remarkable depth. But it doesn't see how today's decisions shape the product six months from now. It doesn't know that shifting regulatory compliance left — building it into the foundation instead of retrofitting it — puts a business at the front of the line in competitive markets where enterprise buyers require certification before the first demo. It doesn't know that a particular domain modeling decision will make or break a pricing tier, or that deferring a feature creates a dependency that blocks three other features in the next quarter.</p>
<p>That strategic reasoning — the ability to see the future implications of present decisions — is what the human brings. And it's not reducible to a prompt.</p>
<p>The methodology I've described is labor-intensive on the front end. You can't skip the compliance layer, shortcut the adversarial convergence, or hand-wave the closed story boundaries. Each layer produces the inputs the next layer requires, and the traceability between layers is what makes the whole chain auditable — which matters in regulated environments, but also matters in any environment where you want to understand why a decision was made six months from now.</p>
<p>The pattern is transferable. I've run it across three products in different domains, with different tech stacks, different team sizes, and different regulatory requirements. The 40% correction rate without the framework is consistent. The 95% accuracy with it is consistent. The methodology scales because the problem it solves — ambiguity at execution time — is universal.</p>
<hr />
<h2>The Human Role Didn't Shrink. It Clarified.</h2>
<p>The job of humans in this era has changed. We are no longer responsible for the mundane, repetitive tasks that AI handles better and faster. What we own is design and vision.</p>
<p>My role shifted entirely to where it has the highest leverage: making decisions during planning, setting architectural direction during domain convergence, and reviewing delivery for intent preservation. I'm not reviewing less. I'm reviewing differently. Instead of reading every line of code to find defects, I'm looking at the shape of what's delivered, confirming it matches the specification, and trusting the CI pipeline to enforce the details mechanically.</p>
<p>The AI is a better executor than it is a decision-maker. The methodology I've built accepts that constraint and designs around it. Every decision gets made by a human with domain context, documented with traceability, and locked before execution begins. The AI then does what it's actually good at: translating explicit instructions into consistent output at speed. And the CI pipeline validates every translation, every time, without human intervention.</p>
<p>That's the unlock. Not better AI. Better inputs to AI — created by humans who understand what they're building and why it matters.</p>
]]></content:encoded></item><item><title><![CDATA[Introducing Moment and Facet: Your Domain Model Deserves to Run Before Your Code Does]]></title><description><![CDATA[You changed a field. Three downstream services broke in production. Nobody saw it coming — not the type checker, not the tests, not the code review. The contract between your service and theirs was ne]]></description><link>https://listenrightmeow.hashnode.dev/introducing-moment-and-facet-your-domain-model-deserves-to-run-before-your-code-does</link><guid isPermaLink="true">https://listenrightmeow.hashnode.dev/introducing-moment-and-facet-your-domain-model-deserves-to-run-before-your-code-does</guid><category><![CDATA[DDD]]></category><category><![CDATA[#Domain-Driven-Design]]></category><category><![CDATA[Event Sourcing]]></category><category><![CDATA[software architecture]]></category><dc:creator><![CDATA[Mike Dyer]]></dc:creator><pubDate>Sun, 05 Apr 2026 00:03:30 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/69ae573286766ac3a67a2e78/0ba532e7-bcb0-4c67-9ebe-0e607dedfdc3.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>You changed a field. Three downstream services broke in production. Nobody saw it coming — not the type checker, not the tests, not the code review. The contract between your service and theirs was never written down, never tested, and never visible to anyone. It lived in tribal knowledge and hope. And it broke on a Tuesday.</p>
<p>If you've built distributed systems, you have a version of this story. Maybe it was a renamed event field. Maybe it was a new required property that one consumer expected and another didn't. Maybe it was a multi-step process that spanned three services and silently stopped completing because the second one changed its response shape. The details differ. The shape is always the same: <em>the failure lived in the space between services, where nobody was looking.</em></p>
<p>I've been building software for over twenty years — Fortune 500 companies, startups that didn't survive, solo consulting where the architecture lived entirely in my head. Across all of that, this is the failure mode that never goes away. Not because engineers are careless. Because there's no tool that makes the contracts between services visible, testable, and enforceable <em>before</em> you ship.</p>
<p>I've spent the last year writing about this gap. I named patterns. I documented a methodology. I wrote about what happens when you use AI not for code generation, but for domain modeling — the hard, slow, unglamorous work of deciding what your system <em>means</em> before deciding how it <em>runs</em>.</p>
<p>What I didn't write about is that I was building the thing I couldn't find.</p>
<p>Today I'm releasing Moment — an open-source domain specification toolchain — and Facet, its companion visualization platform.</p>
<hr />
<h2>The gap that nobody names</h2>
<p>Here's the thing about Domain-Driven Design that nobody talks about at conferences: there's no specification layer.</p>
<p>You whiteboard it. You sticky-note it. You argue about bounded context boundaries in a room (or alone, staring at a screen). And then you go write code. There's nothing in between. No artifact that says "this is the model" in a way that generates your types, your tests, your documentation, and your API contracts. No way to ask "does this event flow actually work?" before you've implemented both sides.</p>
<p>Your domain model lives in seven places by the end of week one: the wiki page that's already stale, the TypeScript interfaces that diverged on Tuesday, the tests that test the wrong contract, the Markdown doc nobody reads, the API spec that's three fields behind, the whiteboard photo from last sprint, and someone's head.</p>
<p>Six of those will be wrong by next month. You just won't know which six.</p>
<p>Evans gave us the vocabulary for <em>where things live</em> — bounded contexts, aggregates, entities, value objects. That spatial model changed everything about how we think about software. But the vocabulary for <em>how things move through time</em> — events crossing boundaries, processes orchestrating across contexts, policies reacting to state changes — has no formal home. It lives in conversations that ended months ago and assumptions nobody wrote down.</p>
<p>That's the gap. And it's where every painful production incident I've ever debugged actually lived.</p>
<hr />
<h2>Two tools. One workflow.</h2>
<p>Moment lives in your terminal. Facet lives in your browser. Together they do something that doesn't exist anywhere else in the DDD ecosystem: they let you <em>run your domain model before you write implementation code</em>.</p>
<p><strong>Moment</strong> is a specification language and toolchain. You write a <code>.moment</code> file — one file — that describes your bounded contexts, aggregates, commands, events, and the temporal flows between them. Flows are the key. A flow describes events crossing bounded context boundaries with explicit relationship types and contracts. <code>OrderPlaced</code> crosses from Ordering to Fulfillment via CustomerSupplier, and the contract says <code>orderId</code> and <code>items</code> are required. That crossing has never had a home before. Now it does.</p>
<p>From that one file, Moment generates everything downstream: typed TypeScript interfaces, BDD scenarios, test scaffolds with crossing assertions, specification documents with Mermaid diagrams, and AsyncAPI 3.0 contracts. One specification, six artifact types, zero drift. Deterministic output — same input, same output, clean git diffs.</p>
<p>But generation isn't the revelation. Simulation is.</p>
<p><strong>Facet</strong> is where your domain model becomes visible. Run <code>moment simulate</code> and Moment produces synthetic event flows — complete with causation chains, correlation tracking, and branch paths. Open Facet and you <em>see</em> those flows rendered as interactive timelines. You watch <code>OrderPlaced</code> cross from Ordering to Fulfillment. You see the causation chain — which event caused which. You see where a contract field is missing, where a process stalls, where a branch path leads somewhere your model didn't anticipate.</p>
<p>You see your domain model <em>running</em>. Before a single aggregate class exists.</p>
<p>I can't overstate what this felt like the first time it worked. I'd spent months writing specifications, generating code, trusting that the model was right because the types compiled and the tests passed. Then I opened Facet and watched the events flow through the system I'd described — and I could <em>see</em> the places where my thinking was incomplete. Not wrong, exactly. Incomplete. Assumptions I'd made about how contexts interact that I'd never been forced to make explicit.</p>
<p>In every other engineering discipline, you test the design before you build. Civil engineers don't pour concrete and then check load calculations. Chip designers don't fabricate and then verify logic. But in domain modeling, we've been going from whiteboard to code for twenty years and pretending that's fine.</p>
<p>It's not fine. We just didn't have the tooling to do better.</p>
<hr />
<h2>What the specification looks like</h2>
<p>A <code>.moment</code> file reads like a domain description, not a programming language:</p>
<pre><code class="language-plaintext">context "Ordering" [Core]

  aggregate "Order"
    identity orderId: UUID

    command PlaceOrder
      input customerId: UUID, items: OrderItem[]
      precondition orderNotPlaced: "Order has not already been placed"
      emits OrderPlaced

    event OrderPlaced
      orderId: UUID
      customerId: UUID
      items: OrderItem[]
      placedAt: DateTime

flow "order-placed"
  lane ordering "Ordering" [Core]
  lane fulfillment "Fulfillment" [Supporting]

  moment "Order submission"
    ordering: PlaceOrder
    ordering: OrderPlaced crosses-to fulfillment via CustomerSupplier
      contract
        orderId: UUID [required]
        items: OrderItem[] [required]
</code></pre>
<p>The <code>flow</code> block is what makes this different from every DDD tool that's come before. It encodes the temporal dimension — the thing Evans didn't formalize, the thing Brandolini's sticky notes gesture at but can't enforce. The crossing contract that broke on a Tuesday? This is where it lives now.</p>
<p>I won't walk through the full pipeline here — the <a href="https://moment.mmmnt.dev">site</a> has a seven-tab interactive example showing the generated TypeScript, Gherkin, test scaffolds, spec docs, AsyncAPI, and simulation output from a single <code>.moment</code> file. Go look. It's the thing I'm proudest of on that page.</p>
<hr />
<h2>Where this came from</h2>
<p>I'm a solo founder. No team. No co-founder. Just me and an AI partner doing the kind of knowledge crunching that Evans described in 2003 — except instead of a room full of domain experts, it's one architect with twenty years of scar tissue and a language model that never forgets a bounded context boundary.</p>
<p>That practice produced <a href="https://sdd.mmmnt.dev">Signal-Driven Development</a> — a three-pass convergence methodology where you model, run a gap report, resolve gaps, and iterate until the gap count hits zero. Zero gaps is the definition of done that DDD never had. I <a href="https://listenrightmeow.hashnode.dev/introducing-signal-driven-development">wrote about it</a>. I <a href="https://github.com/listenrightmeow/signal-driven-development">built the templates</a>. I applied it to every product in my ecosystem, across hundreds of ADRs and thousands of domain decisions.</p>
<p>And through all of it, one realization kept growing: the spatial model isn't enough. DDD gives you a world-class vocabulary for <em>structure</em> — what lives where, what owns what, what depends on what. But it gives you almost nothing for <em>motion</em> — how events flow through that structure over time, what contracts govern the crossings, what happens when a process spans three contexts and the second one fails.</p>
<p>That's what Moment formalizes. And Facet makes it visible.</p>
<hr />
<h2>The ecosystem</h2>
<p>Moment and Facet are part of a larger toolchain called Complai. The full workflow:</p>
<p><strong>Sift</strong> discovers domains — bounded contexts, aggregates, commands, events — through AI-mediated knowledge crunching. It publishes structured domain events.</p>
<p><strong>Moment</strong> takes those building blocks and adds temporal scope — flows, crossings, and contracts. It generates typed implementations and simulation scenarios.</p>
<p><strong>Facet</strong> visualizes those scenarios — interactive timelines, causation chains, crossing contracts, branch paths. It's where you see your model run.</p>
<p><strong>Forge</strong> (coming soon) bootstraps project structure from Moment's typed artifacts.</p>
<p>Every event flowing between these tools uses a shared envelope format. Each tool can be used independently — Moment doesn't require Sift, Facet doesn't require an account for local simulation. The full <a href="https://moment.mmmnt.dev/ecosystem">ecosystem page</a> shows how they connect.</p>
<hr />
<h2>Current state</h2>
<p>Moment is functionally complete. Ten packages published on npm under <code>@mmmnt/*</code>. The full pipeline works end-to-end: parse, derive, generate, emit. Schema governance, drift detection, simulation, MCP server — all shipped.</p>
<p>Facet is live at <a href="https://facet.mmmnt.dev">facet.mmmnt.dev</a>.</p>
<hr />
<h2>Try it</h2>
<pre><code class="language-bash">git clone https://github.com/mmmnt/mmmnt.git
cd mmmnt &amp;&amp; pnpm install &amp;&amp; pnpm turbo build
moment parse spec.moment
moment derive
moment generate --all
moment simulate
</code></pre>
<p>Then open the simulation output in Facet and watch your domain model run.</p>
<ul>
<li><p><strong>Moment</strong>: <a href="https://moment.mmmnt.dev">moment.mmmnt.dev</a></p>
</li>
<li><p><strong>Facet</strong>: <a href="https://facet.mmmnt.dev">facet.mmmnt.dev</a></p>
</li>
<li><p><strong>GitHub</strong>: <a href="https://github.com/mmmnt/mmmnt">github.com/mmmnt/mmmnt</a></p>
</li>
<li><p><strong>Discord</strong>: <a href="https://discord.gg/YcRqsQUQuu">discord.gg/YcRqsQUQuu</a></p>
</li>
<li><p><strong>SDD Methodology</strong>: <a href="https://sdd.mmmnt.dev">sdd.mmmnt.dev</a></p>
</li>
</ul>
<hr />
<p>Moment stands on the shoulders of the DDD community — Evans, Young, Brandolini, Vernon, Tune, Khononov — and encodes their patterns into a toolchain that enforces what books can only recommend. And a thanks to Hatoum for <a href="https://narrativedriven.org/">NDD</a>, and introducing me to DDD 11 years ago.</p>
<p>I built the thing I needed. I think you might need it too.</p>
]]></content:encoded></item><item><title><![CDATA[Event Storming Is a Language, Not a Workshop]]></title><description><![CDATA[The DDD community treats Event Storming as a workshop activity. You book a room. You buy sticky notes — orange for domain events, blue for commands, yellow for aggregates, purple for policies, pink fo]]></description><link>https://listenrightmeow.hashnode.dev/event-storming-is-a-language-not-a-workshop</link><guid isPermaLink="true">https://listenrightmeow.hashnode.dev/event-storming-is-a-language-not-a-workshop</guid><category><![CDATA[Event Storming]]></category><category><![CDATA[#Domain-Driven-Design]]></category><category><![CDATA[software architecture]]></category><category><![CDATA[Reactive Programming]]></category><category><![CDATA[#eventStorming]]></category><dc:creator><![CDATA[Mike Dyer]]></dc:creator><pubDate>Mon, 30 Mar 2026 03:37:15 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/69ae573286766ac3a67a2e78/80f810e8-7c2e-4d1d-8c51-b07544c3c5a0.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The DDD community treats Event Storming as a workshop activity. You book a room. You buy sticky notes — orange for domain events, blue for commands, yellow for aggregates, purple for policies, pink for hotspots. You invite the domain experts, the developers, the product managers. You fill a wall. You photograph it. You translate it into Jira tickets and architecture diagrams. Then the sticky notes come down and the wall goes back to being a wall.</p>
<p>That's how Event Storming is taught, practiced, and discussed. As a facilitated session with a physical output that exists for as long as the adhesive holds.</p>
<p>I think this framing buries the most important thing Brandolini created. And I think the evidence shows that what he created is bigger than the workshop format can contain.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/69ae573286766ac3a67a2e78/458ce5b5-86b7-457e-a361-35bb1614c508.jpg" alt="" style="display:block;margin:0 auto" />

<p>Start with the sticky note colors. They aren't a workshop convenience. They're a type system.</p>
<p>Orange means "something happened" — a domain event. Blue means "someone or something wants to cause a change" — a command. Yellow means "this thing owns state and enforces rules" — an aggregate. Purple means "when this happens, do that" — a policy. Pink means "we don't understand this yet" — a hotspot. Lilac means "this is derived data" — a read model.</p>
<p>Each color is a semantic classification. Each classification carries behavioral expectations. An orange sticky doesn't just label a fact — it declares that something irreversible occurred in the domain, that downstream consequences may follow, and that the system's state changed in a way that matters to the business. A purple sticky doesn't just describe a reaction — it declares a stateless causal link between an event and a command, a temporal dependency that governs how the system behaves over time.</p>
<p>These aren't categories you impose on a domain. They're structural roles that domain concepts naturally fill. Every domain has things that happen (events), things that cause change (commands), things that own state (aggregates), and things that react to change (policies). Brandolini didn't invent these roles. He gave them colors.</p>
<p>The question is whether the colors — the vocabulary — are bound to the workshop format, or whether they describe something about reactive systems that exists independently of sticky notes on a wall.</p>
<hr />
<p>Here's the argument that they do.</p>
<p>Take any reactive domain and model how it behaves over time. Not what it <em>is</em> — what it <em>does</em>. Follow a single business process from trigger to completion.</p>
<p>A customer places an order. That's a domain event — something happened. The system needs to reserve inventory. That's a policy — when an order is placed, initiate reservation. The policy produces a command — ReserveInventory. The command executes against the Inventory aggregate, which checks stock levels, enforces the invariant that reserved quantity cannot exceed available quantity, and emits a new event — StockReserved. That event triggers another policy — when stock is reserved, initiate payment capture. Payment capture produces a command against the Payment aggregate. The Payment aggregate processes it and emits PaymentCaptured. Which triggers the fulfillment policy. Which produces the CreateShipment command. Which executes against the Shipment aggregate. Which emits ShipmentCreated.</p>
<p>Event → Policy → Command → Aggregate → Event → Policy → Command → Aggregate → Event.</p>
<p>Orange → Purple → Blue → Yellow → Orange → Purple → Blue → Yellow → Orange.</p>
<img src="https://cdn.hashnode.com/uploads/covers/69ae573286766ac3a67a2e78/4178d074-5cf3-4d62-9a55-fb995bad6f46.jpg" alt="" style="display:block;margin:0 auto" />

<p>I didn't import Event Storming to produce that decomposition. I described what happens in the domain over time, and the vocabulary appeared. The events are events because irreversible things happened. The policies are policies because stateless reactions connect those events to actions. The commands are commands because something needs to change state. The aggregates are aggregates because something owns the state being changed and enforces the rules about how it changes.</p>
<p>The reactive flow isn't a framework. It's how temporal causality decomposes in bounded systems. Brandolini's colors are a notation for a structure that already exists in every reactive domain.</p>
<hr />
<p>Now go further. Look at what happens at the boundaries.</p>
<p>The OrderPlaced event was emitted by the Order aggregate in the Order bounded context. But the InventoryReservation policy lives in the Inventory context. That event just crossed a context boundary. The policy that consumes it doesn't know anything about the Order aggregate's internals — it only knows that something happened and it needs to react.</p>
<p>This is where Evans' Anti-Corruption Layer meets Brandolini's reactive flow. The context boundary is the place where the event's semantics must be translated — where OrderPlaced in the Order context becomes a signal that the Inventory context interprets through its own ubiquitous language. The policy is the translation mechanism. It sits at the boundary, consumes a foreign event, and produces a command in the local context's vocabulary.</p>
<img src="https://cdn.hashnode.com/uploads/covers/69ae573286766ac3a67a2e78/fd83250c-7b25-498c-a1b2-b14f09b7e79a.jpg" alt="" style="display:block;margin:0 auto" />

<p>Brandolini's purple sticky — the policy — is doing the same work as Evans' ACL. It's the boundary translation layer for reactive flows. This isn't a metaphor. It's a structural equivalence. The policy sticky note is the temporal version of the spatial ACL. Evans described how bounded contexts protect their internal models from external semantics. Brandolini described how bounded contexts react to external events through local translations. Same architectural function, different dimension — space versus time.</p>
<p>Nobody in the DDD literature has formalized this equivalence. Evans writes about spatial boundaries. Brandolini writes about temporal flows. The two vocabularies coexist in the community without anyone pointing out that they're describing the same boundary mediation from different angles.</p>
<hr />
<p>The shift that made this visible to me came from an unexpected place.</p>
<p><a href="https://narrativedriven.org/">Narrative Driven Development</a> — Sam's work — introduced a framing that reoriented how I think about domain events entirely. NDD treats domain events not as data payloads or state transitions, but as <em>moments in time</em>. A domain event isn't just a record that something changed. It's a moment with temporal significance — it happened at a point in time, in a sequence, with causal relationships to what came before and what comes after.</p>
<p>This sounds like a nuance. It isn't. It's a reorientation of the entire modeling perspective.</p>
<p>When you think of events as state changes, you model them as facts about aggregates. "The order's status changed to Placed." That's spatial — it describes the aggregate's state at a point. When you think of events as moments in time, you model them as points in a causal sequence. "An order was placed, which means inventory must be checked, which means payment must be captured, which means fulfillment must begin." That's temporal — it describes what unfolds across the system as a consequence.</p>
<p>The spatial framing keeps you inside the aggregate. The temporal framing pulls you across boundaries, through policies, into the reactive flow that connects the entire domain. And the moment you model with temporal scope — events crossing boundaries over time — Event Storming's swim lane vocabulary isn't something you choose to use. It's the structure your model produces.</p>
<p>NDD planted the seed. Months of building grew it into something I couldn't ignore: the reactive flow decomposition isn't Brandolini's invention. It's a property of temporal domain modeling that Brandolini's workshop makes visible. The workshop is one discovery mechanism. Temporal modeling is another. They converge on the same structure because the structure is in the domain, not in the method.</p>
<hr />
<p>Here's where this gets practically important and stops being philosophical.</p>
<img src="https://cdn.hashnode.com/uploads/covers/69ae573286766ac3a67a2e78/f9534854-1431-432d-91d5-49bfa9255bfb.jpg" alt="" style="display:block;margin:0 auto" />

<p>If Event Storming's vocabulary is a discovered structure rather than an invented method, then access to that structure doesn't require the workshop. This matters because the workshop has prerequisites that exclude a large part of the DDD community.</p>
<p>You need a room. You need domain experts who can dedicate half a day or more. You need a facilitator skilled enough to keep the session productive. You need developers and product people in the same physical or virtual space. You need the organizational authority to pull all of these people out of their day jobs simultaneously.</p>
<p>Solo practitioners don't have this. Small teams don't have this. Engineers at companies where domain experts are executives who won't sit in a four-hour workshop don't have this. The workshop format creates an accessibility barrier that locks out precisely the people who would benefit most from the vocabulary.</p>
<p>But the vocabulary works without the workshop. You can model a domain's temporal behavior alone — event by event, policy by policy, command by command — and arrive at the same reactive decomposition that a room full of sticky notes would produce. Not because you're doing Event Storming without the workshop. Because you're modeling temporal causality, and the structure is the same regardless of how you discover it.</p>
<p>The workshop is valuable. Brandolini's facilitation techniques surface domain knowledge from non-technical experts in ways that nothing else matches. The physical, collaborative, high-energy format produces insights that solo modeling rarely does. If you can run a workshop, run one.</p>
<p>But stop telling people that Event Storming requires the workshop. It doesn't. The workshop requires Event Storming's vocabulary. The vocabulary doesn't require the workshop. The distinction matters because thousands of practitioners are locked out of the method by logistics while the language sits there, available, waiting to be used.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/69ae573286766ac3a67a2e78/816ee20b-b39c-4d24-9e0d-ca5254d7c9e4.png" alt="" style="display:block;margin:0 auto" />

<p>There's a deeper point here about what Brandolini actually contributed to DDD.</p>
<p>Evans gave us the spatial vocabulary — bounded contexts, aggregates, entities, value objects. The nouns of domain modeling. The things the system <em>is</em>.</p>
<p>Brandolini gave us the temporal vocabulary — events, commands, policies, read models. The verbs of domain modeling. The things the system <em>does</em>.</p>
<p>These aren't competing frameworks. They're complementary dimensions of the same domain. Evans models the space. Brandolini models the time. Together, they give you a complete vocabulary for describing what a system is, what it does, how it reacts, and how it changes.</p>
<p>But the community treats Evans as the foundation and Brandolini as a workshop technique. That's wrong. Brandolini's vocabulary is as foundational as Evans'. It's just been trapped inside a workshop format that makes it look like a facilitation tool instead of what it actually is — the temporal half of domain-driven design.</p>
<p>Event Storming isn't a workshop. It's a language. The workshop is one way to speak it.</p>
<p>It's not the only way.</p>
<hr />
<p><em>This is an independent post — not part of the Signal-Driven Development series. More at</em> <a href="https://listenrightmeow.hashnode.dev"><em>listenrightmeow.hashnode.dev</em></a></p>
]]></content:encoded></item><item><title><![CDATA[AI Made My Architecture Deeper, Not Simpler]]></title><description><![CDATA[The pitch is always simplification. AI will make development faster, architectures leaner, systems easier. Fewer decisions, fewer layers, fewer meetings. Point the model at your domain, let it generat]]></description><link>https://listenrightmeow.hashnode.dev/ai-made-my-architecture-deeper-not-simpler</link><guid isPermaLink="true">https://listenrightmeow.hashnode.dev/ai-made-my-architecture-deeper-not-simpler</guid><category><![CDATA[#Domain-Driven-Design]]></category><category><![CDATA[DDD]]></category><category><![CDATA[software architecture]]></category><category><![CDATA[AI-assisted development]]></category><category><![CDATA[SDD]]></category><category><![CDATA[Domain Modeling]]></category><dc:creator><![CDATA[Mike Dyer]]></dc:creator><pubDate>Sun, 29 Mar 2026 23:40:25 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/69ae573286766ac3a67a2e78/0ba5c5f7-3957-4336-bdf3-0c7a165565eb.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The pitch is always simplification. AI will make development faster, architectures leaner, systems easier. Fewer decisions, fewer layers, fewer meetings. Point the model at your domain, let it generate the structure, ship it.</p>
<p>That's not what happened.</p>
<p>What happened is the opposite. Working with AI as a domain modeling partner over months of sustained architectural work produced a system with more layers, more explicit decisions, more structural depth than anything I've built with a team. Not because the AI over-engineered it. Because the AI never let me skip the parts that teams routinely skip.</p>
<hr />
<p>Here's what I mean by "skip."</p>
<p>Every architect has done this: you're modeling a domain, you hit a decision that requires careful analysis, and you defer it. Not intentionally. You just move on because the next bounded context is more interesting, or the aggregate you're working on feels close enough, or the team is tired and somebody says "we'll clean this up later." The decision doesn't get made. It doesn't get documented. It becomes an implicit assumption embedded in the codebase, invisible until something breaks in production and someone traces the failure back to a modeling choice that was never actually a choice — it was an absence.</p>
<p>Teams do this constantly. Not because they're careless, but because collaborative modeling has natural momentum. Discussions move forward. Whiteboards get erased. The facilitator keeps the energy up. And the hard, awkward, slow decisions — the ones where you sit with ambiguity for twenty minutes before the right boundary reveals itself — those are exactly the decisions that group dynamics conspire to skip.</p>
<p>AI doesn't have group dynamics. It doesn't get tired of your domain model. It doesn't lose context from the session three weeks ago. And when you move past a decision without resolving it, it doesn't politely let it go. You can try to skip ahead, but the model holds. Every unresolved ambiguity sits there, waiting, until you deal with it.</p>
<p>That's not simplification. That's a forcing function for depth.</p>
<hr />
<p>Two patterns emerged from this work that I didn't anticipate.</p>
<p>The first was the Candidate Lifecycle — the realization that when AI generates domain artifacts, every output must enter as a candidate, not as confirmed knowledge. In a team setting, the knowledge crunching process embeds trust implicitly. The domain expert says "we process refunds within 30 days," the team discusses it, the model absorbs it. Nobody stamps it "confirmed." The confirmation was the conversation itself.</p>
<p>AI doesn't participate in that conversation. It generates interpretations — some right, some close, some subtly wrong in ways that look right. Without an explicit confirmation boundary, those interpretations flow into the model as if they were validated domain knowledge. They weren't. They were candidates that nobody reviewed because the process assumed implicit trust that no longer existed.</p>
<p>I didn't design the Candidate Lifecycle because I read a paper about AI trust boundaries. I designed it because I watched interpretations enter the model unchallenged and realized the model was accumulating confident noise. The pattern wasn't planned. It was forced — the architecture demanded it because the alternative was a domain model built on unverified assumptions.</p>
<p>The second was the Classification Gap — the discovery that a semantically wrong but structurally valid classification produces a model that passes every check. A reactive behavior misclassified as a static constraint looks perfect on paper. The gap report says nothing. The heuristics are healthy. And the architecture is missing an entire reactive path because the concept that would have anchored it was placed in the wrong category.</p>
<p>I didn't discover the Classification Gap through a literature review. I discovered it because I was staring at a specification that looked clean but felt wrong. Something was missing from the event flows, but the gap report insisted the model was converged. Tracing it back led to a single concept — a Policy classified as an Invariant — that had silently collapsed a temporal behavior into a spatial constraint. Structurally valid. Semantically backwards. Invisible to every automated check.</p>
<p>Both of these patterns — the Candidate Lifecycle and the Classification Gap — are things a team would eventually discover too. In production. After the architecture hardens around the wrong assumptions. After the missing reactive path causes a failure that nobody can trace to a modeling decision because the decision was never visible in the first place.</p>
<p>AI surfaced them during modeling. Not after deployment. Not during code review. During the part of the process where they were still cheap to fix.</p>
<hr />
<p>This is the counterintuitive result: AI partnership doesn't produce simpler architectures. It produces more honest ones.</p>
<p>Every decision that a team would have deferred, the AI partnership forced. Every ambiguity that would have been papered over with "we'll figure it out during implementation," the modeling process surfaced and demanded resolution. Every classification that would have gone unchallenged because the team was moving fast and the model looked clean enough — each one became a decision point with explicit confirmation or rejection.</p>
<p>The architecture got deeper because every layer exists for a reason that was articulated during design, not discovered during debugging. The provenance chains exist because I realized AI interpretations needed auditable lineage. The confirmation boundaries exist because I watched unverified outputs accumulate. The classification verification exists because I found the invisible error that passes every test.</p>
<p>None of these layers were planned in advance. None of them came from an architecture diagram drawn before writing code. They emerged from the sustained pressure of building with a partner that has perfect recall, infinite patience, and no incentive to skip the hard parts.</p>
<hr />
<p>I want to be precise about what I'm not saying.</p>
<p>I'm not saying AI is a better collaborator than humans. A domain expert who knows the business deeply, who can say "no, that's not how we process claims" with the authority of ten years of operational experience — that person is irreplaceable. AI doesn't have domain expertise. It has pattern recognition and recall.</p>
<p>I'm not saying teams produce shallow architectures. Teams with strong architects, good facilitation, and genuine domain expert participation produce extraordinary systems. The best architectures I've seen were built by teams, not solo practitioners.</p>
<p>What I'm saying is that the specific mechanics of sustained AI partnership produce architectural depth in places that other processes don't reach. Let me be concrete about why.</p>
<p>A human collaborator holds maybe three or four bounded contexts in their head at once. By the fifth, they're referencing notes from earlier sessions, reconstructing context, losing the fine-grained relationships between aggregates in Context A and the policies they trigger in Context C. That's not a criticism — it's a cognitive limit. AI holds the entire specification in working memory. All six bounded contexts, all nine aggregates, all fifty-four domain events, all the invariant relationships between them, simultaneously. When you say "does this new policy in the Order context conflict with the fulfillment saga we defined three weeks ago," the answer comes back with the specific saga steps, the events it subscribes to, and the exact point where the new policy would create a race condition. No notes. No reconstruction. Immediate structural reasoning across the full model.</p>
<p>That changes what questions you can ask. You stop simplifying your questions to fit your collaborator's context window and start asking the hard cross-cutting questions that would take a team twenty minutes of whiteboard reconstruction. The architecture gets deeper because the questions get harder.</p>
<p>Then there's adversarial reasoning. I can ask my AI collaborator to argue against its own recommendation. Not as a performance — genuinely. "You just suggested this aggregate boundary. Now tell me why it's wrong. What would Vernon say? Where does this violate the heuristic thresholds we set in the last pass?" And the challenge comes back with structural reasoning, not ego. No defensiveness. No anchoring to its own prior suggestion. It will tear down something it built ten minutes ago if the reasoning demands it. Try asking a human architect to genuinely attack their own design decision in the same session they made it. The social dynamics make it almost impossible. The AI has no social dynamics. The adversarial review is clean.</p>
<p>There's the forcing function of articulation. You cannot hand-wave past a decision with an AI partner. With a team, you can say "this aggregate handles order state" and everyone nods because they roughly know what you mean. With AI, that statement generates follow-up: which state transitions? What commands mutate it? What invariants constrain those transitions? What events are emitted? If you can't answer, the model has a gap, and the gap is visible immediately — not six weeks later when someone tries to implement it and discovers the aggregate's responsibilities were never actually defined.</p>
<p>And there's the absence of sunk cost. Human teams invest identity in architectural decisions. The person who championed the microservice boundary feels ownership of it. Challenging it three months later isn't just a technical discussion — it's a social one. AI has zero investment in prior decisions. When the third pass reveals that a boundary drawn in the first pass was wrong, the restructuring is purely analytical. No politics. No hurt feelings. No "but we already built the infrastructure for this." The architecture evolves based on what the domain demands, not what the team has already committed to.</p>
<p>None of this replaces the domain expert. None of it replaces the team that debates trade-offs over lunch and builds shared understanding through years of working together. Those things produce their own kind of depth — the kind that comes from lived operational experience and collective intuition.</p>
<p>But this combination — one architect, full model context, adversarial reasoning without ego, forced articulation, zero sunk cost — changed how I will architect for the rest of my career. Not because AI is smarter than the teams I've worked with. Because it operates in a fundamentally different mode that reaches places those teams structurally couldn't, for reasons that have nothing to do with intelligence and everything to do with mechanics.</p>
<p>The complexity wasn't added. It was always in the domain, waiting for a process that wouldn't let me look away.</p>
<hr />
<p><em>Next: If AI introduces failure modes that structural analysis can't catch and trust boundaries that traditional DDD never needed, what's the architectural response? The same one Evans gave us for legacy systems — but applied to intelligence instead of data.</em></p>
<p><em>This is Post 8 of a 17-post series on Signal-Driven Development — a solo-practitioner DDD methodology built with AI.</em> <a href="link"><em>Post 7: The Classification Gap</em></a></p>
]]></content:encoded></item><item><title><![CDATA[The Classification Gap: The Bug That Passes Every Test]]></title><description><![CDATA[There's a class of domain modeling error that no test catches. No structural analysis flags it. No linter complains. Your gap report comes back clean. Your aggregate has the right number of invariants]]></description><link>https://listenrightmeow.hashnode.dev/the-classification-gap-the-bug-that-passes-every-test</link><guid isPermaLink="true">https://listenrightmeow.hashnode.dev/the-classification-gap-the-bug-that-passes-every-test</guid><category><![CDATA[DDD]]></category><category><![CDATA[software architecture]]></category><category><![CDATA[#Domain-Driven-Design]]></category><category><![CDATA[AI]]></category><category><![CDATA[Domain Modeling]]></category><category><![CDATA[SDD]]></category><dc:creator><![CDATA[Mike Dyer]]></dc:creator><pubDate>Sun, 29 Mar 2026 22:47:38 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/69ae573286766ac3a67a2e78/ca29d321-0928-407e-861c-b12ae5d94dbf.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>There's a class of domain modeling error that no test catches. No structural analysis flags it. No linter complains. Your gap report comes back clean. Your aggregate has the right number of invariants, your bounded context has healthy ratios, your event flows trace end to end.</p>
<p>The model is wrong.</p>
<p>Not wrong like a missing field or a misspelled event name. Wrong like a reactive behavior modeled as a static constraint. Wrong like a Policy collapsed into an Invariant — structurally valid, semantically backwards, invisible to every check you run against it.</p>
<p>I call this the Classification Gap. And if you're using AI to assist with domain modeling — even informally, even just bouncing ideas off ChatGPT — you will hit it. You probably already have. You just couldn't name it.</p>
<hr />
<p>Here's the example that made it concrete for me.</p>
<p>An architect describes a business rule: <em>"When an order is placed, check inventory and reserve stock."</em> That's a reactive behavior. Something happens — an order is placed. In response, the system does something — checks inventory, reserves stock. In DDD vocabulary, this is a Policy. A stateless reaction to a domain event that produces a downstream command. The triggering event and the resulting action are the whole point.</p>
<p>Now imagine the AI classifies it as an Invariant instead. An Invariant is a static constraint on an aggregate — a rule that must always be true. "An order cannot exceed the customer's credit limit." "A shipment must have at least one line item." These are structural truths about the domain. They don't react to events. They don't trigger commands. They just... hold.</p>
<p>The misclassification looks fine. The Invariant has a name, a description, an owning aggregate. The aggregate now has one more invariant — which actually improves its invariant-to-command ratio. If you're running structural completeness checks, every box is ticked. If you're evaluating heuristic thresholds, the numbers look healthier than before.</p>
<p>The gap report says nothing.</p>
<p>But you've just collapsed a temporal behavior into a spatial constraint. You've taken something that fires when an event crosses a boundary and turned it into something that sits inside an aggregate doing nothing. The reactive path — the entire causal chain from "order placed" to "stock reserved" — is gone from the model. Not broken. Not misconfigured. Just absent, because the concept that would have anchored it was placed in the wrong category.</p>
<hr />
<p>In traditional DDD, this mistake is self-correcting. A human modeler who classifies a reactive behavior as a static constraint will feel the friction at implementation time. They'll try to write the code and something won't fit. The Invariant doesn't express what they meant. The code becomes awkward. The feedback loop is the implementation itself — the discomfort of writing code that fights the model is the signal that the model is wrong.</p>
<p>Evans never had to name this problem because the modeling medium — human conversation, whiteboard sessions, iterative refinement through code — naturally surfaces it. The human modeler carries the semantic intent in their head. When the model diverges from that intent, they feel it. They may not articulate it as "I misclassified a Policy as an Invariant," but they'll say "this doesn't feel right" and restructure.</p>
<p>AI doesn't feel anything.</p>
<p>When an AI classifies a concept into a building block type, it commits with confidence. The classification is linguistically coherent. The output is well-structured. And the moment it enters your domain model, the original semantic intent — the reactive behavior, the temporal causality, the "when X happens, do Y" — is gone. What remains is a structurally valid Invariant with no trace of what it was supposed to be.</p>
<p>The feedback loop is broken. Not degraded, not delayed — broken. Because every downstream check evaluates the classified output, not the original intent. Structural analysis sees a valid Invariant. Heuristic evaluation sees healthy ratios. The gap report confirms convergence. The system has produced an internally consistent model built on a wrong foundation, and nothing in the verification pipeline can see it.</p>
<hr />
<p>This is what makes the Classification Gap different from every other modeling error. Missing elements get caught by structural completeness checks. Threshold violations get caught by heuristic analysis. Naming inconsistencies get caught by convention rules. Every other category of error leaves a visible trace — a gap in the report, a metric outside bounds, a rule that fires.</p>
<p>The Classification Gap leaves no trace because the model is complete. The error isn't in what's missing. It's in what's present but miscategorized.</p>
<p>And the most susceptible boundary — Policy versus Invariant — is also the most architecturally consequential. It's the boundary between time and space in your domain model. Between something that reacts to events and something that constrains state. Get it wrong and you don't just have a cosmetic error in your specification. You have a model that will produce an architecture without the reactive paths your domain requires. The event flows won't be designed because the concepts that anchor them were never modeled as reactive. The sagas won't be triggered because the policies that initiate them don't exist.</p>
<p>The system will work. It will pass tests. And it will be fundamentally wrong about how the domain behaves over time.</p>
<hr />
<p>This isn't just a Policy-versus-Invariant problem, though that's the sharpest edge. Every classification boundary in DDD carries some version of this risk.</p>
<p>A Command misclassified as a Domain Event inverts the causal direction — intentions become facts, and the model's sense of what requests action versus what records completion flips. An Aggregate misclassified as a Domain Service loses its state boundary — the invariant enforcement surface disappears, and the consistency guarantee with it. A Policy over-promoted to a Saga gains compensation logic it doesn't need, adding architectural complexity for behavior that should be fire-and-forget.</p>
<p>Each of these is structurally valid. Each passes every check. Each produces a different architecture than the domain actually requires.</p>
<p>The common thread is that building block type is a semantic decision, not a structural one. It encodes what a concept <em>does</em> in the domain — how it relates to events, state, time, and causality. Structural analysis can verify that the pieces fit together. It cannot verify that the pieces are the right kind.</p>
<hr />
<p>If you're using AI for domain modeling today — even as a thinking partner, even just for brainstorming bounded contexts or sketching event flows — the Classification Gap is already in your process. The question isn't whether it will happen. The question is whether you'll catch it before the architecture hardens around it.</p>
<p>Traditional DDD never needed a name for this because the detection mechanism was embedded in the human modeler's discomfort. AI-mediated DDD needs the name because the discomfort doesn't exist. The model looks clean. The verification passes. The architecture proceeds.</p>
<p>And somewhere in your specification, a behavior that should react to events is sitting quietly as a constraint, waiting for someone to notice that the system doesn't do what the domain requires.</p>
<p>That's the Classification Gap. The bug that passes every test.</p>
<hr />
<p><em>Next: The architecture that emerged from building detection for problems like this was deeper than anything a team would have produced. Not because AI is smarter — but because it doesn't let you skip the hard parts.</em></p>
<p><em>This is Post 7 of a 17-post series on Signal-Driven Development — a solo-practitioner DDD methodology built with AI.</em> <a href="link"><em>Post 6: The Candidate Lifecycle</em></a></p>
]]></content:encoded></item><item><title><![CDATA[When Jobs to Be Done Meets Domain-Driven Design]]></title><description><![CDATA[There are moments in practice where two ideas you've held separately for years suddenly click together — not because someone told you they were connected, but because you were working through a proble]]></description><link>https://listenrightmeow.hashnode.dev/when-jtbd-meets-ddd</link><guid isPermaLink="true">https://listenrightmeow.hashnode.dev/when-jtbd-meets-ddd</guid><category><![CDATA[DDD]]></category><category><![CDATA[#Domain-Driven-Design]]></category><category><![CDATA[jobs-to-be-done]]></category><category><![CDATA[Event Storming]]></category><category><![CDATA[software architecture]]></category><category><![CDATA[Product Management]]></category><dc:creator><![CDATA[Mike Dyer]]></dc:creator><pubDate>Sun, 22 Mar 2026 05:31:42 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/69ae573286766ac3a67a2e78/e5f9f857-2c66-4402-98ed-b4a69e2ebd5c.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>There are moments in practice where two ideas you've held separately for years suddenly click together — not because someone told you they were connected, but because you were working through a problem and the structure fell out on its own.</p>
<p>This happened to me recently while thinking through how user input normalizes into domain concepts. I was working with the Jobs to Be Done framework on one side and Domain-Driven Design building blocks on the other. And the mapping wasn't just convenient. It was nearly exact.</p>
<p>What surprised me more: nobody seems to have published this connection.</p>
<hr />
<h2>Two Frameworks, Same Year, Same Problem</h2>
<p>Clayton Christensen popularized Jobs to Be Done in <em>The Innovator's Solution</em> in 2003. The core idea: customers don't buy products — they hire them to do a job. Understanding the job — the situation that triggers the need, the motivation behind the action, the outcome that signals completion — is the only reliable way to understand what to build.</p>
<p>Eric Evans published <em>Domain-Driven Design: Tackling Complexity in the Heart of Software</em> in 2003. Same year. His core idea: software should model the domain — not the database, not the UI, not the org chart. The domain model is the shared language between engineers and domain experts. Get the model right, and the system writes itself. Get the model wrong, and no amount of engineering fixes it.</p>
<p>Two books. Same year. Completely different audiences — product people reading Christensen, architects reading Evans. They decomposed the same fundamental question: <strong>what should this system do, and why?</strong></p>
<p>They never cited each other.</p>
<hr />
<h2>The Mapping</h2>
<p>A JTBD statement has a canonical structure:</p>
<blockquote>
<p><strong>"When I</strong> [situation], <strong>I want to</strong> [motivation], <strong>so I can</strong> [outcome]."</p>
</blockquote>
<p>That structure isn't decorative — it's a decomposition. And each component maps directly to a DDD building block:</p>
<table>
<thead>
<tr>
<th>JTBD Component</th>
<th>What It Captures</th>
<th>DDD Building Block</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Situation</strong> ("When I...")</td>
<td>The state of the world that triggers the need</td>
<td>Context / Preconditions</td>
</tr>
<tr>
<td><strong>Motivation</strong> ("I want to...")</td>
<td>The action the actor wants the system to perform</td>
<td>Commands / Intents</td>
</tr>
<tr>
<td><strong>Outcome</strong> ("So I can...")</td>
<td>The observable result that satisfies the job</td>
<td>Domain Events / Post-conditions</td>
</tr>
</tbody></table>
<p>Read that table again. A product manager who writes "When a compliance violation occurs, I want to be notified immediately, so I can remediate before audit" has already decomposed three things:</p>
<ul>
<li><p>A <strong>domain event</strong> that triggers behavior (violation occurred)</p>
</li>
<li><p>A <strong>command</strong> that expresses intent (notify me)</p>
</li>
<li><p>A <strong>post-condition</strong> that defines success (remediation triggered before audit)</p>
</li>
</ul>
<p>They don't know they've done it. They've never heard the term "aggregate root." But the decomposition is structurally identical to what an architect would produce in an Event Storming session.</p>
<hr />
<h2>What the Mapping Means</h2>
<p>This isn't a party trick. The structural alignment has three real consequences.</p>
<p><strong>First, JTBD input normalizes directly into DDD concepts.</strong> If you take a well-formed JTBD statement and decompose it — situation into context, motivation into command, outcome into event — you get the raw material for a domain model. No interpretation layer required. The structure does the translation.</p>
<p><strong>Second, DDD gives JTBD structural rigor.</strong> JTBD practitioners struggle with vague outcomes. "So I can feel confident" is a common pattern — and it's useless for both product and engineering. DDD's requirement for observable state changes forces outcomes to be concrete. Not feelings — events. Not aspirations — post-conditions. If the outcome can't be modeled as something that happened in the system, the JTBD statement is incomplete.</p>
<p><strong>Third, the gap between product and engineering shrinks.</strong> If JTBD input maps to DDD building blocks, then the translation layer between "what the user needs" and "what the system models" becomes mechanical, not interpretive. The architect doesn't have to guess what the product manager meant. The product manager doesn't have to understand aggregates. The decomposition structure itself bridges the vocabulary gap.</p>
<p>This is the part that excites me most. The perennial friction between product and engineering isn't a people problem — it's a structural problem. Product teams and engineering teams decompose the same domain using different vocabularies. The JTBD-to-DDD mapping reveals that the vocabularies are isomorphic. They're describing the same structure with different words.</p>
<hr />
<h2>The Event Storming Parallel</h2>
<p>The mapping gets more interesting when you bring in Alberto Brandolini's Event Storming.</p>
<p>Event Storming uses a specific flow: a <strong>domain event</strong> (orange sticky) triggers a <strong>policy</strong> or <strong>reaction</strong>, which issues a <strong>command</strong> (blue sticky), which produces a new <strong>domain event</strong> (orange sticky). The canonical reactive loop:</p>
<blockquote>
<p>Event → Policy → Command → Event</p>
</blockquote>
<p>Now look at the JTBD structure again:</p>
<blockquote>
<p>Situation (something happened) → Motivation (I want to do something) → Outcome (something new happens)</p>
</blockquote>
<p>And the DDD mapping:</p>
<blockquote>
<p>Precondition (system state) → Command (intent) → Domain Event (state change)</p>
</blockquote>
<p>Three independent frameworks. Three different disciplines — product management, domain modeling, collaborative workshop facilitation. Developed across a decade (2003, 2003, ~2013). And the decomposition shape is the same:</p>
<p><strong>Trigger → Action → Observable Result.</strong></p>
<hr />
<h2>The Shape of the Problem</h2>
<p>Here's the thesis I keep coming back to.</p>
<p>When independent frameworks converge on the same structure without ever referencing each other, the structure isn't a framework artifact — it's a property of the problem domain itself.</p>
<p>Christensen wasn't thinking about domain events when he formalized the Situation → Motivation → Outcome structure. Evans wasn't thinking about jobs to be done when he modeled Precondition → Command → Event. Brandolini wasn't thinking about either when he laid out Event → Policy → Command → Event on a wall of sticky notes.</p>
<p>They were all trying to answer the same question: <em>how do you rigorously decompose what a system should do and why?</em> And they all arrived at the same shape.</p>
<p>That shape — trigger, action, observable result — isn't Christensen's invention, or Evans's, or Brandolini's. It's the shape of the problem. Every rigorous decomposition method finds it, because it's the only shape that captures causality, intent, and outcome in a single structure.</p>
<p>I've seen this convergence pattern before. When you model a domain with temporal scope — events crossing bounded contexts over time — Event Storming's swim-lane vocabulary emerges naturally as a byproduct. You don't set out to build Event Storming. You include temporal scope in the design, and the reactive flow (event → policy → command → event) organizes itself into the same vocabulary Brandolini formalized. You don't find Event Storming. Event Storming finds you — the moment you model events crossing boundaries over time.</p>
<p>The JTBD mapping is the same phenomenon one layer up. You don't set out to connect JTBD to DDD. You try to normalize user input into domain concepts, and the decomposition maps itself.</p>
<hr />
<h2>What This Means for Practice</h2>
<p>If you're a product manager writing JTBD statements, you're already doing domain decomposition. You just don't have the vocabulary to name what you've produced. Learn enough DDD to recognize that your "outcomes" are domain events. It will make your JTBD statements sharper and your conversations with engineering more productive.</p>
<p>If you're an architect practicing DDD, look at the JTBD statements your product team is writing. They're not requirements — they're decompositions. The situation is your precondition. The motivation is your command. The outcome is your event. You may find that the product team has already done half your Event Storming before the session starts.</p>
<p>If you're a solo builder wearing both hats, this mapping is a gift. Write your own JTBD statements for the domain you're modeling. Then decompose them. Situation into context. Motivation into command. Outcome into event. You'll have the skeleton of a domain model before you draw a single diagram.</p>
<p>And if you're skeptical — try it. Take any well-formed JTBD statement and apply the mapping. See if the DDD building blocks fall out. In every case I've tested, they do. Not because the mapping is clever, but because the problem has a shape, and both frameworks found it.</p>
<hr />
<p><em>Christensen and Evans published in the same year. They decomposed the same problem. They never cited each other. And the mapping is nearly exact.</em></p>
<p><em>Every JTBD statement is a domain event waiting to be named.</em></p>
<hr />
<p><strong>Further reading:</strong></p>
<ul>
<li><p>Clayton Christensen &amp; Michael Raynor, <em>The Innovator's Solution</em> (2003)</p>
</li>
<li><p>Eric Evans, <em>Domain-Driven Design: Tackling Complexity in the Heart of Software</em> (2003)</p>
</li>
<li><p>Alberto Brandolini, <em>Introducing EventStorming</em> (~2013; <a href="http://eventstorming.com">eventstorming.com</a>)</p>
</li>
<li><p><a href="http://narrativedriven.org">narrativedriven.org</a> — Narrative-Driven Development, temporal modeling, and the reactive path vocabulary</p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[The Candidate Lifecycle: When AI Models Your Domain, Who Confirms It?]]></title><description><![CDATA[This post marks a shift.
Posts 1 through 5 gave away a methodology — Signal-Driven Development, the gap report, the three-pass convergence process. That was the community gift. Use it, fork it, adapt ]]></description><link>https://listenrightmeow.hashnode.dev/the-candidate-lifecycle-when-ai-models-your-domain-who-confirms-it</link><guid isPermaLink="true">https://listenrightmeow.hashnode.dev/the-candidate-lifecycle-when-ai-models-your-domain-who-confirms-it</guid><category><![CDATA[DDD]]></category><category><![CDATA[#Domain-Driven-Design]]></category><category><![CDATA[software architecture]]></category><category><![CDATA[llm]]></category><category><![CDATA[signal-driven-development]]></category><dc:creator><![CDATA[Mike Dyer]]></dc:creator><pubDate>Sun, 22 Mar 2026 05:17:18 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/69ae573286766ac3a67a2e78/59298c06-76d2-482d-a834-6debc38520db.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>This post marks a shift.</p>
<p>Posts 1 through 5 gave away a methodology — Signal-Driven Development, the gap report, the three-pass convergence process. That was the community gift. Use it, fork it, adapt it.</p>
<p>What follows is different. Over the next four posts, I'm formalizing patterns that emerged from practicing SDD rigorously with an AI collaborator across production domains. These aren't theoretical observations. They're problems I hit, named, and solved — problems that every team incorporating AI into domain modeling will encounter.</p>
<p>Nobody in the DDD community is publishing on these patterns, because nobody else has built a system that does AI-mediated domain modeling at this depth. That's not a boast. It's an observation about where the field is. <a href="https://www.domainlanguage.com/articles/ai-components-deterministic-system/">Evans</a> is writing about integrating AI components into deterministic systems. <a href="https://ddd.academy/accelerate-your-strategic-design-with-llms/">DDD Europe 2026</a> has workshops on LLM-assisted strategic design. The community is exploring the intersection. I've been living in it for months.</p>
<p>The first pattern is the Candidate Lifecycle. It answers a question that traditional DDD never needed to ask.</p>
<hr />
<h2>The Implicit Consensus Problem</h2>
<p>In traditional DDD, knowledge crunching is collaborative. A group of people — developers, domain experts, architects — sit in a room, put stickies on a wall, argue about language, and iterate toward a model. The model that survives the room is the model the team has agreed on.</p>
<p>The agreement is implicit. Nobody votes on whether <code>OrderPlaced</code> is the right event name. Nobody signs off on the aggregate boundary. The team converged through conversation, and the model reflects that convergence. When the developer writes code that mirrors the model, they're implementing a shared understanding — imperfect, informal, but collectively owned.</p>
<p>This works because the feedback loop is human. When a domain expert says "that's not how we think about it," the model changes. When a developer says "I can't implement this boundary cleanly," the team revisits. The model is continuously validated by the people who created it.</p>
<p>Now replace the room with an AI.</p>
<p>You feed a product requirements document into a language model. The model produces a domain specification — bounded contexts, aggregates, events, commands, invariants. The specification is structurally plausible. The names sound right. The boundaries look reasonable. The event flows make sense.</p>
<p>But who agreed to this model?</p>
<p>The AI doesn't have domain expertise. It has statistical pattern completion trained on millions of documents that include DDD examples, software architecture discussions, and domain modeling content. When it names an aggregate <code>OrderFulfillment</code> and places it in a <code>Logistics</code> bounded context, it's not making a domain judgment. It's producing a statistically likely output given the input and its training distribution.</p>
<p>The model might be right. It might be excellent. But the mechanism that produced it is fundamentally different from the mechanism that produces a model in a collaborative workshop. There was no implicit consensus. There was no domain expert pushback. There was no developer saying "this boundary feels wrong." There was a prompt, a completion, and a plausible-looking output.</p>
<p>This is the implicit consensus problem: <strong>traditional DDD's trust model breaks when AI generates the domain model.</strong> The trust was always embedded in the process — we trust the model because we built it together. When AI builds it, that embedded trust doesn't exist.</p>
<hr />
<h2>The Candidate Lifecycle</h2>
<p>The Candidate Lifecycle is a design pattern for AI-mediated domain modeling. It establishes an explicit trust boundary between AI-generated output and confirmed domain knowledge.</p>
<p>The core principle: <strong>nothing an AI produces is domain knowledge until an architect explicitly confirms it.</strong></p>
<p>Every domain artifact that an AI generates — every aggregate, every event, every bounded context boundary, every invariant — enters the system as a <em>candidate</em>. A candidate is a proposal with provenance. It carries what was proposed, why it was proposed (the AI's reasoning), what alternatives were considered, and what produced it (which model, which prompt strategy, which input).</p>
<p>A candidate is not part of the domain model. It's a proposal <em>to</em> the domain model. The domain model only changes when the architect does one of three things:</p>
<p><strong>Confirm</strong>: The candidate is correct. The AI's classification matches the architect's domain understanding. The aggregate should exist, the boundary is right, the event name is appropriate. The candidate becomes confirmed domain knowledge.</p>
<p><strong>Override</strong>: The candidate is partially correct but misclassified. The AI identified a real domain concept but categorized it wrong — it proposed an invariant where the architect recognizes a policy, or it placed a service in the wrong bounded context. The architect corrects the classification while preserving the underlying insight. The override is recorded with rationale.</p>
<p><strong>Reject</strong>: The candidate is wrong. The AI hallucinated a concept, misinterpreted the input, or produced something that contradicts the architect's domain understanding. The rejection is recorded with rationale — because the rejection is itself domain knowledge. It documents what the domain is <em>not</em>.</p>
<p>The confirmation, override, and rejection are the new design surface. In traditional DDD, the design surface is the whiteboard — where you draw boundaries and name things. In AI-mediated DDD, the design surface is the confirmation boundary — where you decide which AI proposals become trusted domain knowledge and which don't.</p>
<hr />
<h2>Why Provenance Matters</h2>
<p>Every candidate must carry provenance — metadata about what produced it and why. This isn't an implementation detail. It's a domain requirement.</p>
<p>Provenance answers the question that every architect will eventually ask: "Why does this aggregate exist?" In traditional DDD, the answer is "we decided in the March workshop." In AI-mediated DDD, the answer must be traceable: this aggregate was proposed by this model version, using this prompt strategy, in response to this input, with this reasoning, and it was confirmed by this architect on this date.</p>
<p>Provenance serves three purposes.</p>
<p><strong>Auditability</strong>: When a domain model is the foundation for a production system, someone will eventually need to reconstruct why a particular decision was made. Provenance provides the full chain — from the input document, through the AI's interpretation, to the candidate proposal, through the architect's confirmation. Every link in the chain is recorded.</p>
<p><strong>Reproducibility</strong>: When you update your AI model or change your prompt strategy, you need to understand what changed. Provenance lets you ask: "Which candidates in this specification were produced by the previous model version? Have any of them been invalidated by the updated model's output?" Without provenance, model upgrades are blind — you can't tell which parts of your domain model might be affected.</p>
<p><strong>Trust calibration</strong>: Over time, provenance data reveals patterns about AI reliability. Which types of domain concepts does the model classify well? Where does it consistently struggle? Provenance transforms individual confirmation decisions into aggregate insight about the AI's modeling capability. This is how the trust boundary becomes data-driven rather than faith-based.</p>
<hr />
<h2>The Evans Connection</h2>
<p>At <a href="https://www.infoq.com/news/2024/03/Evans-ddd-experiment-llm/">Explore DDD 2024</a>, Evans framed a trained LLM on a ubiquitous language as effectively a bounded context. It has its own model of the domain, shaped by its training data and fine-tuning. It speaks a language that overlaps with but isn't identical to the domain expert's language.</p>
<p>This is a powerful framing. And the Candidate Lifecycle is the answer to the question it raises: <strong>how does knowledge from the AI's bounded context become trusted domain knowledge in the architect's model?</strong></p>
<p>In traditional DDD context mapping, we have patterns for this. When two bounded contexts need to share knowledge, we use patterns like Published Language, Anti-Corruption Layer, Customer-Supplier, or Conformist. Each pattern defines who owns the translation, who controls the contract, and how mismatches are handled.</p>
<p>The AI's "bounded context" needs the same treatment. The AI produces output in its own model. That output must cross a trust boundary before it enters the architect's domain model. The Candidate Lifecycle is the translation mechanism — it's the Anti-Corruption Layer between the AI's statistical model and the architect's domain model.</p>
<p>Evans is now <a href="https://www.domainlanguage.com/articles/context-mapping-an-ai-based-component/">writing explicitly about this pattern</a> — drawing Anti-Corruption Layers between deterministic application code and probabilistic LLM output. We arrived at the same architectural conclusion independently. The AI's output must be constrained, translated, and explicitly accepted before it enters the deterministic system. The Candidate Lifecycle formalizes the acceptance mechanism for domain modeling specifically.</p>
<hr />
<h2>What This Changes About DDD Practice</h2>
<p>The Candidate Lifecycle has implications that go beyond the obvious.</p>
<p><strong>The architect's role changes.</strong> In traditional DDD, the architect is a creator — they model the domain through collaborative discovery. In AI-mediated DDD, the architect becomes a curator and a judge. The AI generates candidates at a speed and volume that no human modeler could match. The architect's job is to evaluate, confirm, override, reject, and document. The creative act shifts from "invent the model" to "validate the model and improve it."</p>
<p>This isn't a lesser role. It's a more rigorous one. The architect who evaluates fifty AI-proposed candidates and confirms thirty, overrides twelve, and rejects eight has made fifty explicit domain decisions — each documented with rationale. The architect who draws a model on a whiteboard has made the same decisions implicitly, with no record of what was considered and rejected.</p>
<p><strong>Rejection becomes a first-class artifact.</strong> In traditional DDD, rejected ideas are lost — they exist only in the memory of the people who were in the room. In the Candidate Lifecycle, every rejection is recorded with rationale. "This was proposed as an aggregate, but it has no invariants and no independent lifecycle — it's a value object" is domain knowledge. It documents what the domain is not, which constrains future modeling decisions and prevents the same mistake from being proposed again.</p>
<p><strong>The speed of iteration changes.</strong> A three-pass convergence that might take weeks with a human team can happen in hours with AI generating candidates and an architect curating them. But the curation can't be automated — that's the whole point. The AI proposes, the architect decides. The speed gain is in generation, not in judgment.</p>
<p><strong>Provenance creates institutional memory.</strong> When the architect who confirmed a set of candidates leaves the team, the provenance chain remains. The next architect can reconstruct not just what was decided, but why — including the AI's reasoning, the alternatives that were considered, and the rationale for each confirmation and rejection. This is better institutional memory than most teams have ever had for their domain models, because it was captured at decision time rather than reconstructed after the fact.</p>
<hr />
<h2>The Pattern in Practice</h2>
<p>If you're incorporating AI into your domain modeling process today — whether through ChatGPT, Claude, a fine-tuned model, or a purpose-built system — here's how to apply the Candidate Lifecycle manually:</p>
<p><strong>Mark everything the AI produces as provisional.</strong> Don't copy AI-generated domain concepts directly into your specification. Create a separate "candidates" section. Each candidate gets an ID, the AI's proposed classification (aggregate, event, policy, etc.), the AI's reasoning if available, and a status: pending, confirmed, overridden, or rejected.</p>
<p><strong>Review candidates in bounded context order.</strong> Start with context boundaries. Then aggregates within each context. Then events and commands within each aggregate. Confirmation cascades downward — confirming a bounded context doesn't confirm its aggregates, but rejecting a bounded context rejects everything inside it.</p>
<p><strong>Document every override and rejection.</strong> The override rationale ("this was proposed as an invariant but it's actually a policy — it reacts to events rather than constraining state") is more valuable than the confirmation rationale. Overrides and rejections are where your domain understanding diverges from the AI's pattern matching. They're the signal.</p>
<p><strong>Track which model version produced which candidates.</strong> When you update your AI model or change your prompting approach, you need to know which parts of your domain specification were produced under the previous configuration. Provenance doesn't need to be sophisticated — "GPT-4o, March 2026, prompt v2" is sufficient for manual tracking.</p>
<p><strong>Run the gap report after confirmation.</strong> The gap report (<a href="https://listenrightmeow.hashnode.dev/the-gap-report-ddds-missing-feedback-loop">Post 5</a>) evaluates the confirmed specification, not the raw AI output. Gaps found post-confirmation are real gaps in the architect's curated model — not noise from unreviewed AI proposals.</p>
<hr />
<h2>What Comes Next</h2>
<p>The Candidate Lifecycle establishes the trust boundary. But it assumes the candidates are at least structurally valid — that an aggregate is an aggregate, that a policy is a policy, that the AI's classification is correct even if the domain judgment is wrong.</p>
<p>What happens when the classification itself is wrong? When the AI proposes something that passes every structural check, looks correct in every gap report, and produces a domain model that appears complete — but the behavioral semantics are fundamentally broken?</p>
<p>That's the Classification Gap. Post 7.</p>
<hr />
<p><em>This is Post 6 of a series on DDD, AI, and the methodology that emerged from practicing both rigorously.</em> <a href="https://listenrightmeow.hashnode.dev/the-gap-report-ddds-missing-feedback-loop"><em>Post 5</em></a> <em>delivered the gap report deep dive and the</em> <a href="https://github.com/listenrightmeow/signal-driven-development"><em>SDD repository</em></a><em>. The series continues with the Classification Gap in Post 7.</em></p>
]]></content:encoded></item><item><title><![CDATA[The Gap Report: DDD's Missing Feedback Loop]]></title><description><![CDATA[In Post 4, I introduced Signal-Driven Development and its core claim: DDD has never had a definition of done. SDD provides one — zero unresolved gaps across a structured convergence process.
But I lef]]></description><link>https://listenrightmeow.hashnode.dev/the-gap-report-ddds-missing-feedback-loop</link><guid isPermaLink="true">https://listenrightmeow.hashnode.dev/the-gap-report-ddds-missing-feedback-loop</guid><category><![CDATA[DDD]]></category><category><![CDATA[Architecture Design]]></category><category><![CDATA[#Domain-Driven-Design]]></category><category><![CDATA[software architecture]]></category><category><![CDATA[signal-driven-development]]></category><dc:creator><![CDATA[Mike Dyer]]></dc:creator><pubDate>Sun, 22 Mar 2026 05:03:38 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/69ae573286766ac3a67a2e78/bbe8da4a-6928-4bbf-be9c-ba89583f063b.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In <a href="https://listenrightmeow.hashnode.dev/introducing-signal-driven-development">Post 4</a>, I introduced Signal-Driven Development and its core claim: DDD has never had a definition of done. SDD provides one — zero unresolved gaps across a structured convergence process.</p>
<p>But I left the gap report itself as a concept. This post makes it concrete. What does a gap report actually look like? What does it measure? How does the three-pass convergence trajectory work when you're sitting in front of a real domain specification?</p>
<p>I'm also releasing the <a href="https://github.com/listenrightmeow/signal-driven-development">SDD repository</a> — templates for gap reports, resolution logs, domain specifications, and architecture palettes, plus a complete worked example showing three-pass convergence on a fictional domain. Grab the templates and run a pass on your own system. That's not a suggestion — it's the fastest way to understand whether SDD solves a problem you have.</p>
<hr />
<h2>Anatomy of a Gap Report</h2>
<p>A gap report evaluates a domain specification against four categories. Each gap is a question the specification hasn't answered.</p>
<p><strong>Structural Gaps (SG)</strong> are missing or malformed elements. These are binary — the element exists or it doesn't. An aggregate without invariants. A command that doesn't produce a domain event. A bounded context with no declared relationships to other contexts.</p>
<p>Structural gaps are the easiest to identify and the most dangerous to ignore. An aggregate without invariants is a consistency boundary that enforces nothing — it's a data structure with a misleading name. <a href="https://www.amazon.com/Domain-Driven-Design-Tackling-Complexity-Software/dp/0321125215">Evans</a> is explicit about this: the aggregate exists to protect invariants. If there are no invariants, there is no aggregate.</p>
<p><strong>Heuristic Gaps (HG)</strong> are patterns that violate established DDD principles. Unlike structural gaps, these aren't binary — they're threshold-based. <a href="https://www.amazon.com/Implementing-Domain-Driven-Design-Vaughn-Vernon/dp/0321834577">Vernon's</a> small aggregate heuristic suggests no more than six commands per aggregate. <a href="https://www.domainlanguage.com/ddd/">Evans's</a> bounded context principles suggest no more than three shared terms across contexts before you question whether the boundary is real. Saga step counts beyond five suggest decomposition.</p>
<p>Every heuristic has a measurable default grounded in published DDD literature. Every default is overridable — because every domain has legitimate reasons to deviate. The gap report doesn't penalize deviation. It forces you to acknowledge and document it. The difference between an architect who exceeds a heuristic intentionally and one who exceeds it accidentally is the documentation of the decision.</p>
<p><strong>Language Gaps (LG)</strong> are ambiguities in the ubiquitous language. The same term used with different meanings across contexts without explicit declaration. Unnamed concepts referenced in multiple places. Overloaded terms where a single word carries two distinct domain meanings.</p>
<p>Language gaps are subtle and consequential. When "treatment" means both "the full clinical encounter" and "a single medical intervention," every conversation about treatments becomes ambiguous. The code will resolve the ambiguity — but it will resolve it silently, and different developers will resolve it differently.</p>
<p><strong>Decision Gaps (DG)</strong> are architectural choices that haven't been made or haven't been documented. A bounded context boundary that could reasonably be drawn in two places. A relationship type that's assumed but not declared. A scope decision that's implicit rather than explicit.</p>
<p>Decision gaps are the gaps that define your architecture. The structural gaps and heuristic violations are usually mechanical to fix. The decision gaps require judgment, tradeoff reasoning, and the willingness to commit to a position and document why.</p>
<hr />
<h2>What a Gap Looks Like</h2>
<p>Here's a structural gap from a real worked example — a veterinary clinic domain specification:</p>
<blockquote>
<p><strong>SG-01: Appointment aggregate has zero invariants</strong></p>
<p><strong>Severity</strong>: Error <strong>Rule</strong>: Aggregates must protect at least one invariant — an aggregate without invariants has no consistency boundary to enforce. <strong>Specification element</strong>: Appointment aggregate in Scheduling context <strong>Analysis</strong>: Appointment defines four commands and four events but no invariants. What prevents double-booking the same time slot? What prevents checking in a cancelled appointment? What prevents rescheduling to a time in the past? Without invariants, the Appointment aggregate is a data container, not a consistency boundary. <strong>Recommendation</strong>: Define invariants. At minimum: (1) No two appointments for the same veterinarian may overlap in time. (2) Appointment status must follow a valid lifecycle. (3) Rescheduled time must be in the future.</p>
</blockquote>
<p>Notice the structure. The gap states what was measured (<strong>zero invariants</strong>), why it matters (<strong>no consistency boundary</strong>), what the consequences are (<strong>double-booking, invalid transitions</strong>), and what to do about it (<strong>define specific invariants</strong>). It's not a vague warning. It's an actionable diagnostic that tells you exactly where to look and what question to answer.</p>
<p>Here's a heuristic gap from the same domain:</p>
<blockquote>
<p><strong>HG-04: Zero sagas in a domain with multi-step processes</strong></p>
<p><strong>Severity</strong>: Warning <strong>Rule</strong>: Domains with cross-aggregate, multi-step processes typically require at least one saga. <strong>Metric</strong>: 0 sagas, 3 policies <strong>Analysis</strong>: The full visit lifecycle spans multiple aggregates across multiple contexts: Appointment → Visit → Treatment → Invoice. This is a multi-step process with potential failure points. What if treatment is started but the visit is never closed? What if the visit is closed but invoice generation fails? Policies handle the happy path. There's no compensation or failure handling. <strong>Recommendation</strong>: Evaluate the visit lifecycle as a saga candidate with compensation for each step.</p>
</blockquote>
<p>And here's a decision gap:</p>
<blockquote>
<p><strong>DG-02: How does pricing work?</strong></p>
<p><strong>Severity</strong>: Error <strong>Analysis</strong>: PricingService "calculates line item prices based on treatment codes and clinic pricing rules." But there's no pricing model, no price list aggregate, no pricing configuration. Where do prices come from? Are they per-treatment-code? Per-veterinarian? Time-based? The service exists but its data model is undefined. <strong>Recommendation</strong>: Define a PriceList aggregate with pricing rules. Determine whether pricing is static or dynamic.</p>
</blockquote>
<p>That decision gap is an error, not a warning, because it describes a domain concept that other parts of the specification depend on but that doesn't exist. Invoice generation references pricing. Pricing references nothing. There's a hole in the model where a concept should be.</p>
<hr />
<h2>The Three-Pass Trajectory</h2>
<p>The gap report becomes powerful across passes. Here's what convergence actually looks like, from the veterinary clinic worked example:</p>
<p><strong>Pass 1</strong>: 18 gaps identified. 5 errors, 13 warnings. The domain specification has all six aggregates named and placed. It has events flowing between contexts. It looks like a domain model. But three of six aggregates have zero invariants. The Treatment aggregate is a sequential pipeline pretending to be an aggregate. There's no saga handling a multi-step visit lifecycle. Billing has no pricing model. The veterinarian schedule doesn't exist as a domain concept.</p>
<p>Every gap gets resolved. The resolution log documents what was decided and why. 16 accepted as recommended, 2 accepted with modification, 0 rejected. The specification grows: invariants go from 5 to 12, a saga is introduced, two new aggregates are added, a language overload is fixed.</p>
<p><strong>Pass 2</strong>: 5 gaps. Zero errors, 5 warnings. The structural problems are gone. What remains are refinement concerns — the saga needs a timeout, the walk-in path has an event ordering dependency, a pricing snapshot rule needs to be explicit. These are the decisions that define the architecture's resilience, not its structure.</p>
<p>All five resolved. The invariant count climbs from 12 to 18. Every aggregate now has at least one invariant. Every cross-context relationship is declared with a type. Every scope decision is documented.</p>
<p><strong>Pass 3</strong>: Zero gaps. Zero errors. Zero warnings. Converged.</p>
<p>The trajectory — 18 → 5 → 0 — is the signal. It tells you the methodology is working. Each pass reduces the gap count because the previous pass's resolutions addressed the root causes, not just the symptoms. When you fix the aggregate that has no invariants, you also fix the downstream gaps that depended on that aggregate having a consistency boundary. Foundational decisions resolve first; dependent decisions cascade.</p>
<p>If Pass 2 had produced more gaps than Pass 1, that would be the most important signal: the specification is diverging, not converging. Something is structurally wrong — probably a foundational boundary decision that's incorrect, causing every resolution to introduce new inconsistencies. Non-convergence means stop, revisit the boundaries, and restart the pass.</p>
<hr />
<h2>The Resolution Log</h2>
<p>The gap report identifies questions. The resolution log records answers. Every resolution documents three things:</p>
<p><strong>The decision</strong>: What was chosen and what was rejected. An aggregate without invariants can be fixed by adding invariants (if it's a real consistency boundary) or dissolved (if it isn't). The resolution records which path was taken.</p>
<p><strong>The rationale</strong>: Why this decision was made. This is the most valuable artifact SDD produces. Six months from now, when someone asks "why is this a saga instead of a policy chain?" the resolution log has the answer — with the gap that prompted the question, the alternatives considered, and the reasoning that led to the current design.</p>
<p><strong>The structural impact</strong>: What changed in the specification. "+1 saga, +1 event, +1 invariant." This makes the change traceable and auditable. Every element in the final specification traces back to either the initial extraction or a specific gap resolution.</p>
<p>The resolution log is the architecture decision record that DDD always needed but never formalized at the domain modeling level. <a href="https://adr.github.io/">ADRs</a> capture decisions about technology choices and system-level architecture. Resolution logs capture decisions about domain model structure — why this aggregate exists, why this boundary is drawn here, why this invariant matters.</p>
<hr />
<h2>Running Your Own Pass</h2>
<p>You don't need tooling to try this. You need a domain specification and the gap report template.</p>
<p><strong>Step 1</strong>: Pick a bounded context in your system. Write the domain specification — name every aggregate, every command, every event, every invariant, every policy, every saga. Make every relationship explicit. If you can't name it, it's a gap.</p>
<p><strong>Step 2</strong>: Run the gap report against it. For each aggregate, ask: does it have invariants? For each command, ask: does it produce an event? For each bounded context, ask: are its relationships declared? Check the heuristic thresholds — command density, term overlap, saga step count. Look for language overloads and undocumented decisions.</p>
<p><strong>Step 3</strong>: Write the resolution log. For every gap, decide: change the model or document why the current design is intentional. Record the rationale.</p>
<p><strong>Step 4</strong>: Update the specification with the resolutions and run the gap report again. The gap count should drop. If it does, you're converging. If it doesn't, revisit your boundary decisions.</p>
<p>The <a href="https://github.com/listenrightmeow/signal-driven-development">SDD repository</a> has everything you need — templates for all four artifacts and a complete worked example showing three-pass convergence. The veterinary clinic example walks through 18 gaps across three passes with full resolution rationale for every decision.</p>
<hr />
<h2>Why This Matters</h2>
<p>The gap report solves a problem that every DDD practitioner has felt but few have named: the anxiety of not knowing whether the model is done.</p>
<p>You finish a domain modeling session. The event storming board is covered in stickies. The bounded contexts feel right. The aggregates have names. But there's a nagging uncertainty — did we miss something? Are the boundaries correct? Is that aggregate doing too much? Is that policy actually a saga?</p>
<p>Without a gap report, the only way to answer those questions is experience. The architects who've seen dozens of domain models can spot the patterns. The architects who haven't can't — and they won't know what they missed until implementation reveals it.</p>
<p>The gap report makes the experienced architect's intuition explicit, measurable, and transferable. It asks the questions that a senior DDD practitioner would ask. It flags the patterns that Evans, Vernon, and Brandolini documented. It forces the decisions that matter into the open where they can be examined.</p>
<p>Each gap is the question an experienced practitioner would ask. SDD asks it for you.</p>
<hr />
<h2>What Comes Next</h2>
<p>Post 5 gives you the methodology to try. The <a href="https://github.com/listenrightmeow/signal-driven-development">repository</a> gives you the tools.</p>
<p>But the gap report revealed something I didn't anticipate when I first built this process. When AI enters the domain modeling pipeline — when the specifications aren't authored by humans but generated by language models — a new category of failure emerges. Structurally valid models that are semantically wrong. Patterns that pass every gap report check but misrepresent the domain's actual behavior.</p>
<p>Post 6 introduces the first pattern that SDD surfaced about AI-mediated domain modeling: the Candidate Lifecycle.</p>
<hr />
<p><em>This is Post 5 of a series on DDD, AI, and the methodology that emerged from practicing both rigorously.</em> <a href="https://listenrightmeow.hashnode.dev/introducing-signal-driven-development"><em>Post 4</em></a> <em>introduced Signal-Driven Development. The series continues with AI-mediated domain modeling patterns starting in Post 6.</em></p>
]]></content:encoded></item><item><title><![CDATA[Introducing Signal-Driven Development]]></title><description><![CDATA[Not "done" in the project management sense — not "the sprint ended" or "the stakeholders signed off." Done in the engineering sense. Structurally complete. Semantically consistent. Ready for implement]]></description><link>https://listenrightmeow.hashnode.dev/introducing-signal-driven-development</link><guid isPermaLink="true">https://listenrightmeow.hashnode.dev/introducing-signal-driven-development</guid><category><![CDATA[DDD]]></category><category><![CDATA[Architecture Design]]></category><category><![CDATA[#Domain-Driven-Design]]></category><category><![CDATA[software architecture]]></category><category><![CDATA[signal-driven-development]]></category><dc:creator><![CDATA[Mike Dyer]]></dc:creator><pubDate>Sat, 21 Mar 2026 19:41:04 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/69ae573286766ac3a67a2e78/641c0913-21f3-4c49-b743-9ebb344fa77c.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Not "done" in the project management sense — not "the sprint ended" or "the stakeholders signed off." Done in the engineering sense. Structurally complete. Semantically consistent. Ready for implementation with confidence that the architecture will hold.</p>
<p><a href="https://www.amazon.com/Domain-Driven-Design-Tackling-Complexity-Software/dp/0321125215">Evans</a> never answered this question. Neither did <a href="https://www.amazon.com/Implementing-Domain-Driven-Design-Vaughn-Vernon/dp/0321834577">Vernon</a>, <a href="https://www.eventstorming.com/">Brandolini</a>, or anyone else in the DDD community. And it's not because the question is unimportant — it's because the traditional answer was always implicit. You knew the model was done when the team stopped finding new insights during knowledge crunching. When the conversations with domain experts stopped producing surprises. When the event storming board stabilized.</p>
<p>That's not a definition of done. That's a feeling.</p>
<p>I spent the better part of a year doing rigorous domain modeling across multiple products — complex domains with regulatory concerns, event-sourced architectures, cross-context dependencies, and temporal behaviors that don't fit neatly into Evans's spatial model. I did this work as a solo practitioner with an AI collaborator, using the approach I described in the first three posts of this series.</p>
<p>What emerged wasn't just a set of domain models. It was a methodology. One with a measurable definition of done, a repeatable convergence process, and a feedback loop that makes domain modeling adversarial in the best sense of the word.</p>
<p>I'm calling it Signal-Driven Development.</p>
<hr />
<h2>The Core Problem: DDD Has No Feedback Loop</h2>
<p>Domain-Driven Design gives you an extraordinary vocabulary for modeling complex systems. Bounded contexts. Aggregates. Domain events. Policies. Sagas. The building blocks are precise, expressive, and battle-tested across two decades of practice.</p>
<p>What DDD doesn't give you is a way to know when you've used them correctly.</p>
<p>Consider the typical DDD workflow. You do knowledge crunching — workshops, event storming sessions, whiteboard conversations with domain experts. You iterate on the model. You refine bounded context boundaries. You identify aggregates and their invariants. At some point, someone says "I think we're good" and the team moves to implementation.</p>
<p>But "I think we're good" is a subjective assessment. There's no structural verification. No way to measure whether the model is complete, whether the boundaries are consistent, whether the heuristics that <a href="https://www.amazon.com/Domain-Driven-Design-Tackling-Complexity-Software/dp/0321125215">Evans</a> and <a href="https://www.amazon.com/Implementing-Domain-Driven-Design-Vaughn-Vernon/dp/0321834577">Vernon</a> established are actually being honored. The model might have an aggregate with twelve commands and no invariants — a consistency boundary that enforces nothing. It might have two bounded contexts sharing fifteen terms with identical definitions — contexts that aren't actually bounded. It might have a policy that emits six commands in response to a single event — a stateless reaction doing the work of a saga.</p>
<p>These aren't obscure edge cases. They're the gaps that every experienced DDD practitioner has learned to spot through years of pattern recognition and hard-won intuition. The gaps that junior architects miss entirely. The gaps that AI-generated models introduce systematically, because an LLM has no intuition — only statistical pattern completion.</p>
<p>DDD needs a feedback loop. Not a checklist. A diagnostic system that examines a domain specification structurally, measures it against the heuristics that the DDD community has established over twenty years, and produces a report that tells you exactly what's incomplete, what's inconsistent, and what violates the principles you claim to follow.</p>
<p>That feedback loop is what I'm calling a <strong>gap report</strong>. And the methodology built around it is Signal-Driven Development.</p>
<hr />
<h2>Three-Pass Convergence</h2>
<p>SDD's core mechanic is structured convergence through iterative gap resolution. The process works like this:</p>
<p><strong>Pass 1</strong> produces a domain specification — the full structural model of your domain expressed in DDD building blocks. Bounded contexts, aggregates, domain events, commands, policies, sagas, projections, invariants, value objects, domain services. Everything named, everything placed, every relationship explicit. The gap report for Pass 1 identifies what the specification cannot answer: missing invariants, boundary violations, heuristic threshold breaches, methodology process gaps. In a complex domain, Pass 1 typically surfaces 20 to 35 gaps.</p>
<p><strong>Pass 2</strong> resolves those gaps. This is where the real architecture happens. You're not generating a model — you're interrogating one. Every gap is a question the specification couldn't answer. Some gaps resolve by adding missing elements (an aggregate without invariants needs invariants, or it needs to be dissolved). Some resolve by restructuring (two contexts with heavy term overlap need a boundary reassessment). Some resolve by making an explicit architectural decision and documenting why. The gap count drops — typically to 5 to 10. If it doesn't drop, the specification is diverging rather than converging, and that divergence is itself a diagnostic signal.</p>
<p><strong>Pass 3</strong> drives to zero. The remaining gaps are usually the hardest — the architectural decisions that require genuine tradeoff reasoning. A saga with seven steps that might need decomposition. A bounded context whose name doesn't match its actual responsibility. An aggregate whose event fan-out suggests it's doing too much. These are the decisions that experienced architects agonize over in whiteboard sessions. SDD forces them into the open by making them measurable.</p>
<p>The definition of done is zero unresolved gaps. Not "zero gaps identified" — gaps will always be identified. Zero <em>unresolved</em> gaps. Every gap has been examined, and for each one, the architect has either changed the model to address it or documented why the current design is intentional. The resolution is the artifact, not the absence of the finding.</p>
<p>Some domains require four or five passes. The three-pass label describes the typical trajectory, not a hard constraint. The invariant is convergence: each pass must reduce the gap count. If it doesn't, something is structurally wrong with the specification, and that non-convergence is the most important signal the process can produce.</p>
<hr />
<h2>Gap Reports as Diagnostic Signals</h2>
<p>The gap report is the heart of SDD. It's not a test suite. It's not a linter output. It's a structured diagnostic that evaluates a domain specification against three categories of concern.</p>
<p><strong>Structural completeness</strong> asks whether the specification has the elements it needs. Does every aggregate have at least one invariant? Does every bounded context have a clear linguistic boundary? Are there commands without corresponding domain events? Are there domain events that no policy or projection reacts to? These aren't style preferences — they're the structural expectations that <a href="https://www.amazon.com/Domain-Driven-Design-Tackling-Complexity-Software/dp/0321125215">Evans</a> and <a href="https://www.amazon.com/Implementing-Domain-Driven-Design-Vaughn-Vernon/dp/0321834577">Vernon</a> established. An aggregate without invariants isn't a design choice; it's a consistency boundary that enforces nothing.</p>
<p><strong>Heuristic thresholds</strong> measure whether the specification honors the quantitative guidelines the DDD community has developed through practice. Vernon's <a href="https://www.amazon.com/Implementing-Domain-Driven-Design-Vaughn-Vernon/dp/0321834577">small aggregate heuristic</a> suggests no more than six commands per aggregate. Context term overlap beyond three shared definitions suggests insufficient separation. Saga step counts beyond five suggest decomposition is needed. These thresholds aren't arbitrary — they're grounded in two decades of published work by <a href="https://www.domainlanguage.com/ddd/">Evans</a>, <a href="https://www.amazon.com/Implementing-Domain-Driven-Design-Vaughn-Vernon/dp/0321834577">Vernon</a>, <a href="https://www.eventstorming.com/">Brandolini</a>, and others. They're configurable, because every domain has legitimate reasons to deviate. But deviations should be conscious decisions, not invisible accidents.</p>
<p><strong>Methodology process gaps</strong> verify the discipline of the modeling process itself. Does every architectural decision have a documented rationale with at least one rejected alternative? Have gap resolutions been traced to specific changes in the specification? Is the gap count decreasing across passes? These are the meta-rules — the rules about the process, not the model.</p>
<p>The critical insight is that gap reports are <em>signals</em>, not verdicts. A gap doesn't mean the model is wrong. It means the model has a question it hasn't answered. The architect reads the signal, investigates, and either changes the model or documents why the current design is correct. Both outcomes are valid. The gap report's job is to surface the question. The architect's job is to answer it.</p>
<p>This is what makes SDD adversarial in a productive way. The gap report is the colleague who keeps asking "but why?" — not to obstruct, but to force the kind of rigorous reasoning that produces architectures you can defend under scrutiny.</p>
<hr />
<h2>The Architecture Palette</h2>
<p>The third artifact in SDD — alongside the domain specification and the gap report — is the architecture palette. It's a visual projection of the domain specification expressed in DDD building blocks, organized by bounded context, showing the relationships between aggregates, events, commands, policies, and sagas.</p>
<p>The palette serves two purposes. First, it's a communication artifact. A domain specification can be hundreds of elements across dozens of pages. The palette compresses that into a visual map that an architect can hold in working memory. Second, it's a verification surface. Structural patterns that are invisible in a textual specification become obvious in a visual layout — an aggregate that's connected to everything, a bounded context with no outbound events, a saga that spans three contexts when it should span two.</p>
<p>The palette is the thing you put on the wall. The specification is the thing you trust. The gap report is the thing that tells you whether the specification deserves that trust.</p>
<hr />
<h2>What This Looks Like in Practice</h2>
<p>I've run this process across eight product domains over the past year. Not toy examples — production systems with regulatory requirements, event-sourced architectures, and cross-product dependencies. Here's what the convergence trajectory actually looks like.</p>
<p>A typical Pass 1 gap report surfaces 20 to 30 findings. Five to eight are structural errors — missing invariants, aggregates without consistency boundaries, commands that don't produce events. Ten to fifteen are heuristic threshold violations — aggregates with too many commands, contexts with overlapping vocabulary, policies doing the work of sagas. The rest are methodology gaps — architectural decisions made without documented alternatives, gap resolutions without traceability.</p>
<p>Pass 2 resolves all of them. The structural errors are usually straightforward — add the missing invariant, dissolve the aggregate that has no reason to exist, split the policy into a saga. The heuristic violations require judgment — sometimes the threshold is right and the model needs to change, sometimes the domain genuinely requires a larger aggregate and the override needs to be documented. The methodology gaps are discipline — go back and document the reasoning.</p>
<p>Pass 3 finds the residuals. In a complex domain, there are usually two to five gaps that survived Pass 2 — often the ones that require genuine architectural tradeoffs. A bounded context boundary that could reasonably be drawn in two places. A saga decomposition that improves one metric at the cost of another. These are the decisions that define the architecture, and SDD's contribution is forcing them into explicit, documented, measurable resolution rather than leaving them as implicit assumptions buried in the code.</p>
<p>By the end of Pass 3, the gap count is zero. Every structural element has been verified. Every heuristic threshold has been honored or consciously overridden. Every architectural decision has been documented with alternatives considered and rationale recorded.</p>
<p>That's a definition of done.</p>
<hr />
<h2>The Part We Didn't Expect</h2>
<p>I want to be transparent about the intellectual path that led here.</p>
<p>SDD emerged from practice. I didn't start with a methodology and apply it. I started with a problem — how do you do rigorous domain modeling without a room full of people? — and iterated on the process until something repeatable crystallized. The three-pass convergence, the gap report categories, the architecture palette, the definition of done — all of it came from doing the work and noticing what worked.</p>
<p>I didn't research what the DDD community's leading voices were currently publishing until months after the methodology had stabilized. When I finally did — when I read Evans's <a href="https://www.infoq.com/news/2024/03/Evans-ddd-experiment-llm/">Explore DDD 2024 keynote</a>, when I looked at what <a href="https://www.domainlanguage.com/">Domain Language is now focused on</a>, when I read Khononov's work on coupling as a measurable heuristic — the convergence was startling.</p>
<p>Evans is now focused on <a href="https://www.domainlanguage.com/articles/ai-components-deterministic-system/">integrating AI into domain-rich systems</a> while preserving design integrity. His keynote framing — that a trained LLM on a ubiquitous language is effectively a bounded context — is the same conclusion I reached independently while building the constraints that keep AI output within a closed DDD vocabulary. Same destination, completely different paths. He's since published a <a href="https://www.domainlanguage.com/articles/context-mapping-an-ai-based-component/">follow-up on context mapping with AI-based components</a> — drawing an Anti-Corruption Layer between deterministic application code and probabilistic LLM output. We built the same pattern independently.</p>
<p>Khononov's <a href="https://www.informit.com/store/balancing-coupling-in-software-design-universal-design-9780137353538"><em>Balancing Coupling in Software Design</em></a> (Addison-Wesley, 2024) formalizes coupling as a measurable design heuristic with an optimizable function — the same pattern as SDD's threshold model. Take the qualitative principles Evans established, make them quantitative, set configurable thresholds, measure against them. He arrived at it through academic rigor. I arrived at it through building a system that needed to verify domain models automatically.</p>
<p><a href="https://2026.dddeurope.com/">DDD Europe 2026</a> has <a href="https://ddd.academy/accelerate-your-strategic-design-with-llms/">workshops on accelerating strategic design with large language models</a> — Thomas Coopman's two-day session in Antwerp this June. The community is mainstreaming the intersection of AI and DDD as a topic. We've been living in that intersection for months.</p>
<p>I'm not claiming priority. I'm observing convergence. When independent practitioners arrive at the same conclusions from different starting points, it's not a coincidence — it's the problem asserting its own shape. The DDD community is converging on the need for measurable heuristics, AI-mediated modeling, and structural verification because those are the problems that surface when you take DDD seriously at scale. Whether you start from Evans's theory or from a solo practitioner's frustration, the same walls appear.</p>
<p>The domain specification that emerged from three-pass convergence was structurally complete enough to verify context provenance — without a single line of implementation code. The design was the proof.</p>
<p>That's not a theoretical claim. That's a measured result from applying this methodology to a real product domain. The specification produced by SDD's convergence process contained enough structural information that compliance verification could be performed against the domain model directly — before any runtime existed to test against. The architecture didn't need to be built to be verified. It needed to be modeled rigorously enough that verification was a projection of the model itself.</p>
<hr />
<h2>What SDD Is Not</h2>
<p>SDD is not a replacement for DDD. It's an extension. <a href="https://www.amazon.com/Domain-Driven-Design-Tackling-Complexity-Software/dp/0321125215">Evans</a> gave us the building blocks — the vocabulary for decomposing complex domains into bounded contexts, aggregates, and domain events. <a href="https://www.amazon.com/Implementing-Domain-Driven-Design-Vaughn-Vernon/dp/0321834577">Vernon</a> gave us the implementation patterns — the tactical guidance for turning those building blocks into working code. <a href="https://www.eventstorming.com/">Brandolini</a> gave us the discovery vocabulary — Event Storming as a method for collaborative knowledge crunching. <a href="https://narrativedriven.org/">Narrative-Driven Development</a> gave us the temporal dimension — the recognition that domains exist in time, not just in space.</p>
<p>SDD gives DDD a feedback loop and a definition of done.</p>
<p>The gap report doesn't replace knowledge crunching. It makes knowledge crunching measurable. The three-pass convergence doesn't replace architectural intuition. It forces intuition into the open where it can be examined, challenged, and documented. The architecture palette doesn't replace event storming boards. It persists them.</p>
<p>If you practice DDD, you can practice SDD tomorrow. The process is the same — model the domain, interrogate the model, refine the model. SDD adds structure to the interrogation and a measurable endpoint to the refinement.</p>
<hr />
<h2>What Comes Next</h2>
<p>The templates are coming — gap report templates, architecture palette formats, domain specification structures. Everything you need to run a three-pass convergence on your own domain. I'll share them through a public repository designed for practitioners who want to try SDD on a real project, not a tutorial exercise.</p>
<p>Post 5 will go deep on the gap report itself — what the categories look like, how the three-pass trajectory works in detail, and how to read the signals that a gap report produces. That's where the methodology becomes concrete enough to apply.</p>
<p>But the gap report is just the beginning. SDD surfaced patterns I didn't anticipate — patterns about what happens when AI enters the domain modeling process, about what structural verification reveals when it catches problems that humans can't see, about what a rigorous feedback loop does to the quality of architectural decisions over time.</p>
<p>Those patterns are the subject of the rest of this series.</p>
<p>SDD doesn't replace DDD. It gives DDD a definition of done.</p>
<hr />
<p><em>This is Post 4 of a series on DDD, AI, and the methodology that emerged from practicing both rigorously. Posts 1–3 established the solo-builder problem, the AI collaboration model, and the vocabulary gap. The series continues with a deep dive into the gap report in Post 5.</em></p>
]]></content:encoded></item><item><title><![CDATA[The Coherence Problem]]></title><description><![CDATA[Everyone's celebrating velocity. Nobody's talking about what happens when you succeed.
Everyone's talking about AI making solo developers faster.
Ship a SaaS in a weekend. Replace your junior devs. Bu]]></description><link>https://listenrightmeow.hashnode.dev/the-coherence-problem</link><guid isPermaLink="true">https://listenrightmeow.hashnode.dev/the-coherence-problem</guid><category><![CDATA[AI]]></category><category><![CDATA[software architecture]]></category><category><![CDATA[Software Engineering]]></category><dc:creator><![CDATA[Mike Dyer]]></dc:creator><pubDate>Mon, 16 Mar 2026 06:42:13 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/69ae573286766ac3a67a2e78/e7bd96fe-d3cf-4040-ad82-6f8f19d6a190.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>Everyone's celebrating velocity. Nobody's talking about what happens when you succeed.</em></p>
<p>Everyone's talking about AI making solo developers faster.</p>
<p>Ship a SaaS in a weekend. Replace your junior devs. Build an MVP before lunch.</p>
<p>Cool. But nobody's talking about what happens when you <em>succeed</em>.</p>
<hr />
<h2>The Week That Changed My Framing</h2>
<p>I spent the last week maintaining a multi-product software ecosystem — alone, with AI as my architecture partner. Not generating code. Not shipping features. Managing the <em>coherence</em> of a system that's grown complex enough to behave like a team-scale project.</p>
<p>One architectural decision triggered documentation updates across 21 pages. A single boundary change cascaded through 5 product requirement documents, 4 platform-level architecture decision records, and a dozen cross-references that all needed to stay internally consistent.</p>
<p><em><strong>That's not a speed problem. That's a coherence problem. And it's the problem nobody warns you about when they celebrate the solo AI dev.</strong></em></p>
<hr />
<h2>Speed vs. Coherence</h2>
<p>Here's what I've learned building this way:</p>
<blockquote>
<p><strong>AI doesn't make you faster. It makes you capable of holding more complexity in working memory than one person should be able to.</strong></p>
</blockquote>
<p>That's a fundamentally different thing.</p>
<p>Speed means you do the same work in less time. Coherence means you do work that wasn't possible before — maintaining the kind of cross-system architectural consistency that used to require a team of people whose entire job was keeping the model straight.</p>
<p>When I work with AI on architecture, the value isn't autocomplete. It's that my AI partner can hold the full specification of a multi-product ecosystem in context while I make a decision — and then execute the downstream implications of that decision across every artifact it touches. In real time. Without drift.</p>
<p>A human team doing this would need an architect who understands the decision, a technical writer updating the docs, a project manager tracking the cascade, and a QA engineer verifying consistency.</p>
<p>I have one terminal and a conversation.</p>
<hr />
<h2>The Part Nobody Warns You About</h2>
<p>But here's the part the "AI productivity" narrative misses entirely:</p>
<p><em>The system got complex enough that I had to build a new service just to manage the dependency graph between my own architectural artifacts.</em></p>
<p>Read that again.</p>
<p>I didn't build it because I wanted to. I built it because the cascade problem — one change rippling through dozens of documents — became its own engineering challenge. The AI partnership made me productive enough to create a system that now requires its own tooling to maintain.</p>
<p>That's not a failure. That's what real architecture looks like. It's just that most solo developers never get there because without AI, the complexity ceiling hits you long before the architecture demands it.</p>
<hr />
<h2>The Question Nobody's Asking</h2>
<p>The discourse right now is fixated on velocity. How fast can you ship. How many lines of code per day. How quickly you can go from idea to deployment.</p>
<p>I'd argue the more interesting question is:</p>
<p><em><strong>How much architectural integrity can one person sustain?</strong></em></p>
<p>Because the companies that win long-term aren't the ones that shipped fastest. They're the ones whose architecture held up when it mattered — when the edge cases arrived, when the compliance audit landed, when the system had to evolve without a rewrite.</p>
<p>If AI is genuinely changing what a solo builder can accomplish, the interesting frontier isn't "build it faster." It's build it with the kind of structural rigor that used to require a team standing behind you.</p>
<hr />
<p>The solo dev + AI revolution is real. I'm living it.</p>
<p>But if all you're using AI for is speed, you're solving the wrong problem. Speed was never the bottleneck. The bottleneck was always one person trying to hold an entire system in their head without losing the thread.</p>
<p>AI doesn't speed up that work. It makes it possible for the first time.</p>
<p><strong>That's the unlock. Not velocity. Coherence.</strong></p>
]]></content:encoded></item><item><title><![CDATA[The Single-Seat Architect]]></title><description><![CDATA[A lot of professionals aren't making it back into the market.
That's not a prediction. That's what's happening right now. And if you're in this industry, you already know someone it's happened to. You]]></description><link>https://listenrightmeow.hashnode.dev/the-single-seat-architect</link><guid isPermaLink="true">https://listenrightmeow.hashnode.dev/the-single-seat-architect</guid><category><![CDATA[AI]]></category><category><![CDATA[software development]]></category><category><![CDATA[software architecture]]></category><dc:creator><![CDATA[Mike Dyer]]></dc:creator><pubDate>Mon, 16 Mar 2026 06:14:10 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/69ae573286766ac3a67a2e78/d926737d-aef3-4188-8e87-f8a15b1abac6.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>A lot of professionals aren't making it back into the market.</p>
<p>That's not a prediction. That's what's happening right now. And if you're in this industry, you already know someone it's happened to. You probably just haven't said it out loud yet.</p>
<p>So let me say it.</p>
<hr />
<h2>How We Got Here</h2>
<p>A non-trivial percentage of CEOs discovered ChatGPT sometime in the last two years. They typed a prompt. They got something back that looked like a product spec, or a business plan, or a chunk of code. And something broke in their brain.</p>
<p>They didn't learn what AI can do. They learned what AI <em>looks like</em> it can do. And that's a dangerous distinction — because now they think building software is easy. They think the reason it was hard before was the developers, not the problem. They think the $200/hr architect was overhead, not the only person in the room who understood why the last three projects failed.</p>
<p>This isn't new. Developers have been treated like they were a dime-a-dozen for fifteen years. Every outsourcing wave, every bootcamp boom, every "we'll just hire three juniors instead of one senior" decision — it all came from the same place: the belief that software is a labor problem, not a thinking problem.</p>
<blockquote>
<p><strong>AI didn't create that delusion. It compounded it.</strong></p>
</blockquote>
<hr />
<h2>What Actually Changed</h2>
<p>Now the thinking goes: if AI can generate code, why do I need a team? If ChatGPT can write a PRD, why do I need a product person? If I can get a working prototype in a weekend, why was that project estimated at six months?</p>
<p>And here's the thing — they're not entirely wrong that the landscape changed. They're just catastrophically wrong about <em>what changed</em>.</p>
<p>What changed isn't that software got easier to build. What changed is that the floor collapsed. The baseline tasks that used to require a human — writing boilerplate, translating specs into code, wiring up CRUD endpoints — those aren't jobs anymore. They're prompts.</p>
<p>The hard problems are exactly as hard as they were before AI. System design. Ambiguity resolution. Figuring out why the requirements contradict each other before you've built the wrong thing for six months. Understanding regulatory constraints. Knowing when the architecture will collapse under scale and why. Making trade-offs between consistency and availability when a product person is staring at you waiting for a yes.</p>
<p>AI can't do any of that. Not because the models aren't good enough yet. Because those problems require judgment, context, and the kind of earned intuition that only comes from having been wrong enough times to recognize the shape of the next mistake before you make it.</p>
<hr />
<h2>The Other Side</h2>
<p>I know this because I lived the other side of it.</p>
<p>I spent three quarters unemployed last year. Twenty-plus years of building software, and I couldn't get back in. Companies everywhere were claiming they were hiring, but I honestly don't think 90% of them were. Many were preparing for headcount budgets that never materialized. Others were taking advantage of a flooded market, low-balling experienced engineers 50 to 70 percent below their market rate — and finding takers, because people were desperate.</p>
<p>The market was nasty. From what I'm hearing, it still is.</p>
<p>And while I was in that gap, I watched the AI narrative accelerate in real time. Every week, another LinkedIn post about shipping a product in a weekend. Another CEO tweeting about replacing their engineering team. Another think piece about how developers need to "adapt or die" — written by someone who's never had to adapt to anything harder than a new iPhone.</p>
<hr />
<h2>Who's Actually Coming Back</h2>
<p>But here's what I noticed from the other side.</p>
<p>The professionals starting to make it back aren't the ones with the deepest specialization. They're the ones with the widest experience. The developer who also understood product. The backend engineer who also did infrastructure. The architect who'd sat in sales calls and heard what the customer actually needed versus what the ticket said.</p>
<p>They didn't just learn to code. They learned to think across boundaries. And now, paired with AI, those people are devastating.</p>
<p>A new role is settling under that collapsing floor. I'd call it the <strong>single-seat architect</strong> — someone who spent years accumulating lateral experience across product, engineering, data, infrastructure, and operations, and who now discovers that AI gives them the leverage to build entire products on their own.</p>
<blockquote>
<p>Not prototypes. Not MVPs held together with API calls and prayer. Actual products with real architecture, real domain modeling, real compliance, real coherence across every layer of the stack.</p>
</blockquote>
<p>That's not hype. I'm building this way right now — solo, with AI as my architecture partner. The rigor isn't lesser because there's no team. It's different. AI doesn't replace the people I've worked with over the years. It replaces the limitations of working alone.</p>
<hr />
<h2>The Uncomfortable Part</h2>
<p>The market isn't going to correct for this kindly. There will not be a gentle transition period where displaced specialists reskill into architects over a few months of online courses. Architecture isn't a certification. Lateral thinking isn't a bootcamp. The judgment that makes someone valuable in the AI era was built over years of cross-disciplinary work — product decisions, infrastructure trade-offs, customer conversations, failed projects, recovered projects.</p>
<p><em><strong>You can't speedrun that.</strong></em></p>
<p>So when I hear people say "AI won't replace developers, it'll just change what developers do" — I think that's a comforting lie dressed up as optimism. AI is already replacing developers. What it won't replace is the person who knows which problem to solve, how the pieces fit together, and what's going to break at 3 AM when the architecture can't support what sales just promised.</p>
<p>If that's you — if you spent your career going wide instead of just deep — this is your moment. The market has never valued lateral thinkers more than it does right now, even if it doesn't know how to say that in a job posting yet.</p>
<p>And if that's not you yet — stop learning another framework. Start learning an adjacent discipline. Product. Data. Infrastructure. Compliance. Sales. Anything that forces you to think about software as a <em>system</em> rather than a <em>stack</em>.</p>
<hr />
<p>The floor is gone. Nobody's going to rebuild it for you. Not your employer. Not a bootcamp. Not the next framework.</p>
<p>But if you've spent your career learning how things break — not just how they're built — you don't need a floor. You never did. The floor was for people who needed something to stand on. You're the person other people called when the floor gave out.</p>
<p>That hasn't changed. The market just forgot for a minute.</p>
<p><em><strong>Remind them.</strong></em></p>
]]></content:encoded></item><item><title><![CDATA[The Reactive Path Has No Vocabulary]]></title><description><![CDATA[I've been practicing Domain-Driven Design for over a decade. I've built event-sourced systems. I've implemented CQRS. I've drawn bounded context boundaries, defined aggregates, modeled domain events w]]></description><link>https://listenrightmeow.hashnode.dev/the-reactive-path-has-no-vocabulary</link><guid isPermaLink="true">https://listenrightmeow.hashnode.dev/the-reactive-path-has-no-vocabulary</guid><category><![CDATA[#Domain-Driven-Design]]></category><category><![CDATA[DDD]]></category><category><![CDATA[software architecture]]></category><category><![CDATA[Solo Developer]]></category><category><![CDATA[System Design]]></category><dc:creator><![CDATA[Mike Dyer]]></dc:creator><pubDate>Tue, 10 Mar 2026 09:45:42 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/69ae573286766ac3a67a2e78/7c1d6bd2-77c6-4a18-980e-a49c9b8ecea0.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I've been practicing Domain-Driven Design for over a decade. I've built event-sourced systems. I've implemented CQRS. I've drawn bounded context boundaries, defined aggregates, modeled domain events with intentional language. I'd have told you, with confidence, that I understood the methodology.</p>
<p>Then I ran a structured assessment on my own knowledge and found a gap I'd been building around for years.</p>
<p>Not a minor gap. Not an edge case or an advanced technique I hadn't gotten to yet. A fundamental gap in how I modeled half of every system I'd ever built.</p>
<p>I want to tell you what it was, because I think it reveals something about how DDD actually transfers knowledge — and why that transfer mechanism fails for more people than anyone admits.</p>
<p>Here's the setup. If you've worked with event-driven architecture, you know there are two sides to the system. There's the command path — where requests come in, business rules get enforced, and domain events get emitted. And there's the reactive path — where those events get consumed and the system responds.</p>
<p>My command path was clean. Domain-organized. Rigorous naming. Aggregates enforced invariants. Commands expressed intent in business language. Value Objects carried meaning. If you read the command side of my code, you could understand what the business did.</p>
<p>My reactive path was a different story.</p>
<p>Every function that consumed a domain event was an "event handler." That was the entire vocabulary. Hundreds of functions across multiple systems, all filed under one undifferentiated category. Some of them transformed data into read models. Some of them evaluated conditions and issued commands to other aggregates. Some of them coordinated multi-step workflows across time, tracking state across multiple events. All of them were "event handlers."</p>
<p>I wasn't modeling the reactive side of my systems. I was just coding it.</p>
<blockquote>
<p>I wasn't modeling the reactive side of my systems. I was just coding it.</p>
</blockquote>
<p>The gap became visible when I ran a structured assessment — a systematic walk through DDD's conceptual landscape, starting from philosophical foundations and working forward through tactical patterns, strategic design, and the post-Evans vocabulary that's emerged in the last twenty years.</p>
<p>I did this with an AI collaborator, specifically to pressure-test what I thought I knew against what I actually knew. Not to learn DDD from scratch — to find the holes that years of practice had papered over.</p>
<p>The hole appeared in the reactive path.</p>
<p>The DDD community — particularly through practitioners like Alberto Brandolini's <a href="https://www.eventstorming.com/">Event Storming</a> work and the broader <a href="https://www.narrativedriven.org/article/introduction-to-ndd">Narrative-Driven Development (NDD)</a> movement — has developed precise vocabulary for the reactive side of a system. Three concepts, each with distinct responsibilities:</p>
<p>A <strong>Projection</strong> is pure data transformation. An event arrives, data moves into a read model. No business logic. No decisions. Just reshaping data for consumption.</p>
<p>A <strong>Policy</strong> is a decision point. "When X happens, evaluate a condition, and if it holds, issue command Y." One event in, one command out. Stateless. The bridge between "something happened" and "decide what to do about it."</p>
<p>A <strong>Saga</strong> is coordination across time. "When X happens, do Y, then wait for Z, then if Z succeeds do W, but if Z fails compensate by doing V." Stateful. Tracks progress across multiple events and commands. No single aggregate owns the flow — the saga manages it as a long-running process.</p>
<p>These are not interchangeable. A projection has no business logic. A policy makes a single decision. A saga tracks state across steps. They have different responsibilities, different stateful characteristics, different failure modes, and critically, different testing strategies. Treating them as the same thing — "event handlers" — collapses three distinct architectural concerns into one bucket.</p>
<p>That's exactly what I'd been doing. Across every system I'd built.</p>
<p>And here's the part that stung: I'd read about all three. I'd encountered the terms. I could have given you a textbook definition of each one. But textbook definitions and operational understanding are completely different things. The concepts existed in my reading vocabulary but not in my modeling vocabulary. I knew the words. I didn't use them to think.</p>
<p>This is where Evans's deeper insight about language hits home — the one most practitioners skim past on their way to learning aggregate patterns. Evans embedded a claim in DDD that comes directly from linguistics: the <a href="https://en.wikipedia.org/wiki/Linguistic_relativity">Sapir-Whorf hypothesis</a>. The idea that the language available to you shapes — and constrains — what you can think.</p>
<p>If you don't have a word for something, you will struggle to model it.</p>
<p>I didn't have operational words for the reactive path. So I didn't model it. I coded it. The functions worked. The events got processed. But the structural distinctions that would have made the architecture legible, testable, and evolvable were absent — not because the code was wrong, but because the vocabulary I was thinking in didn't make the distinctions visible.</p>
<blockquote>
<p>If you don't have a word for something, you will struggle to model it. I didn't have operational words for the reactive path. So I didn't model it.</p>
</blockquote>
<p>The moment the vocabulary landed — the moment "event handler" split into "projection," "policy," and "saga" as distinct concepts in my working mental model — I could suddenly see the structure that had been invisible. This function is a projection: it has no business logic, it just reshapes data. That function is a policy: it evaluates a condition and issues a command. That other function is actually a saga: it's tracking state across multiple steps, and the fact that it doesn't have explicit compensation logic is a bug, not a feature.</p>
<p>The assessment didn't teach me something I'd never heard of. It surfaced something I'd heard of but never internalized — because the environment in which I practiced DDD never forced the internalization.</p>
<p>And that's the real finding. Not the gap itself, but why the gap existed.</p>
<p>DDD transmits its deepest knowledge through team osmosis. You sit in a room where a senior practitioner points at a reactive flow and says "that's a policy, not a saga — here's why the distinction matters." You absorb the vocabulary not through reading but through repeated exposure in collaborative contexts where the terms carry operational weight. The naming happens in the room, during modeling sessions, through productive disagreement about what things are.</p>
<p>If you've never been in that room — if you're a solo practitioner, or you work on a team where nobody has that vocabulary, or you crossed into DDD from a team that practiced it implicitly without naming the patterns — those concepts stay in the reading layer. You know the words. You can define them on a whiteboard. You just don't reach for them when you're actually modeling, because they never crossed from knowledge into instinct.</p>
<blockquote>
<p>DDD transmits its deepest knowledge through team osmosis. If you've never been in the room where the naming happens, the concepts stay in the reading layer.</p>
</blockquote>
<p>This isn't a gap that reading fixes. I'd read about policies and sagas. The books were on my shelf. The gap persisted because reading is not the same as being challenged to apply the vocabulary under pressure, in the context of a real system, where someone asks "is that a policy or a saga?" and you have to answer with consequences.</p>
<p>The structured assessment recreated that challenge. It forced me to walk through my own architecture and name things — not in the abstract, but against real systems I'd built. And when the naming forced a distinction I'd never made, the gap was visible.</p>
<p>One gap. Found in days. After being invisible for my entire career.</p>
<p>That tells me something about DDD's accessibility problem that goes beyond the solo-builder framing I've been writing about. The problem isn't just that solo builders lack teams. It's that the methodology's most important knowledge transfers through a channel — collaborative practice with experienced practitioners — that has no fallback. If the channel isn't available, the knowledge doesn't transfer. Not because the practitioner is lazy or the books are bad. Because some knowledge only crystallizes under pressure, and pressure requires a counterpart.</p>
<p>The assessment I ran proved that the counterpart doesn't have to be a human team in a physical room. But it does have to exist. And the fact that this gap persisted — a gap the community had named, published about, and built tooling around — suggests that a lot of practitioners are carrying similar invisible gaps right now.</p>
<p>Not because they haven't studied. Because the transfer mechanism DDD depends on doesn't reach them.</p>
<p>What I'm sitting with now is a follow-up question: if one structured assessment can surface a long-standing gap in a few days, what would a systematic methodology for this kind of diagnostic look like? Not a coaching plan. Not a reading list. A repeatable discipline that any architect can apply to their own understanding and their own systems.</p>
<p>I have some ideas. More on that next.</p>
<p>This is the third post in a series on rigorous domain modeling without a team. Previously: <a href="https://listenrightmeow.hashnode.dev/ddd-has-a-solo-builder-problem-and-nobody-talks-about-it">DDD Has a Solo-Builder Problem</a> and <a href="https://listenrightmeow.hashnode.dev/knowledge-crunching-doesnt-need-a-room">Knowledge Crunching Doesn't Need a Room</a>.</p>
]]></content:encoded></item></channel></rss>