Tickets
How work moves through orchflows. A ticket is one markdown file holding the durable assignment, lifecycle, result, and verification truth for one job.
One file is the whole work order
┌─ B1.2.md ─────────────────────────────────────────────────┐ │ id, run, status, admission, dispatch_v1 │ │ executor: orch-do standard: orch-code │ │ parent: B1 <- the call edge │ │ depends_on: [B1.1] <- optional graph edge │ │ bound: 45m <- time budget │ ├──────────────────────────────────────────────────────────┤ │ ## Goal observable result │ │ ## Context facts and constraints │ │ ## Details optional planner guidance │ │ ## Report executor-written, streamed while │ │ the work happens │ └──────────────────────────────────────────────────────────┘
The semantic assignment is sealed before dispatch. A later semantic change opens a successor run citing the accepted predecessor result; it never rewrites this run. The result sections belong to the executor, written as the work happens — never in one write at the end.
Before issue, tickets.py lint grades a hand-authored ticket's exact pre-issue shape without writing it anywhere. After issue, tickets.py show inspects one ticket's parsed identity without mutation.
Dispatch protocol
orchflows.dispatch.v1 makes the ticket the fence around at-least-once agent delivery. The caller invokes one command — tickets.py do or judge for a callable, tickets.py dispatch for a ticket written by hand — which promotes readiness, establishes the workspace, opens one attempt, and commits one immutable launch atomically: the agent, model, effort, and the whole prompt the child is given.
The child's prompt tells it to commit inside its candidate before closing and to print one verbatim artifact: <kind>:<identity> line, so the parent relays a machine line instead of a paraphrase. There is no accept step: the child's first filed record is its acceptance.
tickets.py land is the return in one command — it imports the outcome, joins it, retires the derived worktree, and reports the frontier that join made ready. The granular operations stay public for recovery.
A run is a tree of tickets
tickets/<run>/ ├── 00-root.md the whole job ├── B1.md a frame: goal, journal, no executor, no standard ├── B1.1.md ┐ ├── B1.2.md │ callables, one bounded work item each, minted under B1 └── B1.3.md ┘
The parent field places each ticket, so the ticket tree is the call tree — a fresh context reads the tree instead of reconstructing the call stack.
How tickets relate
parent— the call edge. A runtime child seals through its parent's own generation.depends_on— the dependency graph. A ticket is admitted only once every dependency has landed a report.landprints every ready ticket, and they go out in parallel — the rolling frontier, no phase barriers.root_generation,cut_generation,assignment_seal— bind every member to one validated immutable snapshot. A stale or mismatched seal is never dispatched.
Lifecycle
pending ──▶ ready ──▶ claimed ──▶ land ──▶ complete · blocked
stalled · limited · failed
└──▶ suspended
Admission grades the ticket against a snapshot of the whole run — dependencies complete, executor bound by the stamped standard, workspace policy, inputs resolvable — and stamps a hash receipt. dispatch-open atomically records the claim and absolute lease. After an attempt opens, the assignment is a fixed target.
Review
callables under the frame ─▶ JUDGE ─▶ LAND
one outside path a callable, runs the
then a do done
to repair predicate
Critique and repair — a critique is a judge ticket over the artifacts it is handed, and the repair answering it a do ticket, sequenced by the calling workflow's prose. Closing a frame over two or more do children refuses unless the tree holds a judging child or the journal says unjudged: <reason>.
Errors and feedback
- Refusals are named, never silent. The dispatch protocol documents
state-inaccessible,stale-attempt,live-attempt,identity-mismatch,claim-without-dispatch,idempotency-conflict, and more. - The join rules on everything. No returned result is trusted until
tickets.py landadjudicates it. A worker cannot declare itself done. - The absolute lease does not move. Launch replay, transport activity, and result filing never extend
lease_expires_at. An ended attempt must be retired or atomically replaced before a successor runs. - Findings fork by severity. Blocking defects go to a
dorepair ticket; non-blocking ones are recorded as candidate scope — logged, never dropped.
The through-line: authority, evidence, the definition of done, the answer, and the review trail live in one file, and receipts plus fresh readers keep any of it from moving while someone works against it.