Arrange Act Assert

Jag Reehals thinking on things, mostly product development

You Can't Ship Agents Like Software

29 Aug 2026

Every generation of software has a moment where the old discipline stops fitting.

Structured programming got object orientation. Monoliths got services. Servers got containers. Each time, the work did not get easier. It got differently hard, and the teams that noticed early built the practices everyone else adopted five years later.

Agents are that moment again, and this time the change is deeper than architecture.

For seventy years, software has been a machine for replaying decisions a human made earlier. A programmer decided what should happen, froze that decision into code, and the computer replayed it a billion times, identically.

Compilers changed how we wrote the decisions. Frameworks changed how we organised them. Nothing changed who made them.

An agent samples the decision at runtime, on the specifics of the case in front of it.

Karpathy called neural networks Software 2.0: behaviour learned from data rather than written by hand. Agents are the next step in that lineage. The model does not just classify or complete. It chooses what to do, in what order, with which tools, and when to stop.

That one property, decisions sampled at runtime instead of frozen at build time, quietly breaks most of the assumptions the SDLC rests on.

It does not break the need for a lifecycle. Teams shipping agents as if they were shipping prompts are rediscovering, one incident at a time, why the SDLC existed in the first place.

Call the replacement the ADLC, the agent development lifecycle. Most of it will look familiar. The places where it differs are exactly the places where agent projects die.

What actually breaks

Four assumptions the SDLC never states because it never had to:

Same input, same output. Classical software is a pure function wearing a trench coat, not because production is deterministic, but because the SDLC treats the logic as if it were. Same relevant state, same branch. Every practice downstream depends on this: unit tests, bug repros, the very idea of a regression. An agent samples from a distribution. Run it twice and you get two defensible, different answers. Sometimes one defensible answer and one confident disaster.

Tested once means tested. An agent's behaviour shifts when the prompt changes, the tools change, the data changes, or the model provider ships an upgrade you did not ask for. A model deprecation is a breaking change to your system's behaviour with a date attached.

The artifact is the code. An agent's behaviour lives in five places: the prompt, the tool schemas, the model identifier, the retrieval corpus, and the orchestration around all four. Version any subset and you have not versioned the system.

Failures throw. Deterministic software fails loudly: exception, stack trace, line number. An agent's worst failures return 200. Fluent, well formatted, wrong. Nothing crashed, so nothing paged. The error surfaces three weeks later in someone's board deck.

None of this means agents are unengineerable. It means the engineering has to move to different load-bearing walls. In classical software, most control happens before deployment. In agent systems, part of the engineering discipline has to travel into the runtime.

The ADLC, stage by stage

Put the two lifecycles side by side and the shape of the change becomes obvious:

SDLC ADLC
Requirements in prose Specs that execute
Design allocates responsibility Design allocates freedom
Implementation spends CPU Implementation spends attention
Tests assert a value Evals score a distribution
No equivalent Runtime controls: assertions, retries, refusals

Requirements become specs that execute. In the SDLC, requirements are prose a human translates into code, and the translation is where fidelity dies. In the ADLC, the workflow you describe becomes a versioned spec: steps, tools, checks, sign-off points. The domain expert stops filing tickets and starts shipping.

Design becomes boundary design. Classical design allocates responsibility. Agent design allocates freedom. Which tools exist in a step's world, what enters its context, what it must produce before the next step is allowed to start. The wrong answer used to cost you maintainability. Now it costs you an agent with production credentials and a vague goal, improvising.

Implementation becomes context engineering. The scarce resource is no longer CPU. It is the model's attention. Pass large results by reference. Scope each step's view to what the step needs. The discipline that keeps the bill down is usually the one that stops the model drowning in the wrong evidence.

Testing becomes evals. A unit test asserts a function returns 7. An eval scores a distribution. The bar moves from passes to passes 96 percent of the time, and holding that number matters more than reaching it once. No eval baseline, no way to tell an improvement from a regression with better manners.

Runtime grows a stage the SDLC never had. Assertions between steps. Retries that carry the validator's complaint back as context. Hard stops in front of anything irreversible. The most important control is the one that fires when the model is not sure enough to act. In agent systems, the refusal is a feature you build, test, and demo.

Observability is not monitoring

Classical observability asks whether the system is working as built. Agent observability asks whether it is still doing what we meant. Health checks cannot answer that. A fluent, wrong agent is perfectly healthy.

So the trace becomes the central artifact of the whole lifecycle. Per step, it must record:

The test is simple. If a reviewer, an auditor, or an eval harness cannot reconstruct why the agent did what it did from the trace alone, the trace is a log wearing the wrong badge.

That test is executable. apps/example-agent-trace runs a support agent that drafts an answer, fails a check, retries with the complaint fed back as context, and issues a refund. A reviewer function then gets the exported spans and the events carrying their trace ids, and nothing else. It answers every one, in eight questions, two of which read:

  Which checks judged the answer, and how did they score it?
    cites-policy=fail (answer cites no policy), cites-policy=pass (answer cites a policy)

  Was there a retry, and what did it change?
    attempt 2 after: answer cites no policy

The same work instrumented as one span and four log lines answers none of the eight. Both results are asserted, so the example fails if either stops being true.

Two details from building it are worth stealing. Tool arguments and results reach the span as hashes, so the record proves which input produced which output without shipping a customer's data to your telemetry backend, and raw capture stays a separate decision. And evaluation results are events rather than attributes: they carry the trace and span id of the step that emitted them, which is what lets a failing score and the answer that earned it open together.

Get that record right and it pays for itself four times over:

It is the debugger. There is no stack trace for a bad decision. The context the model saw at the moment it chose is the closest thing you get, and it is usually enough.

It is the audit trail. The demos that convince regulated buyers are not the ones where the agent is impressive. They are the ones where every claim links back to a step, a tool call, and a check that passed.

It is the cost meter. Tokens are the new CPU seconds, except the bill arrives per decision and compounds across every retry and fan-out. Per-step accounting is what turns "the agent got expensive this month" into "this retry loop costs more than the answer it eventually gave".

It is the training signal. Which is where things get interesting.

Self-evolution, with adult supervision

Classical software does not improve in maintenance. It is repaired there. Every improvement is a human reading a bug report and writing a patch.

An agent system can close that loop on itself, because the raw material is already flowing. Every run leaves a complete record of what was attempted, what the checks said, and what a human corrected.

Feed it back and the system compounds:

This is the part of the ADLC with no SDLC ancestor at all. It is also the part most likely to go wrong, because a self-modifying system without a fitness function does not improve. It drifts, confidently.

So the same discipline that governs the agent's actions has to govern the agent's evolution:

Evolution is a deployment, and it flows through the pipeline like one.

apps/example-agent-gates builds both gates and asserts that they hold. Inside a run, a spin-loop rule stops an agent that has decided the answer is one more search away, three searches in and before the tool that moves money, and the stop lands on the trace with the rule, the observed value and the limit. A human approval gate records not only the outcome but the evidence, observed when the process saw the decision and inferred when the caller did not say, because an approval reconstructed from "the tool ran after a prompt" is not a human decision.

Between runs, the same script scores three versions of a refund procedure against a suite. The released version scores 75 percent, a candidate that widens the window scores 63 percent and is refused with the failing cases named in the decision, and a candidate that adds the missing rule scores 100 percent and ships. Every case of every version leaves an evaluation event, so a regression is a query rather than a rerun.

The discipline survives. The artifacts change.

It is tempting to read all this as software engineering being displaced. The opposite is happening.

Every hard-won instinct transfers intact. What changes is the artifact each instinct grips:

The SDLC was never really about software. It was about making the behaviour of a complex system legible, checkable, and safe to change. Software that decides at runtime needs that more than any software before it, not less.

Agents are the new variant of software. The teams that win with them will not be the ones with the cleverest prompts. They will be the ones who noticed, earliest, that a new kind of software had arrived and quietly built it a lifecycle.


Both examples run with no API key and no backend: example-agent-trace and example-agent-gates, built on autotel 7.3.0 and autotel-genai 0.8.0, where a tool call first gets its own execute_tool span and a refused one is marked blocked. Each example asserts the claim it demonstrates and exits non-zero when the claim stops holding.

agents ai engineering observability autotel