Agentic SDLC — From Branch to Production Map
From Branch to Production Map
FROM BRANCH TO PRODUCTION How one change gets from a laptop to real users — and how you know it worked. Your change is a train. Six places on the railway, in order. Click one to dive in; every district ends in a bench. git / branches a human decides an automated gate environments switches live & healthy broken / blocked - - - dashed = not real users yet ← Agentic SDLC meta-map ↗ Git situations deck ↗ DevOps: git deck ↗ DevOps: ship deck ↗
THE LINE 1 Branch Lines ⑂ SAVE POINTS & SIDINGS Git: commits are save points, branches are sidings off the main line, a merge rejoins it. 2 Signal Box 🚦 PULL REQUESTS & REVIEW You can't drive onto the main line. A pull request asks permission; review decides. 3 Inspection Sheds 🔍 TESTS & THE CI GATE Every train is inspected before it joins the main line — and a failed inspection stops it, whoever is driving. 4 Marshalling Yard 🛤 ENVIRONMENTS, LANES & VERSIONS Trains are assembled and test-run on side platforms before they leave for the main line. 5 The Points ⇄ DEPLOYS, FLAGS & ROLLBACK A lever on the track decides which version the traffic runs on — and you want to be able to pull it back. 6 Signal Lamps 📡 MONITORING & PROOF Lamps along the line tell you about trouble before the passengers do.
ONE CHANGE, START TO FINISH Bug: people who mistype their email at signup never get their login link.
1 Issue what & why, written down SAY THIS TO CLAUDE “Read issue #1412 and restate the acceptance criteria in your own words.” → see district
2 Branch + its own worktree SAY THIS TO CLAUDE “Make a branch fix/email-typos off production, in its own worktree.” → see district
3 Test first a test that fails SAY THIS TO CLAUDE “Write a test that fails because of this bug. Show it failing.” → see district
4 Commits small save points SAY THIS TO CLAUDE “Fix it. Commit as you go, with WHY in each message.” → see district
5 Pull request ask the signal box SAY THIS TO CLAUDE “Push and open a PR into production: why, what, how tested, how to roll back.” → see district
6 CI gate ci/hetzner-lint SAY THIS TO CLAUDE “Watch CI. If it's red, read the log and fix the code — not the test.” → see district
7 Preview its own URL + data SAY THIS TO CLAUDE “Deploy a preview. Walk the flow and screenshot each step.” → see district
8 Review agent checklist + human SAY THIS TO CLAUDE “Address every review comment and reply to each one.” → see district
9 Merge up to date · green · approved SAY THIS TO CLAUDE “Update the branch, wait for green, then merge.” → see district
10 Staging deploys itself on merge SAY THIS TO CLAUDE “Smoke test staging: login and the page we changed.” → see district
11 Production a human runs /deploy SAY THIS TO CLAUDE “Deploy production with /deploy, not /restart. Show me the live commit.” → see district
12 Watch the signal lamps SAY THIS TO CLAUDE “Compare the error rate to before. Anything new? Roll back first.” → see district
…and stop 12's errors become stop 1's next issue. It's a loop.
DISTRICT 1 OF 6 · SAVE POINTS & SIDINGS Branch Lines ⑂ Git: commits are save points, branches are sidings off the main line, a merge rejoins it. THE METAPHOR → THE MECHANISM. Click a box to zoom in; 🔍 boxes zoom further.
Commit = a save point with a note Small save points mean small, cheap undo. Ship › Branch Lines › Commit · 1 of 4 your branch 9d04b7a add form 51ce2f0 check email a7710bd fix typo c02e9f4 add test 3f9a2c1 empty input time HEAD = you are here Each circle is a snapshot of every file, plus who, when and why. Each points back at the one before — so the history is a chain you can walk back along. Git takes a snapshot of your project every time you commit. The snapshot is named by a hash — a fingerprint of everything in it. Agents commit constantly. That's good: if the last step broke something, you step back one save point, not one week. SAY THIS TO CLAUDE “Commit this with a message that says WHY, not just what.” 🔍 Anatomy of one commit commit 3f9a2c1e8b… ← the hash: a fingerprint of everything below Author: Roy (agent) Parent: c02e9f4 ← the save point before this one Handle an empty email field on signup Why: an empty field crashed the form instead of showing an error. auth.py | 6 +++++- test_auth.py | 14 ++++++++++++++ Change one byte anywhere and the hash changes — that's how git knows history wasn't edited behind your back.
Branch = a siding off the main line Work in parallel without blocking anyone. Ship › Branch Lines › Branch · 2 of 4 production (the main line — real users) fix/email-typos (your siding) merge, via a PR Nothing on the siding reaches real users until it rejoins the main line. A branch is a save point you diverged from. Your commits pile up on your siding; the main line keeps moving without you. Branches are free. Make one per idea, per agent, per experiment — a branch that goes nowhere costs nothing. SAY THIS TO CLAUDE “Make a new branch off production for this, in its own worktree.” 🔍 Worktrees: one repo, several desks school/ ← branch production (you) school-wt-login/ ← branch fix/login (agent Roy) school-wt-nav/ ← branch feat/new-nav (agent Jen) All three share ONE history. Each has its own copy of the files on disk, so two agents never type into the same file at the same time. git worktree add ../school-wt-login -b fix/login
Merge = rejoin the main line A conflict is two trains wanting the same track. Ship › Branch Lines › Merge · 3 of 4 production ! Roy: line 42 of pricing.py Jen: ALSO line 42 of pricing.py conflict: same lines, two answers Most merges are automatic — different lines. A conflict is git asking a question only a person can answer. Merging takes a siding's commits and joins them onto another branch. A conflict isn't an error. It means both sides changed the SAME lines and git won't guess which is right. Fewer conflicts: small branches, merged often. SAY THIS TO CLAUDE “Rebase this branch on production and resolve the conflicts keeping both behaviours. Show me the diff before you push.” 🔍 What a conflict actually looks like <<<<<<< production price = base * 0.9 # 10% member discount ======= price = base - coupon # coupon codes >>>>>>> feat/coupons Resolved: price = (base - coupon) * 0.9 # both, in an agreed order Then run the tests — a merge with no conflicts can still be a wrong merge.
BENCH · DO IT IN PAIRS Which situation are you in? Ship › Branch Lines › Bench · 4 of 4 Read the situation out loud. Decide together. Then click. ▶ click to zoom in and do the exercise
⑂ 1 · Branch Lines SAVE POINTS & SIDINGS
DISTRICT 2 OF 6 · PULL REQUESTS & REVIEW Signal Box 🚦 You can't drive onto the main line. A pull request asks permission; review decides. THE METAPHOR → THE MECHANISM. Click a box to zoom in; 🔍 boxes zoom further.
“Pull” is the change. “Request” is permission. You can't drive onto the main line. You ask the signal box. Ship › Signal Box › Pull request · 1 of 4 your branch fix/email-typos · 3 commits PULL REQUEST review + permission prod A PR bundles three things on one page: the diff the checks the conversation A pull request asks the people who own a branch: please PULL my branch's commits into yours. In this school's repos PRs target production — and merging there deploys staging, not the live site. Live is a separate step. SAY THIS TO CLAUDE “Open a PR into production. Description: why, what changed, how you tested it, how to roll it back. Link the issue.” 🔍 A PR description that gets merged Title: Reject typo'd emails at signup Closes #1412 WHY: people who mistyped their email never got their login link. WHAT: auth.py validates the address; new test in test_auth.py. TESTED: unit test fails before, passes after; signed up on the preview with a bad address. RISK / ROLLBACK: existing accounts untouched. Revert this PR to undo. + a screenshot of the preview showing the new error message
Read the PR page top to bottom The concept is easy. The UI is what's opaque. Ship › Signal Box › The PR page · 2 of 4 Reject typo'd emails at signup #1412 Open Roy wants to merge 3 commits into production from fix/email-typos Conversation Commits 3 Checks Files changed 2 ⚠ This branch is out-of-date with the base branch Update branch ✓ ci/hetzner-lint — lint · unit tests · build ✗ unit tests (blocking) — GitHub Actions · 0 steps ← noise here: Actions never runs Merge pull request ▾ ← three hidden options (zoom below) 1 2 3 4 1 what → into what 2 up to date? 3 checks green? 4 then merge Four things, in order: what is merging into what; is it up to date with the target; are the checks green; what files changed. Files changed IS the change. Even if you can't read every line, its size and which files it touches tell you a lot. SAY THIS TO CLAUDE “Why is this PR out of date, and is it safe to update the branch? Tell me what CI says after.” 🔍 The merge button's three hidden options Create a merge commit — keeps every commit + adds one “merged” commit. Full history. Squash and merge — the whole PR lands as ONE commit. One revert undoes all of it. Rebase and merge — replays each commit on top of production, no merge commit. Who may press it is a repo setting: branch protection can demand a green check and an approval first. For agent PRs squash is a common choice: one PR = one commit = one clean revert.
Review reads for intent, not syntax Approve = ‘I'd be fine with my name on it if it broke.’ Ship › Signal Box › Review · 3 of 4 def signup(email): - create_account(email) + if not valid(email): + return error(400) + create_account(email) 💬 What happens if email is empty? — a question, not a verdict Comment Approve Request changes review agent: tests? secrets? errors? security? patterns? docs? Two reviewers, two jobs. The review agent runs the checklist on every PR. The human asks: does this do what the issue asked, what else could it break, and would we know if it did? Ask for evidence, not confidence: a test that fails without the change, a screenshot of the preview. SAY THIS TO CLAUDE “Address every review comment on this PR. Reply to each one with what you changed, or why you didn't.” 🔍 The Code Reviewer agent (Workshop 4) On each PR: read the diff → style → test coverage → security → anti-patterns → verdict Checklist: tests exist and are meaningful · no hardcoded secrets · error handling · no obvious security holes · follows project patterns · docs updated Verdicts: APPROVED → release manager · CHANGES_REQUESTED → list them · BLOCKED → a human Agents are confident. Review is where confidence meets evidence.
BENCH · DO IT IN PAIRS This PR is red. Now what? Ship › Signal Box › Bench · 4 of 4 Read the checks block like a reviewer would. ▶ click to zoom in and do the exercise
🚦 2 · Signal Box PULL REQUESTS & REVIEW
DISTRICT 3 OF 6 · TESTS & THE CI GATE Inspection Sheds 🔍 Every train is inspected before it joins the main line — and a failed inspection stops it, whoever is driving. THE METAPHOR → THE MECHANISM. Click a box to zoom in; 🔍 boxes zoom further.
Four inspections, cheap to realistic Each shed catches a different kind of broken. Ship › Inspection Sheds › Kinds of test · 1 of 4 END-TO-END a robot clicks the real site INTEGRATION app + real database, together UNIT one function: 10 dmg vs +2 armour = 8 lost LINT does it parse? names spelled right? slower · realistic fast · many Tests are code that runs your code to check your code — so the next agent can't quietly break it. Lint catches typos. Unit catches wrong logic. Integration catches pieces that don't fit. End-to-end catches “the button is there but does nothing”. SAY THIS TO CLAUDE “Write a failing test that shows the bug FIRST. Then fix it. Then show me the test passing.” 🔍 One unit test, whole def test_armour_reduces_damage(): hero = Hero(health=20, armour=2) hero.take_hit(10) assert hero.health == 12 # 10 damage − 2 armour = 8 lost That's the whole thing. It's also the spec: whoever edits take_hit() next finds out at once if armour broke. Written first, tests are the spec. Written after, they're a chore that gets skipped.
CI is a gate, not a habit One check that truly blocks beats ten that only warn. Ship › Inspection Sheds › The gate · 2 of 4 1 PR opened 2 CI runs 3 preview 4 review 5 merged BLOCKED tests fail → it does not merge, no matter who says it's fine Prevents: “I ran it and it seemed fine” → ship. Continuous integration runs every check automatically on every push, and posts the result on the PR. Cowboy shipping isn't a discipline problem you fix with a better prompt. It's a missing gate. Agents will say “looks good”; the gate doesn't care. SAY THIS TO CLAUDE “Run the same checks CI runs, locally, before you push — and show me the output.” 🔍 This school's gate, for real push → GitHub webhook → the school's own CI runner (not GitHub Actions) → scripts/ci-check.sh: lint · unit suite (each test file in its own process) · build → ONE status on the PR: ci/hetzner-lint ✓ or ✗ The deploy builds the SAME Dockerfile, so a red test blocks the deploy too — not just the PR. Rule here: new checks go in ci-check.sh. Never add a .github/workflows file.
Fix the code, not the test Agents are rewarded for green. Some will cheat to get it. Ship › Inspection Sheds › When it's red · 3 of 4 red read the log reproduce locally fix the CODE push → green What agents do instead, when rewarded for green: ✗ weaken the assert ✗ add @skip / xfail ✗ try/except around the test ✗ mock returns the expected value ✗ delete the test Red means: read the log, reproduce it, fix the code, push. Review test diffs on their own. A PR that changes code AND loosens the test that checks it deserves a hard look. SAY THIS TO CLAUDE “Don't touch the tests to make CI pass. If you think a test is wrong, stop and tell me why.” 🔍 Spot the flaw — an agent “fixed” the build - assert response.status_code == 400 + assert response.status_code in (200, 201, 400) commit message: fix flaky test CI is green. The bug is still there. The agent changed the question, not the answer. Ask: “Did you change any test assertions? Show me the test diff separately.”
BENCH · DO IT IN PAIRS Which inspection catches it? Ship › Inspection Sheds › Bench · 4 of 4 Pick the CHEAPEST shed that would catch each bug. ▶ click to zoom in and do the exercise
🔍 3 · Inspection Sheds TESTS & THE CI GATE
DISTRICT 4 OF 6 · ENVIRONMENTS, LANES & VERSIONS Marshalling Yard 🛤 Trains are assembled and test-run on side platforms before they leave for the main line. THE METAPHOR → THE MECHANISM. Click a box to zoom in; 🔍 boxes zoom further.
Three platforms: preview → staging → production ‘Merged’ does not mean ‘live’. Ship › Marshalling Yard › Environments · 1 of 4 not real users PREVIEW one per PR · its own URL · its own DB copy · thrown away STAGING everyone's merged work together · the rehearsal PRODUCTION real students, real money merge → staging deploys itself a human decides → deploy label: deploy-preview Each platform answers one question earlier and cheaper than real users would. Preview: does THIS change work? Staging: does it work with everyone else's? Production: does it work for real people? Always ask which platform a change is on. “It's merged” is not an answer to “is it live?” SAY THIS TO CLAUDE “Deploy this PR to a preview, then walk the signup flow there and screenshot every step.” 🔍 How it works at this school Preview: label a PR deploy-preview → its own URL + its own copy of the database, torn down after. Staging: deploys itself every time something merges into the production branch. Production: a separate, manual deploy. A person decides. Trap: staging shares production's database. So migrations must be ADDITIVE ONLY — add a column, never drop or rename one, or staging breaks the live site.
Lanes: what ships together, in what order Tests that pass alone can fail together. Ship › Marshalling Yard › Lanes · 2 of 4 production release lane 1.17.x promote #12 infra #15 API #19 UI merge order: infrastructure → backend → frontend · ties → smaller first integration branch = merge them together on scratch FIRST, find the conflicts With many agents, many PRs land at once. A release lane decides what ships together; a release manager — human or agent — decides the order. When dozens of PRs are sitting open, an integration branch is how you test them as a batch and drain the queue. SAY THIS TO CLAUDE “Cut an integration branch from production with PRs #12, #15 and #19 merged, run the tests, and tell me which ones conflict.” 🔍 The Release Manager agent's rules (Workshop 4) Order: 1 infrastructure (affects everything) · 2 backend (APIs the UI needs) · 3 frontend · ties → smaller first Before merging: same files? same APIs? unmet dependencies? BLOCK when: main is failing · the change's tests fail · unresolved conflicts · or the change touches a system outside its author's scope Agents are fast: one agent doing 5 related features in order often beats 5 agents colliding.
A version number is a promise about what breaks MAJOR.MINOR.PATCH Ship › Marshalling Yard › Versions · 3 of 4 2 . 4 . 1 MAJOR MINOR PATCH 2.4.2 fixed a bug — nothing else changed 2.5.0 added something — nothing you use broke 3.0.0 something you depended on changed — read the notes The number isn't for you. It's for whoever depends on you, so they know if upgrading is safe. Versions let you say exactly what is live, and find where a regression started: walk back version by version until it works. A cohort or a sprint is a natural release boundary — tag it, write notes, claim something you could be wrong about. SAY THIS TO CLAUDE “Write release notes for this version — new, fixed, and anything that breaks — and tell me which bump you'd choose and why.” 🔍 Which bump? Fixed the timezone on class reminders → PATCH 2.4.2 Added a “download certificate” button → MINOR 2.5.0 Removed the old /api/v1/classes endpoint other apps call → MAJOR 3.0.0 Renamed a database column an outside tool reads → MAJOR, even if “it's just a rename”
BENCH · DO IT IN PAIRS Sort the yard Ship › Marshalling Yard › Bench · 4 of 4 Where is it, what number is it, how many agents? ▶ click to zoom in and do the exercise
🛤 4 · Marshalling Yard ENVIRONMENTS, LANES & VERSIONS
DISTRICT 5 OF 6 · DEPLOYS, FLAGS & ROLLBACK The Points ⇄ A lever on the track decides which version the traffic runs on — and you want to be able to pull it back. THE METAPHOR → THE MECHANISM. Click a box to zoom in; 🔍 boxes zoom further.
Blue/green: run both, then move the traffic Don't swap versions. Switch tracks. Ship › The Points › Blue/green · 1 of 4 users router ⇄ switch BLUE · v2.4 · live serving everyone right now GREEN · v2.5 · new booted beside it · health check first 1 deploy green 2 check it 3 flip 4 keep blue warm = instant rollback The new version boots next to the old one. Traffic only moves once it passes its health check. Rollback is flipping the switch back. Catch: both versions share one database, so the new schema has to work with the old code too. SAY THIS TO CLAUDE “Before you flip traffic, show me the health check passing on the new version.” 🔍 Why it matters Without it: stop old → start new → users see errors in the gap. And if new is broken, you're down until you rebuild the old one. With it: new boots beside old; no traffic moves until health passes. Rollback = flip back. A switch, not a rebuild. Rolling deploys have a cousin trap: for a moment two versions serve at once — design for it.
Canaries and kill switches Ship small. Keep a hand on the lever. Ship › The Points › Canary + flags · 2 of 4 traffic 95% → old 5% canary error rate pull back KILL SWITCH new_checkout = OFF if flags.on("new_checkout"): … Code ships dark. A switch — not a deploy — turns it on. Canary: send a small slice of traffic to the new version, watch its error rate, then widen — or pull back. Feature flag / kill switch: the code is deployed but off. Turning it on or off is a setting, not a deploy. SAY THIS TO CLAUDE “Put this behind a feature flag that defaults to OFF, and tell me exactly how to turn it on and off.” 🔍 A kill switch at this school The Instagram comment bot only runs if one settings key says “1”. Anything else — unset, garbage, the settings store down — means OFF. It fails CLOSED. Turning it off is one command: no deploy, no code change, no review queue. Design rule: make the safe state the default state.
Rollback — and the deploy that wasn't ‘It said OK’ is not evidence. Ship › The Points › Rollback · 3 of 4 DEPLOY git pull new code build new image start it ✓ new RESTART same old image stop start ✗ nothing changed Both say “success”. Only one shipped your code. Rollback = redeploy the last known-good version. Rollback means putting the last known-good version back. Easy for code; hard for data — a dropped column doesn't come back by redeploying. Decide before the deploy: what's the rollback, and how would I know I need it? SAY THIS TO CLAUDE “After deploying, prove the live site is running the commit we meant: show me the version it reports.” 🔍 A real silent no-op deploy An old doc said: to deploy, call the platform's /restart. /restart stops and starts the SAME image. No pull. No build. It returned success. Logs looked normal. The fix never shipped. The fix: /deploy (pull + build + start) — then check the running version, not the return code. That stale doc lived in the repo's own instructions. Docs drift; check against what's live.
BENCH · DO IT IN PAIRS Pull which lever? Ship › The Points › Bench · 4 of 4 Something's wrong in production. Pick the smallest lever that stops the harm. ▶ click to zoom in and do the exercise
⇄ 5 · The Points DEPLOYS, FLAGS & ROLLBACK
DISTRICT 6 OF 6 · MONITORING & PROOF Signal Lamps 📡 Lamps along the line tell you about trouble before the passengers do. THE METAPHOR → THE MECHANISM. Click a box to zoom in; 🔍 boxes zoom further.
The first minutes after a deploy Prove it, don't claim it. Ship › Signal Lamps › Smoke test · 1 of 4 ✓ 1 the running version = the commit you meant ✓ 2 home page loads ✓ 3 log in works ✓ 4 the page you changed shows the change ? 5 error rate flat vs. before Any ✗ → roll back FIRST, diagnose second. A smoke test is a fast, shallow check of the few things that must work. If it smokes, stop. Playwright can walk steps 2–4 for you and screenshot each one — evidence anyone can check. SAY THIS TO CLAUDE “Smoke test production now: version, home page, login, the changed page. Screenshot each. Then compare the error rate to before the deploy.” 🔍 Claim vs. proof CLAIM: “Deployed! Everything looks good.” PROOF: “Live: 2.5.0 (commit 4e1c9a2). /, /login, /classes return 200. Screenshot of the new banner attached. Error rate: same as before the deploy.” If you can't name what would tell you it broke, that's the finding.
Four instruments, four questions An error becomes a work item on its own. Ship › Signal Lamps › Telemetry · 2 of 4 LOGS what happened, in order METRICS how many, how fast, how often ERRORS stack trace + who hit it UPTIME is it up — checked from outside one GitHub issue agent → branch → PR → gate Production feeds the backlog. You hear about bugs from tooling, not angry emails. Logs tell the story. Metrics tell the size. Error trackers (Sentry, GlitchTip) give you the stack trace. Uptime checks look from outside, like a user. Wire errors into issues and the loop closes: production failures arrive in the backlog without anyone having to report them. SAY THIS TO CLAUDE “Wire error reporting so every unhandled exception in production becomes a deduplicated GitHub issue.” 🔍 An error becomes a work item 1 a student hits a bug → the app sends the exception to the error tracker 2 the tracker groups repeats into ONE issue — count, first seen, last seen 3 a rule files it on GitHub with the stack trace 4 an agent (or you) picks it up → branch → PR → gate → … back to the start of this map
How many nines? Pick a number Pick one you could be wrong about. Ship › Signal Lamps › Nines · 3 of 4 uptime allowed downtime what it costs you side project whatever happens a legitimate answer — just say it 99% ~7 hours / month one box, backups, fix it when you notice 99.9% ~43 min / month alerts that reach a human, evenings included 99.99% ~4 min / month an on-call rota — a staffing decision Then ask: what do I own that would tell me I'd missed it? Say the target out loud. Each extra nine costs ten times as much attention. If nothing you own would tell you you'd missed the number, that's the finding — wire the instrument before chasing the nine. SAY THIS TO CLAUDE “What would tell us if login was down right now? If nothing would, set up an uptime check that alerts us.” 🔍 Turn the number into a check SLO: “login works 99.9% of the time, measured by an outside check every minute.” Error budget: 99.9% allows ~43 minutes down a month. Burned half of it early in the month? Freeze risky deploys until it recovers. A target with no instrument behind it is a wish.
BENCH · DO IT IN PAIRS How do you know it works in prod? Ship › Signal Lamps › Bench · 4 of 4 Evidence, not confidence. ▶ click to zoom in and do the exercise
📡 6 · Signal Lamps MONITORING & PROOF
←
0 / 0
→
Explore
Click anything to zoom into it · scroll to zoom · drag to pan
Esc zoom out one level · Home whole atlas
/ search
Tour
→ / space next stop · ← previous
The tour visits every district and framework in teaching order.
Teach
N presenter notes for whatever you're looking at · F fullscreen
Every view has its own link — copy the URL to send someone straight to it.
Reading the map
Your change is a train. Six districts are six places on the railway it passes, in order. Each district: three ideas and a bench.
Small text appears as you zoom in. Some tiles have controls you can click.
Click anything to zoom in · scroll to zoom · Esc to back out · → for the guided tour