Yoke documentation
DB Reference — QA, Release, and Session Tables
Schemas for the QA platform tables, release entries, merge locks, and harness session / claim tables. Cross-link back from db-reference.md for entry points, the domain catalog, timestamp discipline, JSON-payload conventions, qa CLI, body write path, and the status lifecycle reference.
Table: qa_requirements
Stores QA requirements attached to items, epic tasks, or deployment runs. Each requirement declares what kind of QA must be performed, when in the lifecycle it is due, and what success looks like.
id INTEGER PRIMARY KEY
item_id INTEGER -- nullable; FK to items(id)
epic_id INTEGER -- nullable; FK to epic_tasks(epic_id)
task_num INTEGER -- nullable; FK to epic_tasks(task_num)
deployment_run_id TEXT -- nullable; FK -> deployment_runs(id)
deployment_stage TEXT -- nullable; pinned deployment QA stage
deployment_member_item_id INTEGER -- nullable; attached item for item-scoped stage QA
qa_kind TEXT NOT NULL -- free-form: implementation_review, simulation, smoke, e2e, visual-regression, etc.
qa_phase TEXT NOT NULL -- CHECK: verification | post_deploy | manual_acceptance
target_env TEXT -- semantic: local | preview | ephemeral | prod
blocking_mode TEXT NOT NULL DEFAULT 'blocking' -- CHECK: blocking | non_blocking
requirement_source TEXT NOT NULL DEFAULT 'explicit' -- CHECK: explicit | seeded_default | ac_derived | flow_derived
success_policy TEXT -- JSON: defines what counts as success (see below)
capability_requirements TEXT -- JSON array: e.g. ["browser","docker","ssh"]
suite_id TEXT -- nullable, unconstrained; links to future test-intelligence suite
waived_at TEXT -- ISO timestamp if waived
waiver_rationale TEXT -- why waived
created_at TEXT NOT NULL
Polymorphic FK constraint: Exactly one of (item_id), (epic_id + task_num), or (deployment_run_id) must be non-NULL. Deployment rows are legacy when stage/member are both NULL; scoped rows require a stage and use a member only for item scope. Enforced by CHECK constraint.
Indexes: Base subject indexes remain on item, epic task, and deployment run. Plan-case materialization uses separate partial unique indexes for legacy run, run+stage, and run+stage+member subjects. Scoped keys include the execution-target digest after plan, case key, and host baseline so a superseding receipt preserves old evidence while allowing a fresh exact-target execution.
success_policy JSON Schema
The success_policy column stores a JSON object defining what counts as success. It must support non-binary, statistical, and composite assessments. Examples:
// Deterministic
{"type": "deterministic", "check": "exit_code", "expected": 0}
// Threshold
{"type": "threshold", "metric": "score", "min": 3.5, "max": 5.0}
// Statistical
{"type": "statistical", "required_passes": 7, "total_runs": 10}
// Agent judgment with confidence
{"type": "agent_judgment", "min_confidence": 0.8, "pass_threshold": 0.8, "fail_threshold": 0.4}
// Composite (multi-criteria)
{"type": "composite", "criteria": [
{"metric": "layout_score", "min": 4},
{"check": "no_missing_elements"},
{"metric": "color_match", "min_pct": 80}
]}
Downstream consumers (conduct, usher) implement policy evaluation. A centralized evaluation engine is deferred. Full per-type semantics live in qa-platform/success-policy-schema.md.
Table: qa_runs
Records individual QA executions against a requirement. Multiple runs per requirement support statistical success policies.
id INTEGER PRIMARY KEY
qa_requirement_id INTEGER NOT NULL -- FK to qa_requirements(id)
performed_by TEXT NOT NULL -- how it ran: agent, shell, playwright, manual, github-actions
qa_kind TEXT NOT NULL -- what was tested (denormalized from requirement for query convenience)
verdict TEXT -- CHECK: pass | fail | undetermined | error (nullable until inspection writes it)
verdict_reason TEXT -- required when undetermined; agent outcomes also require linked evidence
execution_status TEXT -- CHECK: captured | capture_failed (nullable for non-browser runs)
score REAL -- nullable numeric score
confidence REAL -- nullable confidence level (0.0-1.0)
raw_result TEXT -- → JSONB on Postgres; JSON: full execution output
duration_ms INTEGER -- nullable execution duration
started_at TEXT -- ISO timestamp
completed_at TEXT -- ISO timestamp
created_at TEXT NOT NULL
Index: idx_qa_runs_requirement(qa_requirement_id)
Capture vs inspection. For requirements whose method_id is browser-check or browser-inspection, the two columns serve distinct concerns:
execution_status='captured'means the daemon successfully saved the expected screenshots to disk.execution_status='capture_failed'means the daemon errored, an artifact path was missing, a step failed, or completeness check failed.verdictis set only after screenshot inspection (LLM or human evaluation of the screenshot content). Infrastructure success alone never writesverdict='pass'.- Typical lifecycle:
yoke qa case run --requirement-id <id>records the
method's execution result. An evidence-backed Browser inspection can remain undetermined, halting the item until a project owner/operator resolves its qa_needs_review request. Missing evidence is an execution failure and asks no human.
Every downstream gate that filters verdict='pass' (status-transition, pre-merge, pre-deploy, flow-gate updates) therefore gates on inspection outcome, not capture.
Browser run freshness: For Browser method cases, the QA gate checks that passing runs are fresh — i.e., their created_at is at or after the latest commit timestamp on the item's branch. If an Engineer retry changes code after a Browser case was recorded, the prior passing run is stale and does not satisfy the gate. This prevents evidence for a different deployed revision from passing. The freshness check applies only to rows that already carry verdict='pass'; unresolved inspection rows fail the verdict predicate first. When no branch can be resolved (for example, an item without a worktree), the freshness check is skipped gracefully.
Table: qa_artifacts
Links binary/text artifacts (screenshots, diffs, logs, traces) to a QA run.
id INTEGER PRIMARY KEY
qa_run_id INTEGER NOT NULL -- FK to qa_runs(id)
artifact_type TEXT NOT NULL -- screenshot, diff_image, log, trace, etc.
content_type TEXT -- MIME type: image/png, text/plain, etc.
artifact_handle TEXT -- typed handle JSON: {"backend":"s3","bucket":B,"key":K} or {"backend":"local","path":P}
metadata TEXT -- → JSONB on Postgres; JSON: dimensions, file size, etc.
created_at TEXT NOT NULL
Index: idx_qa_artifacts_run(qa_run_id)
Artifact handles: artifact_handle is the only file reference — a typed JSON document naming where bytes live. All submitted files and inline bytes use the configured project S3 store; upload completes before its row is recorded. Only a genuinely unconfigured bucket selects permanent server-local storage; hosted tenants use YOKE_QA_ARTIFACT_BROKER_URL, YOKE_QA_ARTIFACT_BROKER_TOKEN_FILE, YOKE_QA_ARTIFACT_BUCKET, and immutable YOKE_QA_ARTIFACT_PREFIX settings. Invalid configured storage returns its real error without a row or local downgrade. Existing readable local handles and repo baselines remain supported, but bare paths are refused. Gates check local files and accept valid S3 handles structurally without an added network call.
Table: release_entries
id INTEGER PRIMARY KEY
item_id INTEGER NOT NULL -- backlog item ID
category TEXT NOT NULL DEFAULT 'improvements' -- features|improvements|bug_fixes|internal
title TEXT NOT NULL
version TEXT NOT NULL
project_id INTEGER NOT NULL REFERENCES projects(id) -- explicit attribution
created_at TEXT NOT NULL -- app-supplied ISO-8601 UTC; see "Timestamp discipline" below
UNIQUE(item_id, version, project_id)
CHECK(category IN ('features','improvements','bug_fixes','internal'))
Table: merge_locks
id INTEGER PRIMARY KEY
session_id TEXT NOT NULL
branch TEXT NOT NULL
epic_id TEXT
acquired_at TEXT NOT NULL
expires_at TEXT NOT NULL
Table: harness_sessions
Tracks active harness sessions and their registered identity. Sessions with ended_at IS NULL are active. A persisted active work claim protects its live holder regardless of heartbeat age, park mode, process presence, or relay restart. The scheduler reports that claim as claimed_by_other_live; stale-session reclaim refuse to release it with reason=active_work_claim. Explicit release, completion, cancellation, or authorized termination settles ownership. Parking records a delivery wait for wake routing but does not decide retention. Derived liveness (sessions.list, yoke_core.domain.session_staleness.session_liveness) is active, waiting, stale, or ended: a parked live session holding an active work claim is waiting, never stale. Wake and message routing key on the activity-only activity_liveness instead. Each roster row also carries reclaimable, the same stale_reclaim_candidate predicate find_stale_sessions applies, so the Sessions page counts only sessions the sweep would act on.
Stale-session thresholds (canonical reference). The reclaim windows are config-tunable, not code literals. The sweep first selects an occupancy tier:
session_stale_ttl_minutes(default20) — the short tier for a session with no active work claim, no session-owned strategy-document claim, and no session-owned coordination lease. One base applies on every harness; transient stop signals attempt only a non-destructive empty-session end.session_stale_ttl_with_holdings_minutes(default1440) — the minimum tier for session-owned strategy-document locks. Active work claims have no inactivity bound.yoke sessions terminate SESSION-ID --reason Ris the authorized operator route to release a stranded holder. It refuses a session a wake is resuming asTERMINATION_RESUME_IN_FLIGHT— awake_relayattempt started at or after the recorded native exit that is still open or resumed a process not yet reported exited, within the resume-custody quiet window — because a headless worker's process exits between turns and a message resumes it;--allow-resume-in-flightoverrides for a deliberate restaff or evidence it cannot resume.
Resolver: yoke_core.domain.sessions_analytics_core owns both source thresholds. The sessions-card stale-eligible time is absent while an active work claim protects its holder.
Long commands and sparse tool boundaries. Registered Command cases and watcher-backed suites refresh the owning session and claims while the child runs. The persisted work claim protects a silent or crashed holder until explicit release or terminal action.
session_id TEXT PRIMARY KEY -- globally unique session ID (from contract)
executor TEXT NOT NULL -- executor identity (e.g., claude-code, codex)
provider TEXT NOT NULL -- model provider (e.g., anthropic, openai)
model TEXT NOT NULL -- model identifier (e.g., claude-opus-4-7)
execution_level TEXT NOT NULL DEFAULT 'primary' -- session grouping identity stamped by session-routing settings
capabilities TEXT DEFAULT '[]' -- JSON array of capability tags
workspace TEXT NOT NULL -- absolute path to working directory
mode TEXT DEFAULT 'wait' -- session mode (charge, feed, strategize, wait)
offered_at TEXT NOT NULL -- ISO 8601 when session was registered
last_heartbeat TEXT NOT NULL -- ISO 8601 of last heartbeat
native_process_gone_at TEXT -- when the death was first seen, not when last reported
native_process_gone_evidence TEXT -- bounded JSON evidence from local records
ended_at TEXT -- NULL while active; set when session ends
offer_envelope TEXT -- checkpoint and skip-memory telemetry JSON (optional)
actor_id INTEGER NOT NULL -- the actor this session acts for; never NULL
Actor binding. A session a person opened binds that person's identity. A session another session LAUNCHED binds the launching actor, transitively — the launch's requester_actor_id, read at registration through the authenticated launch side channel, outranks both the machine's OS login and a relay's bearer-token actor, and an unreadable launch refuses registration rather than falling back to either. Every action one session takes on another (message, wake, keep-alive, terminate, launch) writes a SessionActionPerformed row into the TARGET session's history carrying the ACTING actor, and is role-checked against the target project by yoke_core.domain.session_action_authority: project membership for messaging, waking, holding alive, and terminating a launched worker; project owner or org admin for terminating another actor's interactive session. Contract: docs/archive/decisions/session-actor-follows-the-person.md.
The process-gone columns record machine evidence without ending a claim holder. The reporting machine keeps its record until the control plane ends the session, so a retained session is reported again every poll; the stamp is the native's own exit time where the machine read one (correcting an earlier report's guess), else the time this session's first report about that same process earned, and a report about a different, older process is dropped whole rather than overwriting newer evidence, so later activity can still supersede it. sessions.list.native_process exposes the observation until a later heartbeat, tool call, or episode start supersedes it, and drops it when the evidence measured exit_code 0 under a session that declared a wait — parked, turn_posture waiting whose turn_posture_at is present and not before the current episode_started_at, or holding an item armed in the merge queue — because a finished headless command is that wait rather than a disappearance; a non-zero exit and an exit nobody measured still read as gone. The offer_envelope column retains checkpoint and skip-memory telemetry.
Chain checkpoint: The checkpoint API persists handler outcome and work identity for recovery and telemetry. Terminal closeout consumes a matching checkpoint. Checkpoint budget does not block session ending; idle cleanup preserves active claims, document locks, keepalive holds, and pending launch or wake delivery. Read through yoke sessions checkpoint-read.
Probe sessions are audit rows. A probe has ended within PROBE_MAX_LIFETIME_SECONDS (30) of offered_at, has tool_call_count = 0, and has no first_user_prompt_at stamp. The stamp is written on the session row at the prompt boundary. Probe rows remain in harness_sessions for audit; operator-facing session lists and steering counts exclude them through the shared session_probe predicate. Live sessions are never classified as probes.
Indexes: idx_harness_sessions_level(execution_level), idx_harness_sessions_heartbeat(last_heartbeat).
Shell access: the Python harness-session CLI (begin|touch|end|get|list|stale|reclaim). API: /v1/sessions endpoints.
Table: work_claims
Tracks active harness-session occupancy through one canonical target pair: target_kind names the target vocabulary and scope stores the exact kind-specific JSON object. Claims with released_at IS NULL are active.
- Item (
target_kind='item'):scope={"item_id":N}. - Epic task (
target_kind='epic_task'):scope={"epic_id":N,"task_num":N}. - Process (
target_kind='process'):scope={"process_key":K,"conflict_group":G}. STRATEGIZE and FEED sharestrategy-control-plane:<project>and therefore conflict. - Steering (
target_kind='steering'):scope={"project_id":N}. There is one live session-owned steering seat per project. Its document lock remains instrategy_doc_claims, associated byowner_kind='session'plusowner_session_idandproject_id; work-claim scope stays project-only.
Domain validation requires exactly the keys for the named kind. Storage has no specialized target, typed-owner, or registration-provenance columns.
id INTEGER PRIMARY KEY
session_id TEXT NOT NULL -- FK to harness_sessions.session_id
target_kind TEXT NOT NULL CHECK(target_kind IN ('item','epic_task','process','steering'))
scope TEXT NOT NULL -- canonical JSON object; exact shape is validated by target_kind
claim_type TEXT NOT NULL DEFAULT 'exclusive' CHECK(claim_type='exclusive')
claimed_at TEXT NOT NULL
last_heartbeat TEXT NOT NULL
released_at TEXT
release_reason TEXT -- completed, released, reclaimed, handed_off, expired, session_ended
reason TEXT -- verbatim acquisition rationale
reason_intent TEXT -- canonical acquisition intent
release_reason_intent TEXT -- caller's release intent
Indexes: idx_work_claims_session(session_id), idx_work_claims_session_released(session_id, released_at), and idx_work_claims_heartbeat(last_heartbeat).
Active-claim exclusivity invariants — these partial unique indexes, each scoped to released_at IS NULL so historical released overlap rows remain queryable evidence:
idx_work_claims_active_item ON work_claims(scope) WHERE released_at IS NULL AND target_kind='item'.idx_work_claims_active_epic_task ON work_claims(scope) WHERE released_at IS NULL AND target_kind='epic_task'.idx_work_claims_active_process_conflictindexesscope.conflict_groupwhere the process claim is active.idx_work_claims_active_steering ON work_claims(scope) WHERE released_at IS NULL AND target_kind='steering'.
The item and epic-task indexes are the authoritative storage-level prevention layer for concurrent writers from separate database connections; the application-level WHERE NOT EXISTS check inside claim_work remains in place for readable holder lookups, but the partial unique indexes are what guarantee two writers cannot both leave unreleased active rows for the same work unit. A losing concurrent writer surfaces as SessionError("ALREADY_CLAIMED") with the winning session id preserved in the message.
A steering seat covers a scope, not a project. Its scope is {"project_id": N} for a whole project, or {"project_id": N, "document": "SLUG"} for one strategy document inside it. Two seats coexist unless their scopes overlap: two documents in one project are two seats, while a project seat overlaps only CURRENT-PLAN document steering of that project. A refusal names the holder's actor, machine, and session, and points at both yoke claims steering list --project P --active-only and taking a seat on a different document. Steering is therefore the one kind whose exclusivity is not an index — two overlapping scopes are different JSON objects, so no unique index on scope can reject the pair. Its storage-level layer is the SELECT ... FOR UPDATE on the project row that acquire takes before it evaluates overlap, which serializes every steering acquire in one project; the index on scope remains as the narrower guarantee that one exact scope has one live row. A project seat covers unlinked items and its CURRENT-PLAN members; a document seat covers exactly the items linked to that document in item_strategy_docs — the link strategy.execution.link writes, and the one items.create writes when intake names a strategy_doc. Membership is read live, so a link written after a message was sent still decides which seat the message is now the business of. The fleet report for a document seat lists only its items; delivery-plane and machine facts (unregistered launches, launchable surfaces, plan limits) stay project-wide because a launch with no bound session has no item to attribute and machines are shared by every seat on them. Steering acquisition locks the project row, then creates the seat and, for a document seat, that document's lock in one transaction. A document conflict rolls back the seat. A project-wide seat may lock a standing plan with --plan-doc; without that flag it locks no document. Releasing one seat releases only its paired document lock, even when the session holds another seat in the same project. Steering release and stale-session reclamation release each pair together, while direct release of the paired document is refused until the seat leaves.
Shell access: item/process targets use yoke claims work; steering uses yoke claims steering acquire --project P [--doc SLUG | --plan-doc SLUG] [--reason TEXT] (--doc narrows the seat to that document's linked items; --plan-doc locks the standing plan while the seat covers the whole project; neither flag covers the project and locks nothing), list [--project P] [--active-only] (which names each seat's scope, holder actor, and machine), and release CLAIM_ID --reason TEXT. All dispatch through /v1/functions/call. claims.steering.list answers for one project, so a caller asking which seats exist across every project it can see reads sessions.steering_groups.list (yoke sessions steering-groups list): no inputs, one row per live seat, carrying the seat's session id under the same steering_group_session_id name the session roster spells it on every row that seat covers. Visibility is deliberately the OR the roster applies, because a session steers a project it did not start in — a seat is visible when the project it steers is visible, or when the session holding it lives in a visible project. yoke_core.domain.sessions_steering_groups_read.live_steering_group_session_ids is the read, over the same steering_scope_coverage.live_steering_claims rule the roster's own projection uses, so a released claim and an ended holder are seats in neither. Its one consumer today is the app's card tint, which ranks groups by sorted seat id and reads nothing else; reaching that through the roster cost a second complete roster per mount — claims, holdings, delivery, presentation — for one repeated field.
Steering fleet report
What a steering session cannot see from inside its own turn: available work, quiet claim holders, and four failures that arrive as silence, composed server-side and appended to the messages that session already receives. See steering-fleet-report.md.
Live claim-holder lookup
The canonical recipe for "which session currently holds the work claim on PREFIX-N?" is the registered read (function id claims.work.holder_get):
yoke claims work holder-get PREFIX-N
It returns the active work_claims row (released_at IS NULL) — claim_id, holder session_id, target_kind, scope, claimed_at, and last_heartbeat — in one call. Item lookup matches target_kind='item' plus canonical scope={"item_id":N}; do not query removed specialized or owner columns. The same recipe is the canonical example in the generated agent context packet (yoke_core.domain.schema_api_context, topic claims).
Inside the Yoke source repo only, the in-tree python3 -m yoke_core.hooks.sessions_cli who-claims <item-id> helper additionally joins the owning harness_sessions row (surfacing executor and mode) and accepts --current-episode. That module is not importable from an installed Yoke, so it is an operator/debug recipe for this repo, never a portable one.
work_claims is the active session occupancy primitive — including which session currently steers a project or strategy-doc scope. It is NOT path/file ownership truth (that lives in path_claims) and NOT a dangerous shared-operation lock (that is the sticky coordination kinds below). Process path claims attribute back to their owning process work-claim through path_claims.owner_work_claim_id.
Shared-operation coordination claims
Four work_claims target kinds coordinate a resource that is not a unit of backlog work. They live in the same table as every other claim, so one system carries session binding, heartbeat, telemetry, and the board's Claims column for every hold.
| target_kind | scope | Coordinates |
|---|---|---|
migration_serialization |
{"project_id":N,"model":M,"item_id":N} |
Migration territory for one model, owned by the authoring item |
qa_admission |
{"machine_id":ID} |
One physical test machine, globally |
route_qualification |
{"project_id":N,"grant_key":K} |
One private-route qualification grant |
deploy_serialization |
{"project_id":N,"project_slug":S} |
Every deployment run for one project |
Each has a unique partial index over its exclusivity unit, so a second holder is refused at the database rather than by a read-then-write race. migration_serialization conflicts on (project_id, model) — the item_id in scope records who owns the hold, not what is held, which is what lets the same item re-enter and heartbeat while any other lane is refused. deploy_serialization conflicts on project_id alone for the same reason: the slug rides in the scope so the operator key renders without a database read, and renaming a project must not hand out a second live deploy lock. qa_admission has no project in scope on purpose: a physical machine is one resource whichever project drives the run.
Stickiness is the property that separates these kinds from the rest. migration_serialization, qa_admission, and deploy_serialization are sticky: the stale-session sweep, the session-end release, and the claim-free end check all skip them, because the migration, the remote suite, and the deployment pipeline keep running after the session that started them goes quiet. Recovery is the audited human operator release, never an automatic reclaim. route_qualification is liveness-bound like the backlog kinds — a grant is only valid while its operator session lives — so the sweep reclaims it normally.
Each claim is addressed by one operator key: LIVE_DB_MIGRATION:<model>, QA_HOST:<machine>, DEPLOY:<project-slug>, and the qualification grant token. QA_HOST keys are machine-scoped: list with yoke coordination-claim list --key QA_HOST:<machine> --active-only --json, ignoring any project filter. Signed-in humans outside harness sessions recover a reviewed row with yoke coordination-claim release --key QA_HOST:<machine> --claim-id N --holder-session-id S --reason R; permission remains the holder's project claims.release. Other key kinds still require --project P for operator release. QA host holders can release by claim id with yoke claims work release --claim-id N --reason TEXT after run completion; requirement reads and artifact uploads retain the stored member's project authority after membership ends.
deploy_serialization is the one kind an ordinary workflow takes and releases by hand: creating and executing a deployment run both refuse without it. Its operator surface, refusal shape, and terminal-caller recovery live with the runs it gates, in events-and-deployments.md.
BOARD.md Claims column rendering
The Active Harness Sessions and Recent Sessions tables share one Claims column that renders all three primitives as keycap entries. The shapes:
| Primitive | Active shape | Example |
|---|---|---|
| work_claim (item) | PREFIX-N |
PREFIX-N |
| work_claim (epic task) | PREFIX-N T### |
PREFIX-N T008 |
| work_claim (process) | ⚙ <process_key> |
⚙ FEED |
| work_claim (steering) | 🛞 steering <project> · <documents> |
🛞 steering yoke · CURRENT-PLAN (a project seat holds no document and renders 🛞 steering yoke) |
| work_claim (other kind) | <kind>:<compact-scope> |
future_kind:{"k":"v"} |
| uncovered strategy_doc_claim | 🛞 <project> · <document> |
🛞 yoke · MISSION |
| work_claim + same-item path_claim decoration | PREFIX-N 📁<total> |
PREFIX-N 📁23 |
| path_claim orphan | 📁<total> (PREFIX-N) |
📁5 (PREFIX-N) |
| path_claim process anchor | 📁<total> (⚙ process_key) |
📁3 (⚙ FEED) |
| coordination claim | 🔒 <key> |
🔒 QA_HOST:mac-mini-lab, 🔒 DEPLOY:yoke |
| coordination claim (item-owned) | 🔒 <key> (PREFIX-N) |
🔒 LIVE_DB_MIGRATION:primary (PREFIX-N) |
Rules: same-session multiple path_claims on the same item roll up into one keycap with the summed declared-path total; coordination claims never decorate work_claims (they stay 🔒 keycaps and are omitted from the work-claim list so they do not also render as ?); ordering inside a row is work_claims → uncovered document locks → orphan path_claim keycaps → coordination claims. A steering seat folds in document locks from the same project: current seats name current locks, while released seats name released locks whose hold windows overlapped. Project id is part of the lock key, so same-named documents in two projects remain separate rows; a lock with no matching seat keeps its own keycap. A seat without a lock renders no doc lock. During a server/client rollout, an older recorded board payload without the pairing read keeps the seat and lock as separate rows instead of failing the render. Repeat work claims on the same rendered target and repeat coordination claims on the same key each collapse to the most recent row (one keycap). Steering occupancy is this column, not a separate Steering section. Release reasons are not rendered on Claims — drill into claim detail surfaces for audit history. Released path_claims and coordination claims do not appear on active-session rows. Per-file enumeration is intentionally out of scope — operators drill into per-file detail via path-claims list --item PREFIX-N.
Scheduling and registered identity
compute_schedule() remains the shared scheduler for charge, steering, board, fleet, and frontier reads. Explicit staffing uses pinned workflow bindings to choose the item's entrypoint. Registration stores provider-attested served model facts separately from requested model facts; a missing served value is never filled from the request.
Where each harness's served facts come from differs, and the context window differs most. Codex states its window outright in its rollout (turn_context.model_context_window). Claude states it in exactly one machine-readable place — the JSON it pipes to the configured status line command, as context_window.context_window_size — so Yoke renders a statusLine entry into .claude/settings.json that records the window for the next hook event to relay, and prints the model, window and usage in exchange for the slot. Claude allows one status line per session and hides most footer keyboard hints once any is configured; an operator who wants their own sets statusLine in .claude/settings.local.json, which overrides the project setting and gives up the attestation with it, leaving context_window_tokens NULL. Cursor states no window in any machine-readable surface — its conversation store records only modelName, its model listing spells the window in display labels, and the context model parameter in cli-config.json is the operator's selection rather than an attestation — so its window stays unattested by design. That per-harness map is data, not prose: yoke_contracts.session_context_window_sources carries it, and it also names which harnesses write the window separately from the model — Claude alone, which is why a Claude session keeps reporting a window after its model has settled and the expensive transcript reads have stopped. A session settles only once a hook carrying the served model has COMPLETED against the control plane — a relay that failed open or timed out carried it nowhere, so the facts stay unsettled and the next hook re-sends them. A NULL model therefore means not yet attested or not yet landed, never a client that stopped trying.
The same row also records what the session consumed, in usage_totals. Consumption is read from the same harness artifact and relayed by the same hook path as the served model, but no two harnesses state it in the same shape: Claude stamps a message.usage block on every assistant transcript row, splitting cache writes by cache lifetime and folding thinking into its output count; Codex writes one cumulative token_count rollout event whose input total already contains its cached input and whose output total already contains its reasoning; Cursor folds optional parent-turn stop token fields and print-mode result usage when present, otherwise unavailable for that surface. Each reader converts its source into the same disjoint billable buckets before anything is stored — input, cached_input, cache_write, cache_write_long, output, with reasoning kept as a labelled subset of output that is never added to a total — so one calculation prices every harness and no subset field is charged twice. That per-harness map is data rather than prose: yoke_contracts.session_usage_sources carries it, including Cursor's parent-turn source. One field of a stored document is deliberately not about counts: cost_caveat names why an estimate priced from those tokens is less certain than the tokens themselves — a session-wide total that more than one model served, or a model history holding a record that could not be read — so tokens a source stated exactly are never marked partial for a reason only pricing cares about. Read a stored document with yoke_contracts.session_usage_facts.usage_from_document, never as raw JSON.
Reading is incremental, bounded, and idempotent. A per-session watermark under the machine's Yoke home records how far into the artifact has already been folded, so a hook event parses only what the artifact gained since the last one, and the value relayed is always the absolute total rather than a delta — a resend, a retry, or a duplicate cannot inflate the stored figure, because the control plane replaces rather than adds. Bounded means the reader never holds the artifact: records stream through a fixed-size read and only accumulators survive each one — one observed rollout had grown to 1.9 GB, and reading its unread tail whole cost several multiples of it. A record past the reader's record bound is not simply skipped. Each transcript reader declares the fields it came for, and such a record is walked as a byte stream that keeps those scalars and forgets everything else, so a fourteen-megabyte compacted turn still states the tokens inside it while costing the memory of a handful of numbers. A scan that reaches its byte bound in the middle of a record finishes that record before it stops, because an offset left inside a record is an offset the next read resumes past — losing whatever it stated. What is genuinely lost is still named: a record whose own relevant statement could not be read makes the reading partial with that reason, a record that stated nothing this reader needed costs nothing however large it was, and a read that stopped short says only that it will resume. Each reader stamps into its stored record the version that produced it, so a reader that learns to recover what its predecessor could not re-folds that artifact exactly once — from zero and from empty totals where the fold accumulates, and from the totals it holds where the newest statement replaces them. The same resumable record also carries the served model, effort, and window Codex states across different rows at different moments, so those settle without a fresh whole-file scan per event, and Claude's model read takes a bounded window off the end of its transcript. A served fact is whatever the newest statement says, so a fold that has not yet reached the artifact's end attests nothing rather than the historical value it is holding — a session settles the moment its model lands, and a stale model that settled would be the model it reports for the rest of its life. Settling ends the reads, so a session whose fold is knowingly behind reopens them: every hook resolves again until the fold catches up, rather than advancing one bounded read per user prompt while an already-shipped model goes stale. A record is replaced atomically and one fold runs at a time per session and reader: a checkpoint read during its own rewrite would otherwise report offset zero and replay a whole history, and two folds racing would let the older offset land last. A hook arriving mid-fold answers from the totals already persisted instead of scanning the same bytes again. One Claude response is written as several rows repeating the same usage, so the message id dedups them; a truncated or rotated artifact rereads from the start and reports the reading partial with the earlier history named as gone. A reading that measured nothing never overwrites one that did, and a harness that counts no tokens stores unavailable with its reason rather than a zero. One shape reaches the control plane without a hook. A print-mode cursor-agent turn states its tokens only in the result object it writes to stdout as it exits, after the turn's last hook has already run, so the machine that started that native folds the result from the native's own settled capture instead. A result is one JSON object, so a capture read while it was still being written folds nothing rather than half a turn, and the fold dedups on the result's request_id, so a re-read counts the turn once. The reading then rides the same report that proves the process gone (session_control.relay.liveness) and is stored under the identical never-unlearn rule, whatever that report goes on to decide about the session — a session kept alive by a claim it holds consumed those tokens just as surely as one that ends. Attribution is custody rather than any identity printed inside the result: the machine started that native for that session.
Dollar cost is not stored. It is derived at read time from the shared model reference by yoke_contracts.session_usage_pricing, using the immutable catalog revision effective at the session's initial offered_at, so later publications do not reprice earlier sessions (including after reactivation), and an unknown model or unpriced bucket yields a partial or unavailable estimate that names the gap rather than a confident zero, and a rate the reference labels an estimate is used and then named in the same reason, so an inferred figure never reads as one the provider published. Missing or stale prices never block raw capture. The figure is an API-equivalent estimate: a session run under a subscription plan spends that plan's own meters, no published conversion turns those meters into dollars, and every surface that shows the number says so. Roster readers consume the derived usage_tokens / usage_status / usage_cost_usd / usage_cost_status / usage_note fields rather than the stored column. Machine-card 24-hour usage window: a machine card's usage tile answers for a durable window rather than whatever sessions happen to be loaded on the page it renders on — sessions.list with usage_last_24h: true (mutually exclusive with every other sessions.list filter) returns one row per session (session_id, machine_id, and the same derived usage_* fields the live roster carries) for every session in the window's cohort, scoped to the caller's authorized projects via actor_visible_project_ids, unpaginated. The cohort is the union of two halves, because either alone tells a half-truth about a machine that is busy right now: sessions that ENDED inside the trailing 24 hours, and sessions that STARTED inside it (offered_at, the same stamp the roster renders as when a session started) and have not ended. Ended and not-ended are exact complements, so the union counts each session exactly once, and a still-open session that started before the window is deliberately out — its cumulative usage is not a reading of the last 24 hours. yoke_core.domain.sessions_history_read.read_recent_session_usage_by_machine is the read, dating a killed session by ended_at_sql (falls back to terminated_at) so a session ended_session_sql already counts as ended is not dropped for lacking ended_at, excluding probe rows through the same not_probe_session_sql predicate every operator-facing session read applies, and excluding any session with no machine_id; the UI sums the rows client-side per machine_id on the shared machine-card renderer (appendMachineUsage in universe_machines_usage.js), where each metric label carries the window itself (24H TOKENS, 24H API COST, 24H SESSIONS) instead of a separate heading above the figures, fetched once per machines-panel load/redraw rather than per session-roster filter change.
DB Reference — QA, Release, and Session Tables