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:
- The model proposes options, drafts code, and runs tools.
- The gates test claims before they become changes.
- The engineer defines the system, constrains the work, judges the evidence, approves risk, and owns the outcome.
Figure 01 · the operating contract
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
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
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
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
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:
- the user outcome and relevant business rule;
- inputs, outputs, routes, data ownership, and invariants;
- acceptance criteria, including empty, loading, error, and permission states;
- the test strategy and evidence that must appear on the pull request;
- performance, accessibility, security, and observability constraints where relevant;
- dependencies, explicit non-goals, and rollback considerations.
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
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
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
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
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.
| Phase | Fragile shortcut | Durable alternative |
|---|---|---|
| Concept | Start with a framework scaffold | Prototype the journey and freeze visual references before choosing the stack |
| Architect | Use the familiar technology | Record alternatives, rejection reasons, consequences, and a revisit trigger |
| Foundation | Begin with disconnected layers | Plan a walking skeleton whose every increment boots |
| Testing | Substitute a convenient fake everywhere | Exercise important boundaries against the real kind of service |
| Features | Keep a list of screen names | Build a dependency graph and independently verifiable slices |
| Specification | Leave assumptions in chat | Put canonical context, invariants, and acceptance criteria in the repository |
| Ticketing | Publish issues manually | Use a resumable, idempotent synchroniser with stable identifiers |
| Build | Report that tests passed | Attach commands, results, screenshots, traces, risks, and rollback to the pull request |
| Authority | Accept the tool’s default access | Write explicit allowed, approval-required, and unavailable capabilities |
| Ship | Deploy and watch for complaints | State 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
- Git: git-worktree documentation
- Testcontainers: testing with real disposable services
- Playwright: visual comparisons
- OWASP: AI Agent Security Cheat Sheet
- OpenTelemetry: observability primer
Claude-Lens · built in public · agentic software engineering