Everyways · how-to

How Software Gets Made

Every feature you use arrived through the same rough sequence: someone described a need, someone wrote a change, machines checked it, and it was released to people who could be harmed if it was wrong. Here is that sequence, one stage at a time — including the part where an AI agent does some of the work.

The park behind this page is the pipeline, and it follows along as you read. Drag the margins to move it — or use Hide the article, up in the corner, to push the words out of the way and have the whole thing to yourself.

01It is a loop, not a line

The old picture of software development was a waterfall: requirements, then design, then build, then test, then release, each phase finishing before the next began. It is a tidy diagram and a poor description of reality, because it assumes the hardest question — what should this actually do? — is answered correctly at the start and never revisited.

What teams do instead is go round. A small change is described, built, checked, released, and watched; what is learned from watching it changes what gets described next. The park behind this page is drawn as a circuit for exactly that reason: the track leaves the backlog plaza and comes back to it.

The one idea to keep

Every stage after "write the code" exists to answer one question: is it safe to let people use this? Version control, review, CI, tests, security scanning, staging, canaries and monitoring are all different-priced answers to that single question. A team's process is really just its choice of which answers to buy.

The unit that travels round the loop matters more than the diagram. If it is a six-month project, every stage is slow, risky and hard to reverse. If it is a change of forty lines, every stage is quick, cheap and easy to undo. Almost everything good about modern delivery comes from making that unit smaller.

02Discovery: turning a need into a slice

Work starts as something vague — a complaint, a support ticket, a strategy slide, a thing a competitor shipped. The job of discovery is to turn that into a statement specific enough to be built and, crucially, specific enough that you could tell afterwards whether it worked.

Two artefacts do most of the work. The problem statement says who is affected, what they are trying to do, and what currently goes wrong. The acceptance criteria say what must be observably true for the work to count as done. Neither mentions an implementation. Both are testable.

Vertical slices

A slice should be thin but complete — a working path from interface to storage — not "the database part", which cannot be released or judged on its own.

Definition of done

Written down once, applied every time: tested, reviewed, documented, observable, and actually running in production.

Work in progress limits

Starting is easy and finishing is not. Capping how many things may be in flight is what makes them finish.

Estimates

Useful as a conversation about risk and unknowns. Treated as a promise, they quietly become a deadline nobody agreed to.

Where AI helps first

This is one of the least glamorous and most effective places to use a language model: clustering hundreds of support tickets into recurring themes, drafting acceptance criteria from a rough note, listing the edge cases a description forgot. The output is a draft for a person to argue with, which is exactly the right shape.

03Version control: one true history

Everything the team builds lives in a repository under version control, almost always Git. It stores not the current state of the code but the entire sequence of changes that produced it: who changed what, when, and — if the commit message is any good — why.

That history is not bookkeeping. It is the mechanism that lets several people change the same system at once without overwriting each other, lets you find the exact change that introduced a bug, and lets you undo it without undoing anything else.

TermWhat it isWhy it matters
CommitA snapshot plus a message and a parentThe smallest unit you can revert, review or bisect
BranchA movable pointer to a line of commitsLets unfinished work exist without disturbing anyone
MergeJoining two lines of historyWhere conflicting edits get resolved, by a human
TagA fixed name for one commitWhat "version 4.2.0" actually points at
MainThe branch everyone agrees is the truthWhat gets built, tested and released

The long-running argument about branching strategy is really an argument about batch size. Branches that live for weeks accumulate conflicts and hide risk; trunk-based development — short-lived branches merged into main within a day or two, with unfinished features hidden behind flags — keeps the difference between what is written and what is integrated small enough to reason about.

04Writing the code — now with a machine at the desk

For most of the industry's history this was the bottleneck. Producing correct code was slow, skilled work, and everything around it was organised to protect that scarce capacity. That assumption is the one currently being renegotiated.

A modern coding agent is not autocomplete. It is a language model in a loop with tools: it can read files, search the codebase, write edits, run the test suite, read the failures and try again — repeating until some stopping condition is met. Give it a goal and a working environment, and it will take many steps without being asked for each one.

Completion

Suggests the next few lines as you type. You stay in the loop continuously; the blast radius is one keystroke.

Chat

Answers questions and drafts snippets in a side panel. You paste, adapt, and remain the one editing.

Agent

Takes a task, edits many files, runs commands, iterates against the tests, and hands back a diff for review.

Background agent

The same thing running on its own machine against a ticket, arriving as a pull request you did not watch being written.

The interesting consequence is not that typing gets faster. It is that the constraint moves. When producing a plausible diff is cheap, the expensive parts become deciding what should be built, and establishing that what was built is correct. The rest of this page is mostly about that second thing — which is why it was already the majority of the lifecycle before any of this arrived.

Plausible is not correct

A model is optimised to produce output that looks like the right answer. Most of the time, in well-trodden territory, that coincides with being the right answer. In the remaining cases you get code that reads beautifully, uses a function that does not exist, and handles the empty case by pretending it cannot happen. Nothing downstream of here cares who wrote the change — which is precisely why the pipeline is the safeguard.

05Review: the last place a human reads it

A pull request proposes a change and invites objection before it becomes part of the truth. Reviewers read the diff, ask questions, request changes, and eventually approve. It is the cheapest available moment to catch a misunderstanding, because nothing has been released and nobody is affected yet.

Reviews catch fewer pure bugs than people assume — tests are better at that — and far more of everything else: a misread requirement, a name that will confuse the next reader, an edge case nobody considered, a change that works but makes the next change harder. Good review is mostly about intent.

Small diffs

Attention falls off a cliff after a few hundred lines. A big pull request does not get reviewed harder; it gets approved faster.

One purpose

A refactor mixed with a behaviour change hides the behaviour change. Separate them, even when it costs a second pull request.

Machines first

Formatting, lint and style should be settled by tools before a person looks. Humans are for judgement, not whitespace.

Fast turnaround

A review that waits a day costs far more than its own duration, because the author has moved on and must page it all back in.

AI reviewers are genuinely useful here as a first pass — they never get bored, they read the whole diff, and they are good at spotting the mechanical classes of mistake: an unchecked error, a resource left open, a test that asserts nothing. They are also confidently wrong often enough that treating their comments as a gate rather than a suggestion trains everyone to ignore them.

The load problem

If agents write more changes and humans still review all of them, review becomes the new queue — and a reviewer who feels behind approves rather than reads. Teams that handle this well shrink what needs human attention: smaller diffs, stronger automated checks, and an honest split between changes that need judgement and changes where a green pipeline genuinely is the evidence.

06Continuous integration: build it once, for everyone

Continuous integration is the practice of merging everyone's work into the shared branch frequently — at least daily — and having a machine build and check the result every single time. The server is not the point; the frequency is. CI is what stops "it works on my machine" from being a meaningful sentence.

A typical run, in order, cheapest checks first so failures come back fast:

# pipeline: push → main checkout + restore cache 4s install dependencies (locked) 11s lint + format check 6s type check 14s unit tests 1,284 passed 38s build artefact 52s integration tests 96 passed 2m 41s vulnerability + licence scan 19s publish image sha256:7f3a… → registry

Two properties make the difference between a pipeline people trust and one they route around. It must be fast — beyond about ten minutes, developers stop waiting and start context-switching, and the feedback loop breaks. And it must be deterministic: a suite that fails one run in twenty teaches everyone to press retry, which is the same as having no suite at all.

CI is what makes agents workable

An agent needs a fast, honest signal to iterate against, and a green pipeline is the best one available. It is also the boundary that contains the damage: whatever the agent believed about its own work, the same checks run, on the same machines, before anything merges. Teams that adopt agents successfully almost always invest in CI first.

07Testing: buying confidence at different prices

Tests are executable claims about behaviour. Their value is not that they prove the system correct — they cannot — but that they let you change it without being afraid, which is the difference between a codebase that stays alive and one that ossifies.

KindScopeSpeedCatches
UnitOne function or module, isolatedMillisecondsLogic errors, edge cases, regressions
IntegrationSeveral parts together, real databaseSecondsWrong assumptions between components
End-to-endThe whole system, as a userMinutesBroken journeys, wiring and config faults
ContractThe agreement between two servicesSecondsOne team breaking another team's client
PropertyInvariants over generated inputsSecondsThe cases nobody thought to write down

The conventional advice is a pyramid: many fast, narrow tests underneath, fewer slow, broad ones on top. The reasoning is economic. Broad tests are more convincing and much more expensive — slower to run, harder to write, and prone to failing for reasons that have nothing to do with your change.

A failure caught by a unit test costs seconds. The same defect caught in review costs an hour of someone else's attention. In production it costs an incident, and possibly a user who does not come back. That gradient is the entire justification for everything on the left of the map.

Tests as the contract with an agent

Agents are strikingly good at writing tests, and this is a trap as well as a gift. A test written after the fact by the same process that wrote the code tends to encode what the code does, not what it should do — and it will pass forever while being wrong. The useful discipline is to make the expected behaviour the input: write or review the assertions yourself, then let the agent make them pass.

08Security: checks in the road, not a gate at the end

Security review used to happen once, near release, performed by a separate team with the power to say no and no time to say it usefully. Moving those checks into the pipeline — "shift left" — makes them boring and constant instead of rare and dramatic.

  • Dependency scanning. Most code in a modern application was written by strangers. Scanners compare your locked dependency tree against known vulnerability databases on every build.
  • Static analysis. Pattern-matching the source for injection, unsafe deserialisation, weak randomness and the other recurring shapes of mistake.
  • Secret scanning. Catching the API key that was pasted into a config file, ideally before the commit ever reaches the server — because once pushed, it must be rotated, not deleted.
  • Provenance. Signed commits and signed artefacts, plus a software bill of materials listing exactly what went into a build, so that when a component turns out to be compromised you can answer "were we affected?" in minutes.
  • Least privilege. Build systems hold the credentials that can deploy. Treat the pipeline as production infrastructure, because to an attacker it is the most valuable machine you own.

Two risks that arrived with agents

Prompt injection. An agent that reads issue comments, web pages, dependency documentation or logs is reading text an attacker may control — text that can carry instructions. Treat everything an agent ingests as untrusted input, and never let the ability to read something imply the authority to act on it.

Plausible dependencies. Models occasionally suggest packages that do not exist, and attackers publish those names on purpose. Every dependency an agent adds deserves the same look you would give one a stranger added — because that is what happened.

09Release: small steps you can walk back

Deployment used to be an event: a date, a checklist, a room of people, a rollback plan nobody had rehearsed. The modern version is deliberately anticlimactic. Ship small changes often, and make each one easy to reverse.

That requires separating two things that sound identical. Deployment is putting new code on the servers. Release is letting users reach it. Feature flags split them apart, so code can ship dark for weeks and be switched on for 1% of traffic on a Tuesday morning — and switched off again in seconds, without a deploy.

Blue-green

Two identical environments. Deploy to the idle one, verify, switch traffic across. Rolling back is switching back.

Canary

Send a small slice of real traffic to the new version and compare its error and latency against the old before widening.

Rolling

Replace instances a few at a time, so a bad version never accounts for all capacity at once.

Feature flags

Release as a runtime decision. Powerful, and quietly expensive: every flag is a branch in production that someone must delete.

Staging exists to rehearse this. It is a replica of production — same shape, less traffic, fake data — and it catches configuration and wiring faults that no unit test will. It also lies, reliably, about anything involving scale, real data, or the odd things real users do. Treat a green staging run as evidence, not proof.

The rollback rule

When something breaks after a release, restore service first and understand it afterwards. Debugging in front of affected users is a choice to extend the outage. The corollary is that rollback has to be a routine, tested path — a plan nobody has ever executed is not a plan.

10Running it: the part that never ends

Software is not finished when it is released; it starts being used, which is when most of the truth about it appears. Observability is the ability to answer questions about a running system that you did not think to ask in advance.

Metrics

Cheap numbers over time — request rate, error rate, latency percentiles, saturation. Good at telling you something is wrong.

Logs

Discrete records of what happened. Structured, they are searchable evidence; unstructured, they are an expensive diary.

Traces

The path of one request across every service it touched, with timings. Usually the fastest way to find where it is wrong.

Alerts

Rules that wake a human. Should fire on symptoms users feel, not on every twitch a dashboard makes.

Teams write down a service level objective — say, 99.9% of requests succeed within 300 ms over 30 days — which converts an argument about whether things feel slow into arithmetic. The amount of failure the objective permits is an error budget; spend it on shipping while there is budget left, and stop shipping to spend it on reliability when there is not.

Blameless, and precise about it

After an incident, write down what happened, in what order, and what made the failure possible. "Blameless" does not mean vague: it means the question is which safeguard was missing, not which person typed the command. Engineers who expect to be punished report less, later — and the second failure is always more expensive than the first.

11Feedback: how the loop closes

The last stage is the first stage. Usage data, error rates, support conversations and the things people say out loud all become the input to the next round of discovery. A lifecycle without this arrow is not a lifecycle; it is a factory producing features nobody asked to keep.

Four measurements have proved unusually durable for describing how well the loop turns. They are worth knowing because they are hard to game individually — pushing on one alone breaks another.

MeasureQuestion it answers
Deployment frequencyHow often do changes reach users?
Lead time for changesHow long from commit to running in production?
Change failure rateWhat share of releases cause a problem?
Time to restore serviceWhen something breaks, how fast is it back?

The counter-intuitive finding, repeated across years of research, is that speed and stability rise together. Teams that release frequently are not being reckless — releasing small changes often is precisely what makes each one safe, easy to diagnose and cheap to reverse. The slow, careful, quarterly release is the risky one.

Beware measuring individuals with any of this. Counting commits or lines per person reliably produces more commits and more lines, and nothing else.

12Where agents actually fit

An agent is not a stage of the lifecycle. It is a new kind of worker that can be plugged into most of the existing stages — which is why, on the map, the agent depot sits in the middle of the circuit with cables running out to the workshop, the repository, the review gate and the observability tower, rather than occupying a station of its own.

StageWhat an agent does wellWhat stays human
DiscoveryClustering feedback, drafting criteria, listing edge casesDeciding what is worth building, and for whom
Writing codeScaffolding, mechanical refactors, wide migrations, testsArchitecture, trade-offs, saying "not like this"
ReviewFirst-pass triage, checklists, spotting mechanical faultsJudgement about intent, and accountability
CIDiagnosing failures, unpicking flakes, fixing the buildDeciding what the pipeline must guarantee
TestingFilling in coverage, generating cases, reproducing bugsChoosing what correct means
OperationsSummarising incidents, correlating signals, drafting timelinesThe call to roll back, and telling users the truth

A pattern runs down that table. Agents are strong wherever the work is bounded, verifiable and tedious — where there is a clear signal saying whether the attempt succeeded. They are weak wherever the work is deciding what "succeeded" means. That boundary is not about model capability; it is about who is answerable to the people affected.

Multiple agents

The obvious next move is to run several at once — one per ticket, or a planner directing workers. It works, and it moves the bottleneck again: to merge conflicts between changes written in parallel, to CI capacity, and above all to the humans who must still understand what shipped. Parallelism multiplies output; it does not multiply attention.

13Working with agents, in practice

The teams getting real value out of this are not doing anything exotic. They are applying the lifecycle they already had, with a few adjustments that follow from one observation: the agent is a fast, capable, literal-minded colleague with no memory of yesterday and no stake in the outcome.

  1. Make the pipeline the contract. An agent's work arrives as a pull request and goes through the same checks as anyone's. No side doors, no direct pushes to main, no privileged path.
  2. Specify before you generate. Most bad agent output is a faithful implementation of an under-specified request. Time spent writing down the expected behaviour is repaid immediately.
  3. Keep diffs small and single-purpose. The limiting factor is a human's ability to review. A change that cannot be reviewed cannot be trusted, however green the checks are.
  4. Give it a sandbox and real permissions. An isolated environment with the tools to run the tests, and deliberate boundaries around secrets, production credentials and anything with side effects outside the repository.
  5. Write down the project's context. Conventions, architecture decisions, the reasons behind the odd bits. Agents read documentation more reliably than people do — and the same file helps the next new joiner.
  6. Check the tests before the code. If the assertions are right and honest, a passing implementation is a much smaller thing to verify.
  7. Evaluate the workflow, not the vibes. Keep a set of representative tasks and measure how often the pipeline gets to green without human rescue. It is the only way to tell a change of setup from a change of mood.
  8. Keep a person accountable. Someone approves, and that someone owns what happens next. "The agent wrote it" is not a position anyone can defend to an affected user.

What the map is really showing

Look at the circuit behind this page. The agent depot has cables to almost every station — but the track still runs through review, build, test, security and staging before it reaches the city where people live. Adding a fast new worker to a pipeline does not remove the reasons the pipeline exists. It makes them load-bearing.

Glossary

TermIn one line
SDLCThe repeating sequence by which an idea becomes running software and then evidence for the next idea.
RepositoryThe versioned store holding a project's code and its entire history of changes.
Pull requestA proposed change, opened for review and automated checks before it joins the main branch.
CIContinuous integration: merging work frequently and building and testing every change automatically.
CDContinuous delivery or deployment: every change that passes the pipeline is releasable, or released.
ArtefactThe built, versioned output of the pipeline — a binary, a container image, a package.
StagingA production-shaped environment used to rehearse a release.
CanaryExposing a new version to a small slice of real traffic before the rest.
Feature flagA runtime switch that separates deploying code from releasing behaviour.
RollbackReturning to the previous known-good version to restore service.
ObservabilityMetrics, logs and traces good enough to answer new questions about a live system.
SLOA written target for reliability, and the budget of failure it implies.
PostmortemThe blameless write-up of an incident and the safeguards it revealed were missing.
Technical debtAccepted structural compromise that makes future change slower until it is repaid.
SBOMA software bill of materials: the inventory of everything that went into a build.
Coding agentA language model in a loop with tools, taking multi-step action against a goal in a codebase.
Prompt injectionHostile instructions hidden in content an agent reads, aimed at making it act against its operator.
EvalA repeatable task set used to measure whether an AI-assisted workflow actually works.

Go deeper

  • Continuous Integration — Martin Fowler on what the practice actually requires.
  • DORA — the research programme behind the four measures above.
  • Pro Git — the free book; chapters 2 and 3 are the useful ones.
  • Google SRE Books — service level objectives, error budgets and incident response, free online.
  • OWASP Top Ten — the recurring shapes of application security failure.
  • SLSA — a framework for supply chain integrity and build provenance.
  • How the Internet Works — the other half of this series: what happens once your change is live.
1

Loading… Starting the simulation.