hankdoes.ai

AI experiments and musings.

reference

The Trail to Rail Atlas

The mental model, spectrum, and patterns for building structured LLM agents that evolve from flexible to reliable over time.

The big picture of how we build structured agents. Covers the mental model, the spectrum, and the patterns.


1. The Problem

LLM agents are slow, expensive, and unreliable when given mechanical work. An agent that uses Claude to run find, parse JSON, compare sets, and assemble output spends 95% of its tokens on work that deterministic code handles in milliseconds. Worse, the LLM introduces schema drift, drops items silently, and produces inconsistent results across runs.

The solution is to match each piece of work to the right point on the flexibility-reliability spectrum. Creative reasoning needs full LLM autonomy. Bounded judgments need constrained LLM calls. Mechanical operations need deterministic code. Trail to Rail is a technique for discovering which is which, organically, by starting flexible and locking in only what’s proven stable.

2. What This Is For

Trail to Rail is for building repeatable processes, basically pipelines. A traditional deterministic pipeline is reliable and fast but brittle: every edge case needs explicit handling, and anything the author didn’t anticipate fails. An LLM agent is flexible and knowledgeable but expensive and unpredictable.

Trail to Rail gives you both. The LLM acts as a flexible scheduler and coordinator that can deviate from the plan when conditions warrant, handling edge cases without explicit instructions. This works because models are trained on vast amounts of technical writing, code, and documentation, and can dynamically leverage internal docs specific to your environment. You reach more functionality than a deterministic pipeline with less code and less time.

The downside is that LLM coordination is less predictable, slower, and more expensive than deterministic code. That’s exactly what trail-to-rail evolution addresses: you start flexible, measure what’s stable, and lock down the parts where flexibility isn’t earning its cost. The pipeline gets faster, cheaper, and more reliable over time without losing the ability to handle the genuinely hard cases.

It works well for:

This isn’t the right approach when there’s no repeatable process to optimize, like a one-shot chat agent, a brainstorming tool, or purely creative work where every run should be different.

3. The Spectrum

Every action in a pipeline belongs somewhere on the Trail-Road-Rail spectrum. The spectrum measures four things that always trade off against each other:

  Trail Road Rail
Cost to create Low (just a prompt) Medium (schemas, prompts, forced tool use) High (real code, edge cases, tests)
Flexibility Full Constrained None
Reliability Low Medium Perfect
Throughput One hiker with a backpack A truck A freight train

Trail — LLM as executor. Full autonomy, dynamic reasoning, cross-item synthesis. The agent decides what to do and how to do it, and can go off-path when conditions warrant. Cheap to create (just write a prompt), fully flexible, but expensive to run, slow, and unreliable for mechanical work. Use when: the work is exploratory, creative, or requires reasoning across multiple items at once.

Road — LLM as computation. Structured input, fixed output schema, reasoning unconstrained between the two. The agent decides how to get from A to B, but the on-ramp and off-ramp are defined. Classification, summarization, extraction, scoring. Medium effort to create, and worth it when consistency matters but judgment still varies per item. Use when: you can say “read this one item and fill in these fields” without needing cross-item context.

Rail — Deterministic code. Same input, same output, every time. File discovery, AST parsing, set operations, schema validation, template rendering. High effort to create, but free to run, instant, and perfectly reliable. Use when: you can write a for loop that handles every case without an “else: ask the LLM” branch.

Nobody lays rail to a place they’ve walked once. The right investment is the minimum that matches current demand. Some work stays on the trail forever because it genuinely requires cross-item reasoning that can’t be parallelized or schema-constrained. That’s fine. The goal is not to eliminate trails, it’s to make sure you’re not running heavy freight on a foot path.

4. Trail to Rail: How Agents Evolve

You don’t decide what goes on trail, road, or rail in advance. You discover it by running the agent and watching what happens. A new agent starts as a trail and you pave sections into road and rail as the problem stabilizes. The problem changes a lot early on, so you keep things maximally simple until usage reveals what’s actually stable.

Start on the trail. A new agent starts as a prompt. Claude Code gets the prompt, improvises shell commands, writes scripts on the fly, reads output, and makes judgments. The orchestration and decision-making is all inference, which makes it cheap to create but expensive to run. It works immediately though, and tells you what the agent actually needs to do.

Lay rail where the ground is firm. You watch what the agent keeps doing mechanically and pre-write scripts that do the same thing deterministically. File discovery, AST parsing, set comparisons, JSON assembly. These actions are now locked in, and you control the exact format of the results. Well-structured rail output means the trail agent spends fewer tokens parsing and more tokens reasoning.

Pave roads for the judgment calls. You look at the trail agent’s remaining work and find per-item judgments that don’t need cross-item context. You wrap those in schema-constrained LLM calls with fixed inputs and outputs. Small, cheap models run cached classifiers in parallel, producing better and more consistent results than the expensive trail agent doing the same work inline. The trail agent stays focused on coordination and reasoning.

Blazing the Trail

Every run is a new hiker who’s never been here before. When a hiker hits a fallen tree across the path, they have to stop, backtrack, scout a way around, and find the trail again on the other side. That’s expensive: output tokens, lost focus, degraded downstream work. And the next hiker hits the same tree and does the same thing all over again.

Blazing is how you fix this. You watch where hikers get stuck, then post a sign: “tree down ahead, take the left fork at the boulder.” Now every future hiker reads the sign and stays on pace. A few input tokens to mark a known obstacle costs far less than the output tokens every run burns on rediscovery.

A trail with too many signs becomes overwhelming, the hiker can’t hold it all in context. When that happens, split the trail into sections with a trailhead map at each one. Recent large models follow detailed instructions well, so the tradeoff favors more blazing, not less, until context becomes the bottleneck.

5. Principles

These fall out of the evolution described above.

JSON contracts at phase boundaries. Every phase produces machine-readable JSON consumed by the next phase or gate. Markdown is for humans only, never for cross-phase data transfer. This makes the pipeline debuggable: you can inspect any intermediate artifact and know exactly what the next phase will see.

Verify, don’t trust. Rail is trusted. Trail and road output is always verified, either by a gate script, by the orchestrator reading and checking the output, or by a downstream rail script that would fail on malformed input.

Fix forward at lowest cost. When something fails, prefer the cheapest fix: direct JSON edits over targeted re-enrichment, targeted re-enrichment over full re-runs, re-runs of individual items over re-runs of entire phases.

Structured observability. Every action records what it did, how long it took, what it cost, and whether it succeeded. This audit trail enables data-driven decisions about what to automate next.

Models win ties with reasons. When road output contradicts rail data, the model wins if it provides a reason. Rail data is structural and can miss context the LLM understands (dynamic imports, conditional paths, framework magic). Road schemas should require the model to explain its judgment so contradictions can be resolved rather than silently dropped.

Graceful degradation over hard failure. Some steps are valuable but not critical. A dependency graph builder might fall back to grep mode if full construction fails. Coverage analysis might be optional if tests can’t run. Design fallback paths for non-critical steps rather than blocking the entire pipeline.

6. Patterns

Draft-Enrich-Merge

The signature pattern. Three steps separate concerns: a rail script drafts a complete skeleton of every item, a road batch runner adds judgment to each item in parallel, and a rail script merges the results back by key. The separation matters because the draft guarantees nothing gets dropped (no LLM involved), enrichment can only add to existing structure (can’t corrupt or lose items), and the merge is a deterministic join you can inspect and test. When enrichment fails for some items, the merge still proceeds and reports what’s missing rather than blocking.

Gates

Deterministic validation between phases. A gate checks that the output of one phase meets the contract the next phase expects, catching schema drift and dropped items before they cascade. Place gates before expensive phases, the more a downstream phase costs, the more the gate saves when it catches a problem early.

Phases

A phase is a unit of work that produces artifacts. Phases typically mix all three spectrum positions: rail scripts draft, road enrichment classifies, rail merges, and a trail step synthesizes or writes narrative. Phases are sequenced with gates between them.

Before/After Symmetry

When an agent modifies things (writes code, fixes config), run the same analysis pipeline before and after. The delta between runs gives you quantitative improvement measurement and regression detection.

Observability

Observability closes the loop between running an agent and improving it. Every tool call, script execution, and gate result should be logged centrally with enough structure to answer: what ran, in what order, how long it took, what it cost, and whether it succeeded.

Section boundaries should be tool calls. The agent calls a section-enter tool at the start of each step and a section-exit tool when it finishes. Tool calls are the most reliable behavior you can ask any model to perform, and the enter/exit pair gives you duration, precondition validation, and a clean way to slice the inference history. Everything between the two calls is the exact reasoning the agent did for that section, extractable without guessing.

That slicing is what powers the evolution loop from §4. You compare the same section across runs: same inputs, wildly different token counts means instability. High variance tells you where to invest next, whether that’s blazing (the agent keeps hitting the same obstacle), laying rail (it’s doing mechanical work inconsistently), or paving road (its judgments need constraining). The audit log isn’t a debugging afterthought, it’s the primary input to making the next version better.


7. Execution Model

The phase file is the program. An agent is the runtime. Any framework with tool use works as the runner: Claude Code today, a headless SDK agent in CI tomorrow. The orchestrator is a trail agent that sequences everything else and should never do work that a script could do.


8. Why a Spec, Not a Framework

The traditional way to share a pattern like this is a code framework: a library you install, configure, and extend. That made sense when writing code was the expensive part. It’s not anymore. Code generation tools can produce a working implementation from a detailed specification in a few iteration cycles, tailored to your language, your infrastructure, and your exact use case.

A framework tries to be general enough for everyone. That generality has a cost: twenty connectors when you need two, abstractions deep enough to support configuration you’ll never use, and a maintenance burden shared across contributors with different goals. A spec gives you the design decisions and contracts, then you generate exactly the lightweight code your situation requires.

The Trail to Rail Atlas is the conceptual foundation. The Trail to Rail Specification is one complete reference implementation of these ideas, detailed enough that a code generation agent can rebuild it from scratch in any language. Fork the spec, adapt it to your environment, and generate your own agents. The experience captured in the design is the valuable part, not the code that implements it.