If you've spent any time working with LLM agents on multi-step workflows, you've seen the pattern: you hand an agent a structured plan with five sequential steps, and it decides—entirely on its own—to run step five first. It pre-fetches information from step three, drafts conclusions before gathering evidence, and generally behaves like an eager intern who read the answers at the back of the book.

This isn't malice. It's the model doing what models do: pattern-matching toward completion. If the full plan is visible upfront, the agent treats it as context to be optimised against, not a sequence to be followed. And in long-horizon workflows with many moving pieces, that behaviour compounds into real failures.

Taskrails are my answer to this problem.

The problem

When you supply an agent with an entire operational objective and all its steps up front, you create a tension between what you asked and what the agent infers. The agent sees the whole map—including the destination—and naturally shortcuts toward it. It pre-completes research steps because it already "knows" what the conclusion should be. It runs tasks out of order because nothing structurally prevents it.

This is especially visible with sub-agents operating outside the direct observation of an orchestrator. A single non-deterministic decision, made without a human or parent agent watching, can steer an entire workflow away from its objective.

The deeper issue isn't the model's intelligence—it's the format of the instructions. A natural language paragraph with bullet points leaves too much room for interpretation about order, completion criteria, and boundaries between steps.

Progressive disclosure as a design pattern

In context engineering, progressive disclosure means giving an agent only what it needs to do the immediate task—not the full end-to-end plan. Skills already use this pattern: an agent sees only the keywords of a skill until it chooses to load the full SKILL.md. The agent never has the entire reference in context at once unless it explicitly requests it.

Taskrails extend this to workflows. Instead of handing an agent a five-step plan, you hand it a structured manifest and let it request tasks one at a time. At each step, only the current task's constraints are in context. The agent doesn't know what step three requires until it completes step two and explicitly asks for the next task.

This prevents two failure modes:

Pre-completion. The agent can't draft step five's output while working on step one because step five doesn't exist in its context yet.

Order violation. The tool that serves tasks will only return the next uncompleted task. The agent cannot skip ahead or reorder steps.

How taskrails work

A taskrail is a JSON manifest containing a title, description, and a rail array. Each entry in the array is a task with:

  • A task_id for ordering and state tracking
  • A task_name as a semantic label
  • constraints — a list of MUST, SHOULD, and MAY directives per RFC 2119, describing exactly what the agent should do and how
  • validation — a clear gate describing what "done" looks like, so the agent knows how its work will be judged before it begins
{
  "title": "editorial-article-pipeline",
  "description": "Produce a polished article from raw materials",
  "rail": [
    {
      "task_id": 1,
      "task_name": "gather_sources",
      "constraints": [
        "You MUST collect three data sources relevant to the article topic.",
        "You MUST store each source to the shared workspace.",
        "You SHOULD prioritise primary sources over secondary reporting."
      ],
      "validation": "List the filenames and source URLs you stored."
    },
    {
      "task_id": 2,
      "task_name": "synthesise_findings",
      "constraints": [
        "You MUST read all sources from the shared workspace.",
        "You MUST produce a 300-word synthesis identifying key themes.",
        "You MUST NOT draw conclusions not supported by the sources."
      ],
      "validation": "Output your synthesis with inline citations."
    }
  ]
}

State tracking uses a companion _state.json file recording which tasks have been completed. If a session fails and restarts, the agent resumes from the last completed task—not from scratch, and not halfway through a partially executed step.

The tool surface

The taskrail system exposes a small, deliberate set of tools. Every agent on the system has access to them:

Start self taskrail — Provisioned at session start. Agents that have a "self" taskrail assigned begin by running this tool, which returns the first task and creates a state file for tracking progress.

Resume self taskrail — Recovery tool. Reads the state file and returns the last completed task, allowing the agent to pick up exactly where it left off after a crash or restart.

Get named taskrail — Lists available taskrails by name, with title and step count. Agents discover what workflows are available to them without seeing the full task content.

Start named taskrail — Begin a specific workflow by name. Returns the first task and creates a state file.

Resume named taskrail — Recovery for a specific workflow. Checks for an existing state file and returns the last completed task, or instructs the agent to start fresh if none is found.

Get next task — The core progression tool. Requests the next uncompleted task in the active rail. Automatically updates the state file and signals completion when no tasks remain.

That's it. Six tools. No queue system, no ticketing platform, no external dependencies. The entire system is JSON files and a local tool interface.

What makes it work

I've been running taskrails across the Domestic Automation Daemon agents for several weeks now. Five agents use them: sysadmin, home-agent, web-publisher, web-developer, and ui-validator. Here's what I've observed:

Determinism

Structured tasks bring a meaningful step toward determinism—from the agent's perspective. Because each task is explicit about what to do and how to validate it, there's less room for an agent to reinterpret intent mid-workflow. The same taskrail produces consistent behavior across multiple runs and even across different models.

Validation-first

This matters more than I expected. Including validation criteria inside each task gives the agent a clear picture of how its work will be judged before it starts producing results. It's a well-observed pattern—in both humans and LLMs—that performance improves when evaluation criteria are known upfront. The agent doesn't guess what "done" means; it reads it directly from the task.

Lightweight by design

JSON is native to most LLMs. A taskrail file is a few dozen lines of structured data—no schema registry, no message queue, no database. An agent's tool implementation reads a file, parses it, returns the relevant task. The state file is another small JSON object. This simplicity means taskrails are human-readable, source-controllable, and trivially auditable.

Context economy

Each task loads only its own constraints into the agent's context. At step one, the agent sees only step one's instructions. When it requests step two, the previous task's context is replaced. This keeps the active context window focused on immediate work rather than carrying the entire workflow definition across every turn.

Extensibility

The format invites extension without breaking existing rails. Conditional branching (if/else tasks based on validation outcomes) is a natural next step. Completion timestamps can be added for tracking work-heavy tasks. Lifecycle hooks can act on data flowing between tasks. Because a workflow is structurally segmented, each of these extensions slots into a defined boundary rather than requiring a format redesign.

Concrete example: editorial pipeline

To make this concrete, here's how an editorial workflow would look using taskrails across multiple specialised agents:

  • Orchestrator — Runs a taskrail providing step-by-step guidance about the editorial objective. It doesn't know the full pipeline; it just knows the first task: orient and direct.
  • Data Researcher — Receives a taskrail for data acquisition. Each step constrains what sources to query, where to store results, and what format to use. Validation gates ensure data is actually present before proceeding.
  • Article Drafter — Taskrail introduces writing best practices and steps through data synthesis. Validation ensures previous agent data is available and useful before drafting begins.
  • Review team — Three separate agents (technical accuracy, writing quality, assertion checking) each run their own taskrail against the same draft. They never see each other's rails; they only see their own validation criteria.
  • Finalizer — Packages the finished article or shares it for human review. Validation confirms all review gates passed before publication.

Each agent goes into the workflow blind to everything except its immediate task. The orchestrator doesn't pre-complete the draft. The reviewer doesn't optimise for writing quality ahead of the drafter. Every step depends on the previous one, enforced not by trust but by the tool itself.

Where it lives

The full implementation lives in a private monorepo and will be published as a standalone project soon. The TypeScript extension at agents/sysadmin-agent/.pi/extensions/taskrail-tools.ts and the shared skill at agents/_skills/taskrail/ are the two main components. The system is active across all five agents.

What started as a fix for a specific failure mode—an agent running steps out of order—has become a general pattern I reach for whenever I need reliable multi-step execution. Taskrails don't replace agent judgement. They give it a safe structure to operate within.


Filed under: agentic systems, context engineering, building in the open.