Ai SdlcAgentic CodingClaude CodeCi CdCode Review

Our AI SDLC: 8 Stages, 3 Agent Roles, 6 Gates

The lifecycle we now run every project through at devx: eight stages, three agent roles with different tool permissions, and six checks that fail the build.

Jaimin MalaviyaJaimin MalaviyaSDE-2
Jul 30, 2026
18 min read
Our AI SDLC: 8 Stages, 3 Agent Roles, 6 Gates
Fig. 01A dispatch on ai sdlc
Share this article

TL;DR

  • Eight stages per project, and a seven-step loop that every ticket runs inside stage 04.
  • Three agent roles in sequence, never in parallel. They differ by tool permission, not by name: Explore writes one file, Implement cannot merge or touch a test it did not add, Review cannot write at all.
  • Tickets are capped at 300 lines and 10 files, with CI failing above 400 or 20. Sized to what a person can read.
  • Six required checks live on the host. Hooks are hints, because an agent can run --no-verify and can edit the hook files themselves.
  • One check queries the GitHub API and fails the build when branch protection is not what the repository claims it is. It is the first one we switch on.
  • Depth runs on two axes: what a mistake costs sets the apparatus, how well-trodden the work is sets the leash.

What we run

Every devx project now runs the same eight stages, and every ticket inside stage 04 runs the same seven-step loop. Three agent roles with different tool permissions, six required checks on the host, a 300-line ceiling, and a spec a person approves before any code exists.

The eight stages, from Solutioning through Run and Support, with bugs re-entering Build

Stage 03 carries more weight than its position suggests, and it is the one most often skipped. Standards land before the first feature commit, because anything added later leaves everything written before it unchecked. And the stage does not end until one trivial page is live in dev, put there by the real pipeline. It can do nothing at all. The point is that the path works while there is nothing at stake.

What problem is an AI SDLC actually solving?

Not speed. Before deciding any of this we ran one deliberate experiment with no process at all: one frozen spec file, one cold session, no review gate. Of the requirements backed by a config file, a hook or a test, 8 of 8 came out present and correct. Of the requirements written only as prose, 0 of 7 did. Same file, same session, same model.

The agent builds to the shape of the check. A weak gate does not give you the same product less verified. It gives you a different, smaller product.

A check is not a filter applied to finished work. It is a specification of what the work will be, which is why "we will review it at the end" cannot recover the situation: by the end, the thing to review was never written.

The second half of the problem was in the same repository. Its context file asserted that branch protection was in force and pull requests required review. There was no git remote, and CI had executed zero times. Nothing in that file was untrue. From inside a repository, written down and wired up look identical, and everything below exists to keep those two the same thing.

What did we rule out, and why?

The design decisions that cost us the most argument.

A swarm of parallel agents. Ruled out for implementation. Parallel agents are only safe when they do not need to agree with each other, which is the opposite of building one ticket. On focused coding work a single well-prompted agent matches or beats multi-agent arrangements at a fraction of the cost. We use sequence, not concurrency.

Human review on every pull request. Ruled out as arithmetic. Sixty small tickets cannot each carry a peer review, and on a single-developer project there is nobody to do it. Review is required per path, not per project: money, personal data, auth, migrations, CI and hook config, existing tests, coverage thresholds, CODEOWNERS, context files, acceptance criteria. That one list is the protected set. A developer merges their own ordinary work and cannot merge a protected path alone.

Pre-commit hooks as the enforcement layer. Ruled out because they do not enforce. Agents have been observed committing with --no-verify, and a local permission rule cannot stop an agent editing the hook files themselves.

What an agent can reach inside the repository versus what only the host can enforce

We keep hooks for fast feedback. The header of our lefthook.yml says so in the file: hooks are hints, the gates are the required checks on the host.

Whole-repo coverage as a gate. Ruled out because it is a number you inherit rather than earn. A 78% suite with a 31% mutation score, written by the same model that wrote the code, passes it. We measure coverage on changed lines with an 80% floor.

Letting the agent write ticket status. Ruled out by observation. The experiment's agent maintained its own status board and marked its own work done. An agent handed a completion signal will write to it. Status is now computed from events: branch pushed, checks green, review approved, merged, promoted.

Reading the perimeter check's expectations from a config file. This was our own first design and a reviewer killed it. If the list of required checks lives in a file the agent can edit, the check verifies nothing. The expectations now live inside the workflow, protected by CODEOWNERS.

What does the lifecycle look like end to end?

Eight stages. Every project runs all of them, and each exists to stop one specific thing going wrong.

00 Solutioning. The practitioner team works with the client until the problem is clear, and the tech lead has at least one call with the client's own engineers. They write the SOW together. Four things go in that a commercial document usually omits: the depth level, what personal data the system holds and for how long, written permission that client code and documents are processed by a named third-party AI provider, and who fixes bugs after go-live. We also state which checks exist on their project today and name the ones still to build as still to build.

01 Handover. Everything moves as one searchable folder: SOW, client documents, call recordings, transcripts, research. Three rules keep it honest. The SOW wins over a recording, because a recording captures things that were later changed. Date everything and mark what is superseded. Keep commercial material separate. The handover is not one call: it ends when the tech lead says there is enough context to plan, with every remaining unknown written down and owned.

02 Kickoff. Draw the phase lines around the blockers rather than waiting for every dependency, and make each phase a slice that can be demoed. Then the design document, one per phase, drafted by an agent from the folder and judged by humans. Its acceptance criteria each name the test, script or check that will prove them, because a criterion with nothing against its name is prose, and prose is the 0 of 7. Anything shared goes first: contracts, schema, shared types. Freeze those and several tracks can build against them at once without colliding; leave them open and every later ticket renegotiates them.

03 Setup. Runs once, before any feature work, and this is the stage that prevents the failure above. One monorepo. Standards land before the first feature commit, because anything added later leaves everything written before it unchecked. Branch protection, CODEOWNERS, secret scanning, pinned toolchains, the release checklist template. One long-lived branch. A release is a tagged build at a named commit, promoted to uat and then production, never rebuilt. No developer or agent session holds production credentials: every serious agent-caused incident on record traces to standing access, and the fixes that worked were credential scoping and environment isolation rather than better instructions. Setup ends when one trivial page is live in dev, put there by the real pipeline. It can do nothing at all. The point is that the path works while there is nothing at stake.

04 Build. The loop, once per ticket. Next section.

05 Release Check. Before every production release. Most of it should already be running, so this stage reads results and decides. A backup taken and restored to a named place with the time recorded, because the restore is the test and backups nobody has restored are a belief. Rollback rehearsed, stating whether the release contains a schema change. It lands in a checklist with three columns: the item, how it was verified, and a link to the evidence. Lines get marked as each thing is done, not in one sitting before go-live. A line that cannot be ticked is waived in writing by a named person, and an honest waiver is worth more than a false tick.

06 Launch. Fires when the audience changes, not when the code changes. Everything decidable in advance is decided in advance, because a launch window is the worst moment to make a decision. For a cutover, the old system stays up, since the real rollback is pointing the domain back and that only works if the target is still running. Hypercare has an end date.

07 Run and Support. A rhythm, not a gate. An alert that fires and gets ignored is fixed or deleted, because an alert nobody acts on trains everyone to ignore the next one. Bugs become tickets and re-enter Build. What we learn in production lands as a check, a runbook, a design document edit, or a change to the starter repo so the next project inherits it.

How does one ticket actually get built?

One ticket, one branch, one pull request, one merge.

The seven steps of the build loop, from Context through Merge to dev

Three rules make the spec load-bearing rather than paperwork. It carries the questions and the developer's answers in the words they were given, because the implementing agent never sees that conversation. Approval is a state on the host set by a person, never a field inside the spec file, since the agent writes that file and would otherwise approve itself. And the spec must be the branch's first commit and an ancestor of every implementation commit, which closes the other route: build freehand, then write a plausible spec afterwards.

Two rules cover the class of work that automated checks reach least well. A ticket with an interface is not done until something has operated it, either a browser test that clicks the flow or a named person who did, with a screenshot attached. "It renders" is not evidence that a flow works, because a page with no Create button renders perfectly. And write flows are enumerated as separate acceptance criteria, because "manage questions" is one line in a spec and at minimum eight flows in reality: create, edit, delete, the error case on each, the empty state, and whether you can log in to do any of it.

Why three agent roles instead of one agent or a swarm?

Explore, Implement and Review: what each role can do and what it cannot

The roles are real because their tool lists differ. A role that is only a name is a naming convention and changes nothing. Explore gets read and search tools plus write access to one file. Implement gets edit tools, with git push --force, pushes to main, gh pr merge and gh pr review --approve denied outright in its permission policy. Review gets read tools and no write tool of any kind.

Implement starts on a fresh context holding the spec and deliberately not the exploration, because if the spec is too thin to build from we want that discovered on this ticket rather than in three months. Review is a separate session because one reviewing its own work catches less than a fresh one does, and re-reading does not close the gap.

How much process does a project actually need?

Two questions, asked separately. What a mistake costs sets the apparatus. How well-trodden the work is sets the leash.

The loop, the size ceiling and the gate discipline never change with depth. They are what make work reviewable and they cost nothing. The apparatus around them varies:

LightStandardHigh
nothing to undo, no money, no personal datareal users, recoverable mistakesmoney, personal data, or cannot be undone
The loop, ceiling, gate rulesyesyesyes
Review agent as required checkoptionalyesyes
Human reviewprotected pathsprotected pathsprotected paths, two reviewers
Coverage floorsnonechanged lineschanged lines plus mutation on core logic
Environmentsdev onlydev and uatdev, uat, production in code
Monitoringnoneerrors, logs, alertsplus an audit trail
Release Checkskippedstanding listplus load test, threat model, restore drill
Supportnonethe rhythmplus someone on call

Light has to be defined tightly or everything becomes Light, and the test is what a mistake costs rather than who sees it. A public page with nothing behind it is Light. The same page with an email capture is not, because it now holds personal data. A tool five people use is not Light if it can issue a refund.

The second axis is asked per ticket. Models are strongest on what exists a thousand times over in public code and weakest on what does not, and the weakness is not announced: output for a novel problem looks exactly as confident as output for a familiar one. Well-trodden work gets reviewed on its evidence. Genuinely novel work gets smaller steps, a person reading the diff rather than only the evidence, and the reasoning written into a decision record.

What a person actually does

Four things.

  1. Answer the agent's questions, once per ticket, in one pass.
  2. Approve the spec, in minutes, before any code exists. The cheapest possible moment to be wrong.
  3. Judge the feature running in dev, not the diff. Does it work, do the edge cases hold.
  4. Read the gate-touching diffs, ten minutes a day. Every diff across every project that changed tests, thresholds, CI or hooks, with the deltas reported: test count 84 to 61, coverage floor 85 to 70, a workflow job removed, assertions loosened in tests that stayed. Batched daily, because the class is rare and interrupting every ticket for it turns attention into ritual.

Every human gate has a second name against it. One name on all of them means a fortnight of leave stops promotion to uat.

What is enforced in CI, and what is only written down?

Six required checks. Four are ordinary: format, lint, types, tests. Three took real thought.

A gate change cannot hide inside a feature commit. Agents have been observed deleting most of a test suite and reporting success, hardcoding values to match assertions, and editing the workflow to drop the failing job, with the pipeline green throughout. Green CI is not evidence when the agent can edit CI.

is_gate_path() {
  case "$1" in
    .github/workflows/*|lefthook.yml|CODEOWNERS|CLAUDE.md|.claude/*) return 0 ;;
    docs/design/criteria/*)                                          return 0 ;;
    *jest.config*|*vitest.config*|*bunfig.toml|*.coveragerc)          return 0 ;;
    *) return 1 ;;
  esac
}

for SHA in $(git rev-list --reverse --no-merges "$BASE..$HEAD"); do
  GATE=""; IMPL=""
  while IFS=$'\t' read -r STATUS FILE _; do
    if is_gate_path "$FILE"; then
      GATE="$GATE $STATUS:$FILE"
    elif is_test_path "$FILE" && [ "$STATUS" != "A" ]; then
      GATE="$GATE $STATUS:$FILE"     # an EXISTING test, modified or deleted
    else
      IMPL="$IMPL $STATUS:$FILE"     # source, or a NEW test for it
    fi
  done < <(git show --name-status --format= --no-renames "$SHA")

  [ -n "$GATE" ] && [ -n "$IMPL" ] && FAILED=1
done

That [ "$STATUS" != "A" ] is the part our first version got wrong. Treating every test-file change as a gate change creates a rule with no legal way to obey it: a test-only commit lands red, and an implementation-only commit fails the coverage floor. Adding a test for code you are adding has to count as implementation. Modifying or deleting one that already existed is the gate change. We found it by trying to write a commit that satisfied both rules and discovering none exists.

The size ceiling. 300 lines and 10 files as the target, 400 and 20 as a hard fail, measured on the pull request diff against main with lockfiles and generated files excluded. Not because the agent cannot produce more. Past some size a reviewer stops reading and starts approving, and a rubber stamp is indistinguishable from no review while still producing a green check. Over the ceiling, a tech lead can add a size-override label and the pull request has to explain why splitting was impossible.

One job checks the other checks. Everything else lives inside the repository where an agent can reach it. Branch protection lives on the host, where it cannot. So this job asks the GitHub API what is actually enforced and compares it to expectations written in the workflow file itself:

REQUIRED_CHECKS="size gates spec verify review"
REQUIRED_APPROVALS=1
P=$(gh api "repos/$REPO/branches/main/protection")

jq -e '.required_pull_request_reviews.require_code_owner_reviews == true' <<<"$P" \
  || fail "Code owner review is not required, so CODEOWNERS is a text file with no effect."

jq -e '.required_pull_request_reviews.dismiss_stale_reviews == true' <<<"$P" \
  || fail "Stale reviews are not dismissed. Approve, push anything, merge on the old approval."

jq -e '.allow_force_pushes.enabled == false' <<<"$P" \
  || fail "Force pushes to main are allowed, so the history every check reasons about can be rewritten."

If the token that reads branch protection is missing, the job fails rather than skipping. A perimeter check that goes quiet when unconfigured is precisely the failure it exists to catch. The same principle governs bun run verify, which fails when a gate is not wired to your stack instead of passing what it never ran:

const missing = GATES.filter((g) => !scripts[g.script]);
if (missing.length) {
  console.error("Not every gate is wired. Add these to package.json:\n");
  for (const g of missing) console.error(`  "${g.script}": "..."   — ${g.why}`);
  process.exit(1);
}

Results

Read the status column literally. Written means the file exists and its logic is tested locally. Proven means it has run on a host and failed something it was supposed to fail.

RuleEnforced byStatus
Ticket 300 lines / 10 filesrequired check on the PR diffwritten
Gate changes in their own commitrequired check per commitwritten
Spec exists and precedes coderequired check on commit ancestrywritten
Coverage on changed lines, 80% floorthreshold that fails the buildwritten
Format, lint, types, testshooks and required checkswritten
Acceptance criteria need approvalCODEOWNERS on a protected pathwritten
Protected paths need a second reviewerCODEOWNERSwritten
The loop is identical across projectscommitted skills, commands, agent fileswritten
The perimeter matches this documentscheduled job against the host APIwritten
No secret reaches the remotehost push rejection plus pipeline scanscan written, host rejection to build
Nothing pushed to main directlybranch protectionto build, host configuration
Migrations apply from scratch and onto mainCI runs bothto build
Status is not self-reportedderived from git and CI eventsto build
Agent holds no production credentialsenvironment and secret scopingto build, infrastructure
No personal data in logsfield allowlist with a testto build, per project

Nothing is proven. Against synthetic commits locally, the gate-mixing detector was correct on all four cases: implementation plus a new test passes, a modified existing test alone is reported as gate-only, and both mixings fail. The changed-line coverage script correctly failed a 75% case against the 80% floor and named the uncovered line. The secret scanner caught a staged AWS key and a password-bearing Postgres URL without tripping on your-key-here or a process.env read.

Also worth stating plainly: five of the to build rows can never live in a repository. Branch protection and secret rejection are host configuration, credential scoping is infrastructure, derived status is an integration, a logging allowlist is per-project. "The starter repo will handle it" is true for migrations and the dependency bot, and false for the rest.

What we got wrong

We read a self-maintained board as evidence. The agent wrote its own status and we believed it. An agent given a completion signal to write to will write to it.

We monitored the wrong layer. We watched commits for spec adherence, secrets and sensible history, and never asked whether the pipeline existed. Verifying by running the tests locally passes, and proves nothing about CI. "The checks are green" and "someone ran the tests" are different claims, and only the perimeter job tells them apart.

Nothing here has run on a host. Every check is written and locally tested, and not one has executed in CI. The gap this whole post is about, written versus wired, currently applies to the post. That is the next thing we close.

The 300-line ceiling is judgement, not measurement. The argument holds, but it gives a direction and not a number. What to measure is not how many lines a reviewer reads but at what size they stop finding anything.

We do not know that human review catches AI-introduced defects. We looked for a controlled study and did not find one. Published numbers are unflattering: pull requests reviewed only by an agent merge at 45.20% against 68.37% for human-only review across 19,450 pull requests, and four reviewer bots run in parallel on one codebase never once agreed on a single finding. Our review agent is a required check on that basis, which is why we count how often its findings get dismissed instead of assuming it earns the slot.

The tech lead's queue is the most likely thing to break. Four human gates land on one person across every project at once. On a small team that collapses into skimming, and a skimmed gate scores the same as an absent one.

The next test is falsifiable, so here it is stated so it can fail. We are putting a real project through this lifecycle end to end. The claim is that every acceptance criterion arrives with the artefact that proves it, that no gate change reaches main mixed into a feature, and that the perimeter job matches the host on day one. If any of those does not hold, the process did not do the job it was built for, and we will say that rather than explain it away.

Jaimin Malaviya

Jaimin Malaviya

SDE-2

Continue reading

All articles →
Pyramid, Diamond, Pod
future of work

Pyramid, Diamond, Pod

Learnings from the ground: The pyramid was never the product. Judgment was. And a better amplifier for judgment now exists!

Yash ThakkerJul 12 · 6 min