Documentation
Steering fleet report
A steering session spends its attention on whatever it is doing, and the fleet changes underneath it. The fleet report is that negative space, composed server-side for every steering claim the calling session holds. Omit --project to get one report whose sections are those held scopes, each heading the claim's scope descriptor — the project slug for a whole-project seat, <project> · <document> for a seat narrowed to one strategy document. Pass --project P to keep a single scope. It reports; nothing in it staffs anything.
A seat narrowed to a strategy document reports only that document's work. Every item-keyed section — available work, claim holders, idle holders, live landing readbacks, dead waits, landed-without-close-out — is filtered to the items linked to that document in item_strategy_docs, and undelivered messages to the sessions holding those items. Delivery-plane and machine facts stay project-wide: a launch that never bound a session has no item to attribute it to, and machines are shared by every seat running on them.
It leads with available work — every step the scheduler calls runnable that nobody holds, ordered by rank, each row marked new (never started) or stopped (a claim release put it back) and flagged ! once it has waited past the staffing threshold. That is one list rather than two, because the diagnosis differs per row but the action does not: staff it. It appears before the alarms because the section answering "what can I staff right now" reading like leftovers at the bottom is what made a steering seat believe runnable work was being withheld from it.
Then the failures that arrive as silence, each quiet when it has nothing to say:
- idle holders — a claim held with no tool call past the idle threshold, parked sessions excluded unless the relay has proved their native process gone, and holders inside a long-running call excluded to the section below. A process-gone holder appears immediately, reads process gone, claims held — terminate deliberately if dead, and never receives a wake recipe;
- in flight — a quiet holder that is not stuck but sitting inside one call that is supposed to take this long: a watcher wrapper or a merge-queue landing wait. Quiet is measured between tool calls, so a command that holds the turn for forty minutes reads exactly like a stalled worker; the only difference is an open
session_tool_callsrow naming a command whose own budget covers the silence. Three guards keep that row from excusing real silence: a refused call is closed by the guardrail that refused it and carriesoutcome='denied'on its own row, so it is never open here, a row the session went on working past is residue from a harness that never closed it, and past 45 minutes — the widest budget any of these commands gives itself, the landing wait's own poll deadline — the call has outlived its own bound and rejoins the idle alarm; - landing readbacks — every open item landing pull request, read through the same non-mutating surface an agent calls as
yoke github merge-queue readiness PREFIX-N --json. Each row namesqueue-entry=AWAITING_CHECKS,UNMERGEABLE,MERGEABLE,absent, orunreadabletogether with arming. GitHub consumesautoMergeRequestwhen an entry forms, so null arming beside an entry ismerge-when-ready=consumedand remains in flight; null arming with an absent entry is truly cleared and the row is flagged!for action. The entry state, never absence of arming by itself, decides liveness; - undelivered messages — every envelope no recipient has read yet, one row per (recipient, delivery state) so a session in two situations gets a line each rather than one line whose count and reason disagree. Each row names the recipient, up to three message ids beside the count so the seat can read the envelope rather than infer it, the oldest envelope's age, and one of seven delivery states, which divide three ways.
The seat owes a move on exactly two, and only these raise actionable. never injected, no delivery attempted means the plane owed an attempt and made none: it appears one relay poll after that moment, which is once the recipient's own silence has passed fleet.wake_ack_grace_seconds counted from last_tool_call_at, so silence that accrued before the message counts — a worker quiet for four hours will not run the hook that would attach an envelope sent two minutes ago. never injected, last attempt failed (reason) appears at once whatever its age, carrying the named diagnostic resolved from that attempt's evidence (the adapter's refusal code, the eligibility rule that skipped the route, or the literal unreported when the attempt carried nothing); one reason repeating across a machine's rows is that relay.
The delivery is still on its way in three, all of which end in waiting and none of which is a failure. delivery attempt in flight is an accepted resume that has not settled either way. queued for the recipient's next hook is the ordinary case: no attempt is owed yet, because a hook has run since the send or the plane is still inside one poll of its own window. recipient turn in flight since HH:MMZ is a recipient whose latest session_tool_calls row is still open — the envelope lands on that call's own completion hook, and Yoke never resumes it, because a resume there would start a second turn on the same conversation. A row that is open is only the half of that a clock can see: a turn between tool calls holds none, so the recipient's own machine makes the same refusal from custody — while the pid it recorded for that session is still that process, it declines the wake, gives back the attempt it was charged, and leaves the envelope pending for the running turn's next hook. These three are reported and never count toward actionable, and they are deliberately excluded from the report's fingerprint: every ordinary send passes through one of them, so hashing them would turn routine fleet traffic into changed-report wakes for mail about to land by itself. Reporting any of them as a failure sends the seat after a delivery already under way, and this section formerly dropped all three, which left a seat unable to tell an attempt in flight from one that had never been made.
The recipient is gone in two, and no delivery route remains on both: recipient session ended wound down, recipient session terminated was ended deliberately. This is the one shape no later poll will ever resolve, and a live-recipient filter formerly hid it — the seat that sent the envelope had no other way to learn it never arrived. The row proposes nothing, because the envelope stays addressed to a session that is not coming back; the content has to be re-sent to whoever should have it now.
Terminal receipts never appear at all. Injected, acknowledged, expired, and cancelled envelopes are finished, and membership is decided by the delivery plane's own test for a receipt it would still lease rather than by the receipt's state column: expiry and cancellation converge on a sweep, so between the deadline and the sweep the row reads pending while nothing will ever deliver it. Sender is not a filter — a worker-to-worker envelope goes unread exactly like a steerer-sent one.
Two clauses qualify the two rows the seat owes. wake_escalation says the wake sweep has already escalated that receipt — starved_hook_route for a hook route the envelope proves stopped running, parked_without_idle_wake for a session that stamped parked on a harness whose manifest reports no idle wake, which escalates on the first pass rather than waiting the window out. That second reason is confined to a surface whose own capability declares message_stopped — every headless CLI surface and no desktop or IDE one. A desktop surface declares wake_authority: operator in its harness manifest, so Yoke never resumes it at all: resuming the conversation would fork the transcript its operator is reading. Such a row carries operator_wake and reads waiting for the operator to wake it — the message is delivered by hook injection the moment that person types anything in the chat, and past the same grace window Yoke sends them an actor-addressed Inbox message saying so. An escalated row is a resume in flight, not one a seat still owes by hand. Every row ends with an evidence clause naming the read that brings the recipient machine's own diagnosis to this seat — yoke session-control evidence get --session <id>, carrying --evidence-id when the failed attempt recorded a diagnostic reference; see machine-local-evidence.md;
- vendor-stopped sessions — a live session whose last turn the model provider ended rather than the worker. Nothing else in this report sees one: the session is live, its claim is held, and it will never speak again on its own, so every other detector reads it as a worker quietly thinking. The relay learns of it by reading the session's own native turn record on its regular poll — no idle window, no message needing to be pending first — and stores that observation on the session itself — the provider's message and its classification — and publishes a matching
HarnessSessionTurnEndObservedtelemetry row beside it. Recovery reads the stored copy, so a session stays recoverable and explainable after the telemetry ages out. Only surfaces whose harness manifest declares a readableturn_recordproduce these rows; a harness that fires its turn-end hook instead has nothing stuck and declares that as its recorded reason. Each row names the session, the item stalled behind it, the failure's classification and the provider's own words, and when it stopped — then what happens next. A failure a retry could move (a capacity refusal, a client build the provider stopped serving) is resumed by the relay on a widening backoff, and the row says which attempt is coming and when. A failure no retry can move (an exhausted quota, rejected credentials) is never attempted, and a budget that runs out stops the attempts; both name the seat as the next actor, and only those rows count towardactionable, so a fleet the relay is already handling reads as context rather than as work; - unregistered launches — a launch with no
registered_session_idwhen correlation has already failed, when its exact active native session exists inharness_sessionsbut the launch binding is absent, or when an otherwise in-flight registration has passeddeadline_at. The row explicitly says the instruction was not delivered and teaches reconciliation before retry; unrelated closed history is excluded. It also separates the two shapes that need different hands: a native that is up and answering needs only its binding, so the row reads native is live — bind it, while one that exited leaves an exit code and the last line it wrote, so the row reads native is dead — reconcile, then retry and quotes that line. The quote is there because the capture behind it lives on the machine that produced it, and the row closes with theevidenceread that brings the whole of it to a seat elsewhere whenever a native session id is known — see machine-local-evidence.md; - abandoned launches — a launch whose mandate reached its worker and whose worker never started: no work claim, no outbound message, no completed tool call, and its native now gone. Nothing else on the report can show this, because the launch closed and the item reads unclaimed rather than wrong. The row names the session, how long ago it closed, the native's exit code and last line, and says the work is unstarted — restaff it. Rows age out after six hours;
- landed without close-out — an item whose branch landed (its own
merged_at, ormerge_queue_landed_aton a queue project) while the item never reached a terminal status. The landing is recorded from GitHub by the control-plane observer rather than by the process that was waiting for it, so a merge whose waiter died still appears here instead of reading as an idle holder. Each row names the session still holding the item's work claim, orno live holder, because that is what separates the two recoveries: message the holder to re-enteryoke merge item PREFIX-N, or staff the close-out. Ages are measured from the recorded merge time, not from the poll that noticed it; - dead waits — for each idle holder, its last outbound message that actually asks something and whether an answer can still arrive. Only a message carrying an interrogative sentence or an explicit reply request counts: a holder's last peer message is frequently a confirmation, which waits on nothing, and counting it produced exactly the false positive this section exists to avoid. The scan reads back over the holder's recent conversation rather than its single latest message, so a confirmation sent after a real question neither counts itself nor hides the question still open underneath it.
answerer session has endedandanswerer's own item is already terminalare positive evidence that none can; a live answerer reportsunresolvedrather than a guess, and a holder that asked nobody produces no row. A reply that already came back is checked first, so an answerer that answered and then ended is not a dead wait. Neither is a question addressed to the steering ROLE: the next seat drains it on acquire, so an ended answerer there is a handoff rather than a dead end, and the awaiting-a-seat line below accounts for it instead. Both wrong answers cost real work — a false positive sends the seat to answer a question nobody asked, a false negative parks a worker forever — so the shape reports what it can establish and says when it cannot.
One line then counts the steering messages awaiting a seat in this scope — role-addressed reports that parked with no live seat, plus unacknowledged ones left by an ended seat. Acknowledgement settles a report for handoff: it is never inherited by a successor and never included in this count. The remaining rows are unowned work made visible; acquiring the scope hands them over as one digest, grouped by the sending item and newest first. The line is absent when the count is zero.
A combined report also lists unacked injected (this session) — messages addressed to the calling session whose receipt is injected but still unacknowledged past fleet.wake_ack_grace_seconds. Each row prints yoke messages acknowledge <id>. That is this session's own inbox, not the seat-awaiting count above.
It closes with the live item claims — every holder no section above already named. That inventory and the holder alarms render the same row shape, and an empty section renders nothing at all, so an inventory listing every holder printed a byte-identical row directly beneath the alarm that had just named it: a seat read one quiet holder as two sessions in trouble, and read the inventory's below-threshold rows as more rows under a heading promising no tool call in over twenty minutes. Each holder therefore appears exactly once, in the most specific section that claims it, and the inventory's heading says quiet there carries no alarm.
On a single-scope pull (--project P) that is followed by which (machine, surface) pairs a launch could actually reach — read through the same eligibility composition the launch preview uses, so the report can never name a surface the launch plane would refuse — plus per-surface session counts grouped by requested and served model/effort/context, a capacity line per machine, origin counts, and the plan-limits table. Every balance selection and plan-limit row names its live model, reasoning effort, and compact context window; a requested value that differs from served truth stays explicitly labelled. Every plan-limit row also names the vendor-enforced meter. Cursor supplies separate Cursor Models and Other Models monthly rows: composer- and cursor-grok- selections appear only beside Cursor Models, and every other model appears beside Other Models. Claude and Codex rows likewise identify the concrete vendor counter that supplies their remaining percentage.
The capacity line reads capacity lanes 3/12 · free 8.0 GB · load 4.5 on 12 cores · cap from max_worker_lanes, and it is read from the connected relay rows rather than from eligibility: a machine at its cap is exactly the one eligibility drops, so a report built only from launchable surfaces would show the full box as absent instead of full. Such a machine is listed with no launchable surface and its line ends AT CAP, launches refuse; a relay publishing no reading says capacity unreported and names the update rather than reading as roomy. Lanes count live sessions on the machine plus launches already assigned there and not yet registered, so a burst of launches cannot all pass the same free lane.
On a combined pull (no --project), those machine-level lines — launchable pairs, capacity, the launch-balance guidance note, and the plan-limits table — render once per distinct machine_id after the scope sections. Each ## <descriptor> heading keeps only that scope's facts: available work, the quiet-detector sections, live claims, model-aware launch-balance counts, and origin counts. The per-scope preamble is omitted there because the combined preamble already names the block.
Delivery needs no producer of its own, and no pending message either: composing the report only needs a live steering claim and the interval, so a steering session with an empty inbox still receives it at its next model-visible hook, clearly marked as control-plane state rather than peer-authored text. When a message is also pending, the report rides after it in the same reply rather than a second one glued on afterward — a harness that reads one structured reply per event, or reads raw stdout only on the events that open a turn, gets exactly one reply either way, never a valid value followed by unrelated bytes it cannot parse. Hook injection carries a compact digest (actionable sections, this session's unacked inbox, and yoke steering report get); the pull and the ride compose the same combined report so the two cannot disagree on what changed. Workers message their steerer as ordinary traffic and each of those messages is a wake, so a pending message's report rides an envelope already on its way and reaches every harness through the plane they share; a fleet quiet enough to send no messages still gets its report on its own next hook rather than waiting for one. Composing a report never by itself spends its delivery interval: a reply a sibling guard denies, or one settlement finds malformed, settles as not delivered, and the interval stays open for the next hook to try again instead of losing a whole cycle to a reply that never reached the model. Pull the full body on demand with yoke steering report get (function id steering.report.get); optional --project P filters to one held scope.
Three project-policy keys tune it, and the first two are deliberately separate numbers. steering_report_staffing_minutes (default 5) is how long runnable unclaimed work may sit before the report marks it overdue rather than merely available; waiting age is measured from the moment the work last became pickable — the later of the item's last change and the last release of a claim on it — so a released claim restarts the clock rather than making newly available work look ancient. steering_report_idle_minutes (default 20) is how long a claim holder stays quiet before the report presumes it stuck. "How long may work sit unstaffed" and "how long must a worker be silent before it is stuck" are unrelated judgments that once shared one value. steering_report_interval_minutes (default 2) is the shortest gap between reports appended to one session's messages — one combined report per interval, attached when any held scope changed or needs a decision — and composition happens at most once per interval because ranking each scope's schedule is real work. Scopes with actionable rows sort first.
Every detector except dead waits, verified process death, and positive launch-correlation failures is a time threshold, never an instantaneous read: each lifecycle segment boundary releases a claim and reacquires moments later, so a zero-owner snapshot is the normal shape of healthy work. A correlation failure is already evidence that the coupled instruction was stranded, and an exact active harness_sessions match is already evidence that waiting for registration will not repair the missing launch binding; both therefore report immediately. A parked session is a separate fact from an unowned item and is excluded from the idle list — it declared its wait — unless its process is verified gone and the declaration can no longer describe a running worker. Ended and terminated sessions are excluded entirely, because a session that is already gone would otherwise re-fire as an idle worker on every pass. And a report is attached only when it is worth a read: something needs a decision, or the content changed since the one that session last saw. harness_sessions.last_steering_report_at and last_steering_report_fingerprint record what was last delivered, claimed by a compare-and-set so two concurrent deliveries cannot both carry the same report.
Two absences are deliberate and recorded rather than left silent. A stale claim is not available work — the item keeps its holder until the stale-session sweep releases it — and the window between a holder going stale and the sweep firing needs no section of its own, because idleness is measured from last_tool_call_at rather than from any liveness label: the holder is in the idle list throughout and the item becomes available the moment the sweep releases it. And an item an operator is holding on purpose is excluded by the flag the operator sets: the frontier composition the report reads already drops frozen and operator-blocked items, so yoke items freeze and yoke items block are the whole hold mechanism. Work that will never resume is yoke items cancel, not freeze. Teaching the report to infer a hold from age would hide real unstaffed work.
A steerer or operator can disable one (machine, surface) with yoke session-control surface-policy disable --project P --machine M --surface S --reason TEXT. Launch preview/create and native-resume wakes then skip that relay and name the mark, the reason, and the enable command. In-flight sessions stay up. yoke status lists live marks on that machine. No counters, auto-trip, or probing.
Steering fleet report