Agentic Workflows: How I Ship Software Without Reading the Code
An agentic workflow is a process in which AI agents plan, act, check their own results, and loop until a goal is met, instead of answering one prompt and stopping. A chatbot writes a function when you ask. An agentic workflow takes a goal, splits it into steps, runs tools, reads the output, and tries again when the output is wrong.
I build software this way every day. I don't read the code anymore. I write the goal and the rules, agents do the work, and I judge the evidence they hand back. This post covers the loop I use, the rules that keep it honest, and a real example: ptop, a Rust copy of vtop that went from nothing to a 69/69 fixture-parity build in about four hours on a random workday.
Agentic Workflow vs. a Single Prompt
| Single prompt | Agentic workflow | |
|---|---|---|
| Input | One request | A goal plus constraints |
| Steps | One answer | Plan, act, observe, repeat |
| Tools | None, or one call | Shell, tests, browser, git, APIs |
| Checks its work | No | Yes: tests, reviews, measurements |
| Stops when | It has answered | A verifiable condition passes |
The difference that matters is the last row. A single prompt stops when it has produced text. An agentic workflow stops when something outside the model says the work is done: a test suite passes, a benchmark hits a number, a reviewer finds nothing.
The Loop I Use
Every non-trivial job runs in waves. Each wave has one job, and the next wave doesn't start until the last one hands back evidence.
- Orchestrate. One agent holds the plan. It reads the goal, decides what can run in parallel, and writes a tight brief for each piece of work. It doesn't wander through the codebase itself.
- Implement. Worker agents each get a narrow slice: these files, these steps, this many tool calls, this done condition. Parallel workers get slices that don't overlap.
- Review. A different agent reviews the diff. The reviewer is never the implementer. This is the wave that catches real bugs, so I don't skip it to save tokens.
- Fix. Small findings get fixed inline. Large ones become new tight briefs.
- Re-review. Only when the fix wasn't mechanical or isn't covered by tests.
A task is not done until checks, types, lint, tests and build actually pass. "Should pass" doesn't count. The agent has to show the passing output.
The Rules That Make It Work
Agents fail in predictable ways. They widen the task, re-explore what they already know, claim success without proof, and agree with whatever hypothesis you hand them. Most of my rules exist to close one of those holes.
This is a trimmed excerpt of the CLAUDE.md I load into every session:
## Role: orchestrate; delegate to many tight agents, never one free-roamer
Every sub-agent starts from zero and re-derives what you already know.
Spend a spawn only when it returns more than it costs.
Delegate when the task is:
- Parallelizable into disjoint slices.
- Large enough that doing it inline would flood your context.
- An independent review of work you or another agent produced
(the reviewer must not be the implementer).
Do it yourself when the task is a single-file change, a known-recipe fix,
or small enough that briefing an agent would take as long as doing it.
## Tight brief (required)
Every prompt contains all four, or do not spawn:
- Scope: the exact files it may read or touch.
- Plan: the numbered steps, written by you.
- Tool-call limit: stated in the prompt, with the overrun rule.
- Done criterion: one verifiable condition that ends the job.
Tool-call ceilings: lookup <= 8, single-file change <= 15,
multi-file slice <= 30, independent review <= 25.
Over the top tier means split into more agents.
On reaching the limit unfinished, the agent stops and reports BLOCKED:
what is done, what is left.
End every brief with an exact final line:
===RESULT=== <PASS|FAIL|DONE|BLOCKED|NO-OP> — <one-sentence summary>
Three of these rules do most of the work:
- Tool-call ceilings. An agent with no limit explores forever. A ceiling forces the orchestrator to slice the job small enough to finish, and a BLOCKED report tells me exactly what's left.
- A done criterion that a machine can check. "Make it work" never ends. "
just proofexits 0" does. - Reviewer is not the implementer. An agent reviewing its own diff tends to confirm it. A fresh agent, or a different model, reads the code without the author's assumptions.
Example: Rebuilding vtop in Rust
vtop is a Node.js terminal monitor with dotted braille graphs. I liked it and wanted it lighter, so I had agents rebuild it in Rust as ptop. I used both Claude Code and Codex on it.
1. Write the contract before any code
The first artifact wasn't code. It was a spec, then a contract file the repo calls its "Implementation Law." The contract's core rule:
When spec and oracle disagree, the oracle wins.
The oracle was vtop 0.6.1's actual source, read line by line. That rule turned "make it like vtop" into something an agent can check. It also surfaced bugs nobody would have guessed. For example, vtop's macOS memory math writes total / 1024 ^ 2. In JavaScript ^ is XOR, not exponent, so the expression doesn't do what it looks like. The contract says to reproduce it by default and correct it behind a --fix flag.
2. Make "done" a command
The done criterion was a test harness. It runs the original vtop application source with only its sensors, clock and side effects swapped out, renders frames, and compares them with ptop's output byte for byte. just proof fails if any expected fixture is missing.
The first tagged build, ptop 0.1.0, hit 69/69 fixture parity. Later, the harness grew to 90 frames checked against the upstream application, plus command-line, key-handling and terminal-restoration tests.
3. Audit, then fix what the audit found
After parity, a review pass went over the code. The commit that followed is titled "fix audit findings": zoom wiring, true diff-based terminal output, panic-proofing against hostile input, input retries, a load-average guard, and CLI argument hardening.
Two more bugs only showed up in live use, because the fixtures inject sensor values:
- Live CPU sampling broke on modern macOS. The
kern.cp_timesysctl is gone, so sampling had to switch to Mach'shost_processor_info. - Memory was off by a unit. The Node library vtop uses reports total memory in MiB, not bytes.
4. Don't trust a green suite
The best lesson came after the tests passed. A screenshot from a real session showed purple blocks bleeding into graph history and behind unselected rows. Every frame fixture was correct. The bug lived in the live terminal output: the emitter assumed default colors at each row, but terminals keep color state across cursor moves, so the selected row's background leaked.
The fix reset color state before each changed row. More important, it came with proof: a regression test that fails on the old emitter and passes on the new one, plus an independent replay in tmux. The old binary leaked 111 to 230 colored cells. The fixed one kept exactly the 66 intended cells.
The repair notes also say, in so many words, that the earlier frame-only proof "did not establish continuous live visual parity." That's the kind of sentence I want from an agent: what the evidence proves, and what it doesn't.
5. Measure, then publish
Performance numbers stay tied to the exact binary they measured, and the raw results are kept in the repo. In a matched test on my M2 Max, ptop used about 73% less CPU and 92% less own-process memory than vtop. The ptop write-up has the method, and the four-monitor comparison puts it next to htop and btop.
What Goes Wrong
- One big agent. A single agent told to "build vtop in Rust" explores, forgets, and declares victory. Many narrow agents with done conditions finish.
- Vague briefs. Every sub-agent starts from zero. If the brief doesn't name the files, the agent spends its budget rediscovering them.
- Self-review. It finds less than a fresh reviewer does.
- "Should pass." If the agent didn't run the check, the check didn't pass.
- Tests as the finish line. Tests prove what they cover. ptop's purple-background bug passed every fixture.
How to Start
You don't need my whole setup. Start with three habits:
- Give every task a done condition a command can check.
- Have a second agent review every non-trivial diff.
- Make agents show the passing output, not describe it.
Once those stick, add parallel workers in separate git worktrees so they can't overwrite each other, and give each one a tool-call budget.