Part 4 of Building My Agentic Workflow in Public. Part 3 gave parallel agents a shared contract so their work stayed coherent. That coherence is what let me finally take my hands off the wheel.
By this point I had the pieces: a process, session handoffs, sub-agents, a contract, and an evaluator that could not cheat. What I wanted next was simple to say and hard to do. Hand the system a batch of features, walk away, and come back to something that is 85% to 90% done.
First, stop the interruptions
The first thing in the way was permission prompts. The model would stop and ask me before running this command or writing that file, over and over, for things I had already decided were fine. You cannot walk away from something that needs you every ninety seconds.
So I told it, up front, what it is allowed to do without asking. Each project has a settings file with an accept list and a deny list, and there is a global version too. Safe, routine actions just happen. Genuinely risky ones still stop and ask. It is the difference between a coworker who checks in on every keystroke and one who knows the house rules and only flags the real decisions.
This is the unglamorous half of autonomy. Before a system can run on its own, you have to be explicit about its boundaries, and you have to mean it.
An honest caveat: this helped, it did not fix everything. My rough guess is it removed about half the interruptions, not all of them. The stubborn ones cluster in one place: running the small scripts that live inside my skills. Whether a script sits in the shared toolkit I reuse across projects or inside the project itself, it still stops to ask more often than I would like. So the allow list bought me real quiet, not silence. Untangling the rest is a thread I pick up in Part 6.
Here is a trimmed version of that settings file. The allow list is what runs without a prompt; the
deny list is what always stops and asks, no matter what:
A project's permission rules (sanitized .claude/settings.local.json)
Routine, safe actions sit in allow and just happen. Anything genuinely destructive goes in deny and is stopped even if the model thinks it needs it. Everything not listed still falls back to asking me.
{
"permissions": {
"allow": [
"Bash(ls:*)",
"Bash(git status:*)",
"Bash(python -m pytest:*)",
"Bash(uv pip install:*)",
"mcp__playwright__browser_navigate"
],
"deny": [
"Bash(rm -rf:*)",
"Bash(git push:*)"
]
}
}The loop
Then comes the part that does the work. One command, /ship-features, takes a range of features and
runs a loop over them. It does a few preflight checks, aligns everything to the contract once up front,
and then walks each feature through the same cycle: evaluate the ticket, fix anything blocking, build
it, evaluate the result, fix anything blocking, move on.
flowchart TB
START["/ship-features F1.1 through F1.8"]
START --> PF["Preflight, then align everything<br/>to the contract once"]
PF --> LOOP["Take the next feature"]
LOOP --> CYCLE["Run the eval-and-fix cycle"]
CYCLE --> NEXT{"More features?"}
NEXT -->|yes| LOOP
NEXT -->|no| DONE["Batch done"]
CYCLE -.->|"too many tries: hard stop"| STOP["Pause and ask me"]The inner cycle is where Part 1’s lesson shows up again. The ticket gets evaluated before it gets built, so blocking problems in the spec are caught while they are cheap. Only a clean ticket gets implemented. Then the result gets evaluated too. Both checks can repeat, but only a few times.
flowchart TB T["A feature ticket"] T --> E1["/eval-ticket<br/>vet the ticket"] E1 -->|"blocking (up to a few tries)"| FIX1["tighten the ticket"] FIX1 --> E1 E1 -->|clean| R["/run-feature<br/>build it"] R --> E2["/eval-ticket<br/>review the result"] E2 -->|"blocking (up to a few tries)"| FIX2["fix the code"] FIX2 --> E2 E2 -->|clean| OK["Feature done"]
Why it can’t lie to itself
A loop that runs on its own needs a hard floor, or it will happily try the same broken fix forever. So the counters that track “how many tries has this had” are not kept in the model’s head. They live in small deterministic scripts. The model cannot talk itself into a fourth attempt when the limit is three, because it is not the one counting. After enough failed tries, the whole batch stops and asks me, instead of burning hours pretending to make progress.
This is the part I am most glad I built. The scary thing about an autonomous loop is not that it makes mistakes. It is that it can hide them from you while spending your time and money. Putting the limits in code that the model cannot soften is what makes walking away actually safe.
The subtle bug that almost sank it
One detail nearly stopped all of this, and it is worth telling because it is not obvious. I first built the orchestrator, the thing that runs the loop and calls the specialists, as a sub-agent. At first it seemed to work, which is exactly what fooled me. When a batch only needed small edits, the orchestrator could make them itself and keep going. The trouble showed up the moment a feature needed real building work handed off to a specialist. The platform has a rule: a sub-agent cannot spawn its own sub-agents. My orchestrator needed to spawn the evaluator and the implementer, and it could not, because it was itself a sub-agent.
The fix was to move the orchestrator out of that layer. Instead of a sub-agent, it became a skill that runs in the main session. The main session is allowed to spawn sub-agents, so from there the orchestrator could call the evaluator and implementer it needed.
flowchart TB M["Main session<br/>runs the orchestrator as a skill"] M -->|can spawn| EV["dev-evaluator"] M -->|can spawn| IM["dev-implementer"]
It sounds like plumbing, and it is, but it was the difference between an autonomous loop that worked and one that was stuck one level too deep to do its job.
It actually ran
I tried this on a real project, a trading site I build for myself, and handed it a run of features, one after another. It worked through them mostly on its own: vetting each ticket, building it, checking the result, fixing what it could, and only coming back to me when it hit a wall it could not get past in a few tries. Not perfect, and not zero-touch, but a genuine batch of work moved from “I drive every step” to “I review the outcome.”
What this phase bought me
This is where the workflow stopped being a set of tools I operate and started being a system that operates itself, within limits I set. Permissions decided up front. A loop that evaluates before and after it builds. Counters it cannot fudge. Hard stops it cannot skip. And an orchestrator in the one place that could actually run it.
It is also where a new question opened up, the one Part 5 is about. I built this loop out of commands, skills, and sub-agents, which keeps it simple and portable. But there is another way to run a loop like this, one that trades that simplicity for a lot more raw power. I am in the middle of comparing the two, and I will show you what I am finding.