Field Engineering

Enforcing Engineering Discipline in your AI-Native SDLC

Spec-first design, test-driven build, with Claude Code and Lakebase – an agent delivery team that writes the code and a Postgres database that branches

A companion to Anthropic’s The AI-Native SDLC Playbook.

I started writing software in 1992, when the SDLC was waterfall and the coding tools were C/C++, Visual Basic, PowerBuilder and Delphi against a Sybase, SQL Server or Oracle Database. Detailed requirements, big upfront design, coding, manual testing, in that order, on paper before it was in code. It was slow and it was rigid, but every phase existed for a reason, and that reason was: software change is expensive once it exists, a strongly held belief proven wrong once the Agile Manifesto came to be.

By the early 2000s my team moved to XP, Java, the Gang of Four Design Patterns, and object-oriented coding practices. In these early years I was the team's DBA. We were not altogether successful at XP, but we learned a lot, and we continued to stay faithful to Java and the object-oriented engineering disciplines. Within a few years Scrum was born and XP became TDD. Our teams adopted it, and I became the face of that work to our clients, advocating for how my teams wanted to build whenever I had the chance. My job as tech lead / scrum master was to influence the client's tech stack on projects and to champion our preferred way of working (Agile). Later I wore the agile coach hat, helping organizations adopt agile for their own SDLC: backlog creation, agile ceremonies, inceptions, sprint zeros, the whole machinery.

Throughout my career I have been an active participant in the lifecycle's evolution, always advocating for what was on the horizon: agile delivery, adaptive planning, agile design (what we now call evolutionary design), CI/CD, DevOps, and the first move to the cloud. The infrastructure we once built by hand, message queues, async frameworks, the hard stuff, became services. Through it all one thing held: software must be refactorable, always.

Every one of those shifts changed how we shipped, but none of them changed who was accountable. A human wrote the code, and that human understood why the rituals existed. The reviews, the tests, the sign-offs. The ceremonies held because the team committed to holding them.

That is the part that just broke with coding agents. The code author is now an agent, and from my own experience using them I can tell you they behave just like a junior developer: it will delete the test that blocks it, mock the database it was supposed to exercise, or tell you the hermetic test suite passed. Run it for real and those tests were meaningless. The ritual is still there. The discipline is gone. So an AI-native SDLC cannot lean on agent intent through shared discipline and team commitment anymore; it has to enforce the practices, because the agent can produce a working change in minutes and fake its way to green just as fast.

None of this is hypothetical. The studies are now documenting what we all watched in our own sessions: agents deleting the blocking test (Beck 2025) and misreporting what they did (Tang et al. 2026). One company ran 12,000 agent-written tests, all passing; 80 of 2,000 UAT runs still caught real defects the suite missed (Stack 2026). The answer is not better prompting. It is a hard edge the agent cannot fake, and controls it cannot edit.

A control the worker can ignore is not a control

Start from one principle: a control that a worker can ignore is no longer a control. A developer who agrees to write the failing test first can be trusted most of the time to honor it, because the developer understands why it matters. An agent has no such commitment; it follows a rule only while the rule is in front of it, outweighs the competing instructions, and is not cheaper to skip. The rules have to sit somewhere the agent can't change them, and be enforced by a different body.

The discipline itself never changes: write the test first, keep the spec honest, do not fake a pass. What changes is how you enforce it. I see three ways teams try, told apart by what the agent is physically able to ignore.

The first is prompting and hoping. You write the rules down, as sternly as you like, and the model reads them. Most of the kits live here, superpowers and the test-first skills among them. It does raise the floor. But the same worker that read the rule can set it aside the moment the rule stands between the agent and green.

The second is spec, then trust. Spec Kit, BMAD, GSD: get the intent right up front, hand the agent a task list, and trust the build. The spec is strong and the build-time control is weak, which is exactly where discipline evaporates once the agent takes over.

The third is controls the agent cannot edit. Routing between steps is a program, not a model decision. Every phase is gated and does not advance until a human approves. Tests are immutable within a unit of work. And green is defined operationally: a test runner passing against a live database, not the agent's report that it passed. This is the mode Consort runs in, and is the mode being prescribed by this guide's SDLC.

Only the third survives a determined or careless agent, because it never asks the agent to comply. And it adds the one thing that mode has always been too expensive to reach: rigor against real data.

The one part of the stack that never branched

Code branched. Environments containerized. Infrastructure became code. One part of the stack never played along. The database sat there as a rigid monolith, and the workarounds we accepted traced back to it: mocks instead of the real thing, a shared staging box, schema changes run as a careful ceremony. I watched teams contort around that box for years. So an agent can produce a plan, a spec, and a change in one sitting, then fall back to a mock or queue for shared staging, and "the tests pass" still does not mean "this works against the data it will run on."

Lakebase is what finally lets it branch: it cuts a copy-on-write branch of a real, governed Postgres database in roughly constant time, regardless of data size, isolated and disposable. The test you write first, TDD-style, runs against a live branch of real data rather than a fake, and when you are done you throw the branch away. Put that in the loop and the definition of "done" can finally be "passed against production-shaped data" rather than "passed against a double the agent shaped to its convenience."


The shape of it: your Scrum team, as agents

The complaint I hear most about agent frameworks: after a few days the agent loses the mission. It writes good code for a while, then it drifts, and you restart. The workflow in Consort is a response to that condition. It is a Scrum team rendered as role agents, each with one job and no shared memory, coordinated by an orchestrator written as code, not a model. Routing between phases is a pure function of recorded state, and the orchestrator writes no spec, code or tests of its own. This is the role an agile team calls a Scrum Master. And it is played here by a state machine, not agent inference, so the ceremony that sequences the work cannot drift or be argued out of a step, and it survives context compaction, which a model-driven loop does not.

Two things hold it together:

  • Every phase boundary is a gate that stays shut until a human opens it. A gate refuses to advance without an explicit human approval, that approval is bound to a SHA-256 hash of the artifact it approved, and any later edit to an approved artifact surfaces as drift. The gates run plan -> spec -> test_list -> promote -> deploy.
  • The artifact on disk is the only channel. Roles share no memory; the feature's artifacts live in a dedicated directory the agents read from as a contract between roles. It is the same handoff a real team runs: each specialist reviews the work, adds their concern, and passes it to the next. Any turn can be reloaded cold, so a fresh start is always safe, and the directory is the standing record of who asked for what, what was produced, and who approved it. When someone asks why a change shipped, what it was tested against, or what a fix touched, the answer is that versioned history on the paired branch, not a memory.

Two more things make it durable. Institutional knowledge lives in files, not heads: the engineering canon is encoded in three skills (Stage 2), and a versioned CLAUDE.md at the repo root carries the project's conventions, its commands, and the mistakes worth not repeating, read by every agent at the start of every turn. When the agent trips over the same thing twice, the correction goes into the file, not a chat thread. And autonomy is staged: early on a person watches each edit, but as the gates, the fitness functions, and CLAUDE.md prove out, routine work runs unattended for longer and the human's attention moves to reviewing the artifacts a run produced rather than each keystroke.

The lifecycle runs per increment: Intake -> Plan -> Design -> Build (test-driven, live branch) -> Deploy -> Promote -> Shipped, then loops back to Plan for the next increment. You drive it with the ceremonies you already run, as commands: /sprint, /plan, /design, /build, /deploy, and /spike for throwaway exploration; /deploy carries the increment through both the deploy and promote gates.

Promote Plan Design Build test-driven, live branch Deploy Evolve Intake Shipped

Every phase boundary is a checkpoint the orchestrator will not cross until a person approves: an intake review, a per-story acceptance, and the five hashed, human-approved gates (plan, spec, test_list, deploy, promote). The loop re-opens by design: /deploy ends each sprint as working software, and the human Product Owner reviews it and plans the next increment from what shipped.

The shifts, stage by stage

The stage-by-stage table below is adapted from the shifts table in Anthropic's original playbook (see the Reference), reworked for a branching-database SDLC. Notably missing is this SDLC's separate Test phase. Why? In this SDLC the Testing phase completely shifts left.

Stage Traditional AI-native, on a branching database
Plan Requirements move through tickets and refinement meetings before anyone can act. The ask is captured once as feature-request.md, candidates are sized, and the sprint is committed at the human-approved plan gate.
Design Analysts write requirements, designers parse them, the schema is left to the build. One spec-first lane produces the spec, architecture, schema, and test list, frozen at gates; the three skills encode the canon.
Build Code and tests are handwritten; verification waits for CI or a shared staging database. Navigator and Driver run RED/GREEN against a paired Lakebase branch, and green counts only against real data.
Test QA gates at stage boundaries; testing is a separate phase run after the build. Test is Shift Left. There is no separate test phase; verification runs inside the build loop, against a live branch, every cycle.
Deploy Humans review every line; the migration is a separate script run by whoever is around. An agent reviews the PR against the spec, the schema diff rides in the PR, and a human code owner approves at a gate the agent cannot.
Evolve Humans watch production for bugs, and maintain the product. New features, such as those that require a schema change, ship as a separate, scheduled, high-risk release. The product evolves through evolutionary design principles, accelerated to an hour/day cadence rather than week/month.

The roles, and what each does

Around the orchestrator sit the role agents, each a persistent agent driven by its own prompt. Each optimizes its own concern. That division of labor is where maintainability comes from: on human teams a product owner owns the requirement, a developer implements it, and a reviewer tests it, and the software stayed maintainable because each concern had an owner. The same separation guards integrity, because an agent that both writes code and judges it has every incentive to grade generously. In the list below are each of the roles in Consort, with a snippet of its prompt and then what it does.

  • Product Owner – "You are the human's voice in the loop. You decide what gets built and own every assertion once approved, but you never invent intent." Authors the product overview and NFRs, and turns each ask into a feature request.
  • Spec Author – "the business analyst, and the first role in the Spec Driven Development lane." Turns the requester's open-ended ask into structured stories and Given/When/Then acceptance criteria, and surfaces every ambiguity back to the PO rather than resolving it.
  • Architect Reviewer – "every acceptance criterion gets a layer assignment, cross-cutting concerns are owned, and the design respects the canon before any test list is built." Owns the layers, the NFRs, and the persistence invariants.
  • DBA – "You own the PHYSICAL realization: you consume that contract and produce the concrete schema ... You do not re-author the invariants; you realize them." Turns the architect's logical persistence contract into concrete tables and a per-story migration plan.
  • UX Designer (UI work only) – "you own the design guides and the information architecture, and you ensure downstream UI adheres to them." Grounds each design token in a real reference site it opens with a browser.
  • Test Strategist – "a SUPERVISOR: you do not author the test items yourself, you fan out to a roster of focused per-kind test-analyst subagents ... then you REVIEW + RECONCILE." Orders the result into one Beck-style test list.
  • Navigator – "You PLAN the next test, write a failing assertion (RED) ... You never weaken an assertion to make a test pass."
  • Driver – "You receive a RED test from the Navigator and produce the code to make it pass (GREEN), then REFACTOR ... without changing what the outer-boundary tests check." Never writes or weakens a test.
  • Release Engineer – has no prompt of its own: the deterministic deploy and promote steps the orchestrator runs directly, logged under this label.

The separation is by design: the Spec Author cannot quietly decide the layering, the Navigator cannot weaken a test to pass it, and the Driver cannot approve its own work. Each role has to satisfy a role, and a gate it can't control.

How the roles hand off

Evolve IntakePlan Design Build Deploy revise on findings next test (RED) PO ProductOwner intake:overview, nfrs intakegate SA SpecAuthor proposefeatures Arch Architect estimate(sizing) backloggate PO ProductOwner authorrequests plangate SA SpecAuthor break intostories UX UXDesigner designguide SA SpecAuthor acceptancecriteria Arch ArchitectReviewer annotatelayers DBA DBA realizeschema TS TestStrategist testlist Nav Navigator reflect,critique specgate Nav Navigator RED:failing test Drv Driver GREEN:honest code RE ReleaseEngineer verify run onreal branch Nav Navigator review Drv Driver refactor acceptancegate RE ReleaseEngineer deploy RE ReleaseEngineer verifyreachable deploygate RE ReleaseEngineer openPR RE ReleaseEngineer waitCI promotegate RE ReleaseEngineer merge toparent Shipped workingsoftware live

The orchestrator (a state machine, not a model) drives every arrow and cuts a paired Lakebase branch for each story; a human decides every gate (the dark diamonds), and no role approves its own work. The design lane runs per story and streams into the build lane, where the Navigator and Driver run the Beck cycle against the branch, which is why Test lives inside Build.



Stage 1 – Plan (/sprint, /plan)

Intent is written down at the source, not relayed through a chain of tickets. Open .consort/product-overview.md and you'll read the Product Owner's own words: an inventory management system for "a mid-market warehouse operation that has outgrown spreadsheets," whose V1 is "the simplest thing that lets the team see and adjust stock at one warehouse." That file is standing intent, not a frozen contract; nfrs.md beside it names the non-functional budgets, and design-brief.md names the look. None of them were generated on the fly; the PO facilitated them once, and you approve them.

Planning on a real team is a week of meetings, the backlog on the wall, and everyone trusting everyone else to keep it. I ran plenty of those rooms in weeklong inceptions. It worked while the team was the one holding it. Here the intent is a file the PO owns, and every role reads the same one. Because I now have agents, trust stops being the mechanism that binds; it's now enforced by the same ceremonies written in code.

Inputs and outputs

Role Reads first (prerequisite) Produces (artifact) Handed to
Product Owner (intake) The human's intake answers (or the recorded answers, headless) product-overview.md, nfrs.md (each ## Required item a stable R<n>), design-brief.md (UI only) Spec Author, Architect, UX Designer
Spec Author (propose) product-overview.md, nfrs.md planning/feature-proposals.md (next-sprint candidates, each with a real F<n>-<slug> id) Product Owner, Architect
Architect Reviewer (estimate) planning/feature-proposals.md planning/estimates.json (a t-shirt size per candidate) Product Owner
Product Owner (/plan) feature-proposals.md + estimates.json features/<F>/feature-request.md per committed item, then the plan gate Spec Author (Stage 2)

Running it

Run the sprint. The orchestrator opens with the intake interviews; because StockFlow is seeded, you are confirming what is already there rather than dictating it fresh:

/sprint stockflow-v1

Then plan it. Two roles move before you do: the Spec Author reads the overview and NFRs and proposes the sprint's candidates into planning/feature-proposals.md; the Architect t-shirt-sizes each into planning/estimates.json. You, the human, read both and commit the sprint by keeping the requests that fit, here F1-stock-visibility:

# example script called by the Product Owner. NO NEED TO RUN YOURSELF
./scripts/lk consort-sync-backlog --sprint stockflow-v1 --features F1-stock-visibility

Open the committed request, .consort/features/F1-stock-visibility/feature-request.md, and read what you just locked in:

Record and view stock by SKU and location … each (sku, location) pair is its own record and is uniquely addressable. Two records must never share that pair (resolve the collision at write time, never store a duplicate) … Each unit is identified by a single tracking code (an inventory_code) …

That paragraph is the anchor. It is never overwritten downstream: the Spec Author will structure it, the Architect will layer it, the DBA will turn "(sku, location) is unique" into a real constraint, but the ask itself stays exactly as the requester wrote it. The set of feature-request.md files you keep is the backlog; a deterministic sync-backlog step projects backlog.json from precisely those files. Then you approve the plan gate, and that approval is bound to a hash of the backlog it approved, so a later edit shows up as drift. Scope small; fold each sprint's learning into the next /plan.

What the roles are told to do

Product Owner: "you never invent intent: you facilitate the human's intent into artifacts, then the human approves them ... Scope small; fold each sprint's learning into the next plan."

Spec Author (propose): "propose the candidate features for the next sprint ONLY ... Prior sprints' DELIVERED features are done, never re-propose them. This is the PO's INPUT; you do NOT author feature-request.md or prioritize."

What changes

Requirements elicitation stops being a multi-week committee cycle and becomes an interview the PO reviews. What is enforced: the feature-request.md is read-only downstream, so no role can quietly redefine the ask; the plan gate is a recorded human approval bound to a hash of the backlog it approved. The audit record is the git history of the intent home, with author and timestamp on every artifact. What an org gives up: the reassurance of the old sign-off ritual, replaced by a human-approved gate and a versioned trail that is stronger evidence than a meeting was.

What improves

  • Now: time from first conversation to a committed feature-request.md, read from git history, falling from a multi-week refinement cycle to hours.
  • Later: the share of feature requests the PO accepts into Design rather than closes, and the count of edits to product-overview.md or nfrs.md that arrive after the first spec.md commit for the same change (rework that leaked past planning).

Stage 2 – Design (/design): the spec-first lane

Requirements and design are one lane here, not two teams working in sequence: intent is agreed and frozen before any code is written, and the spec and its test list are checked for drift thereafter, so the target the agent builds against cannot quietly move. It is also where the database change gets designed, rather than improvised during the build. Three skills give the agents their engineering canon, so policy is encoded, not suggested:

  • software-design-principles – SOLID, DRY, clean code at the unit level. Nine hard rules, including "names carry the design," "public boundary tests, private refactors," and "a schema contract change updates the data model AND the code in lockstep."
  • architectural-design-principles – the system level: layered architecture and inward dependency direction, ports and adapters, the twelve-factor app, evolutionary architecture, and evolutionary database design. Seven hard rules, each naming the fitness function that fails the build when it breaks: dependencies point inward; the repository layer is the only door to the database; config lives in the environment; backing services are attached and swappable; processes are stateless; every constraint names its fitness function; and mocks exist only where no real resource does, so the database is never mocked, because the paired branch makes a real one cheap.
  • ui-ux-design-principles – usability heuristics, visual hierarchy, accessibility, information architecture, and testable seams, enforced by a design-adherence gate.

The canon in those three skills is not decoration. I built message queues and async frameworks by hand, back before the cloud would build them for us, and the teams that held up held one rule above the rest: software must be refactorable, always. We held that rule then, in reviews and in our heads. Fitness functions moved it out of our heads and into the build as confirmation.

Inputs and outputs

Role Reads first (prerequisite) Produces (artifact) Handed to
Spec Author (breakdown) feature-request.md, product-overview.md feature-spec.{md,json} (the story index) + a stories/<S>/story.{md,json} stub each UX Designer, Architect
Spec Author (per story) story.json, product-overview.md stories/<S>/acs/<AC>.{md,json} (Given/When/Then) + the feature-spec narrative slice Architect
UX Designer (UI only) design-brief.md, product-overview.md, the draft spec design-guide.{md,json} (tokens), ia.md (screens, navigation, flows) Architect, Test Strategist, Driver
Architect Reviewer the annotated ACs, nfrs.md layer + architectural_notes on each AC; architecture.{md,json} with layers[], nfrs[], persistence_invariants[] DBA, Test Strategist
DBA architecture.json (persistence_invariants[]) db-design.{json,md}: tables[], per-story schema_changes[], realizes_invariants[] Test Strategist, build lane
Test Strategist the annotated ACs, architecture, db-design test-list.json (Beck-ordered) + test-list.md Navigator, Driver
Navigator (reflect) the story's spec slice + test list reflect-verdict.json (a cheap second pair of eyes before any cycle runs) back to the author on a defect

Running it

For F1-stock-visibility the first step is to create a feature branch: the orchestrator claims F1's paired Lakebase and git branch through the SCM workflow (the only supported path), which first verifies workflow state and then forks the pair off the tier parent, so the code and the data move together:

# example script called by the orchestrator. NO NEED TO RUN YOURSELF
./scripts/lk lakebase-scm-claim-feature-branch F1-stock-visibility

Then run the design lane on that branch, and follow each turn in the dashboard:

/design

If you didn't create a feature branch yourself, the design lane will do it for you. The lane then streams one story at a time. For F1-stock-visibility the Spec Author breaks the feature into three stories, S1-file-stock, S2-stock-by-location-table, and S3-sku-detail-view, then writes S1's acceptance criteria as Given/When/Then: AC1-file-stock-record, AC2-retrieve-stock-record, and AC3-collision-resolved-at-write, the one that says two records must never share a (sku, location) pair. Because StockFlow has a UI, the UX Designer produces the design guide and information architecture from design-brief.md, the navbar and app icon among them.

The Architect Reviewer then tags each AC with a layer and writes architecture.json: AC1 and AC2 are API, the stock-by-location table is E2E. It declares the layers the build must realize, boundary, service, repository, models, and the persistence invariants the schema must guarantee, including PI1: (sku, location) is unique. It annotates through a safe writer rather than hand-editing the JSON:

# example script called by the Architect. DO NOT RUN YOURSELF
./scripts/lk consort-annotate-ac --feature F1-stock-visibility --story S1-file-stock \
  --ac AC3-collision-resolved-at-write --layer API \
  --notes "unique (sku, location); resolve the collision at write time, never a 500"

The DBA turns that logical contract into physical DDL in db-design.json: a stock table with an inventory_code column and a unique constraint on (sku, location), plus the per-story migration plan. The Test Strategist then orders test-list.json Beck-style, fanning out to per-kind analysts and reconciling their slices. Finally the Navigator takes one reflect turn, an independent read of the story before any cycle runs, using the feature's other stories as context to catch a cross-story gap early:

# example script called by the Navigator. DO NOT RUN YOURSELF
./scripts/lk consort-cross-story-context --feature F1-stock-visibility --story S1-file-stock

The lane stops once at the spec gate (the whole design lane's output) and again at the test_list gate. You approve both, against the artifacts on disk.

What the roles are told to do

Architect Reviewer: "every acceptance criterion gets a layer assignment, cross-cutting concerns are owned, and the design respects the canon before any test list is built ... you never rewrite a Then clause."

DBA: "You do not re-author the invariants; you realize them" – turning persistence_invariants[] into concrete tables, keys, checks and a reversible per-story migration plan.

Test Strategist: "a single turn juggling behavior + fitness + client at once systematically under-covers ... A focused analyst, fed only its slice's inputs and its single-concern brief, does not."

What changes

Design review moves to the front: the architect reviews the spec before any code is written and layers in the design you care about, the NFRs, the layering, patterns like DRY and SOLID, which is why the work does not end up as everything in one file. Settling it while it is still a document is far cheaper than contesting it after the code exists. What is enforced: the spec and test list are frozen at gates and hashed, so drift is caught; the Architect's persistence_invariants are cross-checked, so a data-persisting feature cannot escape DB coverage by under-declaring; the PO's approved AC then clauses are locked against downstream weakening; Unity Catalog masking and access policy are designed once and inherited by every branch. What shifts: requirements and design fold into one lane, and the DBA's work becomes a spec-lane artifact rather than a build-time improvisation.

What improves

  • Now: elapsed time between the feature-request.md commit and the spec.md commit for the same change, two git timestamps, against the old requirements-plus-design cycle.
  • Later: requirements rework after build starts, counted as spec.md or architecture commits dated after the first plan.json commit for the same change.

Stage 3 – Build (/build): TDD against a live database branch, where testing shifts left

This is the stage the branch changes most, and the reason Test is not a separate stage. When a real database is present from the first failing test, verification happens inside the build loop; testing is not a downstream hand-off, it is what "the code is written" means. The orchestrator cuts a paired Lakebase experiment branch per story, off the feature branch, in about a second, and drives one story-scoped Kent Beck cycle:

PLAN -> RED -> GREEN -> REVIEW -> REFACTOR. In the RED turn the Navigator writes the story's failing tests against the live branch; in the GREEN turn the Driver writes the minimal honest code to pass them; then REVIEW, and REFACTOR only if REVIEW flags it. When a story's combined schema-and-code change is too large to land in one GREEN, the loop drops to one acceptance criterion at a time. Three primitives keep it honest, and they are code, not prose the agent can infer in non-deterministic fashion:

  • honest-GREEN. Green is stamped only when the test runner actually passes against a fresh branch cut for that check, not when the agent asserts it. A behavior test passing while a fitness function is still RED is not done.
  • Immutable tests. A test is never deleted or weakened to force a pass. The one principled exception is rare: when a later story legitimately supersedes an earlier test, a designated step refactors only the flagged superseded tests under an explicit rule, then re-verifies; an ordinary failing test is never touched.
  • No mocks for the database. The paired branch is a real, isolated Postgres, so dev/prod parity is the default and the schema realities that only appear against real data appear here. Mocks stand in only for a resource with no real counterpart, a third-party API, the clock.

Because a branch costs almost nothing, the lane can race N >= 2 strategies in parallel, each on its own branch and worktree through the same test list. The orchestrator builds a comparison report and the human picks Promote (one wins) or Synthesize (combine the best of each).

Inputs and outputs

Role Reads first (prerequisite) Produces (artifact) Handed to
Orchestrator (code) the approved test-list.json, db-design the paired experiment branch + git worktree; plan.json (how many strategies, the branch layout) Navigator
Navigator (RED) the story's test-list items, the ACs, the branch DSN the story's failing tests (RED), one per next-in-order slot Driver
Driver (GREEN) the story's failing tests, architecture.md, design-guide.md, the branch DSN the minimal production code that flips them GREEN; the migration Navigator
Navigator (REVIEW) the story's diff against the rubric review-verdict.json (refactor: true/false) Driver (refactor)
Orchestrator (verify) the full suite on a fresh branch the stamped honest-GREEN result; the comparison + selection log Product Owner (promote)

Running it

Enter the build lane. The orchestrator claims the feature, forking the paired git and Lakebase branch off the tier parent, then cuts a per-story experiment branch and starts the cycle:

/build

For S1-file-stock, the Navigator writes the first failing test against the branch DSN, AC1-file-stock-record: file a stock row for a (sku, location) and read it back. The Driver writes the minimal code to pass it, and when GREEN needs the table it creates the migration through the kit (never a bare alembic revision, which yields an unordered hash name):

# example script called by the driver. DO NOT RUN YOURSELF
./scripts/lk lakebase-new-migration --name "create stock table with unique (sku, location)"

The runner runs against the real branch, so AC3-collision-resolved-at-write is proved the only way it can be: by actually inserting a second row for the same (sku, location) and asserting the app resolves the collision rather than returning a 500, something a mock would happily fake. Watch that test run once and you stop arguing for mocks. Layering and UI adherence are checked deterministically at REVIEW:

# example script called by the navigator. DO NOT RUN YOURSELF
./scripts/lk consort-layering-clean --architecture .consort/features/F1-stock-visibility/architecture.json
./scripts/lk consort-ux-clean --project-dir .

The plan gate locks the experiment plan before a branch is cut; the promote gate records your Promote-or-Synthesize decision, bound to a hash of the comparison log. Throughout, smell detectors (test-deletion attempts, boundary violations, test-list drift) flag problems for a human; the orchestrator never auto-fixes.

What the roles are told to do

Navigator: "One test per cycle ... Test at the outermost public boundary ... The list is immutable between approved gates" and "write a real behavior test against the real paired-branch DB, or an architectural fitness test. Never a DB mock."

Driver: "Never delete a test." / "Never weaken an assertion." / "No mocks for the database. Tests hit the experiment branch's real DB." And on green: "GREEN is verified, not asserted ... A green you can't honestly achieve is surfaced, never faked."

What changes

The QA stage collapses into the build loop, and "the tests pass" changes meaning: it now requires code that works against production-shaped data, with the database's constraints and governance in force. What is enforced, in code rather than prose: immutability answers test-deletion, because within a unit of work the tests are not writable; honest-GREEN answers inaccurate self-reporting, because the record of success is the runner's, not the agent's; no-mock-DB answers over-mocking, because the collaborator is a real database the agent cannot shape. What shifts: fixture management disappears (the branch is the fixture, disposing of it is the reset), and racing two designs against real data becomes cheap enough to do routinely rather than never.

What improves

  • Now: the share of cycles that reach honest-GREEN on the first Driver pass, and the test-deletion and boundary-violation flags the smell detectors raise per story.
  • Later: defects that slip past a green build into a later stage, trending toward zero once green means "passed against real data," and the count of mocks left where a real branch was available.

Continuous verification: agent-written changes clear the same real-data bar

The build loop proves code against real data as it is written. But the system that produces that code keeps changing: you swap the model, rewrite a prompt or a skill, and the schema evolves underneath all of it. Any of those can change what the agent generates, with no guarantee the next change is as sound as the last. So the branched CI is not a formality layered on the build loop; it is the same real-data gate applied continuously. Every PR (including the ones an agent opens after you have retuned it) cuts a fresh Lakebase branch off the target, applies the migrations, and runs the behavior tests and the architectural fitness functions against a real, isolated Postgres, advancing only on green. Code the agent regenerates after a model or prompt change has to clear exactly the bar a human's change does; nothing merges on the agent's say-so. And the standing discipline keeps the suite honest over time: every production incident earns a permanent regression test before the fix lands, so the same failure cannot return.

If you track DORA, this is where the numbers move: change failure rate drops when green means a real run against real data, and lead time stops including the wait for a shared staging database.

What improves

  • Now: first-pass CI rate for agent-written changes, and the turnaround from opening a PR to a green, migrated branch.
  • Later: regressions caught in CI versus found in production, and the trend in fitness-function failures per change.

Stage 4 – Deploy, promote and ship (/deploy)

The Release Engineer steps are deterministic ones the orchestrator runs directly. A human accepts the increment story by story as working software; on acceptance the parent-aware schema diff computes the migrations to apply, and the promote path pushes the branch, opens the PR, waits on CI, and merges, at which point the parent's merge workflow applies the migrations. Separation of duties is structural: the agent that wrote the code cannot approve its release, because the gate requires a human token bound to the artifact.

I have been the person who signs off on a release. On a real team that signature is mostly trust: you read the diff, review acceptance, and you trust that staging told the truth. Here the signature is the same, but it sits on a schema diff and a green run against a real branch.

Inputs and outputs

Step (Release Engineer) Reads first (prerequisite) Produces (artifact / state) Handed to
Accept + schema diff the built-green story, the feature branch the parent-aware schema diff attached to the PR; deploy-gate evidence (reachability + verify) Human (deploy gate)
Prepare PR a clean code working tree state pr-ready: the pushed branch + the opened PR CI
Wait CI the open PR state ci-green: the pr.yml regression gate passed on a fresh branch Human (promote gate)
Merge the promote approval state merged: the PR merged, the parent's merge.yml applies migrations, the branch cleaned up next increment (loops to feature-claimed)

Running it

# Compute what the migration will do to the target tier (the stock table and its
# unique (sku, location) constraint), as a reviewable PR artifact:
./scripts/lk lakebase-schema-diff

# /deploy carries the increment through the deploy gate and then the promote gate:
/deploy F1-stock-visibility --target local

# example scripts called by the release engineer. DO NOT RUN YOURSELF
./scripts/lk lakebase-scm-prepare-pr          # feature-claimed -> pr-ready
./scripts/lk lakebase-scm-wait-ci             # pr-ready       -> ci-green
./scripts/lk lakebase-scm-merge --wait-migrate # ci-green       -> merged (parent applies migrations)

Autonomy is tiered by environment: tier 3 forks features from dev, tier 2 from staging, tier 1 from production; the parent branch is both the fork point and the merge target, so the next sprint forks from a populated parent. The deploy gate and promote gate each require a recorded human approval.

What the roles are told to do

Driver (on shipping): "At /deploy, shipping is the Release Engineer's job, not yours." The deploy and promote steps are deterministic and orchestrator-run, logged under the release-engineer label, so no agent both builds and ships.

The agent reviews the PR; a human owns the merge

Review runs at the PR as well as inside the build loop. Because the schema diff rides in the same PR as the code, an agent review pass reads both against the frozen spec.md, the architecture, and a written review policy, and ranks what it finds by severity: correctness, security, and whether the change still matches what was approved. The findings inform; they do not approve. Branch protection still requires a human code owner, and the human DBA reviews the migration there, as a code owner, rather than in a downstream queue.

What changes

Deployment and rollback stop being runbooks a human follows under pressure and become gated, rehearsed transitions, with the database migration reviewed in the same PR as the code rather than run separately by whoever is around. What is enforced: branch protection turns anything the agent writes into a PR with no direct path to main; the merge applies migrations through the parent's workflow, not by hand; the deploy and promote gates require a human token bound to the artifact, so the agent that wrote the code cannot approve its own release. What shifts: the schema change becomes a first-class, reviewable artifact, and each non-interactive step acts under its own identity, so the log separates what the agent did from what the human authorized.

What improves

  • Now: share of pipeline steps that complete without paging a human, and time from promote to a verified deploy on the parent tier.
  • Later: DevOps Research and Assessment (DORA) metrics, which the CI and deployment tooling already emit, read for stability as well as throughput; and unsafe migrations caught before merge versus those reaching production.


Stage 5 – Evolve

The loop is iterative and incremental. /deploy ends every sprint as working software the human Product Owner can actually use, the per-sprint checkpoint the product overview asks for ("working software I can use after each sprint... that working increment is what I review to decide what the next sprint should be"). That checkpoint is where the loop turns over: the human Product Owner reviews the increment that shipped and decides the next sprint, and the Product Owner agent facilitates that into a refined product-overview.md and the next plan. The parent tier already carries the last sprint's merged work, so the next sprint forks from it.

Evolving the schema is the part that used to be too risky to do incrementally, and StockFlow ships with an evolution deliberately deferred. V1 stored each unit's location, batch and serial together in one hyphen-delimited inventory_code (for example A12-B7-S001), on purpose: the product overview says the team is fine with the single code for V1 and that a later iteration will revisit splitting the fields apart. F6-split-tracking-code is that iteration, the canonical refactor the Product Owner demos after sprint 1. It pulls batch_number and serial_number out into their own columns (location is already its own column, part of UNIQUE(sku, location), so it stays canonical and is not recreated) and then retires inventory_code.

You deliver it expand-then-contract (parallel change), a core technique of evolutionary database design, so the last step is the drop. First expand: add batch_number and serial_number and backfill them from the existing code by splitting on - (segment 2 is batch, segment 3 is serial). The data is variable-width, so a code like X-1 or a bare c leaves the new fields NULL rather than guessing, and an integrity probe surfaces the count of nonconforming rows for review. Only once nothing reads inventory_code does the final contract story drop it. Each step is its own reversible migration, validated on a paired branch of real data before it ships:

# example script called by the driver. DO NOT RUN YOURSELF
# Expand: add the split columns and backfill from inventory_code (integrity-probed)
./scripts/lk lakebase-new-migration --name "add batch_number, serial_number; backfill from inventory_code"
# Contract: once nothing reads it, drop the combined column
./scripts/lk lakebase-new-migration --name "drop inventory_code"

The contract step is where supersession finally shows up for real. Dropping inventory_code supersedes the F1 tests that assert the combined code, so the Navigator flags exactly those tests by name and the Driver carries them forward to the new fields under the immutable-tests rule, rather than deleting them:

# example script called by the navigator. DO NOT RUN YOURSELF
./scripts/lk consort-cycle flag-superseded --feature F6-split-tracking-code --story S2-drop-inventory-code \
  --ac AC1-drop-combined-code --reason "batch_number + serial_number supersede the combined inventory_code" \
  --test tests/features/S3-sku-detail-view.feature

The parent-aware schema diff is the proof F6 is done: it shows inventory_code removed from the stock table alongside the two new columns, and the contract migration is reversible as a round-trip, its down path reconstructing the combined code from location plus the split-out fields.

Why the ability to evolve the schema matters: a schema you cannot change safely is a schema you stop changing, and the data model quietly drifts from what the product actually needs. Deferring the split was the right call for V1 because the split could be made later without drama, one tested increment at a time. Evolutionary database design treats the migration as the unit of change and the paired branch as a disposable test bed, while the fitness functions keep the system's shape honest as it grows.

This is evolutionary database design, the discipline Pramod Sadalage and Scott Ambler wrote the book on: change a live schema in small, reversible steps, never one heroic rewrite. Most teams I have worked with knew the technique; what they could not do cheaply was rehearse it. Practicing a migration against production-shaped data meant the shared staging box, and that made every step a ceremony. On a branch, rehearsal is cheap. Expand, backfill, probe, contract, each step reversible, each proven on a copy of the real thing before it ships.

Inputs and outputs

Actor Reads first (prerequisite) Produces (artifact) Handed to
Product Owner (review + re-plan) the deployed working increment at the /deploy working-software gate a refined product-overview.md and the next sprint's feature-request.md files Stage 1 (/plan)
DBA + build lane (evolve) the new feature-request.md (e.g. F6-split-tracking-code) and the current schema expand-then-contract migrations (add batch_number/serial_number, backfill, drop inventory_code), each validated on a paired branch Deploy

Running it

# The loop re-opens with the next sprint, planned from the working software sprint 1 shipped:
/sprint stockflow-v2
/plan            # the PO commits F6-split-tracking-code

# Build F6 expand-then-contract, then confirm the contract actually happened:
/design
/build
./scripts/lk lakebase-schema-diff       # must show inventory_code removed, batch_number + serial_number added

To reproduce a bug an increment surfaces, you can branch the data and diagnose against real rows at zero risk, then file the fix as the next feature-request.md:

./scripts/lk lakebase-branch            # a disposable branch of real data to diagnose against
./scripts/lk lakebase-feature-status    # where a feature's branch and migrations stand

What changes

This stage is the same loop running again: each sprint ships working software, the human Product Owner reviews it and decides the next, and the schema evolves increment by increment. What is enforced: a schema refactor is a reviewed migration like any other, gated at deploy and promote, and the parent-aware schema diff has to show the contract happened (inventory_code dropped) before F6 counts as done. What shifts: a schema refactor becomes an ordinary sprint of tested increments, and the next sprint is planned from working software the human Product Owner used.

What improves

  • Now: whether each sprint ends in working software the human Product Owner actually reviews, and how much of the next plan traces to that review rather than to guesswork.
  • Later: how safely the schema keeps changing, measured by refactors shipped expand-then-contract with no data loss (R1) and the fitness functions staying green as the model grows.

Drive your SDLC through Consort

You do not need to assemble any of this by hand. The deterministic orchestrator, the role agents, the human-approved gates, the three engineering-canon skills, honest-GREEN, immutable tests, no-mock-DB, the branched-database TDD build, the schema diff as a PR artifact, and the artifact chain are baked into Consort, an open-source Claude Code plugin from databricks-solutions that builds and tests on real, branched data. You only need Lakebase; the rest of the stack is open (Java, Python or Node), it runs on Claude, and it has a VS Code companion. Getting started is three commands:

# Bootstrap the tools
bash <(curl -sL https://raw.githubusercontent.com/databricks-solutions/consort/main/bootstrap.sh)
# Run it from Claude
claude
/consort:start

Then drive it with the Scrum ceremonies above: /sprint, /plan, /design, /build, /deploy, /spike.

You can watch all of it, and steer it, from two places. A standalone observability dashboard shows each turn live: every role, the exact prompt it was given, the artifacts it consumed, and the artifact it produces for the next role to read, so you see the handoff chain assemble in real time rather than reading it back afterward. A VS Code plugin shows the same activity in the IDE, with your paired Git and Lakebase branches across tiers and the code and schema changes in a single diff view. And at each gate you inspect the working software yourself, running against its own branch, before you approve it forward.

The full enforcement argument, the taxonomy of the three modes and where this SDLC sits, is set out in the paper Introducing Consort.

Try it yourself: /consort:start. The first sprint runs on real data, which is the only kind that ever proved anything.

Reference

What motivated me to write this was reading Louis Claxton's "The AI-Native SDLC Playbook" (Claude blog, August 21, 2026), which lays out the agent-native lifecycle and its governance model. This version keeps the same lifecycle, with one structural change: when the database branches like the code, Test is not a separate stage, it lives inside Build, and every stage after Design runs against real data. That playbook governs the code; this one adds the data. For the Claude-platform setup it assumes (permissions, sandboxing, managed MCP, model hosting), the original remains the reference; this guide does not re-cover it. The original: https://claude.com/blog/the-ai-native-sdlc-playbook