5.6 KiB
5.6 KiB
T1.3 — Run executor
| Field | Value |
|---|---|
| Phase | P1 — Walking skeleton |
| Size | L — over 3 days |
| Status | Not started |
| Flags | — |
| Spec | inlined below |
| Blocks | — |
Goal
Drive a hardcoded three-step workflow through the full run lifecycle, emitting every kernel transition to the log. T3.5 later replaces the hardcoded workflow with the IR interpreter.
Facts (inlined — no spec read needed)
Run lifecycle — the states and the legal moves:
spawn
│
v
Scheduled ─────────────────┐
│ admitted │
v │
Running ⇄ Suspended ──────┤
│ all steps terminal │ cancel
v │
Verifying ─────────────────┤ ◄── rests here while verifier
│ │ futures are outstanding
v │
Verified{pass|fail} │
│ │
v v
Grading ──────────────► Cancelled ●
│ ◄── may wait for a tournament group to fill
│
├──► Graded ● ──┐
│ ├──► Archived ●
└──► Ungraded ● ──┘
- Cancel is reachable from every non-terminal state, not only
Scheduled. A run cancelled mid-step is the ordinary case — it is what a user clicking stop does. Cancelling duringVerifyingorGradingis rarer and still legal. VerifyingandGradingare states the run rests in, not synchronous branches. Resolving pass/fail insideverify()collapsed this in the prior implementation and blocked three separate features at once: async verifiers, mid-run UI, and the snapshot barrier (T4.2).Ungradedis terminal and sits besideGraded, not below it. Grading can legitimately end with no score. Without a terminal state saying so, those runs rest inGradingforever and become permanently irreducible (T4.4).Suspendedreleases the worker: a run awaiting human approval or a webhook must not hold an executor slot across a human decision.- Concurrency shape: unbounded across runs; serial steps within a run;
strictly serial attempts within a step; fan-out only for declared
Parallelbranches and for verifiers of one attempt. The serial spine is(TenantId, RunId).
Steps
- Define the run-state enum and its transition function, exhaustive like T0.2's.
Include
Cancelledas reachable from every non-terminal state. - Build the executor loop: take the hardcoded three-step workflow, run steps serially, each step producing one or more attempts (T1.4).
- Emit a
WorkEventfor every transition throughEventLog::commit— run level and attempt level. One commit per transition, not one per run. - Enter
Verifyingonly when every step is terminal. Park there; do not resolve verification inline. - Enter
Gradingas a resting state likewise; allow it to terminate asGradedorUngraded. - Run everything inside a
RunScope(T1.1) so cancel propagates and no task outlives the run. - Add
Suspendedwith lease release wired as a no-op stub in embedded mode — the state must exist now, since T7.3 depends on it.
Acceptance
- One run completes end to end against the stub model.
VerifyingandGradingare observable as distinct states in the log, not skipped or collapsed.
Verify
Harness: embedded redb, stub model (T1.2), one throwaway verifier, one stub
grader. This is the first test that exercises T0.5, T0.6, T0.8, T1.1 and T1.2
together — treat it as the P1 integration point.
Integration test — tests/it_full_run.rs:
- Spawn one run of the three-step workflow; drive to completion.
- Read the log back and assert the run-state sequence contains
Scheduled, Running, Verifying, Gradingand a terminal state, in that order. AssertVerifyingandGradingeach appear as their own record — not inferred, not skipped. - Assert every step produced attempt records and that no state appears out of order against T0.2's table.
- Call
assert_refold_identicalon the finished run. - Cancel matrix: start a run, cancel it from each of
Scheduled,Running,Verifying,Gradingin turn; assert each reachesCancelled. - Ordering: launch 20 runs concurrently, assert each run's own records are totally ordered and that no two steps of one run overlap in time.
Command: cargo test -p executor --test it_full_run
False pass:
- Asserting
Verifying"happened" by checking a boolean on the run record. That passes against a synchronousverify()call, which is the collapse this task exists to prevent. The log record is the assertion. - A cancel test that only covers
Scheduled. That is the one case a broken implementation gets right. - Step 6 with a single run, where serial execution is indistinguishable from accidental parallelism.
Traps
- Making cancel legal only from
Scheduled. Nobody would ship that lifecycle. - Treating
Verifyingas a function call. It removes the seam T4.2 needs. - Interleaving steps within a run "since they look independent". The serial spine
is
(TenantId, RunId); parallelism lives between runs and inside declared fan-out only.
Background (not required to do this task): rust-agentic-sys.md §5.2, §5.3 · rust-agentic-task.md