Agentic Software Engineering

Code generation is becoming abundant. Engineering judgment is not. The real leverage comes from turning product intent into architecture, executable constraints, reviewable changes, and production evidence.

If you want to demonstrate that you can build with AI, do not publish only the final code. Show the decisions, trade-offs, specifications, quality gates, and recovery paths that made the result trustworthy. Those artefacts reveal how you think, not merely how quickly a model can type.

This is the process I used while building Claude-Lens in public. It is not a universal framework, but it is a practical one. Everything below follows from one operating contract:

Figure 01 · the operating contract

The modelGatesThe engineerproposes optionsdrafts the coderuns the toolsmoves faster than youexecution capacitytestsacceptance criteriareviewsecurity policynothing crosses uncheckeddefine the systemset the constraintsapprove the riskjudge the evidenceown the outcomejudgment and accountabilityThe model never sits in the incident review. You do.
The model supplies execution capacity. The engineer retains judgment and accountability. Gates keep the two connected without confusing their roles.

When an agent’s mistake reaches production, the model does not face the stakeholder or lead the incident review. You do. Authority and accountability cannot be separated, so delegation has to be bounded by evidence. The middle column in the diagram—the tests, acceptance criteria, review, and policy—is the control system you spend the lifecycle building.

The lifecycle has seven phases. The first five define the product and the conditions under which it can be built safely. Phase 6 begins production implementation. Phase 7 turns production evidence into the next round of decisions.

This is not a waterfall argument. Discovery continues after implementation starts, and feedback can force any earlier decision to change. The point is to make each transition explicit. An architecture choice becomes a decision record. A product idea becomes acceptance criteria. A claim of correctness becomes executable evidence. You can revisit any of them, but you can see what changed, why it changed, and which downstream assumptions now need review.

Figure 02 · the whole flow, at a glance

Think & define — no production implementationBuild & learn↓ implementation starts here01 Concept02 Architect03 Foundation04 Features05 Spec06 Build07 Shipproduction evidence feeds back into the spec
Five phases reduce ambiguity before production implementation begins. Build and ship then return evidence to the specification.

Phase 01Concept

Prototype the experience before production code

Start by exploring what you want to build: the user, the problem, the core journey, and the smallest outcome worth shipping. This is not yet an in-depth feature specification. It is a conversation about the product’s shape. Who is it for? What job are they trying to complete? What would make the result meaningfully better than the option they have now?

Once the journey is coherent, use Claude Design, Lovable, Google Stitch, or another design tool to generate the screens. I kept the output deliberately lightweight: raw HTML and CSS, not a production React application. That is still code, but it is disposable prototype code. Its job is to make the experience concrete enough to critique without quietly deciding the architecture.

Render each screen to a reference PNG and check it into the repository. A multimodal model can compare the implemented UI with that target later, while browser automation can turn the same reference into a repeatable visual check. The PNG is a survey marker: flat, dated, and resistant to drift. The HTML is the sketchpad; the image is the thing you measure against weeks later.

An image is not a complete specification. It cannot prove behaviour, accessibility, responsiveness, security, or performance. It is one oracle for visual intent, not a substitute for acceptance criteria. Used that way, it closes a loop that would otherwise depend on your memory of a prototype.

Prototype the awkward states as well as the happy path. Empty results, first-run setup, slow loading, invalid input, partial data, denied permissions, and narrow screens often reveal more about the product than a polished dashboard. If those states remain invisible until implementation, the agent will make the product decisions for you while filling in the gaps.

Do not choose a production stack yet. A lightweight prototype is cheap to regenerate or discard. A framework scaffold is already an architectural commitment made at the moment you have the least information. Choose late enough to choose against requirements rather than habit.

The deliverables from Phase 1 are deliberately small: a user journey, screen prototypes, frozen references, and a first list of measurable outcomes. There is no production implementation yet.

Figure 03 · concept pipeline

Talk it outwhat to buildDesign toolClaude · Lovable · StitchHTML + CSSraw screensExport PNGone per screenReference setchecked in Phase 06Disposable prototype code · no production implementation
Design → render → freeze. The references become one part of the automated verification strategy used later.

Phase 02Architect

Separate product capabilities from the foundation beneath them

Bring the prototypes into an architectural conversation. Identify two classes of work: product capabilities, which users can see, and foundational capabilities, which make those features possible—startup, routing, identity, data ownership, persistence, error handling, test infrastructure, security boundaries, and observability.

Technology selection belongs here, after the constraints are visible. For Claude-Lens, the data model forced the useful debate: does a local, single-user tool reading append-only logs need Postgres? What are the read and write patterns? Which data should be stored as snapshots, and where do deltas make more sense? The discussion ended somewhere I did not start: a model shaped around the actual workload rather than the database I knew best.

That distinction matters. “I picked the familiar tool” and “I picked the right tool” produce the same repository when the familiar tool happens to fit. They reveal very different judgment when it does not.

How to disagree with a model built to agree

A model does not defend a position; it produces a likely continuation, and agreement is often part of that pattern. Push hard enough and it may fold whether or not you are right. Do not argue to win. Argue to expose evidence.

Ask it to make the strongest case for the option you dislike. Ask what workload breaks the preferred design, what you might regret in six months, and which assumptions must remain true. Then verify the specific claims. Treat agreement as noise and specificity as signal.

Ask for at least two viable designs, their failure modes, their operational cost, and the condition that would invalidate each choice. The model can widen the option space, but it cannot own the decision. Record consequential choices as short architecture decision records: context, options, decision, consequences, and a revisit trigger.

Also distinguish requirements from preferences. Data residency, an offline mode, a fixed deployment environment, and a latency budget constrain the design. Familiarity with a framework is a preference unless the team or operating model makes it a constraint. Models often treat both with equal confidence; the engineer has to label them accurately before comparing options.

That final field is especially valuable. “Revisit if a session log exceeds 50 MB” is a tripwire you can observe. “Deltas felt faster” is a memory that will disappear. A compact decision record stops a confident chat response from silently becoming permanent architecture.

Figure 04 · what rests on what

Feature pages · built on topDashboardSessionsSession detailSettings…moreFOUNDATION · runnable system with zero product featuresstartup · routing · schema · security · observability · test harnessDefine the base before scheduling what rests on it
Every product capability depends on a shared foundation. Define that base before scheduling the features above it.

Phase 03Foundation

Plan a walking skeleton that can verify itself

The foundation is not one giant task. Consolidate the architectural decisions, then split them into thin, verifiable increments. Follow one rule: every completed task must leave the repository runnable.

If the first task is only a schema, but there is no project that boots and no route through which to exercise it, the agent has no meaningful feedback signal. It can generate files and report what success usually sounds like, but it cannot demonstrate that the pieces work together. So order the work differently.

Task zero: the start command opens something. Task one: one route resolves end to end. Task two: the data layer connects and a real test exercises it. Task three: the test harness runs in the same way locally and in CI. This is a walking skeleton: the stairwell and frame go up before the rooms exist, and you can walk through the empty building.

This phase plans that sequence and defines the evidence for each task; it does not implement the skeleton yet. The implementation begins in Phase 6. By separating the plan from the build, you can inspect the order and the gates before an agent starts changing production code.

The walking skeleton matters especially with agents because it creates the first reliable feedback loop. Without it, you remain the only error detector. With it, every later task inherits a system the agent can boot, probe, and test.

Figure 05 · the walking skeleton plan

Task 00it starts · something opensTask 01one route resolvesTask 02real data layer + testTask 03test harness with teethmust boot ✓must boot ✓must boot ✓must boot ✓A change that cannot run cannot provide evidence—to you or the agent
Plan the foundation so that each implemented increment will boot and produce evidence. Phase 6 executes this sequence.

Build oracles, not a mountain of tests

Use the cheapest test that can detect the failure you care about: unit tests for isolated logic, integration tests for boundaries such as databases and filesystems, contract tests between services, and a smaller set of end-to-end tests for critical journeys. The goal is not a pyramid because somebody drew a pyramid. Cheap tests should catch cheap failures; expensive tests should be aimed at expensive failures.

For database integration, Testcontainers starts a disposable instance of the same kind of service used in production. Run the real migrations, exercise inserts and queries, then tear it down. An in-memory substitute is faster, but types, constraints, migrations, indexes, JSON semantics, and transactions can differ. The bugs you need to catch often live precisely in that gap.

For the UI, browser tests should assert behaviour first. Add visual regression only where appearance is part of the contract. Playwright’s screenshot assertions can compare a page with a checked-in baseline, but the rendering environment must remain consistent. A changed font, platform, or scrollbar can turn a useful oracle into noise.

Think of tests as oracles: mechanisms that can say “this is wrong” without requiring a human to reread every generated line. The Phase 1 PNG is a design oracle. Acceptance criteria are behavioural oracles. Testcontainers is a data-layer oracle. A browser journey is a flow oracle. Type checks, linters, security scans, and accessibility checks cover other failure classes.

An oracle is useful only when failure is actionable. A test that flakes, an enormous screenshot diff, or a lint rule everybody ignores teaches the agent and the team to treat red as background noise. Keep the signal trustworthy: pin the environment, make failures reproducible, and remove checks that cannot explain what contract was broken. Evidence is a product you maintain, not a pile of commands added once to CI.

At the end of Phase 3 you have a blueprint for a runnable application with zero product features. You have defined its increments and their tests. You have not implemented them yet.

Phase 04Features

Turn screens into contracts

Claude-Lens began with a set of screens, but screens are not a delivery plan. Each capability has dependencies, data requirements, failure states, and non-functional constraints. Map that dependency graph, then cut the work into vertical slices that can be demonstrated and tested independently.

You choreograph the tasks: what must happen first, what can run in parallel, and what evidence unlocks the next step. The dependency graph also tells you where parallel work is real and where it is an illusion. Two tickets that touch the same data contract or migration sequence are not independent merely because they have different titles.

Do not rely on holding the whole system in your head. That understanding is useful, but your head is not versioned, shareable, or visible to a fresh agent session. Keep the dependency map, architecture decisions, domain vocabulary, and invariants in the repository. Hold the system in your head as well, not instead.

Write for a competent stranger with no memory

Pick one capability—the dashboard, for example—and ask the model to inspect the reference, identify components and states, and expose missing decisions. Then turn the conversation into a bounded ticket.

A useful ticket contains:

This is enough detail to constrain implementation without prescribing every line. The old standard was “could a developer who knows this codebase understand the ticket?” The more useful standard for agentic work is: could a fresh session with no conversation history produce the intended result from this ticket and its linked canonical artefacts?

Every assumption left only in chat becomes a coin flip at build time. Write for a competent stranger with no memory, because that is the actual execution condition.

Phase 05Specification & ticketing

Freeze the plan, read it end to end, then partition it

Repeat the feature-definition process for every capability and you are still not implementing. You are reducing ambiguity. When the set is complete, step away, then read the whole path in order: architecture, foundation, features, and release.

Read it as one system because some errors live between tickets. Ticket four and ticket nineteen may each be coherent while quietly contradicting each other. Reviewing them separately will not reveal that. An end-to-end specification review can.

The resulting specification lives beside the code, changes through pull requests, and remains traceable to delivered behaviour. Nothing has shipped, but the implementation is now sliced into work that can be independently understood and verified.

Before publishing the queue, test the specification itself. Trace one user journey across the tickets. Confirm that every dependency has an owner, every acceptance criterion is observable, and every non-goal is actually excluded from downstream work. Look for shared concepts named differently and identical words carrying different meanings. This editorial pass is cheaper than discovering the ambiguity through several inconsistent implementations.

The issue tracker is external memory

Do not feed forty tickets, twenty reference images, and the entire architecture to one agent session. Even when the material technically fits, constraints compete and the response tends toward an average of everything requested. Partition the specification before executing it.

I used GitHub Issues as the execution queue. This can look like bureaucracy, but the issue tracker is the memory the agent doesn’t have. One ticket creates one bounded context carrying what is needed and excluding what is not.

My workflow keeps the detailed issue specification in the repository and publishes a corresponding GitHub issue. The repository copy preserves local orchestration context; the remote copy supports assignment, discussion, status, and pull-request traceability. Canonical links and stable identifiers prevent the two representations from drifting.

Automate the publishing step, but make the automation idempotent. Persist the remote issue number and use explicit states such as draft, published, and closed. Re-running the synchroniser should update the intended issue, never create a duplicate.

That property matters because unattended runs fail partway through. The recovery you want is resume, not restart. Anything an agent runs while you are away should be safe to run again from the top.

Phase 06Build

Now—and only now—the production implementation begins

You now have sequenced issues, bounded context, and explicit gates. Pick the first ticket and ask the agent to restate the requirement, identify ambiguity and risk, and propose a short plan. This is not ceremony. The gap between the ticket and the intended behaviour is cheaper to find in a paragraph than in a diff.

No specification removes discovery. Unexpected constraints will surface during implementation; the plan reduces variance rather than eliminating it. When reality contradicts the spec, update the relevant artefact instead of allowing a private chat correction to become the new source of truth.

Then run the same control loop for every change.

Figure 06 · the per-ticket loop

pick a ticket ↓RequirementImplementCommitPull requestReviewFixMergenext ticket
Requirement check → implementation → commit → pull request → review → fix → merge, then repeat.

Put the evidence on the pull request itself: which tests ran and what they proved, screenshots or traces where relevant, migrations reviewed, known risks, and the rollback path. “Implements #34” is a claim. A pull request carrying reproducible evidence is a claim another person can check without reconstructing your reasoning.

Bugs enter as issues and travel through the same loop. Avoid a side channel for quick fixes straight to the main branch. The process can be lightweight, but it has to remain traceable when the pressure rises.

Parallel work is bought during planning

Some independent tickets can now run in parallel. Git worktrees let one repository check out multiple branches into separate working directories, giving each agent an isolated filesystem view without repeatedly cloning the repository.

The tool does not create independence. The dependency graph from Phase 4 does. Two tickets can run side by side only when their contracts and change surfaces are genuinely separate. Worktrees merely let you spend parallelism that was bought earlier through careful decomposition.

Parallelism is not automatically throughput. Agents in separate directories can still collide through shared APIs, ports, databases, generated files, or incompatible migrations. Cap work in progress at the team’s review and integration capacity, give each task clear ownership, and merge small changes frequently. Three agents producing work nobody can evaluate create a queue, not leverage.

Figure 07 · parallel work with worktrees

main repoone cloneworktree · Afeature 1 · agentworktree · Bfeature 2 · agentworktree · Cfeature 3 · agentreview + mergeback to mainSame Git · separate directories · isolated branches · shared review gates
Independent work fans into isolated worktrees, passes the same gates, and converges through review.

Decide what the agent is allowed to touch

An agent that edits files, runs commands, opens pull requests, and reaches the network has a real blast radius. Define that authority before a task needs it.

Apply least privilege: use a disposable environment, keep production credentials out of the workspace entirely, separate read from write, require approval for destructive or external actions, and protect the main branch. OWASP’s AI Agent Security Cheat Sheet recommends the same core pattern: minimum tools, scoped permissions, and explicit authorisation for sensitive operations.

The exact boundary depends on the organisation and task. A useful starting policy is to divide actions into three tiers: permitted because they are cheap to undo; permitted only after human approval; and unavailable to the agent under any circumstance. Write the boundary down so it remains stable across sessions.

Permissions should be task-specific rather than permanent. A dependency audit may need network reads but no repository writes. A refactor may need broad local edits but no external service. A release task may require a narrow deployment capability with a separate approval. Grant the smallest useful capability for the shortest useful time, and capture external actions in an audit trail.

Figure 08 · agent authority

AllowedNeeds human approvalNeverread the repoedit its worktreerun the test suiteopen a pull requestwrite to scratch datacheap to undoinstall a dependencyrun a migrationcall an external servicedelete anythingspend moneyreversible, but not freeproduction credentialsthe production databasemerge to maindeploythe engineer’s machineyou own the consequencesDecide the boundary before you need it, not after
Treat tool access as an explicit policy. The examples will vary, but the three-tier boundary should exist before execution begins.

Phase 07Ship

Shipping is a hypothesis, not the finish line

A feature that passed CI can still fail under real data, latency, permissions, and user behaviour. CI demonstrates that the system works under conditions you anticipated. Production tests it against conditions you did not.

Define the production check before deployment. Which metric should move? What error rate or budget is acceptable? Which signal and dashboard will reveal a regression? What is the rollback path, in a sentence somebody can follow during an incident? Without those answers, a deployment is closer to hope than an experiment.

Instrument the system as part of the feature, not as post-release cleanup. OpenTelemetry describes observability through signals such as traces, metrics, and logs. Correlate those signals with the release and with the acceptance criteria. That connection tells you whether the behaviour you specified is the behaviour users received.

Then close the loop. Compare production evidence with the intended outcome. Turn surprises into tickets. When the system reveals that an assumption or specification was wrong, update the artefact. A specification that stops being true after deployment was a plan; a living specification incorporates what the system teaches you.

Do not confuse more telemetry with better feedback. Start from the user outcome and work backwards to the smallest set of signals that could confirm or disprove it. A dashboard full of infrastructure graphs may show that the service is healthy while users still cannot finish the journey. Operational health, product behaviour, and business outcome are related, but they are not interchangeable.

Figure 09 · production closes the loop

Specacceptance criteriaBuildtests · review · mergeShipwith a stated checkEvidencetraces · metrics · logssurprises become tickets · the spec gets correctedBefore deploy: which number moves · what error rate is tolerablewhich signal shows a regression · what the rollback is
Production evidence is not the end of the pipeline. It is input to the next turn of it.

AsideWhat rigor leaves behind

Prefer decisions that produce inspectable artefacts

The more rigorous version of a step usually leaves something another person or session can inspect. That does not mean maximising documentation. It means choosing the smallest durable artefact that preserves the decision or checks the claim.

PhaseFragile shortcutDurable alternative
ConceptStart with a framework scaffoldPrototype the journey and freeze visual references before choosing the stack
ArchitectUse the familiar technologyRecord alternatives, rejection reasons, consequences, and a revisit trigger
FoundationBegin with disconnected layersPlan a walking skeleton whose every increment boots
TestingSubstitute a convenient fake everywhereExercise important boundaries against the real kind of service
FeaturesKeep a list of screen namesBuild a dependency graph and independently verifiable slices
SpecificationLeave assumptions in chatPut canonical context, invariants, and acceptance criteria in the repository
TicketingPublish issues manuallyUse a resumable, idempotent synchroniser with stable identifiers
BuildReport that tests passedAttach commands, results, screenshots, traces, risks, and rollback to the pull request
AuthorityAccept the tool’s default accessWrite explicit allowed, approval-required, and unavailable capabilities
ShipDeploy and watch for complaintsState the expected metric, tolerated failure, regression signal, and rollback first

The heuristic is simple: if a consequential decision or claim leaves no inspectable trace, a later engineer—and a later agent session—will have to guess.

LimitsWhere this process does not fit

Use the lifecycle in proportion to the risk

This approach is front-loaded. You can spend days defining a product before the first production feature commit. If the concept is wrong, you may create a beautifully structured specification for the wrong thing. It is poorly suited to genuine exploration and too heavy for a two-hour utility.

For exploratory work, vibe coding can be exactly right: prompt, inspect, adjust, and learn quickly. Build a disposable spike, answer the uncertain question, throw the spike away, and begin this lifecycle only if the result deserves to become a maintained system. The mistake is not improvisation; it is allowing exploratory code and undocumented assumptions to harden into production architecture.

The lifecycle does not eliminate agent failure either. Agents still misread tickets, generate confident but wrong code, and sometimes claim a gate passed when it did not. Specifications and oracles reduce how often those failures reach the main branch. The moment you trust a “done” message instead of checking the evidence, you discard that benefit.

There is a cognitive cost as well. Holding a system model, evaluating parallel work, and judging evidence can be more demanding than writing the code yourself. The promised relief may not arrive. What arrives is leverage, and leverage must be controlled.

WrapThe difference

The difference is not the model. It is the control loop.

Vibe coding and agentic engineering can use the same model, tools, and prompts. The distinction is the system wrapped around them: explicit intent, architecture, bounded tasks, a versioned specification, executable acceptance criteria, constrained execution, independent review, and production feedback.

Vibe coding optimises for time-to-first-working-thing. That is useful for a prototype or a script you will discard. Agentic engineering optimises for time-to-safe-change. The seven phases exist to make the second change as understandable as the first, and the twentieth as safe as the second.

The practical test is what survives the session. An improvisational session may leave working code whose reasoning is difficult to reconstruct next month. A controlled lifecycle leaves a specification, dependency graph, decision records, test suite, closed issues, review trail, and production evidence. The code is one artefact among many, and often not the most durable one.

The paper trail is the proof

When building in public, the live application proves that something compiled and ran. The stronger signal is the paper trail: sequenced issues with real acceptance criteria; an architecture record that names rejected options and why; integration tests against the real kind of service; pull requests carrying review evidence; and a history in which each implemented increment boots.

Publish the decisions you got wrong too—the architecture you revisited, the ticket that became three tickets, the acceptance criterion production disproved. A trail with no reversals is not necessarily evidence of judgment. It may only be evidence that the hard decisions were hidden.

That record cannot be produced convincingly by somebody who skipped the thinking. It shows how uncertainty was reduced, how risk was bounded, and how claims were checked. The thinking is the asset, not the typing.

References

Claude-Lens · built in public · agentic software engineering