I Built the Pipeline That Built My Product. Here Is What Delegation Actually Takes
Listen to this article (experimental) · 11:53

Yesterday I typed one sentence, "Add CSV export for completed tasks", pointed a CLI at a small task-tracking API, and walked away. Twelve minutes later there was a plan with acceptance criteria, five tickets on a board, an approval record, and a draft pull request with a working endpoint, a new module, tests, and updated docs: 1,190 lines added across a coherent branch, one commit per task, waiting for review.
The itch is older than the tooling. Anyone who has worked scrum in a company has had this thought during sprint planning: the user stories are sitting right there, refined and estimated, so how nice would it be if an AI took the first iteration, and we spent our time reviewing the PR, running it locally to check everything works, adjusting, and going around again. Does it actually save time? Hard to tell, and I did not build this to win that argument. I built it to make the experience real, and left the impact question for later.
In an earlier post I wrote about Smithproof, the control plane I built to govern AI agents. This is the story of the tool on the other side of that relationship: plan-to-pr, a command-line pipeline that turns a natural-language feature request into reviewed pull requests. The two are more entangled than a demo pairing. The pipeline helped build the control plane, and the control plane now governs the pipeline. Before either of those, though, it earns its own field report, because building it taught me what delegating to coding agents actually takes.
What the pipeline is
Four stages, each with an artifact:
| Stage | What happens | What survives it |
|---|---|---|
| Plan | An LLM classifies the intent, enriches the request against a repo scan, and emits epics, tickets, and tasks with acceptance criteria | plan.json, validated against a schema |
| Tickets | One ticket per task lands on the board (Trello or GitHub Issues) | Cards a team can see and reassign |
| Approve | A human gate; rejection is exit code 4 | approval.json: who, when |
| Execute | Each ticket gets its own branch; an agent generates code task by task | One draft PR per ticket, linked back to its ticket |
The plan run from the demo. The plan itself is committed to a dedicated branch, never to main.
The state that ties it together lives in a .plan-to-pr/ folder inside the target repository: the plan, the ticket mappings, the execution results. Runs are resumable; a completed task is never redone. None of this is exotic. That is the point. The pipeline is deliberately boring orchestration around a model that is anything but predictable.
Execute, end to end: one branch per ticket, one commit per task, one draft PR with the checklist in its description.
Why a pipeline and not a chat
I use interactive coding agents every day, and the obvious question deserves a straight answer: why not open Claude Code, use plan mode, and let the session do all of this?
For exploration, a one-off fix, or anything where I want to steer mid-flight, the interactive session wins and it is not close. But delegation is a different job than collaboration, and the difference shows up in five places.
Artifacts are the default, not a favor. A capable chat agent will write a plan file and create tickets on the board if you ask, and a well-written skill will make it do so every time you remember to invoke it. The difference is where the guarantee lives. A skill is still a set of instructions the model almost always follows; a pipeline stage is code that cannot be skipped. Push the skill approach to its logical end, hooks that block execution until an approval file exists, a script for every stage, and what you have built is this pipeline with a chat window bolted on the front. When I come back three days later, the state of the world is on the board and in the branches because nothing else was possible, not because everyone remembered to ask.
A gate is not a question. An interactive agent asks "does this look right?" and the truth is that after enough approvals you stop reading. The pipeline's approval step is a hard stop with an exit code. Nothing executes until approval.json exists, and rejection is a first-class outcome, not a polite message.
Work units are reviewable units. The pipeline emits one PR per ticket, each small enough to actually review, each linked to the ticket that motivated it. A long agent session tends to produce one sprawling diff shaped by the order the conversation happened to take.
Control flow belongs in code. In the pipeline, the model writes plans and code inside well-defined steps, but the loop itself, what runs, in what order, what happens on failure, is deterministic Python. When something breaks at two in the morning, I debug a pipeline, not a personality.
A pipeline can be governed. This is the one I care most about, and it gets its own section below. A CLI with defined entry points can be registered, observed, budgeted, and refused. A freeform session is much harder to say no to.
None of this is an argument against interactive agents. It is an argument that "have the agent do it" splits into two products: a collaborator you steer, and a delegate you hold to account. The second one needs receipts.
The two coexist in my own work, and this very project used both. The final polish on the tool came from Claude Code with Fable 5 and Opus 4.8, models that are faster and stronger than what the pipeline runs, on a Max subscription where I stop counting tokens. When cost is flat and a human is at the keyboard, the interactive agent wins the sprint. The pipeline takes longer end to end, I suspect, but it runs on a free-tier model, its cost per feature is a number you can read off a dashboard, and its behavior is the same on the tenth run as on the first. Cheaper and more predictable is not the glamorous half of the trade, but it is the half that matters once the human steps away and something has to cap the spend.
The hard part is partitioning, and the fix lives in a prompt
The deepest technical lesson came from the execution model.
Each ticket executes on a fresh branch forked from the base branch. Tickets do not see each other's files, because their branches have not merged yet. The first consequence arrives fast: if the planner groups work by layer, all models in one ticket, all endpoints in another, every branch imports files that exist only on a sibling branch, and nothing runs.
The fix is not in the executor. It is in the planner's system prompt, which teaches the model the tool's own execution topology: code from other tickets is not on disk; if file A imports file B, both belong to the same ticket; group work as vertical slices; put shared foundations in ticket one. The prompt even names the anti-pattern, one ticket for all models and another for all schemas, as a guaranteed import failure. In the demo run, the planner bundled the storage function, the CSV module, the endpoint, and the tests into a single ticket precisely because they are one dependency chain.
I find this genuinely interesting as a design artifact: the constraint is infrastructural, the solution is linguistic, and the two evolved together through observed failures. Two smaller pieces round it out. During execution, a discovery pass classifies which files to create, modify, or read as convention context, and the code generator can read files from other completed tickets' branches with git show, cross-branch awareness without ever checking them out. And because LLM output is hostile to parsers, the plan and code responses pass through a small defensive stack: sentinel markers, four extraction fallbacks, an escape repairer for regex-in-JSON, and a retry loop that feeds the parse error back to the model at lower temperature. Every one of those exists because something real broke.
The receipts
The reason I trust this pipeline is that it has shipped work I then had to live with. Parts of Smithproof, the human-review subsystem and the background-task machinery among them, arrived as plan-to-pr tickets and draft PRs, with the machine-generated branch names still in the history. The codegen prompt still carries the scar tissue: conventions about the exact base classes and schema styles of that codebase, added after runs that got them wrong.
That cuts both ways, and it is worth saying plainly: the pipeline is best at well-scoped feature work in codebases with clear conventions. It is not the tool for gnarly refactors or for problems I cannot yet describe in a sentence. Knowing which half of the work is which turns out to be the actual skill of delegating to agents.
The loop closes
Here is my favorite part. plan-to-pr now runs as a governed agent under Smithproof, the control plane it helped build.
When the governance settings are present, every pipeline invocation registers itself on the control plane, opens a run before doing any work, and reports its model calls, with latency measured and cost estimated, when the run closes. The console shows the pipeline in the fleet like any other agent: three runs yesterday, ten model calls on the execute leg, about five cents of spend.
The pipeline's runs in the control plane's observability view, next to the rest of the fleet.
The part that matters is not the telemetry. It is that the control plane can say no, and the pipeline listens. Quarantine the agent in the console and the very next invocation stops at the door, before a single model call or file change:
Exit code 5. The reason comes from the control plane, not the tool.
The failure semantics took one real decision: deny on decision, warn on outage. If the control plane explicitly refuses the run, the pipeline aborts. If the control plane is unreachable, the pipeline warns loudly and proceeds, because telemetry should never be the reason a build breaks, but a decision to block must always be honored. In the last post I wrote that a policy is a decision and enforcement is an architecture. This is what the enforcement side feels like from inside the governed process, and having built both halves, I can report the loop is more satisfying than either half alone.
Where it stands
plan-to-pr today plans against any git repository with only a free Gemini key, puts tickets on Trello or GitHub Issues, and opens draft PRs on GitHub, with ClickUp, Azure DevOps, and GitLab on the provider roadmap. The governance integration estimates token counts from text lengths until the LLM layer surfaces exact usage, and the runs are tagged accordingly. The pipeline still logs a warning when it fails to re-commit its state folder to the plan branch mid-execution, a checkout conflict that is next on the fix list. Field reports should include the field.
If there is one thing to take from this build: the model was never the hard part. The hard parts were the same ones as in any delegation, decomposition, gates, accountability, and the humility to keep receipts. The tools that make agents useful for real work will look less like smarter chat windows and more like this: pipelines with artifacts, run by agents, governed by something that can tell them no.
Questions this article answers
Why not just use an interactive coding agent in plan mode instead of building a pipeline?
For exploration, a one-off fix, or anything you want to steer mid-flight, the interactive session wins and it is not close. The pipeline is for the other job, delegation, where a stage is code rather than an instruction the model almost always follows, and where the state of the world sits on the board and in the branches three days later because nothing else was possible. Push the skill-and-hooks approach to its logical end and what you have built is this pipeline with a chat window bolted on the front.
How do you stop tickets that run on separate branches from breaking each other?
You fix it in the planner, not the executor. Each ticket executes on a fresh branch forked from the base, so code from a sibling ticket is not on disk, and the planner's system prompt teaches exactly that: if file A imports file B, both belong to the same ticket, group work as vertical slices, put shared foundations in ticket one. In the demo run the planner bundled the storage function, the CSV module, the endpoint and the tests into a single ticket because they are one dependency chain.
Is it actually faster than doing the work with an interactive agent?
Probably not, and that was never the case for it. The interactive session with the stronger models wins the sprint when cost is flat and a human is at the keyboard; the pipeline takes longer end to end but runs on a free-tier model, its cost per feature is a number you can read off a dashboard, and it does the same thing on the tenth run as on the first. Cheaper and more predictable is the half that matters once the human steps away and something has to cap the spend.
Can you actually stop an agent pipeline once it is running?
Yes, and from outside the tool. Every invocation registers on the Smithproof control plane and opens a run before doing any work, so quarantining the agent in the console makes the next invocation stop at the door with exit code 5, before a single model call or file change. The failure semantics are deny on decision, warn on outage: an explicit refusal aborts the run, while an unreachable control plane only warns and proceeds, because telemetry should never be the reason a build breaks.
Read next
AI AgentsI Built an AI Layer That Never Answers. Here Is What a Good Question Actually Takes
Field notes from OrbitSpec, a clarification engine that turns vague requests into typed, auditable Context Bundles before any model runs, and why reliability starts before the answer.
AI AgentsI Built an AI Agent Control Plane. Here Is What Governance Actually Takes
Field notes from building Smithproof, a multi-tenant control plane for AI agents, and why detection without response and policy without enforcement govern nothing.