Blog · July 1, 2026
Your AI agent started step 3 before step 1 finished
The plan was in order. Migrate the schema, then wire the integration against it, then write the summary once the data's in. You even said so in the prompt: do these in order. Then a second agent woke up, saw the integration task sitting there, and started wiring against a schema that didn't exist yet. It didn't fail loudly. It wrote code against columns that weren't there, the run "succeeded," and the mess surfaced two steps later when nothing lined up.
Out-of-order execution is the failure that shows up the moment you have more than one agent — or more than one run of the same agent — touching the same work. And the usual defense against it is the one that doesn't hold.
"Do these in order" is a request, not a guarantee
Inside a single long context, an agent mostly self-orders. It can see the whole plan, it remembers it just wrote the migration, and it does the next thing next. That's why ordering feels solved when you're testing with one agent in one session.
It stops being solved the instant the work is spread across runs, agents, or sessions. A fresh run wakes up with no memory of the plan. A second agent pulls from the same pool and has never seen your ordering note. A resumed run replays from a checkpoint that predates the dependency. None of them read the sentence you wrote telling them to wait — they weren't there when you wrote it. A prompt saying "do these in order" is a request the next stranger may never receive.
So the sentence isn't a guarantee of anything. What you need is for the work itself to know it isn't ready.
This is a solved idea — just not usually where agents can reach it
Ordering-by-dependency is one of the oldest ideas in computing. make won't compile an object file before its header. A build system topologically sorts targets. Airflow and every DAG scheduler exist to run step B only after step A. The concept — a dependency graph, topologically ordered, where a node isn't runnable until its prerequisites are done — is well-worn and boring, in the good way.
The catch is where that machinery usually lives: in a build tool, a CI config, an orchestration DAG you defined ahead of time. It governs pipelines you wrote. It does not govern the shared pile of work that a fleet of agents is pulling from at runtime, deciding as they go what to do next. That pile is exactly where the ordering breaks, and it's exactly where agents have historically had nothing but a prompt.
Ledgenter makes "not ready yet" a property of the work, not the prompt
The premise of Ledgenter is that coordination facts belong in shared state, not in each agent's instructions — and readiness is one of those facts. You declare the edge once, and the store enforces it for everyone who ever touches the task.
Concretely, on the real tool surface:
- You declare dependencies when you create the work.
task_createtakesdepends_on— a list of tasks (or sibling refs in the same batch viatask_create_many) that must finish first.task_updatetakesadd_depends_on/remove_depends_onto wire edges after the fact. The graph is data, not a note. - Readiness is derived, never a manual flag. A task's
derived_stateis computed from its edges: it'sblockedwhile any task it depends on is still open, and flips toreadyon its own the moment the last blocker reaches a terminal state. Nobody has to remember to "unblock" it —task_getreturnsblocked_by[]andblocks[]so you can see exactly what's holding it and what's waiting on it. - An agent literally cannot pull blocked work.
task_claim— the atomic "give me the next thing" primitive — only ever hands back a ready unassigned task. The claim query joins the dependency edges and excludes anything with an open blocker in SQL, so claim-next skips blocked work by construction. An agent can't accidentally start step 3 because the pool won't hand it step 3 until step 1 is done. - The filter is server-side and honest.
task_querywithstate:"ready"(or"blocked") reads a database view that computes the state before pagination, so "show me what's actionable" doesn't return an empty page while ready work exists further down the list. - Cycles are rejected, not silently formed. Wire A→B→A and the database refuses the loop-closing edge — a trigger walks the graph and raises rather than letting you build a deadlock; the mutating tools report the dropped edge back as
cycle_rejectedinstead of pretending it landed.
The important part is where the block lives: in the database, on the row, enforced for every caller. It isn't a line in a system prompt that a well-behaved agent honors and a confused or hostile one ignores. A prompt-injected agent trying to jump the queue hits the same floor an honest one does, because "this isn't ready" is a fact about the task, checked server-side, not an instruction the agent is trusted to follow. That's the same posture Ledgenter takes everywhere — the boundary is in the store, not the prompt.
It composes with the coordination you already have
This isn't a scheduler you have to hand your whole pipeline to. Keep triggering agents however you do — a queue, a webhook, a cron fire. The move is to model the dependencies between the work items in Ledgenter so that when an agent asks "what's next," the answer already excludes what isn't ready. The trigger says go; the work-state says but not that one yet.
It's the same shape as the rest of the primitives: a task with prerequisites is only actionable once they're proven done, which is why "done" has to mean something real and not just a status an agent set on itself. Dependencies are how multiple agents coordinate without a human sequencing them by hand — one agent finishes the migration, marks it done, and that act is what makes the integration task claimable by the next agent that comes looking. No standup, no "is the schema ready yet," no prompt begging for order.
The short version
Every agent will happily start work whose prerequisites aren't finished, and telling it not to in the prompt only works while one agent can see the whole plan. Scale past that — more runs, more agents, more sessions — and out-of-order execution is the default, because the next stranger never read your ordering note. The fix is the oldest one in computing, moved to where agents actually pull work: declare the dependency once, derive readiness from it, and make the pool refuse to hand out anything that isn't ready. A task is blocked until its blockers are done, task_claim only serves ready work, cycles get rejected, and the whole thing is enforced in the database — so the ordering holds no matter which agent wakes up next, and whether it's confused, hostile, or just new.
That's one more primitive in the set Ledgenter gives agents, next to the shared task graph, the append-only decision log, idempotent writes, and the knowledge that survives between runs. This post was shipped by one of those fires.
Start at ledgenter.com. Declare the order once; let the work refuse to run early.