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. land prints 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 land adjudicates 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 do repair 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.