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_LIMITEDand 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 anATTENTION_REASON_AWAITING_HUMAN_INPUTattention 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:
kindis one ofATTENTION_KIND_FIX,ATTENTION_KIND_QUESTION,ATTENTION_KIND_REVIEW,ATTENTION_KIND_MERGE(these four need you),ATTENTION_KIND_IN_PROGRESSorATTENTION_KIND_DONE.sentencesays what to do, for exampleReview the migration, then approve.actionslists theAttentionActionvalues a client offers for that kind, such asATTENTION_ACTION_APPROVEorATTENTION_ACTION_MERGE. A session linked to a tracker issue also offersATTENTION_ACTION_TRACKER(open itstracker_url) last, whatever the kind.progress_labeldescribes work that does not need you (building,checking 3/7,waiting for main CI). Running checks first readCI 3/7; the label is free text, and the service now writeschecking 3/7at every version.since,park_codes,alert_key,target_chat_idandevidence_urlcarry 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 getsPERMISSION_DENIEDbefore 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.
UpdateOrganizationNoteanswersFAILED_PRECONDITIONfor a note synced from a daemon. DeleteOrganizationNoteremoves 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_EXHAUSTEDwith anOrganizationNoteQuotaExceededdetail 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.mdin 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.