Skip to main content

API Changelog

The bossanova.v1 OrchestratorService (see the API Reference) uses Stripe-style, date-based versioning. Each dated version pins a stable response shape: when the server starts serving a new value or behavior, it also registers a down-convert transform so callers pinned to an older version keep receiving the shape they were built against.

The Bossanova-Version header​

Clients select an API version by sending a Bossanova-Version request header containing a YYYY-MM-DD identifier (for example Bossanova-Version: 2026-07-11). The resolved version is echoed back on the response.

  • A request that negotiates the Current version runs zero transforms.
  • A request pinned to an older version has the ordered chain of down-convert transforms applied so it observes the behavior that shipped with its version.
  • A header-less caller pins to the Baseline (the oldest supported version), so it never silently shifts onto newer behavior. This matches the Stripe intent of "pin to the version you started on."

The current version is 2026-09-24. The engineering procedure for adding a new version and its transform lives in the repository at docs/api-versioning.md (view on GitHub).

Versions​

Listed oldest to newest. Every version below is a member of the production registry served by the OrchestratorService.

2026-06-29 — Baseline​

The launch baseline and the oldest supported version. It is the version assumed for header-less callers. No transforms run against a Baseline-shaped response beyond those introduced by later versions.

2026-07-04 — Orphaned session state​

The OrchestratorService began serving SessionState value SESSION_STATE_ORPHANED on Session.state, a terminal state for a headless run that was killed by a daemon restart. Clients pinned to an older version, which were built before this value existed, are down-converted to SESSION_STATE_IMPLEMENTING_PLAN, the prior observable behavior.

2026-07-05 — Agent auth-failed attention reason​

The OrchestratorService began serving AttentionReason value ATTENTION_REASON_AGENT_AUTH_FAILED on Session.attention_status.reason, a new attention reason surfaced when an agent's pane shows the login-required terminal shape (for example "Not logged in" / "Please run /login"). Older clients, which never saw this reason, are down-converted back to no attention reason.

2026-07-06 — Unmanaged account label and limited chat status​

Two transforms shipped in this version:

  • Unmanaged account label — restores the prior "System default" account label for older clients where the server now serves "Unmanaged local credentials" for the unbound rotation account.
  • Limited chat status — maps CHAT_STATUS_LIMITED and the derived "usage-limited…" session display shape back to the prior idle-style behavior for older clients.

2026-07-11 — No-eligible-account rotation outcome​

The OrchestratorService began serving RotationOutcome value ROTATION_OUTCOME_STATUS_ONLY_NO_ELIGIBLE_ACCOUNT on Session.rotation_events[].outcome, distinguishing "no active account to rotate to" from the prior "agent cannot rotate". Clients pinned to an older version are down-converted back to ROTATION_OUTCOME_STATUS_ONLY_NO_CAPABILITY, the prior observable value.

2026-07-18 — Errored session display recolor​

The OrchestratorService began serving the errored-recolor display shape for orphaned/blocked sessions on Session.display_label / display_intent / display_spinner. An errored session now keeps its real underlying status label and spinner (a live "working" spinner, a pending "? question") but has its intent recolored to DISPLAY_INTENT_DANGER so the error stays visible; a legitimate terminal muted PR ("✓ merged" / "closed") is exempt and stays muted. Clients pinned to an older version were built against the prior shapes (a fixed "orphaned"/DANGER/no-spinner tuple for orphaned sessions and an un-recolored base cascade for blocked sessions), so they are down-converted back to those.

2026-07-23 — Respawn-in-place rotation outcomes​

The OrchestratorService began serving two RotationOutcome values on Session.rotation_events[].outcome: ROTATION_OUTCOME_RESPAWNED_SAME_ACCOUNT, audited when a pane that failed authentication is stopped and respawned in place under the same account (the account itself probes healthy), and ROTATION_OUTCOME_RESPAWN_CAP_EXHAUSTED, audited when the per-chat respawn-in-place budget for the window is spent. Clients pinned to an older version are down-converted back to ROTATION_OUTCOME_STATUS_ONLY_NO_CAPABILITY, the prior observable value.

2026-08-03 — Agent-stalled attention reason​

The OrchestratorService began serving AttentionReason value ATTENTION_REASON_AGENT_STALLED on Session.attention_status.reason, a new attention reason raised when a chat reports CHAT_STATUS_WORKING while its agent has made no semantic progress for longer than its phase's threshold, a silently dead turn behind a still-animating spinner. Like the auth-failed reason it is only raised where the session had no other attention, so clients pinned to an older version are down-converted back to no attention reason, the prior "keeps spinning" behavior.

2026-08-04 — Waiting chat status​

The OrchestratorService began serving ChatStatus value CHAT_STATUS_WAITING on ChatStatusEntry.status, ChatStatusDelta.status and SessionStatusEntry.status, alongside a new waiting_reason string on each of those messages. A chat is waiting when it is blocked on an external event (a registered GitHub callback, a background poll tick) rather than computing, and waiting_reason carries a short human-readable explanation such as awaiting checks_passed_ready on owner/repo#123 (it is empty for every other status). Before this version such a chat reported CHAT_STATUS_WORKING, so clients pinned to an older version are down-converted back to CHAT_STATUS_WORKING with an empty waiting_reason.

2026-08-12 — Draft-PR failure ranks below live activity​

The OrchestratorService stopped letting a session-level draft-PR-creation failure claim the session's primary display composite while a chat is live. The ? PR failed label used to outrank every branch except a pending question and a usage-limit banner, so a row could read ? PR failed while the chat underneath it read working, two contradictory presents on one row. That branch now sits immediately below the working branch, so Session.display_label, Session.display_intent and Session.display_spinner carry the live label (working, waiting, initializing, merging, archiving) whenever one applies, and still report ? PR failed when nothing is live. Clients pinned to an older version were built against the previous precedence, so they are down-converted back to ? PR failed.

The failure itself is not hidden from a current client: it is surfaced as a session warning hint, and that hint is deliberately exempt from the accompanying recessive treatment because it is the row's only carrier of the failure.

2026-08-16 — A cron gate that could not run is no longer reported as gated​

The OrchestratorService began distinguishing a cron gate that could not be evaluated from one that ran and decided there was no work. Previously every blocked fire — a gate that timed out, could not be launched, or that the shell reported missing (exit 127) or not executable (exit 126) — was recorded as gated and derived CRON_JOB_STATUS_GATED, a warning-styled "waiting, healthy" value. A broken deployment therefore looked exactly like a quiet backlog.

A gate that could not run now serves CronJob.last_run_outcome gate_failed and derives CronJob.last_run_status CRON_JOB_STATUS_FAILED, and RunCronJobNow returns the matching gate_failed skip reason. A gate that ran and exited non-zero for any other code keeps gated / CRON_JOB_STATUS_GATED, and still blocks the fire. No new CronJobStatus enum value was added; the new outcome reuses the existing CRON_JOB_STATUS_FAILED, so this is a change in the value served rather than in the schema.

Clients pinned to an older version were built against the previous values, so they are down-converted back to outcome gated, status CRON_JOB_STATUS_GATED, and skip reason gated.

2026-08-20 — A session account switch stopped by its own deadline is no longer reported as ABORTED​

The OrchestratorService began distinguishing a session account switch that ran out of time from one that failed for an unknown reason. Previously the daemon had no wire value for "a deadline stopped this", so a switch ended by its own daemon-side respawn budget came back with no typed error code and ProxySwitchSessionAccount answered ABORTED, the historical fallback for an unmodelled failure.

ABORTED was not merely vaguer; it was actively misleading. It invites a retry, and a request killed by its own deadline is exactly the request a caller must not retry: re-sending restarts a budget that has already proven too short, and on an at-least-once command stream it can stack a second switch on top of work the first one may have partially completed.

A switch ended by its daemon-side budget now answers DEADLINE_EXCEEDED. A new CommandResult.ErrorCode value, ERROR_CODE_DEADLINE_EXCEEDED, carries the distinction over the daemon stream. No procedure or message field was added; this is a change in the status code served on an existing procedure rather than in the schema.

The change is scoped to the account switch alone. Other commands that fail with an expired context are unaffected and still answer ABORTED. ProxySwitchSessionAccount's own relay timeout (the orchestrator giving up waiting for the daemon) has always answered DEADLINE_EXCEEDED and is unchanged at every version, including for older clients.

Clients pinned to an older version were built when this case read as ABORTED, so they are down-converted back to ABORTED with the failure message preserved.

2026-08-21 — Switch cancellation and result-ceiling behavior​

The OrchestratorService began distinguishing a session account switch that the caller canceled from one that failed for an unknown reason. Previously the daemon had no wire value for "the caller canceled this", so a canceled switch came back with no typed error code and ProxySwitchSessionAccount answered ABORTED, the historical fallback for an unmodelled failure.

ABORTED invites a retry, which is the wrong signal for a request the caller abandoned. Retrying can stack duplicate switch work on top of the partially completed first attempt.

A switch canceled by its caller now answers CANCELED. A new CommandResult.ErrorCode value, ERROR_CODE_CANCELED, carries the distinction over the daemon stream. No procedure or message field was added; this is a change in the status code served on an existing procedure rather than in the schema.

The change is scoped to the account switch alone. Other daemon-scoped commands that return the same typed code still answer ABORTED, and ProxySwitchSessionAccount's own orchestrator-side cancellation path is unchanged for every version.

Clients pinned to an older version were built when this case read as ABORTED, so they are down-converted back to ABORTED with the failure message preserved.

ProxySwitchSessionAccount began using self-describing timeout text when bosso's own 120-second result ceiling expires before a daemon verdict arrives. The status code remains DEADLINE_EXCEEDED, but current clients now see that the account switch may still be running, and that the request did not cancel or tear down the switch. The caller should re-check the chat's account to see where it landed.

Previously this handler-owned result ceiling used the generic relay timeout text: command timed out after 2m0s. Clients pinned to an older version were built against that message, so they are down-converted back to the legacy text while keeping DEADLINE_EXCEEDED.

2026-08-25 — Head-current check state​

Session.last_check_state now serves only a CI verdict demonstrated at the current PR head SHA. Stale, missing, or non-demonstrated observations serve CHECKS_OVERALL_UNSPECIFIED, while the raw persisted latch moves to Session.last_check_state_observed with last_check_state_head_sha and last_check_state_at provenance.

Clients pinned to an older version are down-converted so last_check_state again equals last_check_state_observed, matching the prior behavior that could surface a stale verdict as if it still described the current head.

2026-09-02 — Organization-scoped visibility​

The API surface now includes organization management messages and nine organization RPCs on OrchestratorService: list, get, create, and rename organizations; list, invite, remove, and role-edit members; and switch the active organization.

The organization visibility cutover is intentionally handler-gated rather than registered as a down-convert transform. Session, chat, repo, cron, notes, webhook, GitHub App, attach-token, daemon, and stream procedures whose caller-visible resource set changes must branch on the resolved API version: clients pinned before 2026-09-02 keep the legacy user-scoped view, while current clients observe organization-scoped visibility. The transform layer cannot express this because it sees only the procedure and payload, cannot inspect the caller, cannot turn a success back into NOT_FOUND, and cannot add or remove streaming frames.

2026-09-03 — SwitchActiveOrganization retired​

SwitchActiveOrganization is retired in favor of AuthKit switchToOrganization. The RPC and request/response messages remain in bossanova.v1 only for wire compatibility: buf breaking: use: FILE blocks deleting them from v1, and true removal requires bossanova.v2.

Current clients receive UNIMPLEMENTED with retirement guidance naming AuthKit switchToOrganization. Clients pinned to an older version are down-converted to the legacy organization management is not implemented message while keeping UNIMPLEMENTED.

2026-09-04 — Abandoned checkout no longer reports an activating subscription​

An account that opened a Stripe Checkout session and never completed it was previously reported as CLOUD_ACCESS_STATE_PENDING_ENTITLEMENT_REFRESH with the message "Your subscription is being activated." and can_create_checkout=false, which left clients polling forever for an activation that would never arrive.

Current clients receive CLOUD_ACCESS_STATE_NEEDS_SUBSCRIPTION with checkout_started=true, can_create_checkout=true, denial_reason="subscription_required" and a message inviting the user to resume checkout. Only an account that has actually paid (one awaiting its entitlement refresh) still reports the activating shape.

No field or enum member was added: the change is in the values served on CloudAccessStatus, carried on GetCloudAccessStatus, CreateCheckoutSession, and RefreshCloudEntitlements. Clients pinned to an older version are down-converted back to the activating shape.

One behavior change on this version is not down-converted, deliberately. CreateCheckoutSession against an account that had started a checkout but never recorded an idempotency key used to be refused; it now records a key and returns a hosted URL. A down-convert transform can only rewrite a response, never turn a success back into an error, and re-introducing the refusal for pinned clients would restore the dead end this version exists to remove. Pinned clients see the old status shape but the new checkout behavior.

2026-09-05 — Repository and daemon listings span every organization you belong to​

ProxyListReposAggregated and ListDaemons used to answer for a single organization, resolved from the caller's own session claim. A repository or daemon registered under another organization the caller belongs to was simply absent from the reply, and nothing in the response said so.

Current clients receive the union across every organization they belong to, while daemon-authenticated callers retain the single-organization inventory. Repository rows remain deduplicated by origin URL exactly as before, and daemon inventory remains acting-user scoped: organization membership never exposes another member's machine or hostname.

Two additions carry the change. The request gains an optional organization_id: set it to narrow the read to one organization (the previous behavior, now stated in the request rather than inherited from the claim), and leave it unset for the union. Each response gains failed_organizations, naming any organization whose read failed, so a partial answer is reported as partial instead of returned as a shorter list. An organization with no live machines is not a failure and is not listed. A narrowed read never reports partial results: one unavailable organization is the whole request failing. DaemonInfo also gains organization_id, allowing clients to attribute each inventory row.

Clients pinned to an older version keep the single-organization read and always see failed_organizations empty, because the path that fills it does not run for them. This is one of the changes that cannot be expressed as a down-convert transform (restoring "the one organization this particular caller's claim named" requires the request, which a response transform never sees), so the older behavior is served by the handler itself rather than by the transform chain.

2026-09-06 — Cloud access resolves across every organization the caller belongs to​

Cloud access used to be decided from the organization the caller claimed on the request alone. A user who belongs to several WorkOS organizations, whose subscription lives on one of them, was refused whenever they arrived claiming a different one: the same person, the same paid seat, a different claim.

Current clients have their access resolved across every organization they belong to: the claimed organization is judged first, and if it cannot grant access on its own the server folds over the caller's other member organizations and grants CLOUD_ACCESS_STATE_ACTIVE when one of them is entitled. Clients pinned to an older version keep the claimed-organization-only verdict.

No field or enum member was added: the change is in the values served on CloudAccessStatus, carried on GetCloudAccessStatus, CreateCheckoutSession, and RefreshCloudEntitlements.

This version registers no down-convert transform, deliberately. The verdict is caller-relative (the same CloudAccessStatus message is correct for one caller and wrong for another, depending on which organization earned it), and a TransformResponse receives only the method name and the response message, never the request. It therefore cannot tell an ACTIVE earned by the caller's own organization from one earned by a sibling. The compatibility seam is instead the handler-level version gate that docs/api-versioning.md names as the sanctioned escape hatch for changes the transform layer cannot express: the handlers serving these three RPCs restrict the caller to their claimed organization when the negotiated version resolves older than 2026-09-06.

2026-09-07 — Cloud access status names your organization​

CloudAccessStatus.workos_org_id is now populated with the WorkOS organization the caller is acting as. The field has existed since cloud subscription gating shipped, but nothing ever filled it, so every client that read it saw an empty string on every response.

Current clients receive the organization resolved for the request: the caller's verified organization scope, falling back to the org_id claim on the session. A daemon caller, which has no organization of its own, still receives an empty value. The field is populated on GetCloudAccessStatus, CreateCheckoutSession and RefreshCloudEntitlements alike, and it describes who asked rather than what the answer was, so it is present regardless of the access state.

This is what lets a command-line client send a refused cloud login to the organization it was actually refused for: it matches the id against the caller's organization list and opens the organization-scoped subscribe page instead of the unscoped one.

No field or message was added; only the value served changed, so clients pinned to an older version, which were built when this field could only be empty, are down-converted back to an empty workos_org_id.

2026-09-08 — Session commands route across every organization you belong to​

Session listings already span every organization the caller belongs to, but commands used to resolve their target only in the active organization. A caller could therefore see and open a session filed in another member organization, then receive NOT_FOUND when trying to stop or otherwise act on it.

Current clients retry a missed session-owner lookup across the caller's member organizations. The caller must already belong to every organization considered, and each candidate is rechecked against the authorized membership scope. Sessions held by non-member organizations remain indistinguishable from unknown session ids: both return NOT_FOUND, and neither dispatches a command.

Clients pinned to an older version keep the active-organization-only command routing behavior. This version registers no down-convert transform because the change turns an error into a success: an error transform never sees the success path, and a response transform cannot recreate the prior NOT_FOUND. The compatibility branch therefore lives in the handler.

2026-09-09 — Owner-resolution outages no longer look like empty results​

ProxyListSessions now distinguishes an unavailable session-ownership store from a session that is legitimately outside the caller's organization. A missing owner remains a silent, privacy-preserving skip. Any other ownership lookup failure returns UNAVAILABLE with a stable public message instead of a successful but silently shortened list; the underlying dependency error remains in server logs only.

Clients pinned to an older version retain the legacy successful short-list response. The server continues scanning after the failed lookup, so that legacy response contains every session whose ownership could still be resolved rather than stopping at the first outage.

2026-09-10 — Fleet reads span every organization you belong to​

An unset ProxyListCronJobsRequest.organization_id now returns the union across every organization the caller belongs to. Each row carries its owning organization, and failed_organizations makes a partial fleet read explicit. Setting organization_id still narrows the request to one authorized organization.

Clients pinned to 2026-09-09 or older keep the claimed-organization-only list. This compatibility behavior lives in the handler because reconstructing the organization named by a caller's claim requires request context unavailable to a response transform.

Seven additional fleet-wide reads now query the caller's own Ready daemons across every organization they belong to. Each request accepts an organization_id filter to narrow the read after a membership check, and each response reports organizations whose fleet could not be read. ProxyEmptyTrash additionally reports its deletion count per organization. Clients pinned to older versions retain the former single-organization behavior.

Repository lists now also fail explicitly when organization-holder enrichment is unavailable, instead of returning blank holder values that look like unclaimed repositories. Clients pinned to 2026-09-09 or older retain the legacy successful repository list without organization stamps for that failure. The response-aware compatibility transform recovers only the typed holder-store failure; other repository-list errors remain errors at every version.

2026-09-11 — Invitation lifecycle completion​

RemoveOrganizationMember now accepts invitation_id to revoke an organization-owned pending invitation. The operation uses an organization-scoped lookup before revoking, so another organization's invitation remains indistinguishable from an unknown id.

Clients pinned to 2026-09-10 or older keep the prior INVALID_ARGUMENT behavior. This compatibility boundary lives in the handler because a successful empty response cannot be transformed back into an error after the revoke side effect has occurred.

ListOrganizationMembers now returns an invitation-only row while WorkOS has accepted an invitation but its user has not yet landed as a local member. The row sets is_invite_accepted and invitation_id; local user and membership ids remain empty. Clients pinned to 2026-09-10 or older do not receive this row.

Membership reconciliation also corrects Stripe seat quantity after an insert or removal changes local membership cardinality. This best-effort billing repair is not version-gated: response transforms cannot undo an external Stripe write, and preserving a stale quantity would preserve the billing defect.

2026-09-12 — Session listings span every organization you belong to​

ProxyListSessions now returns the union across every organization the caller belongs to when organization_id is unset. Set organization_id to narrow the read to one organization after a membership check. Sessions remain attributed with organization_id, and failed_organizations reports any organization whose slice could not be read without discarding the rest of the union.

Clients pinned to 2026-09-11 or older keep the former single-organization read, resolved from their active organization claim. Header-less callers also keep that legacy behavior because an absent version header resolves to the Baseline. This compatibility boundary lives in the handler: a response transform cannot inspect the request or reconstruct the caller-relative claimed organization.

The deprecated ProxyListSessionsAcrossOrganizations RPC remains served for older clients, but new callers should use ProxyListSessions.

2026-09-13 — A healthy account check reports a superseded credential​

AuthCheck.failure_class now carries credential_superseded alongside outcome healthy. It means an ambient codex login for the same provider account holds a different refresh token, so the refresh chain behind the stored credential is dead even though the provider still accepts the stored access token. The account stays eligible, which is why the outcome remains healthy — the class is a warning about the future, not a failure.

Before this version a healthy check always carried an empty failure_class. Clients pinned to 2026-09-12 or older are down-converted back to that empty value on every response that carries an Account: ProxyListAccounts, ProxyManageListAccounts, ProxyAddAccount and ProxyRefreshAccount. A class on a non-healthy outcome is untouched — that shape predates this version.

2026-09-14 — Unproven credential refresh chain​

Account.auth_check.outcome began serving refresh_chain_unproven (with failure_class refresh_not_observed) for a credential check that completed with no provider error, on a credential whose own access token says a token refresh should already have happened, and whose run observed no credential write. That is a live access token sitting above a refresh chain nothing has proven still works: the check keeps passing until the access token expires, then fails hard with no prior warning.

It is a warning, not an accusation. auth_invalid remains the only outcome that removes an account from selection, and an account reporting refresh_chain_unproven stays selectable.

Before this version the identical clean run served healthy with an empty failure_class. auth_check.outcome is a plain string that clients switch on to choose a severity, so clients pinned to 2026-09-13 or older are down-converted back to that pair rather than being shown a token they were never built to read.

2026-09-15 — Sessions honour a manual list order​

Session listings are ordered by the manual list rank a user sets with the reorder shortcut: a ranked session sorts before every unranked one, and two ranked sessions sort by rank ascending. Sessions with no rank keep the order they have always had: newest first by created_at, then by id ascending. That rule still decides the whole list wherever nobody has reordered anything.

Session.list_rank itself is an additive field that shipped earlier and that nothing read. What changed at this version is the ORDER of the sessions field on ProxyListSessions (and its deprecated ProxyListSessionsAcrossOrganizations spelling). Repeated-field order is observable output for a list a client renders top to bottom, so a client that predates the manual rank would see its session list silently rearrange the first time any user pressed the shortcut.

Clients pinned to 2026-09-14 or older therefore have the session list re-sorted back to created_at descending, then id ascending. The down-convert restores the ordering rather than clearing list_rank: blanking the field would hide the cause while leaving the response in the server's order, which is not the order those clients were built against. list_rank stays populated, so a pinned client that later learns to read it needs no renegotiation.

The same version also changes a session's display status in one narrow case. A session whose chats have all gone idle while a callback stays armed used to render waiting, even when its pull request was green and ready to merge — the armed callback described nothing the row needed, while the PR label described the one fact an operator wanted. Such a session now serves its PR-derived label (✓ passing or ✓ approved) on Session.display_label, display_intent and display_spinner.

The rule is a conjunction and nothing wider. A session where any chat that resolved to waiting was actually mid-run still reads waiting, in every PR state, because waiting is the more specific truth about a chat that is working. So does a session whose PR is anything other than passing or approved — including ✓ review, which means review was requested, not that checks passed. And the demotion falls through the rest of the precedence cascade rather than jumping to the PR label, so an in-flight merge, archive or setup, an active workflow, a repair in progress and a failed draft-PR creation all still win.

Clients pinned to 2026-09-14 or older are served the previous waiting / INFO / spinner composite. Session.is_waiting_demoted marks a row whose PR label replaced a waiting one; the down-convert restores the label rather than clearing that mark, for the same reason the list ordering is re-sorted rather than stripped.

2026-09-16 — Verifying, Needs human and Ready statuses​

Session.display_status and Session.merge_block.display_status serve two new values, DISPLAY_STATUS_VERIFYING and DISPLAY_STATUS_NEEDS_HUMAN, with the matching verifying and needs human composite on display_label, display_intent and display_spinner. Both come from the boss/verify commit status the verify stage writes on a pull request head:

  • verifying (info, spinner) means ordinary CI has settled and a verification run has claimed the head, or the head is waiting for one.
  • needs human (warning, no spinner) means the verify stage parked the head for a person. The session also gets an ATTENTION_REASON_AWAITING_HUMAN_INPUT attention whose summary carries the park reason, unless it already has another attention.

Before this version a pending boss/verify counted as an ordinary pending check, so such a session read DISPLAY_STATUS_CHECKING / checking for as long as verify held the head, and a parked head never surfaced its reason. Failing CI, a conflict and still-running ordinary CI keep their existing statuses. Both new values report GATE_PENDING on merge_block.gate, the gate checking reported.

Clients pinned to 2026-09-15 or older receive DISPLAY_STATUS_CHECKING, the checking composite, Checking's merge-block detail, and no verify-park attention, on every session-bearing response including the streamed ProxyCreateSession created message.

The daemon also stops treating a pending boss/verify as unsettled CI when it decides a session's checks have passed, so the matching session transition and checks_passed callbacks arrive while verify holds the head. That is timing, not a response shape, and is the same for every version.

A settled session with a successful boss/build receipt on its current PR head serves the composite ✓ ready (success, no spinner). Ready replaces waiting or the green PR composite; live work, transient operations, failures, Verifying and Needs human retain precedence. Session.display_status and merge gating keep their existing PR-derived meaning. A new push clears Ready until the new head receives a receipt.

The additive transport facts Session.has_build_receipt and Session.is_ready_over_waiting are never persisted. Clients pinned to 2026-09-15 or older receive the preceding composite on session-bearing unary responses. The waiting inverse mark is stamped by list recomputation; a single-session read without that mark restores the underlying green PR label.

2026-09-17 — Session attention​

Session carries a new attention field, a SessionAttention that answers "what does this session need from you" in one place:

  • kind is one of ATTENTION_KIND_FIX, ATTENTION_KIND_QUESTION, ATTENTION_KIND_REVIEW, ATTENTION_KIND_MERGE (these four need you), ATTENTION_KIND_IN_PROGRESS or ATTENTION_KIND_DONE.
  • sentence says what to do, for example Review the migration, then approve.
  • actions lists the AttentionAction values a client offers for that kind, such as ATTENTION_ACTION_APPROVE or ATTENTION_ACTION_MERGE. A session linked to a tracker issue also offers ATTENTION_ACTION_TRACKER (open its tracker_url) last, whatever the kind.
  • progress_label describes work that does not need you (building, checking 3/7, waiting for main CI). Running checks first read CI 3/7; the label is free text, and the service now writes checking 3/7 at every version.
  • since, park_codes, alert_key, target_chat_id and evidence_url carry the facts behind the answer.

Session.attention_status is unchanged.

This version defines the field; the service starts populating it in a follow-up release. Until then, responses at 2026-09-17 carry no attention either, so treat an absent value as "not yet computed" rather than "nothing needed".

Clients pinned to 2026-09-16 or older receive no attention on every session-bearing response, including the streamed ProxyCreateSession created message.

2026-09-18 — Needs human outranks an idle wait​

A session whose head the verify stage parked for a person (DISPLAY_STATUS_NEEDS_HUMAN), and whose waiting chats are all idle on an armed callback, serves the needs human composite (warning, no spinner) with Session.is_waiting_demoted set. Before this version such a session served waiting (info, spinner), which read as checks still running while nothing was. A live verification (verifying), still-running CI and failing CI are unchanged.

Clients pinned to 2026-09-17 or older receive waiting on session-bearing unary responses. As for the 2026-09-15 waiting demotion, only list recomputation stamps the mark, so a single-session read serves the new composite.

The same version splits the verify park's composite by what it asks of the operator. A park whose codes are all policy sign-off codes (human-review:ui, human-review:security, human-review:database, always-human-path) serves needs sign-off (warning, no spinner). A park with any failure or incomplete code (no-receipt, ledger-open, extension-failed, repair-exhausted, no-session, human-review:unproven), an unknown code, or no code serves blocked (danger, no spinner). display_status stays DISPLAY_STATUS_NEEDS_HUMAN, and merge_block and attention_status are unchanged. Clients pinned to 2026-09-17 or older receive needs human (warning) on session-bearing unary responses.

The same version stops an armed callback from hiding live work. Only a chat idle at its prompt with an armed callback is CHAT_STATUS_WAITING; a chat that reports CHAT_STATUS_WORKING stays WORKING and keeps its waiting_reason on ChatStatusEntry, ChatStatusDelta and, when every working chat has one, SessionStatusEntry. Such a session's composite reads working where it read waiting, or ✓ ready over a receipted green PR, and carries the new transport-only Session.is_working_over_waiting (field 79). A Claude chat whose main loop is idle while a background subagent runs now also reports WORKING. Clients pinned to 2026-09-17 or older receive WAITING for a working chat that carries a waiting_reason (from GetChatStatuses, ProxyGetSessionStatuses and ProxyStreamChats) and the waiting or ✓ ready composite on marked session-bearing unary responses; as for the other marks, only list recomputation stamps it.

Available run resume actions​

Fix rows offer resume when the session meets restart requirements: archived and completed sessions, sessions without an original prompt, and sessions with an active human chat cannot restart. Clients pinned to 2026-09-17 retain the original fix action list; earlier clients continue receiving no attention field.

2026-09-19 — A blocked verify park needs a fix, not a review​

Session.attention answers a verify park by what it asks of the operator, the same split 2026-09-18 made for the display label. A park whose codes are all policy sign-off codes is still ATTENTION_KIND_REVIEW, offering ATTENTION_ACTION_APPROVE. A park with any failure or incomplete code (no-receipt, ledger-open, extension-failed, repair-exhausted, no-session, human-review:unproven) or an unknown code, alone or beside sign-off codes, is now ATTENTION_KIND_FIX. It keeps park_codes and evidence_url, offers ATTENTION_ACTION_SEND_BACK and ATTENTION_ACTION_GITHUB (and ATTENTION_ACTION_VIDEO with evidence), never ATTENTION_ACTION_APPROVE, and carries no alert_key or target_chat_id. Its sentence names the failure instead of asking for approval.

Clients pinned to 2026-09-18 or older (that receive attention at all) get ATTENTION_KIND_REVIEW with ATTENTION_ACTION_APPROVE first on session-bearing unary responses. The sentence stays the new one: the earlier sentence could include the evidence video's length, which the response does not carry.

2026-09-20 — An unverified PR needs a review, not a merge​

Session.attention answers ATTENTION_KIND_MERGE only for a PR the verify stage has passed: open, ready, green, mergeable, and carrying a successful boss/verify commit status on its head (verify passed it, or the operator approved its park). A PR that is otherwise ready to merge but that nothing has verified (its repository runs no verify stage, or verify has not reached its head) is now ATTENTION_KIND_REVIEW. That review carries no park_codes, reads "Not verified", and offers ATTENTION_ACTION_GITHUB then ATTENTION_ACTION_MERGE: there is no verify park, so merging is the review's outcome.

Clients pinned to 2026-09-19 or older (that receive attention at all) get ATTENTION_KIND_MERGE with the sentence "Ready to merge" and ATTENTION_ACTION_MERGE first on session-bearing unary responses.

The same version changes the ATTENTION_KIND_FIX answer for a session whose blocked_reason is "session bootstrap never completed; worktree and branch were cleaned up". Such a session died before setup finished, so it has no worktree or chat to open or resume. Its attention now offers ATTENTION_ACTION_DISMISS alone and its sentence says only that it died before setup finished, where it offered ATTENTION_ACTION_DISMISS and ATTENTION_ACTION_OPEN_CHAT and said to open the chat and resume. Clients pinned to 2026-09-19 or older get ATTENTION_ACTION_OPEN_CHAT after ATTENTION_ACTION_DISMISS and the sentence "Run blocked: session bootstrap never completed; worktree and branch were cleaned up · open the chat, then resume" back.

The same version retires the approve and send-back actions on a verify park. A sign-off park's ATTENTION_KIND_REVIEW offers ATTENTION_ACTION_GITHUB (and ATTENTION_ACTION_VIDEO when there is evidence) then ATTENTION_ACTION_MERGE. That merge records the operator's head-bound approval and merges at once. A blocked park's ATTENTION_KIND_FIX offers ATTENTION_ACTION_GITHUB (and ATTENTION_ACTION_VIDEO) and nothing that merges. Neither offers ATTENTION_ACTION_APPROVE or ATTENTION_ACTION_SEND_BACK any more. Park sentences state only the issue, for example "Database migration" and "The PR checklist has 1 open item". Clients pinned to 2026-09-19 or older get ATTENTION_ACTION_APPROVE and ATTENTION_ACTION_SEND_BACK ahead of the other actions on the review (with ATTENTION_ACTION_MERGE dropped), ATTENTION_ACTION_SEND_BACK ahead of them on the blocked park, and the sentences they were served before.

Other attention sentences now state only the issue too: a fault reads, for example, "Run stopped" or "Merge conflict" without the "open the chat, then resume" advice, and a question with no text reads "The agent asked a question". These are display copy and every version receives the new wording.

2026-09-21 — An unverified green PR is ready to merge​

The unverified ATTENTION_KIND_REVIEW from 2026-09-20 is retired. A PR that is open, ready, green and mergeable, with no verify park, that the verify stage has not passed is ATTENTION_KIND_MERGE again, with the sentence "Ready to merge", ATTENTION_ACTION_MERGE then ATTENTION_ACTION_GITHUB, and the new SessionAttention.is_unverified set. Such a PR only reaches the operator when no enabled verify job on the daemon will reach its head: either the repository has no job that runs boss-verify (by prompt, or by running its cron gate), or every such job is disabled. A repository with an enabled verify job still answers it as in progress.

is_unverified is set only on that ATTENTION_KIND_MERGE. The first-party clients label the row "ready" rather than "merge".

Clients pinned to 2026-09-20 get the unverified ATTENTION_KIND_REVIEW back on session-bearing unary responses: the sentence "Not verified" and ATTENTION_ACTION_GITHUB then ATTENTION_ACTION_MERGE. Older pins keep receiving ATTENTION_KIND_MERGE, as before.

A repository whose only enabled verify job runs the boss-verify cron gate, with no /boss-verify prompt, now counts as having a verify job, so its unverified green PR answers in progress for every version, including clients pinned to 2026-09-20 and older. The response carries nothing that tells this in-progress answer apart from any other, so a transform has nothing to restore the old one from.

2026-09-22 — Parks that only need a look are reviews​

A no-receipt verify park now reads "No review of the latest commit". It read "Built by hand, no factory review", which was false: the park fires when the PR head has no successful boss/build status, and a head an agent built in an ordinary chat, or a reviewed head replaced by a later rebase, has none. Clients pinned to 2026-09-21 or 2026-09-20 get "Built by hand, no factory review" back on session-bearing unary responses, and older pins keep "Built by hand, no factory review · send it back through the factory".

The no-receipt and human-review:unproven codes now only ask a person to look, like the UI, security and database codes. A park whose codes are all such codes is an ATTENTION_KIND_REVIEW offering ATTENTION_ACTION_MERGE, and its display_label reads "needs sign-off" with DISPLAY_INTENT_WARNING. It was an ATTENTION_KIND_FIX with no merge, reading "blocked" with DISPLAY_INTENT_DANGER. A park that also has a code meaning something is wrong (an open checklist, exhausted repair, a failed verify extension, a worktree off the PR head, no session, or an unknown code) stays blocked, but the missing review or evidence no longer decides its sentence. A park with ledger-open reads "The PR checklist has open items" (with a count once the items are read), and any other such park reads the generic "Verify is blocked". Clients pinned to 2026-09-21 or older get the blocked answer, its sentence and its actions back on session-bearing unary responses, and "blocked" / DISPLAY_INTENT_DANGER back on any such row, including a Blocked, Orphaned or auth-wedged session whose own attention outranks the park.

The new transport-only Session.verify_park_codes (field 81) carries a DISPLAY_STATUS_NEEDS_HUMAN session's park codes in the verify gate's report order on every read path, even when another attention outranks the park and attention_status names something else. It is never persisted and is empty for any other session. It is additive, so every pin receives it.

In a repo with an enabled verify job, a green, mergeable PR whose head the verify stage passed, and that verify has left unmerged for 30 minutes, is now an ATTENTION_KIND_MERGE reading "Verified but not merged" with ATTENTION_ACTION_MERGE then ATTENTION_ACTION_GITHUB, dated from when the head was first seen verified. It stayed ATTENTION_KIND_IN_PROGRESS, so a PR whose merge failed sat there with nothing asking anyone to merge it. The row carries the progress_label it showed before. Clients pinned to 2026-09-21 or older get the in-progress answer back on session-bearing unary responses: no sentence, no actions but ATTENTION_ACTION_TRACKER, that label, and the session's state time. A head whose newest operator record on the PR is a send-back stays ATTENTION_KIND_IN_PROGRESS on every pin, because verify does not merge it.

A verify park's attention (a review or a blocked fix carrying park_codes) is now dated from when the park began, the time the session timeline gives its park step. It was dated from when the session's state began, often hours earlier. Clients pinned to 2026-09-21 or older get state_entered_at (or updated_at when no state time is recorded) back as the park's since.

The new SessionAttention.should_show_display_label (field 12) is true on an ATTENTION_KIND_FIX whose session's own display state is itself a failure, such as a failing or conflicting PR or a blocked run. A verify park that only asks for a sign-off is not a failure, on a blocked or orphaned run too. Clients show the session's display_label on that row rather than "fix", as the boss TUI does. It is additive, so every pin receives it.

2026-09-23 — A blocked run says "Run blocked" once, and not while its agent works​

A blocked run's ATTENTION_KIND_FIX sentence drops the reason's own leading "run blocked:" or "blocked:" labels, in any case, before it prefixes "Run blocked: ". A run that reported itself blocked read "Run blocked: run blocked: MAD-1751 implemented" and now reads "Run blocked: MAD-1751 implemented". blocked_reason is unchanged. Clients pinned to 2026-09-22 or older get the sentence built from the whole blocked_reason back on session-bearing unary responses.

A Blocked session whose block no longer needs the operator now carries no attention_status. That is a run whose own agent is working on a turn the block did not see, or one whose non-draft PR is green and mergeable. Its Session.attention already reads ATTENTION_KIND_IN_PROGRESS (or ATTENTION_KIND_MERGE) for every version. Every Blocked session carried ATTENTION_REASON_BLOCKED_MAX_ATTEMPTS before. Clients pinned to 2026-09-22 or older get that hint back on session-bearing unary responses, with the blocked_reason (or "blocked — needs human intervention") as its summary and state_entered_at (or updated_at) as its since.

The in-progress Session.attention for a run whose agent works again, and the session timeline dropping its Blocked step for it, reach every version. A response carries no transcript time or head, so nothing in it tells this answer apart from the green-PR one, and restoring the fix would ask the operator about a run whose agent is already at work. The session stays SESSION_STATE_BLOCKED while its agent works. Only the run's own finish moves it: a plain finish readies the PR (SESSION_STATE_READY_FOR_REVIEW), and a finish that reports the run blocked again starts a new block with the new blocked_reason. That change is persisted, so every version sees it.

2026-09-24 — A run whose automatic resumes gave up says so plainly (Current)​

The ATTENTION_KIND_FIX sentence for a run whose automatic resumes gave up ends its first clause with "so automatic retries stopped", where it said "so the factory stopped retrying". The rest of the sentence, which names the attempt times, the failure and the next step, is unchanged. Clients pinned to 2026-09-23 or older get the old clause back on session-bearing unary responses.

Additive changes​

An additive change adds procedures, messages or fields and leaves every existing procedure behaving as before, so it ships without a new version and every version sees it.

Open checklist items on a verify park​

SessionAttention.open_items lists what keeps a verify park's PR checklist open, one plain line each (an unticked item, a missing section, a do-not-merge marker), as the park's own note recorded them for the current head. It is set only on a park whose checklist is open, and is empty until the daemon has read the note. The park's sentence counts the items, for example "The PR checklist has 2 open items".

Organization notes procedures​

The OrchestratorService declares seven new procedures for notes that the cloud stores per organization: CreateOrganizationNote, GetOrganizationNote, ListOrganizationNotes, UpdateOrganizationNote, DeleteOrganizationNote, GetOrganizationNoteQuota and SyncDaemonNotes, with the OrganizationNote, OrganizationNoteQuota and OrganizationNoteQuotaExceeded messages and the OrganizationNoteOrigin and NoteSyncOutcome enums. The ProxyCreateNote family that relays note calls to your daemons is unchanged.

The six user procedures behave the same on every version:

  • Each requires a signed-in user who is a member of organization_id, and that organization must hold the paid cloud entitlement. A non-member gets PERMISSION_DENIED before the request is validated.
  • Any member reads and lists the organization's unexpired notes. Only a note's author or an organization owner updates or deletes it. UpdateOrganizationNote answers FAILED_PRECONDITION for a note synced from a daemon.
  • DeleteOrganizationNote removes an API-written note and tombstones a synced one, so its daemon cannot sync it back.
  • A create, or an update that changes the body or tags, spends one unit of the organization's hourly quota: 1,000 writes per fixed UTC clock hour by default. Reads, deletes, unchanged updates and idempotent create retries spend none. A write over the quota answers RESOURCE_EXHAUSTED with an OrganizationNoteQuotaExceeded detail carrying the usage and reset time.
  • Every note expires 90 days after it was created. Edits never extend it, and an expired note reads as NOT_FOUND.

SyncDaemonNotes is for daemons only. It accepts at most 50 items, and answers each with a per-item NoteSyncOutcome (synced, stale, rate limited, not entitled, organization refused, expired, rejected or suppressed), so one bad item never fails the batch. Resending a version comes back stale and spends no quota.

See also​

  • API Reference — the generated OpenAPI reference for the OrchestratorService.
  • docs/api-versioning.md in the repository — the engineering procedure for adding a new API version and down-convert transform.

Daemon run resume​

The daemon also adds ResumeSessionRun, which starts a fresh chat in the same session and clears its persisted automatic retry budget.