<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Dani Shemesh</title>
    <description>The latest articles on DEV Community by Dani Shemesh (@dani_shemesh).</description>
    <link>https://dev.to/dani_shemesh</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4122543%2F6ff35884-4831-4ea1-8c5f-42af2447440b.png</url>
      <title>DEV Community: Dani Shemesh</title>
      <link>https://dev.to/dani_shemesh</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dani_shemesh"/>
    <language>en</language>
    <item>
      <title>Should you harness the harness: so, should you?</title>
      <dc:creator>Dani Shemesh</dc:creator>
      <pubDate>Tue, 22 Sep 2026 14:32:05 +0000</pubDate>
      <link>https://dev.to/dani_shemesh/should-you-harness-the-harness-so-should-you-2jh0</link>
      <guid>https://dev.to/dani_shemesh/should-you-harness-the-harness-so-should-you-2jh0</guid>
      <description>&lt;p&gt;Four parts of machinery. This one is the decision.&lt;/p&gt;

&lt;p&gt;It is a yes or no. Either the agent runs your procedure, or your procedure runs the agent. Neither side is only one thing: an orchestrator skill is just the pattern the previous series happened to use, and on the other side a &lt;code&gt;Makefile&lt;/code&gt; that calls the coding agent at each step is already harnessing the harness. Nobody sells that one, and it works.&lt;/p&gt;

&lt;p&gt;This part names no winner. Tools change every few months. Where a procedure should live does not.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Assumes parts one and two. Three and four are a worked example, and are optional.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually justifies the move
&lt;/h2&gt;

&lt;p&gt;First, the ledger. Set the gains out plainly and most of them shrink.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What you gain&lt;/th&gt;
&lt;th&gt;What it really is&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Parallel nodes&lt;/td&gt;
&lt;td&gt;the same work, sooner&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Run history, per-node cost&lt;/td&gt;
&lt;td&gt;reporting you did not have&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Worktree isolation with a real lock&lt;/td&gt;
&lt;td&gt;a script you did not get round to writing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Provider and model per node&lt;/td&gt;
&lt;td&gt;an option you will rarely exercise&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Steps with no model in them&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;a guarantee you could not previously make&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Run state that outlives the process&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;something that did not exist before&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The top four are good reasons to want an engine and bad reasons to change architecture. The bottom two are different in kind, and between them they decide it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Steps that nothing can skip
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://fullgc.github.io/should-you-harness-the-harness-part-1/" rel="noopener noreferrer"&gt;Part one&lt;/a&gt; established the limit: inside a session no step is one the model sits out, and the decision to reach an exact command is itself a judgment with a failure rate.&lt;/p&gt;

&lt;p&gt;Outside, a shell step runs because the program says so. Nothing in the path is capable of skipping it, so nothing does.&lt;/p&gt;

&lt;p&gt;So count your steps. Run the migration. Boot the environment. Post the comment. Tag the release. If most of your procedure needs no intelligence at all, you are paying a round trip and a small probability of omission for every one of them, and the omissions are the expensive part, because a step quietly skipped leaves nothing behind that says so.&lt;/p&gt;

&lt;p&gt;This is the cheap problem. Moving the procedure out fixes it and buying something does not, so if it is the only thing pushing you, that &lt;code&gt;Makefile&lt;/code&gt; is the entire answer.&lt;/p&gt;

&lt;h3&gt;
  
  
  Run state that outlives the process
&lt;/h3&gt;

&lt;p&gt;Autonomy looks like the argument here: a skill needs a person to start it, so anything unattended needs an engine.&lt;/p&gt;

&lt;p&gt;It does not hold. &lt;code&gt;claude -p&lt;/code&gt; expands a skill invocation in the prompt string, there is a documented flag for suppressing permission prompts when nobody is there to answer them, and running the whole thing from CI has its own page in the manual.&lt;/p&gt;

&lt;p&gt;The gates are easier still. Tell the agent not to stop at them and it will not, with the usual caveat that an instruction to a model is a strong default rather than a guarantee. A cron entry can drive the procedure end to end. Running it unattended may raise subscription and licensing questions, which the last section of this part deals with, but those are billing problems rather than missing capabilities.&lt;/p&gt;

&lt;p&gt;So the trigger is not what is missing. What is missing is that the thing you started is a &lt;em&gt;session&lt;/em&gt;: it lives in one process, on one machine, and dies with it. Write the same procedure's progress to a database and it becomes an object with a life of its own.&lt;/p&gt;

&lt;p&gt;Which buys four different things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It survives the crash.&lt;/strong&gt; A failed run resumes from the node that failed, because something outside the process recorded which nodes finished.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It can hold a lock.&lt;/strong&gt; A run that has reserved a worktree stops a second run walking into it, including a second run of your own.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Anyone can read it.&lt;/strong&gt; A colleague approving a gate from Slack, a board showing what is in flight, a query asking what ran on Tuesday and what it cost.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It can be replayed.&lt;/strong&gt; Completed nodes emit events, so a restart consults what actually happened rather than a cursor's opinion about where the run had got to.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;So: does anything need to know about a run besides the process executing it?&lt;/strong&gt; Another person, another run, or the same run tomorrow after a crash. If yes, you need that state somewhere durable, and a &lt;code&gt;Makefile&lt;/code&gt; stops being enough.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adopt, or write one by accident
&lt;/h2&gt;

&lt;p&gt;A CI runner gets you a long way: run ids, readable logs, approval gates, retries, artifacts. What it lacks is the half that knows what a coding agent is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a session threaded from one step to the next, and forked rather than mutated on a retry;&lt;/li&gt;
&lt;li&gt;a worktree lock with an owner and an expiry;&lt;/li&gt;
&lt;li&gt;a provider resolved per step.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can write all of that. &lt;a href="https://fullgc.github.io/should-you-harness-the-harness-part-1/" rel="noopener noreferrer"&gt;Part one&lt;/a&gt; already said what happens next, and it is the whole of the second decision: &lt;strong&gt;writing that lifecycle well means writing a workflow engine badly, in the margins of doing something else.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So the rule is unglamorous. Build while the missing pieces are ones you can name and would not miss. Adopt once the list starts to look like a product.&lt;/p&gt;

&lt;p&gt;In practice nobody arrives at an engine by evaluating engines. They arrive by noticing that their pipeline has grown a state file, then a lock, then a retry policy, then a resume, and asking why they are maintaining all that.&lt;/p&gt;

&lt;h2&gt;
  
  
  The conversation you give up
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhf27b8q0q8utjfd0dxpv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhf27b8q0q8utjfd0dxpv.png" alt="The two guys on a bus meme. The miserable man on the grey side is labelled " width="383" height="340"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The friction is one property rather than a list, and &lt;a href="https://fullgc.github.io/should-you-harness-the-harness-part-2/" rel="noopener noreferrer"&gt;part two&lt;/a&gt; already named it: enforceability and steerability are the same thing with opposite signs, and the sentence that used to skip a gate becomes a pull request against a YAML file. &lt;a href="https://fullgc.github.io/should-you-harness-the-harness-part-3/" rel="noopener noreferrer"&gt;Part three&lt;/a&gt; is what that felt like day to day.&lt;/p&gt;

&lt;p&gt;As a decision criterion it matters more than it sounds, because &lt;strong&gt;whoever staffs the evaluation tends to determine its outcome.&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;What they see&lt;/th&gt;
&lt;th&gt;What they conclude&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Whoever owns the process&lt;/td&gt;
&lt;td&gt;a procedure that cannot be talked out of halfway through&lt;/td&gt;
&lt;td&gt;the fixed control flow &lt;em&gt;is&lt;/em&gt; the product&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The developer at the keyboard&lt;/td&gt;
&lt;td&gt;a procedure that cannot be talked to at all&lt;/td&gt;
&lt;td&gt;a straitjacket where a sentence used to do&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Neither is wrong. It is one property seen from two chairs, and no amount of tooling resolves it. An evaluation staffed by one group produces a confident answer the other will not honor, which is cheaper to find out before the pilot than after the rollout.&lt;/p&gt;

&lt;p&gt;In practice it counted for little. It was annoying and it was liveable. If the next section did not exist, everything in this one would be a list of things to get used to.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part no engineering fixes
&lt;/h2&gt;

&lt;p&gt;An engine is, by construction, headless third-party non-interactive use of somebody else's coding agent. That is the usage pattern a model vendor has the clearest incentive to price or gate differently, and the first one anybody would restrict.&lt;/p&gt;

&lt;p&gt;On 14 May 2026 Anthropic announced exactly that, to take effect a month later. Use of Claude through its own first-party surfaces, the chat apps and the Claude Code CLI you type into, would stay on the flat-rate subscription. Programmatic use would not: the Agent SDK, &lt;code&gt;claude -p&lt;/code&gt;, GitHub Actions and third-party apps were to move to a separate monthly credit billed at standard API rates, worth twenty dollars on Pro and two hundred on the largest Max plan, with no rollover.&lt;/p&gt;

&lt;p&gt;On 15 June, the day it was due to start, Anthropic &lt;a href="https://support.claude.com/en/articles/15036540-use-the-claude-agent-sdk-with-your-claude-plan" rel="noopener noreferrer"&gt;paused it&lt;/a&gt;: "For now, nothing has changed." Note the wording. Not cancelled, paused, with a revised plan promised and advance notice offered before anything takes effect. That is still where it stands.&lt;/p&gt;

&lt;p&gt;What matters is that somebody sat down and drew the line in exactly the place that separates a person typing from a program calling, and then said they intend to draw it again somewhere. It need not be about price next time either. A model tier, a beta, or a rate limit would do.&lt;/p&gt;

&lt;p&gt;The usual reassurance is that the provider is a line of configuration, so you can move. That is sound about &lt;strong&gt;models&lt;/strong&gt; and close to worthless about &lt;strong&gt;access terms&lt;/strong&gt;, because every vendor shipping a coding agent faces the same incentive. Switching does not escape a policy they all converge on.&lt;/p&gt;

&lt;p&gt;It can also invert the main selling point. Best model per node assumes every vendor's best is reachable from a neutral caller. If one vendor's strongest tier works best through its own harness and another's does too, a neutral engine is not getting the best of both, it is getting whatever each is willing to expose to outsiders.&lt;/p&gt;

&lt;p&gt;Vendors draw their boundary between interactive and programmatic, not between skills and engines. A developer running an orchestrator skill by hand is interactive use of one vendor's own harness, which is precisely the usage every vendor is trying to keep cheap. An engine is on the other side permanently, by construction. Fewer capabilities, less exposure, and no way to have both.&lt;/p&gt;

&lt;p&gt;If a change in one vendor's terms would strand a process your whole team depends on, nothing inside the tool helps.&lt;/p&gt;

&lt;h2&gt;
  
  
  The answer
&lt;/h2&gt;

&lt;p&gt;The difference is real. A procedure that runs the agent is enforced in a way a procedure the agent runs can never be, and no amount of careful writing closes that gap.&lt;/p&gt;

&lt;p&gt;It is also narrower than it sounds. Part one said what the engine fixes. The price is a process to run, a schema to maintain, a layer between you and the agent you were talking to five minutes ago, and a bet that vendors keep letting other people's harnesses call their models.&lt;/p&gt;

&lt;p&gt;The teams who should move are the ones already paying for staying put. A procedure that is mostly mechanical steps is cheaper as a program. Still want to harness? A &lt;code&gt;Makefile&lt;/code&gt; will do. A team already maintaining a state file, a lock and a retry policy has written most of an engine and should stop pretending otherwise.&lt;/p&gt;

&lt;p&gt;Everyone else is fine where they are, for now. An orchestrator skill is a procedure a model has agreed to follow. That is worth a great deal, and it is not the same as a procedure that runs.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://fullgc.github.io/should-you-harness-the-harness-part-5/" rel="noopener noreferrer"&gt;fullgc.github.io&lt;/a&gt;, where the whole series lives.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>workflows</category>
      <category>automation</category>
    </item>
    <item>
      <title>Should you harness the harness: inside the Archon engine</title>
      <dc:creator>Dani Shemesh</dc:creator>
      <pubDate>Mon, 21 Sep 2026 19:46:40 +0000</pubDate>
      <link>https://dev.to/dani_shemesh/should-you-harness-the-harness-inside-the-archon-engine-44b9</link>
      <guid>https://dev.to/dani_shemesh/should-you-harness-the-harness-inside-the-archon-engine-44b9</guid>
      <description>&lt;p&gt;The surprise, reading a workflow engine from the inside, is how little of it is about graphs. Topological sorting is a first-year algorithm and it takes maybe fifty lines. Almost everything else is bookkeeping: who holds the lock, what counts as a fatal error, what happens when a process dies without saying so.&lt;/p&gt;

&lt;p&gt;That is the honest description of the category. &lt;strong&gt;The DAG is the part you would write in an afternoon. The rest is the part you would get wrong for a year.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;None of this is required reading. &lt;a href="https://fullgc.github.io/should-you-harness-the-harness-part-3/" rel="noopener noreferrer"&gt;Part three&lt;/a&gt; said what Archon is and what it gives you, and that argument stands without any of what follows. Read on if you are building something like it: the lock expiry, the order the error classifier checks its patterns in, and forking a session instead of appending to it are the same three problems in any system where work outlives the process that started it.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Continues from part three, and assumes its vocabulary: command, workflow, worktree, node.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A run, in four phases
&lt;/h2&gt;

&lt;p&gt;Every execution moves through four phases, and the engine is the same code in every case: the front door you came through only decides who gets told about it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwwcr6098htki2kfa532m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwwcr6098htki2kfa532m.png" alt="A flowchart of the four phases. Discovery scans three sources, parses YAML, validates nodes and validates the DAG shape. Routing checks for an explicit workflow name and otherwise asks a model, then fuzzy-matches. Setup loads config, resolves provider and model, checks for a prior failed run, creates the run row and takes the path lock. DAG execution computes topological layers and runs each layer, evaluating conditions, substituting variables, executing nodes, and retrying or failing on the outcome."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 1: The whole engine on one page. More of it is checks than work.&lt;/em&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Phase&lt;/th&gt;
&lt;th&gt;What happens&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Discovery&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;YAML files are found, parsed and structurally validated&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Routing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;the right workflow is selected, by name or by asking a model&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Setup&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;config loads, a run row is created, the path lock is taken&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Execution&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;nodes run in topological layers, concurrently within each layer&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Discovery: three sources, three checks
&lt;/h3&gt;

&lt;p&gt;Every YAML file found across the three scopes goes through three checks before it is allowed to exist:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It parses.&lt;/strong&gt; A file that does not is skipped, not fatal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Every node validates&lt;/strong&gt; against a schema, for types, required fields and valid enums.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The graph validates.&lt;/strong&gt; No duplicate node IDs, no &lt;code&gt;depends_on&lt;/code&gt; pointing at a node that is not there, no cycles, and no &lt;code&gt;$nodeId.output&lt;/code&gt; reference to a node that does not exist.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The failure mode is the good part. A broken YAML file records a &lt;code&gt;WorkflowLoadError&lt;/code&gt; and shows up in &lt;code&gt;/workflow list&lt;/code&gt;. It does not abort the discovery pass or block any other workflow from loading. One malformed file breaks one workflow.&lt;/p&gt;

&lt;h3&gt;
  
  
  Routing: a model that cannot use tools
&lt;/h3&gt;

&lt;p&gt;When a message arrives without an explicit workflow name, the router builds a prompt listing every available workflow with its description, adds platform context such as the issue title, labels and thread history, and asks a model to reply with &lt;code&gt;/invoke-workflow &amp;lt;name&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Tool use is disabled at the API level with &lt;code&gt;tools: []&lt;/code&gt;, so the router &lt;strong&gt;cannot&lt;/strong&gt; make a tool call. It can only emit text. For a component whose entire job is to pick a name out of a list, removing its ability to do anything else is the right call, and it is the kind of thing that only gets done deliberately.&lt;/p&gt;

&lt;p&gt;The name it emits then goes through a four-tier fuzzy match: exact, case-insensitive, suffix (&lt;code&gt;plan&lt;/code&gt; matches &lt;code&gt;feature-plan&lt;/code&gt;), then substring. If everything fails, the system falls back to a general assistant workflow rather than guessing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setup: unglamorous and load-bearing
&lt;/h3&gt;

&lt;p&gt;Before a single node runs, setup does seven things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;reads config and merges per-project environment variables;&lt;/li&gt;
&lt;li&gt;resolves provider and model;&lt;/li&gt;
&lt;li&gt;checks whether a previous failed run on this working directory can be resumed;&lt;/li&gt;
&lt;li&gt;creates the run row;&lt;/li&gt;
&lt;li&gt;takes the path lock;&lt;/li&gt;
&lt;li&gt;makes the per-run artifacts directory;&lt;/li&gt;
&lt;li&gt;exposes that directory to prompts as &lt;code&gt;$ARTIFACTS_DIR&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The worktree is created here too, on an auto-generated branch you can override with &lt;code&gt;--branch&lt;/code&gt;, and these are git-native worktrees rather than clones. A workflow that does not touch the checkout declares &lt;code&gt;mutates_checkout: false&lt;/code&gt; and opts out of both the worktree and the lock guarding it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Isolation you have to clean up
&lt;/h3&gt;

&lt;p&gt;The part that is easy to skip when building this yourself is the other end:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;archon complete &amp;lt;branch&amp;gt;&lt;/code&gt; removes the worktree, the local branch and the remote one once a finished branch has been pushed and merged.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;archon isolation cleanup&lt;/code&gt; sweeps worktrees older than a week.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--merged&lt;/code&gt; takes merged branches too, and &lt;code&gt;--include-closed&lt;/code&gt; takes abandoned pull requests.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Isolation you cannot clean up is just a slowly filling disk.&lt;/p&gt;

&lt;p&gt;Two constraints come with it. One branch maps to exactly one worktree, so the same branch cannot be run twice at once. And a paused sub-run's worktree is reused when it resumes, so cleaning up too eagerly turns a paused run into a lost one.&lt;/p&gt;

&lt;h3&gt;
  
  
  Execution: layers, then nodes
&lt;/h3&gt;

&lt;p&gt;The executor builds topological layers from the &lt;code&gt;depends_on&lt;/code&gt; edges and runs every node in a layer at once. Per node it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;evaluates the &lt;code&gt;when:&lt;/code&gt; condition and the &lt;code&gt;trigger_rule&lt;/code&gt; join policy;&lt;/li&gt;
&lt;li&gt;runs two passes of variable substitution;&lt;/li&gt;
&lt;li&gt;executes;&lt;/li&gt;
&lt;li&gt;on success, stores the output in an in-memory map and persists a &lt;code&gt;node_completed&lt;/code&gt; event.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On a transient error it retries up to &lt;code&gt;max_attempts&lt;/code&gt;, twice by default, with a three second backoff. On a fatal error it stops immediately. What counts as fatal is a section of its own.&lt;/p&gt;

&lt;h3&gt;
  
  
  The node types
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqyn5xxpwhnuazx0q08oc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqyn5xxpwhnuazx0q08oc.png" alt="A fan-out from a DAG node to six node types, color-coded. Blue for the AI nodes: prompt/command and loop. Orange for the shell nodes: bash and script. Purple for the gates: approval and cancel."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 2: The colors are the argument. Only the blue ones put a model in the path.&lt;/em&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;How it runs&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;prompt&lt;/code&gt; / &lt;code&gt;command&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;AI&lt;/td&gt;
&lt;td&gt;calls &lt;code&gt;provider.sendQuery()&lt;/code&gt;; a &lt;code&gt;command&lt;/code&gt; node loads its prompt from a file in &lt;code&gt;.archon/commands/&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;loop&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;AI&lt;/td&gt;
&lt;td&gt;calls the model repeatedly, scanning each output for a completion tag, until it appears or &lt;code&gt;max_iterations&lt;/code&gt; is hit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;bash&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Shell&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;execFileAsync('bash', ['-c', script])&lt;/code&gt;. No model anywhere in the path&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;script&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Shell&lt;/td&gt;
&lt;td&gt;TypeScript via &lt;code&gt;bun run&lt;/code&gt; or Python via &lt;code&gt;uv run&lt;/code&gt;, with &lt;code&gt;deps:&lt;/code&gt; and &lt;code&gt;timeout:&lt;/code&gt; support&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;approval&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Gate&lt;/td&gt;
&lt;td&gt;writes &lt;code&gt;status: paused&lt;/code&gt;, messages the user, waits for &lt;code&gt;/workflow approve&lt;/code&gt; or &lt;code&gt;/workflow reject&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cancel&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Gate&lt;/td&gt;
&lt;td&gt;marks the run cancelled and stops execution, usually on a conditional branch&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Every type captures output the same way, into &lt;code&gt;$nodeId.output&lt;/code&gt;, which is what lets them compose. For AI nodes it is the concatenated assistant text; for shell nodes it is stdout; for an approval node with &lt;code&gt;capture_response: true&lt;/code&gt; it is whatever the human typed.&lt;/p&gt;

&lt;p&gt;Prompt and loop nodes also accept a long list of per-node overrides, currently Claude only: &lt;code&gt;model&lt;/code&gt;, &lt;code&gt;systemPrompt&lt;/code&gt;, &lt;code&gt;allowed_tools&lt;/code&gt; and &lt;code&gt;denied_tools&lt;/code&gt;, &lt;code&gt;mcp&lt;/code&gt;, &lt;code&gt;hooks&lt;/code&gt;, &lt;code&gt;skills&lt;/code&gt;, &lt;code&gt;agents&lt;/code&gt;, &lt;code&gt;effort&lt;/code&gt;, &lt;code&gt;thinking&lt;/code&gt;, &lt;code&gt;maxBudgetUsd&lt;/code&gt;, &lt;code&gt;fallbackModel&lt;/code&gt;, &lt;code&gt;betas&lt;/code&gt;, &lt;code&gt;sandbox&lt;/code&gt;. Capabilities are checked at execution time, and anything the provider does not support emits a warning and is ignored rather than failing the run.&lt;/p&gt;

&lt;h3&gt;
  
  
  The model resolution chain
&lt;/h3&gt;

&lt;p&gt;The claim &lt;a href="https://fullgc.github.io/should-you-harness-the-harness-part-2/" rel="noopener noreferrer"&gt;part two&lt;/a&gt; leaned on hardest was that the invoking session has no say in what serves a node. Here is the chain in full:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Priority&lt;/th&gt;
&lt;th&gt;Source&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;model:&lt;/code&gt; on the individual node&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;model:&lt;/code&gt; at the top of the workflow YAML&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;assistants.claude.model&lt;/code&gt; in &lt;code&gt;.archon/config.yaml&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;the SDK default&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Never&lt;/td&gt;
&lt;td&gt;the model of the Claude session that invoked Archon&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Provider identity is validated when the YAML loads, so an unknown &lt;code&gt;provider:&lt;/code&gt; is a hard error. Model strings are deliberately &lt;em&gt;not&lt;/em&gt; validated: they go verbatim to the SDK, which is the actual source of truth for what model names exist. Refusing to maintain a second list is the right call and a slightly uncomfortable one, since a typo surfaces at execution rather than at load.&lt;/p&gt;

&lt;h3&gt;
  
  
  Context and sessions
&lt;/h3&gt;

&lt;p&gt;Context is threaded rather than accumulated. Sequential nodes on one provider inherit the previous node's session; a parallel layer resets that, because concurrent nodes cannot share one session; and any node can opt out with &lt;code&gt;context: fresh&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Cross-provider handoff has to pass context explicitly, which is what the second substitution pass is for:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;plan&lt;/span&gt;
  &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;claude&lt;/span&gt;
  &lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Analyze&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;repo&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;and&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;plan:&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;$ARGUMENTS"&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;implement&lt;/span&gt;
  &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;plan&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;codex&lt;/span&gt;          &lt;span class="c1"&gt;# different provider, no shared session&lt;/span&gt;
  &lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;Implement according to this plan:&lt;/span&gt;
    &lt;span class="s"&gt;$plan.output           # full text of the plan node, injected at runtime&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pass one substitutes run-scoped variables (&lt;code&gt;$ARGUMENTS&lt;/code&gt;, &lt;code&gt;$BASE_BRANCH&lt;/code&gt;, &lt;code&gt;$ARTIFACTS_DIR&lt;/code&gt;, &lt;code&gt;$REJECTION_REASON&lt;/code&gt; and friends). Pass two resolves &lt;code&gt;$nodeId.output&lt;/code&gt; for the whole text, or &lt;code&gt;$nodeId.output.field&lt;/code&gt; to parse the output as JSON and pull one field out, which is how structured data moves between nodes.&lt;/p&gt;

&lt;p&gt;For bash bodies, substituted values are wrapped in single quotes before hitting the shell. Node output is model-generated text going into a shell command, so this is the injection boundary, and it is handled where it should be.&lt;/p&gt;

&lt;h3&gt;
  
  
  The state machine, and what resume does not restore
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4zputuhh3eki0622ktc8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4zputuhh3eki0622ktc8.png" alt="A state diagram. A run is created as pending, becomes running when the dispatcher starts, and from running can reach completed, paused, failed or cancelled. Paused returns to running on approve or reject. Failed returns to pending on resume. Two annotations: a five-minute stale window protects against orphaned rows, and the database is the source of truth across processes."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 3: The two callouts are the parts you would not write yourself until the day you needed them.&lt;/em&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Transition&lt;/th&gt;
&lt;th&gt;Trigger&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;→ &lt;code&gt;pending&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;run created; the row reserves the working directory&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;pending&lt;/code&gt; → &lt;code&gt;running&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;dispatcher starts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;pending&lt;/code&gt; → &lt;code&gt;cancelled&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;path-lock conflict, or abandoned before it ever ran&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;running&lt;/code&gt; → &lt;code&gt;paused&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;an approval node is reached&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;paused&lt;/code&gt; → &lt;code&gt;running&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;/workflow approve&lt;/code&gt; or &lt;code&gt;/workflow reject&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;running&lt;/code&gt; → &lt;code&gt;completed&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;every layer finished&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;running&lt;/code&gt; → &lt;code&gt;failed&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;fatal error, or retries exhausted&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;failed&lt;/code&gt; → &lt;code&gt;pending&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/workflow resume&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The database is authoritative rather than a file, which is what allows more than one process to observe and act on the same run. That is the mechanical reason an approval can be answered from Slack while the run itself is on someone's laptop.&lt;/p&gt;

&lt;p&gt;Resume replays &lt;code&gt;node_completed&lt;/code&gt; events to reconstruct prior outputs and skips those nodes. And here is the honest limitation, documented rather than hidden: &lt;strong&gt;resume does not restore the AI session context.&lt;/strong&gt; Only the &lt;code&gt;$nodeId.output&lt;/code&gt; values come back. A node that depended on what a previous node had &lt;em&gt;read&lt;/em&gt; rather than what it &lt;em&gt;returned&lt;/em&gt; may need to re-read artifacts, and the resume message says so explicitly.&lt;/p&gt;

&lt;p&gt;Which is the same trade the previous series arrived at from the other direction. Artifacts survive; conversations do not. An engine does not fix that, it just makes the surviving part durable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the hard calls are
&lt;/h2&gt;

&lt;p&gt;Everything above is what you would expect a workflow engine to contain. What follows is what separates one that works from one that mostly works, and none of it would show up in a feature comparison.&lt;/p&gt;

&lt;h3&gt;
  
  
  The lock, and the five-minute window
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://fullgc.github.io/should-you-harness-the-harness-part-1/" rel="noopener noreferrer"&gt;Part one&lt;/a&gt; argued that arbitrating two runs against one checkout needs a lock with an owner and an expiry. Here is that lock.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;pending&lt;/code&gt; row &lt;strong&gt;is&lt;/strong&gt; the lock token. Before a run starts, the engine checks whether another run is already &lt;code&gt;running&lt;/code&gt; or &lt;code&gt;paused&lt;/code&gt; on the same worktree, and if so the new run is cancelled immediately with an actionable message rather than queued or silently run anyway.&lt;/p&gt;

&lt;p&gt;The expiry is a five-minute stale-pending window, which exists for exactly one situation: a dispatch that crashed between inserting the row and starting work. Without it, one crash poisons that working directory forever. Workflows that do not touch the checkout opt out of both the worktree and the lock with &lt;code&gt;mutates_checkout: false&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Refusing to guess
&lt;/h3&gt;

&lt;p&gt;The rule Archon states most explicitly is a refusal:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;When a process cannot reliably distinguish "actively running elsewhere" from "orphaned by a&lt;br&gt;
crash", it does not autonomously mark the run as failed.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Instead it surfaces the ambiguity with actionable options. Only heuristic, recoverable operations (retry backoff, subprocess timeouts) are allowed to bypass this.&lt;/p&gt;

&lt;p&gt;This is the single most transferable idea in the codebase, and it has nothing to do with AI. A distributed system that guesses about liveness will eventually kill a healthy run, and the user will not know why. Encoding "I cannot tell, so I will ask" as a design rule rather than an oversight is the difference between an engine you trust and one you babysit.&lt;/p&gt;

&lt;h3&gt;
  
  
  Three classes of error
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;classifyError()&lt;/code&gt; sorts every caught error into three buckets:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Class&lt;/th&gt;
&lt;th&gt;Patterns&lt;/th&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Fatal&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;auth failure, permission denied, credit balance, 401, 403&lt;/td&gt;
&lt;td&gt;abort immediately: retrying wastes credits or loops forever&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Transient&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;rate limit, timeout, process exit, 429, overloaded&lt;/td&gt;
&lt;td&gt;retry with exponential backoff&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Unknown&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;everything else&lt;/td&gt;
&lt;td&gt;retry, but count consecutive occurrences and abort after three&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The ordering matters. Fatal patterns are checked &lt;em&gt;before&lt;/em&gt; transient ones, so a message containing both, like &lt;code&gt;unauthorized: process exited with code 1&lt;/code&gt;, classifies as fatal. Get that precedence backwards and an expired credential turns into a retry loop burning money on a request that cannot succeed.&lt;/p&gt;

&lt;p&gt;The unknown bucket retries, because most unknown errors are transient, but it keeps a consecutive counter so an unrecognized permanent failure cannot spin forever. That is a considered answer to "we do not know", rather than picking one of the other two buckets and hoping.&lt;/p&gt;

&lt;h3&gt;
  
  
  Four channels, and the three that can answer you
&lt;/h3&gt;

&lt;p&gt;A run writes itself down in four places. Event rows go to the database, which is what resume replays. A JSONL file log records the same run linearly. An in-process emitter feeds the web console over SSE. Anonymous telemetry goes to Archon.&lt;/p&gt;

&lt;p&gt;Only the first three can tell you why a run is stuck, and they are not redundant: the database has what finished, the log has what was said, the stream has what is happening now. Part three's complaint about having to guess which channel holds the answer is this list seen from the outside.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fe5vtvmuhrr7sniayv2np.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fe5vtvmuhrr7sniayv2np.png" alt="Three diagrams. Error classification routes a caught error to transient (retry on rate limit or timeout), fatal (abort on auth, credits or permission) or unknown (three strikes then abort). Observability lists four channels: event rows in the database, a JSONL file log, an in-process emitter feeding SSE, and anonymous telemetry. Variable substitution runs in two passes: run-scoped variables first, then cross-node output references."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 4: Error classification, the observability channels, and the two substitution passes.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Forking instead of mutating
&lt;/h3&gt;

&lt;p&gt;Whenever a node resumes an earlier session, the executor sets &lt;code&gt;forkSession: true&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;shouldForkSession&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;resumeSessionId&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Forking copies the transcript before appending rather than mutating the source. The reason is retries: if node B fails and runs again, the second attempt forks node A's session afresh instead of appending to a transcript the first attempt already polluted. Without this, a retry inherits the wreckage of its own previous failure, and nothing in the output would tell you.&lt;/p&gt;

&lt;h3&gt;
  
  
  An engine that does not import an AI SDK
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;@archon/workflows&lt;/code&gt; imports only from &lt;code&gt;@archon/providers/types&lt;/code&gt;, a subpath with zero SDK dependencies. The actual &lt;code&gt;@anthropic-ai/claude-agent-sdk&lt;/code&gt; and &lt;code&gt;@openai/codex-sdk&lt;/code&gt; packages live exclusively in &lt;code&gt;@archon/providers&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So the workflow engine can be tested with no AI SDK installed at all. That is the boundary doing real work: the thing that sequences steps has no opinion about who executes them, which is the same inversion part two described, enforced at the level of the dependency graph.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the engineering gets right
&lt;/h2&gt;

&lt;p&gt;None of the decisions above is visible in a demo, and every one of them was somebody choosing the harder correct thing over the obvious one.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The lock has an &lt;strong&gt;expiry&lt;/strong&gt;, because a dispatch that crashes between writing the row and starting work would otherwise poison that working directory permanently.&lt;/li&gt;
&lt;li&gt;Fatal errors are checked &lt;strong&gt;before&lt;/strong&gt; transient ones, so an expired credential does not become a retry loop billing you for requests that cannot succeed.&lt;/li&gt;
&lt;li&gt;Resume &lt;strong&gt;forks&lt;/strong&gt; a session rather than mutating it, so a second attempt does not inherit the wreckage of the first.&lt;/li&gt;
&lt;li&gt;The engine does not import an AI SDK &lt;strong&gt;at all&lt;/strong&gt;, so the thing that sequences steps has no opinion about who executes them.&lt;/li&gt;
&lt;li&gt;When liveness cannot be determined, the system &lt;strong&gt;refuses to decide&lt;/strong&gt; and says so.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What follows
&lt;/h2&gt;

&lt;p&gt;All of that is a good picture of a &lt;em&gt;run&lt;/em&gt;. Whether it is a good picture of &lt;em&gt;your work&lt;/em&gt; is the question the series is named for, and &lt;a href="https://fullgc.github.io/should-you-harness-the-harness-part-5/" rel="noopener noreferrer"&gt;part five&lt;/a&gt; answers it without mentioning Archon again: what an engine gives you that no skill will, and the one risk no engineering fixes.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://fullgc.github.io/should-you-harness-the-harness-part-4/" rel="noopener noreferrer"&gt;fullgc.github.io&lt;/a&gt;, where the whole series lives.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>workflows</category>
      <category>automation</category>
    </item>
    <item>
      <title>Should you harness the harness: what Archon is, and how you use it</title>
      <dc:creator>Dani Shemesh</dc:creator>
      <pubDate>Fri, 18 Sep 2026 11:01:50 +0000</pubDate>
      <link>https://dev.to/dani_shemesh/should-you-harness-the-harness-what-archon-is-and-how-you-use-it-3e5f</link>
      <guid>https://dev.to/dani_shemesh/should-you-harness-the-harness-what-archon-is-and-how-you-use-it-3e5f</guid>
      <description>&lt;p&gt;&lt;a href="https://fullgc.github.io/should-you-harness-the-harness-part-2/" rel="noopener noreferrer"&gt;Part two&lt;/a&gt; defined the category and named two tools in it. This part is one of them from the outside: Archon, the engine we have been evaluating. How a run starts, what composes with what, what the five claims rest on, and what grates.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Assumes part two's vocabulary: node, layer, run state, provider resolution.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What Archon is, and what it used to be
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://archon.diy/docs/" rel="noopener noreferrer"&gt;Archon's documentation&lt;/a&gt; opens with "a workflow engine for AI coding agents", which is part two's category in Archon's own words. You define a multi-step development workflow in YAML, a code review or a bug fix or a feature, and run it with one command. Each run gets its own git worktree, and it can be started from a terminal, a chat message or a GitHub comment without those being three separate integrations.&lt;/p&gt;

&lt;p&gt;Underneath, it is Bun and TypeScript over SQLite or Postgres driving the Claude Code and Codex SDKs, which is &lt;a href="https://fullgc.github.io/should-you-harness-the-harness-part-4/" rel="noopener noreferrer"&gt;part four's&lt;/a&gt; subject rather than this one's.&lt;/p&gt;

&lt;p&gt;Archon is built by Cole Medin, whose plan-implement-validate loop is the spine of the skeleton in &lt;a href="https://fullgc.github.io/designing-agentic-development-workflows-part-1/" rel="noopener noreferrer"&gt;part one of the previous series&lt;/a&gt;, and whose context-engineering work sits upstream of that. The procedure the first series described and the engine this one evaluates come from the same person, arriving by different routes.&lt;/p&gt;

&lt;p&gt;There is a second piece of history, because Archon has not always been this. It started as a Python agent-builder: a tool for constructing Pydantic AI and LangGraph agents. In April 2026 that codebase was archived and the project rewritten from scratch in TypeScript into what its README calls a harness builder, with the original &lt;a href="https://github.com/Decentralised-AI/Archon-agent-builder" rel="noopener noreferrer"&gt;spun off into its own repository&lt;/a&gt;. A good deal of the old vocabulary survived the rewrite, so if you know LangGraph you will keep expecting semantics that are not there.&lt;/p&gt;

&lt;p&gt;Having followed Cole Medin's work since the start of the year, which is how I found Archon at all, that rewrite reads to me less like a change of direction than a conclusion. The move is from building agents to constraining them. Once the methodology was written down as prompts, rules and phases, the next step was to stop asking a model to follow it.&lt;/p&gt;

&lt;h2&gt;
  
  
  How you actually run one
&lt;/h2&gt;

&lt;p&gt;Part two treated "the engine invokes the coding agent" as an abstraction. In practice there are several front doors, and which one you use changes the experience more than it changes the run.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Through the coding agent, via the Archon skill.&lt;/strong&gt; This is the recommended route and the one that makes the whole thing palatable. The setup wizard copies an Archon skill into your target repository, so you keep working in Claude Code from your own project and simply say what you want. The agent picks a workflow, dispatches it in the background, and reports back. One run reads:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwpd1ajgvi1mczhfdbdny.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwpd1ajgvi1mczhfdbdny.png" alt="A chat thread. The user types " width="800" height="352"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 1: One prompt, nine nodes, twenty-five minutes, and a session that stayed free the whole time. The worktree path is in the header.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That is the coding agent driving the engine that drives coding agents. The session you are typing in never blocks for twenty-five minutes, and the run survives independently of it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Through the CLI.&lt;/strong&gt; &lt;code&gt;archon serve&lt;/code&gt; starts the web console, &lt;code&gt;archon workflow list&lt;/code&gt; shows what is available, &lt;code&gt;archon doctor&lt;/code&gt; runs diagnostics. Install is a Homebrew formula, a shell installer, or a clone and &lt;code&gt;bun install&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Through the web console.&lt;/strong&gt; A Mission Control dashboard with run history filtered by project, status and date, a capacity indicator, per-run duration, and a source column recording whether each run came from the CLI, the web, or a chat platform. Workflows appear as cards you can run directly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Through a chat platform or a forge.&lt;/strong&gt; Slack, Telegram and Discord for conversation; GitHub, Gitea and GitLab for issues and pull requests. Each is an adapter over the same engine, which is what part two meant about a run id and an event stream making every surface a client of the same state.&lt;/p&gt;

&lt;h3&gt;
  
  
  What ships, and what you can add
&lt;/h3&gt;

&lt;p&gt;Nineteen workflows are bundled, and they are not toys:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;archon-fix-github-issue&lt;/code&gt; runs classify, investigate, implement, validate, PR, review.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;archon-idea-to-pr&lt;/code&gt; takes a feature idea through the same spine and ends in five parallel reviews.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;archon-piv-loop&lt;/code&gt; is plan-implement-validate with a mandatory human gate.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;archon-comprehensive-pr-review&lt;/code&gt; deploys five parallel reviewers with auto-fix.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;archon-workflow-builder&lt;/code&gt; generates new workflow YAML: the engine writes its own configuration.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frzszukjyk2uywy479xck.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frzszukjyk2uywy479xck.png" alt="The Archon Workflows tab, showing bundled workflows as cards. Each card has WHEN TO USE, TRIGGERS, DOES and NOT FOR fields, category tags, and a Run button." width="800" height="354"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 2: Every bundled workflow declares when to use it and, more unusually, what it is NOT for. That second field is routing metadata: it tells the router when to pick something else.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Your own live in &lt;code&gt;.archon/workflows/&amp;lt;pack&amp;gt;/&amp;lt;workflow&amp;gt;/&lt;/code&gt;, keeping a workflow's YAML, commands and scripts together in one copyable folder. A same-named file in your repository overrides the bundled default, so customizing &lt;code&gt;archon-piv-loop&lt;/code&gt; means writing your own and leaving the name alone.&lt;/p&gt;

&lt;h3&gt;
  
  
  Commands are the unit of reuse
&lt;/h3&gt;

&lt;p&gt;A &lt;strong&gt;command&lt;/strong&gt; is a markdown file holding instructions for one AI task, and Archon's own framing is the useful one: commands are &lt;em&gt;atoms&lt;/em&gt;, each a single focused task with no knowledge of what comes before or after it, and workflows are &lt;em&gt;molecules&lt;/em&gt;, YAML files arranging commands into a graph with a purpose.&lt;/p&gt;

&lt;p&gt;That separation is what makes reuse possible. &lt;code&gt;archon-investigate-issue&lt;/code&gt; is a command, and any workflow that needs an investigation phase references it by name rather than restating it. Change the command once and every workflow using it changes. A workflow, meanwhile, holds no instructions at all: it holds order, conditions and dependencies.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F79igz2ctp0vikcm0xl6n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F79igz2ctp0vikcm0xl6n.png" alt="Two columns. On the left, three command files: investigate-issue highlighted amber, plus write-tests and review-diff. On the right, three workflows named fix-github-issue, idea-to-pr and piv-loop, each drawn as a small graph of connected nodes. Amber arrows run from investigate-issue to all three workflows; the other two commands feed one or two workflows each. The left column is labelled " width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 3: Change the amber file once and three workflows change. A workflow holds no instructions of its own, only which commands and in what order.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Commands compose through artifacts rather than through conversation, and the documented pattern is three lines long:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;write the important findings to an artifact;&lt;/li&gt;
&lt;li&gt;start the next node with &lt;code&gt;context: fresh&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;have that node read the artifact.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Which is precisely the discipline the previous series arrived at by hand, here as the default way the pieces fit together. This is the piece I had underrated.&lt;/p&gt;

&lt;p&gt;Workflows, commands and scripts each resolve across three scopes:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scope&lt;/th&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Beats&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Bundled&lt;/td&gt;
&lt;td&gt;compiled into the binary&lt;/td&gt;
&lt;td&gt;nothing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Global&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;~/.archon/workflows&lt;/code&gt;, &lt;code&gt;/commands&lt;/code&gt;, &lt;code&gt;/scripts&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;bundled&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Repo&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;.archon/workflows&lt;/code&gt;, &lt;code&gt;/commands&lt;/code&gt;, &lt;code&gt;/scripts&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;global and bundled&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Same filename at a higher scope wins, which gives you a personal review checklist available in every project, overridable per repository, without copying anything. It is the packaging problem from &lt;a href="https://fullgc.github.io/designing-agentic-development-workflows-part-5/" rel="noopener noreferrer"&gt;part five of the previous series&lt;/a&gt;, solved by convention instead of by a manifest.&lt;/p&gt;

&lt;h3&gt;
  
  
  The marketplace
&lt;/h3&gt;

&lt;p&gt;There is also a marketplace at &lt;code&gt;archon.diy/workflows&lt;/code&gt;. Each entry carries a slug, author, description, source URL, a &lt;strong&gt;pinned commit SHA&lt;/strong&gt;, tags and a compatibility range.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7ha8b6ymjedslsqq0rt8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7ha8b6ymjedslsqq0rt8.png" alt="The Archon workflows marketplace. A prominent amber warning reads: Community-submitted. Archon hasn't audited every workflow, review source before installing. Below it, cards for Archon PIV Loop and Fix GitHub Issue, each by @coleam00, with an install command." width="799" height="590"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 4: The warning is the most important thing on the page.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Pinning to a SHA rather than a branch is the right call for something that executes in your repository, and the listing carries a blunt warning that community submissions are not all audited and you should read the source before installing. That warning is doing real work: a workflow is code that runs with your credentials in your checkout.&lt;/p&gt;

&lt;h2&gt;
  
  
  The five claims, and what is behind each
&lt;/h2&gt;

&lt;p&gt;Archon's documentation makes five claims for itself: repeatable, isolated, portable, composable, multi-provider. They are the right five, and each one rests on a specific piece of machinery rather than on good intentions. Taking them in turn is the fastest way to see what an engine actually buys.&lt;/p&gt;

&lt;h3&gt;
  
  
  Repeatable
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;"Package your best AI coding patterns as shareable YAML workflows."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The sequence is fixed at definition time and nothing at runtime can renegotiate it. Steps that need no intelligence, the &lt;code&gt;bash&lt;/code&gt; and &lt;code&gt;script&lt;/code&gt; nodes, have no model in the path at all: they run because the graph says so, which means they cannot be skipped, because nothing there is capable of deciding to skip them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The procedure is repeatable; the output is not.&lt;/strong&gt; Every AI node is still a language model doing a language model's job, and running the same workflow twice on the same input gives you two different pull requests. What repeats is that both of them were planned, implemented, tested, reviewed and gated in that order. That is a smaller claim than "deterministic AI coding" and it is the one that actually holds.&lt;/p&gt;

&lt;h3&gt;
  
  
  Isolated
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;"Each workflow runs in its own git worktree, no conflicts, no mess."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The worktree is the mechanism and the path lock is what makes it trustworthy. Five runs on one repository are five directories on five branches sharing a single object store, and the lock stops two runs occupying one worktree.&lt;/p&gt;

&lt;h3&gt;
  
  
  Portable
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;"Run from CLI, Web UI, Slack, Telegram, GitHub, or Discord."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Because the run owns its process and its id, every surface is a client of the same state rather than a separate integration. This is portability of &lt;em&gt;access&lt;/em&gt;, not the portability the previous series worried about: it does not make your workflows runnable on a different harness, it makes them reachable from anywhere.&lt;/p&gt;

&lt;h3&gt;
  
  
  Composable
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;"Chain nodes into DAGs with dependencies, loops, and conditional logic."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Two mechanisms under one word, and both matter. &lt;strong&gt;Reuse&lt;/strong&gt;: a command is a single-task markdown file that any workflow references by name, so it is written once and changed once. &lt;strong&gt;Chaining&lt;/strong&gt;: every node type, AI or shell or gate, writes to &lt;code&gt;$nodeId.output&lt;/code&gt;, so any step can feed any later step, whole or as one JSON field.&lt;/p&gt;

&lt;p&gt;Reuse is why you write fewer instructions. Chaining is why the graph is a graph rather than a sequence. Lose either and the other stops being worth much.&lt;/p&gt;

&lt;h3&gt;
  
  
  Multi-provider
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;"Works with Claude Code SDK, Codex SDK, and local models via Pi."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Provider and model resolve per node, and each node gets a fresh provider instance, so nothing is shared between them. The chain runs node, then workflow, then config, then the SDK default, and never the model of the session that invoked Archon.&lt;/p&gt;

&lt;p&gt;The limit is honest and declared rather than assumed: sessions do not cross providers. Claude supports session resume, Codex and Pi do not, and the engine checks before trying. Cross-provider handoff therefore has to pass context explicitly, which is exactly what the output chaining above is for.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it is like to live with
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F294loyabsp3i9ctlrncv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F294loyabsp3i9ctlrncv.png" alt="The confused Nick Young meme, a man surrounded by floating question marks. Top caption: " width="398" height="340"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Three parts of using Archon that a feature list does not reach, all of them real and none of them why anyone stopped.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;You see what the harness decided to show you.&lt;/strong&gt; Calling this a loss of observability understates it, because by one measure observability improves enormously: an event stream, a run history, status and cost per node, a console, all of which an orchestrator skill lacks entirely. What changes is that every view of the run becomes a rendering somebody else designed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The cost figure is not the bill.&lt;/strong&gt; Per-node cost is recorded and shown, but it counts node cost and not the overhead of the coding agent that invoked the engine. That gap can reach forty percent, which is enough to matter when the number is the one you are budgeting against.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The console will date.&lt;/strong&gt; At the version evaluated it was pleasant to read and unreliable to operate. Viewing was fine; performing operations from it was not.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The first deserves more than a bullet.&lt;/p&gt;

&lt;p&gt;Archon streams message chunks to the platform, writes a JSONL log and puts node states in the web console. Those are considered choices about what matters, and they are better organized than a terminal scrollback. They are also a filter. When the thing you need is not in the rendering, you drop to the logs, and the logs are a lower-level rendering rather than the thing itself.&lt;/p&gt;

&lt;p&gt;You are watching a rendering in a coding agent session too. The difference is &lt;em&gt;what&lt;/em&gt; is rendered: there it is the transcript, and here it is the run, with the transcript demoted to something you go and fetch. Which produces one specific failure mode, and it is the one engineers complained about: &lt;em&gt;it is stuck and I do not know why&lt;/em&gt;. In a session you scroll up. Here you go looking, and first you have to guess which of three channels recorded the answer.&lt;/p&gt;

&lt;p&gt;None of this was decisive. The friction was real, it was annoying, and it was liveable. What actually decides whether to adopt an engine is not in this list, and it is not about Archon.&lt;/p&gt;

&lt;h2&gt;
  
  
  What follows
&lt;/h2&gt;

&lt;p&gt;That is the whole of Archon that most people need: what it is, how you start a run, what composes with what, what the five claims rest on, and what the rough edges feel like.&lt;/p&gt;

&lt;p&gt;The next part goes underneath, into how a run is actually discovered, routed, set up and executed, and into the handful of decisions that separate an engine which works from one that mostly works. It is skippable, and it is the part I found most worth reading.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://fullgc.github.io/should-you-harness-the-harness-part-3/" rel="noopener noreferrer"&gt;fullgc.github.io&lt;/a&gt;, where the whole series lives.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>workflows</category>
      <category>automation</category>
    </item>
    <item>
      <title>Should you harness the harness: what a workflow engine actually is</title>
      <dc:creator>Dani Shemesh</dc:creator>
      <pubDate>Thu, 17 Sep 2026 20:57:16 +0000</pubDate>
      <link>https://dev.to/dani_shemesh/should-you-harness-the-harness-what-a-workflow-engine-actually-is-20mm</link>
      <guid>https://dev.to/dani_shemesh/should-you-harness-the-harness-what-a-workflow-engine-actually-is-20mm</guid>
      <description>&lt;p&gt;&lt;a href="https://fullgc.github.io/should-you-harness-the-harness-part-1/" rel="noopener noreferrer"&gt;Part one&lt;/a&gt; described a procedure that lives inside the agent session, and the limits that follow from it living there. This part is about the alternative, and about how little of what is sold as the alternative actually is one.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;workflow engine&lt;/strong&gt; is a program that holds the procedure and calls the coding agent when it needs intelligence. That is the whole definition. Everything separating an engine from a skill follows from it, including the two things it will cost you.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Assumes part one's list of limits, and the vocabulary of the previous series: orchestrator, skill, sub-agent, gate, run state.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The inversion
&lt;/h2&gt;

&lt;p&gt;An orchestrator skill is a document the model reads. The model holds the procedure, decides which phase comes next, and issues the tool calls. The procedure is a passenger in the conversation.&lt;/p&gt;

&lt;p&gt;A workflow engine turns that inside out. The procedure is a program: it runs in its own process, it holds the control flow, and when it needs intelligence it &lt;em&gt;invokes&lt;/em&gt; the coding agent, waits, and moves on.&lt;/p&gt;

&lt;p&gt;The agent is no longer the thing running the workflow. It is a subroutine the workflow calls.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmchoxl1q7c9e13xo4bns.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmchoxl1q7c9e13xo4bns.png" alt="Two panels. On the left, an orchestrator skill: a dashed boundary labelled " width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 1: The agent is no longer the thing running the workflow. It is a subroutine the workflow calls.&lt;/em&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Orchestrator skill&lt;/th&gt;
&lt;th&gt;Workflow engine&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Who decides the next step&lt;/td&gt;
&lt;td&gt;the model, reading prose&lt;/td&gt;
&lt;td&gt;the runtime, reading a graph&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Where the procedure lives&lt;/td&gt;
&lt;td&gt;a Markdown file, in the session&lt;/td&gt;
&lt;td&gt;a validated file, in its own process&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Where results accumulate&lt;/td&gt;
&lt;td&gt;the conversation&lt;/td&gt;
&lt;td&gt;run state on disk&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Model and provider&lt;/td&gt;
&lt;td&gt;whatever the session is running&lt;/td&gt;
&lt;td&gt;resolved per node&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Steps with no model in them&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;bash&lt;/code&gt; and &lt;code&gt;script&lt;/code&gt; nodes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A run ends when&lt;/td&gt;
&lt;td&gt;the process that started it exits&lt;/td&gt;
&lt;td&gt;it reaches a terminal state&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Everything below follows from that table. Outside the session, which agent serves a step becomes a setting, and the run becomes an object: run six at once, restart one that died, hand a paused one to somebody else.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://archon.diy" rel="noopener noreferrer"&gt;&lt;strong&gt;Archon&lt;/strong&gt;&lt;/a&gt; carries most of the examples below. It is a workflow engine, and the one we have been evaluating; parts three and four cover it properly. &lt;a href="https://github.com/microsoft/conductor" rel="noopener noreferrer"&gt;&lt;strong&gt;Microsoft Conductor&lt;/strong&gt;&lt;/a&gt; was released more recently and looked suspiciously similar.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the inversion buys
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The graph is data, and it is checked before anything runs
&lt;/h3&gt;

&lt;p&gt;A workflow definition describes nodes and the edges between them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;implement&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;command&lt;/span&gt;
  &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;build-feature&lt;/span&gt;
  &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;plan&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The engine parses that, validates it against a schema, and topologically sorts it to prove there are no cycles. A workflow referring to a node that does not exist fails at load time rather than halfway through a run. The machinery is unremarkable, a schema validator and a standard topological sort, and &lt;a href="https://fullgc.github.io/should-you-harness-the-harness-part-4/" rel="noopener noreferrer"&gt;part four&lt;/a&gt; walks through one implementation.&lt;/p&gt;

&lt;p&gt;The equivalent in an orchestrator skill is a numbered list in Markdown. Nothing to validate, because there is no schema, because it is prose. A phase referring to an artifact no earlier phase produces reads perfectly well and fails at three in the afternoon.&lt;/p&gt;

&lt;p&gt;So the first thing an engine buys is also the least discussed: &lt;strong&gt;the control flow becomes something a machine can check.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Parallelism nobody had to design
&lt;/h3&gt;

&lt;p&gt;Because dependencies are declared rather than narrated, the engine can work out which nodes have nothing left to wait for and run all of them at once. Archon computes topological layers and fires each concurrently; Conductor calls the same idea parallel groups.&lt;/p&gt;

&lt;p&gt;Parallelism here is a property of having written the dependencies down. An orchestrator skill runs independent phases one after another because its conversation is serial. An engine runs them together, for the same reason a build system compiles independent files together: the graph already says they do not depend on each other.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2aw9n7riqzu24d1iey1f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2aw9n7riqzu24d1iey1f.png" alt="Two stages. On the left, six nodes scattered with dependency arrows between them: plan, test, lint, implement, review, publish. On the right, the same six sorted into four layers, with test and lint sharing one amber-banded layer marked " width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 2: Nobody designed the parallelism. It is what writing the dependencies down already told you.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The nodes with no model in them
&lt;/h3&gt;

&lt;p&gt;Vocabularies differ across tools; the set does not.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Node kind&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;th&gt;Model in the path&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Command&lt;/td&gt;
&lt;td&gt;runs a packaged instruction through the coding agent&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Prompt&lt;/td&gt;
&lt;td&gt;sends an ad-hoc instruction&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bash / script&lt;/td&gt;
&lt;td&gt;runs a command or a file&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;no&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Loop&lt;/td&gt;
&lt;td&gt;repeats a sub-graph until a check passes&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;no&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Approval&lt;/td&gt;
&lt;td&gt;pauses and waits for a person&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;no&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cancel&lt;/td&gt;
&lt;td&gt;stops the run&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;no&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That right-hand column is the determinism argument, and it is easy to overclaim. &lt;a href="https://fullgc.github.io/designing-agentic-development-workflows-part-1/" rel="noopener noreferrer"&gt;Part one of the previous series&lt;/a&gt; promised &lt;em&gt;scripts over inference&lt;/em&gt;: anything decidable deterministically gets decided by a script. That promise was true and smaller than it sounded. The script was deterministic. &lt;strong&gt;Reaching&lt;/strong&gt; it was not, because reaching it meant a model reading a procedure and choosing to make the call.&lt;/p&gt;

&lt;p&gt;An engine closes exactly that gap. A &lt;code&gt;bash&lt;/code&gt; node runs because the graph says it runs. No prompt, no inference, no decision, and therefore no chance of the step being skipped, because nothing in the path is capable of skipping it.&lt;/p&gt;

&lt;p&gt;The command is repeatable, and so is the decision to issue it. That is what these tools advertise about themselves: Conductor keeps &lt;a href="https://opensource.microsoft.com/blog/2026/05/14/conductor-deterministic-orchestration-for-multi-agent-ai-workflows/" rel="noopener noreferrer"&gt;no LLM in the orchestration loop&lt;/a&gt;, and &lt;strong&gt;Bernstein&lt;/strong&gt;, a fixed pipeline from goal to merge, claims zero LLM tokens spent on coordination at all.&lt;/p&gt;

&lt;p&gt;So &lt;strong&gt;"deterministic" means something stronger here than it can mean in a skill.&lt;/strong&gt; In a skill, the commands are deterministic. In an engine, the commands are deterministic and so is the sequence, with intelligence confined to the nodes that asked for it.&lt;/p&gt;

&lt;h3&gt;
  
  
  The run outlives the session
&lt;/h3&gt;

&lt;p&gt;Part one argued that a workflow run is a state machine whether or not anyone writes one, and that writing your own means writing a workflow engine badly, in the margins of doing something else. This is the part an engine has already written.&lt;/p&gt;

&lt;p&gt;A run is a record in durable storage with a status on it. What matters is what putting that status somewhere authoritative allows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The record can be a lock.&lt;/strong&gt; A run reserving the working directory it occupies stops a second run entering a worktree that is already busy. That is &lt;a href="https://fullgc.github.io/designing-agentic-development-workflows-part-6/" rel="noopener noreferrer"&gt;part six's&lt;/a&gt; concurrency limitation, addressed at the only level where it can be.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A paused run outlives its process.&lt;/strong&gt; An approval node writes the status and stops; another process, a chat command or an HTTP call picks it up later. That is the mechanical reason a gate can be answered from a phone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A failed run can replay.&lt;/strong&gt; Completed nodes emit an event as they finish, so a restart consults what actually happened rather than a cursor recording where the run believed it was.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ambiguity can be surfaced rather than guessed.&lt;/strong&gt; When the system cannot tell "running elsewhere" from "orphaned by a crash", it can refuse to decide and say so. Easy to state, easy to leave out, and hand-rolled state machines leave it out.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this is exotic. It is the ordinary content of a workflow engine, and none of it is reachable from a Markdown file.&lt;/p&gt;

&lt;h3&gt;
  
  
  A run anything can address
&lt;/h3&gt;

&lt;p&gt;The obvious version of this claim is wrong. A skill can be started without a person: non-interactive mode expands a skill invocation in the prompt string, there is a flag for suppressing permission prompts when nobody is there to answer, and driving the whole thing from a CI job is documented. Triggering is not the discriminator.&lt;/p&gt;

&lt;p&gt;What an engine adds is that the thing started has an identity outside the process running it. Starting a run hands back an id and an event stream, and from there:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a colleague can approve a gate from Slack, on a run executing on somebody else's laptop;&lt;/li&gt;
&lt;li&gt;a dashboard can list what is in flight without asking the processes;&lt;/li&gt;
&lt;li&gt;a second run can discover that the working directory is taken;&lt;/li&gt;
&lt;li&gt;a failed run can be resumed by whichever process picks it up.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The platform surface follows from the same fact. Once a run id and an event stream exist, a Slack adapter, a web dashboard and a GitHub integration are all clients of the same state rather than separate integrations. That is a real gain in run-level observability: progress, status and cost per node, history across runs. Whether it is the kind of observability engineers actually missed is a different question.&lt;/p&gt;

&lt;h3&gt;
  
  
  Provider, model and context, per node
&lt;/h3&gt;

&lt;p&gt;An orchestrator skill runs on whatever the developer's session happens to be using. Its only lever on model choice is pushing work into a dispatched sub-agent that declares its own tier.&lt;/p&gt;

&lt;p&gt;An engine settles all three per node:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Model&lt;/strong&gt; resolves through a priority chain: the node's setting, the workflow's default, then config. Archon states the consequence flatly, that the model of the session invoking the engine has no influence whatsoever on the model serving a node. Conductor supports per-agent overrides across Copilot and Claude.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Provider&lt;/strong&gt; is the larger version of the same point. Sessions do not cross vendors, so context must be handed over explicitly, but nothing stops you planning on one agent and implementing on another. Mostly that is a bad idea. What matters is that it stops being architecturally impossible and becomes a line of configuration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Context&lt;/strong&gt; is threaded deliberately. Sequential nodes on one provider can inherit the previous session; parallel nodes start clean; a node can demand a fresh one. The subtle part is resume: a node continuing an earlier session should &lt;em&gt;fork&lt;/em&gt; that transcript rather than mutate it, so a retry cannot corrupt the history it retried from.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The provider argument is also the standard reason an engine is said to survive a vendor changing its terms. It is a real argument about &lt;em&gt;models&lt;/em&gt;, and there is a kind of vendor change it does not protect against at all, which the next section opens and &lt;a href="https://fullgc.github.io/should-you-harness-the-harness-part-5/" rel="noopener noreferrer"&gt;part five&lt;/a&gt; settles.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the inversion costs
&lt;/h2&gt;

&lt;p&gt;Neither of these gets better as the tools mature. One is the property the whole argument rests on, seen from the other side. The other is not about the engineering at all.&lt;/p&gt;

&lt;h3&gt;
  
  
  Enforceability and steerability are one thing
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1c2s473c71a1gspl3cuj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1c2s473c71a1gspl3cuj.png" alt="The two buttons meme. A sweating man agonises over two buttons, one labelled " width="224" height="340"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The control flow is data, fixed at definition time, not negotiable by a model. That is the reason to trust it. It is also the reason it is unpleasant to work with.&lt;/p&gt;

&lt;p&gt;When the harness is the caller, a developer cannot lean over and say skip this gate, I know exactly what this diff is, or ask me before you push from now on, or try it the other way. The thing that was a sentence in a chat becomes a pull request against a YAML file.&lt;/p&gt;

&lt;p&gt;It runs backwards too: the orchestrator skill a developer finds pleasantly steerable is the one a platform owner cannot rely on. One property, two signs, and no amount of tooling resolves it.&lt;/p&gt;

&lt;p&gt;Which of the two signs you notice depends on where you sit, which turns out to matter for adopting an engine more than any feature does. &lt;a href="https://fullgc.github.io/should-you-harness-the-harness-part-5/" rel="noopener noreferrer"&gt;Part five&lt;/a&gt; takes it up there, as a decision criterion.&lt;/p&gt;

&lt;h3&gt;
  
  
  The interop has to stay open
&lt;/h3&gt;

&lt;p&gt;An engine calls somebody else's coding agent headlessly, from outside, with nobody at the keyboard. Of every way to use a coding agent, that is the one a vendor has the clearest reason to price or fence off separately. This is not hypothetical: Anthropic has already drafted a policy drawing the line in exactly that place, then paused it before it took effect.&lt;/p&gt;

&lt;p&gt;That cuts against the section above. Resolving a provider per node is a real answer to a model being wrong for a step, and no answer at all to a vendor changing who is allowed to call it. &lt;a href="https://fullgc.github.io/should-you-harness-the-harness-part-5/" rel="noopener noreferrer"&gt;Part five&lt;/a&gt; has the dates, the amounts, and why switching does not escape it.&lt;/p&gt;

&lt;p&gt;None of that is an argument against engines. It is the thing you are accepting when you adopt one, and it belongs on the same page as the benefits.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the boundary is
&lt;/h2&gt;

&lt;p&gt;Plenty of things called workflow engines really are workflow engines. &lt;strong&gt;n8n&lt;/strong&gt; is one. &lt;strong&gt;LangGraph&lt;/strong&gt; is one. The question is narrower than that, and it has two parts: does the tool &lt;strong&gt;hold the control flow outside the model&lt;/strong&gt;, and can it &lt;strong&gt;run a step in your checkout without a model in the path&lt;/strong&gt;?&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Category and examples&lt;/th&gt;
&lt;th&gt;Control flow outside the model&lt;/th&gt;
&lt;th&gt;Non-model steps in your repo&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Coding-workflow engines: Archon, Conductor, Bernstein&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;In-harness scripting: Claude Code dynamic workflows&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Parallel-session supervisors: Claude Squad, Emdash, Vibe Kanban, Baton&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;not applicable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Graph frameworks: LangGraph&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;only if you build it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agent-loop builders: CrewAI, AutoGen, LangFlow, Flowise, Dify&lt;/td&gt;
&lt;td&gt;varies&lt;/td&gt;
&lt;td&gt;only if you build it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;General automation: n8n&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A script or CI pipeline you wrote&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;yes&lt;/strong&gt;, up to a point&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The top row does both by design. The bottom row also does both, and is not an engine: a &lt;code&gt;Makefile&lt;/code&gt; or a CI job calling the coding agent per step has real control flow, real model-free steps, and a run with an id and logs anybody can read. What it lacks is anything that understands coding agents, which is the distinction &lt;a href="https://fullgc.github.io/should-you-harness-the-harness-part-5/" rel="noopener noreferrer"&gt;part five&lt;/a&gt; turns on.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmwd0rqz22qrr9h4lod6a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmwd0rqz22qrr9h4lod6a.png" alt="A two-by-two chart. The horizontal axis is whether control flow is held outside the model; the vertical is whether a step can run in your checkout with no model in the path. Only the top right quadrant, shaded amber, holds coding-workflow engines and a script or CI pipeline you wrote. In-harness scripting, general automation and graph frameworks sit bottom right; parallel-session supervisors sit bottom left. The top left quadrant is empty." width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 3: Each near miss fails a different axis, which is why the marketing language cannot tell them apart.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The rest of this section is about the rows in between, and why the near misses miss.&lt;/p&gt;

&lt;h3&gt;
  
  
  The harness grew one of its own
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxoatpmpi1diqkzdiy2g3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxoatpmpi1diqkzdiy2g3.png" alt="A screenshot of Anthropic's dynamic workflows announcement, two passages highlighted. The first: " width="320" height="606"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Claude Code's &lt;a href="https://code.claude.com/docs/en/workflows" rel="noopener noreferrer"&gt;dynamic workflows&lt;/a&gt; are the same inversion turning up from the inside: a JavaScript script a runtime executes, where the documentation's own comparison table answers "who decides what runs next" with &lt;em&gt;the script&lt;/em&gt;, against the &lt;em&gt;Claude, turn by turn&lt;/em&gt; it gives for subagents, skills and agent teams. Anthropic names both halves of the trade itself: built for parallel and long-running work extending into hours and days, and consuming meaningfully more usage than a typical Claude Code session.&lt;/p&gt;

&lt;p&gt;Four things keep it a different animal:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The script cannot touch the filesystem or the shell.&lt;/strong&gt; Agents read, write and run commands; the script only coordinates them. So the control flow became deterministic and the steps did not, which is half the argument above, and the half that costs tokens.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Nothing but a person can start a run.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The lifecycle is scoped to the session&lt;/strong&gt; rather than durable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You do not author it.&lt;/strong&gt; Claude writes the script per task, so what you would want to review, version and constrain gets generated rather than declared.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That makes it the right tool for what it says it is for, a long one-off audit or migration running into the hours, with a warning at twenty-five agents or a projected 1.5 million tokens. It is the wrong one for a pipeline you wrote down, that runs a test suite as a step nothing can skip, and that starts when a ticket moves.&lt;/p&gt;

&lt;h3&gt;
  
  
  Parallel-session supervisors
&lt;/h3&gt;

&lt;p&gt;Run N agents in N git worktrees, show them on a board or in a TUI, let a human decide what to look at and what to merge. The interface is the only thing that really varies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Claude Squad&lt;/strong&gt;: a terminal UI over tmux sessions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Emdash&lt;/strong&gt;: an Electron app, with ticket intake from Linear, Jira and GitHub&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vibe Kanban&lt;/strong&gt;: a web board, one worktree per workspace&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Baton&lt;/strong&gt;: a CLI that polls GitHub issues and dispatches&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are genuinely useful, and they are not engines, because there is no pipeline. Coordination is manual, sequencing is a human choosing which session to click, and one worktree per agent is the &lt;em&gt;only&lt;/em&gt; structural thing they share with an engine. Augment's &lt;a href="https://www.augmentcode.com/tools/open-source-agent-orchestrators" rel="noopener noreferrer"&gt;survey of nine such tools&lt;/a&gt; puts it plainly: coordination is either manual or limited to task-graph scheduling within one repo, and only two of the nine model a multi-step pipeline as a graph at all.&lt;/p&gt;

&lt;h3&gt;
  
  
  Graph frameworks, and agent-loop builders
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpn0jv73sz3963avueuqy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpn0jv73sz3963avueuqy.png" alt="The " width="378" height="340"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/langchain-ai/langgraph" rel="noopener noreferrer"&gt;LangGraph&lt;/a&gt; deserves a fairer description than slide decks usually give it, which cast it as the loose agentic alternative to a rigorous engine. It has typed state, explicit nodes and edges, conditional routing, checkpointers that persist state after every node, and an &lt;code&gt;interrupt&lt;/code&gt; primitive for human review. The same primitives listed above.&lt;/p&gt;

&lt;p&gt;So it passes the first test comfortably, and fails the second. LangGraph is a library for building a stateful application, with no notion of your repository, your shell, your worktrees, or a coding agent as a callable step. Its checkpointers persist conversational state to Postgres or Redis and know nothing about a working copy. You would not adopt it, you would use it to &lt;em&gt;build&lt;/em&gt; the engine, and then you would own the engine, which is the outcome part one warned about.&lt;/p&gt;

&lt;p&gt;The agent-loop builders are the largest and most confusing group, because the marketing language is identical. &lt;a href="https://github.com/crewAIInc/crewAI" rel="noopener noreferrer"&gt;&lt;strong&gt;CrewAI&lt;/strong&gt;&lt;/a&gt;, &lt;strong&gt;AutoGen&lt;/strong&gt;, &lt;strong&gt;LangFlow&lt;/strong&gt;, &lt;strong&gt;Flowise&lt;/strong&gt; and &lt;strong&gt;Dify&lt;/strong&gt; all describe themselves in terms of agentic workflows. What they mostly build is a set of cooperating agents and a loop that runs until something decides it is done: AutoGen is explicit that its agents interact in a free-form group chat rather than a fixed sequence. Some, CrewAI in particular, do offer deterministic flows alongside the crews.&lt;/p&gt;

&lt;p&gt;But the object under construction is an AI application, and the LangGraph objection lands here too. If your test suite is a shell command that has to run in a checkout, none of them has anywhere to put it.&lt;/p&gt;

&lt;p&gt;That is the honest shape of the field as of writing, and the part of this article most likely to age badly. The taxonomy should outlast the names in it.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://fullgc.github.io/should-you-harness-the-harness-part-2/" rel="noopener noreferrer"&gt;fullgc.github.io&lt;/a&gt;, where the whole series lives.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>workflows</category>
      <category>automation</category>
    </item>
    <item>
      <title>Should you harness the harness: what the orchestrator skill cannot do</title>
      <dc:creator>Dani Shemesh</dc:creator>
      <pubDate>Wed, 16 Sep 2026 12:16:16 +0000</pubDate>
      <link>https://dev.to/dani_shemesh/should-you-harness-the-harness-what-the-orchestrator-skill-cannot-do-e98</link>
      <guid>https://dev.to/dani_shemesh/should-you-harness-the-harness-what-the-orchestrator-skill-cannot-do-e98</guid>
      <description>&lt;p&gt;A development workflow can be written as a set of skills the coding agent reads and follows: phases in order, artifacts between them, gates that check rather than promise, a script deciding when a loop stops. One skill holds the phase order and dispatches the rest, and that one is the &lt;strong&gt;orchestrator&lt;/strong&gt;. The &lt;a href="https://fullgc.github.io/designing-agentic-development-workflows-part-1/" rel="noopener noreferrer"&gt;previous series&lt;/a&gt; is a long description of building exactly this.&lt;/p&gt;

&lt;p&gt;It works. It also runs &lt;em&gt;inside&lt;/em&gt; the agent session, and that is what this series is about.&lt;/p&gt;

&lt;p&gt;The harness is the agent runtime itself: the thing that loads the skills, dispatches the sub-agents and runs the tools. Harnessing it means taking the procedure out of the session and putting it in a program that &lt;em&gt;calls&lt;/em&gt; the coding agent, rather than a document the coding agent reads.&lt;/p&gt;

&lt;p&gt;Whether that trade is worth making is the question these five parts work through. This one establishes why it is a question at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  A defect, or a limit
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://fullgc.github.io/designing-agentic-development-workflows-part-6/" rel="noopener noreferrer"&gt;Part six of the previous series&lt;/a&gt; already lists the things that hurt, and every one of them is a mistake: a gate described in prose instead of enforced by a script, packaged copies that drifted, two components sharing a schema nobody validated. All of them are fixable without changing the shape of the design. Write the missing check, delete the duplicate, add the contract test, and the limitation goes away.&lt;/p&gt;

&lt;p&gt;This is the other list. These do not go away, because they follow from &lt;em&gt;where the procedure runs&lt;/em&gt; rather than from how well it was written.&lt;/p&gt;

&lt;p&gt;The test for whether something belongs here is a single question: &lt;strong&gt;could a sufficiently disciplined author fix it by writing better skills?&lt;/strong&gt; If yes, it is a defect and belongs in part six's list. If no, it belongs here.&lt;/p&gt;

&lt;p&gt;That line is a judgment call, and someone else would draw it differently. A later section shows the cases I had to argue myself out of.&lt;/p&gt;

&lt;h2&gt;
  
  
  What better skills cannot reach
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;The limit&lt;/th&gt;
&lt;th&gt;Why no skill reaches it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Model, effort, provider&lt;/td&gt;
&lt;td&gt;only a dispatched sub-agent can declare a tier; the orchestrator runs on whatever the session runs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Its own context window&lt;/td&gt;
&lt;td&gt;the thing that would prune the conversation &lt;em&gt;is&lt;/em&gt; the conversation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Two phases at once&lt;/td&gt;
&lt;td&gt;sub-agents fan out inside a turn, but the conversation itself is serial&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Steps that need no intelligence&lt;/td&gt;
&lt;td&gt;every step is a tool call some model chose to make&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The order itself&lt;/td&gt;
&lt;td&gt;phase order is prose, followed because the model is inclined to follow it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A run another process can address&lt;/td&gt;
&lt;td&gt;a session id is not a run: nothing else can find it, watch it, pause it or resume it&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The first five get a section each below. The sixth gets one of its own further down, because it is the one that decides the question.&lt;/p&gt;

&lt;h3&gt;
  
  
  Model, effort, and provider
&lt;/h3&gt;

&lt;p&gt;The first principle in &lt;a href="https://fullgc.github.io/designing-agentic-development-workflows-part-2/" rel="noopener noreferrer"&gt;part two of the previous series&lt;/a&gt; states this as a fact of the runtime rather than a design choice: model and reasoning effort can only be set when a step is dispatched as a sub-agent. A skill's own metadata does not change what serves it.&lt;/p&gt;

&lt;p&gt;That is why the orchestrator is thin. It runs on whatever model the developer's session happens to be using, and the only way to say "this decision deserves the strongest model" is to push the decision out into a dispatched agent that can declare a tier.&lt;/p&gt;

&lt;p&gt;Turning that into a principle is the right thing to do with a constraint you cannot remove. It remains a constraint. The orchestrator is the one component whose cost and capability you cannot specify.&lt;/p&gt;

&lt;p&gt;The larger version is the provider, not the model. An orchestrator skill runs inside one harness and is married to it. Every step is served by whichever vendor's agent the developer happened to open, and if a step would be better served by a different one there is no way to say so, because the thing that would have to say it is a guest in the session it would need to replace.&lt;/p&gt;

&lt;p&gt;Once the procedure lives outside the session, the coding agent becomes a parameter. An engine resolves a provider per node, so planning can run on one vendor's agent and implementation on another's, with context handed between them explicitly because sessions do not cross providers.&lt;/p&gt;

&lt;p&gt;Whether mixing vendors mid-run is a good idea is a separate question, and mostly it will not be. What matters is that it stops being an architectural impossibility and becomes a line of configuration.&lt;/p&gt;

&lt;h3&gt;
  
  
  Its own context window
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0obazzg60iiq1nxo8zaj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0obazzg60iiq1nxo8zaj.png" alt="The Spider-Man pointing meme. Two identical Spider-Men point at each other, one labelled " width="604" height="340"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A run is one conversation. Everything every phase reads or writes accumulates in it, and the orchestrator has no programmatic control over the wrapper it runs in. When context needs pruning between phases, the only available move is to stop and ask the developer to type &lt;code&gt;/compact&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The previous series works around this rather than solving it. Dispatched sub-agents get fresh context, which is why "judgment runs in fresh context" is a principle. But the orchestrator's own conversation grows for the length of the run, and the longer the run, the worse its judgment about its own procedure gets.&lt;/p&gt;

&lt;p&gt;An engine threads sessions deliberately. Sequential nodes on one provider inherit the prior session, parallel nodes start clean, and a node can opt out with &lt;code&gt;context: fresh&lt;/code&gt;. When a node resumes an earlier session it forks it rather than mutating it, so a retry cannot corrupt the transcript it retried from.&lt;/p&gt;

&lt;p&gt;None of that can be arranged from inside the conversation. Whatever did the arranging would be part of what it was arranging.&lt;/p&gt;

&lt;h3&gt;
  
  
  Two phases at once
&lt;/h3&gt;

&lt;p&gt;Parallelism inside a phase works. Dispatch several sub-agents in a single turn and they run concurrently, which is exactly how the review workflow fans its reviewer lanes out.&lt;/p&gt;

&lt;p&gt;What cannot overlap is two phases. The orchestrator is one conversation with one turn in flight, so it dispatches a fan-out, waits for all of it to come back, and only then does anything else. Independent phases still run in sequence, and a slow one blocks everything behind it whether or not anything depends on it.&lt;/p&gt;

&lt;p&gt;Most of the skeleton genuinely is a straight line, so this costs less than it sounds: you cannot implement before you plan.&lt;/p&gt;

&lt;p&gt;An engine computes topological layers from the dependency edges and fires every independent node in a layer at once. That falls out of the control flow being a graph instead of a sequence of prompts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Steps that need no intelligence
&lt;/h3&gt;

&lt;p&gt;The guarantee table in &lt;a href="https://fullgc.github.io/designing-agentic-development-workflows-part-1/" rel="noopener noreferrer"&gt;part one of the previous series&lt;/a&gt; promises &lt;em&gt;scripts over inference&lt;/em&gt;: anything decidable deterministically is decided by a script. That guarantee is real, and smaller than it sounds.&lt;/p&gt;

&lt;p&gt;The script is deterministic. Reaching it is not. In an orchestrator skill there is no such thing as a step the model sits out. Running the test suite means the model reads the procedure, decides this is the moment, and issues a tool call. The command that finally runs is exact and repeatable. Everything upstream of it is a judgment about whether to run it at all.&lt;/p&gt;

&lt;p&gt;So a phase requiring no intelligence whatsoever still costs a round-trip, still costs tokens, and still carries some probability of not happening. Individually that probability is small. Across a twelve-phase skeleton it compounds, and it compounds invisibly, because a step quietly skipped leaves nothing behind that says so.&lt;/p&gt;

&lt;p&gt;Which is why the previous series ends up recommending a tripwire per phase and a guard on every seam. Those exist to catch a class of failure that only exists because the model is a mandatory participant in its own control flow.&lt;/p&gt;

&lt;p&gt;An engine removes the model from the steps that never needed it. A &lt;code&gt;bash&lt;/code&gt; or &lt;code&gt;script&lt;/code&gt; node runs because the graph says so: no prompt constructed, no tokens spent, no inference, and no way to skip the step because nothing in the path is capable of deciding to skip it. The orchestration layer consumes no tokens at all, and the structure is fixed at definition time.&lt;/p&gt;

&lt;p&gt;That changes what &lt;strong&gt;deterministic&lt;/strong&gt; can mean. In an orchestrator skill it means &lt;em&gt;the commands are deterministic&lt;/em&gt;. In an engine it means &lt;em&gt;the commands are deterministic and so is the decision to run them&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpcsnj8lr1hmejz0fda09.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpcsnj8lr1hmejz0fda09.png" alt="Two lanes. In the orchestrator lane, the model reads the procedure, then a dashed box marks a judgment about whether this is the moment, then the test suite runs; a dotted branch leaves the lane labelled " width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 1: The command is identical in both. What differs is whether anything had to decide to reach it.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;One native mechanism escapes this: hooks fire on runtime events without the model choosing to fire them. But hooks intercept a run, they do not sequence one. They can stop something happening. They cannot make the next thing happen.&lt;/p&gt;

&lt;h3&gt;
  
  
  The order itself
&lt;/h3&gt;

&lt;p&gt;This is the deep one, and &lt;a href="https://fullgc.github.io/designing-agentic-development-workflows-part-6/" rel="noopener noreferrer"&gt;part six of the previous series&lt;/a&gt; states it plainly without drawing the full conclusion: an instruction to a language model is a strong default, never a guarantee, and anything load-bearing needs a script behind it.&lt;/p&gt;

&lt;p&gt;The design responds by putting scripts behind the load-bearing parts. The gate check is a script. The loop referee is a script. The seam guards are scripts. Each converts one instruction into an enforced rule.&lt;/p&gt;

&lt;p&gt;But the &lt;em&gt;order&lt;/em&gt; is prose. The list of phases, which one follows which, what happens on a failure: all of it lives in a Markdown file and gets followed because the model is inclined to follow it. You can guard every seam and still have no guarantee the phases ran in the order the document describes, because nothing outside the model is tracking which phase comes next.&lt;/p&gt;

&lt;p&gt;The scripts are patches over individual holes in a surface made of prose.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1s15vtu3y2yvyim8kq6a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1s15vtu3y2yvyim8kq6a.png" alt="A wide slab representing the phase order written as prose, punched through by four holes. Three holes are covered by small amber plates labelled gate check, loop referee and seam guard. The fourth and largest hole, labelled " width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 2: Each script closes one hole. The surface they are fixed to is still prose.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;An engine inverts this. The graph is data, parsed and schema-validated and checked for cycles before anything runs. The runtime decides which node executes next, and the model is invoked &lt;em&gt;by&lt;/em&gt; a node rather than being the thing that decides there is a next node at all. Enforcement stops being a set of patches and becomes the default state of the medium.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one that decides it
&lt;/h2&gt;

&lt;p&gt;A workflow run is a state machine whether or not anyone writes one. It is pending, then running. It pauses at a gate and has to still be paused tomorrow. It completes, or fails, or is cancelled, or is abandoned halfway and resumed on Thursday.&lt;/p&gt;

&lt;p&gt;Something has to know which of those is true. That something has to survive the process that started it, be reachable by a process that did not start it, and be trustworthy enough that two runs cannot both believe they own the same working directory.&lt;/p&gt;

&lt;p&gt;That middle clause is the one that catches people out. A skill run non-interactively has a session, and a session is addressable by whoever holds its id on the machine that has it. A run is addressable by anyone: a colleague approving a gate from Slack, a dashboard listing what is in flight, a second process deciding whether the working directory is free. The difference is not convenience, it is whether the procedure exists anywhere other than inside the process executing it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F56p7xex8e2rl1kutwu0l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F56p7xex8e2rl1kutwu0l.png" alt="Two panels. On the left, a session: a dashed process box holding the run, with Slack, a dashboard, another run and tomorrow each connected by a line that stops short and ends in a cross. On the right, a run: an amber database row that all four reach with arrows." width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 3: The difference is not convenience. It is whether the procedure exists anywhere other than inside the process executing it.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The previous series does implement this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a run directory per run;&lt;/li&gt;
&lt;li&gt;a small JSON state file written atomically, temp file then rename, so a crash cannot leave a half-written record;&lt;/li&gt;
&lt;li&gt;a phase cursor, so an interrupted run can resume;&lt;/li&gt;
&lt;li&gt;terminal states enumerated deliberately, including awkward ones like done-but-unproven, with a principle requiring a report on every one.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is a state machine. It is also hand-rolled, maintained by the same people trying to write the workflows, and every gap in it gets discovered in production.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://fullgc.github.io/designing-agentic-development-workflows-part-6/" rel="noopener noreferrer"&gt;Part six's&lt;/a&gt; concurrency limitation is exactly such a gap. Nothing arbitrates two runs against one checkout, because arbitrating that needs a lock with an owner and an expiry, which needs a store, which needs something to stay authoritative when the process holding it dies.&lt;/p&gt;

&lt;p&gt;Now look at what a real one contains. An engine's runs live in a database rather than a file, precisely so more than one process can observe and act on the same run. A row that outlives the process holding it can double as a lock on the working directory, be resumed by whoever picks it up, and record what actually happened instead of what a cursor believed. &lt;a href="https://fullgc.github.io/should-you-harness-the-harness-part-2/" rel="noopener noreferrer"&gt;Part two&lt;/a&gt; takes each of those apart.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The choice is not between having a lifecycle and not having one.&lt;/strong&gt; It is between adopting one and writing one, and writing one well means writing a workflow engine badly, incrementally, in the margins of doing something else.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is merely harder
&lt;/h2&gt;

&lt;p&gt;Some things look structural and are not. Saying so is what keeps the list above credible, and these are the ones I had to argue myself out of.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Looks structural&lt;/th&gt;
&lt;th&gt;What it actually is&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Triggering&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;claude -p "/my-workflow LB-123"&lt;/code&gt; runs a skill non-interactively, so a CI job, a webhook or a cron entry can start one&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Isolation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;a skill can create a worktree, and the previous series does. What it lacks is a &lt;em&gt;lock&lt;/em&gt;, and it could shell out to one&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Resume&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;a run directory and a phase cursor are enough. An engine replaying from an event log is better, not different in kind&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Observability&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;artifacts are readable after the fact. What is missing is a live event stream: a gap in convenience, not capability&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Per-step cost&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;no breakdown by phase, only the provider console. Worth having, not worth changing architecture for&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Triggering needs one clarification, because the missing piece is easy to misplace. It is not the trigger, it is the &lt;em&gt;run&lt;/em&gt;. A skill started by a cron entry is still a session, and nothing else can find it, watch it, pause it at a gate or resume it tomorrow. That is the last row of the table above.&lt;/p&gt;

&lt;p&gt;Isolation is the closest call. A second run entering the same worktree is exactly the kind of failure a skill cannot see coming, but the guard is a script and a skill can call a script. What it cannot do is &lt;em&gt;own&lt;/em&gt; the lock, which is the previous problem again.&lt;/p&gt;

&lt;p&gt;The cost row has a caveat of its own, and &lt;a href="https://fullgc.github.io/should-you-harness-the-harness-part-3/" rel="noopener noreferrer"&gt;part three&lt;/a&gt; has it: what an engine reports per step is not the same as what the run costs you.&lt;/p&gt;

&lt;h2&gt;
  
  
  What follows
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8d6xyrfrua3qr09ut8of.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8d6xyrfrua3qr09ut8of.png" alt="The Office meme. Pam holds up two sheets of paper, one reading " width="303" height="340"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Two of these are the same fact seen from either end. Every step runs through the model because the model holds the control flow, and the control flow is advisory because the thing holding it is a model. Provider selection, context and parallelism trace back to the same root: the orchestrator is a participant in the conversation rather than the thing running it.&lt;/p&gt;

&lt;p&gt;The last one is different in kind, and it is the one that decides things. The others say what an orchestrator skill cannot do. That one says what it will make you do instead: maintain a workflow engine you never set out to write.&lt;/p&gt;

&lt;p&gt;All of which is a case for looking at a workflow engine, not for adopting one. Before that question can be answered, the category needs a definition, because "engine" has been doing a lot of unexamined work above and most of what is sold under the name is something else.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://fullgc.github.io/should-you-harness-the-harness-part-1/" rel="noopener noreferrer"&gt;fullgc.github.io&lt;/a&gt;, where the whole series lives.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>workflows</category>
      <category>automation</category>
    </item>
    <item>
      <title>Designing agentic development workflows: where it hurts, and what to build first</title>
      <dc:creator>Dani Shemesh</dc:creator>
      <pubDate>Mon, 14 Sep 2026 14:39:10 +0000</pubDate>
      <link>https://dev.to/dani_shemesh/designing-agentic-development-workflows-where-it-hurts-and-what-to-build-first-3m5f</link>
      <guid>https://dev.to/dani_shemesh/designing-agentic-development-workflows-where-it-hurts-and-what-to-build-first-3m5f</guid>
      <description>&lt;p&gt;Five parts of design, and design flatters itself. This one does not.&lt;/p&gt;

&lt;p&gt;What follows is where the shape described so far still hurts, all of it observed in real runs, and then the short list of what to build first if you are starting one. If you read nothing else in the series, read the list.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Follows on from the whole series, but stands alone: every limitation names the thing it broke.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it hurts
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F65ab0n0ztxrj04i04q6e.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F65ab0n0ztxrj04i04q6e.jpg" alt="A skeleton sitting on a park bench, having waited so long it died. Caption: " width="251" height="340"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Honest limitations, all observed in real runs, because a design description without them is marketing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Enforcement exists only where the check exists.&lt;/strong&gt; Every rule that mattered and was not a script eventually got skipped: the unchecked gate; the "always write a report" promise that silently covered only the &lt;em&gt;enumerated&lt;/em&gt; exits; the "keep going" instruction that lost to an agent's instinct to check in. A single unanswered "shall I carry on?" abandoned a run that was already green, two phases from the finish. Politeness is indistinguishable from a crash. The uncomfortable lesson: an instruction to a language model is a strong default, never a guarantee, and anything load-bearing needs a script behind it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkge34pm0scabm8mwpats.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkge34pm0scabm8mwpats.jpeg" alt="Five rules in a row. Three are backed by a script and hold. Two are backed only by prose, and both were skipped on a real run." width="800" height="320"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 1: Every rule that mattered and was not a script eventually got skipped. The amber columns are the ones that had only an instruction behind them.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Uniformity decays.&lt;/strong&gt; In a family built over months, one workflow guards every seam and another guards none; one dispatches its work as tiered agents while another invokes the same steps inline, where they cannot be tiered, isolated or individually measured. The design stays coherent; the implementations drift from it at different rates, and nothing notices.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Adapters disagree at the schema.&lt;/strong&gt; One component queried an identifier and never wrote it out; the component that needed it failed for want of it, and failed &lt;em&gt;after&lt;/em&gt; the code had already been published. Two components, one implicit schema, no test at the boundary. If two things share a data shape, make the shape explicit and validate it, or it will drift.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;History gets overwritten.&lt;/strong&gt; A loop that rewrites its verdict file each iteration keeps only the final state, so the convergence (twelve failing, then three, then zero) is lost. That trajectory is exactly what you want when asking whether the loop actually works. Append; don't overwrite.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Signals get confused with their proxies.&lt;/strong&gt; A platform flag meaning "this comment's anchor line moved" was read as "this comment is no longer true". Those are different claims and they came apart badly: items whose anchor had &lt;em&gt;not&lt;/em&gt; moved were nonetheless judged against code that no longer existed. Before depending on a platform signal, ask what it literally measures.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Concurrency is limited by the target, not the workflow.&lt;/strong&gt; Running several instances at once sounds free until you find that the shared working copy is single-occupancy during publishing, that the test database is one container which the first run to finish deletes out from under the others, and that container names are global to the machine. Most of what caps parallelism lives in the system being worked on.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Improvement is genuinely hard to measure.&lt;/strong&gt; Elapsed time is confounded by caching; token counts by model changes; "number of findings" rewards a noisier reviewer; and an operator who has seen the task before is faster regardless of the tooling. What survives: whether specific, named frictions recur, and counts of expensive operations: environment boots, dependency re-locks, full-suite runs, loop iterations, human waits. Counts cannot be faked by a warm cache.&lt;/p&gt;

&lt;h3&gt;
  
  
  What no amount of care will fix
&lt;/h3&gt;

&lt;p&gt;Everything above is a defect. Write the missing check, delete the duplicate copy, add the contract test at the seam, and the limitation goes away. The shape of the design does not have to change.&lt;/p&gt;

&lt;p&gt;There is a second category that does not work that way, because it follows from running the procedure &lt;em&gt;inside&lt;/em&gt; the agent session rather than around it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The orchestrator cannot choose its own model, its effort or its provider, because a skill's metadata does not change what serves it.&lt;/li&gt;
&lt;li&gt;It cannot manage its own context, so its judgment about its own procedure degrades over a long run, and the only remedy is to stop and ask a human to type &lt;code&gt;/compact&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;It cannot do two things at once, so a fan-out of parallel reviewers is sequential underneath.&lt;/li&gt;
&lt;li&gt;It cannot be started by anything but a person at a terminal, which is the last prerequisite in &lt;a href="https://fullgc.github.io/designing-agentic-development-workflows-part-5/" rel="noopener noreferrer"&gt;part five&lt;/a&gt; and the one that caps how autonomous a workflow of this shape can become.&lt;/li&gt;
&lt;li&gt;It cannot make a step deterministic, only the command inside it. A phase needing no intelligence at all still reaches its script through a model deciding to call it, so what you get is a repeatable command behind an unrepeatable decision: a round trip in cost, and some chance of the step not happening at all.&lt;/li&gt;
&lt;li&gt;It cannot avoid having a lifecycle, so pending and paused and resumed and cancelled become yours to implement: incrementally, in the margins of doing something else.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No amount of better skill-writing reaches any of that. Whether it is worth reaching for something that does is a separate question, and a longer one: &lt;a href="https://fullgc.github.io/should-you-harness-the-harness-part-1/" rel="noopener noreferrer"&gt;Should you harness the harness&lt;/a&gt; works through it.&lt;/p&gt;




&lt;h2&gt;
  
  
  If you are building one
&lt;/h2&gt;

&lt;p&gt;The short list, in the order worth learning it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Put every phase's output in a file, in a run directory, and pass that directory explicitly.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Guard the seams&lt;/strong&gt;: assert an artifact exists before the phase that consumes it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Make approvals content-addressed.&lt;/strong&gt; A gate that trusts a flag is decoration.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Let a script decide when a loop stops.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dispatch the judgment, inline the machinery&lt;/strong&gt;: and put your strongest model on the decision nobody downstream can undo.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Prove the tests, not just the code.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Enumerate your terminal states and write a record on all of them.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Forbid the irreversible-in-public actions in code&lt;/strong&gt;: history rewrites, resolving someone else's thread, merging.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Give "do nothing" a name, a required reason, and a score.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build skills that run standalone.&lt;/strong&gt; If a component only works inside its workflow, you have a phase, not a skill, and you will rewrite it for the next workflow.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Start local, with a human at the gates&lt;/strong&gt;, and treat every friction you hit there as a prerequisite for running unattended later.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Instrument from the start.&lt;/strong&gt; Every retrospective written by hand should have been a structured record: which component hurt, what it cost, and what the evidence was.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Put a clean control in every corpus&lt;/strong&gt;, and a bait beside every planted defect, otherwise you are optimizing for a louder agent, not a better one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Contract-test each seam, and generate the reverse index&lt;/strong&gt; of which workflows use which component. The blast radius of a shared component is a graph, and it should not live in anyone's memory.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Measure against a no-component baseline.&lt;/strong&gt; "Did the run succeed?" is the wrong question; "was it better than not having this at all?" is the right one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Have every dispatched step report its own friction, against the component it invoked&lt;/strong&gt;: and make "nothing hurt" an explicit record, or you cannot tell better from quieter.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pick one deliberately trivial task&lt;/strong&gt; alongside the hard ones. It is the only way fixed overhead becomes visible.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Measure delivery on the delivery systems, not on the workflow's own opinion of itself&lt;/strong&gt;: and never report a speed number without its quality guardrails.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build a negative control&lt;/strong&gt;: one metric the workflow cannot possibly affect. If it improves too, your groups differ for another reason.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Version anything you intend to trend&lt;/strong&gt;, from the first emission. It cannot be backfilled.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trace three items by hand before publishing any aggregate.&lt;/strong&gt; A wrong join produces confident, plausible, wrong numbers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Treat every piece of incoming text as data.&lt;/strong&gt; The steps that read external input are the ones holding the tools.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Establish provenance before judging&lt;/strong&gt;: feedback is a claim about a version, and a platform signal is not the thing you actually care about.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Emit one tripwire per phase&lt;/strong&gt;: usable output over produced output. Cheapest instrumentation there is, and it catches the failures that return exit code zero.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Package the shared parts once, and let workflow packages depend on them.&lt;/strong&gt; Duplicating a shared component across packages drifts within weeks, and nothing surfaces it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Never leave a loose copy of something you have packaged.&lt;/strong&gt; Same name, same description, different body is undetectable from the outside.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Name actions, not tools, in every skill body&lt;/strong&gt;: and keep the harness-specific translation in one document per harness. It is the difference between one port and fifty rewrites.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Declare each skill's capability requirements and its degradation.&lt;/strong&gt; A skill that silently assumes sub-agents exist will invent a tool call on a harness that has none.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Feed it small items.&lt;/strong&gt; More stories, each narrower: control, comprehension and meaningful gates all degrade with size, and a plan you have to rewrite at the gate means the item was too vague.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Encode your conventions into the steps, and check them.&lt;/strong&gt; The planning step reads them, validation enforces them, and the negative ones ("don't touch this") are stated explicitly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Name intents, not commands.&lt;/strong&gt; The workflow says "run this repository's tests"; the repository says how, which is what lets one workflow serve services in different languages, and what stops a hardcoded test command from failing silently in the next repo.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These are small distributed systems whose components are language models. What makes them trustworthy is files, hashes, exit codes, and a refusal to let any single step be the judge of its own success.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://fullgc.github.io/designing-agentic-development-workflows-part-6/" rel="noopener noreferrer"&gt;fullgc.github.io&lt;/a&gt;, where the whole series lives.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>workflows</category>
      <category>automation</category>
    </item>
    <item>
      <title>Designing agentic development workflows: shipping it, porting it, letting it run</title>
      <dc:creator>Dani Shemesh</dc:creator>
      <pubDate>Mon, 14 Sep 2026 14:38:57 +0000</pubDate>
      <link>https://dev.to/dani_shemesh/designing-agentic-development-workflows-shipping-it-porting-it-letting-it-run-lmf</link>
      <guid>https://dev.to/dani_shemesh/designing-agentic-development-workflows-shipping-it-porting-it-letting-it-run-lmf</guid>
      <description>&lt;p&gt;The first four parts describe a workflow that runs on your machine and can be shown to work. This part is about the three transitions that turn it into something other people depend on, and each one costs something specific.&lt;/p&gt;

&lt;p&gt;Packaging makes a collection installable and lets one workflow depend on another's components, at the price of a dependency graph nobody can hold in their head. Porting to another harness is cheap for everything that is a file and expensive for everything that is control flow. And autonomy, meaning a ticket assigned to the agent and a reviewed pull request some time later, turns out to be less about capability than about making the exceptional case detectable.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Leans on &lt;a href="https://fullgc.github.io/designing-agentic-development-workflows-part-1/" rel="noopener noreferrer"&gt;part one&lt;/a&gt; and &lt;a href="https://fullgc.github.io/designing-agentic-development-workflows-part-2/" rel="noopener noreferrer"&gt;part two&lt;/a&gt; throughout.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Packaging and distribution
&lt;/h2&gt;

&lt;p&gt;At some point this stops being configuration on your machine and becomes something other people install. That step forces a decision the design has been able to defer: what is the unit of distribution?&lt;/p&gt;

&lt;p&gt;A note on words, because the two get used interchangeably and they are not the same. This section says &lt;strong&gt;package&lt;/strong&gt; for the general idea, since the argument holds whatever your harness calls it. Every concrete example is a &lt;strong&gt;plugin&lt;/strong&gt;, which is Claude Code's implementation of one. Where something is true only of plugins rather than of packaging in general, it is called out.&lt;/p&gt;

&lt;h3&gt;
  
  
  What a package can hold
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbvd4o2ukoonav9zqdaph.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbvd4o2ukoonav9zqdaph.jpg" alt="Patrick Star from SpongeBob, mid-idea, proposing: " width="193" height="340"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;More than skills. A plugin (the packaging format in Claude Code, and the concrete example throughout this section) can carry skills, agents, slash commands, hooks, MCP servers, LSP servers, background monitors, executables that go on &lt;code&gt;PATH&lt;/code&gt; while it is enabled, default settings, and a schema for configuration it asks the user for at install time. It can also carry arbitrary payload (dashboards, docs, scripts) and reference its own files through &lt;code&gt;${CLAUDE_PLUGIN_ROOT}&lt;/code&gt;, with &lt;code&gt;${CLAUDE_PLUGIN_DATA}&lt;/code&gt; for state that must survive updates.&lt;/p&gt;

&lt;p&gt;That matters more than it sounds. A workflow's whole apparatus can travel as one unit: the orchestrator, the skills it composes, its agents, the hooks that measure it, and the dashboard that reads what they emit. Not just the prompts. The dashboard is the odd one out, riding along as plain files rather than as something the runtime loads, which is what &lt;code&gt;${CLAUDE_PLUGIN_ROOT}&lt;/code&gt; is for. See the &lt;a href="https://code.claude.com/docs/en/plugins.md" rel="noopener noreferrer"&gt;plugins guide&lt;/a&gt; and &lt;a href="https://code.claude.com/docs/en/plugins-reference.md" rel="noopener noreferrer"&gt;reference&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6rg7cpc9ynedbmtax8xp.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6rg7cpc9ynedbmtax8xp.jpeg" alt="A single package. Its upper half holds the components the runtime loads: skills, agents, hooks, commands and MCP servers. Its lower half holds files that merely ride along, a dashboard and docs. One arrow delivers the whole package to a team in one install." width="800" height="379"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 1: One install brings the whole apparatus. The runtime loads the top row; the bottom row rides along as files, which is why a bundled dashboard has to find itself through &lt;code&gt;${CLAUDE_PLUGIN_ROOT}&lt;/code&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  One package, or one per workflow?
&lt;/h3&gt;

&lt;p&gt;This is a distribution question, not an architecture question, and the answer depends on who consumes it, so the trade-off matters more than any default:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;One package for everything&lt;/th&gt;
&lt;th&gt;One package per workflow&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Versioning&lt;/td&gt;
&lt;td&gt;one number for unrelated changes&lt;/td&gt;
&lt;td&gt;independent cadence per workflow&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Consumers&lt;/td&gt;
&lt;td&gt;everyone gets all of it&lt;/td&gt;
&lt;td&gt;a team enables only what it uses&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Blast radius of a release&lt;/td&gt;
&lt;td&gt;the whole family&lt;/td&gt;
&lt;td&gt;one workflow&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Shared components&lt;/td&gt;
&lt;td&gt;trivially one copy&lt;/td&gt;
&lt;td&gt;must live &lt;em&gt;somewhere&lt;/em&gt;, the hard part&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hooks&lt;/td&gt;
&lt;td&gt;unambiguous&lt;/td&gt;
&lt;td&gt;composition across packages is undocumented&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Enable/disable is per package, not per component, you cannot ship five workflows in one package and let a team turn four of them off. That alone makes the monolith uncomfortable once more than one team consumes it.&lt;/p&gt;

&lt;h3&gt;
  
  
  The fact that decides it: packages can depend on packages
&lt;/h3&gt;

&lt;p&gt;A manifest accepts a &lt;code&gt;dependencies&lt;/code&gt; array: bare names, or names with a semver range. I verified this against the validator rather than taking it on trust:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"workflow-x"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.2.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"dependencies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"wf-core"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"wf-telemetry"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"~2.1.0"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install a package whose dependency is missing and the install fails and names what to install rather than half-working. Conflicting ranges from different packages are intersected, and an unsatisfiable combination fails loudly instead of silently picking. Depending across marketplaces is blocked unless the marketplace explicitly allows it. And a package may consist of &lt;em&gt;nothing but&lt;/em&gt; a dependency list, which is the documented way to ship a bundle. See &lt;a href="https://code.claude.com/docs/en/plugin-dependencies.md" rel="noopener noreferrer"&gt;plugin dependencies&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;That is what makes the split viable. Go one package per workflow and the components several workflows share have to live somewhere, and every obvious home is bad: copy them into each package and they drift, put them in one workflow's package and the others depend on an unrelated workflow, keep everything together and you lose per-team enable/disable. A dependency array removes the dilemma. The shared layer becomes its own package, versioned on its own cadence, and each workflow package declares a dependency on it.&lt;/p&gt;

&lt;p&gt;That also settles what granularity to choose. Without dependencies, the shared code dictates it: you keep things together because splitting means duplicating. Once the shared layer can stand alone, that constraint is gone and you are free to decide by who installs what.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Package granularity should follow the consumer boundary, not the code structure.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The question to answer is not "how is this organized?" but "who installs it, and do different installers want different subsets?" That gives a decision rule rather than a preference:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One consumer group, everything used together&lt;/strong&gt; → one package, one version. Granularity you don't need is pure coordination cost: more manifests, more version numbers, more ways for the set to be half-installed. Do not split a collection because it &lt;em&gt;looks&lt;/em&gt; like several things.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Different consumers want different subsets, or parts need different release cadences&lt;/strong&gt; → split, with the shared layer as its own package that the workflow packages depend on. This is the point at which per-package enable/disable and independent versioning start paying for their overhead.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Either way, the shared layer exists exactly once.&lt;/strong&gt; Whether it is a directory inside one package or a package of its own, two copies of a shared component is the failure mode below, not a strategy.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Two things make the split cheap if it comes later, and both are worth doing up front even when you don't need them yet:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reserve the original name for the bundle.&lt;/strong&gt; If the package everyone installs is &lt;code&gt;X&lt;/code&gt;, then when you split, &lt;code&gt;X&lt;/code&gt; becomes a package that is nothing but a dependency list. Consumers keep typing the same install command and never learn that the internals changed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep the internal boundaries visible before they are enforced.&lt;/strong&gt; Directories that already separate "shared" from "this workflow only" split cleanly. A flat pile does not.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When the conditions above do call for a split, this is the shape it takes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Core&lt;/strong&gt;: the deterministic primitives (run state, seam guards, source-control isolation), the shared judgment skills (plan, implement, validate, take-approval, write-failing-tests, publish), and the measurement hooks. One copy, one version, one owner.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Per workflow&lt;/strong&gt;: the orchestrator, the skills only it uses, its agents. Depends on core with a range.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bundle&lt;/strong&gt;: dependencies only, under the name consumers already type.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Three traps
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2j1dfvgci437yu1n0nye.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2j1dfvgci437yu1n0nye.jpg" alt="Charlie from Always Sunny in front of a conspiracy board covered in photos and red string, gesturing wildly. Caption: " width="454" height="340"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Duplication drifts silently, and faster than you expect.&lt;/strong&gt; One observed case: a workflow packaged with twelve skills, eight of them shared with other workflows that were still distributed loose. Three weeks later, five of the six shared skills had diverged: the packaged copies three to seven lines behind, with the &lt;em&gt;same name and a byte-identical description but a different body&lt;/em&gt;. Nothing surfaces that. The lesson is not "don't package": it is &lt;strong&gt;one source of truth, and packaging is downstream of it.&lt;/strong&gt; Never edit the packaged copy, and never leave a loose copy of something you have packaged.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Skills get namespaced. Agents don't.&lt;/strong&gt; A packaged skill is addressed as &lt;code&gt;package:skill&lt;/code&gt;, which makes collisions impossible, but it is also a rename, so every cross-reference in your instructions has to survive it. Worse, if a loose copy of the same skill still exists, it keeps the short unprefixed name and answers to it, which is exactly how two divergent copies coexist unnoticed.&lt;/p&gt;

&lt;p&gt;Agents have no namespace at all, and resolve by a priority ladder in which packaged agents rank lowest, below project-level and user-level ones (&lt;a href="https://code.claude.com/docs/en/sub-agents.md" rel="noopener noreferrer"&gt;sub-agents&lt;/a&gt;). So if a workflow installs its agents into the user directory &lt;em&gt;and&lt;/em&gt; ships them in a package, the user copies quietly win and the packaged ones are dead weight. Pick one home for agents and stick to it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Hook composition across packages is undocumented.&lt;/strong&gt; Whether hooks from several packages all fire on the same event, and in what order, is not specified anywhere I could find. Do not build a design that depends on it. &lt;strong&gt;Put hooks in exactly one package&lt;/strong&gt;, which is a good constraint regardless, because measurement is cross-cutting and therefore belongs in core.&lt;/p&gt;

&lt;h3&gt;
  
  
  What packaging buys beyond convenience
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Version identity.&lt;/strong&gt; A run can record which package version served it, which is what makes the "version anything you intend to trend" rule from part four actually implementable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Team enforcement.&lt;/strong&gt; A marketplace declared in project settings plus an enabled-packages list gives everyone the same set, updating on its own, no "did you copy the new one?" ritual (&lt;a href="https://code.claude.com/docs/en/plugin-marketplaces.md" rel="noopener noreferrer"&gt;marketplaces&lt;/a&gt;, &lt;a href="https://code.claude.com/docs/en/discover-plugins.md" rel="noopener noreferrer"&gt;installing&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Opt-in by default.&lt;/strong&gt; A package can install disabled, and prompt for the configuration it needs at enable time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A package boundary is also a test boundary.&lt;/strong&gt; The "without the component" comparison from part four is naturally per package: does this one earn its place?&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Context cost is not the deciding factor
&lt;/h3&gt;

&lt;p&gt;A package contributes its skills' names and descriptions to the prompt. Five packages of ten skills and one package of fifty cost about the same, because all fifty names appear either way. Split for &lt;strong&gt;ownership and release cadence&lt;/strong&gt;, not to save context. What genuinely costs context is MCP servers, whose tool definitions are present on every turn, so bundle those deliberately, and separately.&lt;/p&gt;

&lt;h3&gt;
  
  
  Migration order
&lt;/h3&gt;

&lt;p&gt;Whichever granularity you land on, the order of operations is the same, because each step de-risks the next:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;One source of truth in the repository.&lt;/strong&gt; Whatever copies exist elsewhere (hand-installed directories, an older package, a colleague's machine) reconcile and delete them first. Packaging a collection that already has two divergent copies just makes the divergence permanent, and this is not a packaging problem.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Core package first&lt;/strong&gt;: shared spine plus hooks, published and consumed by nothing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One workflow package&lt;/strong&gt;, depending on core, taken end to end: install, run, measure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Then the rest&lt;/strong&gt;, plus the bundle.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Record the package version in every run&lt;/strong&gt;, so the next question, "did this get better?", is answerable at all.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Portability across harnesses
&lt;/h2&gt;

&lt;p&gt;Everything so far has one unstated dependency: the harness: the agent runtime that loads the skills, dispatches the sub-agents, fires the hooks and enforces the permissions. That is a real lock-in risk, and it is worth knowing which parts of the design carry over and which parts &lt;em&gt;are&lt;/em&gt; the port. (Portability across &lt;em&gt;repositories&lt;/em&gt;, the same workflow against services in different languages and on different platforms, is the other axis, and it is the "name intents, not commands" rule in part two.)&lt;/p&gt;

&lt;h3&gt;
  
  
  What is already standardized
&lt;/h3&gt;

&lt;p&gt;More than people assume, as of 2026:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The skill format.&lt;/strong&gt; &lt;a href="https://agentskills.io/home" rel="noopener noreferrer"&gt;Agent Skills&lt;/a&gt; is an open specification: a folder with a &lt;code&gt;SKILL.md&lt;/code&gt; (YAML frontmatter plus a Markdown body) that can bundle scripts and references. It was published in December 2025 and is stewarded through the Agentic AI Foundation; by 2026 &lt;a href="https://www.paperclipped.de/en/blog/agent-skills-open-standard-interoperability/" rel="noopener noreferrer"&gt;30–40 tools read the same files from the same directories&lt;/a&gt;, across competing vendors. Notably, &lt;code&gt;.agents/skills/&lt;/code&gt; is the one location every major tool reads natively.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The instructions file.&lt;/strong&gt; &lt;code&gt;AGENTS.md&lt;/code&gt; is the cross-tool convention for "how to build and test this project", now under the same foundation and read by dozens of agents (&lt;a href="https://www.iuriio.com/blog/posts/2026/05/agents-md-field-guide-2026" rel="noopener noreferrer"&gt;field guide&lt;/a&gt;). Some harnesses still prefer their own filename and import it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The tool layer.&lt;/strong&gt; Anything you build as an MCP server works across compliant runtimes without a rewrite. That is the portable way to expose a capability.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The packaging format.&lt;/strong&gt; An &lt;a href="https://kingy.ai/blog/openai-agent-plugins-open-standard/" rel="noopener noreferrer"&gt;Agent Plugins&lt;/a&gt; standard was announced in August 2026 by several vendors together: a root manifest, fixed locations for skills and MCP configuration, and a namespaced escape hatch for client-specific behavior. Worth tracking against whatever native format you package in today.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What is not standardized, and won't be soon
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Capability&lt;/th&gt;
&lt;th&gt;Portability&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Skills (content, frontmatter, bundled files)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Portable&lt;/strong&gt;: same format, same directory&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Project instructions&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Near-portable&lt;/strong&gt;: one convention, minor filename differences&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tools, via MCP&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Portable&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sub-agent dispatch&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Divergent&lt;/strong&gt;: isolated agent files, "personas", TOML definitions, persistent modes: different models of what a sub-agent even &lt;em&gt;is&lt;/em&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hooks / lifecycle events&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Divergent&lt;/strong&gt;: event sets differ sharply, and one harness is far richer than the rest&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Slash commands&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Fragmented&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Model and effort selection per step&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Harness-specific&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Permissions, sandboxing, background tasks&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Harness-specific&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The pattern is consistent: &lt;strong&gt;content ports, control flow doesn't.&lt;/strong&gt; The more a capability is about orchestration, the less portable it is.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Farayet6c41z0bhje7kg9.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Farayet6c41z0bhje7kg9.jpeg" alt="Two harnesses; the lower layer of skills, artifacts, scripts and gates crosses intact, while the upper layer of sub agents, hooks, model tiers and permissions must be rebuilt." width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 2: The file based half ports for free. The orchestration half is the port.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The one rule that buys portability
&lt;/h3&gt;

&lt;p&gt;From &lt;a href="https://github.com/obra/superpowers/blob/main/docs/porting-to-a-new-harness.md" rel="noopener noreferrer"&gt;obra/superpowers' porting guide&lt;/a&gt;, which is the most concrete write-up of this problem I have found, and whose central invariant is worth adopting verbatim:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Skills name actions, not tools.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Write "read the file", "run the command", "invoke the planning skill", "dispatch a sub-agent", never the harness's actual tool name. Porting then means adding a tool-mapping document for the new harness that translates each action into its real tool names, and it &lt;em&gt;never&lt;/em&gt; reaches into a skill body to swap names. Their companion rule: everything ships through the harness's own install mechanism, and you never edit the user's files.&lt;/p&gt;

&lt;p&gt;This is testable, and worth measuring on your own collection. In one measured collection, skill bodies almost never named a tool (three of fifty-four mentioned one) while the orchestration layer was the opposite: a dozen guides named the skill-invocation tool directly, and two dozen agent definitions carried harness-specific model, effort and tool-allowlist fields. Which is exactly the split you would predict, and it tells you where a port's cost lands.&lt;/p&gt;

&lt;h3&gt;
  
  
  What a port actually costs
&lt;/h3&gt;

&lt;p&gt;Per harness, roughly four things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Bootstrap&lt;/strong&gt;: how the entry-point instructions reach the model. The superpowers guide treats automatic session-start injection as a hard requirement: if the only way to get your framework in front of the model is a per-session opt-in, that harness cannot be properly supported.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Discovery&lt;/strong&gt;: how skills are found: a native skill mechanism, a plugin registration API, a manifest field, a generated index, or documenting "read the &lt;code&gt;SKILL.md&lt;/code&gt; directly" as the sanctioned path.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tool mapping&lt;/strong&gt;: one document per harness. Get the tool names from the harness itself, not from its docs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Distribution&lt;/strong&gt;: the harness's own install channel.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Degrade explicitly; never fake
&lt;/h3&gt;

&lt;p&gt;When a harness lacks a capability, the skill must say &lt;strong&gt;do it inline, or report the missing capability&lt;/strong&gt;, never invent a tool call that doesn't exist. Their word for this is &lt;em&gt;degradable&lt;/em&gt;, and the useful discipline is deciding the degradation in advance:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No sub-agents&lt;/strong&gt; → run the step inline, and state plainly that the fresh-context property is lost. That is a real reduction in quality, not a cosmetic one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No hooks&lt;/strong&gt; → measurement moves to a scheduled reader over the artifacts, which part four argues you need anyway for anything happening after the session.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No task-tracking tool&lt;/strong&gt; → a plan file.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No per-step model selection&lt;/strong&gt; → the tiering in part three collapses; say which decisions are now running on the wrong tier rather than pretending the design is intact.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Why this design ports better than most
&lt;/h3&gt;

&lt;p&gt;Not by luck: the earlier principles do the work:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Artifacts are the interface&lt;/strong&gt;, and the run directory is the only channel. Files are the most portable thing in computing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gates are content hashes in a file&lt;/strong&gt;. No harness feature required, so the whole approval mechanism survives a port untouched.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The primitives are scripts&lt;/strong&gt;, and the loop referee is a script. Exit codes are universal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Terminal states and reports are files&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What genuinely doesn't survive: per-step model and effort tiering, hook-based measurement, and the sub-agent isolation that several principles depend on. So the honest summary is that the file-based half ports for free, and the orchestration half is the port.&lt;/p&gt;

&lt;h3&gt;
  
  
  A portability checklist
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Keep skills in the standard format and the standard location.&lt;/li&gt;
&lt;li&gt;Name actions, not tools, and grep your own skills to check.&lt;/li&gt;
&lt;li&gt;Keep exactly one tool-mapping document per harness, outside the skills.&lt;/li&gt;
&lt;li&gt;Keep state in files, not in harness memory.&lt;/li&gt;
&lt;li&gt;State each skill's capability requirements explicitly, with a declared degradation for each.&lt;/li&gt;
&lt;li&gt;Put everything harness-specific behind a single boundary (the packaging manifest, the hooks file, the agent definitions) so a port touches a known, small set of files rather than fifty skill bodies.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  From local to autonomous
&lt;/h2&gt;

&lt;p&gt;Local-first is the starting position, not the ceiling. For some workflows the destination is unattended operation, and the shape of that destination is worth stating concretely.&lt;/p&gt;

&lt;h3&gt;
  
  
  The target flow
&lt;/h3&gt;

&lt;p&gt;A ticket is assigned to the agent and moved into a queued state. That transition, not a human typing a command, is the trigger. The agent picks the ticket up, runs understand → plan → failing tests → build loop, and opens a pull request. The review and the response to review are part of the chain, not separate human errands: the review workflow reviews the change, the fix workflow triages those findings and addresses the ones that deserve it, and only then does a human look. What they find is a pull request that has already survived a review round and been revised once, with a written record of what was changed, what was argued against, and why.&lt;/p&gt;

&lt;h3&gt;
  
  
  The gates do not disappear; they move
&lt;/h3&gt;

&lt;p&gt;This is the key transition, and it is a design job rather than a switch:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Today:&lt;/em&gt; a human approves every plan.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Tomorrow:&lt;/em&gt; a policy approves the ordinary plan and a human approves the exceptional one, and it approves it &lt;em&gt;through the same mechanism&lt;/em&gt;, by recording the approval the way a human would (part four), so the gate check itself never learns there was nobody there. "Ordinary" is machine-checkable: the plan touches no protected file, stays inside a size budget, introduces no new dependency, carries no unresolved open question. Anything else escalates to a person.&lt;/li&gt;
&lt;li&gt;The workflow's real job becomes making "exceptional" detectable. That is where the design effort goes, and it is why the plan artifact needs to be structured rather than prose.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Some gates stay human permanently
&lt;/h3&gt;

&lt;p&gt;The list is the same one from part two: rewriting shared history, resolving someone else's review thread, merging, and anything that speaks on the team's behalf. Autonomy means removing the &lt;em&gt;approval&lt;/em&gt; step from reversible work, never from irreversible-in-public work.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6jnapbwclb4sb9nbj12q.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6jnapbwclb4sb9nbj12q.jpeg" alt="The same pipeline today and when autonomous. The plan gate slot keeps its size and position but its approver changes from a human to a policy script, which escalates to a human when a plan touches a protected file, exceeds the size budget, adds a dependency or carries an open question. Both approvers write the same hash into the same approval log. The publish gate stays human in both rows, and four actions are never automated." width="800" height="380"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 3: The gates move; they do not disappear. A policy approves the ordinary plan through the same recorded mechanism a human would use, so the gate check never learns there was nobody there, and the publish gate never changes hands at all.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What must be true first
&lt;/h3&gt;

&lt;p&gt;Every one of these is a prerequisite, and every one of them was learned from a run that had a human watching:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Every rule enforced by a script.&lt;/strong&gt; An unattended run has nobody to notice a skipped gate. The unenforced-gate story in part two is a nuisance with a human present and a silent data-loss event without one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No question that stops the run.&lt;/strong&gt; "Shall I carry on?" is fatal without an audience. An autonomous workflow either proceeds, or reaches a terminal state and reports; there is no third option.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Terminal states and a report on every exit, including abandonment.&lt;/strong&gt; The report is the only thing a human will read, and for most runs it will be the only thing they read.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Environment-vs-test failure classification.&lt;/strong&gt; Otherwise an infrastructure blip consumes the whole budget and produces a confident, wrong verdict.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Isolation strong enough for concurrency.&lt;/strong&gt; Separate working copies, no shared mutable services, no two runs racing on one database or one container name.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Restraint that holds without supervision.&lt;/strong&gt; The review-and-fix leg must be able to decline a wrong suggestion when nobody is there to back it up. This is the hardest prerequisite and the one most worth evaluating explicitly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost and iteration ceilings&lt;/strong&gt;, because nobody is watching the meter.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Structured telemetry per step&lt;/strong&gt;, so a fleet of runs can be audited after the fact instead of inspected one at a time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An eval suite you actually trust&lt;/strong&gt; (part four), including a clean control. Unattended operation without one is not automation: it is unmonitored production.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Something other than a person to start it.&lt;/strong&gt; A ticket transition can only be a trigger if some process is listening for it and can begin a run, and that process has to outlive any terminal session. This is the one prerequisite on the list that better skills cannot supply.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;The honest reading:&lt;/strong&gt; most of the work between "useful locally" and "trustworthy unattended" is not model capability. It is enforcement, isolation, classification and reporting: the boring parts, again. The exception is the last item, and it is a real one. Everything above it can be built inside the skills; a runner cannot, because a procedure that lives inside an interactive session has nothing to start it. &lt;a href="https://fullgc.github.io/designing-agentic-development-workflows-part-6/" rel="noopener noreferrer"&gt;Part six&lt;/a&gt; returns to that.&lt;/p&gt;




&lt;p&gt;That is the workflow packaged, ported and, eventually, left to run on its own. What is left is the part a description like this owes the reader, and it is &lt;a href="https://fullgc.github.io/designing-agentic-development-workflows-part-6/" rel="noopener noreferrer"&gt;part six&lt;/a&gt;: where the design still hurts, and the short list of what to build first.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://fullgc.github.io/designing-agentic-development-workflows-part-5/" rel="noopener noreferrer"&gt;fullgc.github.io&lt;/a&gt;, where the whole series lives.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>workflows</category>
      <category>automation</category>
    </item>
    <item>
      <title>Designing agentic development workflows: is it working, and is it worth it</title>
      <dc:creator>Dani Shemesh</dc:creator>
      <pubDate>Mon, 14 Sep 2026 14:38:37 +0000</pubDate>
      <link>https://dev.to/dani_shemesh/designing-agentic-development-workflows-is-it-working-and-is-it-worth-it-4g5j</link>
      <guid>https://dev.to/dani_shemesh/designing-agentic-development-workflows-is-it-working-and-is-it-worth-it-4g5j</guid>
      <description>&lt;p&gt;Everything so far has been design, and design argues for itself. This part is about evidence, which does not.&lt;/p&gt;

&lt;p&gt;Three independent ways to know whether a workflow works are worth building, and they answer different questions. Most teams build one of them and then over-claim from it. An external corpus asks whether a step's output is correct. The workflow reporting on itself asks where the process hurts, costs nothing, and is available on day one. Delivery metrics ask whether anything shipped sooner and at what price, which is the question a sponsor asks first and the hardest of the three to answer honestly. Each has a characteristic way of being gamed: a looser grader, a quieter agent, a confounded comparison group.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Assumes the artifacts and terminal states from &lt;a href="https://fullgc.github.io/designing-agentic-development-workflows-part-2/" rel="noopener noreferrer"&gt;part two&lt;/a&gt;, because every measurement here reads them.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing and evaluation
&lt;/h2&gt;

&lt;p&gt;"How do you test this?" is really four questions. A workflow contains four kinds of thing, and each needs a different kind of test.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;The thing&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;th&gt;How you test it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;A script&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;run state, a seam guard, branch setup&lt;/td&gt;
&lt;td&gt;like any other software: run it, check the exit code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;An adapter&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;code that reads or writes an external platform&lt;/td&gt;
&lt;td&gt;check it still gives its consumer what that consumer needs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;A judgment step&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;deciding, reviewing, classifying, planning&lt;/td&gt;
&lt;td&gt;you can't assert an opinion. Score it on tasks whose answers you know&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;The workflow itself&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;the whole thing, end to end&lt;/td&gt;
&lt;td&gt;run it on a small task; check the result and the leftovers&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The two dead ends are trying to unit-test an opinion, and trying to eyeball a script. Sort the component into one of those rows first and the method follows.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where to start
&lt;/h3&gt;

&lt;p&gt;In this order. Each step is cheaper than the next.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Validate the structure&lt;/strong&gt;: does every component parse, and do its file references resolve? A second per commit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test the scripts&lt;/strong&gt;: one test per documented exit code, plus "run it twice and nothing changes the second time." Most of these get re-entered after a failure, so repeating safely matters.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test the seams&lt;/strong&gt;: for each pair of components that pass data, check the producer still emits what the consumer needs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Smoke-test one whole workflow&lt;/strong&gt; on the smallest real task you have.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Then&lt;/strong&gt; build a scored task set for the judgment steps. Most valuable, most expensive: do it once you know what failure looks like.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  The seams are where things quietly break
&lt;/h3&gt;

&lt;p&gt;Borrow contract testing from microservices: the consumer writes down what it needs from the producer, and that expectation runs as a test on &lt;em&gt;both&lt;/em&gt; sides whenever either changes. Only what the consumer actually uses gets pinned, so the producer stays free to change everything else. See &lt;a href="https://docs.pact.io/" rel="noopener noreferrer"&gt;Pact&lt;/a&gt; or &lt;a href="https://totalshiftleft.ai/blog/what-is-api-contract-testing" rel="noopener noreferrer"&gt;this overview&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Not theoretical: in one observed case a component fetched an identifier and never wrote it out. The component that needed it failed for want of it, &lt;em&gt;after&lt;/em&gt; the change had already been published. Two components, one unwritten assumption, no test at the boundary.&lt;/p&gt;

&lt;p&gt;This is also the answer to "a component is used by five workflows, what did I just break?" Keep a generated map of which workflows use which component, and let a change select the suites to run.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scoring a judgment step
&lt;/h3&gt;

&lt;p&gt;You can't write an assertion against an opinion. You can assemble tasks whose right answers you already know, run the step against them, and score it. The task set is the work; the scoring is easy.&lt;/p&gt;

&lt;p&gt;Most of this is from Anthropic's &lt;a href="https://anthropic.com/engineering/demystifying-evals-for-ai-agents" rel="noopener noreferrer"&gt;Demystifying evals for AI agents&lt;/a&gt;, which is worth reading in full:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Start with 20–50 tasks taken from real failures&lt;/strong&gt;: your bug tracker, not your imagination. Early on, changes have big effects, so a small set discriminates fine.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A task is only usable if two people who know the domain would give the same verdict.&lt;/strong&gt; Otherwise you are measuring your own ambiguity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write down a known-good answer for each task.&lt;/strong&gt; It proves the task is solvable &lt;em&gt;and&lt;/em&gt; that your scoring works: the two problems that otherwise look like a weak agent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Include tasks where the right answer is "do nothing."&lt;/strong&gt; Most people skip this. If every task has a problem to find, the best-scoring agent is the one that always finds something. Include correct code that looks suspicious, and one task with nothing wrong at all.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;If everything fails, suspect the task.&lt;/strong&gt; A 0% score across many attempts usually means the task is broken or ambiguous, not that the agent is incapable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Grade the result, not the steps taken.&lt;/strong&gt; Checking that an agent followed a particular sequence of tool calls is, in Anthropic's words, &lt;em&gt;"too rigid and results in overly brittle tests"&lt;/em&gt;, capable models keep finding valid routes nobody planned for. If you do care about the path, check only that it didn't do anything &lt;em&gt;extra&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read a few transcripts before believing any score.&lt;/strong&gt; A number can rise because the scoring got looser.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Two task sets are worth having: a planted one, where you introduce problems deliberately and record the answers, as your gate; and a replay one, which costs nothing to write: take real finished work that already has real human feedback, run the workflow against how things looked then, and compare.&lt;/p&gt;

&lt;p&gt;If a &lt;em&gt;model&lt;/em&gt; does the grading, spot-check its verdicts against human ones now and then, and watch for it drifting lenient, a wrong answer marked correct inflates everything downstream.&lt;/p&gt;

&lt;h3&gt;
  
  
  Living with randomness
&lt;/h3&gt;

&lt;p&gt;The same input doesn't give the same output twice, so one run tells you little. Run each task a few times and decide which you need: at least one attempt succeeds (fine when you only need one good answer), or &lt;strong&gt;every attempt succeeds&lt;/strong&gt; (when consistency is the point). Those two diverge fast as attempts increase, so say which one you are quoting.&lt;/p&gt;

&lt;p&gt;Then two practical rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Start every attempt from a clean slate.&lt;/strong&gt; Leftover files and warm caches cause failures that look real but aren't.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compare the outputs, not just the pass rate.&lt;/strong&gt; A step that finds three good things, then three different good things, can score identically each time and still be impossible to depend on.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Cheap checks that punch above their weight
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Tripwires.&lt;/strong&gt; Emit one number per phase: how much of what it produced is actually &lt;em&gt;usable&lt;/em&gt;. The example that earned this a permanent place: a review step produced twelve findings, and none of them pointed at a real file and line. Nothing would ever be posted. Every exit code was zero, so no task set would have caught it. One counter made it obvious.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Relations you can assert with no answer key.&lt;/strong&gt; When you can't say what the right output is, you can often say how two outputs must relate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;shuffle the input order → the decisions should be the same&lt;/li&gt;
&lt;li&gt;feed the same item twice → it should be deduplicated&lt;/li&gt;
&lt;li&gt;feed something with nothing wrong → the output should be empty&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These need no task set, take minutes to write, and catch instability single examples never will. (The formal name is &lt;a href="https://en.wikipedia.org/wiki/Metamorphic_testing" rel="noopener noreferrer"&gt;metamorphic testing&lt;/a&gt;.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Testing a workflow that needs human approval.&lt;/strong&gt; This looks impossible, since something has to approve the plan. The wrong fix is a "skip the gate" flag, which removes the very thing you wanted to cover. Instead, have the test write &lt;em&gt;exactly the approval record a human would have written&lt;/em&gt;: same file, same format, same hash. The gate check downstream passes unchanged, because it cannot tell the difference and doesn't need to. Only the source of the approval changes. The same trick is how a gate later becomes an automatic policy (part five).&lt;/p&gt;

&lt;h3&gt;
  
  
  Where the test cases come from
&lt;/h3&gt;

&lt;p&gt;Before building any of it: read what actually happened. Sample real runs, note what went wrong in plain words, group those notes into named failure categories, and count how often each occurs. Thirty to fifty by hand is the usual starting point.&lt;/p&gt;

&lt;p&gt;The one-line version, from Hamel Husain and Shreya Shankar's work on evals, is hard to improve on: &lt;strong&gt;look at your actual data, by hand, before building any automated metric.&lt;/strong&gt; A task set built without this tests the failures you imagined; one built after it tests the failures you have.&lt;/p&gt;

&lt;h3&gt;
  
  
  What your runtime may already do for you
&lt;/h3&gt;

&lt;p&gt;Check before building anything. A capable agent runtime may offer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A "without the component" comparison&lt;/strong&gt;: run the same task set twice, once with the component installed and once with it removed, and compare the scores. This is an ablation, and it is the single most valuable measurement available, because it answers &lt;em&gt;"does this thing earn its place?"&lt;/em&gt; rather than &lt;em&gt;"did the model succeed?"&lt;/em&gt; A workflow can score 80% on your corpus while your planning skill contributes nothing: the model was getting there anyway. You only find that out by taking the skill away and running the same twenty tasks again.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Separate grading for "did it trigger" and "did it do the right thing"&lt;/strong&gt;: two different bugs, and the first is easy to miss entirely.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deterministic graders&lt;/strong&gt;: pattern matches, "was this tool called", "was this file created", alongside model-based scoring.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Repeats, pass thresholds, cost ceilings and machine-readable output&lt;/strong&gt;, which is all CI needs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Claude Code has a &lt;code&gt;claude plugin eval&lt;/code&gt; command that does exactly this: its &lt;code&gt;--ablation with-without&lt;/code&gt; mode runs a no-plugin baseline arm and reports the delta. As of 2.1.259 it is early access and undocumented, so not everyone has it and none of the detail is guaranteed to hold. Worth watching rather than building on, because a runtime that runs the comparison for you turns the most valuable measurement here into a flag.&lt;/p&gt;

&lt;p&gt;What they generally don't give you: checks partway through a long workflow, partial credit, or any way to assert which sub-agent ran on which model. For those, split the workflow into one case per phase, or check the files the run left behind, a good reason for phases to write files.&lt;/p&gt;

&lt;h3&gt;
  
  
  Five checks that need no test set at all
&lt;/h3&gt;

&lt;p&gt;Assert these after &lt;em&gt;any&lt;/em&gt; run, with no answer key:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;did it finish in one of its declared end states, rather than just stopping?&lt;/li&gt;
&lt;li&gt;does every file it promised exist?&lt;/li&gt;
&lt;li&gt;was every gate satisfied by a real approval?&lt;/li&gt;
&lt;li&gt;did the loop stay inside its limits?&lt;/li&gt;
&lt;li&gt;were there any stops that weren't designed: a question that halted it, a missing input?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you build nothing else, build these five and the component map. They are cheap, need no labeled data, and between them catch most of the ways a workflow quietly rots.&lt;/p&gt;

&lt;h3&gt;
  
  
  Further reading
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://anthropic.com/engineering/demystifying-evals-for-ai-agents" rel="noopener noreferrer"&gt;Demystifying evals for AI agents&lt;/a&gt;, Anthropic. Task-set design, graders, anti-patterns. Start here.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://hamel.dev/blog/posts/evals-faq/" rel="noopener noreferrer"&gt;LLM evals FAQ&lt;/a&gt;, Hamel Husain, and the &lt;a href="https://atalupadhyay.wordpress.com/2026/08/17/ai-evals-done-right-error-analysis-open-axial-coding-and-building-an-llm-as-judge-you-can-actually-trust/" rel="noopener noreferrer"&gt;error-analysis method&lt;/a&gt; behind "read your data first".&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.pact.io/" rel="noopener noreferrer"&gt;Pact&lt;/a&gt;, contract testing for the seams.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://en.wikipedia.org/wiki/Metamorphic_testing" rel="noopener noreferrer"&gt;Metamorphic testing&lt;/a&gt;, asserting relations instead of answers.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.langchain.com/langsmith/trajectory-evals" rel="noopener noreferrer"&gt;Trajectory evals&lt;/a&gt;, LangChain, if you decide you do want to grade the path.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://axiom.co/docs/ai-engineering/evaluate/handling-non-determinism" rel="noopener noreferrer"&gt;Handling non-determinism&lt;/a&gt;, repeats and aggregation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything above grades the workflow from the outside. There is a second approach, cheaper and available on day one: the workflow reporting on itself. That is the next part of this one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making the workflow evaluate itself
&lt;/h2&gt;

&lt;p&gt;Every technique so far grades a workflow from the outside: build a corpus, run it, score the output. That is indispensable and expensive: the corpus is the work, and you cannot build one before you know what failure looks like. Which is the well-known bootstrapping problem: the teams who most need evals have no labeled data yet.&lt;/p&gt;

&lt;p&gt;There is a second axis, and it inverts the arrangement. Have the workflow report on itself. Each dispatched step records where it hurt; the orchestrator sums up its own run; those records aggregate across many runs, per workflow &lt;em&gt;and&lt;/em&gt; per component. No labeled data, no answer key, and it runs on the real distribution of work rather than the one you imagined.&lt;/p&gt;

&lt;p&gt;The two are complements, not alternatives:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;External evals&lt;/th&gt;
&lt;th&gt;Self-reported friction&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Question answered&lt;/td&gt;
&lt;td&gt;&lt;em&gt;is the output correct?&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;where does the process hurt?&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Needs a labeled corpus&lt;/td&gt;
&lt;td&gt;yes, the expensive part&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Distribution tested&lt;/td&gt;
&lt;td&gt;the one you authored&lt;/td&gt;
&lt;td&gt;the one you actually get&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scales with&lt;/td&gt;
&lt;td&gt;authoring effort&lt;/td&gt;
&lt;td&gt;usage&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Available&lt;/td&gt;
&lt;td&gt;after you know the failure modes&lt;/td&gt;
&lt;td&gt;immediately&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Can it be gamed&lt;/td&gt;
&lt;td&gt;by a looser grader&lt;/td&gt;
&lt;td&gt;by a quieter agent&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Crucially, the second bootstraps the first. The taxonomy that self-reporting produces is precisely the open-coding-then-axial-coding step a corpus needs, except the agents do the open coding as they go, instead of a human reading traces weeks later.&lt;/p&gt;

&lt;h3&gt;
  
  
  Exercise the design deliberately
&lt;/h3&gt;

&lt;p&gt;Passive collection tells you about the work you happened to do. To learn about the &lt;em&gt;design&lt;/em&gt;, choose the work so it spans the design's axes. A useful spread for a family of workflows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;one item requiring a schema or data migration (exercises irreversibility and generated artifacts);&lt;/li&gt;
&lt;li&gt;one that is pure logic, deliberately trivial;&lt;/li&gt;
&lt;li&gt;one that cannot be completed without touching a protected or "ask first" file (does the workflow notice the constraint, or edit through it?);&lt;/li&gt;
&lt;li&gt;one that changes a published interface (exercises back-compat judgment, which is what reviewers actually argue about).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The trivial one is not filler; it is the control. If a task with no migration, no new model and no generated files still costs the full apparatus (every phase, every gate) that is fixed overhead, and fixed overhead is invisible unless something cheap is measured beside something expensive.&lt;/p&gt;

&lt;h3&gt;
  
  
  What a self-report has to contain
&lt;/h3&gt;

&lt;p&gt;Free-text retrospectives do not aggregate. A structured record does, and the field list is where the value is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The component it is about&lt;/strong&gt;: not the phase. This is the primary axis; without it every finding collapses to "the workflow is annoying". Validate the name against a known list, or you will get typos and the non-answer "the workflow".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Impact&lt;/strong&gt; (what it did to &lt;em&gt;this&lt;/em&gt; run: blocked / caused rework / merely slow) kept separate from severity (what it would do to a future one). They are orthogonal: a trivial papercut can block, and a serious latent gap can cost nothing today.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Evidence, required and non-empty.&lt;/strong&gt; It is the only thing separating a finding from an opinion: a command and its output, or a file and a line.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A signature&lt;/strong&gt;, so the same problem reported by four runs collapses to one row.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Who reported it&lt;/strong&gt;, which is both a compliance denominator and the difference between a step's own view and the orchestrator's outside view of it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Optionally a suggested fix&lt;/strong&gt;, optional on purpose, because requiring one invites invented ones.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The nil protocol
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcyz07sxkwljnnbvlzwx2.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcyz07sxkwljnnbvlzwx2.jpg" alt="Roll Safe, a man tapping his temple as though he has outsmarted everyone: " width="604" height="340"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A step that hit no friction still files a report: the same self-report described above, carrying an explicit "nothing hurt" value rather than simply not being written.&lt;/p&gt;

&lt;p&gt;Without it, silence and not-bothering are indistinguishable, and those two mean opposite things. A required nil also gives the count a denominator: three frictions across forty steps, rather than three across an unknown number. The failure it prevents is specific. Fix a component, watch the reports drop, and you cannot tell whether the component improved or the agents went quiet. That is the same trap named earlier for corpora without a clean control, and it is the cheapest safeguard in the design.&lt;/p&gt;

&lt;h3&gt;
  
  
  The rollup, and what its columns must not hide
&lt;/h3&gt;

&lt;p&gt;Aggregate by (component, signature), then print, per group:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;count &lt;em&gt;and&lt;/em&gt; distinct runs, separately. Four records from four runs is four independent confirmations; four from one run is one run tripping repeatedly. Same count, opposite meanings.&lt;/li&gt;
&lt;li&gt;breadth: which workflows reported it, by name. This column &lt;em&gt;is&lt;/em&gt; the answer to "is this a shared design fault or one workflow's wording problem", and it is how a fault in a component used by five workflows earns five times the attention.&lt;/li&gt;
&lt;li&gt;both raw and per-workflow-normalized severity. Shared components will accumulate more findings simply by being used more; printing only the raw total confirms that hypothesis by construction, and printing only the normalized one hides the real total cost. Print both and say which you are quoting.&lt;/li&gt;
&lt;li&gt;a reporting-hygiene section: runs that filed neither findings nor nils, findings with suspiciously thin evidence, unknown component names. This is the part that keeps the rest honest, and it is the only place hard targets belong: &lt;em&gt;reporting rate&lt;/em&gt; and &lt;em&gt;evidence present&lt;/em&gt;, never friction counts.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Then fix, and run the same work again
&lt;/h3&gt;

&lt;p&gt;The improvement loop closes by re-running the same items on the improved workflows. That comparison is worth something only if the second run is genuinely a second run:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;fresh sessions, with no transcript of the first in context;&lt;/li&gt;
&lt;li&gt;reset state: the tracker items returned to their starting condition, prior artifacts archived rather than left where an autodetect glob can find them, caches and databases cleared;&lt;/li&gt;
&lt;li&gt;the software tagged at both points, and anything installed-by-copy re-installed, or the second wave silently runs the first wave's components;&lt;/li&gt;
&lt;li&gt;the task specifications untouched. If the first run revealed that an item was ambiguous, resist clarifying it. That is the most tempting and most invalidating edit available;&lt;/li&gt;
&lt;li&gt;the previous attempt's traces removed. Agents have been observed gaining an unfair advantage by reading the version-control history of earlier trials, so a same-task re-run must not leave the first attempt's commits lying around.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What the comparison may honestly claim
&lt;/h3&gt;

&lt;p&gt;Two measures survive the confounds:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Recurrence of named frictions&lt;/strong&gt;: per item fixed, a binary: did it come back? Most direct, least confounded, and it needs no baseline arithmetic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unplanned stops per run&lt;/strong&gt;: anything that is not a designed gate: a question that halted the run, a stuck terminal state, a missing artifact, a guard firing.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Then, as supporting evidence: operator interventions, terminal states, loop iterations, artifact completeness, gate integrity, and counts of expensive operations (environment boots, dependency re-locks, full-suite runs, human waits), which are cache-proof, unlike elapsed time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Not evidence:&lt;/strong&gt; wall-clock, token cost, raw finding counts, lines changed. And report per item; never average across items, because difficulty varies and the average hides the interesting cases.&lt;/p&gt;

&lt;p&gt;State the confounds rather than burying them: the sample is small, it is one codebase, and the operator has seen the work before, which no process controls. The claim available is &lt;em&gt;"these specific frictions were removed"&lt;/em&gt;, not &lt;em&gt;"the workflow is better"&lt;/em&gt;. Where a claim does reach beyond the workflow's own internals (that delivery got faster, say) it needs the comparison discipline below, especially the negative control.&lt;/p&gt;

&lt;h3&gt;
  
  
  Failure modes of the method itself
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Self-report bias.&lt;/strong&gt; A step is a poor witness to its own confusion. The orchestrator's outside view of a step, and the step's own view, are different data, collect both and keep them distinguishable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Only dispatched steps can report as separate voices.&lt;/strong&gt; A step invoked inline by the orchestrator has no independent context, so its "self-report" is really the orchestrator's. If per-step attribution matters, that is another reason to dispatch the step.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Telemetry must never be able to fail a run.&lt;/strong&gt; Give the recorder exactly two outcomes (recorded, or bad usage) and keep it off every failure path. A measurement layer that can break a run gets switched off within a week, and then you have neither.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Never set a target on friction counts.&lt;/strong&gt; "Fewer than ten findings next time" rewards silence.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Separate harness friction from workflow friction.&lt;/strong&gt; Findings caused by how you ran the experiment (contention between parallel runs, a shared container, an expired credential) are about your harness, not the design. File them under a distinct label or they will misdirect every fix that follows.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Why this is the highest-yield thing to build first
&lt;/h3&gt;

&lt;p&gt;It needs no corpus, so it is available immediately; it produces the failure taxonomy that a corpus later requires; and its output is self-sharpening: &lt;strong&gt;a named friction that recurs is a regression test that wrote itself.&lt;/strong&gt; Fix it, record the signature, and every later run checks for its return at no cost.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measuring whether it delivers
&lt;/h2&gt;

&lt;p&gt;The first two axes are inward-facing. Corpus evals ask whether a step's output is correct; self-reported friction asks where the process hurts. Neither can tell you whether anything was delivered sooner, or what it cost, and that is the question a sponsor asks first.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Axis&lt;/th&gt;
&lt;th&gt;Question&lt;/th&gt;
&lt;th&gt;Data needed&lt;/th&gt;
&lt;th&gt;Gamed by&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;External evals&lt;/td&gt;
&lt;td&gt;is the output correct?&lt;/td&gt;
&lt;td&gt;a labeled corpus&lt;/td&gt;
&lt;td&gt;a looser grader&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Self-reported friction&lt;/td&gt;
&lt;td&gt;where does the process hurt?&lt;/td&gt;
&lt;td&gt;nothing; it self-instruments&lt;/td&gt;
&lt;td&gt;a quieter agent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Delivery metrics&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;did it deliver, and at what cost?&lt;/td&gt;
&lt;td&gt;the tracker, the forge, runtime telemetry&lt;/td&gt;
&lt;td&gt;survivorship and confounded groups&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Most teams build exactly one of the three and then over-claim from it. They are not substitutes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Four streams, and a division of labor that is easy to get wrong
&lt;/h3&gt;

&lt;p&gt;Delivery measurement draws on four independent sources, and each owns something the others cannot supply:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Stream&lt;/th&gt;
&lt;th&gt;Owns&lt;/th&gt;
&lt;th&gt;Grain&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Runtime telemetry&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;spend and token volume, sessions, active time&lt;/td&gt;
&lt;td&gt;session × component × model&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Workflow telemetry&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;runs, outcomes, loop iterations, referee decisions, early exits, component calls&lt;/td&gt;
&lt;td&gt;run × work item&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Issue tracker&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;the work item's lifecycle: created, transitions, comments, links&lt;/td&gt;
&lt;td&gt;work item&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Code host&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;branches, commits, pull requests, reviews, CI, deployments&lt;/td&gt;
&lt;td&gt;pull request / branch&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two rules follow, and both are counter-intuitive enough to state plainly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cost only ever comes from the runtime.&lt;/strong&gt; Nothing in a workflow's own telemetry prices tokens. What the workflow stream contributes is the dimension the cost stream &lt;em&gt;lacks&lt;/em&gt;, which workflow, which work item, which outcome. Runtime cost alone has no work-item dimension at all, and that gap is the entire reason both streams exist.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Delivery outcomes only ever come from the tracker and the code host.&lt;/strong&gt; Workflow telemetry says what a run &lt;em&gt;did&lt;/em&gt;; it can never say whether delivery actually changed.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The join, and what breaks it
&lt;/h3&gt;

&lt;p&gt;The spine is the work-item key. The tracker has it natively; the workflow stream carries it; the code host yields it from a branch-name convention (with the PR body and commit messages as fallbacks). Cost joins only indirectly (session → run → work item) which is the join no single system can perform alone, and the reason instrumenting the workflow is a prerequisite for costing anything.&lt;/p&gt;

&lt;p&gt;Three consequences worth designing for up front:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A workflow with no work-item key breaks the spine.&lt;/strong&gt; Anything triggered by a repository or a pull request rather than a ticket must emit an alternative key, or its cost attaches to nothing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Some joins no system provides.&lt;/strong&gt; The wait at a human gate (the approval record carries a hash but no timestamp, as noted above); the exact link from a run to the pull request it produced (inferred from a branch name unless the run stamps its id into the PR body or a commit trailer); and the identity map between tracker account, code-host login and commit email, which is a hard prerequisite for anything per-person.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cardinality is many-to-many.&lt;/strong&gt; One item may have several runs and several pull requests. Fix the rules once (earliest start, latest merge, summed iterations) and exclude bot authors everywhere.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr761ld4iwll4wnmh3vnk.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr761ld4iwll4wnmh3vnk.jpeg" alt="Four telemetry streams joining to one row for work item LB-1234. The tracker, workflow telemetry and code host each join directly on the work item key, the last by parsing it from a branch name. Runtime carries only a session id, so it joins through a chain from session to run to work item, and only workflow telemetry knows the middle link." width="800" height="426"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 1: Cost only ever comes from the runtime, and the runtime has no work item dimension at all. The middle link of that chain exists only if the workflow records it, which is why instrumenting the workflow is a prerequisite for costing anything.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  One row per unit of work
&lt;/h3&gt;

&lt;p&gt;Reduce all of it to a single record per work item: created, started, first commit, PR opened, first review, merged, deployed, done; time in each status; reopens; review rounds; size and files touched; CI attempts; runs, outcome, iterations, gate rejections; and spend.&lt;/p&gt;

&lt;p&gt;Every metric that follows is then a subtraction or a count over that row, which is what keeps a metric catalog from turning into a pile of bespoke queries.&lt;/p&gt;

&lt;h3&gt;
  
  
  Four lenses, and one rule about reading them
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Usage&lt;/strong&gt;: is it actually being adopted: share of eligible items with a run; &lt;em&gt;repeat&lt;/em&gt; use, which separates "tried it" from "adopted it" in a way a user count hides; funnel drop-off showing &lt;em&gt;where&lt;/em&gt; runs die; the outcome mix.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Efficiency&lt;/strong&gt;: cycle time (started → done, excluding backlog), lead time, build time, time in each status, time to merge, review wait, rework rounds, discussion depth, size.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Quality&lt;/strong&gt;: first-pass CI success, reopen rate, escape rate (a defect linked back within some window), revert rate, follow-up churn, change failure rate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost&lt;/strong&gt;: spend per &lt;em&gt;delivered&lt;/em&gt; unit, spend on runs that produced nothing, waste ratio, spend per component, spend per loop iteration.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Efficiency is never reported without quality in the same view. A speed number shown alone invites precisely the trade nobody wants, and the invitation is hard to refuse once the number is on a slide.&lt;/p&gt;

&lt;h3&gt;
  
  
  The elapsed-time paradox
&lt;/h3&gt;

&lt;p&gt;The previous section says elapsed time is not evidence; this section makes cycle time a headline. Both are right, and the distinction matters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A run's&lt;/strong&gt; wall-clock is confounded by caches, warm environments and machine load. Use operation counts instead.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A work item's&lt;/strong&gt; cycle time is measured between events days apart in external systems. No cache touches it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Same word, different clocks. Never mix them in one chart.&lt;/p&gt;

&lt;h3&gt;
  
  
  Comparing assisted with unassisted
&lt;/h3&gt;

&lt;p&gt;The moment there is a number, someone will compare it to the unassisted case. The comparison is worth doing and easy to fake, so fix the discipline before the first chart:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Label every work item&lt;/strong&gt; as unassisted, &lt;em&gt;assisted-any&lt;/em&gt; (a run was started, whatever became of it), or assisted-completed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compare assisted-any against unassisted.&lt;/strong&gt; It is tempting to compare only the runs that finished, but runs are abandoned precisely on the items that turned out to be hard, so excluding them removes the workflow's worst cases and manufactures a win. Report the completed subset separately, labeled "when it completes", never as the headline. This is the single most common way these numbers lie.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compare like with like.&lt;/strong&gt; Same item type, same window, bots excluded.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Band by size and by repository, and compare within band.&lt;/strong&gt; A large change in a repository with a slow suite is not comparable to a small one elsewhere. Without banding, a difference in &lt;em&gt;which items people chose&lt;/em&gt; looks exactly like a difference the workflow made.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Summarize honestly.&lt;/strong&gt; Median and p90, never the mean; show the difference with an interval, and if it spans zero, say "noise"; print both group sizes and show nothing for thin cells.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pair people against themselves.&lt;/strong&gt; For anyone with items in both groups, compute their own median difference and check whether most people moved the same way. Differences between people (seniority, familiarity, how they scope work) are usually larger than the effect being measured.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run the same comparison on a metric the workflow cannot possibly affect.&lt;/strong&gt; The lag between merging and someone finally dragging the item to done is pure bookkeeping; no coding workflow changes it. If the assisted group looks "better" &lt;em&gt;there&lt;/em&gt; too, the groups differ for some other reason (most likely easier items being chosen) and the headline cannot be trusted. A negative control is the cheapest protection against fooling yourself, and almost nobody builds one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Show quality beside speed&lt;/strong&gt;, always.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Even with all of it, this stays observational: people chose when to use the workflow. You can describe a difference; you cannot claim a cause. A causal claim needs randomization (alternating by week, for instance) which is worth doing once a baseline exists.&lt;/p&gt;

&lt;h3&gt;
  
  
  Anti-metrics
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tempting&lt;/th&gt;
&lt;th&gt;Why not&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Run counts, invocation counts&lt;/td&gt;
&lt;td&gt;Activity, not delivery. Trivially inflated&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Token volume&lt;/td&gt;
&lt;td&gt;An input. Use spend per delivered unit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Completed-runs-only timing as the headline&lt;/td&gt;
&lt;td&gt;Survivorship; see step 2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total early exits as a failure count&lt;/td&gt;
&lt;td&gt;A gate refusing to proceed is the gate &lt;strong&gt;working&lt;/strong&gt;. Failure is a terminal state, not an exit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lines of code&lt;/td&gt;
&lt;td&gt;Rewards volume, which this tooling produces cheaply&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Any per-person KPI&lt;/td&gt;
&lt;td&gt;These are program metrics. Per-person views exist for the paired analysis, not for evaluating people&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;And &lt;strong&gt;set no targets before a baseline exists.&lt;/strong&gt; Targets set in advance are either trivially met or quietly abandoned. Measure for a full delivery cycle, publish the baseline &lt;em&gt;with its spread&lt;/em&gt;, then set targets.&lt;/p&gt;

&lt;h3&gt;
  
  
  Instrumentation: the artifacts are the source; hooks are only the trigger
&lt;/h3&gt;

&lt;p&gt;This closes a loop the design opened much earlier. Part two made artifacts the interface between phases for &lt;em&gt;composability&lt;/em&gt;. It turns out they are also the only trustworthy measurement surface: in-session hooks observe tool calls, not workflow semantics, so anything a workflow &lt;em&gt;concluded&lt;/em&gt; (an outcome, a verdict, a count) must be read from the file it already writes for its own correctness. No metric should ever depend on parsing prose.&lt;/p&gt;

&lt;p&gt;The practical shape: choose the one moment when a run is definitively over, read the artifacts there, and emit everything at once, guarded so a single run cannot emit its terminal batch twice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Two classes of signal no in-session instrumentation can reach:&lt;/strong&gt; delayed human reaction (a thread resolved, an item moved days later) and downstream effects (whether the change was reverted, whether a defect was linked to it). Those need a scheduled reader over the artifacts and the external streams. If a workflow's most important number is of that kind, the scheduled reader is &lt;em&gt;part of the measurement design&lt;/em&gt;, not a later nicety.&lt;/p&gt;

&lt;h3&gt;
  
  
  Rules for whoever adds a metric
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Increment by exactly one per observed event.&lt;/strong&gt; Never accumulate a computed quantity into a counter.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Emit numerator and denominator separately, never a ratio.&lt;/strong&gt; A pre-divided rate cannot be re-aggregated across runs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mirror the failure path wherever you handle the success path.&lt;/strong&gt; A crashed step otherwise leaves a span that never closes and a window that never shuts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Watch cardinality.&lt;/strong&gt; Anything unbounded (a file path, a finding title, a branch name) does not belong in a dimension.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Version anything you intend to trend.&lt;/strong&gt; A prompt or rule change alters what a workflow produces; without a version dimension, a trend line silently splices two different systems together. It cannot be backfilled, so it has to be there from the first emission.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Shared metrics, owned metrics, and the tier that gets over-claimed
&lt;/h3&gt;

&lt;p&gt;Most signals should be shared: one definition emitted by every workflow, sliced by workflow name. Resist the urge to fork them per workflow: a new terminal state belongs in the shared vocabulary, not in a private metric.&lt;/p&gt;

&lt;p&gt;But workflows &lt;em&gt;should&lt;/em&gt; also carry their own metrics, because the interesting quality signal is usually specific to what that workflow produces: a reviewer has precision and recall over findings; a build loop has iterations to green; a triage step has restraint. Name those distinctly so they cannot be mistaken for a shared series, and keep them scoped to the workflow that emits them.&lt;/p&gt;

&lt;p&gt;That gives three tiers, and the distinction governs how each may be read:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tier&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Program&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;One definition across every workflow. Comparable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Owned&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Meaningful only for this workflow, measured on its own runs and artifacts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Attributed&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A program metric measured on &lt;em&gt;delivery&lt;/em&gt; artifacts, restricted to work that used this workflow&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Attributed metrics are the ones that get over-claimed. They are measured on work items and pull requests, not on runs, so they would read identically if something else entirely caused the improvement. Report them as "the items that used this workflow", never as evidence about the workflow alone.&lt;/p&gt;

&lt;p&gt;One constraint that should govern every dashboard: &lt;strong&gt;cost is comparable across workflows; quality is not.&lt;/strong&gt; Spend per run ranks any two workflows sensibly. Quality metrics are defined against what a workflow produces and have no counterpart in a sibling, so cross-workflow panels should carry only the shared run-and-cost signals, and every quality panel should be scoped to one workflow.&lt;/p&gt;

&lt;h3&gt;
  
  
  Trace a handful by hand before publishing anything
&lt;/h3&gt;

&lt;p&gt;Before any aggregate goes on a dashboard, follow three to five known items end to end manually (one assisted, one unassisted, one that went wrong) and check that the record matches what actually happened. A single wrong status mapping produces confident, plausible, entirely wrong numbers, and nobody downstream will question a chart.&lt;/p&gt;

&lt;p&gt;Continuous cross-checks earn their keep for the same reason: items marked done with no linked pull request, pull requests merged whose item never completed, branches matching no item, negative durations. Each one is a broken join, and a broken join fails silently.&lt;/p&gt;




&lt;p&gt;A workflow that works on your machine, and that you can prove works, is still a private tool. Part five is the three transitions that make it something else: packaging it so others can install and depend on it, porting it to a different harness, and taking the human out of the gates that can safely lose one. Part six is where all of it still hurts.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://fullgc.github.io/designing-agentic-development-workflows-part-4/" rel="noopener noreferrer"&gt;fullgc.github.io&lt;/a&gt;, where the whole series lives.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>workflows</category>
      <category>automation</category>
    </item>
    <item>
      <title>Designing agentic development workflows: the cost of control</title>
      <dc:creator>Dani Shemesh</dc:creator>
      <pubDate>Mon, 14 Sep 2026 14:38:13 +0000</pubDate>
      <link>https://dev.to/dani_shemesh/designing-agentic-development-workflows-the-cost-of-control-1c6p</link>
      <guid>https://dev.to/dani_shemesh/designing-agentic-development-workflows-the-cost-of-control-1c6p</guid>
      <description>&lt;p&gt;Two parts in, the machinery is settled: phases in order, artifacts between them, gates that check rather than promise, a script deciding when the loop stops. What is not settled is the arithmetic around it, and the arithmetic decides whether any of the machinery matters.&lt;/p&gt;

&lt;p&gt;How large should the item handed to a run be? How many approvals can one person give before approving stops meaning anything? Which steps deserve the strongest model, and which decisions can never be recovered downstream if that choice is wrong? This part is short because the answers are short. They are also the answers most often got wrong, because each one is a place where doing the efficient thing costs you the control the workflow was built for.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Assumes only that gates and loops exist and are enforced, which is &lt;a href="https://fullgc.github.io/designing-agentic-development-workflows-part-2/" rel="noopener noreferrer"&gt;part two&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Sizing the work
&lt;/h2&gt;

&lt;p&gt;The most consequential decision about a run happens before it starts: how big is the thing you handed it?&lt;/p&gt;

&lt;p&gt;The recommendation is deliberately smaller than feels efficient: &lt;strong&gt;more items, each narrower.&lt;/strong&gt; Not because the workflow cannot handle a large one, but because everything that makes a run trustworthy degrades with size:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Control.&lt;/strong&gt; A small change is one a person can actually review. You can hold the whole diff in your head, which is the only state in which "approved" means anything.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Comprehension.&lt;/strong&gt; With a narrow item you can tell what was written and why. With a sprawling one you approve a &lt;em&gt;summary&lt;/em&gt; of what was written, which is a different and much weaker act.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gates become real.&lt;/strong&gt; A gate on a focused plan is a decision. A gate on a plan spanning nine files and three concerns is a rubber stamp, and the attention arithmetic in the next section only gets worse as items grow.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plans get concrete.&lt;/strong&gt; A narrow ask produces a plan naming files and tests. A broad ask produces a plan of &lt;em&gt;intentions&lt;/em&gt;, which is unreviewable and unfalsifiable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Failure gets cheap.&lt;/strong&gt; A run that ends stuck costs you one small item, not a week.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The cost is real and worth stating: more items means more runs, more gate touches, and more fixed overhead per unit of delivered work (part four argues for measuring exactly that). Accept it deliberately. The alternative is not less work: it is the same work with less control over it.&lt;/p&gt;

&lt;p&gt;A usable rule of thumb: &lt;strong&gt;if you cannot state the acceptance criteria in a few lines, the item is too big.&lt;/strong&gt; Split it before running anything.&lt;/p&gt;

&lt;h3&gt;
  
  
  Plan quality is bounded by input quality
&lt;/h3&gt;

&lt;p&gt;The single largest lever on what a workflow produces is not the workflow. It is the description of the work handed to it.&lt;/p&gt;

&lt;p&gt;A vague item produces a confident, vague plan, and then the gate has nothing to push back on, because there is no specific claim to disagree with. Three things make the difference:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Acceptance criteria specific enough that the plan cannot invent scope.&lt;/strong&gt; If two people would build different things from the description, the plan is a coin flip.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The context the agent cannot discover for itself&lt;/strong&gt;: why this is wanted, which constraints apply, which decisions are already settled and not up for reconsideration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pointers into the codebase&lt;/strong&gt;: the module this concerns, the convention it must follow, the thing nearby that already does something similar.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A good diagnostic: &lt;strong&gt;if you find yourself rewriting the plan at the gate, the item was underspecified.&lt;/strong&gt; The fix belongs in the description, not in the plan, otherwise the next run makes the same mistake.&lt;/p&gt;

&lt;h2&gt;
  
  
  Human gates and the economics of attention
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr5abxc5d4x9fz0ujmqj0.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr5abxc5d4x9fz0ujmqj0.jpg" alt="Oprah Winfrey pointing at her audience, giving things away, saying " width="210" height="340"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Gates are the product, not the overhead. Everything downstream faithfully implements whatever a gate approves, so an error at a gate cannot be recovered later.&lt;/p&gt;

&lt;p&gt;Which is also why gate &lt;em&gt;placement&lt;/em&gt; is not a free choice: put them where the process already stopped for a human. A gate somewhere the process never had one gets treated as ceremony and rubber-stamped; a missing gate where the process always had one is the one people notice, loudly, after something ships.&lt;/p&gt;

&lt;p&gt;The shape worth aiming for, stated as a promise to whoever runs it: two stops. Everything between them is unattended, however many retries it takes. And it never merges. That is a claim a person can hold in their head, which matters more than the exact number: they need to know when they will be needed and what the workflow will never do behind their back.&lt;/p&gt;

&lt;p&gt;Which is precisely why the &lt;em&gt;number&lt;/em&gt; of gates matters. Three gates in one run is defensible. Twenty-eight across a batch of parallel runs is not. Attention is the scarce resource, and a human asked to approve twenty-eight artifacts approves them without reading.&lt;/p&gt;

&lt;h3&gt;
  
  
  Batch by phase, not by run
&lt;/h3&gt;

&lt;p&gt;If a gate check only asks "is &lt;em&gt;this&lt;/em&gt; artifact's hash in &lt;em&gt;this&lt;/em&gt; run's approval log", then several parallel runs can present their plans together and each still records its own approval separately. A handful of sittings instead of dozens, with the enforcement unchanged.&lt;/p&gt;

&lt;h3&gt;
  
  
  A gate asks exactly one question: approve, or annotate
&lt;/h3&gt;

&lt;p&gt;Never bundle a decision into it. On one run a gate asked for approval &lt;em&gt;and&lt;/em&gt; offered a choice between two implementation routes, one of which added a runtime dependency. The reply was "approved", which answered only the first question, and the right branch was taken by luck. Discrete choices belong in an explicit question &lt;em&gt;before&lt;/em&gt; the gate, so a bare "approved" cannot be ambiguous.&lt;/p&gt;

&lt;p&gt;When batching, require an answer per item; a blanket "all approved" against four artifacts is ambiguous, and ambiguity at a gate should be treated as feedback, not consent.&lt;/p&gt;

&lt;h3&gt;
  
  
  Measure the attention, not just the elapsed time
&lt;/h3&gt;

&lt;p&gt;Batching four approvals into one sitting can quarter the attention each receives, which shows up later as extra loop iterations that &lt;em&gt;look&lt;/em&gt; like workflow friction. Record how long a human actually spent per artifact, separately from how long the machine ran.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fq2p24jccgmqfonporj8o.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fq2p24jccgmqfonporj8o.jpeg" alt="A person closely reading the first three of a very long row of documents; the remaining twenty five are already stamped approved, unread." width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 1: Attention is the scarce resource. A human asked to approve twenty eight artifacts approves them without reading.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Model and effort tiering
&lt;/h2&gt;

&lt;p&gt;Because the tier is chosen per dispatched agent, spend deliberately:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Where&lt;/th&gt;
&lt;th&gt;Tier&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Deciding whether an external claim is correct&lt;/td&gt;
&lt;td&gt;strongest model, high effort&lt;/td&gt;
&lt;td&gt;the only step that can produce the restraint outcomes, and nothing downstream can recover them&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Root-cause investigation&lt;/td&gt;
&lt;td&gt;strong model, high effort&lt;/td&gt;
&lt;td&gt;every later phase inherits its errors&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Writing the tests that define "correct"&lt;/td&gt;
&lt;td&gt;strong model&lt;/td&gt;
&lt;td&gt;those tests &lt;em&gt;are&lt;/em&gt; the specification&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Building, proving, wording replies&lt;/td&gt;
&lt;td&gt;mid tier, medium effort&lt;/td&gt;
&lt;td&gt;machinery: the decisions were already made&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fetching, parsing, classifying a payload&lt;/td&gt;
&lt;td&gt;cheapest tier&lt;/td&gt;
&lt;td&gt;near-deterministic, high volume&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Cost and trust are both configuration. The same file that picks the tier also declares the tools the step may use, and that second field is a trust boundary, not an optimization. A step that writes the run's report needs to read and write files and nothing else: no shell, no search. Denying it a shell is how you know it cannot edit code, regardless of what it decides it wants to do. Grant the narrowest set that lets the step finish, and prefer removing a tool to adding an instruction telling the step not to use it.&lt;/p&gt;

&lt;p&gt;One firm rule: &lt;strong&gt;keep a floor on effort.&lt;/strong&gt; Make a step cheaper with a smaller model, never with less thinking. A small model thinking hard beats a large model thinking barely, and the failure mode of minimal effort is confident wrongness rather than visible struggle.&lt;/p&gt;

&lt;p&gt;Note the asymmetry that justifies the whole table: the cheapest tier is also the most likely to conclude "the reviewer is right, do what they said": precisely the failure the expensive tier exists to prevent. Tiering is about where the irrecoverable decisions live, not about average difficulty.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the design buys
&lt;/h2&gt;

&lt;p&gt;That is the design: the components, the shape of a run, the principles that keep that shape honest, how work is sized, where the human stands, what each step costs to run, and how failures are classified.&lt;/p&gt;

&lt;p&gt;The largest claim was made &lt;a href="https://fullgc.github.io/designing-agentic-development-workflows-part-1/#what-all-of-this-is-for" rel="noopener noreferrer"&gt;at the very start&lt;/a&gt;: what the person invoking it stops having to do. That is the adoption argument, and it outweighs everything below. What follows is what the engineering buys.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Interruptibility.&lt;/strong&gt; A run can be stopped at any phase and either resumed from its cursor or simply read. Nothing important lives only in a transcript.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Attributable failure.&lt;/strong&gt; Guards and exit codes mean a failure names the step that caused it, instead of an agent quietly proceeding on missing input and failing three phases downstream.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auditability.&lt;/strong&gt; The approval log records what a human approved, by content hash and timestamp. The publish record says what actually reached the outside world. The report explains the run to someone who never watched it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Composability.&lt;/strong&gt; Because phases talk through files, workflows chain, skills stand alone, and any phase can be replaced by a human doing it by hand.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compounding improvement.&lt;/strong&gt; Shared skills mean a fix in one place improves every workflow that uses it, and makes it worth knowing which shared component hurts most.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bounded cost.&lt;/strong&gt; Iteration ceilings, retry caps and escalation limits mean a run cannot grind indefinitely; the failure mode is a clear stop, not a runaway bill.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reviewable output.&lt;/strong&gt; One commit per unit of work, messages that reference the originating item, and a diff confined to the files the plan named. The reviewer's first question, "why is this file in here?", is answered before they ask it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The ability to decline.&lt;/strong&gt; That is the whole difference between a system that &lt;em&gt;addresses&lt;/em&gt; feedback and one that merely &lt;em&gt;obeys&lt;/em&gt; it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every one of those is a claim, and a claim you have not checked is a hope. The rest of this document is about checking them, shipping them, and being honest about where they fail.&lt;/p&gt;







&lt;p&gt;That is the design, and the case for it. Whether any of it is true of your workflow is a different question with a different kind of answer: evidence. Part four is the three independent ways to get it, and the way each one is quietly gamed.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://fullgc.github.io/designing-agentic-development-workflows-part-3/" rel="noopener noreferrer"&gt;fullgc.github.io&lt;/a&gt;, where the whole series lives.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>workflows</category>
      <category>automation</category>
    </item>
    <item>
      <title>Designing agentic development workflows: principles for a workflow you can trust</title>
      <dc:creator>Dani Shemesh</dc:creator>
      <pubDate>Mon, 14 Sep 2026 13:25:00 +0000</pubDate>
      <link>https://dev.to/dani_shemesh/designing-agentic-development-workflows-principles-for-a-workflow-you-can-trust-lka</link>
      <guid>https://dev.to/dani_shemesh/designing-agentic-development-workflows-principles-for-a-workflow-you-can-trust-lka</guid>
      <description>&lt;p&gt;A workflow's phases and its cast are the easy half. The hard half is keeping that order honest when every step is carried out by something that would rather be agreeable than correct.&lt;/p&gt;

&lt;p&gt;What follows are the principles that hold that order in place, in two groups. The machinery comes first, then the doctrine that governs what a step does with its own judgment. Many of them exist because something went wrong in a way that produced no error.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This part assumes part one's vocabulary: orchestrator, skill, agent definition, channel adapter, deterministic primitive, and the run directory. &lt;a href="https://fullgc.github.io/designing-agentic-development-workflows-part-1/" rel="noopener noreferrer"&gt;Part one&lt;/a&gt; introduces them.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Machinery principles
&lt;/h2&gt;

&lt;p&gt;Where state lives, how one phase hands work to the next, what a gate has to be to count as one, and who is allowed to decide that a loop has finished.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. The orchestrator is thin, because only a sub-agent can carry a model
&lt;/h3&gt;

&lt;p&gt;In current agent runtimes, the model and reasoning effort serving a step can only be set when that step is dispatched as a sub-agent. A skill's own metadata does not change what serves it.&lt;/p&gt;

&lt;p&gt;That single platform fact drives the architecture. The orchestrator runs on whatever model the session happens to use, so it stays cheap and mechanical (control flow, gates, dispatch, wiring), and every act of judgment is pushed into an agent that declares its own tier. When a design wants "this decision deserves the strongest model and maximum effort", the only way to express that is to make the decision a dispatched agent.&lt;/p&gt;

&lt;p&gt;Generalized: &lt;strong&gt;find the unit your runtime lets you configure, and make that unit the boundary of judgment.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2. The run directory is the only channel
&lt;/h3&gt;

&lt;p&gt;Every run gets a durable directory, and that directory is the only channel across a dispatch boundary. Inline steps share the orchestrator's context and could pass things in memory; a dispatched sub-agent cannot. Design for the boundary and the inline case comes free.&lt;/p&gt;

&lt;p&gt;The reason is mechanical: environment variables and the working directory do not survive a call into a sub-agent, so the run directory's path is passed as a literal argument into every dispatch, and anything a later phase needs must be on disk. Run state lives in one small JSON file, written atomically (write a temp file, then rename) so a crash cannot leave a half-written record.&lt;/p&gt;

&lt;p&gt;That pays off twice. A run becomes inspectable by anyone, including a human with no access to the transcript. And it becomes resumable: if the state file carries a phase cursor, re-entering the workflow continues from where it stopped instead of restarting over a human's earlier approvals.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Artifacts are the interface between phases
&lt;/h3&gt;

&lt;p&gt;Each phase produces a named file and the next phase consumes it: the request, the exploration, the plan, the tests, the implementation notes, the validation record, the report.&lt;/p&gt;

&lt;p&gt;One small detail worth stealing: when a phase transforms a file, give the output a different name from the input. A step that writes its result over its own input will, on a second run, read its own output, and any deduplication or comparison it was doing silently stops working.&lt;/p&gt;

&lt;p&gt;Because the interface is a file, a phase can be re-run, inspected, replaced by a human doing it manually, or swapped for a different implementation without touching its neighbours.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Guard the seams
&lt;/h3&gt;

&lt;p&gt;Before a phase runs, assert that the artifacts it depends on exist and are non-empty. A tiny script; exit zero or one; nothing else.&lt;/p&gt;

&lt;p&gt;This exists because the characteristic failure of a language model at a seam is not a crash. It is producing the answer &lt;em&gt;in the conversation&lt;/em&gt; and never writing the file (or writing it somewhere else) after which the next phase proceeds on absent or stale input and fails somewhere unrelated. A guard turns a silent, mis-attributed failure into a loud, correctly-attributed stop.&lt;/p&gt;

&lt;p&gt;The step most worth guarding is the one that writes the final report, because that is the step most likely to hand you prose instead of a file.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsj4nds90lz03ntjsng2z.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsj4nds90lz03ntjsng2z.jpeg" alt="Two panels: without a guard a missing file lets the run continue and fail past the last phase; with a guard it stops at the seam where the file is missing." width="800" height="437"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 1: A guard does not prevent the failure. It moves the failure to where the cause is.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  5. A gate is a check, not a promise
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ffullgc.github.io%2Fpublic%2Fagentic-workflow-meme-this-is-fine.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ffullgc.github.io%2Fpublic%2Fagentic-workflow-meme-this-is-fine.jpg" alt="The &amp;quot;this is fine&amp;quot; dog sitting in a burning room. Label: &amp;quot;Gate 3: approved.&amp;quot; The dog says &amp;quot;This is fine.&amp;quot; The flames are labelled &amp;quot;10 unanswered review threads.&amp;quot;" width="640" height="311"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A human gate presents an artifact, takes annotations, and records approval as the artifact's content hash in an append-only log. Before acting on that approval, the workflow &lt;em&gt;re-computes&lt;/em&gt; the hash and refuses if it is absent.&lt;/p&gt;

&lt;p&gt;Keying on content rather than on a flag buys two properties. A revised artifact has a new hash, so a &lt;strong&gt;revision requires fresh approval&lt;/strong&gt; instead of inheriting the old one. And an approval cannot be manufactured by an agent that believes it was approved.&lt;/p&gt;

&lt;p&gt;The workflow that fixes review comments on a pull request had three gates, of which only the first was actually checked, the others in prose. On a real run the third gate, the one guarding everything that reached the outside world, was skipped: the change was pushed to a pull request under review and not one of its ten review threads received a reply. Reviewers were left with silently-changed code and no explanation. The fix was not more prose; it was one shared check called at all three gates, plus the same check &lt;em&gt;inside&lt;/em&gt; the two components that publish, so a mis-sequenced publish fails instead of succeeding quietly.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftdoeo9w5euzkfnwf8epd.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftdoeo9w5euzkfnwf8epd.jpeg" alt="An artifact is hashed and the hash recorded in an append only approval log; a revised artifact hashes to a different value, is not found in the log, and must be approved again." width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 2: Keying approval to content is what makes a gate a check rather than a promise: a revision cannot inherit an approval, and an agent cannot manufacture one.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Judgment runs in fresh context
&lt;/h3&gt;

&lt;p&gt;Any step that judges work done earlier in the same run is dispatched fresh, so it never inherits the context of the step that produced what it is judging. In the workflow that answers pull request review comments, that means the agent deciding whether a comment is correct has not seen the agent that wrote the code under review. The same goes for the agent that proves the tests, and the one that writes the report.&lt;/p&gt;

&lt;p&gt;This is a bias control, not a token optimization. An agent that just spent twenty minutes writing a function is the worst available judge of whether that function is wrong.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. The loop is refereed by a script, not by the agent inside it
&lt;/h3&gt;

&lt;p&gt;The build phase is a two-tier loop: an inner loop of implement→validate, and an outer loop back to re-planning. After each pass, validation writes a &lt;em&gt;structured&lt;/em&gt; verdict (status, failure class, tests run, how many of them were the new ones, a reason, the evidence) and a deterministic referee reads it and returns exactly one of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CONTINUE           run the next iteration
RETRY_MECHANICAL   run the same iteration again; do not re-plan
ESCALATE           go back to planning, re-approve the new plan, resume
STOP_OK            proceed to the next phase
STOP_STUCK         hand back to the human
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The referee is a script because a loop that decides its own termination condition does not terminate reliably. It also enforces the guards an agent inside the loop would rationalize away:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;False green&lt;/strong&gt;: no new tests actually ran, so nothing was demonstrated. Escalate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Same failure signature twice&lt;/strong&gt;: the approach is wrong, not the attempt. Escalate rather than retry.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An iteration ceiling, and a separate cap on identical retries&lt;/strong&gt;: bounded work, always.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The failure class in that verdict is what lets the referee pick correctly instead of retrying blindly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tactical&lt;/strong&gt;: the implementation is wrong. Retry with a change.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mechanical&lt;/strong&gt;: the same attempt failed for a transient reason. Retry identically, capped.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Strategic&lt;/strong&gt;: the plan is wrong. Escalate to re-planning, and re-approve the new plan.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Environmental&lt;/strong&gt;: a credential expired, a container collided, a dependency service is down. This is not a test failure. Stop and surface it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last category is the one everybody omits and everybody needs. On one run an expired credential surfaced as a dead test command; validation classified it as a failing test, and the loop spent two iterations "fixing" code that was already correct, grinding against a wall only a human could move.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8m2cojc47z5b3akmizc5.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8m2cojc47z5b3akmizc5.jpeg" alt="An implement and validate cycle whose continuation is decided by a script drawn outside the loop, fanning out to five verdicts including escalation to planning and a stop to a human." width="800" height="437"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 3: A loop that decides its own termination condition does not terminate reliably. The failure class picks the arrow: tactical continues, mechanical retries, strategic escalates, environmental stops.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  8. Tests come first, and then the tests are proved
&lt;/h3&gt;

&lt;p&gt;Every plan names the failing tests to write. They are written first and confirmed failing before any implementation exists. That part is ordinary test-driven development.&lt;/p&gt;

&lt;p&gt;The part that is less ordinary: after the loop goes green, a prover reverts only the production files, re-runs the new tests expecting them to fail, restores the change, and records a verdict: proven, or vacuous. A test that still passes with the change reverted did not test the change.&lt;/p&gt;

&lt;p&gt;Without this step, "the tests pass" and "the tests test something" are indistinguishable, and the second is the only one anybody cares about. It is also the step that catches the most human-looking failure mode there is: a test written to satisfy a process rather than to catch a defect.&lt;/p&gt;

&lt;p&gt;(The refactor workflow is the instructive exception: it writes no new tests, so its equivalent guarantee is that the &lt;em&gt;existing&lt;/em&gt; suite passes unchanged, and any diff in test behavior is a defect.)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft0badfg7jbugkqdmq3ai.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft0badfg7jbugkqdmq3ai.jpeg" alt="Four steps: green, revert only the production code, re run the new tests, restore, with a failure meaning proven and a pass meaning vacuous." width="800" height="374"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 4: A test that still passes with the change reverted did not test the change.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  9. Only the adapters know the outside world
&lt;/h3&gt;

&lt;p&gt;The components that reason read and write files and never touch the network. The adapters are the only platform-aware code. The orchestrator wires them together.&lt;/p&gt;

&lt;p&gt;That split is what lets the same workflow run driven by a live pull request or by a plain artifact on a branch with no pull request at all, without the second being a degraded path. The deciding and building phases are identical in both, because neither can tell the difference.&lt;/p&gt;

&lt;h3&gt;
  
  
  10. Terminal states, and a report on every exit
&lt;/h3&gt;

&lt;p&gt;Enumerate the ways a run can end (done; done-but-unproven; nothing-to-do; bad-input; stuck; not-approved) and write a report on every one, including the boring ones. A request whose work turns out to be already complete is a &lt;em&gt;finished&lt;/em&gt; run, not an error, and it still deserves a one-line record.&lt;/p&gt;

&lt;p&gt;"Done but unproven" earns its place as a distinct state: a run that shipped and explained itself but could not demonstrate that its tests catch the defect is neither done nor stuck. Naming that state is what stops it being rounded up to "done", which is exactly how a vacuous test ships looking verified.&lt;/p&gt;

&lt;p&gt;Watch for two things here. A promise to "write a report on every exit path" only ever covers the &lt;em&gt;enumerated&lt;/em&gt; exits; a run abandoned mid-flight leaves nothing unless something else writes a record. And if the runtime treats certain filenames specially, a sub-agent may be unable to write the very file it exists to produce: check that the reporting step can actually write to the name you chose.&lt;/p&gt;

&lt;h2&gt;
  
  
  Doctrine principles
&lt;/h2&gt;

&lt;p&gt;What a step should refuse to do, how it should treat text that arrives from outside, and how it should record a decision so the decision can be audited later.&lt;/p&gt;

&lt;h3&gt;
  
  
  11. Restraint is a first-class outcome
&lt;/h3&gt;

&lt;p&gt;For any workflow that &lt;em&gt;receives&lt;/em&gt; input from elsewhere (a review, an alert, a suggestion) the valuable behavior is often to not act. Responding to review feedback is easy to do badly in four specific ways:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;silently ignoring an item;&lt;/li&gt;
&lt;li&gt;satisfying the letter of it while missing the point;&lt;/li&gt;
&lt;li&gt;obediently implementing an item that was wrong, breaking working code;&lt;/li&gt;
&lt;li&gt;editing at a location the reviewer pointed to several commits ago, which now means something else.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So the deciding phase classifies each item and chooses an action, and several of the available actions must produce zero diff: a question gets answered, an incorrect claim gets a reasoned argument, an out-of-scope request becomes a ticket, a stale item is recognized as stale. Make this measurable: an evaluation that checks "did the run leave the tree untouched where it should have" against the real diff, and in which a run that changes everything fails.&lt;/p&gt;

&lt;p&gt;Two failure modes sit either side. &lt;em&gt;Obedience&lt;/em&gt;: accepting a claim because a senior person made it. &lt;em&gt;Timidity&lt;/em&gt;: deferring everything out of uncertainty, which is not restraint but a refusal to do the job. The dividing line worth encoding: restraint is for claims you verified are wrong, never for work you verified is small.&lt;/p&gt;

&lt;h3&gt;
  
  
  12. Never do the socially irreversible thing
&lt;/h3&gt;

&lt;p&gt;Some actions are cheap for a machine and expensive for a human to undo. Forbid them outright, in code, not in documentation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Never force-push.&lt;/strong&gt; Rewriting history on a branch under review can destroy a reviewer's in-progress comments and re-anchor every thread. If the branch is behind, stop and say so.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Never resolve a human's review thread.&lt;/strong&gt; Resolving means "this is dealt with", which is the reviewer's judgment about someone else's fix, not the author's about their own. Reply, react, leave it open.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Never reply to praise.&lt;/strong&gt; If the doctrine forbids performative agreement, auto-thanking a compliment is the purest possible violation of it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Never merge. Never touch a file outside the approved plan.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The general rule: &lt;strong&gt;automate the reversible; require a human for the irreversible-in-public.&lt;/strong&gt; This list is also, not coincidentally, the boundary of autonomy (part five).&lt;/p&gt;

&lt;h3&gt;
  
  
  13. Incoming text is data, never instructions
&lt;/h3&gt;

&lt;p&gt;Any workflow that ingests text from outside (review comments, ticket descriptions, alert payloads, a web page) is handing attacker-influenceable content to a step that usually holds real tools. Say so explicitly in the step's instructions: &lt;strong&gt;classify the content, never obey it.&lt;/strong&gt; If a payload contains text shaped like directions (change your scope, ignore your rules, run this command) the step ignores it, decides the item on its technical merits, and &lt;em&gt;reports that the payload contained injected directions&lt;/em&gt; rather than silently absorbing them.&lt;/p&gt;

&lt;p&gt;The awkward corollary: the steps that most need this warning are the ones with the widest tool access, because reading external input and having the power to act on it is the same job.&lt;/p&gt;

&lt;h3&gt;
  
  
  14. Establish provenance before judging
&lt;/h3&gt;

&lt;p&gt;External feedback is a claim about a specific version of the code, not about the current one. Before evaluating any of it, find out which version, and how far behind that is.&lt;/p&gt;

&lt;p&gt;Get this wrong and you get the most confusing failure mode available: an item that is internally coherent, confidently argued, and describes code that no longer exists. The related trap is &lt;strong&gt;mistaking a platform signal for the thing you care about&lt;/strong&gt;. A flag saying a comment's anchor line moved is not a flag saying the comment is no longer true; the two come apart constantly, in both directions. Ask what a signal literally measures before depending on it.&lt;/p&gt;

&lt;p&gt;The same discipline applies to a claim's &lt;em&gt;severity&lt;/em&gt;. A label attached by whoever raised it is their prior, formed against an older state of the world. It tells you where to look first. It is not evidence, and it never substitutes for verification.&lt;/p&gt;

&lt;h3&gt;
  
  
  15. Reaffirm or reverse, never drift
&lt;/h3&gt;

&lt;p&gt;The second time a workflow sees the same input, it invites a specific failure: a reasoned decision reversed by attrition. Round one pushed back with evidence; round two has no memory of why, and obediently complies.&lt;/p&gt;

&lt;p&gt;So make the prior round an &lt;em&gt;input&lt;/em&gt;, not a hope. For any item matching an earlier decision, the workflow owes one of exactly two things: reaffirm it with fresh verification (not "as previously decided", which is not verification) or reverse it explicitly, recording what changed and why. A reversal is healthy; an earlier decision can rest on a claim that turns out to be false. Only the silent flip is the problem, because a third round then sees a decision with no trace that it was ever contested, and the thing starts to oscillate.&lt;/p&gt;

&lt;h3&gt;
  
  
  16. Record how you decided, not only what you decided
&lt;/h3&gt;

&lt;p&gt;These are not the same act:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;read the migration and reasoned that it looks fine&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;executed the migration against a scratch database and counted the statements it emitted&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A schema that captures only a conclusion and a confidence number flattens both into the same row. For a judgment step, the method is the product: a step's pushback only deserves to outweigh a senior human's opinion because it was &lt;em&gt;executed&lt;/em&gt; rather than reasoned, and that is precisely the fact the human at the gate most needs to see. Record it as a field (executed, read, or searched) with a detail line naming what was actually done. It also makes a lazy run detectable: an "executed" claim with no detail is a smell.&lt;/p&gt;

&lt;p&gt;Related, and easy to get wrong: &lt;strong&gt;a label must not lie at the gate.&lt;/strong&gt; A human scanning a summary counts categories, so if "the tests are missing" is filed under the same label as "the code is broken", the artifact actively misinforms at the moment it is supposed to inform. Give the distinct thing its own category, and require a claim of runtime harm to name a reachable path from a real entry point, otherwise it is latent at most, and must be labeled that way.&lt;/p&gt;

&lt;h3&gt;
  
  
  17. Report what contradicts the brief you were given
&lt;/h3&gt;

&lt;p&gt;A dispatched step's return contract should lead with whatever changes what its caller believes, not with a summary of what it did.&lt;/p&gt;

&lt;p&gt;If a brief asserted that an item was a real defect and the step discovered the function does not exist: that is the headline, and it must be volunteered rather than buried under counts. Obeyed literally, a return contract that says "report the totals per category" produces exactly that burial.&lt;/p&gt;

&lt;p&gt;Two consequences for whoever writes the brief:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Do not pre-classify in the brief.&lt;/strong&gt; "This is a genuine production bug, not a test gap" anchors the one step whose entire purpose is to determine that. Ask for attention, never for a verdict.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Environment facts in a brief are stale by the time they are read.&lt;/strong&gt; "The credentials are expired, skip that step" may have been true at dispatch and false a minute later. A step should re-probe any environmental assertion at the point of use, and report what it &lt;em&gt;observed&lt;/em&gt; rather than what it was told to expect.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  18. Encode the standards you want, and then enforce them
&lt;/h3&gt;

&lt;p&gt;A workflow is the highest-leverage place to put "how code is written here", because it applies on every run instead of depending on who happens to be driving. That is one of the main reasons to build one at all, but it only counts if it is implemented rather than assumed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the planning step must actually read the repository's own conventions (its instructions file, its layering rules, its protected files) and name the relevant ones in the plan;&lt;/li&gt;
&lt;li&gt;validation must run the real gates: formatter, linter, type checker, tests;&lt;/li&gt;
&lt;li&gt;if there is a review step, one lane of it should be about standards specifically;&lt;/li&gt;
&lt;li&gt;and state the negative conventions explicitly: what not to touch, what not to introduce, which files are extend-only. Those are the ones a model will cheerfully violate, because nothing in the code says "don't".&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A convention nobody checks is a wish. This is the same rule as everywhere else here: enforcement exists only where the check exists.&lt;/p&gt;

&lt;p&gt;Done properly, this is the difference between "the agent produced something that works" and "the agent produced something that looks like ours", and the second is what makes the output reviewable by the people who own the codebase.&lt;/p&gt;

&lt;h3&gt;
  
  
  19. The workflow knows the procedure; the repository knows itself
&lt;/h3&gt;

&lt;p&gt;A workflow should be writable once and runnable against services in different languages and on different platforms. Nothing about "explore, plan, gate, red tests, implement, validate, prove, report" is language-specific, so nothing language-specific should be written into it.&lt;/p&gt;

&lt;p&gt;Which means the concrete facts about a service do not belong in the workflow at all:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;how dependencies are installed;&lt;/li&gt;
&lt;li&gt;how the full suite runs, and how a single test runs;&lt;/li&gt;
&lt;li&gt;how the formatter, linter and type checker are invoked;&lt;/li&gt;
&lt;li&gt;how the service is started locally, and how migrations are applied;&lt;/li&gt;
&lt;li&gt;the repository's own rules: layering, protected or extend-only files, naming, what not to introduce.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of that belongs in the repository: in its instructions file (&lt;code&gt;AGENTS.md&lt;/code&gt;, by the convention in part five) or in the files that file references. The workflow names the intent; the repository supplies the &lt;strong&gt;command&lt;/strong&gt;. This is the same invariant as "skills name actions, not tools", one level up: &lt;em&gt;name intents, not commands.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;So a validation step says "run this repository's test command for the scoped tests" and "run the gates this repository declares", not &lt;code&gt;pytest -k&lt;/code&gt;, &lt;code&gt;npm test&lt;/code&gt;, &lt;code&gt;go test ./...&lt;/code&gt;, or a particular package manager's install line. One useful indirection: if each repository exposes a single well-known entry point (a task runner, a &lt;code&gt;Makefile&lt;/code&gt;, a &lt;code&gt;scripts/&lt;/code&gt; directory) then the workflow's "run the tests" is one stable command regardless of the language underneath, and the per-repository answer lives in one file instead of scattered through skill bodies.&lt;/p&gt;

&lt;p&gt;The failure this prevents is not a crash, which is the recurring theme here. A hardcoded command works exactly as long as every service resembles the one the workflow was built in; the first service in another language does not fail loudly: the wrong command exits non-zero or prints no tests, and the agent reasons confidently over a signal that means nothing. Guarding the seam catches a missing file, not a misinterpreted one.&lt;/p&gt;

&lt;p&gt;Run two cheap tests. Grep your own skills for language- and toolchain-specific tokens: test runners, package managers, build tools, framework entry points; every hit outside a per-repository file is a portability bug. Then run the workflow against a service in another language: the failures are precisely the assumptions you never knew you had written down.&lt;/p&gt;

&lt;p&gt;The cost is honest and worth stating: this moves work onto the repositories, and a workflow is only as portable as the weakest instructions file it meets. A &lt;em&gt;wrong&lt;/em&gt; answer there is worse than a missing one, because the agent will follow it. So apply the same discipline as a harness port (part five): when the repository does not declare something the workflow needs, &lt;strong&gt;stop and say which fact is missing&lt;/strong&gt;, never guess a command from the shape of the directory tree.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6ugzgn02elklejpsiulb.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6ugzgn02elklejpsiulb.jpeg" alt="A table: one workflow column stating four intents, and three repository columns in Python, Node and Go answering those same intents with their own concrete commands." width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 5: The workflow names the intent; the repository supplies the command. The left column is identical for every service, which is the whole point.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;These rules describe how a run should behave. They say nothing about how much work to hand it, how many approvals a person can meaningfully give, or which steps deserve the expensive model. That arithmetic is part three.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://fullgc.github.io/designing-agentic-development-workflows-part-2/" rel="noopener noreferrer"&gt;fullgc.github.io&lt;/a&gt;, where the whole series lives.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>workflows</category>
      <category>automation</category>
    </item>
    <item>
      <title>Designing agentic development workflows: what a workflow is, and what it is made of</title>
      <dc:creator>Dani Shemesh</dc:creator>
      <pubDate>Mon, 14 Sep 2026 12:45:42 +0000</pubDate>
      <link>https://dev.to/dani_shemesh/designing-agentic-development-workflows-what-a-workflow-is-and-what-it-is-made-of-j20</link>
      <guid>https://dev.to/dani_shemesh/designing-agentic-development-workflows-what-a-workflow-is-and-what-it-is-made-of-j20</guid>
      <description>&lt;p&gt;&lt;strong&gt;What the series is for&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Give a team an AI coding tool and you have not given them a way of working. Everyone gets the same agent and uses it differently: one engineer explores the codebase before planning, another goes straight to code, a third writes the tests afterwards if at all. The same engineer does it differently on a different day. The phases live in people's heads and get reconstructed from scratch every time, and nothing in the tool knows that your team reviews a plan before implementation, or what this repository's conventions are, or which mistake the last change of this shape made.&lt;/p&gt;

&lt;p&gt;The attention goes wrong in both directions, too. People sit and watch a model reason through a decision they were never going to argue with, then look away at the one point where their judgment was the only thing that mattered, and find out afterwards that it took a route nobody would have approved.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ffullgc.github.io%2Fpublic%2Fagentic-workflow-meme-10x.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ffullgc.github.io%2Fpublic%2Fagentic-workflow-meme-10x.jpg" alt="Anakin and Padme meme. Anakin: &amp;quot;We gave every engineer the best AI coding agent.&amp;quot; Padme, smiling: &amp;quot;So we're 10x now, at higher quality, without going bankrupt, right?&amp;quot; Anakin stares back in silence. Padme, no longer smiling: &amp;quot;...right?&amp;quot;" width="640" height="635"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Getting a model to write code is the easy part. The part that needs designing is getting it to do the same thing twice, to stop where you want it to stop, and to leave behind something you can actually review.&lt;/p&gt;

&lt;p&gt;This series is about how to design that: a family of agentic development workflows, what they are made of, why the pieces are shaped the way they are, what the shape buys, how you know any of it works, how you ship it to other people, and where it still hurts. It comes out of workflows we have designed, built and run on real work. Nothing here is about a particular product or codebase; the claims are about the pattern.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How the series is organized&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Part&lt;/th&gt;
&lt;th&gt;Question it answers&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;What a workflow is, and what it is made of&lt;/td&gt;
&lt;td&gt;what the thing is, and what a single run looks like&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;2&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://fullgc.github.io/designing-agentic-development-workflows-part-2/" rel="noopener noreferrer"&gt;The principles&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;what keeps the shape honest: the machinery, and the doctrine&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;3&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://fullgc.github.io/designing-agentic-development-workflows-part-3/" rel="noopener noreferrer"&gt;The cost of control&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;how big an item, how many approvals, which model, and what the whole apparatus buys&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;4&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://fullgc.github.io/designing-agentic-development-workflows-part-4/" rel="noopener noreferrer"&gt;Is it working, and is it worth it&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;is the output correct · where does the process hurt · did it deliver&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;5&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://fullgc.github.io/designing-agentic-development-workflows-part-5/" rel="noopener noreferrer"&gt;Shipping it, porting it, letting it run&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;packaging, harness portability, and the road to unattended runs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;6&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://fullgc.github.io/designing-agentic-development-workflows-part-6/" rel="noopener noreferrer"&gt;Where it hurts, and what to build first&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;the limitations, and the short list&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  What we mean by a workflow
&lt;/h2&gt;

&lt;p&gt;In software development, "workflow" is a heavily overloaded term. Throw "agentic" in front of it and get two opposite things, so it's worth defining exactly what we mean before building anything around the concept.&lt;/p&gt;

&lt;p&gt;The distinction that earns its keep is &lt;a href="https://www.anthropic.com/engineering/building-effective-agents" rel="noopener noreferrer"&gt;Anthropic's&lt;/a&gt;: a &lt;strong&gt;workflow&lt;/strong&gt; is a system where models and tools are orchestrated through predefined code paths, while an &lt;strong&gt;agent&lt;/strong&gt; is a system where the model dynamically directs its own process and tool use. Workflows buy predictability on work you can decompose in advance. Agents buy flexibility on work you cannot.&lt;/p&gt;

&lt;p&gt;This series is about workflows in that sense, applied to software development. The part worth noticing is that they are not the opposite of agents. They contain them.&lt;/p&gt;

&lt;p&gt;An agentic development workflow is a named, phased procedure that takes a unit of work from a request to a reviewable result, in which three parties each do what they are good at:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The procedure owns the path.&lt;/strong&gt; Phases and their order are decided before the run, not chosen by a model halfway through it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agents own the judgment inside each phase.&lt;/strong&gt; What to explore, what the plan should be, whether a review comment is correct.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A human owns specific decisions&lt;/strong&gt;, named in advance, that the workflow cannot make for itself.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of the three is sufficient alone. A path with no judgment in it is a script, and judgment with no path is a prompt.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fk9aujmn8omxwqoijlccm.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fk9aujmn8omxwqoijlccm.jpeg" alt="Five phases in a row, EXPLORE, PLAN, BUILD, PROVE, PUBLISH, each writing a file, with human gates between PLAN and BUILD and between PROVE and PUBLISH." width="800" height="437"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 1: A fixed order, a file out of every phase, and a person required at exactly two points.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The interesting claim is not that a model can write code. It is that a procedure makes a model's work inspectable and interruptible at points you pick in advance, and that something other than the model's own discipline enforces those points. How you build such a procedure is a separate question with several answers, and the one used throughout this series is described below.&lt;/p&gt;

&lt;p&gt;That combination is what makes the following six properties reliable rather than aspirational. You can ask a bare prompt for most of them, and you will often get them:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Guarantee&lt;/th&gt;
&lt;th&gt;Mechanism&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;A fixed execution path&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;phases in a declared order, not whatever the model improvises&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Scripts over inference&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;anything that can be decided deterministically is, and by a script&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Auditable state&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;every phase writes a file; the whole run is readable afterwards&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Absolute human authority&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;gates the workflow cannot approve for itself&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Isolation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;the work happens somewhere that isn't your working copy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Right-sized effort&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;each step runs at a deliberately chosen model and effort&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two of the six are different in kind, not just degree. Absolute human authority cannot come from an instruction, because the thing you would be instructing is the same thing that would have to enforce it. And right-sized effort is decided by how a step is dispatched, so a step cannot ask for it on its own behalf. The other four you can get out of a well-written prompt on a good day. What the workflow adds is getting them on every run, including the bad days.&lt;/p&gt;

&lt;p&gt;Those six run through everything below. Every later section is really an argument about how to keep one of them true under pressure.&lt;/p&gt;

&lt;h3&gt;
  
  
  The phases come from your development process, not from the tool
&lt;/h3&gt;

&lt;p&gt;A development workflow maps a development process the team already follows. Something is requested; someone works out what it means; they decide an approach; somebody agrees to it; it gets built; it gets verified; it goes out for review; the review gets answered; it merges. The workflow does not invent that sequence, it encodes it.&lt;/p&gt;

&lt;p&gt;Three things follow, and they remove most of the guesswork from designing one:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;You transcribe phases, you don't design them.&lt;/strong&gt; If your process investigates before planning, so does the workflow. The order is not a modeling choice.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gates go where the process already had a human checkpoint.&lt;/strong&gt; A plan someone signs off, a review someone performs. Don't invent checkpoints your process doesn't have, and don't quietly drop the ones it does.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The artifacts are the ones the process already produces&lt;/strong&gt;: a plan, a list of tests, a description of the change, a review. That is why people who never ran the workflow can still review its output. It looks like what they already read.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Two more things are worth stating plainly.&lt;/p&gt;

&lt;p&gt;If your process is implicit, encoding it forces you to write it down. That is often the most valuable side effect of the whole exercise, and usually the hardest part, because the disagreements about how work &lt;em&gt;should&lt;/em&gt; flow only surface when someone tries to make them executable.&lt;/p&gt;

&lt;p&gt;And don't invent a process for the agent. A workflow that follows a sequence nobody actually uses produces output nobody trusts, and its gates land where nobody wants to look. If a phase exists only because the workflow needed a phase there, delete it.&lt;/p&gt;

&lt;p&gt;This is also why a &lt;em&gt;family&lt;/em&gt; of workflows shares a skeleton and diverges in specific places: the underlying process is shared, and the differences are real differences in how the work is done.&lt;/p&gt;

&lt;h3&gt;
  
  
  One approach among several
&lt;/h3&gt;

&lt;p&gt;This post describes one way to build an agentic development workflow: an orchestrator skill that drives other skills, with the agent runtime as the execution engine and prose as the control flow. It is not the only shape, and the alternatives are not strawmen:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Strength&lt;/th&gt;
&lt;th&gt;Cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Orchestrator skill&lt;/strong&gt; (this post)&lt;/td&gt;
&lt;td&gt;legible to both the people who own it and the model running it; no build step; any step runnable by hand&lt;/td&gt;
&lt;td&gt;prose is a weak way to express control flow&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Code-first orchestration&lt;/strong&gt;: the flow is a program that calls the model&lt;/td&gt;
&lt;td&gt;precise, strongly typed at the seams, testable with ordinary tooling&lt;/td&gt;
&lt;td&gt;heavier to change; the logic is opaque to the model itself&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Graph / state-machine frameworks&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;excellent when the flow genuinely &lt;em&gt;is&lt;/em&gt; a graph&lt;/td&gt;
&lt;td&gt;overhead when it is a straight line with a few gates&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;One instructions file, no phases&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;the cheapest thing that works, and it often does, for small changes&lt;/td&gt;
&lt;td&gt;no gates, no artifacts, nothing to inspect afterwards&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Per-task commands or prompt templates&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;trivial to write and understand&lt;/td&gt;
&lt;td&gt;fine until a task needs several steps to be trustworthy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Event-driven bots&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;no human has to start anything&lt;/td&gt;
&lt;td&gt;a &lt;em&gt;trigger&lt;/em&gt;, not a procedure; it still needs one of the above to run&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Autonomous multi-agent crews&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;maximum flexibility&lt;/td&gt;
&lt;td&gt;minimum auditability, and very hard to gate&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The case for prose-orchestrated skills is maintainability: the people who own the process can edit them, not only the people who can modify a program. The price is that prose expresses control flow poorly, which is exactly why every part that has to be deterministic (the loop referee, the seam guards, the gate checks) is a script and not an instruction.&lt;/p&gt;

&lt;p&gt;Pick by what your team can actually maintain. A precise flow nobody edits is worse than a legible one they do.&lt;/p&gt;

&lt;h3&gt;
  
  
  Local-development first
&lt;/h3&gt;

&lt;p&gt;These workflows are local-first by design. They run on a developer's machine, against their checkout (or an isolated working copy beside it), with the developer present at three or four decision points. That is the primary mode, not a stepping stone to tolerate until the infrastructure arrives.&lt;/p&gt;

&lt;p&gt;Four reasons to be deliberate about it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The human is the highest-value component at a handful of specific moments&lt;/strong&gt;: approving a plan, judging whether a review comment is correct, deciding what gets published. In between, almost worthless. Running locally makes those moments cheap to insert.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Failures are visible and cheap.&lt;/strong&gt; When something goes wrong the developer is right there, the artifacts are on their disk, and they can open them in their normal editor.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nothing needs to be built first.&lt;/strong&gt; No queue, no runner, no service account. A workflow is useful on the day it is written.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It is how you earn the right to run unattended.&lt;/strong&gt; You learn where a workflow breaks while somebody is watching. Every limitation in part six was found with a human sitting in front of a run.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The end game is autonomy, for some of them
&lt;/h3&gt;

&lt;p&gt;For some workflows (implementing a feature is the clearest case) the destination is unattended operation: a ticket gets assigned to the agent and moved into a queued state, and some time later a human finds a pull request that has already been reviewed and revised once.&lt;/p&gt;

&lt;p&gt;That is a real design target, not a fantasy, and it changes what the design in between has to look like. Part five covers the trajectory: which gates become policies, which stay human forever, and what has to be true before you let a run proceed with nobody watching.&lt;/p&gt;




&lt;h2&gt;
  
  
  A family of workflows, one skeleton
&lt;/h2&gt;

&lt;p&gt;Build workflows as a family rather than one at a time, because most of what they do is the same and the differences are informative. They share a skeleton for the reason given earlier (the development process underneath them is shared) and they diverge exactly where the real processes diverge. Six concrete examples, all real shapes:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Workflow&lt;/th&gt;
&lt;th&gt;Trigger&lt;/th&gt;
&lt;th&gt;What is distinctive&lt;/th&gt;
&lt;th&gt;Ends at&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Implement a feature&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;a ticket&lt;/td&gt;
&lt;td&gt;explore the codebase, plan, write failing tests, build loop&lt;/td&gt;
&lt;td&gt;a reviewed branch, then a pull request&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Fix a bug&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;a ticket, or an alert-filed report&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;classify&lt;/strong&gt; first (is it even a bug?), &lt;strong&gt;investigate root cause&lt;/strong&gt;, then plan/test/build, then &lt;strong&gt;prove&lt;/strong&gt; the regression test&lt;/td&gt;
&lt;td&gt;a pull request plus a comment back on the ticket&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Refactor code&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;a named target&lt;/td&gt;
&lt;td&gt;plan for &lt;strong&gt;behavior preservation&lt;/strong&gt;; the &lt;em&gt;existing&lt;/em&gt; suite is the oracle, so no new tests are written&lt;/td&gt;
&lt;td&gt;a pull request that changes no behavior&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Review a change&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;a pull request, a commit range, or a working copy&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;no build loop at all&lt;/strong&gt;: package the diff, fan out several reviewer lanes in parallel, synthesize, then verify each finding before publishing&lt;/td&gt;
&lt;td&gt;structured findings + a posted review&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Fix review comments&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;a pull request's unresolved threads&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;triage&lt;/strong&gt; (decide which comments deserve acting on), then plan/build, then reply in each thread&lt;/td&gt;
&lt;td&gt;commits plus a reply on every thread&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Generate service docs&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;a repository&lt;/td&gt;
&lt;td&gt;inventory what exists, generate, populate, then a &lt;strong&gt;coverage check&lt;/strong&gt; with a to-do ledger&lt;/td&gt;
&lt;td&gt;a pull request&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Read the differences as design information:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The bug workflow adds classify at the front (a surprising number of "bugs" are not bugs, and the cheapest possible outcome is a triaged ticket and no code) and prove at the back.&lt;/li&gt;
&lt;li&gt;The refactor workflow inverts the test relationship: instead of writing failing tests that define new behavior, it treats the existing suite as the specification and any change in it as a defect.&lt;/li&gt;
&lt;li&gt;The review workflow has no loop and no gate on code, because it changes nothing: its output is a judgment, and its risk is publishing a wrong one, so its distinctive phase is &lt;em&gt;verification of its own findings&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;The docs workflow has no tests, so its quality gate is a coverage measure instead.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Underneath, all six share the skeleton below, and more importantly they share their &lt;em&gt;parts&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The same six, as a grid.&lt;/strong&gt; &lt;code&gt;✓&lt;/code&gt; the workflow has that phase, &lt;code&gt;—&lt;/code&gt; it skips it, &lt;code&gt;●&lt;/code&gt; the phase is distinctive to it:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Bug&lt;/th&gt;
&lt;th&gt;Refactor&lt;/th&gt;
&lt;th&gt;Review&lt;/th&gt;
&lt;th&gt;PR comments&lt;/th&gt;
&lt;th&gt;Docs&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Classify&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;●&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;●&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Explore&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Plan&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Red tests&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Build loop&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Self check&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;●&lt;/td&gt;
&lt;td&gt;●&lt;/td&gt;
&lt;td&gt;●&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;●&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Report&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two rows carry most of the information. Classify is present only where a request might turn out not to need code at all (a bug that is not a bug, a review comment that is wrong) and in both of those the cheapest good outcome is no diff. Self check is where each workflow proves its own work, and the oracle differs every time: for a bug it is reverting the fix and watching the new test fail; for a refactor it is the &lt;em&gt;existing&lt;/em&gt; suite passing unchanged; for a review it is verifying its own findings before publishing them; for docs it is a coverage ledger. Same slot, four different definitions of "did this actually work", and the workflows with no entry there are the ones whose oracle is simply the tests named in the plan.&lt;/p&gt;




&lt;h2&gt;
  
  
  The cast
&lt;/h2&gt;

&lt;p&gt;Five kinds of component, and the distinctions earn their keep:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Orchestrator&lt;/strong&gt;: the workflow itself. Owns control flow, phase order, human gates, dispatch and wiring. Owns no domain logic. It is thin on purpose (&lt;a href="https://fullgc.github.io/designing-agentic-development-workflows-part-2/" rel="noopener noreferrer"&gt;part two&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Skill&lt;/strong&gt;: a unit of work: written instructions plus, usually, scripts. Planning, implementing, validating, exploring, presenting something for approval. A skill knows nothing about which workflow is calling it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Agent definition&lt;/strong&gt;: a file declaring a &lt;em&gt;dispatched&lt;/em&gt; sub-agent's context window, model, effort level and tool allowlist, registered with the runtime. This is the only place model and effort can be chosen (&lt;a href="https://fullgc.github.io/designing-agentic-development-workflows-part-2/" rel="noopener noreferrer"&gt;part two&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Channel adapter&lt;/strong&gt;: the only component that knows the outside world exists: one that reads from the code-hosting platform, one that writes to it, one that talks to the issue tracker. Everything else is oblivious.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deterministic primitive&lt;/strong&gt;: a component that reasons about nothing: durable run state, artifact assertions, source-control isolation. Pure scripts; arguments in, exit codes out; no state of their own.&lt;/p&gt;

&lt;h3&gt;
  
  
  A skill up close
&lt;/h3&gt;

&lt;p&gt;Three parts, and the split is what keeps a large collection affordable:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The procedure&lt;/strong&gt;: one file, always read when the skill is used. Keep it to the steps.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;References&lt;/strong&gt;: the detail, in separate files, loaded &lt;em&gt;only when needed&lt;/em&gt;. This is the difference between a skill that costs a paragraph and one that costs a chapter.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scripts&lt;/strong&gt;: the deterministic work, so the model isn't asked to be a computer.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The description in a skill's metadata does more work than it looks like it does. It is what a model reads when deciding whether this skill applies at all, so write it as &lt;strong&gt;"use when … NOT for …"&lt;/strong&gt;. The negative half is the one that gets dropped, and it is the one that prevents the failure nobody notices: a skill firing on work it was never meant for.&lt;/p&gt;

&lt;h3&gt;
  
  
  Who calls whom
&lt;/h3&gt;

&lt;p&gt;A step can run two ways, and the difference matters more than any other implementation detail:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Invoked inline&lt;/strong&gt;: same context, same model. The orchestrator simply follows the skill's procedure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dispatched&lt;/strong&gt;: a sub-agent with fresh context and its own model, effort and tool allowlist.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The same skill serves both paths, unchanged. That is what makes a skill reusable rather than a phase.&lt;/p&gt;

&lt;p&gt;For dispatched work there is an extra link in the chain: the orchestrator does not call the skill, it calls an agent by name; the agent definition supplies the configuration, and the sub-agent it spawns then invokes the skill. The indirection earns its place because the two files know different things: &lt;strong&gt;the agent, in our case, is workflow-aware, the skill is not.&lt;/strong&gt; The skill stays ignorant of which workflow is using it, while the agent carries what only this workflow knows: which tier this decision deserves, and which tools it may touch.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Finayb6x8hcgi51uaacw8.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Finayb6x8hcgi51uaacw8.jpeg" alt="An orchestrator reaching one skill two ways: directly inline, or by dispatching a named agent definition that spawns a fresh context sub agent which then invokes the same skill." width="800" height="437"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 2: The same skill serves both paths. The indirection earns its place because the agent is workflow aware and the skill is not.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Skills have to be reusable
&lt;/h2&gt;

&lt;p&gt;This is what makes a family cheaper than the sum of its members, and it rests on one rule:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A skill that only works inside one workflow is not a skill. It is a phase.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So every skill has to be usable at least two ways. One is inside its own workflow, which is a given. The other is some form of reuse beyond it, and that comes in two shapes. Either one is enough; what is not optional is that one of them holds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Inside a workflow.&lt;/strong&gt; The ordinary case: an orchestrator invokes it as a phase. This one comes free.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Either: inside &lt;em&gt;several&lt;/em&gt; workflows.&lt;/strong&gt; Planning, implementing, validating, writing failing tests, taking human approval, isolating a branch, filing a ticket. These are shared by nearly every workflow in the family. The reuse is not only economy. Improving one of them improves every workflow at once, and a defect in one is felt everywhere. That asymmetry is worth measuring deliberately: &lt;em&gt;which&lt;/em&gt; shared component hurts, and in how many workflows, because a fault in a component used by five workflows deserves five times the attention of a fault in a leaf.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Or: standalone, by a human, with no workflow at all.&lt;/strong&gt; "Explore how this subsystem works." "Plan this change." "Review this diff." "Set up an isolated branch for me." Standalone usability is a design constraint rather than a bonus feature, because it forces a skill to take its inputs explicitly, resolve its own context, and write its output to a file instead of leaning on an orchestrator's conversation. A skill that can be run alone can be tested alone, debugged alone, and adopted by someone who does not want the whole workflow.&lt;/p&gt;

&lt;p&gt;Two further composition properties fall out of the same discipline:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Workflows compose through artifacts.&lt;/strong&gt; Because a review produces a structured findings file, and the fix workflow can consume either that file &lt;em&gt;or&lt;/em&gt; live comments from the platform, review → fix works with no human in between and works before a pull request exists. When one workflow runs &lt;em&gt;inside&lt;/em&gt; another, it runs in a "composed" mode where the parent's approvals cover it, and it writes a decision log instead of posting replies: the accountability moves rather than disappearing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The most reused code is the least clever.&lt;/strong&gt; Run state, seam guards and branch isolation are shared by everything and reason about nothing. That is on purpose. The components with the widest blast radius should be the ones with no judgment to get wrong.&lt;/p&gt;




&lt;h2&gt;
  
  
  Anatomy of a run
&lt;/h2&gt;

&lt;p&gt;Whatever the domain, the skeleton repeats. Individual workflows skip phases (review has no build loop, docs has no tests, refactor writes none) but the order never changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SETUP        register agents, verify prerequisites        (fail fast, before any work)
INPUT        parse the target; resume if a phase cursor exists
STATE        create or reuse the run directory and its state file
ISOLATE      create or adopt a scratch working copy on its own branch
GATHER       fetch the request / diff / feedback  → an artifact
DECIDE       classify, investigate or explore     → an artifact   ← the expensive judgment
  ══ GATE ══ a human approves the decision, recorded by content hash
PLAN         an ordered plan naming files and tests
  ══ GATE ══ a human approves the approach
BUILD        write failing tests → loop { implement → validate } → referee
PROVE        revert, re-run, restore → proven | vacuous
SCOPE GUARD  the diff touches only what the plan named
  ══ GATE ══ a human approves what is about to be published
PUBLISH      push, reply, comment — through adapters, never forcing
CLOSE        a report, on every exit path
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi87839a5m26670s75i1t.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi87839a5m26670s75i1t.jpeg" alt="The run skeleton as a vertical stack of twelve phases with three full width human gates, a retry arrow inside build and an escalation arrow back to plan." width="800" height="437"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 3: The skeleton. The amber bars are the only places a person is required.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where the spine comes from.&lt;/strong&gt; The plan → implement → validate loop in the middle of that skeleton is not original here. It is Cole Medin's PIV loop (plan, implement, validate) which he states as the phased core of his AI-coding methodology (&lt;a href="https://github.com/coleam00/ai-transformation-workshop" rel="noopener noreferrer"&gt;ai-transformation-workshop&lt;/a&gt;), downstream of the context-engineering / PRP work that precedes it (&lt;a href="https://github.com/coleam00/context-engineering-intro" rel="noopener noreferrer"&gt;context-engineering-intro&lt;/a&gt;). Four of his moves carry straight into the design above: prime the context before planning rather than planning cold; a plan that states its own validation strategy; a fresh context window for implementation; and &lt;strong&gt;validation as layers&lt;/strong&gt;: type-check and lint, then unit, then integration, then review, rather than one test command. His &lt;code&gt;CLAUDE.md&lt;/code&gt;-style rules file is the same instinct as the "name intents, not commands" rule in part two: the repository declares its own rules, the procedure stays generic.&lt;/p&gt;

&lt;p&gt;What this document adds is mostly about making that loop hold when the steps are delegated and nobody is watching every one of them: gates recorded by content hash, a script rather than the agent inside the loop deciding when it stops, files as the only interface between phases, and a prove step after green that tries to make the new tests fail. The loop is his; the refereeing, the artifacts and the paranoia are the parts that get learned the hard way.&lt;/p&gt;

&lt;p&gt;A note on SETUP, because that is where the cheapest failures hide: a prerequisite check has to exercise the thing it is checking. Confirming that a build file exists proves nothing about whether its targets can run: the credential they depend on may have expired an hour ago. Probe the actual command, cheaply, &lt;em&gt;before&lt;/em&gt; dispatching anything, and derive the probe from the project rather than hard-coding it. Otherwise you discover it ten minutes later, inside a step, dressed up as a test failure.&lt;/p&gt;

&lt;p&gt;Two more details matter more than they look. &lt;strong&gt;Isolate the work outside the developer's own checkout&lt;/strong&gt; (a separate working copy on its own branch) so a run's noise never disturbs whatever the human has open, and so several runs can proceed at once. And &lt;strong&gt;flatten at the handoff&lt;/strong&gt;: a loop that commits after every iteration produces a history nobody wants to review, so collapse it into reviewable changes before handing over.&lt;/p&gt;

&lt;h2&gt;
  
  
  What all of this is for
&lt;/h2&gt;

&lt;p&gt;Worth stating once before the principles arrive, because it is the only part the person invoking a workflow actually feels: what they stop having to do. They bring the unit of work and nothing else. No long prompt to compose. Nothing to remember about which steps come in which order, or which skill to reach for, or what this codebase's conventions are: the practices are embedded in the procedure rather than in the habits of whoever happens to be driving. That is the whole adoption argument, and it is worth more than every engineering property in the rest of this document. Those properties (interruptibility, attributable failure, auditability, bounded cost, the ability to decline) are collected in part three, once there is enough machinery on the table for them to mean something.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://fullgc.github.io/designing-agentic-development-workflows-part-1/" rel="noopener noreferrer"&gt;fullgc.github.io&lt;/a&gt;. The rest of the series:&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;&lt;a href="https://fullgc.github.io/designing-agentic-development-workflows-part-2/" rel="noopener noreferrer"&gt;Principles for a workflow you can trust&lt;/a&gt;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&lt;a href="https://fullgc.github.io/designing-agentic-development-workflows-part-3/" rel="noopener noreferrer"&gt;The cost of control&lt;/a&gt;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&lt;a href="https://fullgc.github.io/designing-agentic-development-workflows-part-4/" rel="noopener noreferrer"&gt;Is it working, and is it worth it&lt;/a&gt;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&lt;a href="https://fullgc.github.io/designing-agentic-development-workflows-part-5/" rel="noopener noreferrer"&gt;Shipping it, porting it, letting it run&lt;/a&gt;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&lt;a href="https://fullgc.github.io/designing-agentic-development-workflows-part-6/" rel="noopener noreferrer"&gt;Where it hurts, and what to build first&lt;/a&gt;&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>workflows</category>
      <category>automation</category>
    </item>
  </channel>
</rss>
