Scheduling & execution
There is no scheduler component. Selection is a pull model: readiness promotes tasks into a pool, and workers claim from it. This page is the one flow to know.
Short answers
How does a task get selected?
A task becomes ready when every non-waived dependency is
done or partial. Workers list nodes with
state=ready and claim one. Ties break by ULID, bytewise.
How does a task get executed?
The claim opens one SQLite transaction: leases, run tree, first attempt, state transition, events. The worker then executes in the objective's clone and heartbeats to hold its lease.
How does parallel execution happen?
Across objectives. Each objective owns one clone and holds one lease, so its tasks run one at a time. Different objectives run on different workers at the same time.
The most important flow
pending → ready when all deps are done/partial or waived
ready → pending demotion on regression
state=ready
order: topological walk · ULID tie-break
ready → running, ancestors cascade
append lease.claimed + node.running
running → done or partial
rejection loops to a new attempt, limit 3 → blocked
release puts the task back to ready
ready
the pool refills without any central scheduler
Where the parallelism actually is
Worker A
objective-01 · clone A
task → task → task, strictly serial.
The objective lease is the serialization point.
Worker B
objective-02 · clone B
task → task → task, strictly serial.
Runs at the same time as Worker A.
Why a sibling claim is refused
A second claim on the same objective collides with the live
objective lease (relation: parent) or a sibling's lease.
Refusal names the holder, its fence, and its expiry.
- One live lease per subject. The fence counter never resets, so a stale holder cannot write.
- One active run per node, enforced by a partial index.
- One clone per objective — the reason tasks of an objective cannot run side by side.
- An expired lease does not heal itself. Recovery is explicit: status reports stale, then abandon or startup recovery acts.
Where each step lives in the code
| Step | Module |
|---|---|
| 2 · promotion | src/domain/readiness.ts · applied by src/services/readiness/dependency.ts |
| 3 · ordering | src/domain/task-order.ts — topological walk, ULID tie-break |
| 4 · claim | src/commands/node/claim-node.ts |
| 4 · leases | src/domain/lease-hierarchy.ts · SqliteLease |
| 4 · run tree | SqliteExecution — run · attempt rows |
| 5 · heartbeat | src/commands/node/heartbeat-node.ts — renew at fence |
| 6 · settle | src/commands/node/release-node.ts · outcome commands |
| recovery | startup-recovery commands · unblock · abandon |