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)
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. Enforced by CHECK constraint (same pattern as reviews).
Indexes: idx_qa_requirements_item(item_id), idx_qa_requirements_epic(epic_id, task_num), idx_qa_requirements_deployment(deployment_run_id)
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, remote-browser
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 TEXT NOT NULL DEFAULT 'yoke' -- project scope
created_at TEXT NOT NULL -- app-supplied ISO-8601 UTC; see "Timestamp discipline" below
UNIQUE(item_id, version, project)
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 offering themselves to Yoke for work assignment. Identity fields align with the session-offer contract. Sessions with ended_at IS NULL are considered active. The stale-session sweep uses activity recency plus the session's active holdings to select its reclaim threshold.
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 a session holding any of those three active resources. It prevents a long foreground command from losing its claim or lock merely because no tool-boundary heartbeat landed.
Resolver: yoke_core.domain.sessions_analytics_core owns both source thresholds, yoke_core.domain.session_cleanup_holdings.effective_cleanup_ttl selects max(short, holdings) when the session has active holdings, and the sessions-card stale-eligible badge reads that same effective TTL. Downstream documentation should cite the config keys above by name rather than the current literal values — values may shift; the key names are stable.
Long commands and sparse tool boundaries. Registered Command cases and watcher-backed suites run through yoke_core.tools._watch_runner.run_watcher, which refreshes the owning session and active claims while the child runs. A generic foreground command may not pass through that watcher or reach another tool boundary before the short TTL. The holdings tier is the sweep-side safety net for that silent interval; a crashed holder still becomes reclaimable after the longer configured window.
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_lane TEXT NOT NULL DEFAULT 'primary' -- lane identity; path eligibility comes from lane_paths_<lane> config
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 -- full offer envelope JSON (optional; includes supported_paths, max_chain_steps, chain_checkpoint)
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, 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 stores the full session-offer JSON including supported_paths (list of canonical downstream path names the session can execute), max_chain_steps, and the persisted chain_checkpoint. When supported_paths is non-empty, the decision engine validates the required path against it and returns escalate with escalate_reason: "unsupported_path" if the path is not supported. See .yoke/docs/reference/session-offer.md for the path derivation mapping.
Chain checkpoint: After each /yoke do mode handler returns, a chain_checkpoint key is written into offer_envelope via update_chain_checkpoint(). This persists the post-handler state (step, action, chainable, handler_outcome, item_id, task_num, status, required_path, completed_at) so that Step C of the loop can consult durable state rather than prompt-local variables when deciding whether to re-offer. When that item reaches a terminal workflow stage, post-commit closeout marks the matching checkpoint terminal_item_closed: it remains chainable for a live loop's next offer, but no longer blocks an empty session's final hook, and the same handler's final checkpoint write preserves the consumed outcome. The same envelope's max_chain_steps value lets normal session-end reject premature cleanup with CHAIN_PENDING; --force / force=true does not bypass that guard. The explicit chain-end override flag plus a non-empty rationale is required and emits ChainDeclineOverridden. Sessions holding unreleased claims stay active until the claim lifecycle releases them, the stale-session cleaner (yoke sessions reclaim-stale --confirm) reclaims them, or a human explicitly uses python3 -m yoke_core.api.service_client claim-release. Read via read_chain_checkpoint() or the session-checkpoint-read CLI command.
Probe sessions are audit rows, not roster rows. Opening Claude Desktop, or activating the VS Code extension, spawns a harness process that registers a session, sends no prompt, calls no tool, and ends about a second later — one "New" click produced three session rows where one conversation existed. A session that ended within PROBE_MAX_LIFETIME_SECONDS (30) of offered_at with tool_call_count = 0 and no first_user_prompt_at stamp is such a probe. That stamp is written on the session row at the prompt boundary itself, so a real conversation answered inside the window stays a conversation; reading the absence of a telemetry event instead used to turn one into a probe retroactively, the moment the event expired. Its row stays in harness_sessions for audit, and every operator-facing session read excludes it through the one shared predicate in yoke_core.domain.session_probe: the Sessions page and the Overview sessions band (both served by sessions.list) and the steering fleet report's session counts. The predicate requires the session to have ended, because a live session that has done nothing yet is one that has not done anything yet.
Indexes: idx_harness_sessions_lane(execution_lane), 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 and any document seat inside it are the same territory and refuse each other. 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 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 locks no document. Steering release and stale-session reclamation release the 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.
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. The key is the only handle an operator needs.
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.
Session Offer
The session-offer endpoint (POST /v1/sessions/offer) accepts a session-offer payload, computes the shared scheduler result from the DB, and calls the pure decision engine (decide_next_action() from session.py) to determine the next action for the offered session. The response is a NextAction JSON object.
Scheduler computation (compute_schedule() in scheduler.py) delegates frontier classification and ranking to frontier.py, resolves next_step from each item's pinned workflow skill binding, honors implementation WIP eligibility when selecting the assignable step, evaluates work_claims for claim state, and probes truthful SML coherence/staleness across the MISSION, VISION, MASTER-PLAN, and LANDSCAPE views rendered under .yoke/strategy/.
The service_client.py session-offer command calls compute_schedule() directly (direct DB access, not via HTTP) for shell-accessible use.
API: POST /v1/sessions/offer. Service client: python3 service_client.py session-offer --executor E --provider P --workspace W [--lane L] [--session-id S] [--model M].
Yoke-owned /yoke do callers omit --model; the service client reads the session row by session_id. That row keeps two different facts apart: model / reasoning_effort / context_window_tokens hold only what a provider attested it served (NULL until an attestation reader proves one), while requested_model / requested_reasoning_effort / requested_context_window_tokens hold what the session was launched asking for. Those requested columns are filled from two directions and stamped once: the session's own environment at registration, and the launch record at launch binding for a launched session — a harness that serves a launch from a pre-warmed process pool hands it to a process older than the launch, which can read no ask of its own. A later write fills a gap and never rewrites a stated ask. The offer reports both, and a surface showing the request where the served value is NULL must label it as a request. The optional --model flag remains for low-level adapter diagnostics that intentionally need an explicit override.
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. 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, only accumulators survive each one, a single record larger than the reader's record bound is skipped, and an invocation stops at its byte bound and resumes at the next event — one observed rollout had grown to 1.9 GB, and reading its unread tail whole cost several multiples of it. Neither bound is silent: a skipped record and a read that stopped short each make the reading partial with that reason. 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, so refreshed prices apply without rewriting any history, 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.
DB Reference — QA, Release, and Session Tables