Latticeflow: Making AI Agents Stick To The Plan

10 min read Updated July 27, 2026

Published by Dan Richardson at https://freepdx.com/deep-dives/throughline-build

Team
Solo
My contribution
Owner and principal engineer
Outcome
12.9x lower billed input in one matched historical case; 98 completed ship transitions used zero model calls.
Status
Production: In daily use; public repository available
Constraints
  • The 12.9x result is one matched historical workflow, not a universal delivery claim.
  • Deterministic code owns state, side effects, checks, and ticket transitions.
  • The independent deployment uses operator-local identity and requires stronger controls inside an enterprise.

AI coding agents are powerful, but they are a poor place to store workflow policy. I built Latticeflow from scratch: a cross-platform native CLI that takes a development ticket from plan to ship, uses agents for the work that benefits from judgment, and handles state, checks, git, ticket transitions, rework limits, and delivery in deterministic code.

I have used it as my own evolving development harness since January 2026. It now builds almost everything else on this site, including itself.

In one matched historical case, Latticeflow used 991,492 billed input tokens where my earlier prompt-driven workflow used 12,757,736: 12.9x lower. Across the broader 101-ticket evidence corpus, 98 ship transitions completed with zero model calls.

Result
Matched full-lifecycle billed input12.9x lower
Historical equivalent cost at one pinned rate card$41.24 to $4.68
Completed ship transitions98 with 0 model calls
Current automated test suite~2,200 tests across 19 projects

Matched historical token use by workflow phase

The problem I was actually solving

Before Latticeflow, I used a system called Claude-Config: a carefully tuned corpus of Markdown slash commands that drove tickets through planning, implementation, review, and shipping inside a persistent Claude Code conversation.

It worked. It shipped real software, including parts of TradeTrack2. It also made the chat session serve as runtime, state machine, policy store, and tool gateway all at once.

That design became impossible to ignore during one seven-ticket TradeTrack2 chain. The run took about four hours and logged roughly 190 million cache-read tokens. No money left my account. I was using a flat-rate Max plan, so this is not a story about receiving a spectacular bill. It is a story about finding an architectural smell before it became one.

My postmortem estimated that roughly three quarters of those cache reads came from repeatedly reloading the same 26k-token operating corpus while the conversation advanced the workflow. The system was asking a model to remember what phase it was in, reinterpret the rules, decide which tool to call, and perform deterministic transitions over and over.

I was using a chat loop as a state machine.

A change in the economics of piped CLI usage made the risk concrete, but the deeper problem was already clear: the expensive, probabilistic component owned far more of the system than it needed to.

What I built

Latticeflow is the project name. The executable is build.

Point it at a Plane project and a ticket, and one command drives a bounded lifecycle:

  1. establish or promote a plan;
  2. create an isolated git worktree;
  3. dispatch an implementation worker;
  4. run deterministic gates;
  5. dispatch an independent review worker;
  6. enter a bounded rework loop when necessary;
  7. integrate, update the ticket, and ship through ordinary code.

Every phase succeeds, fails with a classified exit code, or produces an explicit rework transition. There is no resident agent, daemon, or hidden conversational state keeping the process alive. Each invocation ends; durable state is explicit.

Latticeflow ticket state machine Latticeflow drives one ticket left to right through six states: plan, cut worktree, implement, gate, review, and ship. Review can send the ticket back to implement, capped at two rework rounds. The gate runs two provers of its own: GateVacuityProver proves every gating check can actually fail, and GateControlProber re-runs failed checks against the untouched base ref. Three tiers of work sit under the spine: deterministic code with no LLM covers state transitions, gates and ticket writes; single API calls cover small scoped decisions; and a worker CLI in a git worktree runs only in plan, implement and review. Any phase can end the run with one of twelve exit codes, 0 through 11, mapped by ChainExitCodeMapper. Latticeflow one ticket, plan to ship the gate proves itself GateVacuityProver proves every gating check can actually fail GateControlProber re-runs failed checks on the untouched base ref plan cut worktree implement gate review ship rework, max 2 rounds deterministic code, no LLM state transitions, gates, ticket writes single API call small scoped decisions worker CLI in a git worktree - only plan, implement, review exit codes 0..11, mapped by ChainExitCodeMapper every phase either succeeds, fails with a classified code, or reworks clean | gate vacuity | ReviewUnavailable (quota / rate-limit) | gate env failure | ticketing outage
One ticket through Latticeflow: a deterministic spine, a gate that proves itself, and only three phases that ever spawn an agent.

Three kinds of work

The core design decision was to stop treating every action as “agentic.” Latticeflow divides work into three tiers:

TierExamplesExecution path
Deterministic operationsstate changes, git operations, checks, Plane writes, shippingcompiled C#
Small scoped decisionsnarrow classification or policy choicesbounded model or API interaction where configured
Agentic workinvestigation, implementation, reviewisolated worker CLI

This gives the model room to explore where exploration is useful without letting that exploration become the control plane.

A genuinely multi-vendor worker boundary

Claude Code, Codex, Gemini, and Copilot each sit behind an IWorkerAgent implementation and can be selected globally or by phase. Each adapter owns its transport, output parsing, completion rules, and authentication posture.

The orchestrator does not collect provider credentials or proxy model traffic. Workers use the operator’s existing CLI authentication and subscription context. That keeps the runner vendor-neutral at the orchestration boundary and makes model choice a configuration concern rather than an architectural rewrite.

Gates that prove they are real

A passing command is not necessarily a meaningful check. A typo, an empty test selector, or a command that ignores failures can turn a “green gate” into theater.

Latticeflow therefore tests the tests:

  • a vacuity prover hard-fails a gating check that cannot demonstrate a failing condition;
  • a control prober reruns a failed check against the untouched base ref so a broken environment is not blamed on the ticket;
  • review receives the gate evidence, not just a success flag.

The goal is not to make the agent sound confident. It is to make the boundary around the agent falsifiable.

Multi-ticket integration without a shared chat

The chain orchestrator builds a graph from blocked_by relationships and dispatches independent ticket levels with bounded concurrency. Integration follows a post-order traversal: children land into a parent’s local chain branch, nested parents merge upward, and only the outermost chain reaches the target branch and pushes.

That lets several agents work in isolated worktrees while preserving an explicit integration order and one final landing decision.

What the measurements showed

The formal evidence package compares Claude-Config with an earlier measured version of Latticeflow. I designed and built both systems, along with the event schema, measurement harness, normalization scripts, and analysis. This is a comparison between two generations of my own working development system, not a benchmark against somebody else’s deliberately weak implementation.

One matched full-lifecycle case

The cleanest case used the same model, day, hour, and repository template.

PhasePrompt-driven workflowLatticeflowReduction
Plan2,275,192276,1408.24x
Implement5,120,701517,8919.89x
Review2,154,159197,46110.91x
Ship3,207,6840moved to code
Total billed input12,757,736991,49212.87x

Under the same pinned historical rate card, the equivalent cost was $41.24 versus $4.68. The token ratio is the durable result; the dollar values are deliberately treated as historical.

This is one matched case, not a universal performance claim. The broader plan-phase corpus is descriptive rather than independently sampled, but every sensitivity cut still fell in an observed range of roughly 9x to 15x lower billed input.

The more durable result: shipping stopped being a conversation

Across 14 recorded Latticeflow runs:

Observed Latticeflow evidence corpus

The event log contains 241 model calls: 22 in plan, 109 in implement, 110 in review, and zero in ship.

Model calls by workflow phase

That does not mean shipping is free. It means shipping is now priced like ordinary software: CPU time, network requests, and engineering maintenance, instead of another open-ended model session.

Did the cheaper path still deliver?

A token reduction achieved by doing less work would not be useful. In the observed runner corpus:

  • 98 of 101 leaf tickets reached Done;
  • 95 of 101 passed their first review;
  • 109 of 109 implementation workers returned Ok;
  • 105 of 110 review sessions entered with zero recorded check failures.

Those are internal delivery signals, not proof of output parity with Claude-Config. The two pipelines were never graded with one shared quality rubric, so I do not claim that the report establishes equivalent quality. It establishes that the runner completed its own gates at a high rate while using materially less model context.

What I learned beyond the headline

The most useful findings were not all flattering.

Cache reads were a poor proxy for rework. Two back-to-back runs used the same operation document, build commit, worker, and model. The run with zero rework recorded 1.87x more cache reads than the run with two rework rounds. The practical lever was not “force fewer turns”; it was “remove turns the agent should never have needed.”

Cache reads in a historical near-replicate

Lower token volume is not savings when the chain does not converge. One GPT-5.5 run used fewer tokens per implementation call than the neighboring Sonnet runs, but a difficult ticket exhausted the rework cap. The root chain never landed: about 66 minutes, zero tickets shipped. The review loop worked; the implementer did not converge. That run remains in the report because deleting it would make the analysis less useful.

A configured optimization was not actually executing. The measured versions accepted --batch-implement, but the composition path never supplied the batch worker. Every reported ticket therefore paid a cold-start context cost. I later fixed the wiring, but I did not retrofit newer measurements into a closed historical corpus.

Optimization was not monotonic. By runs 09-14, the work had shifted from establishing the primary result to tuning and diagnosing the harness. Token use moved up as well as down while the operation document, runner, embedded phase instructions, and worker changed. Run 14 used the original Fable 5 available at the time; it is not an apples-to-current-Fable comparison, and the corpus contains no 5.6 or Sol run.

That is the right way to read this project: not as a contemporary model leaderboard, but as a record of building an observable system, finding where its costs came from, and iterating without hiding regressions or failed paths.

What made it hard

The difficult work was at the boundaries:

  • making nondeterministic workers produce deterministic process outcomes;
  • attributing usage across four CLIs with different event and token-accounting shapes;
  • distinguishing a ticket failure from a broken environment;
  • proving that a passing gate could actually fail;
  • carrying interactive Claude sessions through ConPTY on Windows and a Unix PTY elsewhere;
  • detecting worker completion from persisted transcripts when an interactive stop hook was no longer sufficient;
  • merging concurrent ticket trees without allowing a child failure to leak partial work onto the target branch;
  • keeping the evidence reproducible after the live systems continued to evolve.

The transport and full lifecycle are validated on Windows, macOS arm64, and Ubuntu. The current codebase is C# on .NET 10 LTS, compiles to a self-contained native-AOT executable, runs a three-platform CI matrix, and has roughly 2,200 xUnit tests across 19 projects.

Evidence, not benchmark theater

The report is intentionally narrower than the current product. It freezes historical logs already on disk and regenerates its quantitative tables with Python’s standard library: no network, API keys, or third-party packages.

It also says exactly where the evidence stops:

  • the full-lifecycle 12.9x result is one matched historical case;
  • the wider plan comparison is descriptive and clustered;
  • quality parity between the two generations was not independently established;
  • the older prompt corpus cannot be expanded because its source transcripts no longer exist;
  • the current Latticeflow has continued to evolve and is not represented by a new, conveniently better number.

That restraint is part of the work. Instrumentation only matters when it can tell you something you would rather not hear.

What changes inside an enterprise

The independent version uses local CLI authentication, explicit workflow state, repository-scoped configuration, and persisted evidence. Those are sound boundaries for one operator. They are not a claim of enterprise governance.

Inside an enterprise, I would replace the individual-operator assumptions:

  • organization-managed service identities for models, workers, repositories, and ticket systems;
  • least-privilege tool access with separate read, write, review, and release capabilities;
  • centralized, immutable audit retention instead of evidence held only with the local workspace;
  • secrets supplied by an approved vault, never operator files or prompts;
  • data classification and residency checks before prompt, retrieval, or artifact access;
  • vendor and model approval boundaries, including where customer or regulated data may go;
  • pinned evaluations before model, prompt, tool, or workflow changes reach a production path;
  • human approval for high-risk transitions such as regulated changes, destructive actions, and production release;
  • change-control evidence linking the ticket, decision, code, tests, review, approval, deployment, and rollback point;
  • tested rollback and incident-response paths for both workflow code and agent tools.

The prompts matter, but the agent’s tools deserve the same security review as any other production integration. The enterprise version would keep Latticeflow’s deterministic state and gates, then put managed identity, policy-controlled access, formal evaluations, change approval, rollback, and incident response around them.

The throughline

I built the system that builds the products.

Latticeflow is the productized version of the method behind The Shape of Done, writes through my self-hosted Plane fork, and is dogfooded on its own development. It is the difference between putting an AI tool in the loop and engineering the loop itself.

Current status

Latticeflow is in active development and daily use. The four worker adapters, plan-through-ship lifecycle, bounded rework, deterministic gate, multi-ticket chain orchestration, bootstrap commands, and cross-platform interactive transport are functional. Plane is the production ticketing backend today. A vendor-neutral streaming model client is implemented and tested but is not yet on a production path; GitHub and Linear backends remain future work.

The public repository includes a reproducible historical evidence package. It contains vendored analysis rows, workloads, and scripts so the report can be regenerated without network access, API keys, or third-party dependencies.

Source repository: ThroughlineTech/throughline-build.

Pinch or drag to inspect