I noticed something watching agents not as a developer but as a data engineer — the kind of person professionally annoyed when the same work gets redone for no reason.

You watch an agent ship the same deploy for the third time this week. And every time it figures it out from scratch: right, first run the tests, then build the image, then apply the migrations, then switch the traffic. It thinks out loud, burns tokens, sometimes gets the order wrong, sometimes decides to improvise in the wrong place. But the procedure converged long ago. It doesn’t change from run to run. You know it by heart, and honestly, so does the agent.

Eventually I stopped eyeballing it and counted. I wrote a miner that reads my own Claude Code transcripts — every project, every session, no model anywhere in the loop — and asked it which work I keep re-deriving. Top answer: deploy. Three projects, eight sessions, and behind 75% of them the same command sequence. Eight times an agent reasoned its way to a procedure that had stopped changing months ago.

That’s the mismatch: we’ve built a whole industry around growing an agent’s behavior from scratch on every run — in exactly the places where it should have been compiled long ago.

Two approaches🔗

Roughly, there are two ways to run an AI-assisted task, and today’s tooling leans hard on one of them.

The first is emergence. You give the model autonomy, a set of tools, and memory — and the correct behavior emerges at runtime. The model decides what to do next, builds its own trajectory, corrects itself. The canonical ancestors are BabyAGI and AutoGPT; the whole line of ReAct agents and “autonomous” frameworks are direct descendants. The demo looks like magic: look, it figured it out on its own. That’s the entire pitch.

The obvious objection: nobody ships AutoGPT anymore, so who exactly am I arguing with? Fair. The frontier moved, and it moved toward constraint — Claude Code, Cursor, Codex all ship hooks, skills, slash commands, sub-agents, permission modes. Compared to a 2023 autonomous loop they’re downright disciplined. But that deploy in my transcripts was re-derived eight times in 2026, by one of those disciplined agents, not by AutoGPT. The autonomy shrank. The re-derivation didn’t. Constraining what an agent is allowed to do turns out to be a different problem from noticing that it already knows what to do.

The second is compilation. A procedure that has already converged should be frozen — into a reproducible, auditable artifact. The model is called not as the driver of the whole trajectory, but pointwise, at explicitly marked spots where real thinking is needed. Everything else is an ordinary pipeline that runs the same way every time.

I lean on the second, and for the last year and a half I’ve been writing an engine built exactly for it (zymi). But this essay isn’t about the engine — it’s about why I think the industry picked the wrong default.

Yes, this is the old workflow-vs-agent distinction — Anthropic’s “Building Effective Agents” drew it years ago, and I’m not rediscovering the taxonomy. My claim is about time, not types: the same task migrates from emergence to compilation as its procedure converges, and that migration can be detected and automated.

Nondeterminism is a cost, not a feature🔗

Nondeterminism is a price you pay, and you should pay it exactly where genuine, non-standard thinking is required — nowhere else.

“It figured it out” sounds like a virtue while you’re watching the demo. In production it becomes “it figures it out again every time” — which means it can figure it out differently every time. The same task it solves in 6 steps today, 9 tomorrow, and on the third day it takes a wrong turn at step five. You can’t reproduce it, you can’t really audit it (“wait, why did it decide to apply the migration before the tests?”), you can’t give a guarantee. You’ve hired a brilliant intern who shows up every morning with complete amnesia and reinvents your runtime playbook live, in front of production.

The core problem with emergence isn’t that the model is dumb. It’s that nondeterminism can’t be localized. It’s smeared across the whole trajectory. Since the entire sequence of actions is born at runtime, the entire thing is unreliable — not just the part where judgment was actually required.

And if you look honestly at a typical “agentic” workflow, judgment is required at a vanishingly small number of points. A deploy is one live decision (“this migration looks risky, call a human?”) wrapped in ten utterly mechanical steps. Emergence makes the model regenerate both the risky decision and all ten mechanical steps, every time, with the same “what if” hanging over each.

The shape of a compiled pipeline🔗

The procedure is code: compiled, frozen, reproducible, living in git like any other DAG. The reasoning is the data flowing through it at explicitly marked nodes. You don’t grow the graph anew — you grow it once, then you execute it. (Data engineers will recognize dbt in this shape — that analogy got an essay of its own.)

In practice, for me it looks like this. A pipeline is an ordinary declarative DAG. Most steps are deterministic: call a shell, hit an HTTP endpoint, invoke a tool. The model shows up only at two kinds of seams:

steps:
  - id: run_tests
    tool: shell            # deterministic step, no model needed
    run: "pytest -q"

  - id: assess_migration
    agent: reviewer        # reasoning seam: we think right here
    task: "Assess the risk of migration ${steps.diff.output}"

  - id: promote
    ask: "Apply the migration to prod?"   # delegation seam: judgment goes outward
    depends_on: [assess_migration]

The run_tests step doesn’t reason — and shouldn’t. The assess_migration step reasons, because risk assessment is exactly where a model is needed. And ask takes the judgment out of the pipeline entirely — it hands it to the caller (a human or another agent). The nondeterminism here isn’t smeared — it’s collected into two marked points, and you can put your finger on exactly where the system “thinks.”

By the way, a nice side effect: a pipeline made of only deterministic steps needs no LLM at all. Zero model calls, zero keys. Under emergence there’s simply nothing to make optional: there the model is the runtime.

And yes, I can hear it: “so you’ve reinvented the shell script.” Almost. A compiled pipeline is deliberately boring — a script with marked seams where a model or a human is allowed to think. The point is not the artifact; the artifact is fifty lines of YAML. The point is where it comes from: nobody sits down and writes these cold. They get mined out of what the agent already did — and that changes what you need to build. Not a better script format, but the loop that notices when a script is ready to exist.

What compilation gives you, and emergence structurally can’t🔗

Once the procedure is frozen into an artifact instead of being reborn, a few things come for free:

  • Explicit control flow. The happy path, the branches, the gates, and the model calls are all visible before anything runs. You read the procedure instead of reconstructing it from a transcript afterwards.
  • Audit. Every action is an immutable event in a hash-chained log (I’ve written about that setup separately), so you know not just what the agent did but why — and you can replay a run without re-executing the tools.
  • Localized risk. The dangerous stuff lives only in explicit nodes. “Safe to run up to the dangerous step” becomes a property of the design, not luck.
  • Optional model. No reasoning node, no LLM call.

One thing I’m deliberately not claiming: that the outside world becomes deterministic. Tests flake, APIs drift, repositories move under you, tools have side effects. “Same input, same trajectory” would be a nice line to write and it would be false. What compilation actually buys is explicit, replayable control flow and a record of what was observed and decided — not a frozen universe.

The honest counterargument🔗

Here I’m supposed to say: “but some tasks genuinely haven’t converged.” Open-ended research. Debugging a new, never-seen bug. Fighting your way through an unfamiliar codebase. There’s no procedure to freeze — it still has to be found.

Completely true. And that’s exactly where you want emergence. That’s the reasoning seam — just a wide one.

I’m not against emergence. I’m against making it the default substrate. Emergence is a pointwise tool that lives in a marked node, not the air the whole system breathes. The mistake isn’t reaching for it — it’s leaving it on for everything, including procedures that converged half a year ago and haven’t changed since.

And one more concession, this one not in compilation’s favor. A frozen artifact has to be maintained. If a procedure drifts faster than you can recompile it — a deploy that gets rewritten every week — the compiled pipeline starts rotting faster than it pays off, and a live agent that re-reads the current state each time turns out cheaper to keep. Compilation wins comfortably on the converged and stable. On the converged-but-fast-drifting, it’s honestly an open question, and I don’t have a ready answer.

Which cuts at my own headline example: deploy is exactly the kind of thing that drifts. That’s why the number attached to it matters more than the count. Eight sessions is recurrence; 75% of them sharing a command sequence is stability, and the second one is what earns a compile. Frequency alone would have promoted half my transcripts.

Emergence is the discovery phase, not the production phase🔗

Where do compiled pipelines even come from? Not out of thin air — nobody writes them cold. You get them from observation. You let an emergent agent do a task a few times, watch the trajectory converge (I call this a hotpath), and compile the converged path into a pipeline.

This is exactly the loop I’m building right now, and ideally it’s closed: the agent (Claude Code, in my case) works as usual → periodically a search for repeating patterns runs over its logs → a converged pattern is turned into a pipeline → that same agent then calls the pipeline as an MCP tool instead of figuring the procedure out again. Explore it the hard way once — after that, execute.

To be honest about the status, so I’m not selling vaporware: this loop has been closed end-to-end at least once, and the part I claimed hardest — detection — turned out not to need a model at all. A first-hand example is the release of zymi itself. The procedure converged long ago and I ran it by hand dozens of times, so eventually I compiled it. Here it is in full, nothing elided:

name: release

expose:
  mcp: {}

inputs:
  - name: version
    type: string
    required: true

steps:
  - id: guard
    tool: check_clean

  - id: bump
    tool: bump_manifests
    args: { version: "${inputs.version}" }
    depends_on: [guard]

  - id: check
    tool: run_checks
    depends_on: [bump]

  - id: commit
    tool: commit_release
    args: { version: "${inputs.version}" }
    depends_on: [check]

  - id: ship
    tool: tag_and_push
    args: { version: "${inputs.version}" }
    depends_on: [commit]

Boring, as promised. Five steps, no model anywhere — this pipeline has no reasoning seams, so releasing zymi costs zero tokens and needs no API key.

The event log has three real end-to-end runs of it. Each took 30–40 seconds wall-clock — but nearly all of that is cargo clippy and cargo test, real work that has to happen no matter who drives the release. The pipeline’s own orchestration — guard, bump, commit, tag-and-push — totals 136 milliseconds across four steps, and zero tokens. Compilation didn’t make the work faster; the tests are a floor you can’t get under. It made everything around the work disappear: the re-deriving, the token spend, the chance of a different step order tomorrow.

But look at what isn’t a shell script. expose: mcp: {} is the entire wiring that turns this procedure into a tool any MCP client can call — no server code, no per-runtime adapter.

And the approval gate isn’t in the pipeline at all. tag_and_push declares requires_approval: true in its own definition, because pushing the tag triggers the publish workflow and that is the point of no return. The gate belongs to the dangerous operation, not to the procedure that happens to call it — so it fires from every pipeline that ever uses that tool, and no future pipeline author can forget to add it. Over MCP it renders as an approve/deny prompt in the calling agent; under zymi run it’s a terminal prompt. Either way the run parks itself: the pause is an event in the log, and the pipeline resumes from there when the answer arrives, whether that’s in ten seconds or tomorrow morning.

The version this text is written from was shipped exactly that way. I told the agent “release,” it invoked the pipeline as a tool, stopped at ship for confirmation — and that was it.

The miner is real and open: hotpath-miner. It is itself a compiled pipeline — deterministic parsing and counting over the transcripts, no LLM in the detection loop — with one filter doing the real work: a recurring ask counts as compilable only if a stable command sequence backs it; frequent-but-different-every-time work stays interpreted. The recurrence threshold is a cheap trigger for review here, not proof that a procedure is safe to freeze.

The deploy from the opening paragraph is the top named procedure on its green list (above it sit only raw command combos — git-and-shell tics the skill explicitly tells the agent not to oversell). The line that matters more is on the red one: save-memory — 5 projects, 11 sessions, broader and hotter than deploy itself, and rejected anyway, because there’s no stable sequence underneath it. Asking me to remember something is a real recurring intent with a different shape every time; it stays interpreted. A miner that can’t say no just relabels everything as compilable, and the red list is the only evidence that the filter is doing work rather than decorating a count.

The miner has also started eating its own cooking. Two of its decisions were never really counting: naming a recurring theme its lexicon has no words for, and overruling the filter when a real procedure falls just short of the stability bar. The tempting fixes are exactly the two defaults this essay argues against — hardcode ever more keywords, or bolt a headless LLM call into the loop. Instead, an assisted variant of the pipeline collects those two decisions into two marked ask: steps: the run parks, hands the question to whoever invoked it — me at a terminal, or the driving agent over MCP — and continues with the answer. Detection stays deterministic and keyless; the judgment that remains isn’t smeared through the code but gathered into two nodes you can put your finger on. The same seams as in the YAML above, applied to the tool that argues for them.

Its first real catch went the whole way: a from-scratch server-provisioning procedure I’d thrashed through across five separate sessions — disk, firewall, packages, ssh keys, sshd hardening, systemd units, TLS, web — mined from the logs, distilled by an interactive agent under the promote-hotpath rubric that ships with the miner, written up as a real pipeline with zymi-skill, exposed over MCP, invoked under a human approval gate. What’s still human is the judgment in the middle — I pick the candidate, the agent distills the converged procedure out of the messy history (genuine reasoning-seam work, exactly where a model belongs), and I approve the result before it ever touches prod.

The caveat I owe you here: this has been validated on my own repeated operational work. I haven’t yet shown that someone else can take an arbitrary messy trace of their own and reliably land a useful pipeline without me standing next to them. The bet is to keep shrinking that manual share — so that what gets compiled isn’t only the procedures I happened to notice, but all the ones that actually repeat. This is where the line runs between “just be reasonable about it” and a tool: not advice to alternate emergence with compilation, but a loop that moves work from the discovery phase to the production phase on its own.

So the process has two phases. Emergence is the discovery phase; compilation is the production phase. You need emergence to find the procedure, and determinism to exploit it.

The industry collapsed these two phases: it runs the discovery phase in production, paying the full price of non-standard thinking for thoroughly standard work. Most of what’s called “agents” today is discovery-phase code left running in production by accident. The right move isn’t to throw out emergence — it’s to compile the parts of it that have converged, and keep live thinking only where it belongs.

Recorded skills are the same instinct🔗

In July 2026 Anthropic shipped Record a Skill in Claude Cowork: you record your screen doing a task, narrate your reasoning as you go, and Claude packages the demonstration into a skill it can run again. I read that as the same instinct surfacing elsewhere — a procedure discovered in interaction should end up as a reusable artifact instead of being rediscovered forever.

The difference is where it starts and what it ends as. The source there is a recorded human session: you have to notice the work is repeatable and decide to hit record. Here the source is the agent’s own execution history, which is already on disk whether or not anyone noticed. And the output there is an instruction for the next model run; here it’s a pipeline with explicit tool calls, marked reasoning seams, approval gates, an event log, and an MCP interface — something you review as code, and that runs without a model at all if it has no reasoning seams.

Recorded skills and compiled pipelines are adjacent ideas. The part I care about is the compiler loop between them.

“The log is the agent” — same substrate, opposite direction🔗

Two days before this text went out, Yohei Nakajima published an essay arguing that the harness around a frozen model is not temporary scaffolding but a permanent organ — and prescribing, from neuroscience, almost exactly the substrate I described in the previous article: an append-only log as the source of truth, state as a projection over it, replay, forking, consolidation as an explicit operation. We arrived at the same log through different doors — he from memory and continuity, I from nondeterminism and reproducibility. When two unrelated lines of argument land on the same architecture, that’s usually a sign of an attractor, not of taste.

But the substrate is where the agreement ends, and the name of his research program marks the fork precisely: “The Log is the Agent” (arXiv:2605.21997, the ActiveGraph runtime). In that school the record is the runtime: behaviors subscribe to it, react to writes, and the trajectory emerges from the reacting. My position is less symmetrical than the slogan invites: the log is not the agent — the log is the protocol of a procedure that has already converged. The agent is what you need while it hasn’t. Emergence writes into the log during the discovery phase; compilation reads a frozen path out of it for the production phase. Same record, opposite direction of authority.

And the two problems his essay honestly leaves open are, I’d argue, exactly the ones compilation answers. He names selection — what decides which experience is worth keeping — as the open problem the whole architecture rests on, and warns that “an LLM reflecting on the day and choosing what to keep is a selection function, and an expensive, chatty one.” Agreed — and hotpath-miner was built around that trap: selection here is counting. Recurrence plus a stable command sequence, no model in the detection loop. He also notes that skills are homeless — “a learned procedure has no obvious home that isn’t the context window again.” That home is what this whole essay is about: a learned procedure’s home is a compiled pipeline, with its seams marked and its gates attached.

A side consequence: a compiled artifact is not an agent🔗

And one last thing, which follows from all of this almost mechanically.

If the product is a compiled procedure and not an autonomous agent, then it isn’t competing for the “be the assistant” seat — it’s a tool that plugs into whatever agent runtime you already have.

Emergence-first tooling builds runtimes that want to be the center: assemble your assistant out of our blocks. Compilation builds components that any center can call. For me the universal socket here is MCP: a pipeline is exposed as a tool, and it’s called the same way by Claude Code, by Cursor, or by someone’s homegrown LangGraph. I don’t need to write a wrapper per runtime, and I don’t need to build my own autonomous agent — that would mean stepping right back into the default this whole thing argues against.

A compiled procedure is agent-agnostic by construction. A grown assistant isn’t.

Instead of a conclusion🔗

This isn’t “agents are bad.” It’s: nondeterminism should stand where the thinking actually happens, and nowhere else. Emergence is a great way to find a procedure and a terrible way to run it.

Compile what has converged. Grow what hasn’t yet. And know, looking at the system, at which exact point it is thinking.


The engine where I put all this through its paces is zymi — alpha, Rust + YAML + Python; the miner is hotpath-miner, and the skill that helps an agent author the pipelines is zymi-skill. The question I’d genuinely like answered: where would you draw the line between “already converged” and “not yet,” and what signal would you trust to draw it for you?