> For the complete documentation index, see [llms.txt](https://docs.amigo.ai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.amigo.ai/api-reference/change-logs/amigo-api.md).

# Amigo API

Release history for Amigo backend APIs. Latest numbered Amigo API release: **v0.9.557** (July 2026). Newer Platform and Scribe source updates without standalone release metadata are listed first and intentionally unnumbered. Release numbers with no external documentation impact are omitted.

<details>

<summary>Platform API: Authorization-Bound Parameters for Platform Functions and Workspace Data Queries (July 2026)</summary>

#### Authorization-Bound Parameters for Platform Functions and Workspace Data Queries

Platform function parameters and workspace data query parameters can now declare an authorization binding that ties them to the calling turn's resolved external authorization context. This lays the data-model and authoring groundwork for server-injected, tamper-proof parameter values.

**What changed:**

* **New `authorization_binding` field on function parameters.** When creating or updating a platform function, each parameter in the `parameters` array can now include an optional `authorization_binding` object. When set, the parameter becomes server-bound: at execution time the platform will inject the value from the turn's authorization context rather than accepting it from the caller or model.
* **New `authorization_binding` field on workspace data query parameters.** The same binding field is available on workspace data query parameters (create and update endpoints), with identical semantics.
* **Mutual exclusion with `default`.** A parameter that declares an `authorization_binding` cannot also declare a `default` value. The API rejects requests that set both.
* **Deployment gated until execution support lands.** Deploying a function or workspace data query that includes a bound parameter is currently rejected with a validation error. This prevents a bound parameter from silently falling back to caller-supplied behavior before the execution path is ready to enforce it.
* **`kb_scope` role grant validation.** Role grants with resource type `kb_scope` now enforce that `param_binding` is not set (the grant is unconditional) and that `resource_key` contains only lowercase letters, digits, and underscores (`[a-z0-9_]+`).
* **Bound parameters hidden from caller schemas.** Server-bound parameters are excluded from every emitted caller-facing schema, so models and callers cannot attempt to supply them.

**Authorization binding object shape:**

| Field           | Type   | Description                                               |
| --------------- | ------ | --------------------------------------------------------- |
| `source`        | string | The binding source. Currently `role_grant_resource_keys`. |
| `resource_type` | string | The resource type to project. Currently `kb_scope`.       |
| `access`        | string | The access level. Defaults to `read`.                     |
| `encoding`      | string | How the projected keys are encoded. Defaults to `csv`.    |

**What you need to do:**

* **No action required for existing functions or queries.** The new field defaults to `null`, so all existing resources are unchanged.
* **Prepare for server-bound parameters.** If you plan to use authorization-scoped data access, you can begin defining parameters with authorization bindings. Note that deployment is gated until execution-time injection is available in a subsequent release.
* **Update `kb_scope` grant creation.** If you create `kb_scope` role grants, ensure `resource_key` uses only lowercase letters, digits, and underscores, and do not set `param_binding`.

</details>

<details>

<summary>Platform API: Extended Default Token TTL for Email OTP Provider Grants (July 2026)</summary>

#### Extended Default Token TTL for Email OTP Provider Grants

Provider access tokens issued through the email OTP flow now default to a longer time-to-live when no explicit TTL is requested.

**What changed:**

* **Default TTL extended to 7 days.** When a provider token is minted through the email OTP grant and the caller does not specify a TTL, the issued access token now lives for 7 days instead of the previous short-lived default. Callers that explicitly request a TTL continue to receive the requested value, subject to the existing allowed set.

**What you need to do:**

* **No action required for most integrations.** If your integration omits the TTL parameter when requesting an email OTP provider token, it will now receive a token valid for 7 days. If your integration explicitly passes a TTL value, behavior is unchanged.
* **Review token storage and revocation practices.** Longer-lived tokens increase the window during which a leaked token is usable. Ensure your integration stores provider tokens securely and revokes them when they are no longer needed.

</details>

<details>

<summary>Platform API: Receipt-Backed Background Polling and Delivery Acknowledgement (July 2026)</summary>

#### Receipt-Backed Background Polling and Delivery Acknowledgement

Text conversations now support a receipt-backed protocol for collecting background answers. When a tool crosses the server's blocking window, the platform hands it to a background task and signals the client. Clients poll for the completed answer, receive a delivery receipt, render the result, and then acknowledge the receipt so the platform knows the answer was consumed.

**What changed:**

* **Delivery protocol version 2.** Turn responses include a `delivery_protocol_version` field (value `2` when the serving agent supports the receipt protocol). Clients should begin polling only after observing version 2.
* **Receipt-backed poll responses.** A `poll=true` turn that claims a ready background answer now returns a `delivery` object containing `delivery_id`, `request_id`, and `receipt`. The receipt is opaque and required for acknowledgement.
* **New acknowledgement endpoint.** `POST /{workspace_id}/conversations/{conversation_id}/turns/{delivery_id}/ack` confirms that the client rendered or durably persisted the delivered answer. The request body carries `request_id` and `receipt`. Returns 204 on success.
* **Idempotency-Key header.** `POST .../turns` and `POST .../turns/stream` accept an optional `Idempotency-Key` header (UUID) for client-controlled replay of the same logical send or poll after an ambiguous failure.
* **Poll validation tightened.** `poll=true` now rejects any user content (message text, structured content, media URL, or media type), not only a non-empty message string. The error message reflects the broader check.
* **Streaming does not support poll.** `POST .../turns/stream` with `poll=true` is rejected (422).
* **Conversation close notifies the agent.** `DELETE /{workspace_id}/conversations/{conversation_id}` now signals the serving agent before marking the conversation closed, ensuring in-flight background work is cleaned up. A narrow database conflict during close returns 409 instead of 404.
* **Independent rate-limit budgets.** Polls and acknowledgements each have their own per-conversation rate-limit budget (30 requests per minute per conversation), separate from the turn-send budget.
* **Background delivery turn identity.** A background delivery returned by a poll keeps the originating exchange identity even when later user turns already exist, so feedback and artifacts anchor to the correct exchange.

**What you need to do:**

* **Adopt the receipt-based delivery loop.** When a turn response has `background_pending=true` and `delivery_protocol_version=2`, poll with `poll=true` at moderate intervals (no more than once every few seconds). When the poll returns a `delivery` object, render the answer, then call the acknowledgement endpoint with the delivery receipt. Repeat polling until `background_pending` clears or the poll returns idle.
* **Pass the Idempotency-Key header for replay safety.** When retrying a turn send or poll after a network timeout, reuse the same UUID so the platform can deduplicate.
* **Update poll validation expectations.** If your integration previously sent media or structured content alongside `poll=true`, remove that content. The platform now rejects it.
* **Handle 409 on conversation close.** If a close request returns 409, retry the close.

</details>

<details>

<summary>Scribe API: Generate ICD-10-CM Code Suggestions (July 2026)</summary>

#### Generate ICD-10-CM Code Suggestions

Scribe sessions can now generate ICD-10-CM diagnosis code suggestions derived from the session transcript and clinical note.

**What changed:**

* **New `POST /{workspace_id}/sessions/{session_id}/codes` endpoint.** Generates ICD-10-CM code suggestions for an owned scribe session. The endpoint derives suggestions from the canonical transcript and the latest clinical note (if available), persists the results, and returns them with generation provenance metadata. No request body is required.
* **Grounded suggestions only.** The generation is instructed to propose only codes substantively supported by the source material. Each suggestion includes the ICD-10-CM code, its official description, a brief rationale grounded in the transcript or note, and a confidence score between 0 and 1.
* **Deduplication and bounding.** Suggestions are deduplicated by code value and capped at 50 per generation. Code values, descriptions, and rationales are bounded to prevent unbounded storage.
* **Generation provenance.** The response includes generation metadata (model provider, model name, prompt version, and completion timestamp), consistent with the existing note and summary generation endpoints.
* **Empty extraction treated as failure.** If the model returns no grounded codes, the generation is marked failed and the endpoint returns 503, preserving the invariant that a completed generation always has at least one suggestion.
* **Scope requirement.** The caller must hold the `scribe:notes:rw_own` scope and own the session.

**What you need to do:**

* **Call the new endpoint to generate code suggestions.** Use `POST /{workspace_id}/sessions/{session_id}/codes` after a transcript is available. The response shape includes a `codes` object (with `session_id` and `items` array) and a `generation` metadata object.
* **Handle 409 for empty transcripts.** The endpoint returns 409 if the session transcript is empty.
* **Handle 503 for generation failures.** If the model produces no usable codes or the generation pipeline is unavailable, the endpoint returns 503.

</details>

<details>

<summary>Scribe API: Zoom Active-Session Guard Narrowed to Live-Capture States (July 2026)</summary>

#### Zoom Active-Session Guard Narrowed to Live-Capture States

The duplicate-session guard for Zoom scribe sessions now considers only live-capture states when deciding whether a provider already has an active Zoom session in a workspace.

**What changed:**

* **Narrower active-session definition.** The guard that prevents duplicate Zoom sessions in a workspace now treats only sessions in the initial and live-capture states as "active." Sessions that have moved past live capture into the review stage are no longer counted. This means a provider whose previous Zoom session is awaiting review can start a new Zoom session without receiving a conflict error.
* **Consistent enforcement.** The pre-check and the underlying uniqueness constraint use the same state set, so a request that passes the pre-check cannot be rejected by the constraint (and vice versa).

**What you need to do:**

* **No action required.** Integrations that create Zoom scribe sessions benefit automatically. If your workflow previously required completing or cancelling a session in review before starting a new Zoom session, that step is no longer necessary.

</details>

<details>

<summary>Scribe API: Zoom Session Lifecycle and Transcript Finalization (July 2026)</summary>

#### Zoom Session Lifecycle and Transcript Finalization

Zoom-based scribe sessions are now managed as first-class session rows with dedicated lifecycle transitions, transcript finalization, and a join-deadline watchdog that fails stuck bots cleanly.

**What changed:**

* **Zoom sessions as first-class session rows.** Zoom scribe sessions now share the same session model and status lifecycle as in-person sessions. A Zoom session is created with `mode=zoom` and progresses through the same `created`, `in-progress`, `in-review`, and terminal states.
* **Start transition endpoint.** A new `POST /{workspace_id}/sessions/{session_id}/start` endpoint marks a `created` session as `in-progress`. For Zoom sessions, this is driven by the caller once the meeting bot has joined and audio is flowing. The transition is idempotent - calling it on an already `in-progress` session succeeds without change. A terminal session returns 409 with `invalid_session_state`.
* **Zoom finalize endpoint.** A new `POST /{workspace_id}/sessions/{session_id}/zoom-finalize` endpoint promotes the drained transcript snapshot to the canonical transcript artifact and flips the session to `in-review`. This endpoint is the trigger that makes `GET .../transcript` resolve for Zoom sessions, so a partial transcript is never served to reviewers. The endpoint rejects non-Zoom sessions (409 `not_zoom_session`), sessions whose transcript has not been drained yet (409 `transcript_not_finalized`), and terminal sessions (409 `invalid_session_state`). The operation is idempotent.
* **Terminal-session immutability.** A completed, cancelled, or failed session never gains a fresh transcript artifact. The finalize endpoint enforces this with both an early state check and a transactional guard, so even a narrow race between the check and the state flip cannot produce an orphaned artifact on a terminal session.
* **Mode-aware session reaping.** The background reaper that detects abandoned sessions now uses different staleness rules by mode. In-person sessions are reaped when the streaming worker stops reporting liveness within a short grace period. Zoom sessions - which have no streaming worker liveness signal - are reaped only after a much longer maximum-session horizon measured from creation, preventing live Zoom calls from being incorrectly terminated.
* **Join-deadline watchdog.** Zoom meeting bots that remain in a `joining` state past a configurable deadline (default: 120 seconds) are now automatically failed with a `join_timeout` reason. This prevents the caller from waiting indefinitely when the meeting host never admits the bot or the audio handshake never completes. The deadline covers a slow host-admit and audio handshake.
* **Bot lookup by session.** A new query parameter on the bot listing endpoint resolves the active bot bound to a given scribe session identifier. This lets callers recover a running bot after a timed-out or duplicate creation request instead of orphaning it.
* **Duplicate bot creation returns existing bot.** When a bot creation request conflicts with an already-running bot for the same session, the 409 response now includes the existing bot identifier in the response body, enabling callers to bind to the running bot without a separate lookup.
* **Finalize signal from the transcription relay.** Once the transcription relay drains its final transcript snapshot during shutdown, it reports a `transcript_finalized` signal with the snapshot location and segment count. This signal gates the `zoom-finalize` endpoint. The signal is delivered on a best-effort basis - the session reaper serves as the backstop if the signal is lost.

**What you need to do:**

* **Zoom session integrations should call the start and finalize endpoints.** If your integration manages Zoom scribe sessions, use `POST .../start` once the bot has joined the meeting, and `POST .../zoom-finalize` once the finalize signal confirms the transcript has been drained. The `GET .../transcript` endpoint will not resolve for a Zoom session until finalization completes.
* **Handle new 409 error codes.** The `not_zoom_session` and `transcript_not_finalized` codes are new. Update error handling if your integration calls the finalize endpoint.
* **Bot creation retry logic.** If your integration retries bot creation, check the 409 response body for the existing `bot_id` to recover the running bot instead of retrying blindly.
* **No schema changes to existing endpoints.** The session response shape, existing lifecycle endpoints, and transcript retrieval contract are unchanged.

</details>

<details>

<summary>Platform: Connector Sync-Run Telemetry Routing Change (July 2026)</summary>

#### Connector Sync-Run Telemetry Routing Change

Successful connector sync-run completion events are no longer written to the durable event store. They are recorded to operational metrics and logs only.

**What changed:**

* **Success heartbeats removed from durable events.** The per-poll `connector.sync_run.completed` event - a heartbeat that fires every polling interval whether or not new data was ingested - is no longer emitted through the durable event pipeline. At fleet scale, these heartbeats dominated per-workspace event counts (a single connector could produce thousands of rows per day), inflating analytics for otherwise idle tenants.
* **Failures remain durable.** `connector.sync_run.failed` events continue to be written as durable records with classification and error-code metadata, in addition to operational metrics. These are low-volume and diagnostically valuable.
* **Analytics and projections exclude operational exhaust.** Per-workspace event counts and entity projections now filter out operational connector exhaust, so connector polling volume no longer distorts workspace activity summaries or drives unnecessary compute.
* **Operational metrics coverage.** Every sync run (success and failure) is recorded to operational metrics with bounded-cardinality tags, including record counts and duration. Monitoring dashboards that rely on these metrics are unaffected.

**What you need to do:**

* **No action required for most integrations.** If your integration consumes connector events from the durable event store, successful sync-run completions are no longer present. Failure events are unchanged.
* **Use operational metrics for sync-run monitoring.** If you previously counted durable sync-run events to monitor connector health, use the operational metrics surface instead.

</details>

<details>

<summary>Scribe API: Appointments Endpoint Returns a Rolling Date Window (July 2026)</summary>

#### Appointments Endpoint Returns a Rolling Date Window

The appointments list endpoint now returns a multi-day rolling window of appointments instead of only the current calendar day, so appointment-to-session links remain stable across day boundaries.

**What changed:**

* **Rolling window instead of today-only.** `GET /{workspace_id}/appointments` now returns appointments spanning a configurable window around the current date (default: 14 days back and 30 days forward, inclusive of today). Previously the endpoint returned only the current calendar day's appointments.
* **Stable appointment-session linkage across days.** Because appointment identifiers are date-pinned, a session created on one day stays linked to its appointment as long as that date falls within the rolling window. Previously, a session's linked appointment disappeared from the list the next day, silently breaking the join.
* **Same response contract.** The response shape, pagination behavior, and nested session object are unchanged. The endpoint returns more items (up to approximately 360 with default window sizes), paginated with the same `limit` and `continuation_token` parameters.
* **Environment-tunable window.** The look-back and look-forward window sizes are configurable per deployment. Defaults are chosen to cover multi-week usage periods without generating excessive result counts.

**What you need to do:**

* **Expect more items in the appointments list.** If your integration pages through appointments, be prepared for a larger total result set. The maximum item count with default settings is modest (under 400) and pages through in a few requests at the default page size.
* **Bucket appointments client-side.** The endpoint no longer filters to today only. Clients that display appointments grouped by past, today, and upcoming should bucket by date on the client side.
* **No schema changes required.** The response fields, pagination, and nested session object are identical to the previous release.

</details>

<details>

<summary>Scribe API: Provider Grants No Longer Require a Pre-Existing Provider Entity (July 2026)</summary>

#### Provider Grants No Longer Require a Pre-Existing Provider Entity

Provider access grants are now created immediately active, even when no provider entity identifier is supplied. A provider no longer needs to exist in the system before being provisioned for Scribe access.

**What changed:**

* **Immediate activation.** Admin and internal grant creation endpoints now produce an `active` grant when the provider's email is verified, regardless of whether a `provider_entity_id` is supplied. Previously, omitting the entity identifier created the grant in a `pending_entity` state that required a subsequent login or verification step to activate.
* **Server-generated scoping identifier.** When `provider_entity_id` is omitted, the server generates a random scoping identifier for the grant. This identifier serves as the opaque ownership key for sessions and downstream access checks. Callers that supply a `provider_entity_id` continue to have that value honored verbatim.
* **No request or response schema changes.** The `provider_entity_id` field on the create request remains optional. The response shape is unchanged. Existing integrations that already supply the field are unaffected.
* **`pending_entity` status preserved for legacy grants.** Grants created before this change that carry `pending_entity` status continue to resolve through the existing login and verification flow. New grants are no longer created in this state.

**What you need to do:**

* **No action required for most integrations.** If your integration creates provider grants through the admin or internal endpoints, grants are now active sooner. Remove any polling or retry logic that waited for a `pending_entity` grant to transition to `active`.
* **Update status handling if needed.** If your integration explicitly checks for the `pending_entity` status on newly created grants, that status is no longer returned for new grants. Existing grants in that state are unaffected.

</details>

<details>

<summary>Scribe API: Session Lifecycle Writes, Mode, and Idempotency (July 2026)</summary>

#### Session Lifecycle Writes, Mode, and Idempotency

Scribe sessions now carry a mode, support explicit end and cancel transitions through REST, and enforce stronger idempotency and conflict rules on create.

**What changed:**

* **Session mode.** Every session now has a `mode` field indicating the session modality: `in_person` (default) or `zoom`. The mode is set at creation and included in all session responses (list, detail, appointment nesting). It can also be changed via PATCH.
* **Create session - enhanced idempotency.** `POST /{workspace_id}/sessions` accepts an optional `mode` field (defaults to `in_person`). When a create request reuses an `external_id` that already exists for the same provider, the request fingerprint (mode, external appointment identifier, and metadata) is compared against the stored session. A matching fingerprint returns the existing session (idempotent success). A divergent fingerprint returns `409` with error code `idempotency_key_conflict` instead of silently returning a mismatched session.
* **Active Zoom guard.** A practitioner may have at most one non-terminal Zoom session at a time within a workspace. Attempting to create or update a second active Zoom session returns `409` with error code `active_zoom_session_exists`.
* **Update session.** `PATCH /{workspace_id}/sessions/{session_id}` updates mutable fields on an owned session. Supported fields are `external_appointment_id` (nullable - sending `null` clears the link), `mode`, and `metadata`. Only fields present in the request body are written. A mode change to `zoom` is subject to the active Zoom guard. Returns the full refreshed session.
* **End session.** `POST /{workspace_id}/sessions/{session_id}/end` transitions a `created` or `in-progress` session to `in-review`. If a streaming worker is still attached, the endpoint returns `409` with error code `session_streaming` instead of racing the worker. Sessions already in a terminal or non-endable state return `409` with error code `invalid_session_state`.
* **Cancel session.** `POST /{workspace_id}/sessions/{session_id}/cancel` transitions any non-terminal session to `cancelled`. This is the compensation path for orphaned sessions and user aborts. A session that is already terminal returns `409` with error code `invalid_session_state`.
* **Stable error codes.** Conflict responses from session lifecycle writes carry a machine-readable `code` in the error envelope so callers can distinguish conflict kinds without parsing free-text messages. Codes include `idempotency_key_conflict`, `active_zoom_session_exists`, `session_streaming`, and `invalid_session_state`.
* **Appointment session ordering.** When multiple sessions reference the same appointment, the appointment response now prefers the most recent non-cancelled session rather than the most recent session overall. A cancelled orphan no longer shadows a still-valid older session.

**What you need to do:**

* **Handle the `mode` field.** Session responses now include `mode`. Update integrations that deserialize session objects to accept this field.
* **Include `mode` on create when starting a Zoom session.** Pass `mode: "zoom"` in the create request body. Omitting the field defaults to `in_person`.
* **Handle new 409 error codes.** If your integration creates sessions with an `external_id`, add handling for `idempotency_key_conflict` (retry with consistent parameters or generate a new key) and `active_zoom_session_exists` (resolve the existing Zoom session before starting a new one).
* **Use the new lifecycle endpoints.** Call `POST .../end` to close a session through REST when no streaming worker is attached. Call `POST .../cancel` to abort or compensate an orphaned session. Both return the updated session on success.
* **Use PATCH to update session fields.** Call `PATCH /{workspace_id}/sessions/{session_id}` to modify `external_appointment_id`, `mode`, or `metadata` after creation.

</details>

<details>

<summary>Scribe API: Nested Session Object on Appointments (July 2026)</summary>

#### Nested Session Object on Appointments

The appointment response now returns a nested session object instead of a plain session identifier, giving clients enough context to render visit state directly from the appointments list.

**What changed:**

* **`session` replaces `session_id`.** Each appointment in `GET /{workspace_id}/appointments` and `GET /{workspace_id}/appointments/{appointment_id}` now returns a `session` object instead of a `session_id` string. The object includes the session identifier, status, lifecycle timestamps (started, ended, created, updated), and the external appointment identifier. When no session exists for an appointment, the field is `null`.
* **No second lookup required.** Clients can determine whether a visit is in progress, in review, or completed without calling the sessions endpoint. The nested object is a focused subset of the full session resource - it intentionally excludes artifact availability, which remains on the individual session detail endpoint.
* **Most-recent session wins.** When multiple sessions reference the same appointment, the response includes only the most recent session, determined by creation time with deterministic tie-breaking.

**What you need to do:**

* **Update integrations that read `session_id`.** Replace references to the `session_id` field with the `session` object. The session identifier is now at `session.id`. Status and timestamps are available at `session.status`, `session.started_at`, `session.ended_at`, `session.created_at`, and `session.updated_at`.
* **Remove follow-up session lookups where possible.** If your integration previously fetched the session detail solely for status or timing information, the nested object now provides that data inline.

</details>

<details>

<summary>Scribe API: Appointments Endpoint - Current-Day Listing and Retrieval (July 2026)</summary>

#### Appointments Endpoint - Current-Day Listing and Retrieval

Providers can now list and retrieve appointments for the current calendar day through dedicated Scribe API endpoints.

**What changed:**

* **List appointments.** `GET /{workspace_id}/appointments` returns a paginated list of appointments for the current calendar day, scoped to the authenticated provider and workspace. Pagination follows the same `limit` and `continuation_token` pattern used by the sessions endpoint.
* **Get a single appointment.** `GET /{workspace_id}/appointments/{appointment_id}` retrieves a single appointment by its identifier. Returns 404 if the appointment is not in the current day's data.
* **Session linking.** Each appointment includes a `session_id` field that is automatically populated when a session has been recorded against that appointment. The link is resolved by matching the appointment's external identifier against existing sessions, scoped to the authenticated provider and workspace. Appointments with no matching session return `session_id` as null.
* **Stable response contract.** The response shape is designed as the long-term contract. The current implementation returns a deterministic seed of appointments for the current day. A future release will source appointments from a downstream customer API through a managed External Integration with no change to the wire format.
* **Appointment fields.** Each appointment includes start and end times, duration, reason, appointment type, patient name and entity ID, practitioner name and entity ID, and location name.

**What you need to do:**

* **To list current-day appointments:** call `GET /{workspace_id}/appointments` with valid provider credentials. Use `limit` and `continuation_token` query parameters for pagination.
* **To retrieve a specific appointment:** call `GET /{workspace_id}/appointments/{appointment_id}`.
* **No action required for existing integrations.** These are new additive endpoints. Existing session and recording workflows are unaffected.

</details>

<details>

<summary>Platform API: Admin Provider Access Grant Management for Scribe (July 2026)</summary>

#### Admin Provider Access Grant Management for Scribe

Workspace administrators can now provision, list, inspect, and revoke provider Scribe access grants through dedicated admin endpoints, replacing manual provisioning workflows.

**What changed:**

* **Admin grant endpoints.** A new set of endpoints under `/admin/scribe/grant` lets workspace administrators manage provider access grants through the API. These are the public, workspace-admin-gated counterparts to the existing internal provisioning path.
* **Create a grant.** `POST /admin/scribe/grant` provisions a provider for Scribe access by email. Supplying a known provider entity ID creates the grant as `active` (the admin vouches for the identity). Omitting the entity ID creates a `pending_entity` grant that becomes active once the provider's entity is bound through the login or verification flow. Duplicate active grants for the same workspace and email return 409.
* **List grants.** `GET /admin/scribe/grant` returns a paginated list of a workspace's grants with optional status filtering. Results are ordered newest first with stable page ordering.
* **Get a grant.** `GET /admin/scribe/grant/{grant_id}` retrieves a single grant by ID.
* **Revoke a grant.** `POST /admin/scribe/grant/{grant_id}/revoke` soft-deletes the grant, immediately blocking new human provider logins and new machine-to-machine act-as-by-email token mints. Active sessions and refresh tokens bound to the grant are revoked on the spot. The email can be re-invited afterward, creating a fresh grant.
* **Role-based scopes.** Each grant carries a role (`provider` or `scribe_admin`) that determines the scopes the provider receives.
* **Authorization.** All endpoints require `identity:admin` for the target workspace or global `platform:admin`. Provisioning access is gated on admin authority, not on the Scribe capabilities the grant confers.
* **Backward compatible.** Existing internal grant provisioning and provider login flows are unaffected.

**What you need to do:**

* **No action required for existing integrations.** Internal provisioning paths continue to work.
* **To manage grants via API:** use the new `/admin/scribe/grant` endpoints. Ensure the calling identity has `identity:admin` scope for the target workspace.

</details>

<details>

<summary>Platform API: Source Provenance and PHI-Gated Filenames in the Analytical Catalog (July 2026)</summary>

#### Source Provenance and PHI-Gated Filenames in the Analytical Catalog

The intake pipeline now records source provenance for ingested files in the analytical catalog, and filenames can optionally flow into the catalog for datasets attested as filename-safe.

**What changed:**

* **Source provenance in the analytical catalog.** Every file ingested through a cloud-drive connector now records an opaque source file identifier and a derived source URL in the analytical catalog row. These identifiers contain no operator or patient text and are always populated when a source connector is involved.
* **PHI-gated filename and folder path.** Filenames and source folder paths are treated as PHI by default and remain only on the access-controlled operational record. Connector folder mappings now support a PHI attestation that marks a dataset's filenames as non-PHI. When a dataset carries this attestation, the platform propagates the filename and folder path into the analytical catalog so downstream workflows can cite the source by name.
* **Per-dataset attestation.** The attestation is configured per folder mapping. The default posture (PHI assumed) applies to any mapping without an explicit opt-in. Operators who enable the attestation accept responsibility for ensuring all files in the dataset have non-PHI filenames.
* **Backward compatible.** Existing datasets and integrations are unaffected. The new provenance columns are nullable - rows written before this change and rows without a source connector continue to work without modification.

**What you need to do:**

* **No action required for existing integrations.** All current behavior is preserved. Source provenance columns populate automatically for new ingestions through cloud-drive connectors.
* **To enable filename propagation:** update the connector folder mapping for the target dataset to attest that filenames do not contain PHI. Once attested, new ingestions will include the filename and folder path in the analytical catalog.

</details>

<details>

<summary>Platform API: Provider-M2M Act-As-by-Email Delegation (July 2026)</summary>

#### Provider-M2M Act-As-by-Email Delegation

Provider machine-to-machine clients can now mint provider tokens on behalf of a specific clinician in their workspace by supplying the clinician's email address at token request time.

**What changed:**

* **Act-as by email.** The `client_credentials` token request now accepts an optional `provider_email` form parameter. When supplied, the platform resolves the email to a clinician with an active access grant in the credential's workspace and mints the provider token with that clinician as the subject. The token carries the resolved clinician's identity while the credential remains the machine-to-machine client, preserving audit traceability.
* **Workspace-scoped resolution.** The email lookup is scoped to the credential's own workspace. A clinician who exists only in a different workspace is not resolvable, preventing cross-workspace delegation.
* **Active grant required.** Only clinicians with an active provider access grant in the workspace can be targeted. Clinicians whose grant is pending, unverified, or otherwise inactive produce a generic error with no state disclosure.
* **No entity-ID delegation.** The `provider_entity_id` form parameter is reserved and always rejected. Email is the only supported delegation identifier. Supplying both `provider_email` and `provider_entity_id` returns an `invalid_request` error.
* **Backward compatible.** Omitting both parameters preserves the existing bound-entity mint behavior. Existing integrations are unaffected.
* **Audit trail.** Delegated token mints record both the acting credential's bound entity and the resolved target entity, along with a delegation flag, so audit queries can distinguish self-minted tokens from delegated ones.

**What you need to do:**

* **No action required for existing integrations.** Token requests without the new parameter continue to work as before.
* **To use act-as delegation:** pass the clinician's email address as `provider_email` in the `client_credentials` token request. Ensure the clinician has an active provider access grant in the workspace. The returned token's subject will be the resolved clinician.

</details>

<details>

<summary>Platform API: External Auth Claims Mapping for Customer-Attested Authorization (July 2026)</summary>

#### External Auth Claims Mapping for Customer-Attested Authorization

External-user sessions can now carry customer-attested authorization claims that the platform resolves into internal roles and grants on every turn. This enables fine-grained, per-session access control without requiring the customer to create or manage entities in the platform.

**What changed:**

* **Customer-attested auth claims on session creation.** The external-user session token grant now accepts an optional `auth_claims` field - a JSON array of normalized claim atoms, each with a `namespace`, `key`, and `value`. Claims are validated, deduplicated, and stored immutably on the session. Omitting the field produces an empty claim set (backward compatible). Claims are accepted only on the external-user session grant type; including them on any other grant type returns a 400 error.
* **Claim-to-role mapping CRUD.** A new set of endpoints under `/v1/{workspace_id}/external-auth-claim-mappings` lets workspace administrators create, list, get, and supersede mappings from exact claim tuples to internal external roles. Each mapping links one `(namespace, key, value)` tuple to one external role. Mappings are immutable - semantic changes supersede the prior mapping and create a new active row, preserving the full authorization history for audit. Only one active mapping per claim tuple is allowed (409 on conflict). Creating a mapping that references a role not in the workspace returns 422.
* **Resolve preview endpoint.** A read-only `POST .../resolve-preview` endpoint accepts a list of claim atoms and returns the mapped roles, effective grants, and count of unmapped claims. This lets administrators test their mapping configuration without creating a session.
* **Per-turn authorization resolution.** On every external-user text turn, the platform resolves the session's stored claims against active mappings, deduplicates the resulting roles, unions their active grants, and passes the resolved authorization context to the agent engine. A revoked or absent session fails the turn with 403. A resolution error fails the turn with 503. An empty claim set or zero mapped roles produces an empty authorization context (the turn proceeds but the session sees nothing that requires authorization).
* **Cached resolution with workspace-scoped invalidation.** The per-session resolution is cached and automatically invalidated when any mapping, role, or grant in the workspace changes. A short time-to-live bounds staleness if an invalidation signal is missed.
* **Claim contract.** Claims use a bounded canonical identifier pattern for namespace and key (lowercase alphanumeric with dots, colons, hyphens, and underscores; up to 64 characters). Values are preserved exactly as attested (up to 256 characters). A maximum of 32 claims per session is enforced. Matching is exact, case-sensitive tuple equality only - no wildcards, prefixes, regex, or aliasing.

**What you need to do:**

* **No action required for existing integrations.** External-user sessions without `auth_claims` continue to work as before with an empty claim set.
* **To adopt claims-based authorization:** define external roles, create claim mappings linking your claim tuples to those roles, assign grants to the roles, then pass `auth_claims` when minting external-user session tokens. Use the resolve-preview endpoint to verify your mapping configuration before going live.

</details>

<details>

<summary>Platform API: Shared LLM Client Lifecycle for Production Evals and Insights (July 2026)</summary>

#### Shared LLM Client Lifecycle for Production Evals and Insights

The LLM client used by production evaluations and the insights chat agent is now managed at the application lifecycle level rather than constructed per request.

**What changed:**

* **Single shared client for LLM-backed features.** Production evaluation scoring and the insights chat agent now share a single LLM client that is created when the platform starts and closed when it stops. Previously, each incoming request constructed its own client, which could leave network connections unclosed under sustained traffic.
* **Connection leak resolved.** Under high call volumes, the per-request client pattern accumulated idle network connections over time. The shared client reuses a single connection pool, eliminating the leak.
* **Bounded in-memory cache for enrichment lookups.** The internal cache used during enrichment resolution now prunes expired entries periodically so that a burst of distinct lookups cannot cause unbounded memory growth.
* **No API or behavioral change.** Production evaluation verdicts, insights chat responses, and all public API contracts remain the same. This is a reliability and resource-management improvement.

**What you need to do:**

* **No action required.** The improvement applies automatically. Workspaces that run high volumes of production evaluations or insights chat sessions benefit from reduced connection overhead.

</details>

<details>

<summary>Platform API: Approval Rejection Closes Approval State Cleanly (July 2026)</summary>

#### Approval Rejection Closes Approval State Cleanly

When a human reviewer declines a gated tool call, the agent now treats the approval slot as fully closed. Previously, the agent could occasionally behave as though a replacement approval request had already been queued, which could cause a subsequent retry to skip the gated tool call and leave the UI without an approval control to render.

**What changed:**

* **Approval state fully closed on rejection.** After a reviewer declines a gated action, the platform now explicitly signals to the agent that no approval request is pending and no replacement request has been created. This prevents the agent from incorrectly telling the user that a new request is queued or ready for approval.
* **Clearer rejection language.** The rejection signal now instructs the agent to avoid saying the action is still pending review, queued again, or ready for approval. A replacement request can only be created after the user explicitly asks to retry and a new gated tool call returns an awaiting-approval status.
* **No change to the approval or rejection API.** The reviewer-facing workflow and API surface remain the same. This change affects only the internal steering given to the agent after a rejection.

**What you need to do:**

* **No action required.** Agents that use gated tool calls benefit automatically. After a rejection, the agent reports the decline accurately on the current turn and does not falsely claim a replacement request exists.

</details>

<details>

<summary>Platform API: Voice Provider Prompt Aligned with Runtime Tool Capabilities (July 2026)</summary>

#### Voice Provider Prompt Aligned with Runtime Tool Capabilities

The system prompt sent to session-owning voice providers now describes only the tool capabilities actually available at runtime, rather than deriving tool instructions solely from the service's context graph definition.

**What changed:**

* **Runtime-aware tool contract.** The voice provider's system prompt now includes a tool-use section only when tools are actually wired and available for the call. Previously, tool instructions were generated based on whether the context graph declared tool bindings, which could produce a mismatch when engine setup or executor wiring prevented tools from being offered.
* **Sequential tool execution guidance.** The prompt now instructs the provider to call one tool at a time and wait for its result before calling another, matching the provider's configured execution mode. This prevents duplicate concurrent writes observed in live calls.
* **Write-tool confirmation guidance.** When write-capable tools are present, the prompt includes explicit instructions to confirm the action with the caller and get agreement before calling. Sessions without write tools omit this guidance.
* **Dedicated persona rendering.** The voice provider's persona instructions are now rendered specifically for the session-owning runtime, excluding references to asynchronous background tools, external events, and memory behaviors that do not apply to the provider's execution model. This prevents the provider from receiving a false execution contract.
* **No change to tool authorization.** The set of tools offered to the provider remains limited to those referenced in the service's context graph - the same authorization boundary as before.

**What you need to do:**

* **No action required.** This change improves prompt accuracy for existing voice services. Services using session-owning voice providers benefit automatically from more accurate tool instructions.

</details>

<details>

<summary>Platform API: Streaming Attach-Ticket Handshake Enforcement (July 2026)</summary>

#### Streaming Attach-Ticket Handshake Enforcement

The streaming session handshake now enforces audience, scope, and session binding on attach tickets, completing the split-trust security model for browser-to-worker connections.

**What changed:**

* **Audience enforcement at the handshake.** The streaming worker now validates that the token presented during the WebSocket handshake carries the dedicated streaming audience. A standard REST provider token is rejected at connection time - it can never open a streaming session.
* **Scope enforcement at the handshake.** The worker requires the `scribe:streams:connect` scope on the attach ticket. A token that carries only REST-oriented scopes (such as `scribe:sessions:write`) is rejected, even if it were otherwise valid.
* **Session binding enforcement.** The attach ticket's embedded `session_id` must match the session being connected to. A valid ticket for one session cannot be used to attach to a different session. This prevents a provider-level credential from being used to access sessions beyond the one the ticket was minted for.
* **Distinct rejection reasons.** The handshake returns specific close codes for audience/principal failures, missing scope, and session mismatch, making integration debugging straightforward.

**What you need to do:**

* **No changes required if you already use attach tickets.** Tickets minted through the `token_exchange` grant already carry the correct audience, scope, and session binding. This update enforces checks that were previously documented but not fully validated at the handshake.
* **Do not pass REST provider tokens to the streaming handshake.** The worker now actively rejects them. Use the `token_exchange` grant to obtain a purpose-built attach ticket for each streaming session.

</details>

<details>

<summary>Platform API: Token Exchange - Browser Attach Tickets for Streaming Sessions (July 2026)</summary>

#### Token Exchange - Browser Attach Tickets for Streaming Sessions

The token endpoint now supports an RFC 8693 token exchange grant that lets a backend service exchange a provider access token for a short-lived, session-bound browser attach ticket scoped exclusively to streaming.

**What changed:**

* **New grant type: `token_exchange`.** The existing token endpoint accepts `grant_type=token_exchange` following RFC 8693. The subject token must be a valid provider access token obtained through the `client_credentials` grant (provider M2M). The result is a short-lived attach ticket that a browser can present to the streaming worker to join a session.
* **Dedicated streaming audience.** The attach ticket is issued with a dedicated audience (`scribe-streaming`) that is distinct from the shared REST API audience. A ticket can never be used at a REST endpoint, and a REST provider token can never be presented as an attach ticket.
* **Single-purpose scope.** The ticket carries only the `scribe:streams:connect` scope - a new, non-REST scope that is not included in any standard role or provider scope set. It cannot satisfy any REST authorization check.
* **Session and provider binding.** Each ticket is bound to the caller-supplied `session_id` and inherits the `workspace_id` and `provider_entity_id` from the subject token. The streaming worker enforces these bindings at connection time.
* **5-minute TTL.** Attach tickets expire after five minutes. They are not refreshable - request a new exchange when a ticket expires.
* **Anti-escalation controls.** A ticket can never be exchanged for another ticket. The subject token must not already carry the streaming scope, and must hold session-write authority. Both audience separation and explicit scope checks enforce this.
* **Rate limiting.** Token exchange minting is subject to a per-provider sliding-window rate limit, layered on the existing per-IP rate limit. Exceeding the limit returns HTTP 429 with a `Retry-After` header.
* **Audit logging.** Successful and failed exchange attempts are recorded in the audit log, including the grant type, provider identity, session, and failure reason.

**Request parameters (form-encoded):**

| Parameter              | Required | Description                                                                                       |
| ---------------------- | -------- | ------------------------------------------------------------------------------------------------- |
| `grant_type`           | Yes      | Must be `token_exchange`                                                                          |
| `subject_token`        | Yes      | A valid provider access token (from `client_credentials` provider M2M)                            |
| `subject_token_type`   | No       | Defaults to `urn:ietf:params:oauth:token-type:access_token` if omitted; no other type is accepted |
| `requested_token_type` | No       | Defaults to `urn:ietf:params:oauth:token-type:access_token` if omitted; no other type is accepted |
| `audience`             | No       | Defaults to `scribe-streaming` if omitted; no other audience is accepted                          |
| `session_id`           | Yes      | UUID of the session the ticket is bound to                                                        |
| `scope`                | No       | Defaults to `scribe:streams:connect` if omitted; no other scope is accepted                       |

**Response:** Standard token response with `access_token`, `token_type`, `expires_in`, and `scope`.

**What you need to do:**

* **No changes required for existing integrations.** This is an additive grant type. Existing `client_credentials` and other grants continue to work unchanged.
* **To use token exchange,** first obtain a provider access token through the provider M2M `client_credentials` grant, then exchange it at the token endpoint with `grant_type=token_exchange`, passing the provider token as `subject_token` and the target `session_id`. Pass the resulting attach ticket to the browser for streaming connection. See the [OAuth2 documentation](https://docs.amigo.ai/developer-guide/platform-api/oauth2) for usage guidance.

</details>

<details>

<summary>Platform API: Provider M2M Client Credentials - Machine-to-Machine Provider Tokens (July 2026)</summary>

#### Provider M2M Client Credentials - Machine-to-Machine Provider Tokens

Backend applications can now obtain provider-scoped access tokens through the standard `client_credentials` OAuth2 grant, without requiring an interactive provider sign-in.

**What changed:**

* **Provider M2M client provisioning.** A new set of admin endpoints (`/admin/provider-m2m-clients`) lets workspace administrators create, list, inspect, and revoke machine-to-machine credentials bound to a specific provider identity. The one-time plaintext `client_secret` is returned only at creation.
* **Provider-scoped token minting.** A provisioned M2M client authenticates through the existing token endpoint with `grant_type=client_credentials`. The issued access token carries the provider's identity as its subject, uses `provider` as the principal type, and includes only the scopes allowed for that client. The token is identical in shape to one obtained through the interactive provider sign-in, so downstream services accept it without modification.
* **Scope control.** Each client is provisioned with an allowed scope set (defaulting to session creation, session reads, and note read/write). Token requests may down-scope by passing a `scope` parameter; requests that exceed the allowed set are rejected.
* **Optional lifetime.** Clients can be created with an expiry (1 to 3650 days) or as non-expiring credentials that must be explicitly revoked.
* **Revocation.** Revoking a client is a soft delete - the credential is deactivated and new token requests are rejected immediately. Already-issued short-lived tokens expire naturally. Both `POST .../revoke` and `DELETE .../{credential_id}` perform the same operation.
* **Rate limiting.** Provider M2M token minting is subject to a per-client sliding-window rate limit, layered on existing per-IP and per-client lockout protections. Exceeding the limit returns HTTP 429 with a `Retry-After` header.
* **Audit logging.** Client creation, revocation, successful mints, and failed mints (revoked, rate-limited, invalid scope) are all recorded in the audit log.

**What you need to do:**

* **No changes required for existing integrations.** This is an additive feature. Existing `client_credentials` grants for service accounts continue to work unchanged.
* **To use provider M2M clients,** provision a client through the admin endpoints, store the one-time secret securely, and use the `client_id`/`client_secret` pair in standard `client_credentials` token requests. See the [Provider M2M Clients](https://docs.amigo.ai/developer-guide/platform-api/oauth2-clients#provider-m2m-clients) documentation for endpoint details and usage guidance.

</details>

<details>

<summary>Platform API: Internal Auth Simplification - Shared Secrets Retired for Cluster-Internal Dispatch (July 2026)</summary>

#### Internal Auth Simplification - Shared Secrets Retired for Cluster-Internal Dispatch

Internal service-to-service authentication for outbound dispatch has been simplified. Several shared-secret environment variables that were previously required for outbound call and text initiation are no longer used.

**What changed:**

* **Outbound dispatch no longer requires a shared secret.** The platform services that initiate outbound calls and outbound text conversations no longer send or validate a dedicated shared-secret header for those internal dispatch requests. Transport-level trust boundaries replace the application-level shared secret.
* **Connector-to-platform authentication simplified.** The connector service no longer requires a static API key to call internal platform endpoints. Cluster-internal routes that the connector uses are secured at the transport boundary rather than by a per-request shared secret.
* **Outbound dispatch is always available when the service is running.** Previously, outbound call and text dispatch were disabled when the shared-secret environment variable was empty. The dispatch path is now unconditionally available, removing a class of silent misconfiguration where a missing secret would quietly disable outbound features.
* **Retired environment variables.** The following operator-facing environment variables are no longer read and can be removed from deployment configuration: `OUTBOUND_API_KEY`, `RUNTIME_ADMIN_API_KEY`, `CONNECTOR_RUNNER_PLATFORM_API_KEY`, `VOICE_AGENT_OUTBOUND_API_KEY`, and `MBP_BACKFILL_TOKEN`.

**What you need to do:**

* **No API integration changes required.** Public API contracts, request formats, and response shapes are unchanged. Outbound call and text creation endpoints continue to work as before.
* **Remove retired variables from deployment configuration.** If your deployment sets any of the variables listed above, they are no longer read and can be safely removed.

</details>

<details>

<summary>Platform API: Environment Variable Cleanup - Four Runtime Knobs Retired (July 2026)</summary>

#### Environment Variable Cleanup - Four Runtime Knobs Retired

Four environment-level configuration knobs have been retired and replaced with code-level defaults or narrower runtime boundaries.

**What changed:**

* **Realtime voice model is now a code-level default.** The voice model used for speech-to-speech sessions is no longer configurable through an environment variable. The platform uses a fixed default (`gpt-realtime-2.1`) that was validated for reliable tool calling. Per-agent provider configuration still overrides the default on a per-call basis.
* **Cartesia voice identifier is now a code-level default.** The default text-to-speech voice for calls whose agent has no per-agent voice configuration is now set in code rather than through an environment variable. The previously deployed value was stale and never matched the live fleet default - the new constant reflects the actual production value.
* **Workspace scope eager provisioning is always on.** Workspace scope provisioning now fires automatically when the provisioning job is configured, without requiring a separate enablement flag. The scheduled reconciliation job remains the durable backfill.
* **Egress drain runs only in production.** The external-write proposal egress drain - which delivers human-approved proposals to their destination systems - is now gated by a code-level environment boundary rather than a per-deployment configuration flag. Non-production environments never push approved proposals to real external systems.

**What you need to do:**

* **No integration changes required.** These were operator-facing environment knobs, not API contract changes. Existing API contracts, request formats, and response shapes are unchanged.
* **Remove retired variables from deployment configuration.** If your deployment sets `REALTIME_MODEL`, `CARTESIA_VOICE_ID`, `WORKSPACE_SCOPE_EAGER_PROVISION_ENABLED`, or `EGRESS_DRAIN_DISABLED`, they are no longer read and can be removed.

</details>

<details>

<summary>Platform API: Empty Tool Description Warning for Realtime Voice Sessions (July 2026)</summary>

#### Empty Tool Description Warning for Realtime Voice Sessions

Realtime voice sessions now detect and warn when a tool is declared without a description, preventing a class of silent tool-selection failures.

**What changed:**

* **Empty description detection.** When the platform translates tools for a Realtime voice session, any tool whose description is empty or whitespace-only now triggers a warning. The warning identifies the tool by name only and does not include any caller or session data.
* **Metrics emitted.** A counter metric is incremented for each description-less tool, tagged by tool name, so operations teams can alert on the condition.
* **Tool still declared.** A missing description is degraded behavior, not fatal. The tool remains in the session's tool set. However, a description-less tool may lose tool selection to any better-described peer - the model tends to prefer tools with clear descriptions.
* **No change for well-described tools.** Tools with non-empty descriptions are unaffected.

**Why it matters:**

If a tool arrives at the voice session without a description - for example, because graph-authored instructions were not merged correctly - the model may silently ignore it in favor of other tools that have descriptions. This warning makes that failure mode visible rather than requiring manual debugging of missed tool calls.

**What you need to do:**

* **No integration changes required.** The warning is automatic. Existing API contracts, request formats, and response shapes are unchanged.
* **Review tool descriptions.** If you see warnings for specific tools, ensure those tools have meaningful descriptions in their catalog entry or that their context graph states include usage instructions that get merged into the description.

</details>

<details>

<summary>Platform API: Graph Tool Instructions Preserved for Realtime Voice Sessions (July 2026)</summary>

#### Graph Tool Instructions Preserved for Realtime Voice Sessions

Realtime voice sessions now include per-tool usage instructions authored in the context graph, matching the behavior of the in-house stateful runtime.

**What changed:**

* **Tool instructions merged from the context graph.** When a service's context graph attaches usage instructions to a tool reference in one or more states, those instructions are now appended to the tool's description before the tool set is sent to the voice provider. Previously, Realtime sessions received only the base tool catalog description and silently dropped any graph-authored instructions.
* **Cross-state deduplication.** If the same tool appears in multiple states with identical instructions, the instruction text is included only once. Instructions from different states are merged in graph order.
* **No change for tools without instructions.** Tools that have no additional instructions in the context graph are sent with their catalog description unchanged.
* **No change for in-house voice sessions.** The in-house stateful runtime already applied per-state tool instructions. This fix brings Realtime sessions to parity.

**What you need to do:**

* **No integration changes required.** The fix is automatic for all Realtime voice services. Existing API contracts, request formats, and response shapes are unchanged.
* **Review tool behavior after deployment.** If your context graph includes tool instructions that were previously ignored during Realtime sessions, those instructions now take effect. Verify that the voice agent's tool-calling behavior matches expectations.

</details>

<details>

<summary>Platform API: Structural Tool-Calling Enforcement for Realtime Voice Sessions - Reverted (July 2026)</summary>

#### Structural Tool-Calling Enforcement for Realtime Voice Sessions - Reverted

~~Realtime voice sessions that use reasoning-class speech-to-speech models now enforce tool calling structurally rather than relying solely on the prompt contract.~~

**This change has been reverted.** The structural enforcement approach - which injected a decline signal and set the session tool choice to force a tool selection on every caller turn - was not effective under real audio conditions. The model consistently selected the decline signal instead of calling the intended tool, and the fallback mechanism did not reliably break the cycle. The enforcement has been removed.

**Current state:**

* **No structural tool-calling enforcement.** Realtime voice sessions use `auto` tool choice. The platform does not force the model to select a tool on every turn.
* **No tool name restriction.** The previously reserved name `no_tool_needed` is no longer injected or restricted. Services may use any tool name.
* **Known limitation: autonomous tool calling is not reliable on Realtime voice sessions.** Under `auto` tool choice, reasoning-class speech-to-speech models may narrate about an action instead of emitting a tool call, particularly when the agent persona includes conversational behaviors. Voice services that depend on autonomous tool calling should use the in-house voice provider, which calls tools reliably. Realtime voice sessions are appropriate for tool-less services or for the test-only forced-first-tool configuration.

**What you need to do:**

* **Move tool-dependent voice services to the in-house provider.** If your service requires autonomous tool calling during voice sessions, configure it to use the in-house voice provider rather than the Realtime speech-to-speech provider.
* **No integration changes required.** The revert is automatic. Existing API contracts, request formats, and response shapes are unchanged.
* **The `no_tool_needed` name restriction is lifted.** If you renamed a tool to avoid the previously reserved name, you may rename it back.

</details>

<details>

<summary>Platform API: Autonomous Tool Calling for Realtime Voice Sessions (July 2026)</summary>

#### Autonomous Tool Calling for Realtime Voice Sessions

Realtime voice sessions that use reasoning-class speech-to-speech models now call tools autonomously when the service's context graph binds tools. Previously, these sessions could speak about an action without ever executing the corresponding tool call.

**What changed:**

* **Prompt-driven autonomous tool calling.** When a service's context graph includes tool-bound states, the generated system prompt now ends with an explicit tool-use contract that reliably triggers tool calls for look-ups and actions rather than letting the model answer from its own knowledge. The contract makes clear that conversational scripts (greetings, step guidance) govern only what the agent says - they never excuse or defer a tool call. An anti-narration rule prevents the agent from claiming it is "checking" or "looking up" something without actually calling the tool in that turn. Read-only look-ups are called immediately; mutating actions are confirmed with the caller first. The directive is omitted for services with no tool bindings.
* **Reasoning effort returned to the low-latency default.** The earlier interim change that raised the default reasoning budget for tool-bound sessions has been reverted. Testing confirmed that the prompt contract alone drives reliable tool calling at the minimal reasoning budget, so all sessions - with or without tools - now default to the lowest reasoning effort to preserve first-audio latency. A per-service override in the voice configuration still takes precedence.
* **No change for tool-less services.** Services whose context graphs do not bind any tools are unaffected. Their prompt structure and reasoning budget remain unchanged.

**What you need to do:**

* **No integration changes required.** The prompt adjustments are automatic based on the service's context graph. Existing voice configurations, API contracts, and response shapes are unchanged.
* **Review per-service reasoning effort overrides.** If you previously raised reasoning effort in your voice configuration to work around missed tool calls, the prompt-driven fix makes that override unnecessary. Removing it returns first-audio latency to the platform default.

</details>

<details>

<summary>Platform API: Per-Modality Voice COGS Tracking (July 2026)</summary>

#### Per-Modality Voice COGS Tracking

Realtime voice usage is now priced per modality - audio and text, input and output, cached and uncached - instead of a single blended token rate. This gives operators accurate cost-of-goods-sold breakdowns for voice workloads where audio and text token costs differ significantly.

**What changed:**

* **Per-modality cost tracking.** Realtime voice conversations now record six granular token categories: audio input, audio output, text input, text output, cached audio input, and cached text input. Each category is priced against its own rate, reflecting the large cost difference between audio and text tokens.
* **Blended pricing unchanged for non-voice models.** Chat and text models continue to use the existing input, output, and cached token pricing. The new modality categories apply only to realtime voice sessions.
* **Accurate COGS reporting.** Monthly cost-of-goods-sold reporting now includes a per-modality breakdown for realtime voice, replacing the previous blended estimate.

**What you need to do:**

* **No integration changes required.** Per-modality tracking is automatic for realtime voice sessions. Existing API contracts, request formats, and response shapes are unchanged.
* **Review voice cost reports.** If your organization uses realtime voice, COGS reports now reflect the actual per-modality cost split rather than a blended approximation.

</details>

<details>

<summary>Scribe API: Workspace-Level Allocation Rate Limit and Fleet Exhaustion Visibility (July 2026)</summary>

#### Workspace-Level Allocation Rate Limit and Fleet Exhaustion Visibility

The streaming session allocation endpoint now enforces a workspace-scoped rate limit in addition to the existing per-session cooldown, and reports capacity metrics that make allocation failures attributable to a specific workspace.

**What changed:**

* **Workspace-level allocation rate limit.** A sliding-window rate limit now caps the number of streaming session allocations a single workspace can request within a rolling time window. This is layered on top of the existing per-session cooldown so that one workspace opening many sessions cannot exhaust shared streaming capacity and affect other tenants. The default budget is deliberately generous to accommodate legitimate reconnection bursts across many providers.
* **Consistent retryable response.** When the workspace limit is exceeded, the allocate endpoint returns `503 Service Unavailable` with a `Retry-After` header, the same contract used by the per-session cooldown and capacity-exhaustion paths. SDKs that already implement backoff-and-retry handle this response without changes.
* **Fleet exhaustion metrics.** Allocation failures caused by capacity exhaustion are now tagged with the requesting workspace, making it possible to attribute shared-capacity pressure to a specific tenant. Separate tags distinguish genuine capacity exhaustion from transient network errors.
* **Throttle-offline visibility.** If the backing store used for rate-limit state is temporarily unavailable, the rate limits fail open (allocation is allowed) and a counter is emitted so monitoring can distinguish "no throttling needed" from "throttling temporarily offline."

**What you need to do:**

* **No integration changes required.** The 503 + `Retry-After` response shape is unchanged. SDKs and clients that respect `Retry-After` handle the new workspace-level limit automatically.
* **Review allocation patterns if you receive 503s with a workspace-scope message.** The response detail distinguishes per-session cooldown ("Too many allocation requests for this session") from the workspace limit ("Too many allocation requests for this workspace"). If you see the workspace-level message, your workspace is allocating sessions faster than the platform permits.

</details>

<details>

<summary>Platform API: External Identity Binding Documentation Consolidated (July 2026)</summary>

#### External Identity Binding Documentation Consolidated

Internal platform documentation for external identity binding has been reorganized and corrected. No API or behavioral changes are included.

**What changed:**

* **Consolidated guidance.** Scattered references to external identity binding across guides and runbooks have been unified into a single reference document.
* **Stale field reference corrected.** Documentation that referenced a deprecated identifier field now uses the current field name.

**What you need to do:**

* **No action required.** This is a documentation-only change with no impact on API contracts or runtime behavior.

</details>

<details>

<summary>Platform API: Environment Configuration Simplified - Volume Paths, Transcript Settings, and Connector URL (July 2026)</summary>

#### Environment Configuration Simplified - Volume Paths, Transcript Settings, and Connector URL

Several environment variables that were previously required for deployment have been retired or made mandatory. Volume paths for call recordings and audit exports are now derived automatically from the deployment stage, and the connector-runner internal URL no longer falls back to a default.

**What changed:**

* **Call recording and audit export volume paths derived automatically.** The platform now computes volume paths for call recordings and audit exports from the deployment stage rather than reading them from separate environment variables. The `CALL_RECORDING_VOLUME_PATH` and `AUDIT_EXPORT_VOLUME_PATH` environment variables are no longer read.
* **Database schema passed inline.** Each service now declares its own database schema as a fixed constant rather than reading it from a shared `LAKEBASE_SCHEMA` environment variable. The `LAKEBASE_SCHEMA` environment variable is no longer read by services. Scripts that still need a per-invocation schema can continue to set it for their own use.
* **Connector-runner URL now required.** The connector-runner internal URL no longer falls back to a built-in default. If `CONNECTOR_RUNNER_INTERNAL_URL` is not set, the service fails at startup. Every deployed environment already provisions this value.
* **Legacy transcript settings retired for meeting bots.** The `TRANSCRIPT_S3_BUCKET` and `TRANSCRIPT_S3_PREFIX` settings for the meeting-bot control plane have been removed. Transcript persistence uses the scribe artifact contract exclusively. The `SCRIBE_ARTIFACTS_S3_PREFIX` setting is now a fixed layout constant and no longer read from environment configuration.
* **Text interaction wait budget is now a fixed constant.** The `TEXT_WAIT_FOR_FINAL_TIMEOUT_SECONDS` environment variable is no longer read. The synchronous wait budget for text turns is a fixed platform constant.

**What you need to do:**

* **Remove retired environment variables.** If your deployment sets any of the following, they are no longer read and can be removed: `CALL_RECORDING_VOLUME_PATH`, `AUDIT_EXPORT_VOLUME_PATH`, `LAKEBASE_SCHEMA`, `TRANSCRIPT_S3_BUCKET`, `TRANSCRIPT_S3_PREFIX`, `SCRIBE_ARTIFACTS_S3_PREFIX`, `TEXT_WAIT_FOR_FINAL_TIMEOUT_SECONDS`.
* **Ensure `CONNECTOR_RUNNER_INTERNAL_URL` is set.** If your deployment relied on the previous built-in default for the connector-runner URL, add the variable explicitly. All standard deployments already provision this value.
* **No API or integration changes required.** These are deployment-configuration changes only. API contracts, request formats, and response shapes are unchanged.

</details>

<details>

<summary>Platform API: Drive Sync Resilience - Interrupted Syncs No Longer Strand Files (July 2026)</summary>

#### Drive Sync Resilience - Interrupted Syncs No Longer Strand Files

Google Drive folder syncs now handle mid-sync interruptions gracefully instead of leaving batches and files stranded in an unrecoverable preparing state.

**What changed:**

* **Per-file fault isolation broadened.** Previously, only a narrow set of known file-level errors were caught during sync. Any other transient failure - such as a network timeout or an unexpected service error while fetching a single file - would abort the entire folder sync. Now, any per-file failure is caught, logged, and skipped so the remaining files in the folder continue processing.
* **Batch marked failed on folder-level errors.** If a folder-level failure occurs (for example, an authentication or listing error, or a failure finalizing the batch), the batch is now marked as failed rather than left in a preparing state. This makes the failure visible in the batch list and prevents orphaned files from accumulating silently.
* **Sync error recorded on the source.** When a sync fails at the source level, the error is now recorded on the source record so it is visible through the source listing. Previously, a mid-sync abort left no trace on the source, and the failure was only observable through stranded batches.
* **No change to successful syncs.** The sync workflow, deduplication behavior, file-size limits, and per-folder caps are unchanged for syncs that complete without errors.

**What you need to do:**

* **No action required.** Syncs that previously failed silently now surface failures visibly on the batch and source. If you monitor batch or source status, you may see `failed` statuses where previously the sync appeared to hang indefinitely at a preparing state.

</details>

<details>

<summary>Platform API: Simplified Rolling-Transcript Storage Key Layout (July 2026)</summary>

#### Simplified Rolling-Transcript Storage Key Layout

The rolling-transcript artifact path no longer includes an environment segment. Existing snapshots at the previous path are not migrated automatically.

**What changed:**

* **Environment segment removed from transcript snapshot keys.** The rolling-transcript snapshot path previously included an `env=<environment>` segment between the storage prefix and the workspace identifier. That segment has been removed. The new layout is `<prefix>/workspace=<wid>/provider=<pid>/session=<sid>/raw-transcript/snapshots/latest.json`.
* **Consistent key structure.** The snapshot key now follows the same workspace/provider/session hierarchy used by other artifact keys, without an extra environment partition.

**What you need to do:**

* **No integration changes required.** Rolling-transcript snapshots are internal artifacts consumed by the platform. If you have tooling that reads snapshot keys directly using the old `env=` layout, update it to use the new path structure.
* **Old snapshots are not relocated.** Previously written snapshots remain at their original keys. Active sessions will write new snapshots to the updated path.

</details>

<details>

<summary>Platform API: Feature Gates Retired - Six Capabilities Now Always On (July 2026)</summary>

#### Feature Gates Retired - Six Capabilities Now Always On

Six platform capabilities that were previously behind per-environment or per-workspace feature gates are now unconditionally enabled. No new API surface or behavioral changes are introduced - each capability works exactly as it did when its gate was enabled.

**What changed:**

* **Multi-provider model routing.** Non-voice engage requests route through the provider abstraction layer for all wired model families. Previously gated by an environment variable; now always active.
* **Cartesia speech-to-text for English callers.** English-language voice calls use the Cartesia ink-2 STT provider by default when the API key is provisioned. Previously required an explicit per-environment opt-in flag.
* **Inbound channel-turn processing.** The background consumer that drains inbound channel work (email turns) now runs unconditionally on every pod. Previously required a per-environment enable flag; environments without bound channel use cases simply process an empty work list.
* **World-model read tools on the MCP server.** The MCP server's read surface - entity reads and workspace data queries - is now always registered when the platform's database session is available. Previously gated by an environment variable.
* **Trace export endpoint.** The read-only trace export endpoint is now always active. Previously gated by an environment variable that returned 404 when disabled.
* **Provider-principal authentication.** The provider-principal login flow is now enabled by default, with per-workspace control retained through the existing feature flag. The environment-variable fallback that defaulted to off has been removed.

**What you need to do:**

* **Remove retired environment variables.** If your deployment sets any of the following, they are no longer read and can be removed: `PROVIDER_ROUTER_ENABLED`, `CARTESIA_STT_ENABLED`, `CHANNEL_TURN_CONSUMER_DISABLED`, `MCP_WORLD_TOOLS_ENABLED`, `OTEL_TRACE_EXPORT_ENABLED`, `PROVIDER_PRINCIPAL_ENABLED`.
* **No integration changes required.** All six capabilities behave identically to how they worked when their respective gates were enabled. Existing API contracts, request formats, and response shapes are unchanged.

</details>

<details>

<summary>Platform API: Required Tool Retry for Realtime Voice Sessions (July 2026)</summary>

#### Required Tool Retry for Realtime Voice Sessions

Realtime voice sessions that require a specific tool call from the first caller response now retry automatically instead of failing immediately when the model does not emit the expected tool.

**What changed:**

* **Automatic retry on missing required tool.** When a realtime voice session is configured to require a specific tool call from the first caller response and the model completes that response without emitting the tool, the platform now sends a single retry with an explicit tool-choice override before failing. Previously, the session raised an error immediately.
* **Cancelled-response tolerance.** If the first caller response is cancelled because a new caller turn was detected, the session now waits for the next response rather than failing. This handles cases where the caller speaks again before the model finishes its initial reply.
* **Bounded retry.** The retry is attempted at most once. If the retried response also completes without the required tool, the session ends with an error. This prevents unbounded retry loops.
* **Stricter end-of-session validation.** The session now verifies that the required tool was emitted before the realtime connection closes. Previously, certain timing conditions could allow the connection to end without the required tool having been called.

**What you need to do:**

* **No action required.** Sessions that previously failed when the model omitted the required tool on the first attempt now have one automatic retry. If your integration handles these failures with external retry logic, the additional resilience may reduce the number of externally retried sessions.

</details>

<details>

<summary>Platform API: Voice Tool Authorization Aligned Across Playground and Phone Calls (July 2026)</summary>

#### Voice Tool Authorization Aligned Across Playground and Phone Calls

Realtime voice sessions now enforce a consistent, graph-derived tool authorization boundary regardless of whether the call originates from the browser Playground or a phone number.

**What changed:**

* **Graph-authorized tool set.** Realtime voice sessions now receive only the tools referenced across the service's context graph states, resolved against the registered platform tool catalog. Previously, the full platform tool set was passed to the provider without graph-level filtering.
* **Mandatory allowlist enforcement.** Every tool call from the realtime provider is checked against the authorized set before execution. Calls to tools outside the set are denied with an explicit error returned to the provider. This applies to both Playground and phone-originated sessions.
* **Playground greeting optimization.** Browser-originated voice sessions that use a session-owning provider no longer pre-render a greeting through the default speech pipeline. The provider generates its own opening audio, removing the dead-air delay that occurred when a pre-rendered greeting was discarded.
* **Consistent credential resolution.** Playground sessions skip telephony credential lookup since they connect through the browser rather than a phone network leg. This eliminates unnecessary fallback resolution and aligns the session startup path with phone calls.

**What you need to do:**

* **Review context graph tool references.** Tools that the realtime provider could previously call but that are not referenced in any context graph state will now be denied. Ensure every tool the agent should use during a voice session is referenced in at least one state of the service's context graph.
* **No other action required.** Playground and phone call behavior is otherwise unchanged.

</details>

<details>

<summary>Platform API: Playground Voice Test Calls Use the Configured Voice Provider (July 2026)</summary>

#### Playground Voice Test Calls Use the Configured Voice Provider

Browser voice test calls initiated from the Playground now respect the service's configured session-owning voice provider instead of always running through the default in-house voice pipeline.

**What changed:**

* **Provider-aware test calls.** When a service is configured with a session-owning voice provider, Playground voice test calls now route through that provider. Previously, test calls always used the default pipeline regardless of the service's voice configuration.
* **Consistent call lifecycle.** Test calls through a session-owning provider now emit the same call-started, call-ended, and call-intelligence lifecycle events as production calls, so they appear in conversation history and the Runs surface.
* **Slot management.** Test calls acquire and release session slots with the same guarantees as production calls, including bounded duration caps to prevent slot leaks.
* **Tool and workflow parity.** The configured provider receives the full tool set and workflow prompt from the service's agent, matching production behavior.

**What you need to do:**

* **No action required.** Playground voice test calls automatically use the service's configured voice provider. If you were previously seeing different behavior between Playground tests and production calls on services with a session-owning provider, those differences are now resolved.

</details>

<details>

<summary>Platform API: Exhaustive Skill Reference Scanning on Delete (July 2026)</summary>

#### Exhaustive Skill Reference Scanning on Delete

The skill deletion safety check now scans all context graphs in the workspace before allowing a skill to be removed, rather than examining only the first page of results.

**What changed:**

* **Complete reference scan.** The delete guard now pages through every context graph in the workspace when checking whether a skill is still referenced. Previously, the scan examined only the default first page of context graphs, which could miss references in older graphs and allow deletion of a skill still bound to a live context graph version.
* **Batch version lookup.** The scan retrieves the latest version of all context graphs in a single query instead of one query per graph, reducing latency for workspaces with many context graphs.
* **Targeted service lookup.** Services bound to referenced context graphs are now found through a direct lookup by context graph identity, replacing the previous approach that scanned only the first page of services in the workspace.
* **Fail-closed safety cap.** If the workspace contains too many context graphs to scan completely, the delete request returns a `503` response instead of proceeding on a partial scan. This protects against removing a skill that is still in use.

**What you need to do:**

* **No action required for most workspaces.** The change makes skill deletion safer by ensuring all references are found before the delete proceeds. Workspaces with a large number of context graphs may see slightly longer delete times due to the exhaustive scan.
* **Handle `503` on skill delete.** In the unlikely event that a workspace exceeds the scan safety cap, the delete request will return `503 Service Unavailable`. Retry after reducing the number of unused context graphs or contact support.

</details>

<details>

<summary>Platform API: Tiered Permission Enforcement for Workspace Data Query Invocation (July 2026)</summary>

#### Tiered Permission Enforcement for Workspace Data Query Invocation

Invoking a stored workspace data query now enforces a tiered permission check based on whether the query template performs read-only or write-capable operations.

**What changed:**

* **Read-only queries remain invokable at the view tier.** Stored query templates that contain only read operations continue to require the `Workspace.view` permission, matching prior behavior. These queries run under a read-only transaction backstop that rejects any unintended write.
* **Write-capable queries require the update tier.** Stored query templates classified as write-capable (DML operations against custom schemas) now require `Workspace.update` permission. Callers with only `Workspace.view` receive a `403` response when invoking a write-capable template.
* **Consistent with MCP invoke path.** The tiered gate mirrors the permission split already enforced by the MCP data-access invoke path, so both invocation methods apply the same authorization rules.
* **No change to query creation.** The create-time validation rules for stored query templates are unchanged. This update only affects the invoke path.

**What you need to do:**

* **Verify API key permissions for write-capable queries.** If your integration invokes stored query templates that perform write operations (inserts, updates, deletes against custom schemas), confirm that the API key or credential carries the `Workspace.update` permission. View-tier credentials that previously invoked these templates will now receive a `403` response.
* **No action needed for read-only queries.** Integrations that invoke only read-only stored queries continue to work with `Workspace.view` credentials.

</details>

<details>

<summary>Platform API: Permission-Based Access Control for API Keys and MCP Tools (July 2026)</summary>

#### Permission-Based Access Control for API Keys and MCP Tools

API key authentication and MCP tool authorization now enforce permission-based access control instead of role-name checks. Every API key is linked to a canonical platform role with an explicit permission set, and MCP tools verify individual permissions rather than checking for a named role.

**What changed:**

* **API keys require a canonical role link.** New API keys must reference a recognized platform role and include an explicit, non-empty permission list. The permission list can only narrow the grants of the linked role, never expand them.
* **Existing keys with empty permission lists inherit role defaults.** API keys created before this change that have no stored permission list temporarily receive the full default grants of their assigned role. This preserves backward compatibility while workspaces migrate to explicit scopes.
* **MCP tools enforce individual permissions.** World-model read tools require `Data:View`. SQL and function-call tools require `Data:Query`. Surface configuration tools require `Surface:Create`. Platform function tools require `Workspace:View`. Each tool returns a structured error envelope when the caller lacks the required permission.
* **Prompt log and trace export routes require `Audit:View`.** These endpoints previously checked for admin or owner role names. They now verify the `Audit:View` permission grant on the caller's effective role.
* **Workspace management routes use permission checks.** Update, provision, archive, environment conversion, and test-traffic configuration routes now verify permissions on the caller's resolved role object rather than checking the role name string.
* **API key creation and listing return `503` when canonical roles are unavailable.** If the platform's role registry is missing or ambiguous, key creation and listing fail with a clear service-unavailable response instead of silently proceeding.
* **No change for standard admin and owner keys.** The admin and owner roles include all previously available permissions by default. Existing integrations using those roles continue to work without modification.

**What you need to do:**

* **Verify custom permission lists.** If your API keys use a narrowed permission list, confirm the list includes the permissions needed for your integration's MCP tools and API routes. Add `Data:View` for world-model reads, `Data:Query` for SQL and function calls, `Audit:View` for prompt logs and trace exports, and `Surface:Create` for surface configuration tools.
* **Update integrations that check role names.** If your code inspects the role name string returned from API key endpoints, switch to checking the permission list instead. Role names remain available but are no longer the authorization mechanism.
* **Handle `503` responses on key management endpoints.** API key creation and listing can now return `503 Service Unavailable` if the canonical role configuration is unavailable. Add retry logic for these responses in automation workflows.

</details>

<details>

<summary>Platform API: Compliance Routes Require Audit.view Permission (July 2026)</summary>

#### Compliance Routes Require Audit.view Permission

The compliance dashboard, HIPAA report, and access review endpoints now enforce the `Audit.view` permission. Previously these routes were accessible to any admin or owner API key without an explicit permission check.

**What changed:**

* **Permission enforcement on compliance endpoints.** The compliance dashboard, HIPAA report, and access review routes now verify that the caller's role includes the `Audit.view` permission before processing the request. Requests from roles that lack this permission receive a `403` response.
* **No change for standard admin and owner keys.** The admin and owner roles include `Audit.view` by default, so existing integrations using those roles continue to work without modification.

**What you need to do:**

* **Verify custom roles.** If your workspace uses custom roles that previously accessed compliance endpoints, confirm those roles include the `Audit.view` permission. Add the permission if needed to restore access.

</details>

<details>

<summary>Platform API: External Identity Binding for Memory v2 (July 2026)</summary>

#### External Identity Binding for Memory v2

Memory v2 now supports binding conversation memory to an external patient or user identity, so that behavioral memory dimensions persist across conversations and channels for the same individual.

**What changed:**

* **External identity binding at conversation start.** When a conversation is created with an external identity reference, the platform resolves the reference against workspace records and loads the matching memory dimensions into the conversation context.
* **Persistent cross-conversation memory.** Memory updates during a conversation are written back to the external identity's record. Subsequent conversations for the same identity receive the updated memory state.
* **Graceful fallback on resolution failure.** If the external identifier does not match a known workspace record, the conversation proceeds without persistent memory. The resolution failure is recorded in conversation metadata.
* **Concurrent conversation support.** Multiple conversations can bind to the same external identity. Writes from each conversation are reconciled asynchronously after the conversation ends.

**What you need to do:**

* **Ensure external records exist before binding.** The external identity must be present in the workspace before conversation creation. Conversations that reference an unknown identifier will start without persistent memory.
* **Allow for asynchronous reconciliation.** Memory updates from a completed conversation may not be immediately visible to a new conversation for the same identity. If your workflow creates back-to-back conversations for the same individual, allow a short interval between them.

</details>

<details>

<summary>Scribe: Session Allocation for Streaming Workers (July 2026)</summary>

#### Session Allocation for Streaming Workers

Browser-based clinical recording sessions now allocate a dedicated streaming worker before connecting over WebSocket. The new allocation step sits between session creation and the WebSocket attach, giving the SDK an explicit host and expiration window.

**What changed:**

* **Allocate endpoint.** A new `POST /sessions/{session_id}/allocate` endpoint assigns a dedicated streaming worker to an existing session and returns a routable host and an expiration timestamp. The SDK opens its WebSocket connection to the returned host.
* **Session state guard.** Allocation is accepted only for sessions in a state that supports streaming. Sessions that have already completed or moved to a terminal state are rejected with a `409 Conflict` response.
* **Capacity-aware retryable errors.** When no streaming capacity is available or the allocation cannot be fulfilled, the endpoint returns `503 Service Unavailable` with a `Retry-After` header. The SDK treats this as a retryable signal and backs off before retrying.
* **Per-session cooldown.** Repeated allocation requests for the same session within a short window are throttled to prevent a single caller from consuming shared capacity. Throttled requests receive the same retryable `503` response.
* **Expiration window.** The allocation response includes an `expires_at` timestamp representing the session ceiling. A reconnect after expiration re-allocates a fresh worker.

**What you need to do:**

* **No action required for existing integrations.** This endpoint supports the upcoming browser recording SDK. Existing session creation and management APIs are unchanged. If you are building a custom integration against the streaming flow, call allocate after creating a session and before opening the WebSocket connection.

</details>

<details>

<summary>Scribe: Resumable WebSocket Streaming for Browser Recording Sessions (July 2026)</summary>

#### Resumable WebSocket Streaming for Browser Recording Sessions

Browser-based clinical recording sessions now connect to the scribe worker over a resumable WebSocket, replacing the previous connection model with a protocol that supports pause, resume, reconnect, and structured lifecycle transitions.

**What changed:**

* **WebSocket streaming endpoint.** A new WebSocket endpoint accepts browser microphone audio in real-time. Authentication uses the provider JWT passed through the WebSocket sub-protocol header; workspace scope is derived from the token claims.
* **Session lifecycle over the socket.** The connection drives the full session state machine: attach validation, first-audio activation, pause and resume, clean end, and unclean disconnect. Each transition is fenced so that a stale or superseded connection cannot overwrite state owned by a newer attach.
* **Reconnect support.** If a connection drops, the browser SDK can reconnect to the same session. The worker rehydrates accumulated transcript state and resumes from the last acknowledged audio offset. An opening handshake frame lets the client declare how much audio it has already delivered.
* **Structured close codes.** The endpoint uses typed close codes to distinguish authentication failure, session-not-found, terminal-state rejection, capacity limits, clean completion, fatal errors, and recoverable disconnects. Clients can use these codes to decide whether to retry, reconnect, or surface an error.
* **Pause and resume.** A pause control frame flushes a transcript snapshot and releases the speech-to-text upstream connection. A subsequent resume frame opens a fresh upstream connection while preserving transcript ordering through the session.
* **Per-worker capacity guard.** Each worker enforces a concurrent session limit. A connection that arrives when the worker is at capacity receives a specific close code rather than silently failing.
* **Periodic acknowledgment frames.** The worker sends periodic acknowledgment frames that report the last processed audio offset. These frames also serve as server-initiated keepalives to prevent idle-timeout disconnects on intermediate infrastructure.

**What you need to do:**

* **No action required for existing integrations.** This endpoint supports the upcoming browser recording SDK. Existing session creation and management APIs are unchanged.

</details>

<details>

<summary>Platform API: Use-Case Ownership Endpoints and List Proxy Removed (July 2026)</summary>

#### Use-Case Ownership Endpoints and List Proxy Removed

The use-case list proxy, ownership assignment, ownership release, and ownership query endpoints have been removed from the Platform API. The separate ownership concept is retired - service binding is now the only linkage between a workspace and a channel use case.

**What changed:**

* **Use-case list endpoint removed.** The `GET /use-cases` proxy that filtered channel-manager use cases by workspace ownership is no longer available. Consumers that need to enumerate use cases should query the channel-manager API directly.
* **Ownership endpoints removed.** `PUT /{use_case_id}/ownership`, `DELETE /{use_case_id}/ownership`, `GET /{use_case_id}/ownership`, and `GET /use-cases/ownership` are removed. There is no replacement - ownership as a separate concept is retired.
* **Ownership permission removed.** The `Channel.ManageOwnership` permission no longer exists. API keys and roles that referenced it will no longer see it in permission lists. No other Channel permissions are affected.
* **Service binding no longer requires prior ownership.** Binding a use case to a service now requires only that the use case exists and the caller has `Channel.create` permission. The previous requirement to first assign ownership before binding is gone.
* **Ownership data dropped.** Existing ownership records have been removed. Workspaces that previously assigned ownership do not need to take any action - their service bindings continue to function.

**What you need to do:**

* **Remove calls to ownership endpoints.** Any integration that assigned, released, or queried use-case ownership should remove those calls. Service binding (`PUT /{use_case_id}/service-binding`) is the only workspace-to-use-case linkage going forward.
* **Remove use-case list proxy calls.** If your integration listed use cases through the Platform API, switch to the channel-manager API directly.
* **Remove `Channel.ManageOwnership` references.** If you checked for or granted this permission, remove those references. It is no longer recognized.

</details>

<details>

<summary>Platform API: Unified Default and Custom Memory Dimensions (July 2026)</summary>

#### Unified Default and Custom Memory Dimensions

Default memory dimensions are now pre-seeded as standard enrichment key registry entries in every workspace, making them indistinguishable from custom dimensions at the API level.

**What changed:**

* **Default dimensions pre-seeded per workspace.** The eight default behavioral memory dimensions - preferred name, communication style, personality, values and goals, motivation and readiness, emotional state, concerns and beliefs, and personal context - are now registered as enrichment keys in every workspace. New workspaces receive them at provisioning; existing workspaces received them through a one-time migration.
* **Same API path for defaults and custom keys.** Default memory dimensions can be read and written through the same enrichment API endpoints as any custom dimension. There is no longer a separate code path or special bypass for writing to default dimensions.
* **System-owned dimensions unchanged.** Clinical state and the consolidated user model remain system-owned and are not writable through the enrichment API. Attempts to create or write to these keys through the enrichment endpoints are rejected.
* **Idempotent, non-destructive seeding.** If a workspace has already customized a default dimension key, the existing configuration is preserved. The seeding inserts only where no registry entry exists.
* **Migration seed bypass removed.** The previous mechanism that allowed writing to default dimensions without a registry entry (used for v1-to-v2 migration seeding) has been removed. All writes now go through the standard registry validation path.

**What you need to do:**

* **No action required.** Default memory dimensions continue to work as before. Workspaces that were already writing to custom dimensions see no change. Integrations that used the migration seed path should switch to the standard enrichment write endpoint, which now resolves default dimensions through the registry like any other key.

</details>

<details>

<summary>Platform API: Patient-Scoped Memory Expansion Tool (July 2026)</summary>

#### Patient-Scoped Memory Expansion Tool

The agent can now search the current patient's memory during a conversation, retrieving observation history and past conversation transcripts beyond the summary already in the prompt.

**What changed:**

* **New `expand_memory` tool.** Agents can search two layers of patient memory: the full observation history behind each memory dimension, and prior conversation turns. Results are keyword-filtered, sorted by recency, and capped.
* **Server-bound patient scope.** The patient identity is resolved server-side from the session's caller binding. The agent cannot choose or override which patient is searched. Sessions without a resolved patient return an empty result.
* **Fail-open behavior.** If the data source is slow or unavailable, the tool returns an unavailable status and the conversation continues without the expanded context.
* **Scoped to the agent engine.** This tool is available only within the agent engine and is not exposed through external integration channels.

**What you need to do:**

* **No action required.** The tool is available automatically to agents that have access to patient memory. No API changes, request format changes, or configuration are needed.

</details>

<details>

<summary>Platform API: Version-List Endpoints Scoped to Caller's Workspace (July 2026)</summary>

#### Version-List Endpoints Scoped to Caller's Workspace

Agent version and context graph version endpoints now enforce workspace ownership before returning results, closing a path where a valid but cross-workspace resource identifier could enumerate another workspace's versions.

**What changed:**

* **Agent version list and get.** The list-agent-versions and get-agent-version endpoints now verify that the agent belongs to the caller's workspace before returning version data. A request with an agent identifier from a different workspace receives a `404 Not Found` instead of version results.
* **Context graph version list and get.** The list-context-graph-versions and get-context-graph-version endpoints now verify that the context graph belongs to the caller's workspace before returning version data. The same `404 Not Found` behavior applies.
* **No request or response shape changes.** All request parameters, response fields, pagination, and status codes remain the same for correctly scoped requests.

**What you need to do:**

* **No action required for correctly scoped calls.** If your integration already uses agent and context graph identifiers that belong to the authenticated workspace, behavior is unchanged.
* **Update any cross-workspace tooling.** Automation that passes resource identifiers from one workspace into API calls authenticated against a different workspace will now receive `404` responses.

</details>

{% hint style="info" %}
This page covers v0.9.400 and later. Older releases live in the archive pages: [v0.9.250 - v0.9.399](/api-reference/change-logs/amigo-api/amigo-api-archive-v0-9-250-399.md), [v0.9.100 - v0.9.249](/api-reference/change-logs/amigo-api/amigo-api-archive-v0-9-100-249.md), and [v0.6.0 - v0.9.99](/api-reference/change-logs/amigo-api/amigo-api-archive-v0-6-v0-9-99.md).
{% endhint %}

<details>

<summary>Platform API: Path-Bearing MCP Resource URIs and SAML SP Base Decoupling (July 2026)</summary>

#### Path-Bearing MCP Resource URIs and SAML SP Base Decoupling

The MCP protected-resource identifier is now a path-bearing URI, and the SAML service provider entity ID can be pinned independently of the token issuer.

**What changed:**

* **Path-bearing MCP resource identifier.** The OAuth 2.1 resource identifier for the world-tools MCP server now includes the endpoint path, making each environment and region's identifier match the URL that clients connect to. Per RFC 9728 §3.1, the protected-resource metadata URL inserts the well-known segment between host and path. Clients that derive the `resource` parameter from the connection URL will match the value the server verifies.
* **Updated discovery location.** The protected-resource metadata endpoint moved to the RFC 9728 §3.1 canonical location for path-bearing resources. The previous origin-root location is no longer served.
* **SAML SP entity ID decoupled from issuer.** The SAML service provider entity ID and assertion consumer URL can now be pinned to a stable base URL independently of the token issuer. This prevents issuer changes - such as regionalization - from silently altering the entity ID that customer identity providers have registered. When no override is configured, the entity ID continues to derive from the issuer.

**What you need to do:**

* **MCP OAuth 2.1 integrations.** If your MCP client hard-codes the resource identifier or the protected-resource metadata URL, update both to use the path-bearing form. Clients that derive `resource=` from the MCP endpoint URL they connect to require no change.
* **SAML federations.** No action is required. Existing SAML federations continue to work. The decoupling prevents future issuer changes from affecting your identity provider configuration.

</details>

<details>

<summary>Platform API: Healthie Inbound Connector (July 2026)</summary>

#### Healthie Inbound Connector

The connector framework now supports Healthie as an inbound EHR data source. The connector reads clinical data from Healthie's proprietary API, maps it to FHIR R4 resources, and emits normalized records to the platform - so downstream systems only ever see FHIR.

**What changed:**

* **New Healthie connector.** Workspaces can now configure a Healthie data source that polls patient and appointment data. The connector maps vendor-specific objects to FHIR R4 Patient and Appointment resources before emitting them, consistent with other proprietary-source adapters.
* **Contract verification guard.** The connector remains inert until Healthie access is explicitly verified for the customer's account and the required credentials are configured. Registering the data source before verification is safe and will not trigger polling.
* **Static API key authentication.** Healthie credentials are stored securely and resolved at poll time. No OAuth token exchange or refresh is required.
* **Paginated polling.** The connector paginates through results with configurable stream selection through poll cadence configuration.

**What you need to do:**

* **Contact your Amigo team to enable Healthie access.** The connector requires verified API access on your Healthie account and provisioned credentials before it will begin polling.
* **No action required for existing data sources.** This change adds a new connector type and does not affect other EHR integrations.

</details>

<details>

<summary>Platform API: Version-List Endpoints Scoped to Caller's Workspace (July 2026)</summary>

#### Version-List Endpoints Scoped to Caller's Workspace

Agent version and context graph version endpoints now enforce workspace ownership before returning results, closing a path where a valid but cross-workspace resource identifier could enumerate another workspace's versions.

**What changed:**

* **Agent version list and get.** The list-agent-versions and get-agent-version endpoints now verify that the agent belongs to the caller's workspace before returning version data. A request with an agent identifier from a different workspace receives a `404 Not Found` instead of version results.
* **Context graph version list and get.** The list-context-graph-versions and get-context-graph-version endpoints now verify that the context graph belongs to the caller's workspace before returning version data. The same `404 Not Found` behavior applies.
* **No request or response shape changes.** All request parameters, response fields, pagination, and status codes remain the same for correctly scoped requests.

**What you need to do:**

* **No action required for correctly scoped calls.** If your integration already uses agent and context graph identifiers that belong to the authenticated workspace, behavior is unchanged.
* **Update any cross-workspace tooling.** Automation that passes resource identifiers from one workspace into API calls authenticated against a different workspace will now receive `404` responses.

</details>

<details>

<summary>Platform API: Canonical Pagination for List Endpoints (July 2026)</summary>

#### Canonical Pagination for List Endpoints

All Category-A list endpoints now use a unified pagination contract with opaque continuation tokens and deterministic sort ordering.

**What changed:**

* **Opaque continuation tokens.** All list endpoints now return an opaque `continuation_token` in the response and accept it as a query parameter. Do not parse, construct, or depend on the internal format of this token.
* **Consistent response shape.** Every list endpoint returns `items`, `has_more`, and `continuation_token`. The `total` field has been removed from endpoints that previously included it.
* **Per-endpoint sort\_by parameter.** List endpoints that support sorting now accept repeatable `sort_by` query parameters in the form `+field` (ascending) or `-field` (descending). Each field may appear at most once. Supported fields vary by resource.
* **Deterministic page ordering.** The server appends a stable tiebreaker to every sort order, so results do not shift between pages even when the selected sort fields contain duplicate values.
* **Invalid token handling.** Supplying a corrupted or invalid continuation token now returns `422 Unprocessable Entity` with a message to restart from the first page, instead of an internal server error.

**Affected endpoints** include list operations for agents, agent versions, API keys, context graphs, context graph versions, services, skills, surfaces, data sources, operators, dashboards, scheduling rule sets, external integrations, external write proposals, production eval definitions, simulation cases, source events, outbound sync log, active escalations, escalation events, audit log, billing invoices, and billing customers.

**What you need to do:**

* **Treat continuation tokens as opaque.** If your integration constructs or parses tokens, update it to pass them through unmodified.
* **Remove total-count dependencies.** If you relied on the `total` field for UI pagination controls, switch to the `has_more` flag to determine whether another page exists.
* **Adopt sort\_by if needed.** If you were passing a `sort_by` string parameter, update to the new `+field` / `-field` format. Check each endpoint's documentation for supported sort fields.

</details>

<details>

<summary>Platform API: OAuth Authorization Server Discovery and Updated Model Routing (July 2026)</summary>

#### OAuth Authorization Server Discovery and Updated Model Routing

The identity service now publishes the RFC 8414 OAuth 2.0 Authorization Server metadata alongside the existing OpenID Connect discovery document, and the platform's routable model set has been expanded.

**What changed:**

* **RFC 8414 discovery endpoint.** The identity service now serves `/.well-known/oauth-authorization-server` in addition to `/.well-known/openid-configuration`. Both paths return the same discovery document, so OAuth 2.0 clients that follow RFC 8414 can locate the authorization server metadata without depending on OpenID Connect conventions.
* **Expanded routable models.** The platform's model routing layer now includes additional model identifiers. All models listed in the developer console model picker are validated end-to-end; the routing sweep no longer allows partial passes when provider credentials are missing.

**What you need to do:**

* **No action required for existing integrations.** The OpenID Connect discovery path continues to work. Clients that prefer the RFC 8414 path can switch to `/.well-known/oauth-authorization-server` at any time.
* **Review model selection.** If you pin a model identifier in your service configuration, confirm it appears in the current model picker. Retired identifiers that are no longer routable will fail at configuration time.

</details>

<details>

<summary>Platform API: External Identity Bindings for Returning Users (July 2026)</summary>

#### External Identity Bindings for Returning Users

External subject keys can now be bound explicitly to world entities so an external-user conversation can load the correct returning-user context without accepting an entity ID from the caller.

**What changed:**

* **New endpoint: `PUT /v1/{workspace_id}/external-identity-bindings`.** Creates or updates the binding between an `external_subject_key` and an `entity_id`. Repeating the same binding is idempotent; attempting to move an active subject key to a different entity returns a conflict.
* **New read endpoints.** `GET /v1/{workspace_id}/external-identity-bindings` lists bindings, and `GET /v1/{workspace_id}/external-identity-bindings/{binding_id}` returns one binding.
* **New revoke endpoint.** `DELETE /v1/{workspace_id}/external-identity-bindings/{binding_id}` revokes the binding so the subject key can be bound again later.
* **Conversation-start resolution.** An external-user conversation resolves the token's stable subject key through this binding. Bound users load the linked entity context; unbound users start without entity context.
* **Principal safety.** Bindings cannot target an entity with an active external role assignment. Conversation start also rejects a binding that later resolves to such a principal.

**What you need to do:**

* **Provision bindings before starting returning-user sessions.** Send the stable subject key in the external-user token and manage its entity mapping through the binding endpoints.
* **Use the binding as the identity source of truth.** Do not rely on a caller-supplied entity ID to resolve returning-user context.

</details>

<details>

<summary>Scribe API: Notes, Summaries, and Checklists (July 2026)</summary>

#### Scribe Notes, Summaries, and Checklists

The session-centric Scribe API now supports generating, retrieving, and finalizing additional clinical documentation artifacts.

**What changed:**

* **New note generation endpoint.** `POST /v1/{workspace_id}/sessions/{session_id}/note` generates a draft note from the session transcript. The request selects a supported note type and can include additional instructions.
* **New note finalization endpoint.** `POST /v1/{workspace_id}/sessions/{session_id}/note/finalize` submits the draft note and returns the updated note artifact.
* **New summary endpoints.** `POST /v1/{workspace_id}/sessions/{session_id}/summary` generates a summary, and `GET` on the same path retrieves the current summary.
* **New checklist endpoints.** `POST /v1/{workspace_id}/sessions/{session_id}/checklist` accepts a checklist title and items, evaluates those items against the transcript, and returns their state and supporting evidence. `GET` retrieves the current checklist.
* **Independent artifact retrieval.** Summary and checklist use separate endpoints and response models, so clients can retrieve only the artifact they need.

**What you need to do:**

* **Treat generation as an explicit action.** Call the relevant `POST` endpoint before expecting a note, summary, or checklist to be available. Supply the checklist items you want evaluated when generating a checklist.

</details>

<details>

<summary>Platform API: Event-Based Triggers Now Match Live Workspace Events (July 2026)</summary>

#### Event-Based Triggers Now Match Live Workspace Events

Active event-based triggers can now enqueue their configured action when a matching workspace event arrives.

**What changed:**

* **Live event matching.** An active trigger without a cron schedule is matched by its `event_type` and optional `event_filter` when the workspace emits a supported event.
* **Standard run history.** A match enqueues the trigger through the existing run pipeline with `source: "event"`, so status, attempts, results, and errors remain available from the trigger run history endpoint.
* **Duplicate suppression.** Repeated delivery of the same event is collapsed to one run for each event-and-trigger pair.
* **At-most-once event intake.** Matching observes live events only. Events emitted while the matcher is unavailable are not replayed or reconciled later.

**What you need to do:**

* **Review active event triggers.** Existing active definitions can begin running when their configured event arrives.
* **Do not treat event matching as a guaranteed-delivery queue.** Keep a durable source and a separate reconciliation path when every event must be processed.

</details>

<details>

<summary>Platform API: Conversation Starters and Typed Real-Time Voice Controls (July 2026)</summary>

#### Conversation Starters and Typed Real-Time Voice Controls

Services can now return structured starter choices and configure real-time voice sessions through a validated schema.

**What changed:**

* **New endpoint: `POST /v1/{workspace_id}/services/{service_id}/conversation-starters`.** Returns starter chips for a service, optionally personalized with an `entity_id`. The request supports `auto`, `generate`, and `configured` generation modes, an optional deterministic fallback, and a maximum result count from 1 to 10.
* **Starter selection contract.** Each returned starter is intended to become the first user message when the client creates a conversation.
* **New `voice_config.realtime` object.** Real-time voice services can set an approved model, built-in or custom voice, speech speed, noise reduction, transcription, turn detection, reasoning effort, output-token limit, and context truncation behavior.
* **Strict validation.** The `realtime` object requires `session_provider: "gpt_realtime"`. Unknown fields are rejected, incompatible reasoning settings fail validation, and `realtime.voice` cannot be combined with the deprecated `realtime_voice` shortcut.

**What you need to do:**

* **Prefer `voice_config.realtime.voice` over `realtime_voice`.** The shortcut remains readable for compatibility but is deprecated.
* **Handle starter fallback.** A response can contain configured, generated, or deterministic starters depending on service configuration and request mode.

</details>

<details>

<summary>Platform API: Evaluation Quality Trends and Conversation-Level Simulation Verdicts (July 2026)</summary>

#### Evaluation Quality Trends and Conversation-Level Simulation Verdicts

Quality analytics now support channel-neutral trends and direct drill-down from a simulation metric to the conversations behind it.

**What changed:**

* **New endpoint: `GET /v1/{workspace_id}/analytics/eval-quality`.** Returns workspace-wide pass rate and score, per-evaluation-key aggregates, and a time-series trend across voice, text, SMS, and email.
* **Conversation-level metric results.** Simulation performance metrics now include the individual conversation verdicts behind each aggregate, ordered with failures first for review.
* **Normalized checks view.** Each simulation metric includes a consistent checks projection for clients that render lexical, model-judge, and other verdict types together.
* **Human-readable labels.** Metric responses use the evaluation definition's display name when available and fall back to the evaluation key.

**What you need to do:**

* **No action required.** Existing aggregate fields remain available. Use the new results and checks fields to add conversation drill-down.

</details>

<details>

<summary>Platform API: Durable Dataset Updates and Cloud-Storage File Export (July 2026)</summary>

#### Durable Dataset Updates and Cloud-Storage File Export

Customer data intake now has a single asynchronous update workflow and broader support for connected storage content.

**What changed:**

* **New endpoint: `POST /v1/{workspace_id}/intake/datasets/{dataset}/update`.** Starts an asynchronous dataset update that checks mapped storage folders, prepares new files, and publishes immediately when no preparation is required. The endpoint returns `202 Accepted` with an update-run record.
* **New endpoint: `GET /v1/{workspace_id}/intake/update-runs/{run_id}`.** Returns the current update status, source and batch identifiers, timestamps, and an error message when the run fails.
* **Online documents exported during intake.** Documents that are not directly downloadable are exported to a supported file representation before preparation.
* **Shortcuts resolved.** Intake follows storage shortcuts to their target files while preserving the mapped source workflow.

**What you need to do:**

* **Poll the update run.** Treat the initial response as asynchronous and wait for a terminal status before assuming the dataset is published.

</details>

<details>

<summary>Platform API: Classic Conversation History Import (July 2026)</summary>

#### Classic Conversation History Import

Workspace administrators can now import historical Classic conversation metadata so migrated users retain accurate conversation history and dates in Platform API run listings.

**What changed:**

* **New endpoint: `POST /v1/{workspace_id}/world/migration/conversations`.** Imports 1-500 completed conversation records per request with the person entity, stable source conversation ID, channel, start and end timestamps, and optional turn count.
* **Safe repeat imports.** Repeating a source conversation updates its existing imported record instead of creating a duplicate. The response reports imported, created, and updated counts.
* **Workspace validation.** Each `entity_id` must identify a person in the workspace. Timestamps must include a time zone, be in the past, and place `started_at` no later than `ended_at`.
* **Metadata-only scope.** The endpoint imports conversation records, not transcript turns.

**What you need to do:**

* **Import historical metadata in bounded batches.** Use a stable `source_conversation_id` so a corrected batch can be submitted again safely.
* **Plan transcript migration separately.** Imported conversation records can appear in history without transcript content.

For the request schema, see [Import Classic Conversation Metadata](https://docs.amigo.ai/developer-guide/platform-api/data-world-model#import-classic-conversation-metadata).

</details>

<details>

<summary>Platform API: Unified Run Detail, Trajectory, and Non-Voice Authored Turns (July 2026)</summary>

#### Unified Run Detail, Trajectory, and Non-Voice Authored Turns

The unified Runs API now covers single-run reads, framework trajectories, and operator-authored replies on non-voice channels.

**What changed:**

* **New endpoint: `GET /v1/{workspace_id}/runs/{run_id}`.** Returns one conversation or framework run at any status through its channel-neutral run ID.
* **New endpoint: `GET /v1/{workspace_id}/runs/{run_id}/trajectory`.** Returns ordered structural steps for a framework run. Conversation runs return a conflict because their turn detail remains on the conversation endpoint.
* **New endpoint: `POST /v1/{workspace_id}/runs/{run_id}/authored-turn`.** Stages the next operator-authored reply on a text, SMS, email, or web run under takeover. Each staged turn is sent once.
* **Multi-value run filters.** Run list filters accept multiple kind, channel, and status values. Summary filters accept multiple kind and channel values. Both apply OR within an axis and AND across axes.
* **Legacy list endpoints retired.** The framework-only `GET /agent-runs`, conversation-only `GET /conversations`, and active-call intelligence list are removed. Create, detail, and turn endpoints remain available on their resource-specific surfaces.

**What you need to do:**

* **Move run listings to `GET /runs`.** Filter the unified result by kind or channel instead of calling the retired list endpoints.
* **Use conversation detail for transcripts.** The unified run object and framework trajectory do not replace channel-specific transcript detail.

</details>

<details>

<summary>Platform API: API-Key Permission Catalog (July 2026)</summary>

#### API-Key Permission Catalog

Clients can now build API-key creation forms from the server's role and permission model instead of maintaining a separate matrix.

**What changed:**

* **New endpoint: `GET /v1/{workspace_id}/api-keys/permission-catalog`.** Returns every API-key role, the default permissions allowed for that role, and the complete permission universe.
* **Workspace authorization.** The endpoint requires API-key view permission and is scoped to the requested workspace.
* **Server-owned compatibility.** When role defaults or available permissions change, clients receive the current model without a frontend or SDK release.

**What you need to do:**

* **Replace hard-coded permission matrices.** Load the catalog before presenting role and permission choices for a new API key.

</details>

<details>

<summary>Platform API: Per-Question (Turn-Level) Metering Attribution (July 2026)</summary>

#### Per-Question (Turn-Level) Metering Attribution

Token usage billing events now carry a per-question turn index, enabling usage breakdowns at the individual user-question level within a conversation.

**What changed:**

* **Turn-level attribution.** Each token-usage billing event now includes a turn index that identifies which user question (turn) the model call served. This enables per-question cost attribution in addition to the existing per-conversation and per-workspace rollups.
* **Consistent within a turn.** The turn index is snapshotted once at the start of each user turn, so all model calls that serve the same turn - including navigation and response generation - share the same index.
* **Off-turn usage.** Model calls that occur outside a user turn (for example, companion processing or background completions) carry no turn index. These calls still roll up at the conversation grain but are not attributed to any single question.
* **Batch usage unchanged.** Batch and non-conversation usage continues to carry no conversation identifier or turn index and rolls up at the workspace grain only.

**What you need to do:**

* **No action required.** This is an additive change to billing event data. Existing per-conversation and per-workspace rollups are unchanged. If you consume raw billing events for custom cost attribution, the new turn index field is available for per-question breakdowns.

</details>

<details>

<summary>Platform API: Universal Metering - All Workspaces Metered (July 2026)</summary>

#### Universal Metering - All Workspaces Metered

The metering pipeline now meters usage across all workspaces universally, not just billing-registered workspaces. Billing remains scoped to billable customers only - this change adds visibility into non-billable and unregistered workspace usage for cost attribution.

**What changed:**

* **Universal metering.** Every workspace's usage events now flow through the metering pipeline regardless of whether the workspace is a registered billable customer. Previously, only billable workspaces were metered. Non-billable and unregistered workspace usage is now measured and available for cost attribution rather than silently dropped.
* **Per-workspace meter rollup.** A new per-workspace meter rollup captures usage for all workspaces. This is the superset view - it includes both billable and non-billable workspaces. The customer identifier is nullable for workspaces that are not mapped to a registered customer.
* **Billing subset unchanged.** The existing per-customer billing rollup continues to include only registered billable customers. Billing outputs are byte-for-byte identical to the previous behavior - no billing amounts, invoices, or usage reports change as a result of this update.
* **Cost attribution improvement.** The universal metering data enables cost attribution for overhead and internal workspaces. Usage that was previously invisible (because it came from non-billable workspaces) is now captured and can be routed to the appropriate cost center.

**What you need to do:**

* **No action required.** This is a backend metering infrastructure change. Billing behavior, API responses, and usage reports for billable customers are unchanged. If you consume metering data for custom cost attribution, the universal per-workspace rollup now provides a complete picture of all workspace usage.

</details>

<details>

<summary>Scribe API: Interactive API Documentation (ReDoc) (July 2026)</summary>

#### Interactive API Documentation (ReDoc)

The Scribe API now serves interactive API documentation through a public docs endpoint, making it easier for developers to explore available endpoints, request and response shapes, and authentication requirements.

**What changed:**

* **Interactive docs endpoint.** The Scribe API now serves a ReDoc-powered interactive documentation page. The page is publicly accessible (no authentication required) and renders the same OpenAPI schema that was already available at the public schema endpoint.
* **Security headers updated.** The documentation page is served with appropriate security headers that allow the interactive UI to render correctly.

**What you need to do:**

* **No action required.** This is an additive change. Existing API behavior and endpoints are unchanged.

</details>

<details>

<summary>Platform API: Google Drive Intake - Native File Export and Shortcut Resolution (July 2026)</summary>

#### Google Drive Intake - Native File Export and Shortcut Resolution

Google Drive intake sources now automatically export Google-native files and resolve Drive shortcuts during sync, so knowledge base datasets connected to Google Drive folders ingest the actual content of Docs, Sheets, Slides, Drawings, and shortcut targets without manual conversion.

**What changed:**

* **Google-native file export.** Google Docs, Sheets, Slides, and Drawings have no downloadable binary content. The platform now exports them to an ingestible format during folder sync - Docs are exported as Markdown, and Sheets, Slides, and Drawings are exported as PDF. The exported filename carries the target format's extension (for example, a Google Doc named "Onboarding Guide" becomes "Onboarding Guide.md") so the dataset's accepted-file-type check validates against the exported format. Google-native types that have no useful export (Forms, Sites, and similar) are skipped.
* **Drive shortcut resolution.** Drive shortcuts are now resolved to their target at fetch time. A shortcut to a regular file fetches the target's content (applying export rules if the target is a Google-native file). A shortcut to a folder is traversed as a real subfolder with cycle detection. Previously, shortcuts were listed but could not be fetched.
* **Unsupported native types skipped gracefully.** Native file types without a supported export (Forms, Sites, Jamboard, and similar) are skipped during sync rather than causing an error. The sync continues with the remaining files in the folder.

**What you need to do:**

* **No action required.** This is automatic for all Google Drive intake sources. On the next sync, Google-native files in your connected folders will be exported and ingested alongside regular files. If your dataset's accepted file types do not include Markdown (.md) or PDF (.pdf), the exported files will be rejected by the file-type filter - update your accepted file types if needed.

</details>

<details>

<summary>Platform API: Optional Domain-Wide Delegation for Google Drive Intake Sources (July 2026)</summary>

#### Optional Domain-Wide Delegation for Google Drive Intake Sources

Google Drive intake sources now support an optional domain-wide delegation (DWD) impersonation mode as an alternative to the default direct folder-share model.

**What changed:**

* **New `impersonate_subject` field on source registration.** When registering a Google Drive intake source, you can now provide an optional `impersonate_subject` (a Workspace user email, max 320 characters). When set, the connector authenticates as this user via domain-wide delegation instead of authenticating as the service account directly.
* **Returned on source detail.** The `impersonate_subject` field is included in intake source responses when set, so callers can see which authentication model a source uses.
* **Two authentication models.** Sources without `impersonate_subject` continue to use the existing direct folder-share model (the service account authenticates as itself and the folder is shared directly with it). Sources with `impersonate_subject` use domain-wide delegation, where the service account's client ID is authorized for delegation in the customer's Workspace admin console and the connector mints tokens as the specified user.

**When to use DWD mode:**

* Use DWD when your organization's Workspace policy forbids sharing folders directly with external service account emails. Instead, authorize the service account's client ID for domain-wide delegation in your Workspace admin console and provide a Workspace user email that has Viewer access on the target folder.
* If your organization can share folders directly with the service account email, leave `impersonate_subject` unset. The existing default behavior is unchanged.

**What you need to do:**

* **No action required for existing sources.** Existing sources without `impersonate_subject` continue to work exactly as before.
* **To use DWD**, authorize the service account's client ID for domain-wide delegation in your Workspace admin console, then register or update the source with `impersonate_subject` set to a Workspace user who has Viewer access on the target folder.

</details>

<details>

<summary>Scribe API: AI-Powered Clinical Note Generation and Finalization (July 2026)</summary>

#### AI-Powered Clinical Note Generation and Finalization

The Scribe Sessions API now supports generating clinical notes from session transcripts and finalizing them with a provider signature.

**What changed:**

* **New `POST /v1/{workspace_id}/sessions/{session_id}/note` endpoint.** Generates a clinical note from the session's transcript. Accepts an optional `note_type` (full, medical, SOAP, DAP, BIRP, and several specialty-specific formats - defaults to medical) and optional free-text `instructions` (up to 1,200 characters). Returns the generated note in draft status alongside generation metadata (generation ID, model provider, model name, prompt version, and generation timestamp).
* **New `POST /v1/{workspace_id}/sessions/{session_id}/note/finalize` endpoint.** Finalizes the most recent draft or submitted note for a session, transitioning it to submitted status and recording a signature timestamp. This is the provider's sign-off on the generated content.
* **Strict transcript grounding.** Generated notes are grounded exclusively in the session transcript. The model never invents facts, diagnoses, medications, or plans not present in the transcript. Unsupported sections are omitted rather than fabricated.
* **Circuit-breaker resilience.** The generation pipeline uses circuit-breaker protection so repeated upstream failures trigger fast-fail responses rather than cascading timeouts. The circuit breaker recovers automatically after a short cooldown.
* **Input and output bounds.** Transcript content is bounded (segment count and per-segment length) before generation, and generated output is length-capped, preventing unbounded resource consumption.
* **Nine note types supported.** `full`, `medical`, `soap`, `dap`, `birp`, `amd-psych-intake`, `amd-psych-progress`, `amd-therapy-intake`, `amd-therapy-progress` - each producing notes with clinically appropriate section headings.

**New error responses:**

* `409 Conflict` - returned when the session's transcript is empty and generation cannot proceed.
* `503 Service Unavailable` - returned when clinical generation is not configured or the upstream model is experiencing failures.

**What you need to do:**

* **No action required for existing integrations.** These are new additive endpoints. Existing session and artifact endpoints are unchanged.
* **To generate notes**, call the generate endpoint with an optional note type and instructions after a session has a non-empty transcript. Review the draft note and call the finalize endpoint to sign and submit it.
* Both endpoints require the `scribe:notes:rw_own` scope.

</details>

<details>

<summary>Platform API: Configurable Output Voice for Real-Time Speech-to-Speech (July 2026)</summary>

#### Configurable Output Voice for Real-Time Speech-to-Speech

Services using the real-time speech-to-speech voice family can now select an output voice from a set of built-in voices, controlling how the agent sounds to the caller.

**What changed:**

* **New `realtime_voice` field on service voice configuration.** When the voice model family is set to real-time speech-to-speech, you can now specify an output voice. Supported voices: `alloy`, `ash`, `ballad`, `coral`, `echo`, `sage`, `shimmer`, `verse`, `marin`, `cedar`.
* **Validation enforced at write time.** Setting `realtime_voice` when the voice model family is not real-time speech-to-speech is rejected with a validation error. The field is optional - when omitted, the default voice for the backing model is used.
* **Propagated to call sessions.** The configured voice is carried through to the call session so the caller hears the selected voice for the duration of the call.

**What you need to do:**

* **No action required for existing services.** Services without a `realtime_voice` setting continue to use the default voice.
* **To select a voice**, set `realtime_voice` on the service voice configuration alongside `session_provider: gpt_realtime`. The change takes effect on the next call without a redeploy.

</details>

<details>

<summary>Platform API: Default Realtime Voice Model Updated (July 2026)</summary>

#### Default Realtime Voice Model Updated

The default backing model for real-time speech-to-speech voice agents has been updated to a newer version with improved tool calling, interruption handling, and alphanumeric recognition.

**What changed:**

* **New default realtime model.** The real-time speech-to-speech voice family now defaults to an updated model version that fires tools reliably and handles interruptions more naturally. The previous default remains in the recognized allowlist and can be restored via environment configuration without a redeploy.
* **No behavior change for explicitly configured workspaces.** If your workspace already sets a specific realtime model through environment configuration, that setting is unchanged. The new default applies only to workspaces using the default.
* **Allowlist expanded.** Two additional model tiers (an improved version and a fast/cheap distilled tier) are now selectable through environment configuration for A/B testing. The distilled tier is A/B-ready - it shares the same reasoning guards as the full-size model and can be trialed for cost and latency improvements. It has not yet completed live tool-depth verification, so teams should run tool-depth validation before promoting it to the default.

**What you need to do:**

* **No action required for most users.** The new default is validated for production tool-calling workloads. If you experience unexpected behavior, you can revert to the previous model via environment configuration (no redeploy needed).
* **To trial the distilled tier**, switch the realtime model configuration to the fast/cheap tier and monitor tool-calling behavior. Run tool-depth validation on the distilled tier before promoting it to your default.

</details>

<details>

<summary>Platform API: Per-Conversation Token Attribution for Text Channels (July 2026)</summary>

#### Per-Conversation Token Attribution for Text Channels

Token-usage billing events on text channels now carry the durable conversation identifier, enabling per-conversation cost rollups.

**What changed:**

* **Conversation-grain attribution on text token events.** Every token-usage billing event emitted during a text conversation now includes the conversation identifier. This allows usage reporting and analytics to roll up token consumption per conversation in addition to per session or per workspace.
* **No change for non-conversation usage.** Batch and non-conversation token events carry no conversation identifier and continue to roll up at the workspace grain only.

**What you need to do:**

* **No action required.** This is an automatic enhancement to billing event data. If you consume raw token-usage events for custom analytics, the conversation identifier is now available as an additional dimension for grouping.

</details>

<details>

<summary>Platform API: Bulk Import Endpoint for Conversation Migration (July 2026)</summary>

#### Bulk Import Endpoint for Conversation Migration

A new endpoint enables self-serve migration of historical conversation data from external systems into the platform's world model.

**What changed:**

* **New `POST /{workspace_id}/world/migration/conversations` endpoint.** Accepts a batch of conversation envelopes and upserts them into the world model under a target entity. Each envelope carries channel, direction, timing, participants, and message content. Envelopes are validated independently - malformed or incomplete envelopes are rejected individually without blocking the rest of the batch. The response includes per-envelope success or failure status, plus aggregate imported and failed counts.
* **Per-envelope error reporting.** Each result in the response identifies the envelope by its zero-based index, its status (`success` or `failed`), and a descriptive error message on failure, so callers can identify and retry specific failures without re-submitting the entire batch.
* **Designed for v1 migration.** The endpoint is purpose-built for importing historical conversation records (such as v1 conversation data) into the platform so they appear alongside native platform conversations in entity timelines.

**What you need to do:**

* **No action required for existing integrations.** This is an additive endpoint.
* **To migrate conversation history**, submit batches of conversation envelopes to the new endpoint. Use the per-envelope results to retry only failed items.
* **Permissions:** Requires workspace-scoped API key or session token with write permissions.

</details>

<details>

<summary>Platform API: Channel-Agnostic Eval Quality Analytics Endpoint (July 2026)</summary>

#### Channel-Agnostic Eval Quality Analytics Endpoint

A new analytics endpoint aggregates production-eval verdicts into pass-rate and score trends across all conversation channels - voice, text, SMS, email, and web.

**What changed:**

* **New `GET /v1/{workspace_id}/analytics/eval-quality` endpoint.** Returns production-eval verdict aggregates for the workspace over a configurable time window. The response includes an overall summary (total evaluations, judged count, pass rate, average score), a per-eval-key breakdown ordered by volume, and a time-bucketed trend series at the requested interval (`1h`, `1d`, or `1w`). Supports optional `service_id` filtering.
* **Channel-agnostic quality signal.** Unlike the existing call quality analytics (which derive from voice-shaped call intelligence data), eval quality is keyed on conversations regardless of channel. Voice calls, text sessions, SMS, email, and web conversations all contribute equally to the aggregates.
* **Safe empty-state behavior.** When no verdicts exist for the workspace or time window - for example, before automatic post-conversation evaluation is enabled - the endpoint returns zeroed shapes (empty per-key and trend arrays, null rates in the summary) so consumers can render an empty state rather than receiving an error.

**What you need to do:**

* **No action required.** This is an additive endpoint. Integrate it into your analytics workflows or dashboards when you want a channel-agnostic view of production evaluation quality.
* **Permissions:** Workspace viewer or above (same as other analytics endpoints).

</details>

<details>

<summary>Platform API: Durable Background Reply Delivery, SMS Opt-In, Turn Idempotency, and Conversation Close (July 2026)</summary>

#### Durable Background Reply Delivery, SMS Opt-In, Turn Idempotency, and Conversation Close

Web text conversations now use a receipt-backed delivery protocol for background tool replies, SMS channels record opt-in consent before each reply, message turns support idempotency keys, and conversations can be explicitly closed through the API.

**What changed:**

* **Durable background reply delivery (web text).** When a background tool completes during a web text session, the platform commits the reply to a durable outbox. The caller claims it via polling, renders the message, and acknowledges receipt. This ensures background answers survive disconnects and restarts. The turn-done event now includes `background_pending` (true when a final answer must be collected from the delivery outbox) and `delivery_protocol_version` (the protocol version supported by the serving agent) so clients can adapt their polling strategy.
* **Receipt-backed poll endpoint.** The existing poll request now accepts an optional `poll_request_id` parameter. When provided, the poll atomically claims at most one committed delivery (or a typed safe failure message) from the conversation's outbox. The response includes `delivery_id` and `delivery_receipt` fields that the caller passes to the new acknowledgement endpoint to confirm receipt.
* **Delivery acknowledgement endpoint.** A new internal endpoint accepts the `delivery_id` and `delivery_receipt` from a claimed delivery and advances the outbox. Acknowledged deliveries are not returned again.
* **Conversation close endpoint.** A new endpoint explicitly closes a web text conversation. Closing tears down the delivery state, cancels in-flight background tasks, and prevents further turns. The next inbound message from the contact starts a fresh conversation.
* **Turn idempotency.** The text interact endpoint now accepts an optional `turn_request_id` parameter. When a client retries a turn with the same idempotency key, the platform returns the cached reply from the original turn instead of re-processing the message. Reuse of the same key with a different message body is rejected with a 409.
* **Agent message delivery ID.** The `text.agent_message` event now carries an optional `delivery_id` field linking the SSE event to the delivery outbox entry.
* **SMS opt-in before reply.** On SMS channels, the platform records the recipient's consent before sending each reply. If the recipient has actively opted out (a prior STOP), the reply is suppressed and a structured error is returned. Channels that do not require carrier-level consent proceed without an opt-in gate.

**What you need to do:**

* **Web text clients** should check `background_pending` and `delivery_protocol_version` on turn-done events. When `background_pending` is true and the protocol version is present, use receipt-backed polling with `poll_request_id` to collect background replies, then acknowledge each delivery.
* **SMS integrations** require no changes. Opt-in is handled automatically by the platform before each reply.
* **Clients that retry turns** can pass a `turn_request_id` to get idempotent behavior. Omitting the field preserves existing behavior.

</details>

<details>

<summary>Platform API: Analytics Dashboard KPI Endpoint Typed and Operator Performance Aggregation Fixed (July 2026)</summary>

#### Analytics Dashboard KPI Endpoint Typed and Operator Performance Aggregation Fixed

The composite analytics dashboard endpoint now returns a fully typed response with six named KPIs instead of an untyped dictionary, and the operator performance summary uses a corrected aggregation method.

**What changed:**

* **Typed dashboard response.** The `GET /v1/{workspace_id}/analytics/dashboard` endpoint now returns a structured response with six named KPI objects (`call_volume`, `avg_quality`, `avg_ttfb_ms`, `escalation_rate`, `tool_success_rate`, `avg_duration_s`), each carrying a `value` (current-period value, or null when no data) and a `delta_pct` (signed percent change versus the previous equal-length period, or null when a prior-period comparison is unavailable), plus a `period_days` field indicating the reporting period length.
* **KPI polarity guidance.** Higher is better for `call_volume`, `avg_quality`, and `tool_success_rate`. Lower is better for `escalation_rate`, `avg_ttfb_ms`, and `avg_duration_s`. Consumers should color deltas by each KPI's polarity rather than by delta sign alone.
* **Operator performance aggregation fix.** The operator performance summary endpoint now computes average handle time as a call-weighted mean (weighted by escalations handled per operator) instead of an unweighted mean of per-operator averages. The aggregation also pages through all operators in the workspace rather than truncating at a fixed limit, so workspaces with many operators get accurate totals.
* **Insight metric sentiment field.** Insight block metrics now support a `sentiment` field (`good`, `bad`, or `neutral`) that is independent of the `trend` field (`up`, `down`, `flat`). Trend indicates the direction the value moved (drives the arrow icon). Sentiment indicates whether that movement is good or bad for the business (drives the color). For "higher is worse" metrics - such as escalation rate or latency - a rising value has trend `up` but sentiment `bad`. Consumers should use sentiment for color and trend for direction.

**What you need to do:**

* **Update any integrations** that consume the analytics dashboard endpoint. The response is now a structured object with named fields instead of an untyped dictionary. The field names and value shapes are documented in the OpenAPI spec.
* **Review any code** that interprets operator performance summary averages. The average handle time value may differ from previous responses due to the corrected weighted-mean calculation.
* **Update insight rendering** if you consume insight blocks. Use the `sentiment` field for color (green/red/grey) and the `trend` field for arrow direction. Do not assume that `trend: "up"` means good.

</details>

<details>

<summary>Platform API: Legacy Read Endpoints Removed Behind Unified Runs Surface (July 2026)</summary>

#### Legacy Read Endpoints Removed Behind Unified Runs Surface

Several legacy list and read endpoints that were superseded by the unified Runs surface have been removed from the API. These endpoints were no longer serving traffic - all reads now go through the unified runs list, run detail, and conversation detail endpoints.

**What changed:**

* **Conversation list endpoint removed.** The `GET /v1/{workspace_id}/conversations` endpoint that returned a paginated list of text and voice conversations has been removed. Use the unified runs list endpoint (`GET /v1/{workspace_id}/runs`) with channel filters to list conversations across all channels.
* **Agent runs list endpoint removed.** The standalone `GET /v1/{workspace_id}/agent-runs` list endpoint for framework agent runs has been removed. Use the unified runs list endpoint with the framework kind filter instead.
* **Active calls intelligence endpoint removed.** The `GET /v1/{workspace_id}/calls/active/intelligence` endpoint that returned active calls with live intelligence overlay data has been removed. Active call data is available through the unified runs list with the live status filter.

**What you need to do:**

* **Migrate any integrations** that call the removed endpoints to use the unified runs list endpoint (`GET /v1/{workspace_id}/runs`). The unified endpoint supports filtering by kind (framework or conversation), channel (voice, text, SMS, email, web), and status (including a "live" filter for active runs).
* **Update any SDK or automation code** that referenced the standalone conversation list, agent runs list, or active calls intelligence endpoints.

</details>

<details>

<summary>Voice Agent: Per-Call Media Isolation Now Universal (July 2026)</summary>

#### Per-Call Media Isolation Now Universal

Per-call voice isolation is now permanent and universal for every call. The graduated rollout controls (workspace allowlist and environment toggle) have been removed - every call allocates an isolated media server with no legacy bypass.

**What changed:**

* **Universal isolation.** Every voice call now takes the per-call isolation path. The platform allocates an isolated media server for every call and pins the routing for the duration of the call. There is no longer a legacy shared-path fallback.
* **Workspace allowlist removed.** The optional workspace allowlist that previously narrowed per-call isolation to specific workspaces has been removed. All workspaces are on the isolation path.
* **Environment toggle removed.** The environment-level toggle that enabled or disabled per-call isolation has been removed. Isolation is always active for in-cluster calls.
* **Busy redirect on pool exhaustion.** If the isolation pool is exhausted or the allocation fails, the platform redirects the caller to a busy message and ends the call rather than leaving them in dead air. There is no fallback to a shared path.
* **Contour transport gating simplified.** The choice between media transports is now gated purely by a per-workspace feature flag. The transport suffix is provisioned in every cluster, so the flag alone decides which transport a call uses.

**What you need to do:**

* **No action required.** This is a backend change. All voice calls already on the per-call isolation path are unaffected. Workspaces that were previously on the legacy path are now automatically on the isolation path.
* **Remove any references** to the workspace allowlist or environment toggle in operational runbooks or configuration management - these settings no longer exist.

</details>

<details>

<summary>Platform API: API-Key Role and Permission Catalog Endpoint (July 2026)</summary>

#### API-Key Role and Permission Catalog Endpoint

A new read-only endpoint returns the authoritative role-to-permission model for API-key creation, so clients can build key-creation forms from the server-side source of truth instead of hard-coding the permission matrix.

**What changed:**

* **Permission catalog endpoint.** A new `GET /v1/{workspace_id}/api-keys/permission-catalog` endpoint returns the complete role and permission catalog. The response includes each role's name, priority, description, and default permission set (what an API key of that role may carry), plus the full permission universe with each permission's full name, namespace, and action.
* **Authorization.** The endpoint requires the `ApiKey.view` permission - the same gate used for listing API keys. A caller that can inspect keys can also read the catalog needed to configure them.
* **Workspace-scoped.** The endpoint is served under the workspace scope so clients fetch it with the same credential used for other API-key operations. The payload is workspace-independent today but is scoped for forward compatibility.

**Why this matters:**

Previously, clients (the Developer Console, SDKs, custom tooling) maintained their own copy of the role-to-permission matrix. This copy could drift from the server-side definitions - for example, listing a permission as a role default that the server did not accept - causing key-creation requests to fail with a 422 error. The catalog endpoint eliminates this drift by providing a single source of truth.

**What you need to do:**

* **No action required.** This is an additive endpoint. Existing API-key creation, listing, deletion, and rotation endpoints are unchanged.
* **SDK and console integrations** can use the catalog endpoint to dynamically populate role and permission selectors in key-creation forms instead of maintaining a hardcoded permission list.

</details>

<details>

<summary>Platform API: Framework Run Trajectory Endpoint (July 2026)</summary>

#### Framework Run Trajectory Endpoint

A new endpoint returns the step-by-step structural trajectory of a framework run, so the console and API consumers can inspect the ordered steps a framework agent took during a run.

**What changed:**

* **Trajectory endpoint.** A new `GET /v1/{workspace_id}/runs/{run_id}/trajectory` endpoint returns the ordered structural steps of a framework run. Each step includes structural metadata - step kind, sequence number, actor, state, tool name with input and result summaries, tool success indicator, and decision state transitions. Verbatim transcript text and raw model reasoning are not included.
* **Truncation handling.** The response includes a `truncated` flag. When a run has more steps than the server-side bound, the response contains the first N steps and sets `truncated` to `true`, so consumers can indicate that the trajectory is partial.
* **Conversation run guard.** Requesting the trajectory for a conversation run returns HTTP 409 with a message directing callers to the conversation detail endpoint for per-turn data.
* **Workspace-scoped authorization.** The endpoint is gated on workspace tenant isolation - a run identifier from another workspace or an unknown identifier returns 404 with no tenant leak.

**What you need to do:**

* **No action required.** This is an additive endpoint. Existing run list, run detail, and run-scoped operation endpoints are unchanged.
* **Console integrations** can use the trajectory endpoint to render step-by-step framework run detail in place of the previous "trajectory rendering is a follow-up" placeholder.

</details>

<details>

<summary>Platform API: Intake Dataset Update Runs - One-Click Sync, Prepare, and Publish (July 2026)</summary>

#### Intake Dataset Update Runs - One-Click Sync, Prepare, and Publish

A new dataset update flow orchestrates the full sync-prepare-publish cycle for a dataset in a single action, giving the console a durable status to poll instead of stitching together independent resources.

**What changed:**

* **Dataset update endpoint.** A new `POST /intake/datasets/{dataset}/update` endpoint triggers a one-click update for a dataset. The platform identifies all active cloud storage sources mapped to the dataset, syncs their folders for new or changed files, prepares any newly landed files, and publishes the results. The endpoint returns immediately with a durable update run that can be polled for progress.
* **Durable update run status.** Each update run tracks its progress through a defined set of statuses: checking the cloud storage source, preparing files, publishing, completed, needs review, or failed. The console polls a single resource for the current state rather than inferring status from independent batches and materializer runs.
* **Automatic advancement.** If file preparation is asynchronous, the update run advances automatically as files complete processing. When all files are ready, the run triggers publishing. If any files need review before publishing, the run surfaces a "needs review" status rather than proceeding.
* **At-most-one active run per dataset.** Only one update run per dataset is active at a time. Requesting an update while one is already in progress returns the existing run's current status.
* **Update run detail endpoint.** A new `GET /intake/update-runs/{run_id}` endpoint returns the current state of an update run, refreshing its status against upstream progress on each read.
* **Heartbeat during long syncs.** Long-running cloud storage syncs periodically update the run's timestamp so the run does not appear stale while files are still being discovered and downloaded.
* **Error handling.** If any stage of the update fails (cloud storage authentication, file sync, processing dispatch, or publishing), the run captures a descriptive error and moves to a failed status rather than leaving the run in an indeterminate state.

**What you need to do:**

* **No action required for existing integrations.** The existing source-level sync, batch processing, and materialize endpoints continue to work as before. The dataset update endpoint is an additional orchestration layer.
* **Console integrations** can use the new dataset update endpoint to replace multi-step sync-process-publish workflows with a single call and a poll loop on the returned run.

</details>

<details>

<summary>Platform API: EHR Appointment Reschedule Reliability - Patient Identity and Edit Verification (July 2026)</summary>

#### EHR Appointment Reschedule Reliability - Patient Identity and Edit Verification

Appointment reschedules through the EHR connector are now more reliable: the platform carries the patient identity forward on edits and verifies that reschedules actually applied before reporting success.

**What changed:**

* **Patient identity carried forward on reschedule.** When rescheduling an existing appointment, the platform now reads the appointment's patient identity from the live appointment detail and includes it in the edit submission. The EHR's appointment edit form requires a patient identity - previously, reschedules omitted it, causing the EHR to silently reject the edit with no error. Creates were unaffected (they always sent the patient identity).
* **Edit verification by calendar read-back.** After submitting a reschedule, the platform now verifies the edit applied by reading the provider's calendar and confirming the appointment appears at the new time with the correct appointment identifier. If neither the EHR's response nor the calendar read-back confirms the edit, the endpoint returns a `verify_failed` status (HTTP 502) with a descriptive reason rather than falsely reporting the appointment as rescheduled.
* **Eventual-consistency retry on read-back.** The calendar read-back retries across a short window to accommodate EHR systems where a just-written appointment takes a few seconds to appear in calendar queries. This retry applies only to post-write verification - pre-write idempotency checks do not retry.
* **Multi-day calendar query window.** Calendar queries for conflict checking, post-write read-back, and appointment-id resolution now query a multi-day window around the target date instead of a single day. This works around EHR calendar APIs that return empty results for single-day queries, which previously caused silent read-back failures and missed conflict detection.
* **Unresolvable patient on reschedule returns 422.** If the platform cannot resolve the appointment's patient identity from its live detail (for example, a group or couples appointment where no single patient is identified), the reschedule is refused with a `verify_failed` status (HTTP 422) and a descriptive reason rather than submitting a knowingly invalid edit.
* **Improved error logging for unparseable EHR responses.** When the EHR returns a response that the platform cannot parse into an appointment identifier, the platform now logs non-PHI metadata (status code, content type, response length) for diagnostics. Patient data is never included in log entries.

**What you need to do:**

* **No action required for most integrations.** Appointment reschedules are now more reliable automatically. Previously silent failures will now surface as `verify_failed` responses so callers can detect and handle them.
* **If your integration handles the `verify_failed` status**, note that reschedule failures may now return HTTP 422 (unresolvable patient) in addition to the existing HTTP 502 (write or verification failure). Both carry a descriptive `reason` field.

</details>

<details>

<summary>Platform API: Durable EHR Appointment Sync, Delete Detection, and Faster Detail Refresh (July 2026)</summary>

#### Durable EHR Appointment Sync, Delete Detection, and Faster Detail Refresh

The EHR connector's appointment sync is now more reliable, detects deleted appointments, and refreshes detail-only edits faster.

**What changed:**

* **Durable event delivery for all EHR sync paths.** Appointment, practitioner, availability, and patient sync events are now confirmed delivered before the dedup marker advances. If delivery is not confirmed, the event is automatically retried on the next poll rather than silently dropped. This closes a window where an unconfirmed emit could be marked as sent, causing the change to never surface in the world model.
* **Deleted appointment detection.** When an appointment is deleted in the source EHR, the platform now detects its absence by comparing the current upcoming-appointment window against prior state and emits a cancellation record that supersedes the last observation. Previously, deleted appointments lingered as active records indefinitely because the sync only ever upserted appointments present in each fetch.
* **Safety guards against mass cancellation.** Two guards prevent a degraded or partial EHR fetch from incorrectly cancelling a large number of appointments. Small appointment sets skip the ratio guard entirely (the blast radius is inherently bounded). If a fetch's upcoming set shrinks below a configurable fraction of the prior window, the platform treats the fetch as degraded and skips delete detection for that cycle rather than acting on incomplete data.
* **Faster detail-only refresh.** Appointment detail re-hydration - the periodic forced refresh that catches edits visible only in per-appointment detail (such as no-show status, telehealth toggles, room changes, service or billing code updates, duration changes, or provider reassignments that do not move the calendar slot) - now runs on a faster cadence. Detail-only edits surface within roughly one to two hours instead of most of a day.
* **Tunable re-hydration cadence.** The detail re-hydration frequency can be adjusted per connection configuration without a code change, so practices with different appointment volumes can balance freshness against fetch cost.

**What you need to do:**

* **No action required.** These changes improve sync reliability and freshness automatically. Deleted appointments will now be reflected as cancelled in the world model. Detail-only edits will appear faster.
* **If you have a large practice with high appointment volume**, you can tune the detail re-hydration cadence through connection configuration to balance freshness against API cost.

</details>

<details>

<summary>Platform API: EHR Patient Creation No Longer Auto-Assigns Intake Packet Cohort (July 2026)</summary>

#### EHR Patient Creation No Longer Auto-Assigns Intake Packet Cohort

When creating a patient through the EHR connector, the platform no longer automatically assigns the intake-packet cohort. This prevents the EHR from emailing intake packets to agent-created patients.

**What changed:**

* **No default cohort assignment on patient create.** Previously, when no cohort IDs were specified in a patient creation request, the platform defaulted to assigning the intake-packet cohort. The EHR treats cohort assignment as a trigger to email the associated packet to the patient - a side effect that is unwanted for patients created by the agent. The platform now assigns only the cohorts the caller explicitly passes.
* **Explicit cohort assignment still supported.** Callers that want the intake packet sent can still include the cohort ID in their request. The behavior is identical to before when cohort IDs are explicitly provided.
* **No change to existing patients.** Patients already assigned to the intake-packet cohort are unaffected. This change applies only to new patient creation requests going forward.

**What you need to do:**

* **If you rely on the intake packet being sent automatically at patient creation**, update your integration to explicitly pass the intake-packet cohort ID in the `cohort_ids` field when creating a patient.
* **If you create patients through the agent and do not want intake packets sent**, no action is required - this is now the default behavior.

</details>

<details>

<summary>Platform API: Streaming Intake File Downloads (July 2026)</summary>

#### Streaming Intake File Downloads

Intake file download endpoints now stream file bytes incrementally instead of buffering the entire file in memory before responding.

**What changed:**

* **Streaming proxy for intake downloads.** Both the intake catalog download endpoint and the intake link upload download endpoint now stream file bytes in fixed-size chunks directly from storage to the caller. Previously, the platform read the entire file into memory before sending the response, which could cause memory pressure for large uploads.
* **Bounded-memory proxying.** The response is sent incrementally as bytes arrive from the storage backend. The platform never holds the full file in memory, so download size is no longer constrained by available service memory.
* **Content-Length when available.** When the storage backend supplies a content length, the response includes a `Content-Length` header so callers can display download progress. If the storage backend omits the length, the header is absent and the response uses chunked transfer encoding.
* **Mid-stream error logging.** If the storage connection fails partway through a download, the error is logged with correlation metadata (upload ID, link ID, workspace ID, error type, and upstream status when available) for diagnostics. No PHI is included in the log entry.
* **No change to upload behavior.** File uploads, content-type validation, and virus scanning are unaffected.

**What you need to do:**

* **No action required for most callers.** The response content type, filename, and disposition headers are unchanged. Callers that stream the response body (the recommended approach) work without modification.
* **Callers that rely on Content-Length** should handle the case where the header is absent, as it is now conditional on the storage backend providing it.

</details>

<details>

<summary>Platform API: Single-Run Detail Endpoint (July 2026)</summary>

#### Single-Run Detail Endpoint

A new endpoint returns a single run by its channel-neutral run identifier at any status, closing the gap where the unified runs list had no per-run detail counterpart.

**What changed:**

* **New endpoint: `GET /{workspace_id}/runs/{run_id}`.** Resolve a single run by its channel-neutral `run_id` and return the canonical `Run` object at any status - live, completed, failed, or timed out. The endpoint federates framework runs, conversation runs, and the live voice registry, matched by `run_id` and scoped to the workspace. Use the returned `kind`, `channel`, and source provenance fields to open the channel-appropriate detail view (voice call, text conversation, framework trajectory).
* **Workspace-scoped with no tenant leak.** The run identifier is resolved within the authenticated workspace only. A `run_id` from another workspace or an unknown identifier returns 404 - no information about other workspaces is disclosed.
* **Live voice overlay.** An in-flight voice call is resolvable before its terminal record is written. Once the terminal record exists, it is authoritative and the live entry is dropped, consistent with the list endpoint's deduplication behavior.
* **Fail-open federation.** All sources (framework, conversation, live voice) are queried concurrently. A transient failure in any single source degrades to "no match from that source" rather than failing the request, so a single source outage never blocks a run lookup.
* **Same response shape as the list.** The returned `Run` object carries the same fields as a row from the list endpoint, including optional enrichment fields (entity name, service name, caller identity, contact number, direction, turn count, completion reason).

**What you need to do:**

* **No action required for existing integrations.** This is an additive endpoint. Existing list, summary, takeover, and guidance endpoints are unaffected.
* **Use this endpoint for run detail views.** Instead of resolving a run's source record through channel-specific endpoints, call `GET /{workspace_id}/runs/{run_id}` to get the canonical run and then use its `kind` and `channel` fields to compose the appropriate detail view.

</details>

<details>

<summary>Platform API: Non-Voice Takeover Route Dispatch and Authored-Turn Endpoint (July 2026)</summary>

#### Non-Voice Takeover Route Dispatch and Authored-Turn Endpoint

The takeover and handback endpoints now fully dispatch for non-voice channels (text, SMS, email, web), and a new authored-turn endpoint lets operators stage replies on a taken-over non-voice run.

**What changed:**

* **Takeover and handback dispatch for non-voice channels.** The `POST /{workspace_id}/runs/{run_id}/takeover` and `POST /{workspace_id}/runs/{run_id}/handback` endpoints no longer return 409 for non-voice runs. Taking over a non-voice run suspends the agent (marks the run paused); handing back clears the suspension so the agent resumes. Both transitions are idempotent - a repeat takeover on an already-paused run or a repeat handback on an already-active run is a no-op success. The non-voice takeover response carries `mode: "takeover"` only (listen is not offered because there is no live audio to monitor).
* **New endpoint: `POST /{workspace_id}/runs/{run_id}/authored-turn`.** Stage an operator-authored reply for a non-voice run under takeover. The platform substitutes it for the agent's next outbound turn (exactly-once), so the caller receives the operator's words while the agent stays suspended. Repeatable - each call stages the next turn. Requires `admin` role (Operator:Update) and own-identity enforcement (no impersonation). Returns 404 if the run is not live; returns 409 for voice runs (voice takeover drives the live audio leg directly). Request body: `operator_id` (UUID, required), `text` (string, 1-10,000 characters, required). Response: `run_id` (UUID), `staged` (boolean, always true on 200).
* **Takeover eligibility updated for non-voice channels.** The server-computed `takeover_eligibility` object on run responses now reports non-voice conversation runs as eligible with `mode_options: ["takeover"]` (no listen mode). Previously these channels were reported as ineligible. Voice runs continue to report `mode_options: ["listen", "takeover"]`.
* **Authored-turn attempts are audit-logged.** Each authored-turn call emits an audit event with operator and run identifiers. The turn text is never included in the audit record.

**What you need to do:**

* **No action required for existing integrations.** This is an additive change. Voice takeover workflows are unaffected. Callers that previously received 409 for non-voice takeover will now receive a successful response.
* **Use the new authored-turn endpoint to compose replies.** After taking over a non-voice run, POST to `/{workspace_id}/runs/{run_id}/authored-turn` with the operator's reply text. The agent's next outbound step will send the operator's text instead of its own.

</details>

<details>

<summary>Platform API: Non-Voice Operator Takeover - Authored-Turn Send-Gate (July 2026)</summary>

#### Non-Voice Operator Takeover - Authored-Turn Send-Gate

Operators can now take over non-voice conversation runs (text, SMS, email, web) by composing a reply that the platform sends instead of the agent's next outbound turn.

**What changed:**

* **Authored-turn substitution for non-voice channels.** When an operator takes over a non-voice run, the operator's composed reply is durably staged and consumed exactly once by the agent's next outbound step. The agent's own outbound text is replaced with the operator's authored turn, so the caller receives the operator's reply. This is the non-voice counterpart to voice takeover's live audio seize.
* **Decoupled compose and send.** The operator's authored turn survives pod restarts and rolling deploys between compose and send. The durable staging ensures the operator's reply is never silently dropped.
* **Send-gate on agent outbound.** Before sending each outbound turn (both mid-conversation replies and terminal closing messages), the agent checks for a pending operator-authored turn. If present, it atomically consumes and substitutes it. If absent, the agent's own turn proceeds with zero behavior change.
* **Tenant isolation.** An operator in one workspace cannot plant an authored turn for another workspace's agent. The authored turn is scoped to both the workspace and the run.
* **Distinct from integration-write approval.** The authored-turn mechanism is a separate obligation from the integration approval gate. Integration approval resolves a parked integration write by conversation; authored-turn takeover substitutes the agent's outbound text by run. The two mechanisms operate independently.
* **Fail-open consumption.** If the durable store is transiently unavailable when the agent checks for an authored turn, the agent proceeds with its own outbound rather than blocking. The authored turn remains staged for the next attempt.

**What you need to do:**

* **No action required for existing integrations.** This is an additive capability. Non-voice takeover uses the same run-scoped takeover endpoint already documented for voice runs. Existing voice takeover workflows are unaffected.
* **Non-voice runs now support takeover eligibility.** The server-computed takeover eligibility object on run responses will reflect takeover availability for non-voice channels where previously it returned ineligible.

</details>

<details>

<summary>Platform API: Multi-Value OR Filters on Runs List and Summary (July 2026)</summary>

#### Multi-Value OR Filters on Runs List and Summary

The `status`, `kind`, and `channel` query parameters on the runs list and summary endpoints now accept multiple values, acting as OR filters within each axis.

**What changed:**

* **`status`, `kind`, and `channel` accept repeated query parameters.** Each filter axis now accepts multiple values as repeated query parameters (e.g. `?status=failed&status=timed_out`). Values within an axis are unioned (OR), and different axes are combined (AND). For example, `?channel=voice&status=running` returns only live voice runs, while `?status=failed&status=timed_out` returns runs matching either status.
* **Applies to both list and summary endpoints.** The `GET /{workspace_id}/runs` list endpoint and the `GET /{workspace_id}/runs/summary` summary endpoint both support the same multi-value filter shape.
* **Backward compatible.** A single filter value (e.g. `?status=running`) continues to work exactly as before - the multi-value format is a strict superset of the previous single-value format. Omitting a filter axis entirely means no filter on that axis, matching the previous behavior.
* **`kind` filter routing.** When `kind` includes `framework`, framework runs are included; when it includes `conversation`, conversation runs are included. An empty `kind` (or both values) returns both sources. A `channel` filter still suppresses framework runs (which carry no channel), consistent with the previous behavior.

**What you need to do:**

* **No action required for existing integrations.** Single-value filter parameters continue to work unchanged. This is an additive, backward-compatible change.
* **Use repeated query parameters for multi-select.** To filter by multiple values, repeat the parameter: `?status=failed&status=timed_out`. Do not use comma-separated values.

</details>

<details>

<summary>Scribe API: Session-Centric Provider Access (July 2026)</summary>

#### Scribe Sessions API

A new session-centric REST API gives providers read access to their own clinical documentation sessions and artifacts.

**What changed:**

* **New endpoint: `GET /v1/{workspace_id}/sessions`.** Lists clinical documentation sessions owned by the authenticated provider, ordered by creation time (newest first). Supports pagination via `limit` (1-200, default 50) and opaque `continuation_token` query parameters. Each session includes lifecycle status and artifact availability.
* **New endpoint: `GET /v1/{workspace_id}/sessions/{session_id}`.** Returns a single session owned by the authenticated provider.
* **New endpoint: `GET /v1/{workspace_id}/sessions/{session_id}/transcript`.** Returns the session transcript segmented by speaker with timing information (start and end in milliseconds). Returns 404 if the transcript is not yet available.
* **New endpoint: `GET /v1/{workspace_id}/sessions/{session_id}/note`.** Returns the clinical note for a session, including note type, authoring status (`draft`, `submitted`, `voided`), body text, structured content, generation timestamp, and signature timestamp.
* **New endpoint: `GET /v1/{workspace_id}/sessions/{session_id}/codes`.** Returns ICD code suggestions for a session, each with code, description, rationale, confidence score (0-1, optional), and acceptance status (`suggested`, `accepted`, `rejected`, `voided`).
* **Session lifecycle statuses.** Sessions report their status as `created`, `in-progress`, `in-review`, `completed`, `cancelled`, or `failed`.
* **Artifact availability tracking.** Each session reports per-artifact availability (`pending`, `available`, or `failed`) for transcript, note, summary, and codes independently.
* **Provider ownership enforcement.** All endpoints enforce that the authenticated provider owns the requested session. Sessions belonging to other providers return 404.
* **Structured error envelope.** All error responses use a consistent envelope with `code`, `message`, `correlation_id`, and optional `details` array for field-level validation errors.
* **Correlation ID support.** Every response includes an `X-Correlation-ID` header. Callers can send their own correlation ID on requests for end-to-end tracing.
* **Deployment-specific API base.** Scribe routes use the Scribe API base URL supplied for the deployment, not the general Platform API base URL.
* **OpenAPI schema.** The Scribe API publishes its schema at `/v1/openapi.json` on that base URL for client generation and discovery.

**What you need to do:**

* **No action required for existing integrations.** These are new, additive endpoints. Providers with valid scribe session tokens and the `scribe:sessions:read_own` scope can begin using the endpoints immediately.
* **Handle artifact availability states.** Artifacts may not be immediately available after a session completes. Check the `artifacts` field on the session response before requesting individual artifacts. A `pending` status means the artifact is still being processed; `failed` means it could not be produced.

</details>

<details>

<summary>Platform API: 'Paused' Conversation Run Status (July 2026)</summary>

#### Paused Conversation Run Status

Conversation runs now support a "paused" status that represents a human operator taking over a live run.

**What changed:**

* **New run status: `paused`.** When an operator takes over a conversation run, the run's status is now durably recorded as `paused` rather than remaining `running`. This is a live, non-terminal status - the conversation still exists and the agent is suspended until the operator hands back. On handback, the run resumes its previous status.
* **`ConversationStatus` enum updated.** The conversation status model now includes `paused` as a valid value alongside `active`, `closed`, `completed`, `in-progress`, and `failed`.
* **Run status mapping.** The `paused` conversation status maps to the canonical `paused` run status, which is a live (non-terminal) status. Paused runs continue to appear in the "live" filter (which expands to `running` + `paused`) and are counted in the summary's live total.
* **OpenAPI spec updated.** The conversation status enum in the API schema now includes `paused`.

**What you need to do:**

* **Handle the new status value.** If your integration reads conversation or run status values, ensure it handles `paused` as a valid, non-terminal status. Paused runs are live and will eventually return to `running` (on handback) or reach a terminal state.
* **No breaking changes.** Existing filters and queries continue to work. The "live" status filter already expands to include `paused`, so live-filtered views will automatically include paused runs.

</details>

<details>

<summary>Platform API: Run-Scoped Switch-Mode and Access-Token Endpoints (July 2026)</summary>

#### Run-Scoped Switch-Mode and Access-Token

Two new run-scoped endpoints let operators switch between listen and takeover modes on a live run and mint browser-audio credentials, both addressed by the channel-neutral run identifier.

**What changed:**

* **New endpoint: `POST /v1/{workspace_id}/runs/{run_id}/switch-mode`.** Toggles the operator between `listen` (monitor without driving) and `takeover` (suspend the agent and drive) on a run they have already joined. For voice runs, this mutes or unmutes the operator's conference participant. Requires Operator update permission (operator, admin, or owner role), bound to the caller's own operator identity (no impersonation). Returns 404 if the run is not live in the workspace; 409 if the channel does not support live takeover yet.
* **Request body:** `operator_id` (uuid), `participant_call_sid` (string, max 64 chars - the participant identifier from the takeover response), `mode` ("listen" or "takeover").
* **Response:** `run_id` (uuid), `mode` (the applied mode).
* **New endpoint: `POST /v1/{workspace_id}/runs/{run_id}/access-token`.** Mints browser-audio credentials so the console can attach the operator's WebRTC leg to a live voice run. This is the media-plane companion to the takeover endpoint (the control plane). Requires Operator update permission (operator, admin, or owner role), bound to the caller's own operator identity. Returns 404 if the run is not live; 409 if the channel has no browser-audio leg.
* **Request body:** `operator_id` (uuid).
* **Response:** `token` (string), `identity` (string), `conference_sid` (string, optional), `connect_params` (object, optional - a map of string key-value pairs the console passes to the browser audio device to route the operator's media leg correctly; pass these through verbatim when connecting).
* **Audit logging.** Both switch-mode and access-token attempts are audit-logged with operator attribution on success and failure paths, recording the run, operator identity, and outcome. The audit emit is best-effort and never fails an already-executed action.
* **Error handling.** A malformed upstream response (missing required fields) returns 502 and is not recorded as a success in the audit trail - the response is validated before the success audit is emitted.

**What you need to do:**

* **No action required.** These are additive endpoints. The existing takeover and handback flows continue to work unchanged. Use switch-mode when the operator needs to toggle modes after the initial join, and access-token when the console needs to establish the operator's browser audio leg. If you are building a custom operator console, pass the `connect_params` from the access-token response through to the browser audio device connect call - without them the operator's audio leg may not attach correctly.

</details>

<details>

<summary>Platform API: Simulation Recovery and Metering Accuracy (July 2026)</summary>

#### Simulation Recovery and Metering Accuracy

Two follow-up reliability improvements to the simulation run lifecycle.

**What changed:**

* **Safer recovery finalization.** A recovery attempt no longer marks a run failed if another attempt has already resumed it. This prevents an active, recovered run from being overwritten by a stale failure decision.
* **Metered duration cap.** Billable duration for one simulation run is capped at the platform's per-run execution timeout. This prevents queue and recovery delays from being counted as compute time.

**What you need to do:**

* **No action required.** Simulation recovery is safer and metering is more accurate for runs that experienced queueing or recovery delays. No API surface changes.

</details>

<details>

<summary>Platform API: Run-Scoped Takeover and Handback Endpoints + Takeover Eligibility on Run Object (July 2026)</summary>

#### Run-Scoped Takeover and Handback

Operators can now take over and hand back a live run using the channel-neutral run identifier, and every run now exposes server-computed takeover eligibility so the console no longer needs to derive it client-side.

**What changed:**

* **New endpoint: `POST /v1/{workspace_id}/runs/{run_id}/takeover`.** Registers the caller's operator identity on a live run and (in `takeover` mode) suspends the agent so the human drives. `listen` mode monitors without driving. For voice runs, the response includes the conference and participant identifiers the console needs to attach browser audio. Requires Operator update permission (operator, admin, or owner role), bound to the caller's own operator identity (no impersonation). Returns 404 if the run is not live in the workspace; 409 if the channel does not support live takeover yet.
* **New endpoint: `POST /v1/{workspace_id}/runs/{run_id}/handback`.** Releases the caller's operator from a run they took over so the agent resumes. Same permission and identity requirements as takeover. Returns 404 if the run is not live; 409 if the channel does not support live takeover.
* **New computed field on the run object: `takeover`.** Every run now includes a `takeover` object with `eligible` (boolean), `mode_options` (list of valid modes for the channel), and `reason` (human-facing explanation when not eligible). Eligibility is derived server-side from the run's kind, channel, status, and transport handle. Voice runs that are live and have a transport handle are eligible with modes `listen` and `takeover`. Other channels, non-conversation runs, and terminal runs are not eligible. Because this is a computed field, it is always consistent and requires no console-side derivation.
* **Audit logging.** Both takeover and handback attempts are audit-logged with operator attribution on success and failure, recording the run, channel, operator identity, mode (for takeover), and outcome. The audit emit is best-effort and never fails an already-executed action.
* **Idempotent takeover.** The same operator re-joining a run they already took over is safe and returns the existing session.

**What you need to do:**

* **No action required.** These are additive endpoints and a new computed field. The existing voice-specific operator flows continue to work. If you derive takeover eligibility client-side, you can replace that logic with the `takeover` field on the run object.

</details>

<details>

<summary>Platform API: Promote Playground Session to Coverage Run (July 2026)</summary>

#### Promote Playground Session to Coverage Run

Interactive playground sessions start without a coverage run, so run-scoped operations like fork and score are unavailable. A new endpoint promotes a playground session into a coverage run, unblocking these operations.

**What changed:**

* **New endpoint: `POST /v1/{workspace_id}/simulations/sessions/{session_id}/promote`.** Promotes a run-less interactive session into a coverage run. The endpoint creates a new coverage run, binds the existing session to it, and returns the run identifier along with the session identifier and an `already_bound` flag. The session itself is not recreated - only the run binding is established.
* **Idempotent.** If the session is already bound to a coverage run, the existing run is returned with `already_bound: true`. No new run is created.
* **Response fields:** `run_id` (uuid - the coverage run the session is now bound to), `session_id` (string - unchanged), `already_bound` (boolean).
* **After promotion,** fork, score, and other run-scoped operations work on the session.
* **Requires write permission** (`Service.update` scope).

**What you need to do:**

* **No action required.** This is an additive endpoint. Existing sessions and runs are unaffected. Use this endpoint when you want to fork or score an interactive playground session that was created without a run.

</details>

<details>

<summary>Platform API: Live Built-in Tool Catalog Endpoint (July 2026)</summary>

#### Live Built-in Tool Catalog

A new endpoint exposes the platform's built-in tool catalog at runtime, so integrators and the Developer Console can discover which built-in tools are available without hardcoding a static list.

**What changed:**

* **New endpoint: `GET /v1/{workspace_id}/tools/catalog`.** Returns the live set of built-in tools recognized by the platform, including each tool's identifier, display name, and description. The catalog is read from the running platform rather than a static list, so it stays current as built-in tools are added or retired.
* **Read-only.** The endpoint is a simple GET that requires workspace-level read access. No write operations are exposed.

**What you need to do:**

* **No action required.** This is an additive, read-only endpoint. If you maintain a hardcoded list of built-in tools in your integration, you can replace it with a call to this endpoint to stay current automatically.

</details>

<details>

<summary>Platform API: Run-Scoped Operator Guidance Endpoint (July 2026)</summary>

#### Run-Scoped Operator Guidance

Operators can now send text guidance to a live run using the run's channel-neutral identifier, without needing a channel-specific identifier like a call SID.

**What changed:**

* **New endpoint: `POST /v1/{workspace_id}/runs/{run_id}/guidance`.** Sends text guidance to the agent handling a live run. The agent incorporates the guidance into its next response without the operator taking over. The endpoint resolves the run within the workspace and dispatches per channel.
* **Channel-neutral addressing.** The endpoint accepts a `run_id` rather than a channel-specific identifier. The platform translates the run identifier to the appropriate channel coordinates internally.
* **Operator identity enforcement.** The `operator_id` in the request body must match the authenticated caller's own operator identity. Impersonation is not allowed.
* **Request body.** `operator_id` (UUID, required) and `message` (string, 1-5000 characters, required).
* **Response.** Returns the delivery status and the `run_id` the guidance was sent to.
* **Audit logging.** Every guidance attempt - successful or failed - is audit-logged with operator attribution, the target run, channel, and a content fingerprint. The raw guidance text is never stored in the audit record.
* **Voice-only delivery.** Voice runs are supported. Non-voice live channels return **409 Conflict** because operator guidance is not supported on those channels.
* **Error cases.** Returns 404 if the run is not a live run in the workspace or the operator is not found. Returns 409 if the run's channel does not yet support live guidance. Returns 403 if the caller does not have the required permission.
* **Permission required.** Requires Operator update permission (operator, admin, or owner role).

**What you need to do:**

* **No action required.** This is an additive endpoint. The existing voice-specific guidance path continues to work during the migration. Use this endpoint to send guidance to live voice runs by their channel-neutral run identifier.

</details>

<details>

<summary>Platform API: Unknown sort_by Fields Now Rejected with 422 + Skill Sampling Parameters in Tester (July 2026)</summary>

#### Unknown sort\_by Fields Now Rejected with 422

Paginated list endpoints that accept a `sort_by` query parameter now return **422 Unprocessable Entity** when the requested field is not in the allowed set. Previously, an unrecognized field silently fell back to default ID sorting, which could make a client sort control appear to work while actually having no effect.

The 422 response body includes the list of allowed field names so callers can self-correct.

**What changed:**

* **`sort_by` validation is now strict.** Any paginated endpoint that accepts `sort_by` rejects unknown field names with a 422 instead of silently falling back to ID sort. This matches the validation behavior already used by other endpoints (such as integration runs) that reject unknown fields.
* **Error response includes allowed fields.** The 422 detail message lists the valid field names for the endpoint, so callers can update their requests without consulting documentation.

**What you need to do:**

* **Check any callers that pass `sort_by` values.** If a caller was sending an unsupported field and relying on the silent fallback to ID sorting, it will now receive a 422. Update the `sort_by` value to one of the allowed fields listed in the error response.

***

#### Skill Sampling Parameters Threaded Through Tester

Skill definitions now support optional `temperature` and `top_p` sampling parameters (both 0-1), and the skill tester in the Developer Console threads these parameters through to test executions so that a console "Test" run matches production behavior.

**What changed:**

* **New fields on skill definitions.** `temperature` (number, nullable) and `top_p` (number, nullable) can be set on a skill definition to control response randomness and diversity. Both are model-gated - models that reject sampling parameters ignore them silently.
* **Tester honors sampling parameters.** When you run a skill test from the Developer Console, the tester now passes the skill's configured `temperature` and `top_p` to the execution engine, so test results reflect the same sampling behavior as production runs.
* **At most one parameter.** If both `temperature` and `top_p` are set, the runtime keeps `temperature` and drops `top_p`.

**What you need to do:**

* **No action required for the tester change.** Skill tests will automatically use the configured sampling parameters.
* **Optionally configure sampling parameters.** If you want to control response randomness for a skill, set `temperature` or `top_p` on the skill definition through the API or Developer Console.

</details>

<details>

<summary>Platform API: Simulation Run Failure Reasons and Suite Completeness (July 2026)</summary>

#### Simulation Run Failure Reasons and Suite Completeness

Simulation runs now persist a short failure reason when they fail, and suite run summaries now report the expected case count so consumers can detect cases that failed to start.

**What changed:**

* **Failure reason on run responses.** Simulation run responses (`SimulationRunResponse` and per-run benchmark summaries) now include an `error` field containing a short failure reason when the run's status is `failed` - for example, "All 3 scenarios failed" or "bridge batch run failed". The field is null for successful runs and for legacy failed runs that predate this release. Max length is 500 characters.
* **Expected case count on suite run summaries.** Suite run summary responses now include an `expected_case_count` field that records how many cases were selected at launch time (after any max-cases cap). This count is frozen at launch because suite definitions are mutable and selection may be capped. A gap between `expected_case_count` and `total_runs` means some cases failed to start - they leave no run record and are otherwise invisible in the aggregate. The field is null on legacy suite runs that predate this release.
* **No breaking changes.** Both fields are nullable additions to existing response shapes. Existing integrations are unaffected.

**What you need to do:**

* **No action required.** These are additive fields. If you display simulation run details or suite run summaries, you can optionally surface the new `error` and `expected_case_count` fields to give users better visibility into why runs failed and whether all cases in a suite started successfully.

</details>

<details>

<summary>Platform API: Drain-Aware Simulation Executor with Instant Re-Drive Handoff (July 2026)</summary>

#### Drain-Aware Simulation Executor with Instant Re-Drive Handoff

Simulation batch launches are now drain-aware during graceful shutdowns (rolling deployments). A retiring platform instance stops accepting new batch launches and hands off in-flight runs to a healthy instance with minimal delay, so long-running suites are not interrupted by routine deployments.

**What changed:**

* **New batch launches rejected on retiring instances.** During a graceful shutdown, the platform instance stops accepting new simulation batch launches (bridge, benchmark, suite, and single-case runs) and returns a retryable 503 with a Retry-After header. The client's automatic retry lands on a healthy instance and the batch runs uninterrupted. Interactive session endpoints remain available through the shutdown grace window.
* **Instant heartbeat expiry for in-flight runs.** When a retiring instance shuts down, it immediately expires the heartbeats of any runs still owned by its in-flight batches. The recovery sweep on a healthy instance detects these runs on its very next pass and reclaims them, rather than waiting out the full staleness window. This reduces the handoff gap from minutes to seconds.
* **Heartbeat expiry is best-effort.** If the platform cannot reach the data store during shutdown (for example, if the connection is already closed), the expiry is skipped and the staleness window remains the fallback - the same behavior as an ungraceful exit.
* **No change to the run contract.** Run status values, API endpoints, and client-facing behavior are unchanged. The improvement is transparent to callers.

**What you need to do:**

* **No action required.** This is a reliability improvement. Simulation runs and suite runs that span a rolling deployment will recover faster. If your client already retries on 503 responses, new batch launches will land on a healthy instance automatically.

</details>

<details>

<summary>Platform API: Simulation Run Re-Drive and Automatic Recovery (July 2026)</summary>

#### Simulation Run Re-Drive and Automatic Recovery

Simulation suite runs orphaned by a process exit (rolling deployment, unexpected restart) are now automatically re-executed in place so long suites resume without manual re-triggering, with a bounded attempt cap and a terminal backstop for unrecoverable runs.

**What changed:**

* **Automatic re-execution of orphaned runs.** The platform now detects simulation runs orphaned mid-suite and re-executes them from a clean slate on a healthy replica. Each run's execution state is checkpointed at batch start, and a periodic heartbeat marks the run alive while it executes. When the heartbeat goes stale (the owning process died), a recovery sweep reclaims the run and re-executes it. This replaces the previous behavior of immediately failing orphaned runs, so large regression suites recover automatically instead of requiring manual re-triggering.
* **Bounded re-drive attempts.** Each run tracks how many times it has been re-driven. If a run exceeds the attempt cap (for example, because the scenario consistently causes a failure), it is failed terminally so the suite reaches a reportable state rather than re-driving forever.
* **Multi-replica safety.** The recovery claim uses a compare-and-swap so exactly one replica wins a given run. An attempt fence ensures that a superseded executor (one whose run was re-claimed by another replica) cannot finalize a run it no longer owns - it cannot write a terminal status or emit metering for a run another replica took over.
* **Active-run liveness tracking.** Recovery now uses execution liveness rather than record age when deciding whether a long-running suite has stalled. This prevents queued or active work from being failed only because its record was created earlier.
* **Suite run completion.** When orphaned runs are recovered or failed terminally, their parent suite runs reach a terminal state. Previously, a single orphaned run could cause an entire suite to appear stuck indefinitely.
* **Graceful interruption recovery.** Interrupted in-flight runs remain eligible for retry instead of being failed immediately. Runs that cannot be recovered eventually reach a terminal failure after the configured retry policy is exhausted.

**What you need to do:**

* **No action required.** This is a reliability improvement. Simulation runs and suite runs that previously required manual re-triggering after a platform restart will now recover automatically. There are no API changes - the run status values and endpoints are unchanged.

</details>

<details>

<summary>Platform API: Orphaned Simulation Run Reclaim and Graceful Cancellation (July 2026)</summary>

#### Orphaned Simulation Run Reclaim and Graceful Cancellation

Simulation runs that are orphaned by an ungraceful process exit are now automatically reclaimed and failed, and in-flight runs that are cancelled are now transitioned to a failed state instead of remaining stuck as running.

**What changed:**

* **Orphaned run reclaim.** The platform now periodically detects simulation runs stuck in a running state well past the expected completion window and marks them as failed. This covers the case where the process driving a run exits (for example, during a rolling deployment or unexpected restart) before it can record the run's result. The reclaim is idempotent and workspace-isolated - concurrent platform replicas cannot double-fail a run or clobber a run that completed normally between detection and the status update.
* **Suite run completion.** When orphaned runs are reclaimed, their parent suite runs also reach a terminal state. Previously, a single orphaned run could cause an entire suite run to appear stuck indefinitely.
* **Graceful cancellation handling.** When in-flight simulation runs are cancelled (for example, during a graceful platform shutdown), each run is now transitioned to a failed state before the cancellation completes. Previously, cancelled runs could remain in a running state with no process to finalize them.
* **Idempotent completion guard.** The run completion path now supports an idempotent guard that prevents a run already in a terminal state from being overwritten. This ensures that a run which completed normally is never clobbered back to failed by a concurrent reclaim or cancellation.

**What you need to do:**

* **No action required.** This is a reliability improvement. Simulation runs and suite runs that previously appeared stuck after a platform restart will now reach a terminal state automatically. There are no API changes - the run status values and endpoints are unchanged.

</details>

<details>

<summary>Platform API: Dynamic Behaviors Feature Removed (July 2026)</summary>

#### Dynamic Behaviors Feature Removed

The dynamic behaviors feature has been removed from the Platform API. The endpoints, data model, and underlying storage have been deleted.

**What changed:**

* **Endpoints removed.** The five dynamic behaviors endpoints (create, list, get, replace, delete) previously available under the service scope have been removed. Requests to these endpoints will return 404.
* **Data removed.** Existing dynamic behavior records, including triggers and action configurations, have been dropped. This data is no longer accessible.
* **Breaking change.** This is a breaking removal. Any integrations or automation that referenced the dynamic behaviors endpoints will need to be updated.

**What you need to do:**

* **Remove any references to dynamic behaviors endpoints.** If your integration creates, lists, or manages dynamic behaviors through the API, remove those calls. The endpoints no longer exist.
* **Update any SDK or CLI workflows.** If you used Agent Forge or SDK methods to sync dynamic behaviors, remove those steps from your workflows.

</details>

<details>

<summary>Platform API: Billable Token Metering on Raw Real-Time Voice Runtime (July 2026)</summary>

#### Billable Token Metering on Raw Real-Time Voice Runtime

The raw real-time speech-to-speech voice runtime now emits billable per-response token-usage events, bringing it to parity with the Atlas voice runtime. Calls on the raw real-time runtime now appear in standard usage reporting alongside Atlas and in-house pipeline calls.

**What changed:**

* **Per-response billing events.** Every model response during a raw real-time voice call now emits a billable token-usage event covering input and output tokens. Previously, the raw real-time runtime emitted observability metrics for token counts but did not produce billing events, so calls on this runtime were invisible in usage reporting.
* **Fire-and-forget billing.** The billing emit is non-blocking and never crashes or stalls the audio path. If the billing sink is unavailable or not yet configured for an environment, the emit is a no-op and the call proceeds normally. Transient billing failures are surfaced through observability metrics rather than affecting the call.
* **Parity with Atlas runtime.** Both session-owning voice runtimes (Atlas and raw real-time) now emit the same billable usage event shape per model response, so usage reporting is consistent regardless of which runtime handles a call.

**What you need to do:**

* **No action required.** This is a billing-accuracy improvement. If you use the real-time speech-to-speech voice family, calls on the raw runtime will now appear in your workspace's usage reporting automatically. There are no API changes or configuration changes.

</details>

<details>

<summary>Platform API: Reasoning Effort Pinned for Realtime Reasoning Models (July 2026)</summary>

#### Reasoning Effort Pinned for Realtime Reasoning Models

The voice pipeline now automatically pins reasoning effort to its lowest setting when using a reasoning-capable realtime model, fixing a regression where the model would speak its internal reasoning preamble instead of proceeding to tool calls.

**What changed:**

* **Automatic reasoning effort configuration.** When a voice service is configured with a reasoning-capable realtime model, both the Atlas voice provider and the raw realtime voice provider now set the reasoning effort to the lowest level in the session configuration. This follows the model provider's migration guidance, which requires explicitly setting reasoning effort rather than relying on the default. At the default effort level, the model would narrate a reasoning preamble (e.g., "let me check that for you...") and then fail to execute the intended tool call - the root cause of the zero-tool-call regression reported on the latest reasoning realtime model.
* **Model-gated behavior.** The reasoning effort setting is applied only for reasoning-capable realtime models. Non-reasoning realtime models are unaffected, as they do not support the reasoning effort parameter and would reject it.

**What you need to do:**

* **No action required.** This fix is applied automatically for all voice services using reasoning-capable realtime models. If you were experiencing a regression where the model spoke its thought process instead of calling tools, this resolves it.

</details>

<details>

<summary>Platform API: Full-Duplex Voice Family Is Generally Available (July 2026)</summary>

#### Full-Duplex Voice Family Is Generally Available

The full-duplex voice model family is now generally available and can be selected through service or agent voice configuration.

**What changed:**

* **Full-duplex voice family is now GA.** The full-duplex voice model family is now generally available. Services and agents configured to use the full-duplex provider will connect to the full-duplex session endpoint. The full-duplex family is selectable via service or agent voice configuration and is never the default.
* **Fail-safe unchanged.** If the upstream full-duplex session endpoint is unavailable, the call fails at session connect and the standard fail-safe routes the call to the in-house pipeline, consistent with existing behavior for all voice families.

**What you need to do:**

* **No action required.** Existing configurations continue to work. Select the full-duplex family only for services where you want that speech-to-speech runtime.

</details>

<details>

<summary>Platform API: Guidance Delivery Status Honesty, Transcript Read-Audit, and Blueprint GA Status (July 2026)</summary>

#### Guidance Delivery Status Honesty, Transcript Read-Audit, and Blueprint GA Status

Operator whisper guidance now reports the actual delivery outcome instead of falling back to a default, conversation transcript reads are now audit-logged as PHI access events, and the enterprise UX blueprint has moved to GA status.

**What changed:**

* **Honest guidance delivery status.** The send-guidance response now returns the real delivery outcome from the voice session engine. The `status` field on the response is expanded from `delivered | failed` to `delivered | queued_no_subscriber | deduplicated | failed | unknown`. Previously, unrecognized or missing statuses were silently mapped to a default value, which could mask delivery issues. The platform now reports `unknown` when it cannot determine the outcome rather than claiming success or a queue state.
  * `delivered` - the guidance was delivered to a live subscriber.
  * `queued_no_subscriber` - the session ended between the eligibility check and publish; the message was queued but no subscriber consumed it.
  * `deduplicated` - the message matched a recent guidance within the idempotency window and was not re-delivered.
  * `failed` - delivery failed.
  * `unknown` - the platform could not determine the delivery outcome (the upstream did not return a recognized status).
* **Conversation transcript reads are now audit-logged.** Retrieving a conversation transcript detail (the single-conversation endpoint that returns full message bodies) now emits a PHI read-audit event. This closes the gap where transcript access was not captured in the audit stream. List endpoints (conversation lists, run lists, entity lists) are deliberately not read-audited - they are polled on an interval and would flood the audit stream without expressing meaningful human access.
* **Enterprise UX blueprint moved to GA.** The developer console enterprise UX blueprint has been promoted from preview to generally available status.

**What you need to do:**

* **Update any client code that pattern-matches on the `status` field of the send-guidance response.** The field can now return `queued_no_subscriber`, `deduplicated`, or `unknown` in addition to the previous `delivered` and `failed` values. Clients that treat any non-`delivered` status as an error will continue to work correctly; clients that switch on exact values should add cases for the new values.
* **No action required for transcript read-audit.** The audit event is emitted automatically. If you consume audit events, you can filter for `conversation.transcript_read` to trace transcript access.
* **No action required for blueprint GA.** This is an access-broadening change.

</details>

<details>

<summary>Platform API: Durable Audit Event for Operator Whisper Guidance + Workspace Authorization on Operator Actions (July 2026)</summary>

#### Durable Audit Event for Operator Whisper Guidance

Sending whisper guidance to an agent during a live call now produces a durable audit event attributed to the operator, and operator action endpoints enforce workspace-level authorization on the credential.

**What changed:**

* **`operator.guidance_sent` audit event.** The send-guidance endpoint now emits a durable `operator.guidance_sent` audit event after successful delivery. The event records the operator identity, call reference, message length, a SHA-256 hash of the guidance text, and the delivery status. The raw guidance text is deliberately excluded - conversational content stays out of audit events, consistent with the platform's data handling posture for transcripts. This event joins the existing durable operator lifecycle events (join, mode change, wrap-up) so every action that steers what the AI says to a live caller is traceable.
* **Workspace authorization on operator actions.** Operator action endpoints (join, mode change, leave, access token, and guidance) now verify that the credential's workspace matches the workspace specified in the request body. A valid credential from one workspace can no longer act on another workspace's live calls. This is a defense-in-depth measure - these routes were already network-isolated, but the workspace check closes the gap regardless of network posture.

**What you need to do:**

* **No action required.** The audit event is emitted automatically. If you consume audit events, you can filter for `operator.guidance_sent` to trace whisper guidance actions.
* **No action required for workspace authorization.** If your operator credentials already belong to the same workspace as the calls they act on (the expected configuration), this change has no effect. Requests using a credential from a different workspace than the target call will now receive a 403 response.

</details>

<details>

<summary>Platform API: Surface Responses Expose Review and Archive Provenance (July 2026)</summary>

#### Surface Responses Expose Review and Archive Provenance

Surface responses now include review and archive provenance fields, so operators can distinguish a rejected surface from a naturally-expired one and access the reviewer's typed reject reason.

**What changed:**

* **New `review_notes` field on surface responses.** A string (or null) containing the notes entered by the reviewer when approving or rejecting the surface.
* **New `reviewed_by` field on surface responses.** A UUID string (or null) identifying the credential that approved or rejected the surface.
* **New `reviewed_at` field on surface responses.** An ISO 8601 timestamp (or null) recording when the surface was reviewed.
* **New `archived_at` field on surface responses.** An ISO 8601 timestamp (or null) recording when the surface was archived.
* **New `archive_reason` field on surface responses.** A string (or null) containing the reason provided when the surface was archived.

These fields are returned on all surface read endpoints (get, list). All five fields are null when no review or archive action has been taken.

**What you need to do:**

* **No action required.** These are additive, nullable fields on existing surface responses. Existing clients that do not read these fields are unaffected.
* **To display review provenance**, read `review_notes`, `reviewed_by`, and `reviewed_at` from surface responses. The `reviewed_by` value is the raw credential identifier recorded by the approve or reject action.
* **To display archive provenance**, read `archived_at` and `archive_reason` from surface responses.

</details>

<details>

<summary>Platform API: Audit Export Downloads, Actor Email on Audit Entries, Permission Enforcement (July 2026)</summary>

#### Audit Export Downloads, Actor Email on Audit Entries, Permission Enforcement

Audit export artifacts can now be streamed through a dedicated download endpoint, audit log entries carry the actor's email, and several endpoints now enforce documented permission gates.

**What changed:**

* **New audit export download endpoint.** `GET /v1/{workspace_id}/audit/exports/download/{filename}` streams an audit export artifact (NDJSON) directly to the caller without buffering the full file in memory. The filename must be a bare `{export_id}.ndjson` name - path separators and traversal sequences are rejected with 422. Returns 404 if the artifact does not exist. Each download is audit-logged as a PHI access event. Requires `Audit.export` permission (admin or owner).
* **`download_url` values updated.** The `download_url` returned by the create-export and list-exports endpoints now returns a workspace-scoped proxy download path (`/{workspace_id}/audit/exports/download/{filename}`) instead of the previous path format.
* **`actor_email` field on identity audit log entries.** Each audit log entry now includes an `actor_email` field (string or null) resolved server-side from the actor entity's federation identities. The email identifies who performed the action. It is null when the actor has no email-bearing federation identity (for example, machine credentials). This field is never sourced from target or invited email metadata.
* **`last_modified` format on export list items.** The `last_modified` field on audit export list items is now returned as an ISO 8601 UTC string instead of a raw numeric timestamp.
* **Permission enforcement on audit endpoints.** The list-events, PHI access report, entity access log, and audit summary endpoints now enforce `Audit.view` permission (admin or owner). Previously these endpoints relied on role checks only.
* **Permission enforcement on workspace update and provision.** The update-workspace and provision-workspace endpoints now enforce `Workspace.update` permission (admin or owner). Previously update-workspace accepted any authenticated role.
* **Region is immutable on workspace update.** The update-workspace endpoint now rejects any non-null `region` value with 422. Workspaces are pinned to the region they were created in (data-residency guarantee) and cannot be migrated.
* **Permission enforcement on retention policy update.** The update-retention-policy endpoint now enforces `Workspace.update` permission (admin or owner).

**What you need to do:**

* **Use the new download endpoint for export artifacts.** If you previously constructed download URLs manually, update to use the `download_url` value returned by the create or list endpoints.
* **Handle `actor_email` on audit entries.** If you consume identity audit log entries, the new `actor_email` field is available for display. It is null for machine credentials.
* **Check role requirements.** If you have integrations using non-admin API keys to update workspaces, provision workspaces, update retention policies, or read audit data, ensure those keys have the required permissions (admin or owner).
* **Do not send `region` on workspace updates.** If your integration includes `region` in update-workspace requests, remove it or set it to null. Non-null values are now rejected.

</details>

<details>

<summary>Platform API: Turn Identity on Streaming Done Frame (July 2026)</summary>

#### Turn Identity on Streaming Done Frame

The SSE `done` event emitted at the end of a streaming conversation turn now carries the same stable turn identity fields as the non-streaming `POST /turns` response, so streaming clients can anchor durable per-turn artifacts (such as feedback or annotations) without issuing a follow-up read.

**What changed:**

* **New `turn_id` field on the `done` SSE frame.** The `done` event now includes a `turn_id` (UUID) that matches the `turn_id` on the non-streaming turn response and on conversation history turns. The identifier is deterministic - it is derived from the conversation and the exchange ordinal, so it is identical across streaming and non-streaming paths. Null when the conversation has no user exchange yet (a greeting kickoff on a fresh conversation).
* **New `turn_index` field on the `done` SSE frame.** The `done` event now includes a `turn_index` (integer, zero-based) indicating the ordinal of the user exchange (0 = first user turn). Null exactly when `turn_id` is null.
* **Durable turn counter.** The turn count that drives turn identity now survives session-store expiry. When a conversation is resumed after the session store has evicted its journal, the platform seeds the turn counter from the last persisted value rather than restarting at zero. This prevents duplicate turn identifiers from being issued on resumed conversations.

**What you need to do:**

* **No action required.** These are additive fields on the existing `done` SSE event. Existing streaming clients that do not read these fields are unaffected.
* **Streaming clients that need per-turn anchoring** can now read `turn_id` and `turn_index` directly from the `done` frame instead of issuing a separate conversation history read.

</details>

<details>

<summary>Platform API: Voice Model Family Rename - session_provider Taxonomy (July 2026)</summary>

#### Voice Model Family Rename

The `session_provider` field on voice configuration now uses model family names instead of internal runtime codenames. This is a breaking change to the accepted enum values.

**What changed:**

* **New family-based enum values.** The `session_provider` field now accepts three values: `amigo` (the default Amigo pipeline - STT, reasoning, TTS), `gpt_realtime` (real-time speech-to-speech), and `gpt_live` (full-duplex). These names describe model families.
* **Previous values removed from the API schema.** The former values `inhouse`, `openai_realtime`, and `atlas` are no longer part of the public API contract. The API schema advertises only the three new family names.
* **Server-side migration.** Existing stored configurations are automatically migrated to the new values. In-flight requests using the old values are normalized server-side during a transition period, so existing integrations will not break immediately - but clients should update to the new values.
* **Inheritance simplified.** The inheritance chain is now service to agent to environment default (workspace-level override has been removed from the inheritance path).

**Value mapping:**

| Previous value    | New value              |
| ----------------- | ---------------------- |
| `inhouse`         | `amigo`                |
| `openai_realtime` | `gpt_realtime`         |
| `atlas`           | `gpt_realtime`         |
| `gpt_live`        | `gpt_live` (unchanged) |

**What you need to do:**

* **Update any integrations that set `session_provider`.** Replace `inhouse` with `amigo`, and replace `openai_realtime` or `atlas` with `gpt_realtime`. The old values are accepted temporarily but will be removed in a future release.
* **Update any code that reads `session_provider` values.** Responses now return the new family names. If your code switches on `inhouse`, `openai_realtime`, or `atlas`, update those checks.

</details>

<details>

<summary>Platform API: Full-Duplex Voice Model Family (July 2026)</summary>

#### Full-Duplex Voice Model Family

A new voice model family option is available for the voice configuration `session_provider` field. The `gpt_live` family is a full-duplex model family that handles turn-taking natively - the model decides when to speak, listen, pause, interrupt, or backchannel continuously during the conversation, rather than relying on voice activity detection.

**What changed:**

* **New `gpt_live` session provider value.** The `session_provider` field on voice configuration now accepts `gpt_live` in addition to `amigo` and `gpt_realtime`. Full-duplex calls keep feature parity with other voice calls: context graph navigation, tool calls, turn history, and usage metering all work the same way.
* **Independent model configuration.** The full-duplex family is configured separately from the real-time speech-to-speech family, so enabling or testing one family never affects the other.
* **Native turn-taking.** Full-duplex models own their speak, listen, and tool decisions natively rather than relying on voice activity detection.
* **Graceful fallback.** Where the full-duplex family is not yet enabled, selecting `gpt_live` falls back to the Amigo pipeline rather than failing the call.

**What you need to do:**

* **No action required.** The family is disabled by default and no existing configurations are affected.
* **Do not configure `gpt_live` in production yet.** Calls configured with `gpt_live` fall back to the Amigo pipeline until the family is enabled for your environment.

</details>

<details>

<summary>Platform API: Stable Turn Identity on Conversation Turns (July 2026)</summary>

#### Stable Turn Identity on Conversation Turns

Conversation turn messages now carry a stable, deterministic identity that links each message to the user exchange it belongs to. The identity is consistent across `POST /turns` responses and conversation history reads, so clients can anchor durable per-turn artifacts (such as feedback or annotations) across page reloads and re-fetches.

**What changed:**

* **New `turn_id` field on conversation turn messages.** Each message in a conversation's turn list now includes a `turn_id` (UUID). The identifier is derived deterministically from the conversation and the exchange ordinal, so it is identical whether the message is returned from `POST /turns` or read back from the conversation history. User and agent messages from the same exchange share the same `turn_id`. The field is null on messages that precede the first user turn (proactive greetings, channel-event preludes).
* **New `turn_index` field on conversation turn messages.** Each message also includes a `turn_index` (integer, zero-based) indicating the ordinal of the user exchange it belongs to (0 = first user turn). Derived server-side and never stored. Null exactly when `turn_id` is null.
* **`turn_id` on `POST /turns` response updated.** The top-level `turn_id` on the turn response is now a deterministic UUID (previously a random opaque string). It matches the `turn_id` stamped on the returned messages and on the same conversation's history turns, so it can be used as a durable key for per-turn artifacts. Null only when the conversation has no user exchange yet (a poll or kickoff before the first user message).
* **Consistent identity across voice and text.** Both text and voice conversation histories derive turn identity using the same logic, so the identifiers are stable regardless of which read path serves the history.

**What you need to do:**

* **No action required for existing integrations.** The new fields are additive. Existing clients that do not read `turn_id` or `turn_index` are unaffected.
* **Adopt `turn_id` for durable per-turn references.** If you anchor feedback, annotations, or other artifacts to a conversation turn, switch from any client-generated identifier to the platform-provided `turn_id`. It is stable across responses and history re-reads.
* **Update `turn_id` parsing if needed.** If your integration previously parsed the top-level `turn_id` on `POST /turns` responses as an opaque string (e.g., `turn_abc123def456`), note that it is now a UUID (or null). Update any type assumptions accordingly.

</details>

<details>

<summary>Platform API: Text Conversation Lifecycle Hardening - Terminal Close, Force-New, and Rebind Recovery (July 2026)</summary>

#### Text Conversation Lifecycle Hardening

Text conversation lifecycle management on thread-keyed channels (SMS, iMessage) has been hardened with three capabilities: terminal close on completion, force-new reset on outbound creation, and automatic use-case rebind recovery.

**What changed:**

* **Terminal close on conversation completion.** When a text conversation's context graph reaches its terminal state (completed), the platform durably marks the conversation as completed. The next inbound message on the same provider thread starts a fresh conversation instead of resuming the finished one. Previously, a completed conversation could remain active, causing inbound messages to route to a dead engine. Conversations that end for other reasons (idle timeout, disconnect, error) remain active and resumable.
* **Force-new on outbound conversation creation.** The create-conversation endpoint now accepts a `force_new` boolean field for thread-keyed channels (SMS, iMessage). When set to `true`, the platform closes any existing active conversation on the target provider thread (recipient + use case) before dispatching the outbound opener, ensuring a brand-new conversation is materialized. The closed conversation emits a conversation-closed event and an audit entry. `force_new` is rejected with `422` on `channel=web` because web conversations always start fresh.
* **Use-case rebind recovery.** When a workspace reassigns a use case to a different service while a conversation is still active on a provider thread, inbound messages on that thread would previously fail because the active conversation was bound to the old service. The platform now detects the mismatch, retires the stale conversation, and re-resolves the thread to materialize a fresh conversation bound to the current service. Recovery is capped at one retry per inbound turn to prevent loops.

**What you need to do:**

* **No action required for terminal close or rebind recovery.** Both are automatic. Completed text conversations will now correctly free their provider thread for new conversations, and use-case rebinds will self-heal on the next inbound message.
* **Use `force_new` for outbound resets.** If you need to start a fresh outbound conversation on a thread that may have an existing active conversation, pass `force_new: true` in your create-conversation request. This is only valid for SMS and iMessage channels.
* **Update integrations that assume conversation permanence.** If your integration assumes a text conversation on a given thread is permanent and never replaced, be aware that completed conversations and force-new resets now close the prior conversation and start a new one.

</details>

<details>

<summary>Platform API: Normalized Checks View for Simulation Evaluation Results (July 2026)</summary>

#### Normalized Checks View for Simulation Evaluation Results

Simulation run responses and per-case assertion details now include a `checks` field - a normalized, read-only projection that flattens each metric and assertion verdict into a uniform shape for consistent rendering.

**What changed:**

* **New `checks` field on simulation run responses.** The response from the get-simulation-run endpoint now includes a `checks` array alongside the existing `eval_results`. Each element is a flat object with `source_type` (metric or assertion), `key`, `label`, `value_type`, `evaluation_method`, `expected`, `actual`, display-ready strings (`expected_display`, `actual_display`), `verdict`, optional `score` and `score_label`, `rationale`, `references`, and identifiers (`run_id`, `case_id`, `session_id`, `trace_id`).
* **New `checks` field on per-case assertion details.** The case assertion detail response now includes a `checks` array alongside the existing `results`, using the same normalized shape.
* **Raw results unchanged.** The existing `eval_results` and `results` fields are not modified. The `checks` array is a computed view that rides alongside them.
* **Consistent rendering.** The normalized shape eliminates the need for clients to determine where the useful value lives across different result shapes (metric thresholds, deterministic assertions, LLM judge verdicts). Every check renders with the same field set.
* **Value type and evaluation method are independent axes.** `value_type` describes the measured value (numeric, boolean, categorical, text, structured). `evaluation_method` describes how it is checked (threshold, equals, contains, regex, tool\_called, llm\_judge, custom). For LLM judge assertions, the primary actual value is the verdict, not the numeric score - the score appears on the `score` field as secondary data.

**What you need to do:**

* **No action required.** The `checks` field is additive. Existing integrations that read `eval_results` or `results` continue to work without changes.
* **Adopt `checks` for rendering.** If you render simulation check results in a custom UI, you can switch to reading the `checks` array for a simpler, uniform rendering path.

</details>

<details>

<summary>Platform API: Escalation Policy and Risk Signal Configuration Removed from Service API (July 2026)</summary>

#### Escalation Policy and Risk Signal Configuration Removed from Service API

The escalation policy, risk signal configuration, hard escalation rules, and safety filters toggle have been removed from the Service resource. These fields were part of an earlier escalation routing design that has been superseded by the agent's built-in safety evaluation and escalation reasoning.

**What changed:**

* **`escalation_policy` removed from Service create, update, and response.** The per-service escalation policy object (which mapped trigger sources to actions like operator handoff, call forwarding, or hangup) has been removed from the Service API. Services no longer carry per-trigger routing configuration for engine-detected escalations.
* **`risk_signal_config` removed from Service response.** The per-workspace risk scoring configuration (thresholds, weights, enabled flag) has been removed from the Service resource.
* **`hard_escalation_rules` removed from Service response.** The list of non-negotiable escalation rules (healthcare compliance rules with detection modes and intent patterns) has been removed from the Service resource.
* **`safety_filters_enabled` removed from Service create, update, and response.** The boolean safety filters toggle has been removed from the Service resource.
* **`escalation_config` removed from context graph action states.** The per-state escalation tuning override (topic risk score, auto-escalate threshold, max loop count, operator skill) has been removed from action state configuration.
* **`summarize` context strategy removed.** The `summarize` option for context management strategy has been removed. The supported strategies are now `full` (default) and `compact`. Existing configurations using `summarize` should be updated to use `compact`.
* **Active call intelligence response simplified.** The `current_risk_score` and `risk_trend` fields have been removed from the active call intelligence response.
* **Context window management simplified.** The context window warning threshold (which previously triggered an intermediate summarize step) has been removed. The engine now transitions directly from full context to compact mode when token usage approaches the escalation threshold.

**What you need to do:**

* **Remove `escalation_policy` from Service create and update requests.** If you set escalation policies on services through the API, remove the field from your request bodies. The field is no longer accepted.
* **Remove `safety_filters_enabled` from Service create and update requests.** If you set this field, remove it. Safety evaluation is now handled by the agent's core reasoning.
* **Update context strategy references.** If you configure context strategies and use `summarize`, change to `compact`. The `summarize` strategy is no longer supported.
* **Update integrations reading removed fields.** If your integration reads `escalation_policy`, `risk_signal_config`, `hard_escalation_rules`, `safety_filters_enabled`, `current_risk_score`, or `risk_trend` from API responses, update your code to handle their absence.
* **No change to escalation behavior.** The agent continues to evaluate safety concerns and trigger escalations as part of its core reasoning loop. Operator escalation remains available through the standard operator join flow.

</details>

<details>

<summary>Platform API: Per-Turn Prompt Log Emission for Voice Calls (July 2026)</summary>

#### Per-Turn Prompt Log Emission for Voice Calls

Voice calls now emit prompt logs incrementally after each conversation turn instead of batching all logs at call teardown. This improves log durability and makes prompt logs available for inspection while a call is still in progress.

**What changed:**

* **Incremental prompt log emission.** After each voice conversation turn, the platform emits any new prompt logs generated during that turn. Previously, all prompt logs for a call were batched and emitted only during call teardown.
* **Improved durability.** If a call ends unexpectedly (for example, due to infrastructure failure), at most the current turn's prompt log is lost rather than the entire call's log history. Logs from all prior completed turns have already been emitted and are safely persisted.
* **Live queryability.** Prompt logs are now available from the prompt-logs API while the call is still active, matching the behavior of usage metering which already emits per-turn. Operators and debugging tools can inspect prompt data without waiting for the call to end.
* **Teardown flush unchanged.** The existing teardown flush at call end continues to run, pushing any remaining logs from the final turn and session summary. The teardown flush is aware of what has already been emitted and only sends logs that have not yet been delivered.
* **Voice only.** This change applies to voice calls. Text and simulation channels retain their existing emission mechanisms.

**What you need to do:**

* **No action required.** Per-turn prompt log emission is automatic for all voice calls. There are no new API endpoints, request fields, or response fields. Prompt logs appear through the same API surface as before - they are simply available sooner during a call.
* **Review monitoring dashboards.** If you monitor prompt log delivery, you may notice logs arriving throughout a call rather than in a single batch at the end. This is expected behavior.

</details>

<details>

<summary>Platform API: LLM Token Metering for Conversation Navigation and Production-Call Evaluation (July 2026)</summary>

#### LLM Token Metering for Conversation Navigation and Production-Call Evaluation

Two previously unmetered LLM call sites now emit token usage as billing events: the conversation navigation step on text and simulation turns, and the AI judge and AI-query metric calls made during production call evaluation.

**What changed:**

* **Navigation tokens metered on text and simulation turns.** Each text (SMS, iMessage, web chat) and simulation turn runs a navigation LLM call that decides how the conversation moves through the context graph. These tokens were previously computed but never billed. They are now emitted as usage events per turn, under the navigation model rather than the response model. Voice calls already metered navigation tokens and are unchanged.
* **Production-eval judge and AI-query tokens metered.** Running evaluations against a completed production call invokes an AI judge for LLM-judge assertions and an AI model for AI-query metrics. These calls now emit token usage events, matching simulation evaluations, which already metered both.
* **Metering never affects results.** A metering failure never affects the turn or the evaluation outcome.
* **No API surface changes.** There are no new endpoints, request fields, or response fields. This update adds billing-side metering so these tokens are counted in usage reporting.

**What you need to do:**

* **No action required.** Metering is automatic.
* **Review usage reports.** If you track LLM token consumption, note that navigation tokens on text/simulation turns and production-eval judge tokens - previously invisible in billing - will now be included in your usage totals.

</details>

<details>

<summary>Platform API: LLM Token Metering for Framework Agent Runs (July 2026)</summary>

#### LLM Token Metering for Framework Agent Runs

Framework agent runs now emit per-run LLM token usage as a billing event at run completion. This means inference consumed by framework runs (partner agent frameworks dispatched through the platform) appears in standard usage reporting alongside all other platform LLM usage.

**What changed:**

* **Per-run token billing.** When a framework agent run completes successfully, the platform emits a billing event containing the run's total input tokens, output tokens, and cached tokens, along with the model used. These tokens now appear in the same usage meters as every other platform inference call site.
* **Metering never affects run status.** A metering failure never affects the run's terminal status - the run still succeeds or fails based on its own outcome.
* **No API surface changes.** There are no new endpoints, request fields, or response fields. The token usage already reported in the run response (input, output, and cached token counts) is unchanged. This update adds billing-side metering so those tokens are counted for usage reporting.

**What you need to do:**

* **No action required.** Token metering is automatic. Framework agent run usage will appear in your workspace's usage reports.
* **Review usage reports.** If you track LLM token consumption for cost management, note that framework agent run tokens - previously invisible in billing - will now be included in your usage totals.

</details>

<details>

<summary>Platform API: Audio Verification Removed - Reduced Per-Turn Voice Latency (July 2026)</summary>

#### Audio Verification Removed - Reduced Per-Turn Voice Latency

The per-turn audio verification step that sent conversation audio to a secondary model for speech-to-text correction has been removed. This eliminates a blocking delay on every voice conversation turn, significantly reducing end-to-end response latency.

**What changed:**

* **Audio verification removed from the voice pipeline.** The platform previously ran an optional audio verification step on each voice turn that analyzed raw conversation audio to detect and correct speech-to-text errors (misspelled names, misheard phone numbers, etc.). This step added a blocking delay to every turn. It has been removed entirely.
* **Voice settings: `correction_categories` field removed.** The `correction_categories` field has been removed from the voice settings API request and response models. This field previously provided domain hints (e.g., "medication names", "insurance carriers") to the audio verification model. Since audio verification no longer exists, the field is no longer accepted or returned.
* **No change to other voice settings.** All other voice configuration fields (keyterms, pronunciation dictionaries, sensitive topics, post-call intelligence toggles, language, speed, volume, etc.) are unaffected.

**What you need to do:**

* **Remove `correction_categories` from API calls.** If you set `correction_categories` in voice settings update requests, remove the field. The API no longer accepts it.
* **Update response parsing.** If you read `correction_categories` from voice settings GET responses, remove that field from your response models. It is no longer returned.
* **No action needed for voice quality.** The keyterms feature (which boosts specific words in real-time speech recognition) remains available and is the recommended approach for improving transcription accuracy for domain-specific vocabulary.

</details>

<details>

<summary>Platform API: Returning-User Entry State for Known Callers (July 2026)</summary>

#### Returning-User Entry State for Known Callers

When the platform resolves a sole-match caller at session open, it now checks whether that person has a prior completed conversation. If they do, the session starts in a returning-user entry state and the agent greets the caller with awareness of the prior interaction rather than treating every call as a first contact.

**What changed:**

* **Returning-user detection at session open.** When the platform resolves a single known entity for the caller, it looks up that entity's most recent completed conversation. If one exists, the session is flagged as returning-user and the agent uses the returning-user entry state defined in the context graph.
* **Conversation recency in caller context.** A natural-language recency line (e.g., "Last conversation: 12 minutes ago" or "Last conversation: 3 days ago") is appended to the caller context block. This line persists across context refreshes during the session so the greeting always has content to anchor on.
* **Sole-match only.** The returning-user signal is derived only when a single entity is resolved. Ambiguous lookups (multiple matches) and household-level resolutions stay on the new-user path - no "welcome back" promise is made without loaded entity knowledge.
* **Operator sessions reset to new-user.** When the resolved entity is an external principal (operator or clinician), the returning-user flag is reset to new-user. The operator is not a subject of care, so their prior conversation history should not drive a clinical welcome-back greeting.
* **Graceful degradation.** If the conversation history lookup fails, the session silently falls back to the new-user entry state. The recency signal is a best-effort enhancement that never blocks session creation.
* **New telemetry.** Sessions where returning-user derivation runs now emit a metric indicating whether the caller was classified as returning or new, giving visibility into returning-user distribution.

**What you need to do:**

* **No action required for most integrations.** The returning-user entry state is derived automatically from existing conversation history and context graph configuration. If your context graph defines a `returning_user_initial_state`, callers with prior conversations will now use it.
* **Review your context graph entry states.** If you have not authored a returning-user entry state in your context graph, the default new-user entry state continues to apply for all sessions. To take advantage of personalized returning-user greetings, define a returning-user initial state in your context graph.

</details>

<details>

<summary>Platform API: Legacy Clinical Event Review Surface Retired (July 2026)</summary>

#### Legacy Clinical Event Review Surface Retired

The legacy clinical event review pipeline has been retired. This surface provided model-based and human review of flagged clinical events from voice agent sessions. Workspaces enrolled in the private preview can use External Write Review to approve or reject external write proposals before delivery to target systems.

**What changed:**

* **Review queue API endpoints removed.** The `/v1/{workspace_id}/review-queue` endpoint group (list, detail, approve, reject, correct, claim, unclaim, batch approve, batch reject, stats, dashboard, trends, performance, history, my-queue, correction schema, and diff) has been removed from the Platform API.
* **Review queue data surface removed.** Review queue records are no longer available through the generic data API.
* **Pipeline dashboard review loop field.** The `review_loop` field in pipeline status responses is now always null. The field is preserved for wire compatibility but carries no data.
* **Pipeline review metrics.** The `GET /pipeline/review` endpoint now returns zeroed metrics (queue depth 0, no pending items, no approval or rejection counts). The endpoint remains available for compatibility but reflects no active data.
* **Command center data quality.** The command center data quality panel no longer reports review queue depth or approval rate from the retired surface. These fields return zero or null values.
* **Entity intelligence.** The entity intelligence provenance response no longer includes review history from the retired surface. The `review_history` field is preserved but always returns an empty list.

**What you need to do:**

* **Update any integrations using the review queue endpoints.** Remove dependencies on the retired `/v1/{workspace_id}/review-queue` endpoints. If your workspace is enrolled in the private preview, use `/v1/{workspace_id}/external-write-proposals` for human review of external writes.
* **Update dashboard integrations.** If you consume the `review_loop` field from pipeline status or entity intelligence `review_history`, these fields now return null or empty values. Remove any UI or logic that depends on them.
* **External Write Review remains private preview.** Do not treat the proposal-review endpoints as generally available unless your workspace is enrolled.

</details>

<details>

<summary>Platform API: External Write Proposal Review API - Private Preview (July 2026)</summary>

#### External Write Proposal Review API

Private-preview workspaces can list, inspect, approve, and reject external write proposals through workspace-scoped REST endpoints.

**What changed:**

* **List proposals.** `GET /v1/{workspace_id}/external-write-proposals` returns a paginated, newest-first list of external write proposals. Supports optional `status` filtering (`proposed`, `approved`, `rejected`, `pushing`, `pushed`, `failed`, `superseded`) and includes a total count for building paged UIs.
* **Get proposal detail.** `GET /v1/{workspace_id}/external-write-proposals/{proposal_id}` returns a single proposal by ID.
* **Approve a proposal.** `POST /v1/{workspace_id}/external-write-proposals/{proposal_id}/approve` records an approval decision. Only proposals in `proposed` status can be approved.
* **Reject a proposal.** `POST /v1/{workspace_id}/external-write-proposals/{proposal_id}/reject` records a rejection decision with a required reason (1-1000 characters). Only proposals in `proposed` status can be rejected.
* **Server-derived reviewer identity.** The reviewer's identity is derived from the authenticated session - never accepted from the request body. Callers without an authenticated user identity (e.g., legacy API keys with no bound person) receive a `403` response.
* **Concurrency-safe decisions.** If two reviewers attempt to decide the same proposal simultaneously, only one succeeds. The other receives a `409 Conflict` response.
* **Audit logging.** Every decision is audit-logged with the connector type, resource type, and reviewer identity. The proposed payload (which may contain PHI) is never included in audit logs.
* **Permission-gated.** Listing and viewing require `ReviewQueue.view`. Approving and rejecting require `ReviewQueue.review`.

**What you need to do:**

* **Confirm preview enrollment before integrating.** These endpoints are not generally available.
* **For enrolled workspaces:** Use the list endpoint to display pending proposals and the approve or reject endpoints to record decisions. See the [Review Queue developer guide](https://docs.amigo.ai/developer-guide/platform-api/integrations/review-queue) for the request and response contracts.

</details>

<details>

<summary>Platform API: External Write Proposal Delivery - Private Preview (July 2026)</summary>

#### External Write Proposal Delivery

Private-preview workspaces can deliver human-approved external write proposals with retry behavior based on the destination's idempotency characteristics.

**What changed:**

* **Asynchronous delivery for approved proposals.** When a reviewer approves a proposal, the platform attempts delivery to the configured external system and records the result on the proposal.
* **Idempotency-aware delivery guarantees.** FHIR sinks with PUT-by-id semantics receive at-least-once delivery - transient failures and mid-delivery crashes are safely retried. Non-idempotent sinks (booking, cancellation, patient creation) receive at most one automatic delivery attempt. If a failure occurs on a non-idempotent sink, the proposal is recorded for human re-drive rather than retried, preventing double-writes.
* **Bypasses automatic sync safety nets.** The delivery engine does not apply confidence thresholds, source allowlists, or entity type filters when dispatching approved proposals. A human approval is a stronger authority than unattended egress heuristics.
* **Proposal lifecycle tracking.** Each proposal tracks its lifecycle (proposed, approved, pushing, pushed, failed, rejected) with attempt counts, reviewer identity, decision timestamps, and delivery outcomes.

**What you need to do:**

* **Confirm preview enrollment and destination semantics.** Before approving proposals, verify whether the destination supports idempotent retries and define a human re-drive procedure for failed non-idempotent writes.

</details>

<details>

<summary>Platform API: Self-Serve Custom Memory Dimensions via Enrichment Key Tags (July 2026)</summary>

#### Self-Serve Custom Memory Dimensions via Enrichment Key Tags

Workspaces can now opt enrichment keys into the memory extraction pipeline by tagging them through the enrichment-keys API, without requiring engineer intervention. A new `tags` field on enrichment keys controls routing into platform subsystems.

**What changed:**

* **New `tags` field on enrichment keys.** The create and patch endpoints for enrichment keys now accept an optional `tags` array. The response model for all enrichment key endpoints (create, list, get, patch) includes the `tags` field.
* **`memory_extract` routing tag.** Tagging a key with `"memory_extract"` opts it into the memory extraction pipeline as a workspace-custom memory dimension. The conversation extractor will infer this key from transcripts alongside the system-default memory dimensions.
* **Validation on tagging.** The API validates that a key tagged with `"memory_extract"` meets the extraction pipeline's requirements: the key must belong to a `person` entity type, use a valid snake\_case identifier, have a non-empty `description` (which serves as the extraction target definition for the LLM), use `value_type` of `"string"` (the extractor emits free-text narratives), and must not shadow a system-default memory dimension. Keys that fail these checks receive a `400` response with a specific error message.
* **Patch behavior.** Setting `tags` on a patch replaces the full tags list. Pass `[]` to remove all tags. Validation runs against the post-update state, so adding the `memory_extract` tag in the same patch that sets a description is supported. The update is rolled back if validation fails.
* **Consolidation cadence.** The memory consolidation pipeline now runs on a more frequent cadence. Most runs are no-ops - total processing volume stays proportional to conversations per day. A failure backoff mechanism prevents a single problematic entity from consuming repeated processing attempts.

**What you need to do:**

* **To add a custom memory dimension:** Create or update an enrichment key with `entity_type` set to `"person"`, `value_type` set to `"string"`, a non-empty `description` that defines what the extractor should look for, and `tags` set to `["memory_extract"]`. The key will be picked up by the extraction pipeline automatically.
* **No action required for existing integrations.** The `tags` field defaults to an empty array. Existing enrichment keys are unaffected.

</details>

<details>

<summary>Platform API: Real-Time Live Voice Overlay on Runs List and Summary (July 2026)</summary>

#### Real-Time Live Voice Overlay on Runs List and Summary

The unified runs list and summary endpoints now include active voice calls in real time, so live voice runs appear with `running` status while the call is in progress rather than only after it ends.

**What changed:**

* **Live voice runs in the list.** `GET /runs` now includes synthetic `running` conversation runs for voice calls that are currently active. These entries appear alongside database-sourced runs and are sorted, filtered, and paginated consistently. When a call ends and its terminal database record is written, the synthetic entry is automatically replaced by the authoritative record.
* **Live voice count in the summary.** `GET /runs/summary` now includes active voice calls in the `running` count and the `conversation` kind count, so the summary reflects calls in progress in real time.
* **Deduplication.** If a call has both a live entry and a completed database record (for example, during the brief overlap after a call ends), the database record takes precedence and the live entry is dropped. No duplicate runs appear in the list.
* **Best-effort overlay.** The live voice data source is consulted on a best-effort basis. If it is unavailable or slow, the endpoints return database-only results without error. The overlay never causes a request to fail.
* **Filter compatibility.** Live voice runs respect the existing `kind`, `channel`, and `status` filters. They appear only when the query includes conversation runs, the voice channel (or no channel filter), and the `running` status (or no status filter, or the virtual `live` filter).

**What you need to do:**

* **No action required for existing integrations.** The list and summary endpoints return the same shape as before. Live voice entries use the same run contract and field set as other conversation runs.
* **For dashboards showing live run counts:** The `running` count in the summary response now accurately reflects voice calls in progress. If you previously supplemented the runs summary with a separate active-calls query to get real-time voice counts, you can remove that workaround.

</details>

<details>

<summary>Platform API: Unified Runs Summary Endpoint (July 2026)</summary>

#### Unified Runs Summary Endpoint

A new endpoint returns aggregate run counts across the workspace's unified run surface (framework + conversation runs), providing the data needed for the Operations Runs page summary strip without requiring clients to page through the full run list.

**What changed:**

* **New endpoint: `GET /runs/summary`.** Returns total run count, live count (running + paused), per-status breakdown (running, paused, completed, failed, timed\_out), a full `by_status` map, and a `by_kind` map (framework vs conversation).
* **Optional `kind` filter.** Pass `framework` or `conversation` to scope the summary to one run source. Omit to include both.
* **Optional `channel` filter.** Pass a conversation channel (`voice`, `text`, `sms`, `email`, `web`) to scope the summary to conversation runs on that channel. When set, framework runs are excluded from the counts since framework runs do not carry a channel.
* **`by_status` field for forward compatibility.** The response includes a `by_status` object that carries every status value present in the data, so new run lifecycle states surface automatically without requiring a schema change.
* **`by_kind` field.** Shows the total count split between `framework` and `conversation` sources.

**What you need to do:**

* **No action required for existing integrations.** This is a new read-only endpoint. The existing list endpoint is unchanged.
* **To display run summaries:** Call `GET /runs/summary` (optionally with `kind` and/or `channel`) and use the returned counts to populate summary cards or dashboard strips. The `by_status` object provides the full breakdown if you need to display statuses beyond the named convenience fields.

</details>

<details>

<summary>Platform API: Simulation Runs Summary Endpoint (July 2026)</summary>

#### Simulation Runs Summary Endpoint

A new endpoint returns aggregate counts across a workspace's simulation runs, providing the data needed for the Operations Runs page summary strip without requiring clients to page through the full run list.

**What changed:**

* **New endpoint: `GET /simulations/runs/summary`.** Returns total run count, per-status breakdown (running, completed, failed, plus any additional statuses), total sessions, total turns, and the most recent run creation timestamp.
* **Optional `service_id` filter.** Pass a service ID to scope the summary to runs for a specific service. This mirrors the list endpoint's service filter so the summary stays consistent with a filtered run list.
* **`by_status` field for forward compatibility.** The response includes a `by_status` object that carries every status value present in the data, so new run lifecycle states surface automatically without requiring a schema change.
* **`last_created_at` field.** ISO 8601 timestamp of the most recently created run, or `null` when no runs match.

**What you need to do:**

* **No action required for existing integrations.** This is a new read-only endpoint.
* **To display run summaries:** Call `GET /simulations/runs/summary` (optionally with `service_id`) and use the returned counts to populate summary cards or dashboard strips. The `by_status` object provides the full breakdown if you need to display statuses beyond the three named convenience fields.

**Permissions:** Requires service view permission.

</details>

<details>

<summary>Platform API: Per-Request Wait-for-Final and Filler Suppression for Background Tools (July 2026)</summary>

#### Per-Request Wait-for-Final and Filler Suppression for Background Tools

The text conversation turn endpoint now accepts two optional per-request flags - `wait_for_final` and `suppress_filler` - that give synchronous and batch callers explicit control over how background tool results are delivered.

**What changed:**

* **New optional field: `wait_for_final`.** When set to `true` on a message turn, the platform holds the request open and waits for the background tool to finish (bounded to approximately 30 seconds) instead of returning the acknowledgement immediately. If the tool completes within the budget, the response carries the final answer with `background_pending: false`. If the wait times out, the response returns `background_pending: true` with the conversation ID so the caller can resolve it later with `poll: true`. Default is `null`, which inherits the channel policy (web/sync text = do not wait).
* **New optional field: `suppress_filler`.** When set to `true` on a message turn that ends with background work pending, the filler/acknowledgement text is omitted from the response output. The caller receives an empty output list with `background_pending: true` as the unambiguous poll-later signal, preventing batch callers from mistaking the acknowledgement for the real answer. Default is `null`, which inherits the channel policy.
* **Validation with `poll: true`.** Both flags are rejected (422) when combined with `poll: true`. Polling is itself the drain-and-report primitive, so turn-control flags are meaningless on a poll request.
* **Existing behavior unchanged.** When both flags are `null` (the default), every existing integration path behaves identically to before - no wait, no suppression.

**What you need to do:**

* **No action required for existing integrations.** The defaults preserve current behavior.
* **For batch or synchronous callers:** Consider setting `wait_for_final: true` to receive background tool results inline without a separate poll cycle. If the tool takes longer than the budget, fall back to `poll: true` as before.
* **For callers that parse agent output programmatically:** Consider setting `suppress_filler: true` to ensure that filler acknowledgement text is never returned when a background tool is pending. Use `background_pending: true` as the authoritative signal to poll later.
* **Remove any `wait_for_final` or `suppress_filler` from poll requests.** If you set these flags on a `poll: true` request, the endpoint returns 422.

</details>

<details>

<summary>Platform API: JWT Bearer Integration Auth - RFC 7523 Flow Selection (July 2026)</summary>

#### JWT Bearer Integration Auth - RFC 7523 Flow Selection

The `oauth2_jwt_bearer` integration auth type now requires an `assertion_usage` field that selects which RFC 7523 flow to use when requesting an access token. This replaces the previous behavior where all JWT bearer integrations used the authorization grant flow (§ 2.1) unconditionally.

**What changed:**

* **New required field: `assertion_usage`.** When creating or updating an integration with `oauth2_jwt_bearer` auth, you must now specify `assertion_usage` as one of two values:
  * `authorization_grant` (§ 2.1) - sends the JWT as `assertion` under `grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer`. This is the flow previously used by all JWT bearer integrations.
  * `client_authentication` (§ 2.2) - sends the JWT as `client_assertion` with `client_assertion_type` under `grant_type=client_credentials`. This is the `private_key_jwt` method required by SMART Backend Services and similar providers.
* **Existing integrations backfilled.** All existing `oauth2_jwt_bearer` integrations have been backfilled with `assertion_usage: authorization_grant`, preserving their current behavior. No action is needed for existing integrations unless you want to switch to the `client_authentication` flow.
* **Breaking change for new integrations.** The `assertion_usage` field is required on all new `oauth2_jwt_bearer` integration configurations. Requests that omit this field will be rejected.

**What you need to do:**

* **For existing integrations:** No action required. Existing integrations continue to use the authorization grant flow as before.
* **For new integrations:** Include `assertion_usage` in your `oauth2_jwt_bearer` auth configuration. Choose `authorization_grant` for the standard JWT bearer grant flow, or `client_authentication` for providers that require the `private_key_jwt` client credentials flow.
* **For SMART Backend Services / Epic integrations:** Use `assertion_usage: "client_authentication"` to send the JWT as a client assertion under the client credentials grant, which is the flow these providers require.

</details>

<details>

<summary>Platform API: Server-Side Operator Identity Enforcement (July 2026)</summary>

#### Server-Side Operator Identity Enforcement

All operator action endpoints now enforce that the authenticated caller's identity matches the operator profile they are acting as. This closes a security gap where any user with the admin role could perform operator actions (join call, send guidance, switch mode, leave call, wrap up, get access token) attributed to a different operator.

**What changed:**

* **Identity binding on all operator actions.** The platform now verifies that the caller's authenticated identity corresponds to the target `operator_id` on every operator action request. The check matches the caller's verified user identity against the operator profile. If the identities do not match, the request is rejected with `403 Forbidden`.
* **No admin bypass.** Even users with admin or owner roles cannot act as a different operator. The identity enforcement applies uniformly regardless of role - acting as another operator is the vulnerability this change addresses.
* **API key callers without user identity are rejected.** Legacy API keys that do not carry an associated user identity cannot perform operator actions. Only callers with a verified user session are accepted.
* **Affected endpoints.** Join call, switch mode, leave call, get access token, send guidance, and wrap up endpoints for operators all enforce this check.

**What you need to do:**

* **Ensure operator profiles have correct email addresses.** The platform matches the caller's verified email against the operator profile's email. If an operator's profile does not have the same email as their sign-in account, they will be unable to perform operator actions. Verify operator email addresses in the Operators configuration.
* **Update any automation that acts as a different operator.** If you have scripts or integrations that use one user's credentials to perform actions as a different operator, those will now be rejected. Each operator must authenticate with their own credentials.
* **No API contract changes.** Request and response schemas are unchanged. The only behavioral change is stricter authorization - requests that would previously succeed when the caller and operator identities did not match will now return `403`.

</details>

<details>

<summary>Platform API: Event-Driven Delivery for Background Tools on Non-Live Channels (July 2026)</summary>

#### Event-Driven Delivery for Background Tools on Non-Live Channels

Non-live channels - email, SMS, iMessage, and WhatsApp - now suppress interim filler replies when the agent dispatches a background tool, and automatically deliver the real answer once the tool completes. Previously, these channels could send a placeholder acknowledgment followed by the actual answer as a separate message, which was confusing for recipients who receive messages asynchronously.

**What changed:**

* **Filler suppression on non-live channels.** When a background tool is dispatched during a conversation on email, SMS, iMessage, or WhatsApp, the platform buffers the agent's interim reply instead of sending it immediately. If the turn completes without pending background work, the reply is sent normally. If the turn ends with a background tool still running, the buffered filler is discarded.
* **Automatic re-drive on tool completion.** When a background tool finishes on a non-live channel, the platform automatically re-drives the session so the agent can incorporate the tool results and deliver the real answer. The recipient receives a single message with the final answer rather than a placeholder followed by a correction.
* **Live channels unchanged.** Phone and web chat conversations are unaffected. These channels maintain a persistent connection, so the existing behavior of sending an interim acknowledgment and then delivering the real answer on the same session continues to work as before.
* **No configuration required.** The platform determines the appropriate delivery behavior based on the channel type. No API changes, new parameters, or workspace configuration are needed.
* **Rolling deployment safe.** The platform uses structural separation to ensure mixed-version deployments cannot mis-deliver messages during upgrades. No operator coordination is required.

**What you need to do:**

* **No action required.** This change applies automatically to all non-live channel conversations. Recipients on email, SMS, iMessage, and WhatsApp will receive a single definitive reply instead of an interim placeholder when background tools are involved.
* **Review any workflows that depend on interim replies.** If you have downstream systems that monitor for interim acknowledgment messages on non-live channels, those messages will no longer be sent when a background tool is pending.

</details>

<details>

<summary>Platform API: Identity Binding Test Values in Simulation Sessions (July 2026)</summary>

#### Identity Binding Test Values in Simulation Sessions

Simulation sessions now support identity binding test value resolution for `external_user.subject_key` bindings on integrations. This lets simulations exercise integrations that require an external user identity without needing a verified external user session.

**What changed:**

* **Simulation sessions resolve test values.** When a simulation session is created by a caller with integration view permissions, the session automatically enables identity binding test value resolution. Integration auth bindings that carry author-configured test values for `external_user.subject_key` will use those test values during the simulation, mirroring the behavior already available for workspace-authenticated text conversation turns.
* **Server-derived, not caller-supplied.** The test value resolution decision is made server-side based on the caller's role permissions. Callers with integration view permissions get test value resolution automatically. This applies to simulation session creation, test conversation creation, simulation case runs, and benchmark runs.
* **Propagated across session lifecycle.** The setting is stored with the session metadata and propagated to forked sessions, so branching scenarios within a simulation inherit the same identity binding behavior as the parent session.
* **No change to production paths.** Test values remain inert on voice calls, production external-user sessions, and any path where a verified external subject key is present. The gating rules are unchanged - this change only adds simulation sessions as an additional gated path.

**What you need to do:**

* **No action required.** If you use simulations to test integrations with identity bindings, those integrations will now resolve test values automatically when your role has integration view permissions. No API changes or new parameters are needed for the standard simulation endpoints.
* **Direct agent engine session creation.** If you create simulation sessions directly against the agent engine (not through the Platform API simulation endpoints), a new optional `allow_identity_binding_test_values` boolean field is available on the session creation request. The Platform API simulation endpoints set this automatically.

</details>

<details>

<summary>Platform API: Eager Post-Conversation Evaluation for All Channels (July 2026)</summary>

#### Eager Post-Conversation Evaluation for All Channels

Eager post-conversation evaluation now applies to all conversation channels - text sessions, SMS, email, and web - in addition to voice calls. Previously, the eager evaluation trigger only fired at the end of voice calls. Now every completed conversation, regardless of channel, is automatically evaluated against the workspace's active eval definitions when the feature is enabled.

**What changed:**

* **All channels trigger evaluation.** When eager evaluation is enabled for a workspace, completing a text session, SMS conversation, email conversation, or web conversation triggers the same automatic evaluation that previously only applied to voice calls. The evaluation uses the conversation's durable identifier to resolve the completed conversation.
* **Same evaluation definitions and scoring.** Text-channel evaluations use the same eval definitions, scoring pipeline, and daily cost cap as voice evaluations. Results are directly comparable across channels.
* **Same opt-in model.** No additional configuration is required. Workspaces that already have active eval definitions and the eager evaluation flag enabled will automatically begin evaluating text-channel conversations.
* **Best-effort and non-blocking.** The evaluation trigger is best-effort and does not affect conversation teardown. If a conversation cannot be resolved for evaluation (for example, if it has no durable conversation identifier), the trigger is skipped and the daily budget is not consumed.

**What you need to do:**

* **No action required for existing voice-only workspaces.** If eager evaluation is already enabled, text-channel conversations will begin being evaluated automatically. Review your daily evaluation cap if you expect a significant increase in evaluated conversation volume.
* **Review evaluation definitions.** Evaluation definitions originally written for voice conversations may need adjustments to score text-channel conversations appropriately. Both modes produce comparable results, but dimension-specific criteria (such as audio quality or hold time) may not apply to text channels.

</details>

<details>

<summary>Platform API: Triggerable Event Type Validation (July 2026)</summary>

#### Triggerable Event Type Validation

Trigger definitions now validate the `event_type` field against a closed set of supported platform events at write time. Previously, any string was accepted, which meant a typoed event name would silently create a trigger that never matched any incoming event.

**What changed:**

* **Write-time validation on create and update.** When you create or update a trigger, the `event_type` field must be one of the supported platform event types. Unsupported or misspelled values are rejected with a validation error.
* **Supported event types.** The supported set covers appointment lifecycle events (`appointment.booked`, `appointment.cancelled`, `appointment.confirmed`), booking requests, call intelligence and outcome events, channel events (email bounced/clicked/complained/delayed/delivered/opened/received/rejected, message received, voicemail status), conversation events (channel switched, started, turn recorded), coverage creation, entity enrichment and resolution, intake file receipt, medication refill requests, outbound initiation and scheduling, patient creation and updates, relationship establishment, review actions (approve, correct, reject), surface creation and submission, ticket creation, triage completion, trigger lifecycle events (completed, failed, fired), and the cron schedule event.
* **Existing triggers are unaffected.** Triggers already stored with event types outside the supported set continue to exist and fire as before. The validation applies only to new writes through the API.

**What you need to do:**

* **Review trigger creation workflows.** If you create triggers programmatically, ensure the `event_type` value matches one of the supported event types. Requests with unsupported values will now receive a validation error.
* **No changes needed for existing triggers.** Previously created triggers are not affected by this validation.

</details>

<details>

<summary>Platform API: Trigger Run Observability Metrics (July 2026)</summary>

#### Trigger Run Observability Metrics

Trigger action execution now emits granular observability metrics and structured log events, giving operators visibility into each step of a trigger run.

**What changed:**

* **Per-round timing.** Each action execution round emits a timing metric capturing how long the model call took, tagged by trigger name and model. Operators can use this to identify slow rounds and model-level latency trends.
* **Per-tool timing and status.** Every tool invocation within an action emits a timing metric with the tool name and outcome status (success, error, or timeout). This lets operators pinpoint which tools contribute to run duration and which tools are failing.
* **Structured log events.** Action execution now emits structured log events at each stage - round start (with tool count and model), model response (with tool use count), tool call start, and tool completion (with status and duration). These events provide a full trace of the execution path for debugging and audit.
* **Exhausted-run monitoring.** Runs that permanently fail after exhausting their retry budget now contribute to a dedicated operational metric for alerting.
* **Additional dispatch metrics.** The dispatcher now emits claim latency and queue age metrics through additional metric names, improving compatibility with monitoring dashboards that expect standardized metric naming.

**What you need to do:**

* **No action required.** These are additive observability improvements. No API contracts, trigger behavior, or scheduling semantics have changed. If you operate monitoring dashboards, new metric names are available for per-round timing, per-tool timing, dead-run counts, claim latency, and queue age.

</details>

<details>

<summary>Platform API: Durable Trigger Run Pipeline (July 2026)</summary>

#### Durable Trigger Run Pipeline

Trigger fires - both cron-scheduled and manual - now route through the durable run pipeline instead of executing actions inline. This gives every fire automatic retries, dead-lettering, and full run-lifecycle observability.

**What changed:**

* **Cron fires produce durable runs.** When the cron scheduler detects a due trigger, it emits a fired event, advances the next fire time, and inserts a durable run record. Action execution is handled by the durable dispatcher rather than running inline in the scheduler. This decouples schedule advancement from action execution, so a slow or failing action no longer delays other triggers.
* **Manual fires produce durable runs.** Manually firing a trigger now follows the same path - a fired event is emitted and a durable run is enqueued. The response still returns the fired event identifier immediately.
* **Retry budgets from action configuration.** The durable run's maximum attempt count is derived from the backing action's configuration. Deterministic actions (such as outbound task scheduling) can opt into bounded retry budgets, while agentic actions default to at-most-once execution.
* **Consistent queue contract.** All fire sources - cron, manual, and future sources such as webhooks and platform events - use the same durable run queue. The dispatcher claims, executes, and records terminal status for runs regardless of how they were enqueued.
* **Duplicate prevention.** A per-trigger distributed mutex prevents duplicate cron enqueue across platform replicas. The existing idempotency guard at the action-execution layer continues to prevent double-execution when a run is retried or redelivered.

**What you need to do:**

* **No action required.** This is an internal execution change. Trigger behavior, scheduling semantics, and API contracts are unchanged. Fires that previously executed inline now execute through the durable pipeline with the same at-most-once or bounded-retry semantics.
* **Observability improvement.** Every fire now produces a run record visible through the trigger runs surface, including fires that previously executed and completed without a durable trace.

</details>

<details>

<summary>Platform API: SMS Pre-Send Content-Compliance Check (July 2026)</summary>

#### SMS Pre-Send Content-Compliance Check

Outbound SMS messages sent through US A2P 10DLC or US/CA toll-free sender pools now undergo an automated content-compliance check before delivery. The check verifies that the message body is consistent with every governing registration (A2P campaign and toll-free verifications) associated with the sender pool.

**What changed:**

* **Pre-send content verification.** When an outbound SMS carries a text body and the sender pool includes US A2P or US/CA toll-free numbers, the platform verifies the message content against all governing registrations before sending. The check considers the registered use-case categories, description, message samples, and declared content properties (age-gating, embedded links, embedded phone numbers, direct lending) for each registration.
* **Clear rejection on mismatch.** If the message content does not match the registered use case, the send is rejected with HTTP 422 and a reason explaining which registration the content is inconsistent with. This lets callers fix the message before reattempting.
* **Fail-closed design.** If the compliance check cannot produce a verdict (for example, due to a transient error or an unparseable result), the send is blocked rather than allowed through. There is no kill switch - content compliance is always enforced for eligible sends.
* **Media-only sends are unaffected.** Messages with only media attachments and no text body skip the content-compliance check, since there is no textual content to verify.
* **Opt-in gate runs first.** The recorded-consent (opt-in) check runs before the content-compliance check, so a missing opt-in is still a cheap rejection that does not trigger the compliance check.
* **Updated 422 response description.** The 422 error response documentation now includes "message content does not match the registered use case" as a possible rejection reason.

**What you need to do:**

* **No action required for compliant messages.** If your outbound SMS content matches your registered A2P campaign or toll-free verification use case, no changes are needed.
* **Review rejection responses.** If you receive a 422 with a content-mismatch reason, review the message body against your registered use case and adjust the content accordingly.
* **New environment variable.** Deployments that self-host the channel manager service need to provide an additional API key environment variable for the compliance check. Contact your Amigo representative for configuration details.

</details>

<details>

<summary>Platform API: Eager Post-Call Production Evaluation (July 2026)</summary>

#### Eager Post-Call Production Evaluation

Completed voice calls can now be evaluated automatically against a workspace's active evaluation definitions immediately after the call ends, without requiring an explicit API call.

**What changed:**

* **Automatic post-call evaluation.** When enabled for a workspace, the platform triggers evaluation of each completed call as soon as teardown finishes. The evaluation runs the same definitions and produces the same verdicts as the existing on-demand evaluation endpoint, so results are directly comparable.
* **Per-workspace daily cap.** A configurable daily cap limits the number of eager evaluations a single workspace can trigger per UTC day. This bounds evaluation spend as a cost guardrail. The cap resets automatically at the UTC day boundary. Set the cap to zero to disable the limit entirely.
* **Slot refund on resolution miss.** The daily cap slot is reserved before evaluation begins. If the call cannot be resolved (for example, because data has not yet been committed), the slot is refunded so transient timing issues do not permanently consume the workspace's daily budget.
* **Dark-launched.** The eager trigger is gated behind a per-workspace feature flag and is off by default. Workspaces opt in by having active evaluation definitions - no additional configuration is required beyond defining what to evaluate and enabling the flag.
* **Dedicated fault isolation.** The eager evaluation path uses its own fault isolation boundary, separate from the paths used during live calls. Evaluation failures or slowdowns cannot affect in-call behavior.
* **Call resolution by durable key.** The eager path resolves the completed call using the call's durable telephony identifier, so evaluation works correctly even when the conversation identifier is not known at teardown time.

**What you need to do:**

* **No action required.** The feature is dark-launched and off by default. To enable eager post-call evaluation for a workspace, contact your Amigo representative to enable the feature flag. Once enabled, any workspace with active evaluation definitions will begin receiving automatic post-call evaluations.
* **Existing on-demand evaluation is unchanged.** The existing endpoint for triggering evaluation on a specific call continues to work as before.

</details>

<details>

<summary>Platform API: Run List Enrichment - Entity Name and Service Name (July 2026)</summary>

#### Run List Enrichment - Entity Name and Service Name

Conversation runs returned by the unified runs endpoint now include resolved entity and service names so list views can display human-readable labels without fetching each entity or service separately.

**What changed:**

* **New `entity_name` field.** The run object now includes `entity_name`, which contains the display name of the entity associated with the run. The name is resolved from the workspace's entity data as a best-effort batch lookup on the built page.
* **New `service_name` field.** The run object now includes `service_name`, which contains the name of the service associated with the run. The name is resolved from the workspace's service configuration as a best-effort batch lookup on the built page.
* **Best-effort, all optional.** Both fields are populated on a best-effort basis. Framework runs and conversation runs without an associated entity or service return null for these fields. If name resolution is temporarily unavailable, the runs are returned without names rather than failing the request.
* **No change to existing fields.** All previously available fields (`caller_id`, `phone_number`, `direction`, `turn_count`, `completion_reason`) remain unchanged.

**What you need to do:**

* **No action required.** These are additive, optional fields. Existing integrations that consume the runs list will continue to work without changes. To display entity and service names in your UI, read the new fields from the run object in the response.

</details>

<details>

<summary>Platform API: Trigger Run Idempotency Guard (July 2026)</summary>

#### Trigger Run Idempotency Guard

Trigger action execution now includes an idempotency guard that prevents duplicate runs from re-executing an action that already succeeded for the same fired event.

**What changed:**

* **Idempotency check before execution.** Before executing a trigger action, the platform checks whether a previous run for the same fired event has already succeeded. If so, the action is skipped and the run is marked as a duplicate skip. This prevents a retry from repeating an action whose successful result was already recorded.
* **Fail-closed on check failure.** If the idempotency check itself fails (for example, due to a transient data access issue), the run is marked as failed rather than proceeding without the safety guard. This ensures actions are never executed without the duplicate protection in place.
* **Per-action retry budgets.** Deterministic actions (such as outbound task scheduling) can specify a bounded retry budget, allowing safe redelivery up to a configurable maximum. Agentic actions (LLM-backed) default to at-most-once execution to avoid unpredictable side effects from repeated runs.
* **Observability.** New metrics track idempotency skips and check failures per trigger, so operators can monitor duplicate suppression and diagnose guard failures.

**What you need to do:**

* **No action required.** The idempotency guard is applied automatically to all trigger action executions. Existing trigger configurations continue to work as before. No API changes, no new parameters, and no client-side changes needed.

</details>

<details>

<summary>Platform API: Durable Trigger Run Processing (July 2026)</summary>

#### Durable Trigger Run Processing

Trigger firings now create durable run records that progress asynchronously through action execution and bounded retries to a terminal status.

**What changed:**

* **Durable run processing.** Trigger firings create run records that progress asynchronously to a terminal `succeeded` or `failed` result. Transient failures are eligible for retry up to the configured maximum attempts.
* **Automatic recovery.** Interrupted runs with attempts remaining are made eligible for another try. Runs that exhaust all attempts receive a terminal failure state.
* **Observable outcome.** Clients can inspect the run's attempt count, timestamps, terminal status, and failure detail through the trigger-run API instead of inferring completion from the initial fire response.

**What you need to do:**

* **No action required.** Trigger runs process asynchronously. Existing trigger configurations, schedules, and webhook-fired triggers continue to work as before. Clients that need completion should read the run status rather than treat the initial fire response as a delivery receipt.

</details>

<details>

<summary>Platform API: Run List Enrichment - Caller, Direction, Turns, and Outcome (July 2026)</summary>

#### Run List Enrichment - Caller, Direction, Turns, and Outcome

Conversation runs returned by the unified runs endpoint now include descriptive enrichment fields so list views can display caller identity, direction, turn count, and outcome without fetching each run's detail.

**What changed:**

* **New optional fields on conversation runs.** The run object returned by `GET /v1/{workspace_id}/runs` now includes five additional fields for conversation runs: `caller_id` (resolved caller identity), `phone_number` (raw contact number), `direction` (inbound or outbound), `turn_count` (number of conversational turns), and `completion_reason` (how the conversation ended).
* **Best-effort, all optional.** These fields are populated on a best-effort basis from the originating channel. Framework runs leave all five fields null. Conversation runs may also leave individual fields null when the source channel does not provide the data.
* **Free-form strings, not strict enums.** `direction` and `completion_reason` are free-form strings rather than restricted enums. This means an unexpected value from a producer never causes a run to be dropped from the list - resilience over strictness.
* **Caller and contact kept separate.** `caller_id` (resolved caller) and `phone_number` (raw contact field) are returned as separate fields, not merged, so consumers can distinguish a resolved identity from a fallback value.

**What you need to do:**

* **No action required.** These are additive, optional fields. Existing integrations that consume the runs list will continue to work without changes. To display the new data, read the new fields from the run object in the response.

</details>

<details>

<summary>Platform API: Unified Runs List Endpoint (July 2026)</summary>

#### Unified Runs List Endpoint

A new endpoint federates framework runs and conversation runs into a single paginated list, giving a merged, channel-inclusive view of all run activity in a workspace.

**What changed:**

* **New endpoint: `GET /v1/{workspace_id}/runs`.** Returns a paginated, newest-first list of runs that merges framework runs (partner agent frameworks) and conversation runs (voice, text, SMS, email, web) at read time behind a single run contract. Each run carries a deterministic identifier, canonical status, kind, channel, and source references.
* **Filtering by kind, channel, and status.** Filter by `kind` (`framework` or `conversation`) to see only one source. Filter by `channel` (`voice`, `text`, `sms`, `email`, `web`) to narrow conversation runs by channel - setting a channel filter automatically excludes framework runs, which carry no channel. Filter by `status` using canonical values (`running`, `paused`, `completed`, `failed`, `timed_out`) or the virtual `live` value, which expands to running + paused.
* **Sort control.** The `sort_by` parameter accepts `+started_at` or `-started_at` to control ordering. Default is newest first (`-started_at`). Only `started_at` is supported as a sort field because it is the timestamp carried on the run wire model, which allows the cross-source merge to reproduce each source's ordering exactly.
* **Opaque continuation-token pagination.** The response includes `has_more` and an opaque `continuation_token` for fetching subsequent pages. Page size is controlled by `limit` (1-200, default 50).
* **Canonical status mapping.** Each run's status is normalized to a canonical value regardless of source. Voice conversation runs derive status from their completion reason. Non-voice conversation runs derive status from their conversation state. Framework runs carry their native status directly.
* **Agent-runs endpoint unchanged.** The existing agent-runs endpoint continues to serve as the framework-only proxy surface. The new `/runs` endpoint is the merged view.

**What you need to do:**

* **No action required for existing integrations.** The agent-runs endpoint is unchanged. Adopt the new `/runs` endpoint when you want a unified view across framework and conversation runs.
* **To use the new endpoint:** send a `GET` request to `/v1/{workspace_id}/runs` with a workspace API key or operator identity token. Use query parameters to filter and paginate.

</details>

<details>

<summary>Platform API: Terminal Lifecycle Marker for Failed and Timed-Out Framework Runs (July 2026)</summary>

#### Terminal Lifecycle Marker for Failed and Timed-Out Framework Runs

Framework agent runs that end with a failure or timeout now emit a terminal lifecycle marker, making them visible in the durable run projection and historical run listings.

**What changed:**

* **Non-success runs now appear in durable run listings.** Previously, runs that failed or timed out produced no completion record. Because the durable run projection derives its view from completion steps, these runs were invisible in historical queries and the Framework Runs table. A terminal lifecycle marker is now emitted for any run that ends in a failed or timed-out state.
* **Best-effort telemetry.** The terminal marker is emitted on a best-effort basis. If the emit fails, the failure is logged and the run's terminal status is unaffected - a run that failed still reports as failed, and a run that timed out still reports as timed out.
* **No duplicate markers for successful runs.** Successful runs already emit a completion step as part of their trajectory. The terminal marker is only emitted for non-success runs, so there is no duplicate completion for runs that succeed.

**What you need to do:**

* **No action required.** Failed and timed-out runs will now appear automatically in the Framework Runs table and the durable run list endpoint. No API changes, no new parameters, and no configuration needed.

</details>

<details>

<summary>Platform API: Tenant Isolation for Session Event Injection (July 2026)</summary>

#### Tenant Isolation for Session Event Injection

Session event injection (used by operator guidance and external event endpoints) now enforces workspace ownership, preventing cross-workspace injection.

**What changed:**

* **Workspace ownership check on injection.** When injecting an event into an active voice session, the platform now verifies that the caller's workspace matches the workspace that owns the call. If the workspaces do not match, the request is rejected with a `403 Forbidden` response.
* **Closes cross-workspace injection vector.** Previously, a valid session identifier was sufficient to inject events into any active call. An operator in workspace A could potentially inject guidance or external events into workspace B's calls. The workspace ownership check closes this vector.
* **Workspace field added to injection payload.** The injection request now includes a workspace identifier. This field is populated automatically by the platform when forwarding injection requests - no changes are required by API consumers calling the operator guidance or session event endpoints.
* **Observability for rejected attempts.** Cross-workspace injection attempts are logged for security observability. No sensitive content is included in the log entry.

**What you need to do:**

* **No action required for most integrations.** If you use the operator guidance endpoint or the session event injection endpoint through the Platform API, the workspace context is handled automatically. Your existing calls will continue to work as long as the operator and the call belong to the same workspace (which is the expected case).
* **If you operate multiple workspaces:** be aware that injection requests targeting calls in a different workspace will now receive a `403 Forbidden` response instead of succeeding.

</details>

<details>

<summary>Platform API: Provider Access Grant API with Role-Based Scopes (July 2026)</summary>

#### Provider Access Grant API with Role-Based Scopes

Provider access grants now support role-based scope resolution and a full lifecycle management API, enabling workspace administrators to create, list, and revoke provider grants with fine-grained control over scribe access.

**What changed:**

* **Grant roles.** Each provider access grant now carries a role - either `provider` or `scribe_admin`. The role determines which scope set the grant conveys: provider grants receive the base scribe provider scopes, while scribe admin grants receive the extended administrative scope set (which includes cross-provider session visibility, access management, impersonation, and record deletion).
* **Create provider grant.** A new endpoint creates a workspace-scoped provider access grant. The request specifies the target workspace, email, role, whether MFA is required, and optionally a provider entity binding. The grant status is determined automatically: grants without a provider entity start as `pending_entity`, grants with an unverified email start as `pending_verification`, and fully provisioned grants start as `active`. Duplicate active grants for the same email or entity are rejected with a `409 Conflict` response.
* **List provider grants.** A new endpoint lists provider grants in a workspace with optional status filtering. Results are ordered by grant creation time (newest first).
* **Revoke provider grant.** A new endpoint revokes a provider grant and automatically invalidates all sessions and refresh tokens bound to that grant. The response includes counts of sessions and refresh tokens revoked, providing full visibility into the downstream impact of the revocation.
* **Role-aware token minting and refresh.** Access tokens minted from provider grants now carry the scope set corresponding to the grant's role. Token refresh operations validate that the requested scopes match the grant's role-derived scopes - attempts to alter scopes during refresh are rejected.
* **Audit logging.** Grant creation, creation failures (duplicates and integrity errors), and revocation are all audit-logged with grant metadata including role, scopes, and downstream revocation counts.

**What you need to do:**

* **No action required for existing provider grants.** Existing grants default to the `provider` role and continue to receive the same scribe provider scopes as before.
* **To create scribe admin grants:** specify `role: "scribe_admin"` when creating a provider access grant. The grant will convey the administrative scope set.
* **To manage provider grants programmatically:** use the new create, list, and revoke endpoints through the internal grants API surface. All endpoints require workspace admin credentials and the `identity:admin` scope.

</details>

<details>

<summary>Platform API: Scribe Provider Scopes (July 2026)</summary>

#### Scribe Provider Scopes

The identity system now defines dedicated scope sets for scribe provider sessions and scribe administration, enabling fine-grained access control for clinical scribe workflows.

**What changed:**

* **Provider scribe scopes.** A new set of scopes grants providers access to scribe session recording, note authoring (read and write on own notes), and reading their own encounters and appointments. These scopes are purpose-built for provider-facing scribe sessions.
* **Scribe admin scopes.** An administrative scope set extends provider scopes with cross-provider session visibility, access management, impersonation, and record deletion. These scopes are intended for scribe platform administrators.
* **Scribe scopes excluded from role expansion.** Scribe scopes are never granted through standard workspace roles (viewer, member, admin, owner). They are available only through explicit scribe-specific session flows, preventing unintended privilege escalation through role inheritance.
* **New `provider_scribe_sessions:create` scope.** A dedicated session-creation scope for provider scribe sessions. This scope is non-delegatable, consistent with other session-creation scopes.
* **Provider scope restrictions updated.** Scribe admin scopes and the provider scribe session creation scope are excluded from provider token grants, ensuring providers receive only their own scribe scopes and not administrative capabilities.

**What you need to do:**

* **No action required for existing integrations.** Standard workspace roles and existing session flows are unchanged. Scribe scopes only appear when explicitly requested through scribe session flows.
* **To use scribe provider sessions:** request scribe provider scopes when minting provider scribe sessions. Your session credentials must carry the `provider_scribe_sessions:create` scope.
* **To administer scribe access:** use credentials that carry scribe admin scopes for cross-provider session management, access control, and record deletion.

</details>

<details>

<summary>Platform API: Managed Integrations (July 2026)</summary>

#### Managed Integrations

Integrations can now be marked as system-managed, protecting them from modification or deletion through normal workspace API keys. Only principals carrying the platform admin scope can create, update, or delete managed integrations and their endpoints.

**What changed:**

* **New `managed` field on integrations.** A boolean field (default `false`) on the integration resource indicates whether the integration is system-managed. The field appears in create, update, list, and get responses.
* **Create protection.** Setting `managed: true` when creating an integration requires platform admin scope. Requests without the required scope receive a `403 Forbidden` response.
* **Update protection.** Updating any field on a managed integration requires platform admin scope. Changing the `managed` flag itself also requires platform admin scope. Requests without the required scope receive a `409 Conflict` response.
* **Delete protection.** Deleting a managed integration requires platform admin scope. Requests without the required scope receive a `409 Conflict` response.
* **Endpoint protection.** Creating, updating, or deleting endpoints on a managed integration requires platform admin scope. Requests without the required scope receive a `409 Conflict` response.
* **Audit logging.** Blocked mutation attempts against managed integrations are audit-logged with the action, resource, and reason.

**What you need to do:**

* **No action required for existing integrations.** All existing integrations default to `managed: false` and behave exactly as before.
* **To create a managed integration:** pass `managed: true` in the create request body. Your credentials must carry platform admin scope.
* **To modify or delete a managed integration:** ensure your credentials carry platform admin scope.
* **To check whether an integration is managed:** inspect the `managed` field in the integration list or detail response.

</details>

<details>

<summary>Platform API: Agent Runs - Durable List Endpoint (July 2026)</summary>

#### Agent Runs - Durable List Endpoint

A new list endpoint on the Agent Runs surface returns a paginated, filterable list of framework agent runs for a workspace. Run data is sourced from a durable read model, so runs are available for querying beyond the lifetime of a single session.

**What changed:**

* **New `GET /v1/{workspace_id}/agent-runs` endpoint.** Returns a paginated list of framework agent runs for the workspace, ordered newest first. Each item includes the run ID, framework, status, origin source, entity ID, token usage (input and output), step count, duration, start time, and creation time.
* **Filtering.** Optional `framework` and `status` query parameters let you narrow results. Accepted framework values are `claude-agent-sdk` and `openai-agents`. Accepted status values are `running`, `succeeded`, `failed`, and `timed_out`. Invalid filter values return a 422 validation error.
* **Pagination.** The endpoint accepts `limit` (1-200, default 50) and an opaque `continuation_token` for cursor-based pagination. The response includes `has_more` and a `continuation_token` for fetching the next page. Pass the returned token as `continuation_token` on the next request to advance.
* **Response shape.** The response body contains `items` (array of run summaries), `has_more` (boolean), and `continuation_token` (opaque, present only when `has_more` is true).
* **Durable read model.** Run data is sourced from a durable projection rather than live session state, so historical runs remain queryable after runtime restarts or session expiry.
* **Read-rate-limited.** The endpoint is gated by the standard read rate limit.

**What you need to do:**

* **To list framework runs:** call `GET /v1/{workspace_id}/agent-runs` with your workspace API key or operator identity token. Use the optional `framework` and `status` query parameters to filter, and `continuation_token` to paginate through large result sets.
* **Existing agent run endpoints are unchanged.** Dispatch, polling, result retrieval, and the harness context endpoint work exactly as before.

This framework-only list endpoint has since been retired. For current run listing and filtering, see the [Unified Runs](https://docs.amigo.ai/developer-guide/platform-api/conversations/runs) developer guide.

</details>

<details>

<summary>Platform API: Voice Conversations - Per-Turn Transcript Recovery (July 2026)</summary>

#### Voice Conversations - Per-Turn Transcript Recovery

Voice conversation detail now recovers per-turn transcripts from a durable analytical projection when the live session cache has expired. Previously, if the real-time session data was no longer available, voice conversation detail fell back to a single concatenated transcript displayed as one system message - losing the turn-by-turn structure. With this change, the platform reads per-turn voice data from a dedicated projection, restoring ordered user and agent turns with full metadata.

**What changed:**

* **Per-turn voice transcript recovery.** When the live session cache no longer holds turn data for a voice call, the platform now reads from a dedicated per-turn analytical projection before falling back to the single-transcript display. This restores the turn-by-turn conversation structure including user transcripts, agent transcripts, agent actions, state information, and state transitions.
* **`include_tool_calls` parameter support for voice detail.** The `GET` conversation detail endpoint now passes the `include_tool_calls` query parameter through to the voice detail path. When `include_tool_calls=true`, recovered voice turns include tool call data on agent turns. Tool calls are omitted by default to keep the response payload small.
* **Graceful degradation preserved.** If the analytical projection is unavailable or the read fails, the platform falls back to the previous behavior (single concatenated transcript). No existing behavior is removed.

**What you need to do:**

* **No action required.** Per-turn transcript recovery is automatic for all voice conversations. The conversation detail endpoint returns richer turn data without any changes to your integration.
* **To include tool calls in recovered voice turns:** pass `include_tool_calls=true` on the conversation detail request. This parameter was already supported for text conversations and now applies to voice conversations as well.

</details>

<details>

<summary>Platform API: Agent Runs - Harness Context Endpoint (July 2026)</summary>

#### Agent Runs - Harness Context Endpoint

A new read endpoint on the Agent Runs surface returns the neutral session-bootstrap context for a service - the same projection the hosted runner renders from - so a customer's own framework can bootstrap a session against the same world model.

**What changed:**

* **New `GET /v1/{workspace_id}/agent-runs/harness-context` endpoint.** Returns the harness context for a given service, including agent identity, reference instructions, world scope, tool descriptors, guardrails, and the server-enforced write floor.
* **Query parameters.** Accepts `service_id` (required, UUID) and `version_set` (optional, defaults to `release`, max 255 characters).
* **PHI-free projection.** The response carries no scoped entities or rendered caller prose. It is safe for external framework bootstrapping.
* **Byte-identical to hosted render.** The context is produced through the same resolution and projection path used by the hosted runner, so a remote fetch and a hosted session see the same world model.
* **Read-rate-limited.** The endpoint is gated by the standard read rate limit.

**What you need to do:**

* **To bootstrap your own framework session:** call `GET /v1/{workspace_id}/agent-runs/harness-context?service_id={id}` with your workspace API key or operator identity token. Use the returned context to configure your framework's session with the same identity, tools, guardrails, and world scope the platform provides.
* **Existing agent run endpoints are unchanged.** Dispatch, polling, and result retrieval work exactly as before.

For details, see the [Harness Context](https://docs.amigo.ai/developer-guide/platform-api/functions/harness-context) developer guide.

</details>

<details>

<summary>Platform API: Agent Runs and Definitions - Drop CrewAI Framework Support (July 2026)</summary>

#### Agent Runs and Definitions - Drop CrewAI Framework Support

The `crewai` framework has been removed from the platform. The two supported bring-your-own frameworks for native agent definitions and runs are now `openai-agents` (declarative handoff graph) and `claude-agent-sdk` (single agent with optional subagents). Both are fully executable end-to-end.

**What changed:**

* **CrewAI removed from accepted frameworks.** The `crewai` value is no longer accepted in the `framework` field on Agent Definitions or Agent Runs endpoints. Requests that specify `crewai` will receive a validation error.
* **Two supported frameworks.** The supported frameworks are `openai-agents` and `claude-agent-sdk`. Both are registrable and runnable - there is no longer a distinction between registrable-but-not-runnable and fully runnable frameworks.
* **Existing CrewAI definitions.** Previously registered CrewAI definitions remain in the registry as archived records but cannot be used to create new versions or dispatch runs.

**What you need to do:**

* **If you had CrewAI definitions:** Migrate to one of the two supported frameworks (`openai-agents` or `claude-agent-sdk`) and register a new definition. Archive any existing CrewAI definitions.
* **If you were not using CrewAI:** No action required.

For details on the supported framework shapes, see the [Agent Definitions](https://docs.amigo.ai/developer-guide/platform-api/functions/agent-definitions) developer guide.

</details>

<details>

<summary>Platform API: Agent Runs - Durable Trajectory Persistence (July 2026)</summary>

#### Agent Runs - Durable Trajectory Persistence

Agent runs now retain their normalized trajectory beyond the lifetime of the live execution. Previously, trajectory data could disappear after a runtime restart or run eviction. Completed trajectory steps are now available for supported downstream analytics, distillation, and quality evaluation.

**What changed:**

* **Trajectory persistence on run completion.** When a framework run succeeds, the platform writes each normalized trajectory step to the analytical data store. Each step carries the same actor attribution, framework tag, tool call metadata, usage counts, and state transitions already visible in the run result.
* **Provenance stamping.** Every persisted trajectory step is stamped as platform-executed, indicating the platform ran the framework and directly observed its tool calls. This distinguishes platform-executed history from future client-reported trajectories.
* **Content-tier fields excluded.** Verbatim transcript text and raw model reasoning (chain-of-thought) are not persisted - only structural metadata (tool names, token counts, state transitions, actor attribution) is written. This is consistent with the platform's data handling posture for analytical data.
* **Best-effort, never affects run status.** Trajectory persistence is best-effort. A persistence failure is logged and skipped but never changes the run's terminal status (succeeded, failed, or timed out). A per-step failure skips that step; the remaining steps still persist.
* **Dark-ship safe.** When the persistence path is not yet configured for an environment, trajectory capture is a no-op. The run path is completely unaffected, so the feature can be rolled out incrementally.
* **Deduplicated writes.** Each trajectory step uses a stable identifier for at-least-once deduplication, so retries of the same logical step do not produce duplicate records.

**What you need to do:**

* **No action required.** Trajectory persistence is automatic for all completed framework runs. There are no new API fields, parameters, or endpoints. Run dispatch, polling, and result retrieval work exactly as before.

</details>

<details>

<summary>Platform API: Agent Runs - Native Definition Runs End-to-End (July 2026)</summary>

#### Agent Runs - Native Definition Runs End-to-End

The Agent Runs endpoint now supports dispatching native agent definitions end-to-end. You can run a customer-authored agent definition - either a registered definition by ID or an inline definition body - directly against the workspace's data surface, with full trajectory normalization, token usage, and actor attribution.

**What changed:**

* **Native run mode on the create-run endpoint.** `POST /v1/{workspace_id}/agent-runs` now accepts a `native` object as an alternative to `service_id` + `framework`. Exactly one mode must be specified per request.
* **Run by registered definition.** Set `native.definition_id` (and optionally `native.version`) to run a previously registered agent definition. When `version` is omitted, the latest version is used. The definition must belong to the same workspace.
* **Run by inline definition.** Set `native.inline` to a definition document for dev/playground iteration. The inline body is validated against the platform clamp schema before dispatch - validation errors return a 422 with field-level detail.
* **Two supported frameworks.** Native runs are supported for the single-agent-with-subagents (`claude-agent-sdk`) and declarative-handoff-graph (`openai-agents`) framework shapes. Both are fully executable end-to-end.
* **Clamp validation at dispatch.** Native definition bodies are re-validated at dispatch time even for registered definitions, so a schema change that invalidates a previously-registered body surfaces as a 422 rather than a mystery failed run.
* **Same trajectory output.** Native runs produce the same normalized trajectory, actor attribution, token usage (including cache tokens), and framework tagging as platform runs.

**What you need to do:**

* **To run a registered definition:** `POST /v1/{workspace_id}/agent-runs` with `native: {definition_id: "..."}` and a `message`. Omit `service_id` and `framework`.
* **To run an inline definition:** `POST /v1/{workspace_id}/agent-runs` with `native: {inline: {...}}` and a `message`. The inline body must include a `framework` field and conform to the clamp schema for that framework.
* **To run a specific version:** Add `version` to the `native` object (e.g., `native: {definition_id: "...", version: 3}`).
* **Existing platform runs are unchanged.** The `service_id` + `framework` path works exactly as before.

For endpoint details, see the [Agent Runs](https://docs.amigo.ai/developer-guide/platform-api/functions/agent-runs) developer guide. For definition registration, see [Agent Definitions](https://docs.amigo.ai/developer-guide/platform-api/functions/agent-definitions).

</details>

<details>

<summary>Platform API: Agent Definitions - Native Agent Definition Registry (July 2026)</summary>

#### Agent Definitions - Native Agent Definition Registry

The Platform API now includes a dedicated registry for native agent definitions. Customers can register, version, validate, list, and archive their own framework-native agent definitions as immutable, versioned resources within a workspace.

**What changed:**

* **New Agent Definitions CRUD endpoints.** A new set of endpoints under `/v1/{workspace_id}/agent-definitions` lets you register, list, retrieve, validate, and archive native agent definitions. Definitions are workspace-scoped and identified by a stable name and framework.
* **Immutable versioning with idempotent push.** Every push of a changed definition body mints a new version number. Re-pushing a byte-identical body returns the existing version without creating a duplicate, so CI pipelines can push on every run safely.
* **Clamp validation.** Definition bodies are validated against a strict whitelist schema. Only fields the platform will honor are accepted - any unrecognized field is a 422 validation error naming the offending path, never silently ignored or rewritten. A dry-run validation endpoint (`POST .../validate`) lets you check a body without storing anything.
* **Framework lock per name.** A definition's framework is set on first registration and cannot be changed. Attempting to register the same name with a different framework returns a 409 Conflict. To switch frameworks, archive the existing definition and register a new one.
* **Soft archive.** `DELETE .../agent-definitions/{definition_id}` soft-archives a definition, freeing the name for reuse. Existing versions remain immutable and retrievable.
* **Two supported frameworks.** `openai-agents` (declarative handoff graph) and `claude-agent-sdk` (single agent with optional subagents).
* **Write-tool and agent-count metadata.** Each version is tagged with whether it references write tools and how many agents it declares, so callers can inspect these properties without parsing the body.
* **Paginated listing with framework filter.** The list endpoint supports filtering by framework, including or excluding archived definitions, and pagination with continuation tokens.

**What you need to do:**

* **To register a native definition:** `POST /v1/{workspace_id}/agent-definitions` with a `name` (slug) and `body` (the framework-native definition document including a `framework` field). The response includes the definition ID, version number, and whether a new version was created.
* **To validate without storing:** `POST /v1/{workspace_id}/agent-definitions/validate` with the same request shape. Returns validation results or a 422 with field-level errors.
* **To list definitions:** `GET /v1/{workspace_id}/agent-definitions` with optional `framework`, `include_archived`, `limit`, and `continuation_token` query parameters.
* **To retrieve a definition with version history:** `GET /v1/{workspace_id}/agent-definitions/{definition_id}`.
* **To retrieve a specific version body:** `GET /v1/{workspace_id}/agent-definitions/{definition_id}/versions/{version}`.
* **To archive:** `DELETE /v1/{workspace_id}/agent-definitions/{definition_id}` (requires admin+ role).

**Permissions:** Register and validate require Service create permission (member+). List and get require Service view permission (viewer+). Archive requires Service delete permission (admin+).

For full endpoint documentation, see the [Agent Definitions](https://docs.amigo.ai/developer-guide/platform-api/functions/agent-definitions) developer guide.

</details>

<details>

<summary>Platform API: Customer Data Intake - Folder Path Materialization (July 2026)</summary>

#### Customer Data Intake - Folder Path Materialization

Documents ingested from mapped cloud storage folders now carry the original folder path, enabling retrieval filtering by source folder location.

**What changed:**

* **Folder path preserved on intake documents.** When documents are ingested from a connected cloud storage folder, the platform now records the relative folder path within the mapped folder tree. The path uses folder names joined by `/` (e.g. `clinical/notes`), with an empty string at the root level.
* **Path updated on re-sync.** If a file moves to a different subfolder and is re-synced, the stored folder path updates to reflect its new location rather than retaining the original path.
* **Exposed in the file listing.** The `source_folder_path` field is returned on each file in the intake file list endpoint, so the console and integrations can display or filter by original folder structure.
* **Enables folder-based retrieval filtering.** With folder paths available, retrieval queries can filter documents by their original folder location - for example, restricting results to documents that lived under `clinical/` or `billing/reports/` in the source folder structure.
* **Null for manual uploads and older documents.** Documents uploaded manually (not through a connected folder source) carry no folder path. Documents ingested before this change also have no folder path. In both cases the field is null.

**What you need to do:**

* **No action required.** The folder path is recorded automatically during intake for documents from connected folder sources. Existing documents are unaffected.
* **To use folder-based filtering:** Use the `source_folder_path` field in the intake file list to scope views or retrieval to specific source folders.

</details>

<details>

<summary>Platform API: Memory - Clinical State Projection (Phase-2b Structured Dimensions) (July 2026)</summary>

#### Memory - Clinical State Projection (Phase-2b Structured Dimensions)

The memory system now includes a deterministic clinical state dimension that projects active conditions, medications, and allergies from connector/EHR data directly into the patient's memory profile - no LLM extraction required.

**What changed:**

* **New `clinical_state` memory dimension.** A new system-default memory dimension called `clinical_state` is now computed for every workspace. It contains a concise, human-readable summary of the patient's active clinical facts - conditions, medications with doses, and allergies - projected directly from structured connector/EHR data in the world model.
* **Deterministic, not agent-inferred.** Unlike LLM-extracted memory dimensions, the clinical state projection is fully deterministic. It rolls up already-structured clinical data without involving an LLM, which eliminates hallucination risk and gives it the highest precision tier (clinical/safety).
* **Higher confidence tier than LLM memory.** The clinical state projection is emitted at a confidence tier above LLM-extracted memory. When both an LLM-inferred observation and the structured projection exist for the same patient on the same key, the projection always wins. This ensures source-of-truth clinical facts from connector data are never overwritten by agent-inferred observations.
* **Loaded at session start.** The clinical state summary is available to the agent at session start alongside the user model - no tool call needed. Clinical facts that were previously reachable only through runtime tool calls are now part of the agent's initial context.
* **Genuinely current statuses.** Conditions and medications are filtered to all genuinely current clinical statuses, not just "active." Recurrences and relapses are included so they are never silently dropped from the clinical picture.
* **Bounded with visible overflow.** The summary is bounded by recency: conditions and medications are ordered by most recent first and capped, with a visible overflow marker (e.g., "+3 more conditions") rather than silent truncation. Allergies are never truncated due to their safety-critical nature.
* **Automatic updates.** The projection updates automatically as underlying clinical data changes through connector syncs. The updated summary is available on the next session without manual intervention.

**What you need to do:**

* **No action required.** The clinical state dimension is computed automatically for all workspaces with connected clinical data sources. It appears in the patient's memory profile alongside existing dimensions.
* **No change to existing memory behavior.** Existing LLM-extracted dimensions (emotional state, engagement patterns, communication preferences, and so on) continue to work exactly as before. The clinical state projection is additive.

</details>

<details>

<summary>Platform API: Metering - Daily Meter Grain, Invoice Summation Fix, and Connector Enrichment Metering (July 2026)</summary>

#### Metering - Daily Meter Grain, Invoice Summation Fix, and Connector Enrichment Metering

Usage metering now records meter values at daily grain instead of monthly, invoice generation correctly sums daily meter rows across a billing period, and connector enrichment calls are now metered per workspace.

**What changed:**

* **Daily meter grain.** Meter values are now emitted at daily granularity instead of monthly. Each day produces a separate meter row per meter key and metering source. This gives more precise usage visibility and supports mid-period billing adjustments.
* **Invoice summation fix.** Invoice generation now correctly sums all daily meter rows for a billing period when computing line-item quantities. Previously, only one row per meter key was used, which could under-report usage for periods spanning multiple days. Invoices now reflect the full accumulated usage.
* **Connector enrichment metering.** When the platform uses AI models during connector data enrichment, the token usage is now metered and attributed to the workspace whose data was enriched. For batch enrichment calls that process data from multiple workspaces in a single request, usage is apportioned proportionally across the workspaces based on their share of the input data. This ensures that connector enrichment costs appear in standard usage reporting alongside other metered usage.

**What you need to do:**

* **No action required.** The daily grain change is backward-compatible - billing periods still aggregate correctly, and invoices reflect the same total usage. Connector enrichment usage will begin appearing in usage reports automatically.
* **If you consume raw meter data:** Note that meter rows are now emitted daily rather than monthly. Queries that assume one row per meter key per month should be updated to sum across the billing period.

</details>

<details>

<summary>Platform API: Memory - Custom (Layer-2) Dimensions in Extraction (July 2026)</summary>

#### Memory - Custom (Layer-2) Dimensions in Extraction

The memory extraction pipeline now supports per-workspace custom dimensions alongside the fixed system defaults.

**What changed:**

* **Two-tier dimension model.** Memory extraction dimensions are now organized into two tiers. Layer-1 dimensions are the fixed system defaults computed for every workspace (emotional state, engagement patterns, communication preferences, and so on). Layer-2 dimensions are workspace-specific custom keys that a workspace opts into through the enrichment registry.
* **Opt-in via enrichment registry.** A custom dimension is picked up by the extractor only when its registry entry carries the `memory_extract` tag. This is an opt-in model - most enrichment keys (such as connector-fed clinical facts) are deliberately excluded from extraction because they should not be inferred from conversation transcripts.
* **Merged into the extraction pipeline.** Custom dimensions are rendered as additional targets in the extraction prompt and added to the observation key-gate, so they flow through the same extraction and validation machinery as the system defaults. No per-workspace prompt code is needed.
* **Safety constraints on custom keys.** Custom dimension keys must be valid identifiers (bare snake\_case, no special characters) and must not shadow a system default key. System defaults always take precedence - a workspace cannot replace or override built-in dimensions like emotional state or crisis indicators. Custom dimensions are capped at the behavioral precision tier, so they do not affect safety-critical signal routing.
* **Deterministic prompt ordering.** Custom dimensions are sorted by key before rendering, which stabilizes prompt layout for tests and debugging. Extraction still uses a model, so the resulting observations can vary across runs.

**What you need to do:**

* **To use custom dimensions:** Register person-entity enrichment keys in your workspace's enrichment registry with the `memory_extract` tag and a text description. The extractor will pick them up automatically on its next run.
* **No action required for existing workspaces.** Workspaces with no custom dimensions continue to use the system defaults exactly as before. There is no change to existing extraction behavior.

</details>

<details>

<summary>Platform API: Agent Runs - Native Definition Validation (July 2026)</summary>

#### Agent Runs - Native Definition Validation

Native agent definitions are now validated through a strict whitelist schema that treats customer-authored definitions as untrusted input.

**What changed:**

* **Whitelist clamp validation.** Every native definition is parsed through a strict schema that accepts only the fields the platform will honor. Any unrecognized field is a validation error naming the offending field - fields are never silently ignored or rewritten. This prevents configuration injection through unexpected keys such as subprocess environment variables, custom tool URLs, or sampling parameters.
* **Tool name validation.** The `allowed_world_tools` list on each actor in a native definition must reference tools from the platform catalog only. An unknown tool name is rejected with a 422 identifying the invalid name. Tools are referenced by catalog name - there is no passthrough to arbitrary tool endpoints.
* **Write tool tagging.** When a native definition references any write tool, the definition is tagged with a write indicator. This drives the write badge in the console and the write-scope enforcement at the data surface boundary.
* **Bounded fan-out.** Native definitions enforce caps on the number of agents, tasks, turns, and iterations to limit resource consumption per run.
* **No platform identifiers in the body.** The schema rejects platform identifiers (workspace ID, entity ID, service ID) in the definition body, preventing cross-workspace reference injection.
* **Model-family validation.** When a native definition pins a specific model, the platform validates that the model belongs to the correct provider family for the chosen framework. A mismatched model is rejected at registration rather than failing at run time.
* **Internal consistency checks.** Task-to-agent references, agent handoff targets, entry agent resolution, and text placeholders are all validated for internal consistency at registration time.
* **Schema revision tracking.** Each validated definition is stamped with a schema revision so that if the accepted shape changes in the future, definitions that no longer parse are diagnosable with a clear error naming the offending path and the revision.

**What you need to do:**

* **Review native definitions for unrecognized fields.** If your native definition includes fields outside the supported set, they will now be rejected. Remove any unsupported fields before registering.
* **Verify tool names.** Ensure all `allowed_world_tools` entries reference valid platform catalog tool names. Use the read dispatcher tools (`world_read`, `list_world_read_tools`) for read access, and named clinical write tools for write access.
* **Check model pinning.** If you pin a model in your native definition, confirm it belongs to the correct provider family for your chosen framework.

</details>

<details>

<summary>Platform API: Agent Runs - Native Run Mode (July 2026)</summary>

#### Agent Runs - Native Run Mode

Agent runs now support a native run mode where a customer's own agent definition drives the run instead of a platform-authored persona and context graph.

**What changed:**

* **Native run context.** When dispatching a run in native mode, the run context carries the workspace scope, tool surface, and write-scope bindings but omits the platform persona and context graph. Identity and instructions come from the customer's own agent definition (their own framework graph or crew) rather than from a platform-authored agent version.
* **Server-side safety enforcement.** Native runs enforce safety at the data surface boundary - the tool surface and write-scope bindings are applied server-side, so customer-authored agents cannot bypass workspace data access controls regardless of how they are configured.
* **Entry actor resolution.** For native runs, the entry actor name is resolved from the native definition body at the framework edge rather than from a platform persona. Trajectory normalization uses a neutral default when no platform persona is present.

**What you need to do:**

* **No action required for existing runs.** Platform-mode runs (with a persona and context graph) behave identically. The native run mode is an additional option for customers who bring their own agent definitions.
* **Native run users: note the safety boundary.** Your agent definition controls identity and instructions, but workspace data access is still governed by the tool surface and write-scope bindings configured on the workspace.

</details>

<details>

<summary>Platform API: Agent Runs - Actor Attribution and Multi-Agent Trajectory (July 2026)</summary>

#### Agent Runs - Actor Attribution and Multi-Agent Trajectory

Agent run trajectories now carry per-step actor attribution and capture agent-to-agent delegation events, enabling cross-framework analytics over multi-agent runs.

**What changed:**

* **Actor attribution on every trajectory step.** Each step in the trajectory now includes an `actor_name` field identifying which actor within the run produced the step. For single-agent runs, every step attributes to the entry agent. For multi-agent definitions (runs with subagents or delegated agents), steps attribute to the specific actor that produced them. The field is always populated - it never defaults to an empty string - so grouping and filtering by actor is reliable across frameworks.
* **Delegation handoff steps.** When one agent delegates to another within a multi-agent run, the trajectory now includes a `handoff` step with `handoff_kind` set to `delegation`. The step's `actor_name` identifies the source (delegating) agent, and a new `handoff_to` field identifies the target agent. Previously, these delegation events were silently dropped from the trajectory.
* **Framework identifier on trajectory metadata.** The trajectory reference now includes a framework identifier so consumers can distinguish which supported framework executed the run without inspecting step-level details.

**What you need to do:**

* **API consumers: note new fields.** If you consume the run detail or trajectory response, two new fields are available on each trajectory step: `actor_name` (string, always present) and `handoff_to` (string, present on delegation handoff steps). Both are safe to ignore if you do not need multi-agent analytics.
* **No action required for single-agent runs.** Single-agent runs behave identically - every step attributes to the entry agent, and no delegation handoff steps appear.

</details>

<details>

<summary>Platform API: Intake Files - Hide Withdrawn Documents (July 2026)</summary>

#### Intake Files - Hide Withdrawn Documents

The intake files list endpoint now hides files whose parent document has been withdrawn (source-deleted) by default, keeping the Files list clean when upstream sources remove content.

**What changed:**

* **New `include_withdrawn` query parameter.** The list intake files endpoint now accepts an `include_withdrawn` boolean query parameter (defaults to `false`). When `false`, files belonging to withdrawn documents are excluded from the response. When `true`, all files are returned regardless of document status.
* **Default behavior change.** Previously, files from withdrawn documents appeared in the files list with no indication that their parent document had been removed. Now these files are hidden by default, so the files list reflects only active content.
* **Files without a parent document are unaffected.** Snapshot and CSV files that are not linked to a parent document continue to appear in the list regardless of the `include_withdrawn` setting.

**What you need to do:**

* **No action required for most users.** The default behavior now excludes withdrawn files, which matches the expected experience. If you need to see files from withdrawn documents, pass `include_withdrawn=true` to the list endpoint.

</details>

<details>

<summary>Platform API: Agent Runs - Dual-Mode Auth, Cache Usage, and Hardening (July 2026)</summary>

#### Agent Runs - Dual-Mode Auth, Cache Usage, and Hardening

Agent runs now accept operator identity tokens (the console path) in addition to workspace API keys, report granular cache token usage, and include several reliability improvements.

**What changed:**

* **Dual-mode authentication.** Agent run endpoints now accept both workspace API keys and operator identity tokens. The console's Framework Runs panel uses an identity token derived from the operator's session - this path was previously rejected. The bearer is forwarded end-to-end and re-verified by the data surface, so the caller's credential governs data access throughout the run.
* **Cache token usage.** Run results now include `cached_tokens` (cache-read input tokens) and `cache_creation_tokens` (cache-write input tokens) alongside the existing `input_tokens` and `output_tokens`. These fields reflect native provider accounting where cache reads and writes are billed at different rates. Without them, a cache-heavy run reported a misleading near-zero input count.
* **Trajectory span deduplication.** Trajectory span IDs now include a per-run component, preventing span-ID collisions when the same workspace dispatches multiple runs. This fixes duplicate event detection in downstream event buses.
* **MCP tool call timeout.** The timeout for each tool call over the world-tools data surface has been increased to accommodate data queries and cold starts that routinely exceeded the previous default.
* **Improved error messages.** Failed runs now surface actionable error text instead of opaque internal exception strings. Grouped exceptions (such as connection failures during tool calls) are flattened to their leaf messages.

**What you need to do:**

* **Console users: no action required.** The Framework Runs panel on the service detail page now works with your console session - no API key needed.
* **API consumers: update usage parsing.** If you consume the run detail response, note the two new fields in the `usage` object: `cached_tokens` and `cache_creation_tokens`. Both default to `0` and are safe to ignore if you do not need cache-granular cost tracking.

</details>

<details>

<summary>Platform API: OIDC/SAML Federation - Consumed-Invitation Recovery (July 2026)</summary>

#### OIDC/SAML Federation - Consumed-Invitation Recovery

OIDC and SAML federation login now recovers operators who accepted a workspace invitation through a non-federation method (such as magic link or email one-time password) before signing in with their workspace's federated identity provider. Previously, these operators were rejected with a 401 because their identity was recorded under a different authentication provider, and the federation login could not resolve them.

**What changed:**

* **Source-workspace recovery path.** When a federated login cannot resolve an operator through the standard per-provider identity lookup or a provision policy, the platform now checks whether the asserted email matches an existing active member of the federation source's own workspace. If it does, the login succeeds and the issued token is scoped to that workspace.
* **Token scoped to source workspace.** Tokens issued through the recovery path are always scoped to the federation source's own workspace - never to a client-supplied workspace ID. This prevents a workspace-scoped identity provider from minting tokens for workspaces outside its boundary.
* **No durable identity created.** The recovery path does not create a persistent federation identity mapping. Each subsequent login re-verifies active membership in the source workspace, so the operator must remain an active member for future logins to succeed.
* **Pending invitations not auto-accepted.** Unlike the inbox-proof authentication methods (magic link, email one-time password), the federation recovery path does not bind pending invitations from an identity provider assertion. Invited users still accept through the standard console flow, which verifies the invited email independently.
* **Audit logging.** Recovery-path logins are recorded with a dedicated audit event that includes the federation source, the matched authentication provider, and the asserted email.

**What you need to do:**

* **No action required.** Operators who were previously unable to log in through their workspace's OIDC or SAML provider after accepting an invitation via magic link or email one-time password can now sign in without manual intervention. No configuration changes are needed.

</details>

<details>

<summary>Platform API: Workspace-Bound Admin Routes and platform:admin Scope (July 2026)</summary>

#### Workspace-Bound Admin Routes and platform:admin Scope

All identity admin API routes are now workspace-bound. Admin callers can only act on resources within their own workspace unless they hold the new global `platform:admin` scope. This closes a class of cross-tenant authorization gaps where a workspace admin could target another workspace's resources.

**What changed:**

* **New `platform:admin` scope.** A new global scope (`platform:admin`) grants cross-workspace admin authority over the identity admin API. This scope is never granted through workspace role expansion - it must be explicitly provisioned to platform-team credentials. It is non-delegatable: credentials cannot pass it to downstream grants.
* **Workspace-bound admin routes.** Every admin route that reads or writes a workspace-scoped resource now verifies the caller is an admin of that specific workspace (by comparing the token's active workspace to the target resource's workspace). Previously, holding `identity:admin` (which is granted to every workspace owner/admin via role expansion) was sufficient to act on any workspace's resources.
* **List routes pinned to caller workspace.** Admin list endpoints (credentials, federation sources, SSO connections, provision policies, users, sessions, audit log) now pin non-platform-admin callers to their own workspace. A workspace admin who omits the workspace filter sees only their own workspace's data rather than all workspaces. Explicit cross-workspace filter values are rejected for non-platform-admin callers.
* **Credential scope escalation guard.** Creating or updating a credential that carries the `platform:admin` scope now requires the caller to already hold `platform:admin`. This prevents a workspace admin from minting a credential with cross-workspace authority and self-escalating.
* **MFA reset scoped to workspace.** A workspace-bound admin's MFA reset now deletes only the target entity's enrollments within the caller's workspace, not across all workspaces. A `platform:admin` caller can reset across all workspaces.
* **Federation identity creation authorized against credential workspace.** Creating a federation identity mapping now verifies the caller is an admin of the target credential's workspace, preventing cross-tenant identity binding.
* **SSO connection cross-workspace source guard.** Creating an SSO connection now verifies the referenced federation source belongs to the same workspace as the connection, preventing a workspace's login from being bound to another workspace's identity provider.
* **Provision policy workspace enforcement.** Global provision policies (those without a workspace) require `platform:admin` to create, update, or delete - a workspace-bound admin cannot create a global policy that would auto-provision access across tenants.
* **Internal grant workspace binding.** The internal email-change grant route now verifies the caller's token is scoped to the target workspace (or holds `platform:admin`), closing a cross-tenant provider takeover path.
* **Audit log cross-workspace reads restricted.** Only `platform:admin` callers can read audit log entries across workspaces. Workspace-bound admins are restricted to their own workspace's audit entries.

**What you need to do:**

* **No action required for workspace-scoped admin usage.** If your admin credentials operate within a single workspace (the common case), existing integrations continue to work unchanged - the token's workspace already matches the resources being managed.
* **Cross-workspace admin tooling.** If you have automation that manages resources across multiple workspaces using `identity:admin`, those credentials must now also carry the `platform:admin` scope. Contact your platform team to provision this scope on the relevant credentials.
* **Credential creation with `platform:admin`.** If you programmatically create credentials that carry the `platform:admin` scope, the calling credential must itself hold `platform:admin`.

</details>

<details>

<summary>Platform API: Simulation Performance - Per-Run Metric Breakdown (July 2026)</summary>

#### Simulation Performance - Per-Run Metric Breakdown

The simulation performance endpoint now returns a per-run breakdown for each metric instead of pre-computed trend series and pass rates. This moves aggregation to the client, so switching between analysis windows (latest run, last N runs, all runs) is instant with no refetch.

**What changed:**

* **Per-run metric data replaces pre-computed series.** Each metric in the performance response now carries a `per_run` array. Each entry represents one run that exercised the metric (ordered oldest to newest) and includes the run timestamp (`at`), the mean numeric value for that run (`value`, null for non-numeric metrics), and pass/measured counts (`passed`, `measured`).
* **Removed fields.** The previous `current_value`, `series`, `delta`, `pass_rate`, and `accruing` fields on each metric have been removed. Clients should derive current values, deltas, and pass rates from the `per_run` array.
* **Non-numeric metrics included.** A metric appears in the response if it produced a numeric value or a measured verdict in at least one run. Categorical and boolean metrics that carry no numeric value still report pass and measured counts per run, so they have a computable pass rate.
* **Pending-only runs excluded.** Runs where a metric only sat in a pending state (no value and no measured verdict) do not contribute a point to the `per_run` array.

**What you need to do:**

* **Update client-side aggregation.** If you consume the performance endpoint, replace reads of `current_value`, `series`, `delta`, `pass_rate`, and `accruing` with logic that derives those values from the `per_run` array. For example, `current_value` is the `value` of the last entry, `delta` is the difference between the last two entries' values, and `pass_rate` is `sum(passed) / sum(measured)` over your chosen window.
* **No endpoint or permission changes.** The endpoint path, query parameters, and permission requirements are unchanged.

</details>

<details>

<summary>Platform API: Simulation Performance Overview - Aggregated Run Analytics (July 2026)</summary>

#### Simulation Performance Overview - Aggregated Run Analytics

A new endpoint aggregates recent graded simulation runs into a single performance overview, replacing the need to fetch each run individually and aggregate client-side.

**What changed:**

* **New performance endpoint.** `GET /v1/{workspace_id}/simulations/performance` returns an aggregated view of recent case and suite simulation runs in a single request. The response includes an overall pass rate, total evals measured, a count of cases and suites that need attention, and the number of runs analyzed.
* **Per-metric run breakdown.** Each metric tracked across runs is returned with a `per_run` array containing one entry per run that exercised the metric (oldest to newest), including the run timestamp, mean value, and pass/measured counts. Clients derive trend series, current values, deltas, and pass rates from this data.
* **Per-case rollups.** Case-kind runs are rolled up by case, showing pass rate, passed and measured counts, the most recent run ID and timestamp, and per-eval assertion breakdowns. Each assertion includes per-conversation verdicts so you can drill into rationale and cited turns without an additional request.
* **Per-suite rollups.** Suite-kind runs are rolled up by suite, with each suite containing its own per-case breakdowns in the same shape as the top-level case rollups.
* **Needs attention flagging.** Cases and suites with a pass rate below 100% are counted in the `needs_attention_count` field, making it easy to identify which areas require investigation.
* **Configurable analysis window.** The `limit` query parameter controls how many recent runs are included in the analysis (default 15, capped at 50). An optional `service_id` parameter filters to runs for a specific service.

**What you need to do:**

* **To use the performance overview**, send a GET request to `/v1/{workspace_id}/simulations/performance`. The response contains the full aggregated view. No additional per-run fetches are needed.
* **Permissions.** The endpoint requires the Service view permission, consistent with other simulation read operations.

</details>

<details>

<summary>Platform API: Agent Runs - Framework Agent Execution (July 2026)</summary>

#### Agent Runs - Framework Agent Execution

A new pair of endpoints lets you dispatch and poll partner-framework agent runs that execute unmodified against your workspace's world-tools data surface. Two frameworks are supported today: the Claude Agent SDK and the OpenAI Agents SDK. Each run uses your service's authored agent configuration (persona, context graph, version set) and accesses workspace data exclusively through the platform's world-tools surface under the caller's own credential.

**What changed:**

* **New dispatch endpoint.** `POST /v1/{workspace_id}/agent-runs` accepts a service ID, framework selection, user message, and optional timeout, and returns a run ID with status `running` (HTTP 202). The framework executes asynchronously on the server.
* **New poll endpoint.** `GET /v1/{workspace_id}/agent-runs/{run_id}` returns the run's current status (`running`, `succeeded`, `failed`, or `timed_out`), final agent text, token usage, and a normalized trajectory of the framework's native output.
* **Two supported frameworks.** `claude-agent-sdk` runs the Claude Agent SDK autonomously. `openai-agents` runs the OpenAI Agents SDK autonomously. Both frameworks run in their own design - the platform provides configuration, data access, and trajectory normalization at the boundaries.
* **Timeout budget.** Each run accepts a `timeout_s` parameter (1-300 seconds, default 120) that caps the server-side wall-clock time for the entire run. Runs that exceed the budget transition to `timed_out`.
* **Normalized trajectory.** The trajectory in the poll response is a sequence of steps (transcript, tool call, decision, completion, usage) normalized from the framework's native output. The shape is consistent across frameworks.
* **Workspace-scoped authentication.** Both endpoints require a workspace API key. The caller's credential is forwarded to the world-tools surface, so all data access executes under the caller's own workspace permissions.

**What you need to do:**

* **To run a framework agent**, send a POST to `/v1/{workspace_id}/agent-runs` with your service ID, chosen framework, and user message. Poll the returned run ID until status is no longer `running`.
* **Model resolution.** Both frameworks resolve the model from the version set's engage model preference. If the engage model is compatible with the target framework, it is used directly. If the engage model belongs to a different provider family (or is not set), the framework falls back to a platform default - a non-compatible model preference is never forwarded to a framework that cannot use it. No explicit model pin is required on either framework.
* **Version set configuration.** The `version_set` field (default `release`) selects which version set's agent configuration the run uses. Ensure your service has the intended version set configured.

</details>

<details>

<summary>Platform API: Fleet Status - Tool-Runner Fleet Observability (July 2026)</summary>

#### Fleet Status - Tool-Runner Fleet Observability

The fleet status endpoint now supports querying the tool-runner fleet in addition to the voice fleet, giving operators visibility into capacity for both the per-call voice fleet and the isolated background-tool fleet.

**What changed:**

* **New `fleet` query parameter.** The fleet status endpoint (`GET /v1/{workspace_id}/sessions/fleet-status`) now accepts an optional `fleet` query parameter. Pass `fleet=voice` (the default) to read the per-call voice fleet, or `fleet=tool-runner` to read the isolated background-tool fleet. Omitting the parameter returns the voice fleet, so existing callers are unaffected.
* **Same response shape.** Both fleet selections return the same response model - `fleet`, `namespace`, `ready`, `allocated`, `total`, `max_replicas`, `headroom`, and `by_state`. The `fleet` and `namespace` fields in the response echo which fleet was read, so consumers can confirm which fleet's data they received.
* **Tool-runner headroom.** When the tool-runner fleet has a configured capacity ceiling, the `headroom` field reports remaining isolated-unit slots (`max_replicas - allocated`). When the ceiling is not configured, `max_replicas` and `headroom` are null.

**What you need to do:**

* **No action required for existing integrations.** The default behavior is unchanged - calls without the `fleet` parameter continue to return voice fleet status.
* **To monitor tool-runner capacity**, pass `?fleet=tool-runner` to the fleet status endpoint.

</details>

<details>

<summary>Platform API: Cross-Provider Identity Linking for Invited Operators (July 2026)</summary>

#### Cross-Provider Identity Linking for Invited Operators

Operators who accepted a workspace invitation through a magic link or email one-time-password and later attempt to sign in with Google are now automatically linked to their existing identity, instead of receiving a 401 error.

**What changed:**

* **Automatic cross-provider identity linking.** When an invited operator first accepts an invitation through a magic link or email code, their identity is created under the email provider. If they later sign in with Google, the platform now detects the existing email-based identity and creates a linked Google identity for the same operator - so subsequent Google sign-ins resolve immediately without repeating the invitation flow.
* **Trust-gated linking.** The link is only created when Google is authoritative for the operator's mailbox. This means the Google account must either be managed by the email domain's own workspace administration (for corporate domains) or the mailbox must be Google-native (for consumer addresses). A consumer Google account carrying a third-party corporate email address is not linked, because Google's email verification in that case does not prove current inbox control.
* **Conflict detection.** If the operator's entity already has a different Google identity linked, the platform refuses the new link and logs the conflict rather than overwriting the existing binding. An administrator must resolve the conflict manually.
* **Audit logging.** Every cross-provider link emits an audit event recording the linked providers, email, and Google identity subject, so workspace administrators have full visibility into automated identity bindings.

**What you need to do:**

* **No action required.** Operators who previously encountered 401 errors when signing in with Google after accepting an invitation via magic link or email code can now sign in with Google directly. No configuration changes are needed.

</details>

<details>

<summary>Platform API: Source Change Routing - Document Withdrawal and Cursor Persistence (July 2026)</summary>

#### Source Change Routing - Document Withdrawal and Cursor Persistence

The intake system now supports document withdrawal when a connected data source reports a file has been trashed or removed, and persists change cursors so incremental polling resumes from the last committed position.

**What changed:**

* **Document withdrawal.** When a data source's change feed reports a file deletion, the platform tombstones the corresponding document by flipping its status to withdrawn with a timestamp and reason. The withdrawal is idempotent - re-delivered delete events are no-ops. The materialized customer data layer mirrors the withdrawal status.
* **Re-activation on re-upload.** If a previously withdrawn document is re-uploaded or restored from trash, the tombstone is automatically cleared and the document is treated as a new version. Documents move between active and withdrawn states without manual intervention.
* **Cursor persistence on data sources.** Each connected data source now tracks a change cursor, a last-synced timestamp, and a last-sync error. The cursor advances only after a poll's batch of changes is durably applied, so a crash resumes from the last committed position. Errors are recorded for observability without moving the cursor backward.

**What you need to do:**

* **No action required for existing data sources.** Document withdrawal and cursor persistence are handled internally. Existing data source configurations continue to work without changes.

</details>

<details>

<summary>Platform API: Folder-Listing Diff Deletion Fallback for Folder-Only Access (July 2026)</summary>

#### Folder-Listing Diff Deletion Fallback for Folder-Only Access

The intake connector's deletion detection now falls back to a folder-listing diff when the incremental change feed is unavailable, so data sources with folder-only access (no shared-drive membership) can still detect and withdraw removed files.

**What changed:**

* **Automatic fallback.** When the incremental change feed is unavailable (for example, when the service account has folder-level access but is not a member of the shared drive), the connector automatically falls back to comparing the current folder listing against previously recorded files. Removed files are withdrawn using the same tombstone mechanism as the change feed path.
* **Truncation guard.** If any folder listing is truncated by the per-sync file cap, the diff is skipped entirely. An incomplete listing is never treated as authoritative, and the skip is recorded as a sync error for operator visibility.
* **Blast-radius circuit breaker.** If a single sync would withdraw more than 50% of a source's known documents, the withdrawal is held for manual review rather than applied. The hold is recorded as a sync error so operators can investigate before documents are removed. This prevents a transient glitch or misconfiguration from wiping a knowledge base.
* **No baseline required.** Unlike the change feed (which needs a first run to establish a cursor), the folder-listing diff works immediately by comparing the current listing against the platform's recorded file inventory.

**What you need to do:**

* **No action required.** The fallback is automatic. Data sources that previously could not detect deletions due to folder-only access now have deletion detection enabled. If a held withdrawal appears as a sync error, review the source's folder contents before clearing the error.

</details>

<details>

<summary>Platform API: Incremental Change Detection for Drive-Based Data Sources (July 2026)</summary>

#### Incremental Change Detection for Drive-Based Data Sources

The intake connector for drive-based data sources now supports an incremental change feed that detects file creations, modifications, and deletions since a saved cursor. Previously, the connector could only list all files under a folder - it had no way to detect deletions because a removed file simply stops appearing in the listing.

**What changed:**

* **Incremental change feed.** The connector can now poll for changes since a cursor, receiving explicit per-file events for creates, modifications, trashes, and removals. This replaces the need to diff folder listings to detect deleted files.
* **Deletion detection.** Trashed and removed files are surfaced as explicit deletion events. The connector routes these to a withdrawal so downstream systems know the file is gone, without requiring a full folder re-scan.
* **Cursor management.** A baseline cursor can be fetched when a data source is first connected. On each subsequent poll, the connector receives an updated cursor to persist for the next cycle. If no changes have occurred, the previous cursor is returned unchanged.
* **Shared drive scoping.** When the data source is backed by a shared drive, change detection is scoped to that drive so only relevant changes are returned.
* **Pagination safety.** The change feed follows pagination internally with a bounded page limit, consistent with existing listing operations.

**What you need to do:**

* **No action required for existing data sources.** The incremental change feed is used internally by the connector to improve deletion handling. Existing data source configurations continue to work without changes.

</details>

<details>

<summary>Platform API: Epic SMART Backend Services JWKS Endpoint (July 2026)</summary>

#### Epic SMART Backend Services JWKS Endpoint

The connector runner now serves a public JWK Set endpoint for Epic SMART Backend Services integration. Epic fetches this endpoint to verify the client-assertion JWTs that the platform signs when requesting access tokens.

**What changed:**

* **New public endpoint: `GET /connectors/epic/jwks`.** Returns a JWK Set containing the public RSA key used for SMART Backend Services client assertions. The key identifier (`kid`) is the RFC 7638 JWK thumbprint, and the key is marked for RS384 signature use.
* **Signing key provisioned through infrastructure automation.** The RSA signing key is provisioned by infrastructure tooling and injected into the service through secret management. The service requires the key at startup - it will not start without it.
* **Cache-friendly response.** The endpoint returns cache headers allowing intermediaries and Epic to cache the JWK Set, reducing repeated fetches.

**What you need to do:**

* **No action required for existing integrations.** This endpoint is consumed by Epic during the SMART Backend Services token exchange flow. If you are setting up a new Epic integration, register the JWKS URL with your Epic environment as part of the SMART Backend Services configuration.

</details>

<details>

<summary>Platform API: Trigger Run History - Durable Execution Log for Triggers (July 2026)</summary>

#### Trigger Run History - Durable Execution Log for Triggers

Trigger execution history is now served from a durable run queue instead of the entity-event timeline. Each trigger fire produces a run record that tracks status, attempts, and results through its full lifecycle.

**What changed:**

* **New trigger run model.** The `GET /v1/{workspace_id}/triggers/{trigger_id}/runs` endpoint now returns purpose-built execution records instead of entity-event timeline entries. Each run includes an ID, trigger ID, fired event ID, source, status, attempt metadata, result text, and error details.
* **Updated response shape.** The run response fields have changed. Previous fields (`event_id`, `event_type`, `data`, `effective_at`) have been replaced with a richer model: `id`, `workspace_id`, `trigger_id`, `fired_event_id`, `source`, `input_override`, `status`, `attempt_count`, `max_attempts`, `next_attempt_at`, `claimed_by`, `claimed_at`, `lease_expires_at`, `result_text`, `error`, `created_at`, and `updated_at`.
* **Run statuses.** Each run progresses through a lifecycle: `queued`, `running`, `succeeded`, `failed`, or `dead`. Runs that exhaust their retry attempts without succeeding are moved to `dead` status.
* **Run sources.** The `source` field indicates how the trigger was fired: `cron` (scheduled), `manual` (user-initiated), `webhook`, or `event`.
* **Per-run input overrides.** Runs can carry optional per-run input overrides as a JSON object, allowing individual fires to customize the action input without changing the trigger definition.
* **Attempt tracking.** Each run tracks `attempt_count` and `max_attempts`. Runs that fail but have remaining attempts are automatically requeued. Runs that exhaust all attempts are dead-lettered with an error.
* **Idempotent enqueue.** Duplicate fire events for the same workspace and fired event produce only one run record. Retried enqueue attempts return the existing run rather than creating duplicates.

**What you need to do:**

* **Update integrations that consume trigger run history.** If you read trigger run data from `GET /triggers/{trigger_id}/runs`, update your code to use the new response shape. The previous `event_id`, `event_type`, `data`, and `effective_at` fields are no longer returned.
* **No action required for trigger creation or firing.** Existing trigger configurations and fire operations continue to work. The change affects only how execution history is returned.

</details>

<details>

<summary>Platform API: Document Embeddings in Intake Materializer (July 2026)</summary>

#### Document Embeddings in Intake Materializer

The intake materializer now computes document embeddings automatically during document-leg materialization. Previously, the `embedding` column was left empty for document datasets. Embeddings are now generated server-side during the merge step, so document rows are ready for vector search immediately after materialization.

**What changed:**

* **Automatic embedding computation.** When the materializer processes document datasets, it computes a vector embedding for each new or content-changed document as part of the merge operation. The embedding is derived from the document body text (truncated to fit the model's context window) and stored as an array of floats on the document row.
* **Incremental embedding.** Only new or content-changed documents receive an embedding computation. Documents whose content has not changed since the last materialization are skipped entirely - they are neither rewritten nor re-embedded. This keeps re-runs efficient.
* **Configurable embedding model.** The materializer accepts an `embedding_model` parameter that selects the embedding model used for vector computation. The default model produces 1024-dimension vectors suitable for English-language retrieval. Setting the parameter to empty skips embedding computation, leaving the column null - useful for parser-only iterations where you want to materialize document metadata without incurring embedding cost.
* **Input truncation.** Only the embedding input is truncated to fit the model's token window. The full document body is always stored without truncation, so no content is lost.

**What you need to do:**

* **No action required for existing integrations.** If you trigger materialization through the API or Developer Console, document datasets will now include embeddings automatically. To skip embeddings (for example, during a parser-only test run), pass an empty `embedding_model` parameter.
* **Re-materialize existing document datasets.** Previously materialized document datasets have null embeddings. Trigger a new materialization run to backfill embeddings for existing documents.

</details>

<details>

<summary>Platform API: Intake Materializations List Endpoint (July 2026)</summary>

#### Intake Materializations List Endpoint

A new endpoint returns per-dataset materialization status for a workspace, showing which datasets have been materialized into customer data tables and their current row counts.

**What changed:**

* **`GET /v1/{workspace_id}/intake/materializations`.** Lists each registered dataset alongside its customer data destination table status - `materialized` (with row count and last-write timestamp) or `not_materialized` (no table yet). The dataset list mirrors `GET /datasets` (the contracts registry) with the same pagination, sorting, and search parameters.
* **Response fields.** Each item includes `dataset` (string), `ingestion_mode` (string), `status` (`materialized` or `not_materialized`), `row_count` (integer, null when not materialized), and `last_materialized_at` (ISO-8601 timestamp, null when not materialized).
* **Pagination.** Supports `limit` (1-200, default 50), `continuation_token` (offset, default 0), `sort_by` (default `name`), and `search` (optional, max 200 characters). Returns `items`, `has_more`, and `continuation_token` in a standard paginated response envelope.
* **Graceful degradation.** If the destination catalog is unavailable (for example, in local development), all datasets report `not_materialized` rather than returning an error.
* **Auth.** Member read permission. Uses the same rate limiting as other read endpoints.

**What you need to do:**

* **No action required for existing integrations.** This is a new read-only endpoint. Use it to display materialization status in dashboards or to verify that datasets have been materialized after triggering a materialization run.

</details>

<details>

<summary>Platform API: Use Case Ownership Endpoints - Assign, Release, Get, List (July 2026)</summary>

#### Use Case Ownership Endpoints

Use case tenancy is now operator-assigned. A workspace claims ownership of a channel use case through explicit ownership endpoints rather than through the create proxy. The use case create and delete proxies have been retired.

**What changed:**

* **`PUT /v1/{workspace_id}/use-cases/{use_case_id}/ownership`.** Assign ownership of a use case to the current workspace. The use case must exist in the channel service. Idempotent - re-assigning an already-owned use case returns 200. Returns 404 if the use case does not exist, 409 if it is already owned by another workspace. Requires the Channel ManageOwnership permission (admin tier).
* **`DELETE /v1/{workspace_id}/use-cases/{use_case_id}/ownership`.** Release the current workspace's ownership of a use case. Returns 404 if this workspace does not own the use case, 409 if the use case still has an active service binding (unbind first). Requires Channel ManageOwnership permission.
* **`GET /v1/{workspace_id}/use-cases/{use_case_id}/ownership`.** Check whether the current workspace owns a specific use case. Returns the ownership record (use case ID and workspace ID) or 404. Requires Channel view permission.
* **`GET /v1/{workspace_id}/use-cases/ownership`.** List all use case IDs owned by the current workspace. Requires Channel view permission.
* **`POST /use-cases` removed.** The use case create proxy has been retired. Use cases are created directly in the channel service; platform records ownership separately.
* **`DELETE /use-cases/{use_case_id}` removed.** The use case delete proxy has been retired. Use cases are deleted directly in the channel service; release ownership in platform first.
* **New permission: `Channel.ManageOwnership`.** Admin-tier permission required for assign and release operations. Members cannot self-claim use cases.

**What you need to do:**

* **Update integrations that create use cases.** If you previously used `POST /use-cases` on the platform API to create use cases, create them directly in the channel service and then call `PUT /{use_case_id}/ownership` to assign ownership to your workspace.
* **Update integrations that delete use cases.** If you previously used `DELETE /use-cases/{use_case_id}`, release ownership first with `DELETE /{use_case_id}/ownership`, then delete the use case directly in the channel service.
* **Ensure admin credentials for ownership operations.** The assign and release endpoints require admin-tier credentials with the Channel ManageOwnership permission.

</details>

<details>

<summary>Platform API: Intake Materialization Endpoint (July 2026)</summary>

#### Intake Materialization Endpoint

A new endpoint lets you trigger materialization of curated intake snapshot datasets into workspace customer data tables on demand.

**What changed:**

* **`POST /v1/{workspace_id}/intake/materialize`.** Triggers the intake-to-customer-data materializer for the workspace. The materializer rescans the workspace's curated intake files and (re)materializes each snapshot dataset into workspace-scoped customer data tables. Every write is an idempotent keyed merge or full overwrite, so re-triggering is always safe.
* **Optional dataset scoping.** The request body accepts an optional `dataset` field (string). When provided, only the specified dataset is materialized. When omitted, all curated snapshot datasets for the workspace are materialized.
* **Response.** Returns `202 Accepted` with a `run_id` (integer). When the materializer is not configured in the environment, `run_id` is `null` and the call is a no-op.
* **Error handling.** If the materializer job fails to launch, the endpoint returns `502 Bad Gateway` with a descriptive error message.
* **Auth.** Uses the same authentication and rate limiting as `POST /intake/batches/{batch_id}/process`.

**What you need to do:**

* **No action required for existing integrations.** This is a new endpoint. Use it when you need to trigger materialization of curated intake data into customer data tables after upload and processing are complete.

</details>

<details>

<summary>Platform API: Async Tool Lifecycle Signaling - background_pending, tool_started SSE, Drain-First Guidance (July 2026)</summary>

#### Async Tool Lifecycle Signaling

The text conversation turn response now tells you whether the agent's answer is final or whether background work is still running. A new SSE event announces slow tools the instant they start, and the drain-first delivery contract is documented on the turn response.

**What changed:**

* **`background_pending` on turn responses.** `TurnResponse` (from `POST /v1/{workspace_id}/conversations/{conversation_id}/turns`) now includes a `background_pending` boolean field (default `false`). When `true`, the turn's `output` is only an acknowledgement that a tool is still running in the background - the definitive agent answer will arrive later, out-of-band. This field is set from the turn's internal state regardless of whether `include_tool_calls` was requested, so it is always authoritative.
* **`text.tool_started` SSE event.** The workspace event stream (`GET /v1/{workspace_id}/events/stream`) now emits a `text.tool_started` event the instant a slow tool begins executing - before the blocking window elapses. The event carries `conversation_id`, `tool_name`, `call_id` (format `bg:<task_id>`), and `depth`. The `call_id` matches the eventual `text.background_result` event, so a client can collapse the start and result into a single progress card.
* **Drain-first delivery contract documented.** The `background_pending` field description documents three ways to retrieve the final answer for a background-pending turn: re-issue `POST .../turns` with `poll=true` (a no-message drain), send the next user turn, or read the conversation back with `GET .../conversations/{id}`. The workspace event stream mirrors this activity for dashboard-style observers but is not the per-chat delivery path.

**What you need to do:**

* **Check `background_pending` on turn responses.** If your integration consumes turn responses, inspect `background_pending`. When `true`, do not treat `output` as the final answer - poll or send a follow-up turn to retrieve the completed result. Ignoring this field means your client may display a "still working" acknowledgement as if it were the agent's real answer.
* **Subscribe to `text.tool_started` for live progress (optional).** If your client already listens to the workspace SSE stream for `text.background_result`, you can now also listen for `text.tool_started` to show immediate progress when a tool begins. Match start and result events using the shared `call_id`.
* **No breaking changes.** `background_pending` defaults to `false`, preserving existing behavior for turns that complete synchronously.

</details>

<details>

<summary>Platform API: Per-Credential Rate Limiting on MCP Endpoint (July 2026)</summary>

#### Per-Credential Rate Limiting on MCP Endpoint

The MCP data-access endpoint (`/v1/mcp`) now enforces a per-(workspace, credential) sliding-window rate limit, protecting shared compute resources from runaway partner agents or tool-call loops.

**What changed:**

* **Per-credential throughput cap.** Each authenticated credential is allowed up to 120 requests per 60-second window against a given workspace's MCP surface. The limit is keyed on the verified credential, so one partner credential cannot exhaust resources for the entire workspace.
* **Standard rate-limit response.** When the limit is exceeded, the endpoint returns HTTP 429 with `Retry-After`, `X-RateLimit-Limit`, `X-RateLimit-Remaining`, and `X-RateLimit-Reset` headers. Clients should back off for the number of seconds indicated by `Retry-After`.
* **Fail-open behavior.** If the rate-limiting infrastructure is temporarily unavailable, requests are allowed through. The throughput cap never blocks access to the read surface due to infrastructure issues.

**What you need to do:**

* **No action required for well-behaved clients.** The 120 requests/minute limit is generous for normal agent usage (approximately 2 requests per second sustained). If your integration exceeds this rate, implement backoff logic using the `Retry-After` header value from the 429 response.

</details>

<details>

<summary>Platform API: Channel-Generic Conversation Lifecycle API - Start Outbound &#x26; Switch Channel (July 2026)</summary>

#### Channel-Generic Conversation Lifecycle API - Start Outbound & Switch Channel

The conversations endpoint now supports proactively starting outbound conversations on SMS and iMessage, switching an active conversation to a different channel, and per-turn channel attribution in conversation history.

**What changed:**

* **Outbound conversation start.** `POST /v1/{workspace_id}/conversations` accepts a `channel` field, a `recipient` (E.164 phone number), a `use_case_id` (resolves the sending number and routing server-side - never caller-supplied), and an optional `instruction` (steers the agent's opener). The platform dispatches the agent's opening message through the channel and returns the durable conversation. Requires `Conversation:StartOutbound` permission (member tier and above). The service must be owned by the workspace, and the use case must be owned by the workspace and service.
* **Only SMS and iMessage are wired for outbound today.** `channel` accepts `sms` or `imessage`. Any other channel (`email`, `whatsapp`, `voice`) returns `501`. The default `channel=web` remains an inbound create and is unaffected.
* **Channel switching.** `POST /v1/{workspace_id}/conversations/{conversation_id}/channel` re-keys the *same* durable conversation onto a different channel (`sms` or `imessage` today). The `conversation_id`, history, and plan are preserved; the channel, provider, and provider thread ID change. Accepts `recipient`, `use_case_id`, and `reason` (all required), plus optional `dispatch_opener` (default `false` - sends a first agent turn on the new channel) and optional `instruction`. An inbound reply on the new channel re-converges to the same conversation because both platform-api and agent-engine derive the routing key from `(channel, recipient, use_case_id)` via a shared builder (convergence is guaranteed by construction, not by an end-to-end handset test). Requires `Conversation:SwitchChannel` permission (member tier and above). Voice conversations cannot be switched.
* **Error responses.** Both endpoints reject external-user tokens with `403` and callers missing the required permission with `403`. An unsupported channel returns `501`; a missing `recipient` or `use_case_id` returns `422`; a service or use case not owned by the workspace (and, for the create endpoint, the service) returns `404`; and if outbound is not configured, both return `503`. For switching, the conversation being not found, already closed, or not switchable returns `404`; another active conversation already on the same `(channel, recipient)` returns `409`; `dispatch_opener=true` on a conversation with no service returns `422`; and if the opener dispatch itself fails the switch is still persisted and the response is `502`.
* **Per-turn channel attribution.** Each turn in conversation history now includes a `channel` field indicating which channel the turn occurred on, returned by both `GET /v1/{workspace_id}/conversations` and `GET /v1/{workspace_id}/conversations/{conversation_id}`. Conversations that switched channels render each turn on its originating channel. The field may be null on turns written before this feature and on internal turns.
* **New permissions.** `Conversation:StartOutbound` and `Conversation:SwitchChannel` are granted at member tier and above (same tier as `Channel:Send`). Viewer and operator roles do not carry them, and external users cannot start outbound conversations or switch channels.
* **World event.** A `conversation.channel_switched` event is emitted when a conversation changes channels, carrying the new channel and the reason for the switch.
* **Audit logging.** Channel switches are audit-logged with the target channel and reason.

**What you need to do:**

* **No action required for existing integrations.** The default `channel=web` on conversation creation preserves the existing behavior. Per-turn channel attribution adds a new nullable field to turn responses - existing consumers can ignore it.
* **To start outbound conversations,** supply `channel` (`sms` or `imessage`), `recipient`, and `use_case_id` on the create request. Ensure your API key's role has `Conversation:StartOutbound` permission (member tier and above).
* **To switch channels,** call the new `/{conversation_id}/channel` endpoint with the target channel, recipient, use case, and reason, and ensure your role has `Conversation:SwitchChannel` permission.

</details>

<details>

<summary>Platform API: Tool-Dispatch Lifecycle Axis &#x26; Write-Time Guardrails (June 2026)</summary>

#### Tool-Dispatch Lifecycle Axis & Write-Time Guardrails

A tool bound to a context-graph state (`ToolCallSpec`) gains a third orthogonal dispatch axis, **`lifecycle`**, alongside `execution` and `delivery`, and the context-graph version write endpoint now rejects two dead-end dispatch combinations.

**What changed:**

* **New `lifecycle` field: `coupled` | `independent` (default `independent`).** `independent` is the historical fire-and-forget behavior: a background task runs to completion and delivers its result regardless of what the conversation does next. `coupled` ties the task to the conversation's current commitment: if the conversation moves on (a new user turn, or it ends) before the task finishes, its result is cooperatively superseded and dropped instead of folded in late with a stale answer. Honored on the text path; voice keeps its own call-teardown rule. Stored sparse (a binding left at the default omits the field on the wire), so every existing context graph is byte-for-byte unchanged.
* **Write-time guardrails on context-graph version creation.** A tool bound `execution="background"` whose result is `delivery="queue"` on any axis is now rejected because a fire-and-forget result folded into the next turn never proactively reaches the user. A `background` tool in the **terminal state** is also rejected because the conversation ends after it, so there is no future turn to deliver the result into.

**What you need to do:**

* **No action required.** The default (`independent`) preserves existing behavior exactly. Opt a binding into `coupled` only when its result is meaningless once the conversation has moved on (for example, a side lookup that only informs the current turn). The OpenAPI spec exposes the new field, and `@amigo-ai/platform-sdk` (≥ v0.81.0) carries the type.

</details>

<details>

<summary>v0.9.505 - Platform API: Remove Channels API (SES Setup + Email Templates) (July 2026)</summary>

#### Remove Channels API (SES Setup + Email Templates)

The `/v1/{workspace_id}/channels` endpoint group has been removed. SES setup management and email template CRUD are no longer available through the Platform API.

**What changed:**

* **SES setup endpoints removed.** The following endpoints are no longer available:
  * `POST /v1/{workspace_id}/channels/ses-setup` (create)
  * `GET /v1/{workspace_id}/channels/ses-setup/{id}` (get with DNS refresh)
  * `POST /v1/{workspace_id}/channels/ses-setup/{id}/verify` (DNS verification)
  * `DELETE /v1/{workspace_id}/channels/ses-setup/{id}` (delete)
* **Email template endpoints removed.** The following endpoints are no longer available:
  * `POST /v1/{workspace_id}/channels/email/templates` (create)
  * `GET /v1/{workspace_id}/channels/email/templates` (list by use case)
  * `GET /v1/{workspace_id}/channels/email/templates/{id}` (get)
  * `PUT /v1/{workspace_id}/channels/email/templates/{id}` (update)
  * `DELETE /v1/{workspace_id}/channels/email/templates/{id}` (delete)
* **OpenAPI spec updated.** All channel-related paths, request models, and response models have been removed from the published OpenAPI specification.

**What you need to do:**

* **Remove any integrations using these endpoints.** If you have automation or tooling that calls the channels SES setup or email template endpoints, remove those calls. Requests to the removed paths will return 404.

</details>

<details>

<summary>v0.9.504 - Platform API: OAuth2 Client Last-Used Tracking (July 2026)</summary>

#### OAuth2 Client Last-Used Tracking

OAuth2 clients now record when they were last used to mint a token, giving workspace admins visibility into client activity without querying logs.

**What changed:**

* **`last_used_at` tracked on token exchange.** Every successful `client_credentials` token exchange now stamps the OAuth2 client with the current timestamp. The field is null until the client's first successful token request and is updated on every subsequent exchange.
* **Visible on the client record.** The `last_used_at` timestamp is available on the OAuth2 client object, so workspace admins can identify stale or unused clients.
* **No change to error behavior.** Failed authentication attempts (invalid client ID, wrong secret) do not update the timestamp.

**What you need to do:**

* **No action required.** The field is populated automatically on the next successful token exchange for each client. Existing clients will show `null` until they are next used.

</details>

<details>

<summary>v0.9.503 - Platform API: OAuth2 Scope Documentation on Channel Manager Routes (July 2026)</summary>

#### OAuth2 Scope Documentation on Channel Manager Routes

Every gated Channel Manager route now documents the exact OAuth2 scope it requires, making it straightforward to configure least-privilege OAuth2 clients.

**What changed:**

* **Required scope documented per route.** Each Channel Manager API route that enforces OAuth2 authorization now states the required scope in its description. This covers all channel operations (email, SMS, iMessage, ringless voicemail, outbound voice), setup management (Twilio, SendBlue, SES), phone number operations, compliance submissions (A2P, toll-free, CNAM, SHAKEN/STIR, regulatory bundles), use case management, and OAuth2 client administration.
* **Setup-scoped vs. resource-less scopes clarified.** Routes that check authorization against a specific setup state which setup the scope applies to (for example, the setup identified by a path parameter or query parameter). Routes that are not tied to a specific setup (such as creating a new setup or managing OAuth2 clients) note that they require a resource-less grant - only an exact-name grant satisfies them, not a wildcard scope.
* **Exact-name-only scopes identified.** Sensitive scopes such as `twilio-setup:credentials`, `twilio-setup:access-token`, and `clients:admin` are marked as exact-name grants that are not matched by wildcard scope patterns.
* **Bundle listing scope behavior documented.** The bundle listing route notes that it checks multiple compliance scopes and filters response content by which scopes the caller holds, rather than returning a blanket 403.

**What you need to do:**

* **No action required for existing integrations.** This is a documentation-only change. No authorization behavior has changed.
* **Review your OAuth2 client grants.** Use the per-route scope documentation to verify that your OAuth2 clients carry only the scopes they need. The scope tables in the Authentication guide list every scope with its access level and description.

</details>

<details>

<summary>v0.9.502 - Platform API: Production Eval Verdicts (July 2026)</summary>

#### Production Eval Verdicts

The same eval framework used for simulation testing is now available for production calls. Workspaces can define eval criteria that run against live completed calls and retrieve per-call verdicts with pass/fail status, rationale, and cited conversation turns.

**What changed:**

* **Production eval definitions.** A new set of CRUD endpoints lets you create, list, get, update, and delete eval definitions that apply to your workspace's live calls. Each definition specifies an eval type (assertion or metric), an eval key, and an expected outcome. Definitions can be scoped to a specific service or applied workspace-wide. Service-scoped definitions override workspace-wide definitions with the same key when evaluating calls for that service.
* **On-demand call evaluation.** A new `POST /v1/{workspace_id}/calls/{conversation_id}/evaluate` endpoint runs all active eval definitions against one completed call and persists the verdicts. The endpoint is synchronous - the caller waits while the judge processes. Deterministic assertions (transcript contains, tool called, final state) resolve instantly; AI judge assertions and metric evals invoke an AI model. Each eval that encounters an error produces its own error verdict rather than failing the entire evaluation.
* **Verdict retrieval.** A new `GET /v1/{workspace_id}/calls/{conversation_id}/eval-results` endpoint returns all persisted verdicts for a call. Each verdict includes status (passed, failed, pending, skipped, or error), optional numeric score, rationale, justification (for AI-evaluated metrics), and cited turn indices.
* **Idempotent re-evaluation.** Re-evaluating a call overwrites prior verdicts for the same eval keys rather than creating duplicates.
* **Shared eval semantics.** Production eval verdicts use the same judge, transcript format, assertion evaluators, and metric threshold comparisons as simulation evals. A verdict means the same thing whether it came from a simulation or a live call.
* **Assertion kinds supported.** `transcript_contains` / `must_contain`, `transcript_not_contains` / `must_not_contain`, `tool_called`, `final_state`, and `llm_judge` (the default for unrecognized kinds).
* **Metric eval thresholds.** Metric evals support `gte`/`min`, `lte`/`max`, `equals`, and `contains` comparisons, as well as bare value equality and no-threshold (value-only) mode.

**Permissions:**

| Operation                          | Required Permission |
| ---------------------------------- | ------------------- |
| List, get definitions              | Service view        |
| Create, update, delete definitions | Service update      |
| Evaluate a call                    | Service update      |
| Get eval results                   | Service view        |

**What you need to do:**

* **No action required for existing integrations.** Production evals are opt-in. No existing behavior is changed.
* **To get started:** Create one or more production eval definitions for your workspace, then call the evaluate endpoint on any completed call to see verdicts.

</details>

<details>

<summary>v0.9.501 - Platform API: Slot Provider Display Name Resolution (July 2026)</summary>

#### Slot Provider Display Name Resolution

Scheduling slots now automatically resolve the provider's display name from the practitioner projection, matching the existing behavior for appointment participant displays.

**What changed:**

* **Provider display resolved on slots.** When a scheduling slot carries a provider identifier, the platform now resolves it against the practitioner projection to populate the provider display name on the entity snapshot. Previously, slots that did not carry an explicit provider name from the source connector showed a blank provider display.
* **Connector-supplied names preserved.** If the source connector already provides a provider name (for example, through a vendor-specific extension), that value takes priority. The resolved name is used only when the connector-supplied display is empty or missing.
* **Provider identifier fallback for legacy data.** Slots from connectors that historically emitted the provider identifier under a different extension key are now resolved correctly without waiting for a re-sync. The projection reads from both the current and legacy extension keys, preferring the current one.
* **Shared resolution logic.** The display resolution logic for both appointment participants and slot providers is now consolidated, ensuring consistent behavior across entity types.

**What you need to do:**

* **No action required.** Slot entities in the world model will automatically show resolved provider display names on their next projection refresh. Existing slots with connector-supplied provider names are unaffected.

</details>

<details>

<summary>v0.9.500 - Platform API: Per-Tenant Intake Upload Isolation by Default (July 2026)</summary>

#### Per-Tenant Intake Upload Isolation by Default

Intake uploads are now routed to isolated per-tenant storage by default.

**What changed:**

* **Per-tenant isolation is now the default.** All workspaces route intake uploads to isolated per-tenant storage by default. Previously, per-tenant isolation had to be enabled platform-wide.
* **Dynamic exclusions.** Workspaces that still depend on the legacy shared storage layout are excluded through a per-upload evaluation rather than a static configuration list, so exclusions can be updated without a service redeploy.
* **No change to reads.** Read operations resolve each file from where it was stored, so files uploaded under either layout remain readable.

**What you need to do:**

* **No action required for most integrations.** Upload and download behavior is unchanged from the caller's perspective.
* **If you manage tenant exclusions:** Contact your platform administrator to update exclusion rules.

</details>

<details>

<summary>v0.9.499 - Platform API: Extended Zoom Bot Session Duration and Optional Duration Limits (July 2026)</summary>

#### Extended Zoom Bot Session Duration and Optional Duration Limits

Zoom meeting bot sessions now support up to four hours by default, and the maximum duration limit is now optional rather than required.

**What changed:**

* **Default session duration extended to four hours.** Zoom bot sessions now default to a four-hour maximum duration, up from the previous 30-minute default. This supports longer meetings such as all-hands, training sessions, and extended clinical consultations without requiring callers to specify a custom duration.
* **Maximum duration is now optional.** The `maxDurationSeconds` field on bot creation requests is now optional. When omitted, sessions run without a fixed time limit - the bot remains active until the meeting ends or the session is explicitly stopped. When provided, the value is validated against the platform's internal maximum.
* **Bot response schema updated.** The `maxDurationSeconds` field in the bot response model is now nullable. Bots created without a duration limit return `null` for this field instead of a default value.
* **Simplified duration validation.** Session duration validation no longer references separate configurable maximum and production maximum settings. Duration requests are validated against a single internal maximum (four hours). Requests exceeding this limit or exceeding the speech-to-text provider's session limit are rejected.

**What you need to do:**

* **No action required for most integrations.** Existing integrations that specify `maxDurationSeconds` continue to work unchanged. The extended default benefits integrations that previously relied on the default duration.
* **If you parse the bot response:** The `maxDurationSeconds` field can now be `null`. Update any client code that assumes this field is always present as an integer.
* **If you set custom durations:** The maximum allowed value is now four hours (14,400 seconds). Requests exceeding this limit will be rejected.

</details>

<details>

<summary>v0.9.498 - Platform API: PHI/PII Scrubbed from Channel Manager Logs (July 2026)</summary>

#### PHI/PII Scrubbed from Channel Manager Logs

Webhook and send logs in the channel manager no longer include protected health information (PHI) or personally identifiable information (PII) such as phone numbers, email addresses, or message content.

**What changed:**

* **Phone numbers removed from send logs.** Outbound SMS send operations no longer log the recipient phone number.
* **Webhook payloads no longer logged verbatim.** Inbound SMS, iMessage, and email webhook handlers no longer log raw request bodies or form parameters that may contain message content or contact information. Inbound SMS webhooks now log only the parameter key names present in the request, not their values.
* **Phone numbers removed from iMessage webhook logs.** Inbound and delivery event logs for iMessage no longer include sender or recipient phone numbers. Authentication gate context no longer carries phone numbers.
* **Email addresses removed from inbound email logs.** Inbound email webhook logs no longer include sender addresses, recipient addresses, or mail-from values in warning and drop messages (DMARC failures, spam filtering, sender mismatches, missing headers, and unresolved recipients).
* **Unparseable webhook payloads logged only on failure.** For iMessage webhooks, raw payload bodies are now logged only when the payload cannot be parsed, rather than on every request. This limits exposure of message content in logs to cases that require debugging.

**What you need to do:**

* **No action required.** This is a security hardening change. No API behavior, request formats, or response formats have changed. If you rely on platform logs for debugging webhook issues, note that contact details and message content are no longer present in log entries - use conversation records and message detail endpoints instead.

</details>

<details>

<summary>v0.9.497 - Platform API: Semantic Memory Consolidation (July 2026)</summary>

#### Semantic Memory Consolidation

The memory system now includes a semantic consolidation layer that integrates per-conversation episodic observations into a durable, trajectory-aware patient model. Instead of relying solely on the latest observation per dimension, the platform synthesizes longitudinal patterns across conversations into a single integrated narrative the agent loads and reasons from.

**What changed:**

* **Trajectory-aware patient models.** The platform now runs a nightly consolidation job that reads recent episodic observations for each patient and produces an integrated semantic model capturing trajectories - rising anxiety across multiple contacts, shifting motivation, eroding engagement - not just the latest data point. This is the information that no single conversation reveals.
* **Bounded input, constant cost.** Consolidation uses a bounded window: the patient's current model plus a configurable number of recent observations per dimension. A patient with five years of history costs the same to consolidate as one with five weeks. There is no unbounded history scan.
* **Mandatory lineage.** Every consolidated model cites the specific observation IDs it was synthesized from. The chain from the model back to source conversations is always traceable - "why does the system believe this?" resolves to specific observations.
* **Safety signal preservation.** Safety-relevant signals (hopelessness, self-harm, suicidal ideation, crisis) are never lost during consolidation. Any safety signal present in the observation window is carried into the consolidated model explicitly and faithfully, with high confidence.
* **Incremental processing.** Only patients with new episodic observations since their last consolidation are processed. Successfully consolidated patients advance a per-entity watermark; failed patients retain their previous watermark and retry on the next run.
* **Error isolation.** Individual patient consolidation failures do not block other patients. Failed entities are recorded in a queryable error log and retry automatically on the next run. If all entities in a run fail (indicating a systemic issue), the run raises an alert.
* **Standard data path.** Consolidated models flow through the same enrichment path as episodic observations, so the agent receives the integrated model through its existing loading mechanism with newest-wins resolution.

**What you need to do:**

* **No action required.** Semantic consolidation runs automatically. Patients with conversation history will progressively receive integrated models that surface longitudinal patterns. The agent will load the consolidated model alongside existing enrichment data without configuration changes.
* **If you consume memory observations downstream:** Consolidated observations carry `layer` information distinguishing them from per-conversation episodic observations. The consolidated model includes lineage references back to its source observations.

</details>

<details>

<summary>v0.9.496 - Platform API: Per-Client Rate Limiting on Channel Manager Authenticated Routes (July 2026)</summary>

#### Per-Client Rate Limiting on Channel Manager Authenticated Routes

All authenticated Channel Manager API routes now enforce per-client sliding-window rate limits. Each OAuth2 client is rate-limited independently per route, preventing any single integration from monopolizing API capacity.

**What changed:**

* **Per-client rate limits on all authenticated routes.** Every authenticated Channel Manager endpoint now enforces a per-client request cap over a sliding time window. Rate limits are scoped to the OAuth2 client ID and the specific route, so one client's traffic does not count against another client's allowance, and limits on one endpoint do not affect other endpoints.
* **429 responses with Retry-After header.** When a client exceeds its rate limit, the API returns HTTP 429 (Too Many Requests) with a `Retry-After` header indicating how many seconds the client should wait before retrying. The `Retry-After` value reflects the actual time remaining in the current window.
* **Fail-open on limiter unavailability.** If the rate limiting backing store is temporarily unreachable, requests are allowed through rather than rejected. A limiter outage never takes down the API - it degrades gracefully by admitting all traffic until the store recovers.
* **Rate limit tiers by route category.** Limits are calibrated per route based on expected usage patterns:
  * **High-throughput read endpoints** (get message, get email, get attachment, get setup status, get credentials, get campaign, get verification, get brand registration): 600 requests per minute per client
  * **Send endpoints** (send email, send SMS, send iMessage): 300-600 requests per minute per client
  * **List and search endpoints** (list messages, list emails, list events, list templates, list phone numbers, list bundles, list voicemails): 240 requests per minute per client
  * **Outbound voice phone number selection**: 1,200 requests per minute per client
  * **Moderate-throughput endpoints** (send ringless voicemail, mint access token, send opt-in, opt-out removal, email template writes, get regulation, list available numbers): 60-120 requests per minute per client
  * **Setup and use case management** (create/update/delete setup, create/delete use case, phone number provisioning, compliance submissions, phone number assignment): 10-30 requests per minute per client
  * **OAuth2 client management** (create, update, delete, rotate secret): 10 requests per minute per client

**What you need to do:**

* **No action required for typical usage.** The rate limits are set well above normal integration traffic patterns. Most integrations will not be affected.
* **If you run high-volume batch operations:** Review your send and list call volumes against the limits above. If you anticipate exceeding the per-minute caps, implement client-side throttling or spread requests across a longer window.
* **Handle 429 responses.** If your integration does not already handle HTTP 429, add retry logic that respects the `Retry-After` header. This is standard HTTP practice and ensures your integration recovers gracefully from rate limit events.

</details>

<details>

<summary>v0.9.495 - Platform API: Appointment Participant Resolution and Scheduled Rehydration (July 2026)</summary>

#### Appointment Participant Resolution and Scheduled Rehydration

EHR-connected appointments now resolve participants by client vs non-client classification, correctly handle group and couples sessions, and automatically rehydrate upcoming appointment detail on a scheduled cadence.

**What changed:**

* **Client vs non-client participant mapping.** Appointments synced from connected clinical systems now distinguish between client appointments (patient sessions) and non-client appointments (administrative, personal, or front-desk blocks). Patient references are attached only to client appointments. Non-client appointments carry practitioner and location participants but no patient reference, preventing phantom patient-less appointments from appearing in scheduling views and patient timelines.
* **Group and couples session support.** Appointments with multiple attendees (group therapy, couples sessions) now resolve all attending patients as participants rather than only the primary patient. The primary patient and all additional attendees are included in the appointment's participant list, with deduplication so no patient is referenced twice. Attendees explicitly excluded from a specific session are omitted.
* **Practitioner reference accuracy.** Practitioner references on appointments now use the same identifier space as the staff roster, so practitioner participants resolve correctly to their roster entities. Previously, a different grouping identifier was used that did not match any roster entry.
* **Scheduled upcoming appointment rehydration.** The platform now periodically re-fetches full detail for all appointments in the upcoming window (the next several days). The incremental sync only enriches appointments that have changed, so stable upcoming appointments previously carried incomplete data (missing modality, service type, and practitioner resolution). The scheduled rehydration ensures that all upcoming appointments carry full detail - including practitioner, patient, location, and service type - regardless of whether they changed recently.
* **Scheduled contact rehydration.** Patient contact information (email, phone) is now periodically re-synced for the full patient roster. Previously, contact-only edits on stable patient records were not picked up by the incremental sync. The scheduled rehydration ensures that contact changes are reflected without waiting for other patient data to change.

**What you need to do:**

* **No action required.** The changes are automatic. Upcoming appointments will progressively gain full participant and service detail through the scheduled rehydration. Patient contact information will stay current through the roster rehydration. Non-client appointments will no longer produce patient references.
* **If you filter appointments by patient participant:** Appointments that previously carried a spurious patient reference (non-client blocks, admin time) will no longer have one. If your integration relied on every appointment having a patient participant, update your filtering to handle appointments with only practitioner and location participants.

</details>

<details>

<summary>v0.9.494 - Platform API: Mid-Session Patient Binding for Voice and Text (July 2026)</summary>

#### Mid-Session Patient Binding for Voice and Text

Patients resolved during a conversation - not just at session start - are now bound to the session so the session-ended event carries the correct patient entity. This restores memory and continuity for returning patients identified mid-call.

**What changed:**

* **Patient lookup binding.** When a patient lookup returns exactly one match (a sole strict match by name and date of birth), the matched patient is now bound to the session as the primary entity. Previously, only patients created during a call or resolved by phone at session start were bound. This caused returning patients - the common case - to be silently dropped from the session-ended event, which meant downstream memory extraction and continuity loading never received their data.
* **Text session binding.** Text conversations now participate in the same mid-session patient binding as voice calls. A patient resolved by creation or sole-match lookup during a text chat is bound to the session and included in the session-ended event.
* **First resolution wins.** The session binds the first patient resolution only. Ambiguous multi-match lookups (where more than one patient matches) are not bound, avoiding incorrect entity associations when duplicate records exist.
* **Session-ended event accuracy.** The session-ended event now carries the patient entity resolved during the conversation (via any resolution path) rather than relying solely on the entity snapshot captured at session creation time. This applies to both voice and text channels.

**What you need to do:**

* **No action required.** The change is automatic. Sessions that previously emitted session-ended events without a patient link (because the patient was identified mid-call rather than at session start) will now include the correct patient entity. Memory extraction and continuity loading for returning patients will resume without configuration changes.

</details>

<details>

<summary>v0.9.493 - Platform API: Enriched Eval Transcripts and Rationale on All Eval Kinds (July 2026)</summary>

#### Enriched Eval Transcripts and Rationale on All Eval Kinds

Simulation eval results now include rationale and turn references on every eval kind, and the transcript fed to AI judges and metrics has been enriched with tool call arguments, results, available tools, empathy tier, and terminal markers.

**What changed:**

* **Rationale and references on all assertion kinds.** Deterministic assertions (transcript contains, tool called, final state) now produce a human-readable rationale and cite the turn index that supports the verdict. Previously, only AI judge assertions and justified metric evals returned rationale and references. For example, a transcript-contains assertion that passes returns a rationale like "Found 'appointment confirmed' in turn 5" with `references: [5]`. A tool-called assertion that fails returns "Tool schedule\_appointment was never called. Tools used: lookup\_patient, check\_availability." with an empty references list.
* **AI judge assertions now return turn references.** The AI judge prompt now asks the model to return a `references` list of turn indices alongside `passed`, `score`, and `rationale`. References are validated to be in-range turn indices; out-of-range or non-integer entries are dropped.
* **Enriched transcript for judges and metrics.** The indexed transcript now includes tool call arguments and results (size-capped per field), available tools per turn, empathy tier, and a terminal marker on the last turn. This gives AI judges and metrics visibility into tool correctness, available-but-unused tools, emotional tone, and where the conversation ended - not just utterances.
* **Case content in metric prompts.** When a simulation run includes case content (the scenario the conversation was run against), that context is now passed into justified metric prompts. Goal-oriented metrics can score against what the simulated patient was trying to do, not just the raw exchange.

**What you need to do:**

* **No action required.** Existing eval definitions continue to work. Rationale and references are populated automatically on all new eval results. Clients that read eval results will see the new `rationale` and `references` fields populated where they were previously empty or absent.
* **If you display eval results:** Consider surfacing the rationale and references fields for deterministic assertions - they now provide the same explanatory detail that was previously available only for AI-evaluated metrics.

</details>

<details>

<summary>v0.9.492 - Platform API: Batch Document Extraction and Processing Manifest (July 2026)</summary>

#### Batch Document Extraction and Processing Manifest

Document batch processing now dispatches a single extraction run per batch instead of one run per file. This eliminates gateway timeouts that occurred when starting processing on large document batches.

**What changed:**

* **Single extraction run per batch.** When you call Start Processing on a document batch, the platform now fires one extraction job run that processes all received files in that batch. Previously, each file triggered its own run, which serialized on the request path and caused 504 gateway timeouts for batches with many files.
* **Processing manifest endpoint.** A new endpoint returns the per-file extraction parameters for all still-received files in a batch. The extraction job fetches this manifest at the start of its run and processes each file in sequence. The manifest includes per-file metadata (file type, content type, hash, size) and dataset-wide extraction configuration from the contract. Filenames are deliberately excluded from the manifest to keep PHI off the wire.
* **Resumable and idempotent.** Start Processing now accepts batches in both `ready` and `processing` status, so you can re-run it to pick up files that were not dispatched on a previous attempt. Terminal batches return a 409. If no received files remain, the endpoint returns the current batch state without dispatching.
* **Snapshot and CSV batches unchanged.** Snapshot and CSV files still process in version order with chained write-backs. Only document batches use the new single-run path.
* **Per-file fault isolation in batch runs.** If one file in a batch fails extraction, the remaining files continue processing. A best-effort failed verdict is recorded for the errored file so the batch can still roll up to a terminal state.

**What you need to do:**

* **No action required.** The change is transparent to API consumers. The Start Processing endpoint accepts the same request shape and returns the same response. Large document batches that previously timed out should now complete successfully.
* **If you poll batch status:** No changes needed. The batch still transitions through `processing` to `completed` or `failed` as each file reaches a terminal verdict.

</details>

<details>

<summary>v0.9.491 - Platform API: Persisted Background Tool Completions (July 2026)</summary>

#### Persisted Background Tool Completions

Background tool completions are now persisted as structured tool-call records and correlated to the original dispatch by a stable task-based key. Previously, a background tool's result was surfaced only as a live streaming card and a prose system message - if you re-read the conversation later, the tool call appeared stuck at "running" with no recorded outcome.

**What changed:**

* **Structured persistence.** When a background tool completes (success or failure), the platform writes a durable tool-call record containing the tool name, result, success status, execution duration, and an optional error message. This record appears alongside other tool calls in conversation history.
* **Stable correlation key.** The completion record, the live streaming card, and the background-result event all share the same identifier derived from the dispatch's task ID. A reader can pair the completion to the original "running" dispatch entry by that shared key without parsing prose.
* **Dispatch placeholder preserved.** The original "running" dispatch entry is intentionally left in place. The correlation model is pairing, not suppression - both the dispatch and its completion are visible in the tool-call timeline.
* **Both re-entry paths covered.** Completions are persisted whether they arrive during an active streaming session or are drained at the start of the next REST request.

**What you need to do:**

* **No action required.** Background tool completions are now automatically persisted. Existing conversations with in-flight background tools will record completions going forward. Previously completed background tools that were only surfaced as prose system messages are not retroactively backfilled.

</details>

<details>

<summary>v0.9.490 - Platform API: EHR Write-Back Endpoint for Patient Creation and Appointment Booking (July 2026)</summary>

#### EHR Write-Back Endpoint for Patient Creation and Appointment Booking

The connector runner now exposes a synchronous, authenticated HTTP surface for writing patient and appointment data back to the connected EHR. This endpoint is designed for upstream automation agents that need to create patients, book initial consultations, reschedule appointments, and cancel appointments - with safety guarantees against duplicate writes and double-bookings.

**What changed:**

* **Synchronous EHR write-back endpoint.** Three new operations are available: book (create patient + book appointment), reschedule, and cancel. Each operation accepts a structured request body and returns a typed response indicating the outcome.
* **HMAC-SHA256 authentication.** Every request must include an HMAC-SHA256 signature over the raw request body, using the same signature scheme as existing webhook integrations. The signing secret is bound to exactly one workspace - requests targeting a different workspace are rejected.
* **Idempotency keys.** Every write request requires a caller-supplied idempotency key. The platform claims the key before the first external write, stores the terminal response, and returns the cached result on retries. Concurrent duplicate requests see an in-flight status instead of triggering duplicate writes.
* **Partial failure recovery.** If patient creation succeeds but the subsequent appointment booking fails, the response includes the new patient ID with a partial status. Retrying with the same idempotency key skips patient creation and completes only the booking, preventing duplicate patients.
* **Live calendar conflict checks.** Before every booking or reschedule, the endpoint performs a live re-check of the provider's calendar at the requested time. If the provider is already booked, the request is refused with a re-offer status rather than creating a double-booking.
* **Office room selection.** For reschedules that involve an in-person modality, the endpoint automatically selects a free physical office at the new time. If no office is available, telehealth fallback or re-offer behavior applies based on the requested modality.
* **Cohort assignment on patient creation.** New patients can be assigned to one or more cohorts at creation time. Assigning a cohort that carries an intake packet triggers the EHR to email that packet automatically as a side effect of creation.
* **Gated writes.** Real EHR mutations are gated behind a safety flag. Until the flag is enabled (pending compliance sign-off), the endpoint operates in dry-run mode - it runs all validation and conflict checks and reports what would be written, without mutating the EHR. Callers can also request dry-run mode explicitly.
* **Audit events.** Every completed write (book, reschedule, cancel) emits a durable audit event for compliance tracking.
* **Improved telehealth detection for appointments.** When a physical room is explicitly specified on an appointment, the room is now authoritative for determining telehealth status. The telehealth flag is set based on whether the room is the telehealth room. Free-text and service code fallbacks apply only when no room is specified. This prevents in-person bookings from being incorrectly flagged as telehealth when they share a service code with telehealth appointments.

**What you need to do:**

* **No action required for existing integrations.** The async event-driven write-back path is unchanged. New-patient create-then-book flows that require idempotency guarantees should use the new synchronous endpoint instead of the async path.
* **Configure credentials for the new endpoint.** If you plan to use the synchronous write-back surface, provision the HMAC signing secret and workspace binding through your deployment configuration.

</details>

<details>

<summary>v0.9.489 - Platform API: Stable Document Resolution for Connector Syncs (July 2026)</summary>

#### Stable Document Resolution for Connector Syncs

Connector-synced files are now resolved to existing documents using a stable external identity rather than filenames. This prevents duplicate documents when the same file is synced repeatedly and correctly handles file renames between syncs.

**What changed:**

* **Identity-based document resolution.** Each connector-synced file now carries its source system's permanent file identifier. On each sync, the platform looks up whether a document already exists for that identity. If found, the file is ingested as a new version of the existing document. If not found, a new document is created at version 1 with the external identity recorded.
* **No duplicate documents on re-sync.** Previously, connector syncs could create duplicate documents if the same external file appeared in multiple sync cycles. The platform now enforces uniqueness on the combination of workspace, dataset, source type, and external file identifier, so each external file maps to exactly one document.
* **Renamed files matched correctly.** Because resolution is based on the source system's stable identifier rather than the filename, a file that is renamed in the source system is still matched to its existing document. The filename is updated as display metadata without creating a new document.
* **Provenance fields on file list.** The Files list response now includes `source_type` and `source_file_id` on each file row, showing the origin and external identifier of the document the file belongs to. These fields are null for manual uploads and snapshot/CSV files.
* **Manual uploads unchanged.** Files uploaded through the console continue to use the explicit document selection model. No external identity is recorded for manual uploads.

**What you need to do:**

* **No action required for existing documents.** Existing documents created before this change default to `manual` source type with no external identifier. They continue to work as before.
* **Update API consumers that read file list responses.** If you parse the file list response, two new optional fields (`source_type` and `source_file_id`) are now present on each file row. These fields are nullable and do not affect existing integrations.

</details>

<details>

<summary>v0.9.488 - Platform API: Recursive Subfolder Discovery for Drive Intake Sources (July 2026)</summary>

#### Recursive Subfolder Discovery for Drive Intake Sources

Drive-based intake sources now discover files across the entire folder tree under each mapped folder, not just the top-level folder. The per-sync file cap has been increased to accommodate larger folder structures.

**What changed:**

* **Recursive file discovery.** When a source folder is synced, the platform now walks the entire subtree under the configured folder. Files in nested subfolders are discovered alongside files in the root folder. One mapped folder corresponds to one dataset, and the full tree is included in the discovery batch.
* **Subfolder traversal, not ingestion.** Subfolders are traversed to find files but are not themselves treated as files. Only non-folder items appear in the resulting batch.
* **Cycle protection.** The traversal detects and skips folders that have already been visited, preventing infinite loops caused by shortcuts or circular folder structures.
* **Increased per-sync file cap.** The maximum number of files included in a single sync has been raised from 500 to 1,000 to accommodate deeper folder trees. Files beyond this cap are still logged and skipped rather than silently dropped.

**What you need to do:**

* **Review your mapped folders.** If your source folders contain subfolders, files in those subfolders will now be included in sync batches automatically. Ensure that nested content is appropriate for your dataset.
* **Check file counts.** If your folder tree contains more than 1,000 files, only the first 1,000 are included per sync. Contact support if you need a higher cap or plan to onboard larger folder trees.

</details>

<details>

<summary>v0.9.487 - Platform API: OAuth2 Scope Enforcement on Channel Manager Setup and Client Routes (July 2026)</summary>

#### OAuth2 Scope Enforcement on Channel Manager Setup and Client Routes

The channel manager now enforces OAuth2 scopes on all setup creation routes (email, SMS, and iMessage) and all OAuth2 client management routes (create, update, delete, and secret rotation). Previously, these routes did not require scope authorization. Now, each request must carry a bearer token with the appropriate scope.

**What changed:**

* **Setup creation routes.** Creating a new email setup, SMS setup, or iMessage setup now requires the corresponding setup-create scope on the bearer token. Requests without the required scope receive a 403 response.
* **OAuth2 client management routes.** Creating, updating, deleting, and rotating the secret of an OAuth2 client now require the client administration scope. Requests without the required scope receive a 403 response.
* **Consistent authorization model.** These routes now follow the same scope enforcement pattern used by the read, update, and delete routes added in the previous release, completing scope coverage across the channel manager API surface.

**What you need to do:**

* **Ensure your OAuth2 tokens include the required scopes.** If you create channel setups or manage OAuth2 clients through the channel manager API, your bearer token must carry the appropriate scope for the operation.
* **Handle 403 responses.** API clients should be prepared to receive 403 responses when the token does not grant the required scope for setup creation or client management operations.

</details>

<details>

<summary>v0.9.486 - Platform API: Per-Call Isolation Attach Authorization (July 2026)</summary>

#### Per-Call Isolation Attach Authorization

The per-call media isolation path now enforces that the media transport attaches to the same isolated server that was allocated for the call. This closes an authorization gap where a valid assignment could be used to attach media to a different server than the one the platform assigned.

**What changed:**

* **Route pinning on allocation.** When the platform allocates an isolated server for a call, the allocated route identity is written back onto the call's runtime assignment. This binding lets the attach step verify that the incoming media stream reached the correct server.
* **Attach-time route validation.** When a media stream connects to an isolated server, the platform now checks that the stream's route identity matches the route pinned on the assignment. Streams that reach a different server than the one assigned are rejected.
* **Conflict detection.** If an assignment is already pinned to a different route (indicating two allocations raced onto a single call), the platform fails the call leg rather than silently routing media to the wrong destination.
* **Idempotent re-pinning.** Telephony provider retries that carry the same route identity are treated as no-ops, so duplicate callbacks do not cause conflicts.
* **Best-effort for legacy paths.** Calls on the legacy (non-isolated) path that do not have a runtime assignment skip route pinning gracefully. Storage and infrastructure failures fail closed - the call leg is failed rather than proceeding without verification.

**What you need to do:**

* **No action required.** This change is transparent to API consumers. Calls routed through per-call isolation now have stronger media-routing integrity guarantees with no changes to API surface or call behavior.

</details>

<details>

<summary>v0.9.485 - Platform API: OAuth2 Scope Enforcement on Channel Manager Read Routes (July 2026)</summary>

#### OAuth2 Scope Enforcement on Channel Manager Read Routes

The channel manager now enforces OAuth2 scopes and setup-level access checks on all by-ID read, update, and delete routes for email, SMS, and iMessage resources. Previously, a valid bearer token could access any resource regardless of its associated setup. Now, each request is validated against the token's granted scopes and the setup that owns the resource.

**What changed:**

* **Email routes.** The get email, get email body, get email attachment, get email raw, get email template, update email template, and delete email template endpoints now require the appropriate email scope and verify that the token has access to the setup that owns the resource. Requests with insufficient scope or mismatched setup access receive a 403 response.
* **SMS routes.** The get SMS message and get SMS message attachment endpoints now require the SMS read scope and verify setup-level access through the associated use case. Requests with insufficient scope or mismatched setup access receive a 403 response.
* **iMessage routes.** The get iMessage and get iMessage media endpoints now require the iMessage read scope and verify setup-level access. Requests with insufficient scope or mismatched setup access receive a 403 response.
* **New error responses.** All affected endpoints now document 401 (missing, expired, or invalid bearer token) and 403 (token lacks the required scope or access to the setup) responses.

**What you need to do:**

* **Ensure your OAuth2 tokens include the required scopes.** If you access channel manager resources by ID, your token must carry the appropriate read or write scope for the channel (email, SMS, or iMessage) and must have access to the setup that owns the resource.
* **Handle 401 and 403 responses.** API clients should be prepared to receive 401 responses for authentication failures and 403 responses when the token does not grant access to the requested resource's setup.

</details>

<details>

<summary>v0.9.484 - Platform API: EHR Connector Write-Back - Patient Lookup, Appointment Cancellation, and Idempotent Booking (July 2026)</summary>

#### EHR Connector Write-Back - Patient Lookup, Appointment Cancellation, and Idempotent Booking

The connector runner's outbound write-back pipeline now supports patient lookup by contact information, appointment cancellation, appointment rescheduling, and idempotent appointment creation. Write-back remains gated per connector pending sign-off and service-line configuration.

**What changed:**

* **Patient lookup by contact.** The connector can now search the EHR patient list with contact columns (phone and email) to resolve whether a caller is an existing patient or a new patient. This supports the agent's new-vs-existing patient identification flow.
* **Appointment cancellation.** Outbound write-back now handles appointment cancellation events. When the agent cancels an appointment, the connector sends the cancellation to the EHR using the appointment's native identifier. Cancellation failures surface as structured errors rather than silent no-ops.
* **Appointment rescheduling.** Booking an appointment with an existing native appointment identifier updates the existing appointment rather than creating a new one.
* **Idempotent appointment creation.** Retried appointment creates check whether an appointment already exists for the same provider and start time before sending. If a match is found, the retry is treated as a no-op to prevent double-booking.
* **Appointment booking validation.** Appointment writes now require a room and service line (practice-specific scheduling attributes) to be present. Writes missing these attributes are skipped with a structured reason rather than sent as incomplete requests.
* **Patient creation validation.** Patient creation now requires an assigned provider in addition to first and last name. Writes missing the provider are skipped with a structured reason.
* **Booking attribute resolution.** Free-text availability slot titles are resolved into the structured scheduling attributes (room, service line, duration, telehealth flag) that the EHR booking form requires. This bridges the gap between the availability data the agent sees and the structured data the EHR expects.
* **Structured error handling for bookings.** Appointment booking responses are now inspected for EHR-reported validation errors, which are surfaced as structured failures rather than treated as successful no-ops.

**What you need to do:**

* **No action required.** Write-back remains gated and does not fire in production until explicitly enabled per connector. These changes prepare the write-back pipeline for production enablement once sign-off and service-line configuration are complete.

</details>

<details>

<summary>v0.9.483 - Platform API: Intake Batch Processing (July 2026)</summary>

#### Intake Batch Processing

Intake batches - groups of files discovered during a source sync - can now be listed, inspected, and processed through dedicated endpoints.

**What changed:**

* **List batches.** A new `GET /intake/batches` endpoint returns paginated batches for the workspace. Supports filtering by source ID and sorting by creation time or status.
* **Get batch detail.** A new `GET /intake/batches/{batch_id}` endpoint returns a single batch with its associated files.
* **Process a batch.** A new `POST /intake/batches/{batch_id}/process` endpoint starts processing a batch that is in `ready` status (returns 202). Batches in any other status return 409 Conflict.
* **Sequential snapshot processing.** Snapshot and CSV files within a batch process in version order - each file's completion triggers the next, since change-data-capture depends on the prior curated baseline. Document files are independent and process in parallel.
* **Automatic batch roll-up.** When every file in a batch reaches a terminal verdict (curated, rejected, or failed), the batch status rolls up automatically to `completed` or `failed`. This happens as each file's async processing job writes back its status.
* **Status write-back integration.** The existing file status write-back endpoint now advances the parent batch when a batch-sourced file reaches a terminal verdict - chaining the next snapshot version and rolling up the batch status. Batch advancement is best-effort; a transient failure in chaining does not affect the file's committed status.

**What you need to do:**

* **To process synced files in bulk**, call `GET /intake/batches` to find batches in `ready` status, then `POST /intake/batches/{batch_id}/process` to start processing.
* **No changes to existing upload or sync workflows.** Direct file uploads and source sync continue to work as before. Batches are created automatically during source sync.

</details>

<details>

<summary>v0.9.482 - Platform API: Channel-Manager SMS Transport (July 2026)</summary>

#### Channel-Manager SMS Transport

The SMS service now supports a channel-manager transport path that runs in parallel with direct vendor integrations. Service phone numbers configured with the channel-manager provider route inbound and outbound SMS through a centralized channel-management layer instead of calling vendor APIs directly.

**What changed:**

* **New SMS transport provider.** Phone number mappings now support a `channel_manager` provider option alongside existing vendor providers. When a service phone number is configured with this provider, outbound replies are sent through the channel-manager transport rather than a direct vendor API.
* **Channel-manager inbound webhook.** A new inbound webhook path accepts messages forwarded by the channel-manager service. Inbound messages are matched to the correct service configuration using the channel-manager use case identifier, then processed through the same batching and orchestration pipeline as vendor-direct messages.
* **Use-case-based routing.** Each channel-manager phone number mapping includes a use case identifier that links the phone number to a specific channel-manager routing configuration. Inbound messages carry this identifier for service resolution, and outbound replies include it so the channel-manager routes the message to the correct downstream carrier.
* **Transparent to agent logic.** The transport selection is invisible to the agent, conversation orchestration, and batch processing. The same batching, orchestration, and response flow applies regardless of whether a message is routed through a direct vendor integration or the channel-manager transport.
* **OAuth2 authentication for outbound.** Outbound messages sent through the channel-manager transport authenticate using OAuth2 client credentials. The SMS service obtains a short-lived token per send request.

**What you need to do:**

* **To use the channel-manager transport**, configure the service phone number mapping with the `channel_manager` provider and the corresponding use case identifier. Ensure the channel-manager OAuth2 client credentials and base URL are configured in the SMS service environment.
* **No changes to existing vendor-direct phone numbers.** Phone numbers configured with existing vendor providers continue to work as before.
* **No changes to agent or conversation logic.** The transport selection is handled at the infrastructure level and does not affect agent behavior, conversation state, or message batching.

</details>

<details>

<summary>v0.9.481 - Platform API: Test Credential Allowlist for Text Sessions (July 2026)</summary>

#### Test Credential Allowlist for Text Sessions

Workspaces can now designate specific API credentials as test principals for the text channel, mirroring the existing test caller numbers feature for voice. Text turns from test credentials are tagged as test traffic and excluded from billing, metric scores, analytics, outbound EHR sync, and entity views.

**What changed:**

* **Get test credential IDs.** A new `GET /v1/workspaces/{workspace_id}/test-credential-ids` endpoint returns the credential IDs currently configured as test principals for the workspace. Requires any authenticated API key.
* **Update test credential IDs.** A new `PUT /v1/workspaces/{workspace_id}/test-credential-ids` endpoint sets the test credential allowlist (up to 100 credential IDs). Requires `Workspace.update` permission (admin or owner role).
* **Automatic test tagging on text turns.** When a text conversation turn (REST, streaming, or WebSocket) is initiated by a credential on the workspace's test allowlist, the entire session is tagged as test traffic. The platform's source filters then exclude the session from billing, metric scores, analytics, outbound EHR sync, and entity views - identical to how test caller numbers work for voice.
* **Workspace credentials only.** The allowlist is honored only for workspace-authenticated credentials. External user credentials share a single parent credential across all end users behind an integration, so they are intentionally excluded to prevent accidentally dropping real patient traffic. Per-end-user test tagging is a planned future capability.
* **Fails open.** If the workspace settings lookup fails for any reason, the turn proceeds as production traffic. A settings error can never silently exclude real traffic from billing.

**What you need to do:**

* **To tag text sessions as test traffic**, add the credential IDs used by your smoke test or QA harness to the workspace's test credential allowlist using the PUT endpoint. Any text turns initiated by those credentials will be excluded from billing and analytics.
* **No changes to existing voice test caller numbers.** The `test-caller-numbers` endpoints continue to work as before for voice traffic.
* **No changes to production text traffic.** Text turns from credentials not on the allowlist are unaffected.

</details>

<details>

<summary>v0.9.480 - Platform API: Google Drive Source Sync (July 2026)</summary>

#### Google Drive Source Sync

Registered Google Drive intake sources can now be synced on demand, discovering files in mapped folders and landing them into the corresponding intake datasets.

**What changed:**

* **Sync an intake source.** A new `POST /intake/sources/{source_id}/sync` endpoint triggers a sync for a registered Google Drive source. The platform authenticates with the provisioned service account credential, discovers non-folder files in each mapped folder, downloads them, and lands them into the corresponding intake dataset. Each mapped folder produces one batch. The response returns the list of batches with their IDs, target datasets, file counts, and status.
* **Shared Drive and personal folder support.** The sync automatically detects whether a mapped folder lives in a Shared Drive or a personal folder shared with the service account. No additional configuration is needed - the drive type is resolved at sync time.
* **Per-sync file cap.** Each folder sync is capped at 500 files. If a folder contains more files than the cap, the sync lands the first 500 and logs a warning. Larger folders are a planned follow-up for batch job processing.
* **Oversized file skipping.** Files that exceed the platform's upload size limit are skipped before download, with a logged warning. Files whose size is not reported by the storage provider are checked after download.
* **Per-file fault tolerance.** If an individual file fails to download, fails a security scan, or does not conform to the dataset's contract, that file is skipped and logged. The remaining files in the folder continue to sync. A single file failure never fails the entire batch.
* **Native document formats not yet supported.** Cloud-native document formats (such as collaborative documents and spreadsheets) that have no downloadable binary representation are skipped with a warning. Export support is planned for a future release.
* **Credential provisioning validation.** If the service account credential has not been uploaded to the referenced credential path, the sync returns a 422 error with a descriptive message. Authentication failures against the storage provider return a 502 error.
* **Files linked to batches.** Each file landed by the sync is linked to its batch, providing traceability from file back to the sync cycle that produced it. Files uploaded directly through the console or API remain unaffected.

**What you need to do:**

* **To sync a registered source**, call `POST /intake/sources/{source_id}/sync` after ensuring the service account credential is uploaded to the credential path returned during source registration.
* **No changes to existing upload or registration workflows.** Direct file uploads and source registration continue to work as before.

</details>

<details>

<summary>v0.9.479 - Platform API: Google Drive Intake Source Registration and Batch Tracking (July 2026)</summary>

#### Google Drive Intake Source Registration and Batch Tracking

The intake pipeline now supports registering external file sources - starting with Google Shared Drive - that automatically discover and land files into intake datasets. Files synced from a source are grouped into batches for tracking.

**What changed:**

* **Register an intake source.** A new `POST /intake/sources` endpoint lets you register a Google Shared Drive source by providing a display name and one or more folder-to-dataset mappings. Each mapping connects a Drive folder to an intake dataset, so files discovered in that folder are landed into the corresponding dataset. An optional Drive identifier can be provided; if omitted, it is resolved automatically at sync time.
* **Credential reference, not credential storage.** The source configuration stores a reference to where the service account credential lives - never the credential itself. The credential storage path is derived deterministically when the source is created and returned in the response. Operators upload the service account key to that path out of band.
* **List intake sources.** A new `GET /intake/sources` endpoint returns a paginated list of registered sources for the workspace, with support for sorting by creation time or display name.
* **Batch tracking for source syncs.** Files discovered and processed during a source sync are grouped into batches. Each batch tracks the source, dataset, file count, and processing status (discovered, ready, processing, completed, or failed). Files uploaded directly through the console or upload API are unaffected and have no batch association.
* **Intake files linked to batches.** Files landed by a source sync are linked to the batch they were discovered in, providing traceability from file back to the sync cycle that produced it.

**What you need to do:**

* **To use Google Drive intake sources**, call `POST /intake/sources` with your folder-to-dataset mappings. After registration, upload the service account key to the credential path returned in the response. The platform will discover and land files on each sync cycle.
* **No changes to existing upload workflows.** Direct file uploads through the console or API continue to work as before. The batch and source fields are only present on files landed by a source sync.

</details>

<details>

<summary>v0.9.478 - Platform API: Patient Intake Fields in Connector Runner Sync (July 2026)</summary>

#### Patient Intake Fields in Connector Runner Sync

The connector runner now enriches synced patients with intake fields - referral category and preferred contact method - alongside roster demographics and contact detail.

**What changed:**

* **Referral category and preferred contact method.** When the connector runner detects a changed patient during sync, it now fetches the patient's intake fields (how the patient found the practice and how they prefer to be contacted) from the source system's patient edit form and merges them into the emitted patient record as extensions. Previously, synced patients carried roster demographics and contact detail but not intake data.
* **Independent per-poll cap for intake hydration.** Intake field fetches are capped separately from contact enrichment calls per sync cycle. The intake cap is lower than the contact cap because each intake fetch retrieves a larger payload from the source system. Patients past the cap emit with whatever other enrichment succeeded and pick up intake fields on a subsequent cycle when their roster record next changes.
* **Non-fatal intake failures.** An intake-fetch failure emits the patient with roster demographics and any contact detail that was successfully fetched, rather than dropping the patient. Every changed patient is always emitted - intake enrichment is best-effort, independent of contact enrichment.
* **Contact and intake enrichment are independent.** A failure in one enrichment type does not affect the other. A patient can emit with contact detail but no intake fields, intake fields but no contact detail, both, or neither - depending on which fetches succeeded and which were within their respective caps.
* **No API surface changes.** This enhancement is internal to the connector runner's patient sync behavior. No endpoints, request shapes, or response shapes have changed.

**What you need to do:**

* **No action required.** Patient records emitted by the connector runner now carry intake data automatically when available. If you consume patient entities downstream, you may see new extension fields (referral category and preferred contact method) that were previously absent. These fields are additive and do not change existing field semantics.

</details>

<details>

<summary>v0.9.477 - Platform API: Patient Contact Enrichment in Connector Runner Sync (July 2026)</summary>

#### Patient Contact Enrichment in Connector Runner Sync

The connector runner now enriches synced patients with contact detail - email, phone, physical address, and per-channel messaging consent - alongside the existing roster demographics.

**What changed:**

* **Contact enrichment for changed patients.** When the connector runner detects a changed patient during sync, it now fetches the patient's contact detail (email addresses, phone numbers with type qualifiers such as home, work, or mobile, and physical addresses) and merges it into the emitted patient record. Previously, synced patients carried only roster demographics (name, date of birth, provider).
* **Per-channel messaging consent.** Each email and phone entry carries a per-channel consent flag (SMS consent on phone entries, email consent on email entries) when the source system reports it. The consent value is a boolean - true, false, or absent when unknown. Downstream agents and workflows can read these flags to determine whether SMS or email outreach is permitted for a patient.
* **Assigned practitioner.** Synced patients now include an assigned practitioner reference when the source system provides one, letting a patient entity resolve to the provider they see.
* **Phone type qualifier.** Phone entries include a type qualifier (home, work, mobile) mapped from the source system's phone type, so downstream consumers can distinguish between phone lines.
* **Capped per-poll hydration.** Contact enrichment calls are capped per sync cycle to bound latency. Patients past the cap emit roster demographics only and pick up contact detail on a subsequent cycle when their roster record next changes.
* **Non-fatal contact failures.** A contact-fetch failure emits the patient with roster demographics rather than dropping the patient. Every changed patient is always emitted - contact enrichment is best-effort.
* **No API surface changes.** This enhancement is internal to the connector runner's patient sync behavior. No endpoints, request shapes, or response shapes have changed.

**What you need to do:**

* **No action required.** Patient records emitted by the connector runner now carry richer contact data automatically. If you consume patient entities downstream, you may see new fields (email, phone with type, physical address, messaging consent, practitioner reference) that were previously absent. These fields are additive and do not change existing field semantics.

</details>

<details>

<summary>v0.9.476 - Platform API: OAuth2 Scope Enforcement on Channel Manager Routes (July 2026)</summary>

#### OAuth2 Scope Enforcement on Channel Manager Routes

All channel manager routes now enforce OAuth2 scopes, requiring bearer tokens to carry the appropriate scope and setup-level access before any operation is permitted.

**What changed:**

* **Scope enforcement on all Tier-1 routes.** Every channel manager route - including send operations (SMS, email, iMessage, ringless voicemail, outbound voice), use case management (create, read, update, delete), phone number management (provision, assign, unassign, delete, list), compliance operations (A2P brand registration, A2P campaigns, tollfree verification, regulatory bundles, CNAM, SHAKEN/STIR), setup reads (Twilio, SES, SendBlue), access token minting, webhook secret rotation, email template management, and SMS consent management - now validates that the bearer token carries the required OAuth2 scope for the operation and has access to the target setup.
* **Setup-scoped authorization.** Authorization is checked against the setup that owns the resource. For routes that accept a setup ID directly (such as phone number or compliance routes), the scope check runs before any database work. For routes that resolve the setup from a use case or binding (such as send operations), the scope check runs as soon as the owning setup is determined.
* **Consistent error responses.** All protected routes now return `401` for missing, expired, or invalid bearer tokens, and `403` when the token lacks the required scope or access to the target setup. These responses are documented in the OpenAPI spec for every affected route.
* **Simplified outbound voice and ringless voicemail routing.** The select-outbound-voice-phone-number and send-ringless-voicemail routes now resolve the channel binding directly rather than loading the base use case first. A use case that is not bound to the expected channel returns `404` instead of `422`. The `422` response for channel mismatch has been removed from both routes.

**What you need to do:**

* **Ensure bearer tokens carry the required scopes.** If you call channel manager endpoints with OAuth2 tokens, verify that your tokens include the scopes required for the operations you perform. Requests with tokens that lack the required scope will now receive a `403` response instead of proceeding.
* **Update error handling for outbound voice and ringless voicemail.** If your integration handles the `422` channel-mismatch error from the select-outbound-voice-phone-number or send-ringless-voicemail endpoints, update your error handling to expect `404` instead. The `422` response is no longer returned by these routes.
* **Handle `401` and `403` responses.** All channel manager routes now consistently return `401` for authentication failures and `403` for authorization failures. Ensure your API clients handle these responses appropriately.

</details>

<details>

<summary>v0.9.475 - Platform API: At-Least-Once Delivery for Connector Runner Clinical Data Sync (July 2026)</summary>

#### At-Least-Once Delivery for Connector Runner Clinical Data Sync

The connector runner now waits for delivery confirmation before marking clinical records as processed. A failed confirmation leaves the record eligible for re-emission instead of immediately suppressing it as already seen.

**What changed:**

* **Delivery confirmation before dedup marking.** Previously, the connector runner marked records as processed (updating deduplication hashes) immediately after buffering them for delivery. If the downstream delivery failed silently - due to transient errors, rate limits, or partial failures - the records were already marked as seen. The next sync cycle would classify them as unchanged and skip them, causing permanent silent loss of clinical data until the source content changed or the dedup window expired.
* **At-least-once semantics.** The connector runner now confirms that buffered records have been durably delivered before updating dedup hashes. If delivery confirmation fails, the dedup hashes are not updated and the records re-emit on the next sync cycle. This applies to both EHR FHIR page ingestion and raw record polling paths.
* **Duplicate-resistant downstream writes.** Re-emitted records use deterministic identifiers so supported downstream processing can suppress ordinary duplicates. This remains an at-least-once path: retries can produce duplicates, repeated delivery can continue to fail, and the mechanism does not guarantee eventual success.
* **No API surface changes.** This fix is internal to the connector runner's sync behavior. No endpoints, request shapes, or response shapes have changed.

**What you need to do:**

* **No action required.** The change applies automatically to supported connector-runner sync paths. Records whose delivery is not confirmed remain eligible for re-emission on a later sync cycle. Consumers should still tolerate duplicates and monitor records that continue to fail.

</details>

<details>

<summary>v0.9.474 - Platform API: Live Voice Fleet Capacity Status Endpoint (July 2026)</summary>

#### Live Voice Fleet Capacity Status Endpoint

A new endpoint surfaces live voice fleet capacity so operators can monitor how much isolated-call headroom remains.

**What changed:**

* **New `GET /v1/{workspace_id}/sessions/fleet-status` endpoint.** Returns live fleet capacity including ready servers (warm buffer awaiting allocation), allocated servers (active calls), total server count, the configured maximum replica ceiling, and computed headroom (max replicas minus allocated). The counts are workspace-global - one fleet serves every workspace - so the workspace ID in the path is only the auth anchor, not a data filter. The `by_state` field provides a full breakdown of server counts by state for detailed monitoring.
* **Operator-only access.** The endpoint requires the Operator view permission. API keys whose role does not carry this permission receive a 403 response.
* **Nullable ceiling fields.** The `max_replicas` and `headroom` fields are null when the fleet capacity ceiling is not configured upstream. The endpoint never fabricates a ceiling it was not given.
* **Error handling.** If the fleet capacity read fails (for example, because the fleet infrastructure is unavailable), the endpoint returns a 502 response with a descriptive detail message. If fleet status is not available (not running in the expected environment), the endpoint returns 503.

**Response shape:**

| Field          | Type            | Description                                                                       |
| -------------- | --------------- | --------------------------------------------------------------------------------- |
| `fleet`        | string          | Fleet name                                                                        |
| `namespace`    | string          | Fleet namespace                                                                   |
| `ready`        | integer         | Servers in warm buffer (awaiting allocation)                                      |
| `allocated`    | integer         | Servers handling active calls                                                     |
| `total`        | integer         | Total servers across all states                                                   |
| `max_replicas` | integer or null | Configured capacity ceiling (null if not set)                                     |
| `headroom`     | integer or null | Remaining capacity slots: max\_replicas minus allocated (null if ceiling not set) |
| `by_state`     | object          | Server counts keyed by state                                                      |

**What you need to do:**

* **Use this endpoint to monitor fleet capacity.** If you operate voice workloads and need visibility into how much isolated-call capacity remains, poll this endpoint from your monitoring tools or the Developer Console. The `headroom` value tells you how many additional concurrent isolated calls the fleet can accept before reaching its ceiling.
* **Handle null ceiling fields.** If `max_replicas` is null, the capacity ceiling is not configured and `headroom` will also be null. Your monitoring should treat this as "ceiling unknown" rather than "unlimited."

</details>

<details>

<summary>v0.9.473 - Platform API: Fail-Closed Resource Indicator Validation on Token Grants (July 2026)</summary>

#### Fail-Closed Resource Indicator Validation on Token Grants

The token endpoint now rejects RFC 8707 `resource` indicators on grant types that do not honor them, preventing callers from receiving a shared-audience token when they expected a resource-scoped one.

**What changed:**

* **`resource` parameter rejected on unsupported grant types.** Previously, passing a `resource` indicator on a grant type that does not thread the resource into the minted token audience (such as authorization code or refresh token grants) was silently ignored. The caller would receive a token with the shared audience, believing it held a resource-scoped token. At the resource boundary (for example, an MCP server), the token would be rejected with a 401. The token endpoint now returns a 400 `invalid_target` error with a descriptive message when `resource` is provided on a grant type that does not support it.
* **`client_credentials` grant unaffected.** The `client_credentials` grant continues to honor the `resource` parameter and mint resource-scoped tokens as before.
* **Fail-closed behavior.** This validation runs before the resource allowlist check. Even if the resource value is valid and allowlisted, it is rejected if the grant type does not support resource indicators. This prevents any path where a caller could silently receive a shared-audience token instead of the resource-scoped token they requested.

**What you need to do:**

* **Remove `resource` from non-client-credentials token requests.** If you pass a `resource` parameter on authorization code, refresh token, or other non-client-credentials grant types, those requests will now fail with a 400 error. Remove the `resource` parameter from these requests. If you need a resource-scoped token, use the `client_credentials` grant.
* **No changes needed for `client_credentials` callers.** If you only use `resource` with `client_credentials` grants, no action is required.

</details>

<details>

<summary>v0.9.472 - Platform API: Observer Rate Limiting Fix for Rejected Connections (July 2026)</summary>

#### Observer Rate Limiting Fix for Rejected Connections

Observer connections that are rejected before being fully admitted no longer consume rate-limit budget.

**What changed:**

* **Rate-limit budget preserved for rejected observers.** Previously, when a client attempted to observe a call but was rejected due to a permanent error (such as a non-existent call or a workspace mismatch), the connection was still counted against the per-IP burst counter and the concurrent observer gauge. This meant that a client retrying against a permanently invalid target would eventually exhaust its own rate-limit budget, even though none of its connections were ever served. The platform now counts an observer connection against rate limits only after the connection has passed all admission checks (authentication, rate-limit pre-check, call existence, and workspace match).
* **Permanent rejection codes unchanged.** The close codes for call-not-found and workspace-mismatch remain stable. These are terminal rejections - clients should not reconnect after receiving them.
* **Gauge accuracy improved.** The concurrent observer gauge now reflects only actively served connections. Connections that are rejected before full admission no longer inflate the gauge, which prevents spurious rate-limit rejections for other clients in the same workspace.

**What you need to do:**

* **No action required.** This fix applies automatically to all observer connections. If you previously experienced rate-limit errors (close code 4029) after repeated connection attempts to invalid calls, those should no longer occur. Clients that correctly treat call-not-found and workspace-mismatch as terminal errors are unaffected.

</details>

<details>

<summary>v0.9.471 - Platform API: Channel Manager OAuth2 Enforcement and List Endpoint Removal (July 2026)</summary>

#### Channel Manager OAuth2 Enforcement and List Endpoint Removal

Two channel management endpoints now require OAuth2 bearer tokens, and three list-all-setups endpoints have been removed.

**What changed:**

* **OAuth2 required on outbound voice phone number selection.** The endpoint that selects an outbound voice phone number for a use case now requires a valid OAuth2 bearer token with the appropriate scope and setup access. Requests without a valid token receive a 401 response. Requests with a valid token that lacks the required scope or does not cover the target use case receive a 403 response.
* **OAuth2 required on Twilio setup credentials.** The endpoint that retrieves Twilio sub-account credentials for a setup now requires a valid OAuth2 bearer token with the appropriate scope and setup access. The same 401/403 behavior applies.
* **List-all Twilio setups endpoint removed.** The endpoint that listed all Twilio setups has been removed. Use the individual setup detail endpoint to retrieve setup information by ID.
* **List-all SendBlue setups endpoint removed.** The endpoint that listed all SendBlue setups has been removed. Use the individual setup detail endpoint to retrieve setup information by ID.
* **List-all SES setups endpoint removed.** The endpoint that listed all SES setups (both on the channel manager and the Platform API) has been removed. Use the individual SES setup detail endpoint to retrieve setup information by ID.
* **Platform API SES setup list removed.** The paginated SES setup list endpoint on the Platform API has been removed alongside the upstream channel manager endpoint.
* **Client library updated.** The platform client library no longer exposes the list-all methods or the phone number scan method that depended on them. Code that called these methods will need to be updated to use ID-based lookups.

**What you need to do:**

* **Update integrations that call the removed list endpoints.** If you list Twilio, SendBlue, or SES setups, switch to fetching individual setups by ID. The list-all endpoints no longer exist and will return 404.
* **Supply OAuth2 tokens for outbound voice and credential endpoints.** If you call the outbound voice phone number selection or Twilio setup credentials endpoints, ensure your requests include a valid OAuth2 bearer token with the required scope and setup access. Unauthenticated requests will now fail with 401.
* **Update client library consumers.** If you use the platform client library and call `list_setups`, `list_ses_setups`, `find_phone_number`, or `resolve_sub_account_sid`, these methods have been removed. Use the individual get methods instead.

</details>

<details>

<summary>v0.9.470 - Platform API: Memory Extraction v1 Prompt - Gate, Suppress, and Floor Controls (July 2026)</summary>

#### Memory Extraction v1 Prompt - Gate, Suppress, and Floor Controls

The memory extraction pipeline now applies three eval-driven controls - gate, suppress, and floor - that significantly reduce noise in extracted patient memories.

**What changed:**

* **Gate check before extraction.** The extractor now evaluates whether the conversation contains genuine patient self-disclosure before attempting extraction. Conversations where the speaker is a clinician or staff member performing operational tasks, purely transactional calls (scheduling, billing, registration, insurance, records requests), and calls where the caller is not clearly the patient are skipped entirely with no observations extracted.
* **Suppression of structured-record data.** Information that belongs in structured records or connectors is now explicitly excluded from memory extraction. This includes demographics and registration data (legal name, date of birth, address, phone, email, insurance IDs), logistics (appointment times, scheduling, billing status, records requests, referral paperwork), and clinical facts (medications, doses, lab values, vitals, diagnoses). The patient's subjective experience of a symptom is still captured; the clinical datum is not. Preferred name is captured only when the patient states a naming preference or correction, not when they provide their legal name during registration.
* **Floor check after filtering.** After applying the gate and suppression rules, the extractor evaluates whether remaining observations are genuinely durable and decision-relevant. If nothing meaningful about who the person is or how to work with them was revealed, extraction returns no observations. Generic filler statements (such as "efficient communicator" or "organized and proactive") are avoided unless specifically evidenced and decision-relevant.
* **New prompt examples.** The extraction prompt now includes examples of conversations that correctly produce empty results: a clinician asking about a policy for another patient, and a patient who gives their name, date of birth, and insurance then reschedules an appointment with no other self-disclosure.

**What you need to do:**

* **No action required.** These changes apply automatically to all memory extraction. You may notice fewer low-value observations in patient memory models, particularly for transactional calls. Existing memories are not affected.
* **Review memory-dependent workflows.** If you have workflows that depend on memory extraction volume (for example, monitoring observation counts per call), expect a reduction in observations for transactional and operational calls. This is intentional - the extracted observations should be higher quality and more decision-relevant.

</details>

<details>

<summary>v0.9.469 - Platform API: Test Caller Recognition and Permission Gate (July 2026)</summary>

#### Test Caller Recognition and Permission Gate

Inbound voice calls from designated test caller numbers are now correctly tagged as test traffic so the platform excludes them from billing, metric scores, analytics, EHR outbound, and entity views. The endpoint for managing test caller numbers now requires admin or owner permissions.

**What changed:**

* **Test caller tagging fix.** Previously, inbound calls from test caller numbers could be misrouted because the test designation was applied as a call direction rather than a traffic source classification. Test callers are now recognized and tagged at the source level, so the call follows the normal inbound path (credentials, greeting, and audio pipeline are unchanged) while the platform's source filters exclude the session from billing and analytics.
* **Permission gate on test caller numbers endpoint.** The endpoint for setting test caller numbers on a workspace now requires the Workspace update permission (admin or owner role). Previously, any authenticated API key could modify the list. Because this setting controls a billing-exclusion lever, it is now restricted to workspace administrators.

**What you need to do:**

* **Check API key permissions.** If you have automation that updates test caller numbers, ensure the API key used has admin or owner role. Keys with viewer, operator, or member roles will now receive a 403 response.
* **No changes needed for test callers.** Calls from numbers on the test caller list will automatically be excluded from billing and analytics without any configuration change.

</details>

<details>

<summary>v0.9.468 - Platform API: Feature-Flag Rollout-Substrate Health Endpoint (July 2026)</summary>

#### Feature-Flag Rollout-Substrate Health Endpoint

The Platform API now exposes a health endpoint that reports whether feature-flag flips are actually runtime-controllable, surfacing the silently-inert state where flag changes, per-workspace ramps, kill switches, and rollbacks resolve to code defaults instead of taking effect.

**What changed:**

* **New `GET /health/flags` endpoint.** Returns the live health verdict for the feature-flag rollout substrate. The response includes whether the flag provider is registered, whether the provider signaled ready at startup, whether the environment gate is enabled, an overall `runtime_controllable` boolean, and a human-readable `reason` string explaining the current state. No authentication required - designed for external health monitors and alerting.
* **`runtime_controllable` field.** `true` only when all three conditions are met: a flag provider is registered, the provider signaled ready, and the environment gate is enabled. When `false`, flag flips are a no-op and all flags resolve to code or environment defaults.
* **Reason string.** The `reason` field provides a plain-language explanation of why flags are or are not runtime-controllable, so operators and monitors can immediately understand the state without inspecting infrastructure.
* **Metric emission.** The endpoint emits a gauge metric indicating whether flags are runtime-controllable, so you can alert on the transition from controllable to inert.

**Response fields:**

| Field                  | Type    | Description                                                    |
| ---------------------- | ------- | -------------------------------------------------------------- |
| `provider_registered`  | boolean | Whether a flag provider is wired at startup                    |
| `provider_ready`       | boolean | Whether the flag provider signaled ready during initialization |
| `env_enabled`          | boolean | Whether the environment gate for flag evaluation is enabled    |
| `runtime_controllable` | boolean | `true` only when all three conditions above are met            |
| `reason`               | string  | Human-readable explanation of the current state                |

**What you need to do:**

* **Point health monitors at the new endpoint.** If you rely on feature flags for dark launches, per-workspace ramps, or kill switches, monitor the `runtime_controllable` field. When it is `false`, flag flips will not take effect.
* **No authentication required.** The endpoint is publicly accessible, consistent with other health check endpoints.

</details>

<details>

<summary>v0.9.467 - Platform API: OAuth2 Token Issuance Endpoint (July 2026)</summary>

#### OAuth2 Token Issuance Endpoint

The Platform API now includes a public token endpoint for machine-to-machine OAuth2 authentication using the client credentials grant.

**What changed:**

* **New `POST /v1/oauth/token` endpoint.** Registered OAuth2 clients can now request short-lived access tokens by authenticating with their client ID and secret. The endpoint supports both HTTP Basic authentication and form-body credentials (per RFC 6749 section 2.3.1). Tokens are issued as JWTs with standard claims including subject, scope, resource boundary, and expiration.
* **Scope down-scoping.** The requested scope is intersected with the client's granted scopes. Granted patterns support wildcard matching (for example, `sms:*` covers `sms:send`). High-privilege scopes governing client administration and credential access are never matched by wildcards - they must be explicitly granted by exact name.
* **Resource boundary in tokens.** Issued tokens carry a `setups` claim that defines the resource boundary - the set of setup IDs the client is authorized to access, or `["*"]` for unrestricted access.
* **No refresh tokens.** The client credentials grant does not issue refresh tokens. Clients request a new token when the current one expires (default lifetime: 1 hour).
* **Token endpoint is publicly accessible.** The token endpoint is safe to call from external systems. Client management endpoints (create, update, delete, rotate) remain cluster-internal until scope-based access gating is added.

**Error responses:**

| Status | Condition                                                   |
| ------ | ----------------------------------------------------------- |
| 401    | Missing client credentials                                  |
| 403    | Invalid client credentials (unknown client or wrong secret) |
| 422    | Malformed request (for example, unsupported grant type)     |

**What you need to do:**

* **To use machine-to-machine authentication,** register an OAuth2 client through the client management endpoints, then call `POST /v1/oauth/token` with your client credentials and desired scopes to receive an access token.
* **Handle scope down-scoping.** The `scope` field in the response may be a subset of what you requested. Check the returned scope to confirm which permissions were granted.

</details>

<details>

<summary>v0.9.466 - Platform API: Multi-Type Document Schemas for Customer Data Intake (July 2026)</summary>

#### Multi-Type Document Schemas for Customer Data Intake

Document datasets can now accept multiple file types per schema, so a single dataset can receive a mix of document formats without requiring separate schemas for each type.

**What changed:**

* **Multi-type document schemas.** When registering a document schema, you can now pass an `accepted_file_types` array (up to 16 entries) alongside the primary `file_type`. For example, a dataset can accept PDF, Word, and Markdown documents: `{"file_type": "pdf", "accepted_file_types": ["pdf", "docx", "md"]}`. The primary `file_type` is always included in the accepted set.
* **Per-document type pinning.** Each document is pinned to a single file type when it is created (version 1). All subsequent versions of the same document must match the type set at creation. Uploading a version with a different type returns 422 with an error describing the mismatch.
* **Upload validation against accepted types.** File uploads are validated against the dataset's full accepted type set. If the uploaded file's type is not in the accepted set, the endpoint returns 422 with a message listing the accepted types.
* **New `accepted_file_types` field on dataset responses.** The dataset list and detail responses now include an `accepted_file_types` array showing the full set of accepted types. Empty for snapshot datasets and legacy single-type document datasets.
* **New `file_type` field on file responses.** Each file version now includes a `file_type` field indicating the type of that specific upload. Null for legacy and snapshot files.
* **Snapshot datasets remain single-type.** Tabular types (CSV, XLS, XLSX) cannot be mixed with other types. Attempting to register a schema that combines tabular and document types returns a validation error.
* **Two new error cases on upload.** Uploading a file type not in the accepted set returns 422 (`DocumentTypeNotAllowedError`). Uploading a new version whose type does not match the document's pinned type returns 422 (`DocumentTypeMismatchError`).

**What you need to do:**

* **No action required for existing datasets.** Existing single-type document datasets continue to work as before. The `accepted_file_types` field is empty for legacy datasets, and uploads are validated against the single primary type.
* **To accept multiple document types,** pass `accepted_file_types` when registering a new schema. Existing schemas must be recreated with the desired types.
* **Update integrations that display file metadata.** File responses now include a `file_type` field. Dataset responses include `accepted_file_types`.
* **Handle new 422 error cases.** Upload calls may now return 422 for type-not-allowed and type-mismatch errors. Update your error handling to surface these to users.

</details>

<details>

<summary>v0.9.465 - Platform API: Memory Extraction Error Tracking (July 2026)</summary>

#### Memory Extraction Error Tracking

The memory extraction pipeline now durably records per-session extraction failures in a queryable errors table, replacing ephemeral log-only error reporting.

**What changed:**

* **Durable error sidecar for extraction failures.** When a memory extraction attempt fails for a session, the failure is recorded in a persistent, queryable table. Each error row captures the conversation, session, workspace, failure type, and a content-free error summary. Operators can triage failures with a simple query against this table rather than searching through ephemeral compute logs.
* **PHI-safe error messages.** Error messages written to the errors table are stripped of any transcript or model-response content. Only the exception type and a short, content-free header are persisted, so the analytics-layer table never carries protected health information.
* **Automatic retry on next run.** Sessions that fail extraction are not marked as processed, so they are automatically retried on the next pipeline execution. The error record is preserved for observability even if the retry succeeds.
* **Best-effort error persistence.** Writing to the errors table is isolated from the main processing ledger. If the error write itself fails, the pipeline run continues and completes normally - observability is best-effort and never causes a pipeline failure.
* **Updated exit summary.** The pipeline exit message now reports both the number of observations written and the number of failures, giving operators an at-a-glance view of pipeline health.

**What you need to do:**

* **No action required.** Error tracking is automatic. If you operate memory extraction pipelines, you can query the errors table to monitor and triage extraction failures.

</details>

<details>

<summary>v0.9.464 - Platform API: Document-Owned Version History for Customer Data Intake (July 2026)</summary>

#### Document-Owned Version History for Customer Data Intake

Document datasets now support per-document version chains. Each logical document maintains its own version history, and the platform tracks a `current` pointer that advances only on successful extraction.

**What changed:**

* **Per-document versioning.** Document uploads now track versions per document rather than per dataset. Each document maintains an independent version counter. Uploading a new file without a `document_id` creates a new logical document at version 1. Supplying an existing `document_id` allocates the next version in that document's chain.
* **New `document_id` form parameter on upload.** The file upload endpoint accepts an optional `document_id` field (UUID). Omit it to create a new document; supply it to add a new version to an existing document. If the supplied `document_id` does not exist in the target dataset, the endpoint returns 422.
* **Snapshot datasets reject `document_id`.** Supplying `document_id` when uploading to a snapshot (CSV) dataset returns 422. Snapshot datasets continue to version at the dataset scope.
* **`document_id` and `version` fields on file responses.** The file response object now includes `document_id` (uuid or null) and `version` (integer or null), so API consumers can group files by document and display version history.
* **Current version pointer.** Each document tracks the latest successfully processed version. When extraction completes successfully, the current pointer advances to that version. Failed extractions do not move the pointer, so downstream consumers always reference the most recent valid extraction.
* **Extraction job receives document context.** The asynchronous extraction pipeline now receives the document identifier alongside the version number, so extraction artifacts are stored under the correct document's version chain.

**What you need to do:**

* **Update integrations that list or display files.** File response objects now include `document_id` and `version` fields. Use `document_id` to group files by document and `version` to display version history.
* **To upload a new version of a document,** include the `document_id` form field with the existing document's ID. The platform allocates the next version automatically.
* **No action needed for snapshot datasets.** Snapshot (CSV) uploads continue to work as before. The `document_id` and `version` fields are null for snapshot files.

</details>

<details>

<summary>v0.9.463 - Platform API: Fail-Closed External Principal Session Correlation (July 2026)</summary>

#### Fail-Closed External Principal Session Correlation

The external user session-mint path now enforces strict fail-closed correlation between external subject keys and entity bindings, closing a trust-root impersonation vector.

**What changed:**

* **Fail-closed subject key/entity correlation.** When minting a session via the `external_user_session` grant type, the asserted `consumer_entity_id` must exactly match the entity already bound to the external subject key. Previously, the session-mint path could establish a first entity binding on a subject that had none, or silently accept a new subject with an asserted entity. Both paths are now rejected.
* **Three rejection cases.** The session-mint path rejects with a `400 invalid_request` error when: (1) the subject key is already bound to a different entity (entity rebind), (2) the subject key exists but has no entity bound yet and the session-mint attempts to establish the first binding (first bind via session), or (3) no subject record exists at all but an entity is asserted (unknown subject). All three cases are fail-closed.
* **Entity bindings are provisioned out of band.** The entity binding on an external subject must be established through the authoritative provisioning path (the platform-api edge broker or an explicit admin flow), not through the session-mint path. The session-mint path only correlates against existing verified bindings.
* **Richer error and audit metadata.** The `400` error response now describes the pair as "inconsistent or unverified" rather than only "inconsistent." Audit log entries for session creation failures now include a `conflict_reason` field indicating which of the three rejection cases triggered, and the `existing_entity_id` field is `null` when no prior entity binding existed.
* **Race condition coverage.** The same fail-closed correlation is enforced on the concurrent-insert recovery path, so a race between two session-mint requests cannot bypass the check.

**What you need to do:**

* **Review external user session integrations.** If your integration relies on the session-mint path to establish the first entity binding on an external subject (creating a subject and asserting an entity in the same request), this will now be rejected. Ensure that entity bindings are provisioned through the authoritative admin or edge-broker path before the external user attempts to mint a session.
* **Update error handling.** If your client parses the `error_description` field from session-mint `400` responses, note the updated wording. The `error` field remains `invalid_request`.

</details>

<details>

<summary>v0.9.462 - Platform API: OAuth 2.1 Resource-Server Plumbing for the MCP Boundary (July 2026)</summary>

#### OAuth 2.1 Resource-Server Plumbing for the MCP Boundary

The MCP boundary (`/v1/mcp`) now supports audience-isolated token verification and advertises OAuth 2.1 protected-resource metadata, laying the groundwork for per-resource token scoping that prevents cross-service token replay.

**What changed:**

* **Audience-isolated token verification (dark, off by default).** When enabled, the MCP boundary verifies JWTs against a dedicated MCP resource audience instead of the shared API audience. A token issued for the shared API is rejected at the MCP endpoint, closing the confused-deputy vector where a platform API token could be replayed against the MCP surface. Ships dark - the feature is off by default and byte-identical to current behavior until explicitly enabled.
* **OAuth 2.1 protected-resource metadata endpoint.** A new `/.well-known/oauth-protected-resource` endpoint serves RFC 9728 protected-resource metadata for the MCP resource server. MCP OAuth 2.1 clients can discover the authorization server, supported scopes, and JWKS location from this document. The endpoint is public, unauthenticated, and excluded from the OpenAPI schema (it is OAuth protocol plumbing, not a product API).
* **Bearer challenge with resource metadata on 401.** When the MCP boundary returns a 401, the `WWW-Authenticate` header now includes a `resource_metadata` parameter pointing at the protected-resource metadata URL. OAuth 2.1-aware MCP clients can use this to automatically discover the authorization server. Clients that do not understand the parameter ignore it.
* **Per-resource audience minting in identity.** The identity service now supports an allowlist of per-resource audiences (RFC 8707 resource indicators). When configured, a grant can request a token scoped to a specific MCP resource audience. The allowlist is empty by default, keeping the per-resource path dormant - only the shared audience is mintable and existing behavior is unchanged.

**What you need to do:**

* **No action required.** All changes ship dark or are additive. The MCP audience binding is off by default and existing tokens continue to work at the MCP boundary. The protected-resource metadata endpoint and Bearer challenge are informational and do not affect existing integrations. No schema changes to existing endpoints.

</details>

<details>

<summary>v0.9.461 - Platform API: Shadow Rollout Defaults Graduated to On (July 2026)</summary>

#### Shadow Rollout Defaults Graduated to On

Four shadow observability features that were previously off by default and required per-environment opt-in are now enabled by default across all environments. These features remain shadow-only - they observe and report but do not block or modify any runtime behavior.

**What changed:**

* **Write-audit shadow now on by default.** The HIPAA write-audit shadow at the write boundary (introduced in v0.9.455) is now enabled by default. Every model-originated world write that carries a write scope emits a uniform audit record without requiring per-environment configuration. Set `WORLD_WRITE_AUDIT_SHADOW_ENABLED=false` to suppress the signal.
* **Guardrail evaluation shadow now on by default.** The runtime-agnostic shadow guardrail evaluator (introduced in v0.9.457) is now enabled by default. Each agent transcript is evaluated against the current state's guardrails and boundary constraints in a detached background task with no turn or first-audio latency impact. Set `GUARDRAIL_SHADOW_ENABLED=false` to suppress the signal.
* **Scheduling precondition shadow now on by default.** The scheduling-EHR-precondition shadow (introduced in v0.9.458) is now enabled by default. Every model-originated scheduling write emits a shadow record of whether the workspace had an active clinical data source. Set `SCHEDULING_PRECONDITION_SHADOW_ENABLED=false` to suppress the signal.
* **Control-structure invariant shadow now on by default.** The runtime-agnostic control-structure invariant shadow (introduced in v0.9.459) is now enabled by default. Each voice call's state-transition path is checked against engageable-state, loop-detection, and max-iteration invariants. Set `CONTROL_SHADOW_ENABLED=false` to suppress the signal.
* **No enforcement changes.** All four features remain shadow-only. They log and emit metrics but never block writes, reject transitions, or affect call audio. Enforcement for each feature is planned for future releases.
* **Kill switches preserved.** Each feature retains its per-environment configuration flag as a kill switch. Setting any flag to `false` suppresses that shadow signal entirely.

**What you need to do:**

* **No action required.** Shadow signals are now active by default. If you previously set any of the configuration flags to `true`, those settings are now redundant and can be removed. If you need to suppress a specific shadow signal, set its flag to `false`.

</details>

<details>

<summary>v0.9.460 - Platform API: Batch Entity Resolver and Always-On MCP PHI Audit (July 2026)</summary>

#### Batch Entity Resolver and Always-On MCP PHI Audit

The world-model read surface now includes a batch entity resolver, and all MCP read tools that return PHI are now HIPAA-audited on every invocation.

**What changed:**

* **New `world_state_resolve` read tool.** A new world-model read tool resolves a batch of entity IDs (up to 100) to their current state in a single call. For each resolved entity, the response includes identity fields (entity type, canonical ID, name, display name, gender, birth date), clinical fields (clinical status, code text, effective date), appointment fields (status, start, end, type), and the linked patient canonical ID. IDs not found in the authenticated workspace are listed under `unresolved` - no error is raised and no cross-workspace data is leaked. The tool is available through the MCP read surface for external and partner agents and through the internal agent runtime for in-house agents.
* **Always-on HIPAA audit for MCP PHI reads.** The entity detail, entity timeline, and entity graph MCP read tools now emit a HIPAA audit record on every invocation. Previously, these tools did not audit reads. The audit is fire-and-forget and does not affect response latency.
* **Workspace-scoped isolation.** The batch resolver matches entities within the authenticated workspace only. Entities from other workspaces are never returned.
* **PHI handling.** All values returned by the batch resolver are PHI and are logged under workspace-scoped audit only.

**What you need to do:**

* **No action required.** The new `world_state_resolve` tool is available immediately through the MCP read surface. Existing read tools continue to work as before, with the addition of HIPAA audit logging. No schema changes to existing tools.

</details>

<details>

<summary>v0.9.459 - Platform API: Runtime-Agnostic Control-Structure Invariant Shadow (July 2026)</summary>

#### Runtime-Agnostic Control-Structure Invariant Shadow

The platform now validates the control-structure invariants of every voice call against the actual observed state-transition path, regardless of which voice runtime produced the transitions. This runs as a shadow signal - it observes and reports but does not block or modify call behavior.

**What changed:**

* **Shadow control-structure invariant checking for all voice runtimes.** When enabled, a per-call background subscriber checks each state transition against three structural invariants: engageable state (the turn must land on a state type that accepts user interaction), loop detection (cross-turn state revisitation across the full call path), and max iteration (a single turn must not exceed the per-turn transition hop cap). The subscriber covers all three voice runtimes (in-house pipeline, real-time speech-to-speech, and Atlas) from a single integration point.
* **Asymmetric signal value by runtime.** For the in-house pipeline, the navigation engine already code-enforces these invariants, so the shadow confirms enforcement and acts as a regression tripwire. For self-navigating runtimes (where the model picks transitions in-prompt with no code enforcement), the shadow surfaces real violations - states that are not engageable, cross-turn loops, or excessive per-turn hops that code enforcement would otherwise catch.
* **Zero latency impact.** The invariant subscriber runs in its own detached task off its own event queue, adding no latency to the turn or first-audio path.
* **Honest coverage reporting.** Each shadow verdict records whether engageable-state type resolution was available for the call. Calls where state type information is unavailable (such as self-navigating provider sessions without an in-house session) report the engageable invariant as unresolved rather than guessed.
* **Fully suppressed.** A failure in the shadow path - whether in the evaluator or the recording step - is logged for observability but never propagates to the call. The shadow can never affect live audio or agent behavior.
* **On by default.** The feature is enabled by default. Set `CONTROL_SHADOW_ENABLED=false` to suppress the signal per environment.

**What you need to do:**

* **No action required.** The control-structure invariant shadow is transparent to API consumers. It does not affect request or response schemas, and call behavior is unchanged. Shadow verdicts appear in platform metrics and logs. Enforcement (blocking transitions that violate control-structure invariants) is planned for a future release.

</details>

<details>

<summary>v0.9.458 - Platform API: Scheduling Precondition Shadow (July 2026)</summary>

#### Scheduling Precondition Shadow

The platform now validates that workspaces have an active clinical data source before scheduling writes land, closing a gap where scheduling operations (such as appointment creation) could succeed for workspaces with no connected clinical system. This runs as a shadow signal - it observes and reports but does not block writes.

**What changed:**

* **Scheduling precondition shadow at the write boundary.** When enabled, every model-originated scheduling write (Appointment lifecycle operations) that lands in the event store triggers a background check for whether the workspace has an active clinical data source. The result is recorded as a shadow signal for observability.
* **Catches all write paths.** The precondition check runs at the shared write boundary, so it covers scheduling writes from all channels - voice, text, MCP, and REST - without requiring per-caller logic. This closes the gap where scheduling writes through certain paths bypassed the session-level scheduling gate.
* **Non-production sources excluded.** Writes from simulation, test, and playground sources are excluded from the precondition check, so shadow data reflects only production scheduling activity.
* **Per-workspace caching.** The precondition probe is cached per workspace with a short time-to-live window, so a burst of scheduling writes does not generate excessive backend load. Workspace configuration changes rarely, so brief staleness is acceptable for a shadow signal.
* **Bounded probe execution.** The precondition probe runs with a hard timeout to prevent a slow backend read from affecting connection availability. If the probe times out or fails, the failure is recorded separately so probe errors are never confused with true precondition violations.
* **Zero latency impact on writes.** The shadow runs as a detached background task. A scheduling write that already succeeded is never affected by the precondition check, regardless of the check's outcome.
* **Fully suppressed.** A failure in the shadow path - whether in the probe or the recording step - is logged for observability but never propagates to the write that triggered it.
* **On by default.** The feature is enabled by default. Set `SCHEDULING_PRECONDITION_SHADOW_ENABLED=false` to suppress the signal per environment.

**What you need to do:**

* **No action required.** The scheduling precondition shadow is transparent to API consumers. It does not affect request or response schemas, and scheduling writes are delivered unchanged. Shadow signals appear in platform metrics and logs. Enforcement (refusing scheduling writes for workspaces without an active clinical source) is planned for a future release.

</details>

<details>

<summary>v0.9.457 - Platform API: Runtime-Agnostic Shadow Guardrail Evaluation (July 2026)</summary>

#### Runtime-Agnostic Shadow Guardrail Evaluation

The platform now includes a runtime-agnostic guardrail evaluation layer that screens every agent response against the guardrails and boundary constraints configured on the current conversation state. The evaluator runs in shadow mode - verdicts are logged and metered but never enforced, so agent responses are delivered unchanged.

**What changed:**

* **Shadow guardrail evaluation for all voice runtimes.** When enabled, each agent transcript produced during a voice call is evaluated against the current state's guardrails and boundary constraints. The evaluation runs as a detached background task per call, covering all three voice runtimes (in-house pipeline, real-time speech-to-speech, and Atlas) from a single integration point.
* **Per-state rule resolution.** The evaluator resolves guardrails and boundary constraints from the current conversation state. Each guardrail carries a hard or soft enforcement level; boundary constraints are always soft. A hard guardrail match produces a "block" verdict, a soft match produces "warn", and no match produces "allow".
* **Zero latency impact.** The shadow evaluator runs off the non-blocking event path. It adds no latency to the turn or first-audio path.
* **Honest coverage reporting.** Each shadow verdict records whether the call's per-state guardrails were available for evaluation. Calls where state-level guardrails are not yet wired report the coverage gap in the metric stream, so the shadow data accurately reflects which calls have full guardrail coverage.
* **Fully suppressed.** A failure in the shadow evaluator can never affect the live call. All errors are caught and logged without propagating to the audio or response path.
* **Dark by default.** The feature is off by default and is enabled per environment after reviewing shadow verdict volume and decision distribution in staging.

**What you need to do:**

* **No action required.** The shadow guardrail evaluator is transparent to API consumers. It does not affect request or response schemas, and agent responses are delivered unchanged. When enabled in your environment, shadow verdicts appear in platform metrics and logs. Enforcement of guardrail verdicts (blocking or modifying responses) is planned for a future release.

</details>

<details>

<summary>v0.9.456 - Platform API: Caller-Agnostic HIPAA Write-Audit Shadow (July 2026)</summary>

#### Caller-Agnostic HIPAA Write-Audit Shadow

The platform now provides uniform HIPAA write-audit coverage at the world model write boundary. Every model-originated write - regardless of channel (voice, text, MCP, or REST) - can emit a corresponding audit row, closing the gap where clinical writes from certain channels were not individually audit-logged.

**What changed:**

* **Uniform write-audit at the write boundary.** When enabled, every model-originated write that lands in the event store emits a corresponding audit event through the same event pipeline. This provides caller-agnostic audit coverage - voice, text, MCP, and REST writes all produce audit rows from a single boundary rather than requiring per-caller audit logic.
* **Model-origin writes only.** Only writes that carry a write scope (the mandatory model-write guard introduced in v0.9.455) or require scope verification emit audit rows. Trusted machine ingestion such as connector EHR syncs is excluded, so the audit ledger contains only access-relevant entries.
* **Zero latency impact on writes.** The audit emit runs asynchronously and is fully decoupled from the originating write. Voice writes, which run on tight latency budgets, are never slowed by audit processing.
* **Idempotent audit entries.** Each audit row uses a deterministic identifier derived from the source write, so retried writes cannot produce duplicate audit entries.
* **Fail-safe.** If the audit emit fails, the failure is logged for observability but never affects the write that already succeeded. An audit failure cannot block or roll back a successful clinical write.
* **Dark by default.** The feature is off by default and is enabled per environment via configuration after validating audit volume and ledger correctness in staging.

**What you need to do:**

* **No action required.** The write-audit shadow is transparent to API consumers. It does not affect request or response schemas. When enabled by your environment, audit rows appear automatically alongside existing audit data.

</details>

<details>

<summary>v0.9.455 - Platform API: Mandatory Write Scope for Agent-Originated Writes (July 2026)</summary>

#### Mandatory Write Scope for Agent-Originated Writes

All agent-originated write operations now require a write scope - a per-session guard that binds every write to the patient entity the agent resolved during the conversation. This prevents wrong-entity writes, a safety-critical risk class where the model targets data at an unintended entity.

**What changed:**

* **Write scope is now mandatory for all agent write tools.** Every write tool dispatched during an agent session (scheduling, cancellation, confirmation, rescheduling, patient create/update, insurance, medication refill, call logging, triage logging, and ticket creation) now requires a write scope. The scope is constructed once per session and injected into every write tool automatically.
* **Shadow logging for uncovered paths.** If a model-originated write reaches the persistence boundary without a write scope, the platform logs the gap as a metric and warning for observability. No writes are blocked in this increment - enforcement is planned for a future release.
* **No change for system-originated writes.** Writes from connector syncs, enrichment pipelines, and other trusted internal processes are not subject to write scoping.
* **No change for read operations.** Read tools and queries are unaffected.

**What you need to do:**

* **No action required.** The write scope is constructed and injected automatically per session. If you are using the platform API or SDK to start conversations, no changes are needed. The scope is transparent to API consumers and does not affect request or response schemas.

</details>

<details>

<summary>v0.9.454 - Platform API: Atlas Per-Turn Conversation Persistence (July 2026)</summary>

#### Atlas Per-Turn Conversation Persistence

Atlas voice calls now attempt to persist finalized caller and agent transcripts during the call through the same conversation-journal path used by the in-house voice pipeline and text sessions. Previously, Atlas transcripts were available only on the live observer bus and in the call-end turn count. Persistence remains best-effort and can be unavailable for a turn or an entire degraded session.

**What changed:**

* **Best-effort per-turn persistence.** For an eligible session, each finalized caller or agent transcript is offered to the hot session store and durable analytics path. Successfully stored turns can then appear in conversation detail, entity timelines, and downstream analytics.
* **Per-call duplicate suppression.** The runtime tracks item IDs that were successfully flushed during the call and skips the same item if it reappears in a later provider-history update. This suppresses duplicate journal entries for those successful flushes; it is not an exactly-once durability guarantee.
* **Non-blocking failure path.** If a durable write fails, the failure is logged and the same item can be retried on a later turn at the same journal position. Persistence failure does not intentionally block the audio path.
* **Graceful degradation.** When the session store is unavailable or the call started in a degraded state (no engine session), per-turn persistence is silently skipped. The call continues with full audio and observer-bus transcripts; only durable journal writes are omitted.
* **Observability.** Failed journal flushes are tracked in platform metrics so operators can monitor persistence health without inspecting logs.

**What you need to do:**

* **No action required.** Eligible Atlas calls can now include persisted turn-level transcript data. Clients should tolerate missing turns when persistence is unavailable or a session is degraded.

</details>

<details>

<summary>v0.9.453 - Platform API: In-Flight Write-Tool Deduplication for Realtime Voice (July 2026)</summary>

#### In-Flight Write-Tool Deduplication for Realtime Voice

The real-time speech-to-speech voice runtime now includes the same in-flight write-tool deduplication previously available in the Atlas runtime. This is a life-critical safety guard that prevents double-writes for scheduling, insurance, and medication operations during voice calls.

**What changed:**

* **Write-tool dedup on realtime voice.** When the model re-invokes an identical write tool (same tool name and arguments) while the first invocation is still executing, the duplicate call is short-circuited. The platform returns a structured response to the model indicating the operation is already in progress, so the conversation turn continues without stalling and no duplicate write is performed.
* **Scoped per call.** The dedup state is scoped to each individual call. Once the original write completes, a subsequent identical call in the same conversation is allowed to proceed normally.
* **Parity with Atlas.** This brings the real-time speech-to-speech runtime to parity with the Atlas runtime, which already had this guard. Both runtimes now protect against double-writes using the same mechanism.
* **Observability.** Deduplicated write-tool calls are tracked in platform metrics and logged for troubleshooting.

**What you need to do:**

* **No action required.** The guard applies automatically to all voice calls using the real-time speech-to-speech runtime. Write tools that were previously at risk of double-invocation during concurrent execution are now protected.

</details>

<details>

<summary>v0.9.452 - Platform API: Graceful Busy Handling at Voice Capacity Limits (July 2026)</summary>

#### Graceful Busy Handling at Voice Capacity Limits

When the voice fleet is at capacity under per-call media isolation, callers now hear a brief apology and the call ends gracefully instead of remaining connected to dead air.

**What changed:**

* **Busy redirect at capacity.** When per-call voice isolation is active and the platform cannot allocate an isolated media server for a call, the caller is now redirected to a short busy message and the call is ended cleanly. Previously, the caller would remain connected with no audio indefinitely because there is no legacy fallback path under per-call isolation.
* **Best-effort and safe.** The redirect is best-effort - if it fails for any reason (network issue, telephony provider error), the outcome is no worse than the previous dead-air behavior. The redirect only applies to calls that have already failed allocation; it never touches a successfully allocated call.
* **No change for legacy path.** Workspaces that have not yet enabled per-call isolation are unaffected. Calls on the legacy path continue to use the existing retry contract.
* **Observability.** Redirect attempts are tracked in platform metrics with success and failure tags, and logged for troubleshooting.

**What you need to do:**

* **No action required.** The improvement applies automatically to all workspaces using per-call voice isolation. Callers will receive a clear busy message instead of silence when the voice fleet is at capacity.

</details>

<details>

<summary>v0.9.451 - Platform API: Realtime Voice Model Default and Startup Validation (July 2026)</summary>

#### Realtime Voice Model Default and Startup Validation

The default backing model for realtime voice runtimes (real-time speech-to-speech and Atlas) has changed to the function-calling model, and the platform now validates the configured model at startup.

**What changed:**

* **Default realtime model changed.** The realtime voice backing model now defaults to the function-calling variant. The previous default did not reliably emit function calls even when tools were declared with explicit instructions, which prevented tools and context-graph transitions from firing during realtime voice sessions. The new default supports function calling, so tools and context-graph transitions work on the paved path for realtime voice agents.
* **Startup validation for realtime model.** The configured realtime model is now validated against a recognized allowlist when the service starts. If the model name does not match a recognized realtime model, the service refuses to start with a descriptive error. This catches operator typos at deploy time rather than surfacing as a connection error mid-call.
* **No change to override behavior.** The realtime model remains configurable per environment, so teams can still A/B test realtime models without a redeploy. Both the real-time speech-to-speech runtime and the Atlas runtime continue to read from the same configuration knob.

**What you need to do:**

* **If you rely on the previous default model**, set the realtime model configuration explicitly to preserve your current behavior. If you have not customized the realtime model, your voice agents will automatically use the function-calling model.
* **If you use a custom realtime model**, verify that your configured value matches a recognized model. Unrecognized values will now prevent the service from starting.

</details>

<details>

<summary>v0.9.450 - Platform API: OAuth2 Client Management for Machine-to-Machine Authentication (July 2026)</summary>

#### OAuth2 Client Management for Machine-to-Machine Authentication

The Platform API now includes endpoints for registering and managing OAuth2 clients that authenticate using the `client_credentials` grant. These clients enable machine-to-machine integrations where automated services need to interact with the platform without user-driven authentication.

**What changed:**

* **Create OAuth2 client.** `POST /v1/oauth/client` registers a new M2M client with a name, description, granted scopes, and an allowed setup list. The response includes a high-entropy client secret that is returned once at creation time and cannot be retrieved later - only its hash is stored. The `allowed_setup_ids` field accepts either an explicit list of setup IDs (validated against existing setups) or the `["*"]` wildcard for access to all setups.
* **Update OAuth2 client.** `PUT /v1/oauth/client/{client_id}` updates client metadata, granted scopes, or the setup allow-list. At least one field must be provided. The client secret is not affected - use the rotate-secret endpoint for that.
* **Delete (revoke) OAuth2 client.** `DELETE /v1/oauth/client/{client_id}` soft-deletes a client, preventing it from authenticating. The client record is retained for audit purposes. Re-deleting a revoked client returns 404.
* **Rotate client secret.** `POST /v1/oauth/client/{client_id}/rotate-secret` issues a new secret, immediately invalidating the previous one. The new secret is returned once and cannot be retrieved afterward.
* **Scope model.** Clients are granted scopes following a `resource:action` pattern (e.g., `sms:send`, `email:read`, `twilio-setup:write`). Wildcard patterns such as `sms:*` or `*` are supported and expanded at token issuance time.
* **Two-dimensional access control.** Each client's access is defined by the intersection of its granted scopes (what actions it can perform) and its allowed setup list (which resources it can access).

**What you need to do:**

* **To create M2M integrations**, use the new endpoints to register OAuth2 clients with the appropriate scopes and setup access. Store the client secret securely when it is returned at creation time - it cannot be retrieved later.
* **To rotate a compromised secret**, call the rotate-secret endpoint. The old secret is invalidated immediately with no grace period.

</details>

<details>

<summary>v0.9.449 - Platform API: Advanced Stats, Dashboard, and Operator Performance Now Served from Analytical Projection (July 2026)</summary>

#### Advanced Stats, Dashboard, and Operator Performance Now Served from Analytical Projection

The advanced call stats, analytics dashboard, and operator performance endpoints now read from the same live analytical projection used by call quality, emotion trends, latency, tool performance, and call comparison analytics, replacing the previous data source. This completes the migration of all analytics endpoints to the analytical projection.

**What changed:**

* **Advanced call stats served from analytical projection.** `GET /analytics/advanced-stats` now reads percentile-based call metrics, by-service breakdowns, and by-direction breakdowns from the live analytical projection optimized for dashboard workloads. The endpoint continues to accept the same query parameters (`days`, `date_from`, `date_to`, `interval`, `service_id`, `direction`) and returns the same response shape (`summary` with percentile duration and quality metrics, `trend` with per-interval breakdowns, `by_service` with per-service aggregates, and `by_direction` with per-direction aggregates).
* **Dashboard served from analytical projection.** `GET /analytics/dashboard` now reads composite KPI data from the same analytical projection. The endpoint continues to accept the same query parameters (`days`) and returns the same response shape (six KPIs - `call_volume`, `avg_quality`, `avg_ttfb_ms`, `escalation_rate`, `tool_success_rate`, `avg_duration_s` - each with `value` and `delta_pct` for period-over-period comparison).
* **Operator performance served from analytical projection.** `GET /analytics/operator-performance` now reads escalation statistics and operator-involvement trend data from the same analytical projection. The endpoint continues to accept the same query parameters (`days`, `date_from`, `date_to`, `interval`, `service_id`) and returns the same typed response shape (`summary` with total calls, escalated count, escalation rate, operator-handled count, and average quality/duration breakdowns by escalation status, and `trend` with per-interval escalation counts).
* **Non-production calls excluded.** Consistent with other analytics endpoints, test and simulation calls are automatically filtered out of all three endpoints.
* **No response format changes.** All three endpoints return the same response structure as before. No client changes are needed.

**What you need to do:**

* **No action required.** All three endpoints are backward-compatible. If you consume advanced call stats, dashboard, or operator performance data, no client changes are needed.

</details>

<details>

<summary>v0.9.448 - Platform API: Latency and Tool Performance Analytics Now Served from Analytical Projection (July 2026)</summary>

#### Latency and Tool Performance Analytics Now Served from Analytical Projection

The latency analytics and tool performance analytics endpoints now read from the same live analytical projection used by call quality, emotion trends, and call comparison analytics, replacing the previous data source.

**What changed:**

* **Latency analytics served from analytical projection.** `GET /analytics/latency` now reads latency summary and trend data from the live analytical projection optimized for dashboard workloads. The endpoint continues to accept the same query parameters (`days`, `date_from`, `date_to`, `interval`, `service_id`) and returns the same response shape (`summary` with percentile and average latency metrics, and `trend` with per-interval breakdowns).
* **Tool performance analytics served from analytical projection.** `GET /analytics/tool-performance` now reads tool call aggregates and trend data from the same analytical projection. The endpoint continues to accept the same query parameters (`days`, `date_from`, `date_to`, `interval`, `service_id`) and returns the same response shape (`summary` with total tool calls, succeeded, failed, overall failure rate, and average failure rate per call, and `trend` with per-interval breakdowns).
* **Non-production calls excluded.** Consistent with other analytics endpoints, test and simulation calls are automatically filtered out of both endpoints.
* **No response format changes.** Both endpoints return the same response structure as before. Latency analytics returns `summary` (avg/p50/p95 engine latency, avg/p50/p95/p99 time-to-first-byte, avg navigation and render latency, avg silence ratio) and `trend` (per-interval call count and average latency metrics). Tool performance returns `summary` (total tool calls, succeeded, failed, overall failure rate, avg failure rate per call) and `trend` (per-interval call count, total tool calls, total failed).

**What you need to do:**

* **No action required.** Both endpoints are backward-compatible. If you consume latency or tool performance analytics data, no client changes are needed.

</details>

<details>

<summary>v0.9.447 - Platform API: Emotion Trends and Call Comparison Now Served from Analytical Projection (July 2026)</summary>

#### Emotion Trends and Call Comparison Now Served from Analytical Projection

The emotion trends and call comparison analytics endpoints now read from the same live analytical projection used by call quality analytics, replacing the previous data source.

**What changed:**

* **Emotion trends served from analytical projection.** `GET /analytics/emotion-trends` now reads emotion distribution and valence/arousal trend data from the live analytical projection optimized for dashboard workloads. The endpoint continues to accept the same query parameters (`days`, `date_from`, `date_to`, `interval`, `service_id`) and returns the same response shape (`emotion_distribution` and `trend` arrays).
* **Call comparison served from analytical projection.** `GET /analytics/call-comparison` now reads period-over-period comparison metrics from the same analytical projection. The endpoint continues to accept the same query parameters (`current_from`, `current_to`, `previous_from`, `previous_to`, `service_id`) and returns the same response shape (`current`, `previous`, `delta`).
* **Non-production calls excluded.** Consistent with the call quality analytics endpoint, test and simulation calls are automatically filtered out of both endpoints.
* **No response format changes.** Both endpoints return the same response structure as before. Emotion trends returns `emotion_distribution` (dominant emotion counts) and `trend` (per-interval call count, average valence, and average arousal). Call comparison returns `current` and `previous` period summaries (total calls, average quality score, median and 95th percentile quality scores, average duration, escalation rate) and a `delta` section with absolute and percentage changes.

**What you need to do:**

* **No action required.** Both endpoints are backward-compatible. If you consume emotion trends or call comparison data, no client changes are needed.

</details>

<details>

<summary>v0.9.446 - Platform API: Typed Call Quality Analytics Response (July 2026)</summary>

#### Typed Call Quality Analytics Response

The call quality analytics endpoint now returns a structured, typed response with summary statistics, time-series trend data, and quality-score distribution - replacing the previous untyped dictionary response.

**What changed:**

* **Structured response model.** `GET /analytics/call-quality` now returns a `CallQualityAnalyticsResponse` object with three sections: `summary` (aggregate metrics for the period), `trend` (time-series data points per interval bucket), and `quality_distribution` (call counts bucketed by quality-score band).
* **Summary includes percentile and duration metrics.** The `summary` section now includes `avg_quality_score`, `p50_quality_score` (median), `p95_quality_score` (95th percentile), `total_calls`, `escalation_rate` (0.0-1.0), and `avg_duration_seconds`.
* **Trend series capped at 2200 data points.** The `trend` array contains one entry per interval bucket, each with `date`, `avg_quality`, `call_count`, and `escalation_count`.
* **Quality distribution buckets.** The `quality_distribution` section breaks call counts into four bands: excellent (90-100), good (70-89), fair (50-69), and poor (0-49).
* **Data source updated.** Call quality analytics are now served from a live analytical projection optimized for dashboard and reporting workloads. The endpoint continues to accept the same query parameters (`days`, `date_from`, `date_to`, `interval`, `service_id`).
* **Non-production calls excluded.** Test and simulation calls are automatically filtered out of analytics results.

**What you need to do:**

* **Update response parsing.** If you consume the call quality analytics endpoint, update your client code to use the new typed response structure (`summary`, `trend`, `quality_distribution`) instead of the previous untyped dictionary.

</details>

<details>

<summary>v0.9.445 - Platform API: Session-Ended World Event with Patient Link (July 2026)</summary>

#### Session-Ended World Event with Patient Link

Voice calls and text interaction bursts now emit a durable `session.ended` world event at session end, linking the conversation to the resolved patient entity. This event is the keying signal for Memory v2.

**What changed:**

* **Durable session-ended event emitted for voice and text.** When a voice call ends or a text interaction burst completes, the platform emits a `session.ended` world event on the conversation entity. The event carries the modality (voice or text), completion reason, turn count, and session end time.
* **Patient entity linked as a related entity.** When the session resolved a patient, the event includes the patient entity as a related entity, establishing the first durable conversation-to-patient link in the world event stream. Anonymous or unmatched callers still produce the event without a patient link, so lifecycle data is preserved regardless of patient resolution.
* **Idempotent per session lifecycle.** Each event uses a deterministic identifier derived from the workspace, conversation, and session instance, so retried cleanups of the same session do not produce duplicate events. Distinct interaction bursts within the same text conversation (for example, an idle timeout followed by a resumed session) emit separate events.
* **Fail-open.** A failure to emit the session-ended event does not block session cleanup. The failure is logged and metered for observability.

**What you need to do:**

* **No action required.** The event is emitted automatically for all voice calls and text sessions. If you consume world events for analytics or downstream processing, you can now key on `session.ended` events to track conversation-to-patient associations and session lifecycle metadata.

</details>

<details>

<summary>v0.9.444 - Platform API: Per-Case Scoring for Simulation Benchmark Runs (July 2026)</summary>

#### Per-Case Scoring for Simulation Benchmark Runs

Benchmark and suite case runs are now scored against the case's own success definition when one is present, instead of always falling back to the coarse terminal-state or max-turns bridge rubric.

**What changed:**

* **Per-case success definitions are now evaluated at run time.** Saved simulation cases (created by the scenario generator, seeded via the API, or authored manually) can carry a success definition in their metadata. When a case run is prepared, the platform resolves this definition and scores the run against it. Cases without a success definition continue to use the standard bridge scoring rubric.
* **Benchmark cases persisted by the scheduling benchmark seed now include their success definition.** Previously, benchmark seed cases stored grounding data and case metadata but omitted the success definition, so all benchmark runs fell back to the coarse rubric. Newly seeded benchmark cases now include their success definition automatically.
* **Malformed success definitions fall back gracefully.** If a case carries a success definition that cannot be parsed (for example, due to schema drift from a newer or older seed format), the run falls back to the standard bridge rubric and an error is logged. The run is never crashed by a bad success definition.

**What you need to do:**

* **No action required for existing cases.** Cases without a success definition continue to work as before. Cases that already carry a success definition in their metadata will now be scored against it automatically.
* **Re-seed benchmarks to pick up per-case scoring.** If you are running scheduling benchmarks seeded before this release, re-seeding the benchmark cases will include the success definition so future runs use per-case scoring instead of the coarse rubric.

</details>

<details>

<summary>v0.9.443 - Platform API: Lower-Variance Simulation Inference (July 2026)</summary>

#### Lower-Variance Simulation Inference

The simulated caller, AI judge, and metric scoring now use temperature 0 to reduce sampling variance across otherwise identical runs.

**What changed:**

* **Lower-variance simulated caller.** The simulated patient caller now uses temperature 0 for turn generation.
* **Lower-variance AI judge.** Assertion kinds that fall through to the AI judge are now evaluated at temperature 0.
* **Lower-variance metric scoring.** AI-evaluated metric scoring calls now use temperature 0.
* **No change to scenario generation.** Scenario-level diversity continues to come from the separate scenario-generation step.
* **No configuration required.** Temperature 0 is the default for these simulation inference calls.

**What you need to do:**

* **Do not treat temperature 0 as a reproducibility guarantee.** Model and infrastructure changes can still affect generated turns and model-judged verdicts. Use repeated runs and deterministic assertions when a stable regression signal is required.

</details>

<details>

<summary>v0.9.441 - Platform API: Short-Lived Access Token TTLs (July 2026)</summary>

#### Short-Lived Access Token TTLs

The token endpoint now accepts an optional `ttl_seconds` parameter that lets you request a shorter-lived access token on supported grant types.

**What changed:**

* **New `ttl_seconds` form parameter.** The `POST` token endpoint accepts an optional `ttl_seconds` integer parameter. When provided, the issued access token expires after the specified number of seconds instead of the default lifetime.
* **Allowed values.** `ttl_seconds` must be one of `60` (1 minute), `300` (5 minutes), or `900` (15 minutes). Any other value returns a `400` error with a message listing the allowed values.
* **Supported grant types.** The parameter is supported on `api_key`, `client_credentials`, `personal_access_token`, and `email_otp` (provider intent) grants.
* **Not supported for operator tokens.** Including `ttl_seconds` on an operator bearer token grant returns a `400` error. Operator tokens continue to use their fixed longer lifetime.
* **`expires_in` reflects the requested TTL.** The `expires_in` field in the token response matches the requested TTL when `ttl_seconds` is provided.

**Request parameter:**

| Parameter     | Type    | Required | Description                                                                                                                |
| ------------- | ------- | -------- | -------------------------------------------------------------------------------------------------------------------------- |
| `ttl_seconds` | integer | No       | Access token lifetime in seconds. Must be one of: `60`, `300`, `900`. Defaults to the standard token lifetime when omitted |

**What you need to do:**

* **No action required for existing integrations.** The parameter is optional and defaults to the existing behavior when omitted.
* **To use short-lived tokens,** include `ttl_seconds` in your token request form body. This is useful for scoped automation, CI/CD pipelines, and scenarios where minimizing token exposure is important.

</details>

<details>

<summary>v0.9.440 - Platform API: Justified AI Metric Verdicts in Simulation Eval Results (July 2026)</summary>

#### Justified AI Metric Verdicts in Simulation Eval Results

Simulation eval results for AI-evaluated metrics now include a justification and conversation turn references alongside the metric value, so you can see why a metric received its score and which parts of the conversation drove it.

**What changed:**

* **Justification on eval results.** When a simulation run evaluates an AI-scored metric, the eval result now includes a `justification` field containing a plain-language explanation of why the metric received its value, grounded in the conversation transcript. This is distinct from the existing `rationale` field, which describes the pass/fail threshold comparison.
* **Turn references on eval results.** Each justified eval result includes a `references` field - a list of 0-based conversation turn indices that the evaluation cited as supporting evidence for the value. This lets you trace a metric score back to specific moments in the conversation.
* **Two-tier compute for cost efficiency.** Only metrics that are referenced by an eval definition receive the justified computation (value + justification + references). All other active AI-evaluated metrics receive a lightweight bare-value computation that matches the batch pipeline output, keeping per-run cost proportional to what is actually surfaced.
* **Transcript-aware evaluation.** Justified metrics are evaluated against a turn-indexed transcript that includes caller and agent utterances, tool calls with outcomes, and state transitions. This gives the evaluation model visibility into tool usage and conversation flow, not just spoken text.
* **No change to metric values.** The metric value itself is computed using the same rules as the batch pipeline and the previous on-the-fly path. The justification is additional context, not a replacement for the value.

**Response schema additions:**

| Field           | Type              | Description                                                                                                                     |
| --------------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `justification` | string or null    | The model's explanation of the metric value, grounded in the transcript. Null for non-AI-evaluated metrics. Max 8000 characters |
| `references`    | array of integers | Turn indices (0-based) cited as evidence. Empty array for non-AI-evaluated metrics. Max 100 entries                             |

**What you need to do:**

* **Update response parsing.** If you consume simulation eval results, handle the new `justification` (string or null) and `references` (array of integers) fields. Both fields are always present on eval result objects.
* **No action required for existing evals.** Existing eval definitions and metric configurations work without changes. The justification is produced automatically for AI-evaluated metrics referenced by evals.

</details>

<details>

<summary>v0.9.439 - Platform API: Targeted Force-on-Demand Tool Selection for Atlas Voice (July 2026)</summary>

#### Targeted Force-on-Demand Tool Selection for Atlas Voice

The Atlas voice runtime now uses a per-turn tool selection strategy that forces the model to call a tool after each caller turn, then relaxes back to automatic selection once the tool fires. This addresses realtime model reluctance to volunteer tool calls under automatic selection.

**What changed:**

* **Forced tool selection after caller turns.** When a new caller message arrives during an Atlas voice call, the runtime arms forced tool selection for the model's next response. The model must emit a tool call rather than responding with speech alone. This ensures tools are reliably invoked when the caller's message warrants one.
* **Automatic relaxation after tool execution.** Once the forced tool call fires, the runtime immediately relaxes back to automatic selection. The response that delivers the tool result to the caller is generated under automatic selection, so the model speaks the result naturally rather than being forced into another tool call.
* **Per-call state isolation.** The forced/relaxed selection state is tracked per call, not globally. Concurrent calls do not interfere with each other's tool selection mode.
* **Fail-open behavior.** If the selection update fails for any reason, the call continues with the previous selection mode. A failed update never drops or interrupts the call.
* **Removed temporary probe.** The previous blanket forced tool selection mode (which forced a tool call on every turn regardless of context) has been removed. The new targeted approach forces only after caller turns and relaxes after each tool call.

**What you need to do:**

* **No action required.** This change is internal to the Atlas voice runtime. Tool behavior during Atlas voice calls will be more reliable - tools that were previously not called despite being registered and available should now fire consistently when the caller's message warrants a tool invocation.

</details>

<details>

<summary>v0.9.438 - Platform API: Document Ingestion Mode for Customer Data Intake (July 2026)</summary>

#### Document Ingestion Mode for Customer Data Intake

The customer data intake pipeline now supports document datasets alongside the existing structured (snapshot) datasets. Document datasets accept unstructured files such as PDFs, Word documents, and plain text, and process them through an asynchronous text extraction pipeline.

**What changed:**

* **Automatic ingestion mode from file type.** When you register a dataset, the platform infers the ingestion mode from the `file_type` you specify. Tabular types (`csv`, `xls`, `xlsx`) create a snapshot dataset with schema validation and change detection, as before. All other types (`pdf`, `docx`, `txt`, and others) create a document dataset with text extraction.
* **Document datasets have no schema or primary key.** Document datasets do not require `primary_key` or `schema` fields in the registration request. Instead, they accept an optional `document_processing` configuration that controls extraction behavior.
* **New `document_processing` configuration.** Document dataset registration accepts a `document_processing` object with two fields: `retain_source` (boolean, default `true`) controls whether the original file is retained after extraction, and `extraction_mode` (one of `text_extract`, `OCR`, or `hybrid`, default `text_extract`) selects the extraction strategy. When omitted, the platform applies sensible defaults.
* **New `ingestion_mode` field on dataset responses.** The dataset list and detail responses now include an `ingestion_mode` field (`snapshot` or `document`) so you can distinguish between the two dataset types.
* **Asynchronous document processing.** Uploaded documents are landed with a `received` status and processed asynchronously. The extraction pipeline produces per-page text files, a combined document text, and extraction metadata. The file status updates to `curated` on success or `failed` with a reason on extraction failure.
* **No change to snapshot datasets.** Existing snapshot (CSV/Excel) datasets continue to work exactly as before. The schema validation, change detection, and synchronous processing paths are unchanged.

**What you need to do:**

* **To use document ingestion,** register a dataset with a document file type (e.g. `pdf`, `docx`, `txt`). The platform automatically creates a document dataset. You can optionally include a `document_processing` configuration to control extraction behavior.
* **Update response parsing.** If you consume dataset list or detail responses, handle the new `ingestion_mode` field. Snapshot datasets return `"snapshot"` and document datasets return `"document"`.
* **No migration needed for existing datasets.** Existing snapshot datasets are unaffected.

</details>

<details>

<summary>v0.9.437 - Platform API: Reject Deterministic Fillers on Blocking Tools at Version Create (July 2026)</summary>

#### Reject Deterministic Fillers on Blocking Tools at Version Create

Creating a context graph version now validates that deterministic filler phrases are not configured on blocking tools, and returns an error if the combination is detected.

**What changed:**

* **Write-time validation for filler and execution mode.** When you create a new context graph version, the platform checks every tool in every state for deterministic filler phrases combined with blocking execution. Deterministic fillers only play when a tool runs in the background - a blocking tool is awaited inline on the speaker loop, so the scripted filler phrase can never be spoken before the result arrives. Previously, this misconfiguration was accepted silently and the filler phrases were never played at runtime.
* **422 error with actionable detail.** If any tool combines deterministic filler phrases with blocking execution, the create version request returns `422 Unprocessable Entity`. The error detail lists each offending state and tool pair and instructs you to set execution to `background` on those tools.
* **No change to existing versions.** This validation applies only when creating new versions. Existing published versions are not retroactively validated.

**What you need to do:**

* **Review tools with deterministic fillers.** If you have tools configured with scripted filler phrases (including audio fillers, which are normalized into progress phrases), ensure those tools use background execution. Tools with blocking execution and deterministic fillers will now be rejected at version creation time.
* **Update execution mode before publishing.** If version creation fails with this validation error, set `execution="background"` on the listed tools and retry.

</details>

<details>

<summary>v0.9.436 - Platform API: Per-Channel Voice Use Case Bindings and Customizable Voice URLs (July 2026)</summary>

#### Per-Channel Voice Use Case Bindings and Customizable Voice URLs

Voice use cases are now split into three distinct channel types with dedicated configuration, and voice URLs are now caller-supplied rather than derived from internal routing.

**What changed:**

* **Separate voice channel types.** The three voice channels - inbound voice, outbound voice, and ringless voicemail - are now distinct use case types, each with its own set of fields. Previously, all three shared a single voice binding with nullable fields. The split means each channel exposes only the fields relevant to it, eliminating ambiguity.
* **Customizable inbound voice URL.** Inbound voice use cases now accept a caller-supplied `inbound_voice_url` at creation time. When a phone number is assigned to the use case, this URL is written onto the phone number so inbound calls POST directly to whatever endpoint you configure. Previously, this URL was derived internally. The URL is editable via `PUT /v1/use-case/{id}`; changes take effect on the next phone number assignment.
* **Optional TwiML App URL.** Inbound and outbound voice use cases now accept an optional `twiml_app_url` field. When supplied at creation, the platform mints a per-use-case TwiML App with that URL. When omitted or set to null, no TwiML App is created. You can also create, update, or clear the TwiML App via `PUT /v1/use-case/{id}` after creation.
* **New response fields.** Inbound voice responses now include `inbound_voice_url`, `twiml_app_sid`, and `twiml_app_url`. Outbound voice responses include `twiml_app_sid` and `twiml_app_url`. Ringless voicemail responses include only the setup reference - no TwiML App fields, since ringless voicemail does not use a TwiML App.
* **Create request changes.** The create use case request body is now discriminated across five channel variants (inbound voice, outbound voice, ringless voicemail, SMS, email, iMessage) instead of grouping all voice channels into one. Each variant accepts only the fields relevant to that channel.
* **Update request changes.** The update use case request body is similarly split. Inbound voice updates accept `inbound_voice_url` and `twiml_app_url`. Outbound voice updates accept `twiml_app_url`. Ringless voicemail updates accept only `description`. Setting `twiml_app_url` to null on an update removes the TwiML App.
* **List and get responses.** The list and get use case endpoints return the channel-specific response shape, so consumers can rely on the `channel` discriminator to determine which fields are present.

**What you need to do:**

* **Update use case creation calls.** If you create voice use cases, update your request bodies to use the new per-channel format. Inbound voice use cases now require `inbound_voice_url` and accept an optional `twiml_app_url`. Outbound voice use cases accept an optional `twiml_app_url`. Ringless voicemail use cases require only the setup reference.
* **Update response parsing.** If you parse use case responses, handle the three separate voice channel response shapes. The `channel` field discriminates between them. Fields like `twiml_app_sid` and `twiml_app_url` are present only on inbound and outbound voice responses; `inbound_voice_url` is present only on inbound voice responses.
* **Update use case update calls.** If you update voice use cases, use the channel-specific update request shape. Inbound voice supports `inbound_voice_url` and `twiml_app_url`; outbound voice supports `twiml_app_url`; ringless voicemail supports only `description`.

</details>

<details>

<summary>v0.9.435 - Platform API: Simulation Eval Verdicts Per Conversation (July 2026)</summary>

#### Simulation Eval Verdicts Per Conversation

Simulation eval results are now emitted per conversation rather than as a single run-level verdict, giving you granular pass/fail outcomes for each conversation in a multi-conversation run.

**What changed:**

* **Per-conversation verdicts.** When a simulation run contains multiple conversations, each eval definition now produces one verdict per conversation instead of one verdict for the entire run. This means a run with 10 conversations and 3 evals produces up to 30 individual verdicts, each tied to its specific conversation. Previously, each eval produced a single run-level result that aggregated across all conversations.
* **Assertions scoped to conversation context.** Assertion evals (transcript checks, tool call checks, final state checks, and AI judge evaluations) are scoped to the turns and session data of each individual conversation. This eliminates false positives and negatives that occurred when assertions evaluated the combined transcript of all conversations.
* **Metric evals scoped to conversation.** Metric check evals resolve metric values per conversation, so each conversation's metric result is compared against the expected value independently.
* **Concurrent evaluation.** Conversations within a run are evaluated concurrently, so a run with many conversations does not incur serial evaluation latency. If one conversation's evaluation fails, the failure is isolated to that conversation's verdict - other conversations still receive their results.
* **Backward compatible for single-conversation runs.** Runs with a single conversation or runs with no observed conversations continue to produce a single verdict per eval, matching previous behavior.
* **Each verdict carries a conversation identifier.** Eval results returned in the run detail response include a conversation identifier on each verdict, so you can attribute results to specific conversations.

**What you need to do:**

* **Update eval result consumers.** If you parse eval results from the run detail endpoint, expect multiple results per eval definition when the run contains multiple conversations. Each result now includes a conversation identifier.
* **No configuration changes needed.** Per-conversation verdicts are automatic for all runs with multiple conversations. Existing eval definitions work without modification.

</details>

<details>

<summary>v0.9.434 - Platform API: Atlas Voice Runtime - Graph-Scoped Tool Selection (July 2026)</summary>

#### Atlas Voice Runtime - Graph-Scoped Tool Selection

The Atlas voice runtime now scopes the single agent's tool set to the tools the context graph actually references, rather than attaching the full workspace tool catalog.

**What changed:**

* **Graph-scoped tools.** The Atlas single-agent runtime now computes the union of tool references across all states in the compiled context graph and attaches only those tools to the agent. Previously, the agent received the full workspace tool set (skills, surface tools, and platform functions), which could include dozens of tools. With a large tool set, the model must choose one tool out of many on every turn, which reduces selection reliability. Scoping to the graph's declared tools narrows the working set to the handful the agent actually needs, improving tool selection accuracy.
* **Fail-open for sparse graphs.** If the context graph references no tools that match a deployed platform tool (for example, a graph that declares no tool references or references only tools that are not deployed), the agent falls back to the full workspace tool set. This ensures the agent is never left without tools.
* **Unresolved tool reference warnings.** When the context graph references a tool name that does not match any deployed platform tool - due to a typo, naming drift, or a disabled tool - the runtime logs a warning with the unresolved tool names. This makes binding gaps visible in monitoring rather than silently falling back to the full tool set.
* **Updated roster log.** The per-call tool roster log now includes whether graph-scoping narrowed the tool set and how many tool references were unresolved, giving operators a clear signal for diagnosing tool selection issues on live calls.

**What you need to do:**

* **No action required.** Tool scoping is automatic for all Atlas voice sessions that use a context graph. If your context graph correctly references the tools each state needs, the agent will receive exactly those tools. If your graph does not reference any tools, behavior is unchanged - the agent receives the full tool set as before.
* **Review context graph tool references.** If you see unresolved tool reference warnings in your monitoring, check that the tool names in your context graph states match the names of deployed platform functions, skills, or surface tools in your workspace.

</details>

<details>

<summary>v0.9.433 - Platform API: Atlas Voice Runtime - Cold-Start Telemetry Split (July 2026)</summary>

#### Atlas Voice Runtime - Cold-Start Telemetry Split

The Atlas voice runtime now emits a separate cold-start telemetry metric that isolates the connection phase from greeting generation, giving operators a precise breakdown of first-audio latency.

**What changed:**

* **Connection phase metric.** The Atlas voice runtime now emits a dedicated connection delay metric that measures the time from session start through the realtime connection handshake, before greeting generation begins. Previously, only the overall setup delay (session start to first audio) was reported, which combined connection time and greeting generation into a single number.
* **Precise latency breakdown.** The connection delay and the existing setup delay share the same start-time anchor, so subtracting the connection delay from the setup delay gives an exact greeting generation duration with no measurement skew. This split lets you identify whether first-audio latency is dominated by the connection handshake or by greeting generation.
* **Logged per call.** Each Atlas voice session logs the connection delay at session connect, alongside the existing setup delay logged at first audio. Both values appear in call-level diagnostics.

**What you need to do:**

* **No action required.** The new metric is emitted automatically for all Atlas voice sessions. If you monitor voice cold-start performance, you can now distinguish connection latency from greeting generation latency in your observability dashboards.

</details>

<details>

<summary>v0.9.432 - Platform API: Intake CDC Processing - Idempotent Retry and Reprocess (July 2026)</summary>

#### Intake CDC Processing - Idempotent Retry and Reprocess

The intake change-data-capture (CDC) processing job is now fully idempotent on retry and reprocess, and the catalog parameter allowlist is enforced at every destructive operation.

**What changed:**

* **Idempotent retry and reprocess.** The CDC processing job no longer advances the baseline pointer until all downstream writes - curated file output, CDC diffs, status write-back, and analytical catalog append - have committed successfully. Previously, the baseline pointer was updated before the run fully committed, which meant an automatic retry or manual reprocess would diff the new version against an already-advanced baseline and produce a zeroed-out diff. The baseline is now re-pointed only after all steps complete, so retrying or reprocessing a version produces the same correct diff every time.
* **Catalog allowlist enforced.** The processing job now validates the target catalog against an explicit allowlist of permitted environment catalogs before any destructive operation. This validation runs both at job startup and again at each destructive call site, so that manually re-running an individual notebook cell cannot bypass the check. Previously, the catalog parameter was validated only by character set, which would have accepted a well-formed but incorrect catalog name.
* **Duplicate-safe analytical catalog writes.** The analytical catalog append step now removes any prior rows for the current version before inserting, so a retry or reprocess replaces existing rows rather than appending duplicates. This makes the append step idempotent on the composite key of workspace, dataset, and version.

**What you need to do:**

* **No action required.** These changes fix incorrect behavior on retries and reprocesses. If you previously observed zeroed-out CDC diffs after a retry or reprocess, those runs will now produce correct results. No configuration or integration changes are needed.

</details>

<details>

<summary>v0.9.431 - Platform API: Intake Pipeline - Full Analytical Catalog Row and PHI Filename Exclusion (July 2026)</summary>

#### Intake Pipeline - Full Analytical Catalog Row and PHI Filename Exclusion

The async intake processing job now writes a complete file metadata row to the analytical catalog and excludes the original filename from the analytical layer for PHI protection.

**What changed:**

* **Full file metadata in analytical catalog.** The intake processing job now appends a complete file metadata row to the analytical catalog, including content type, file hash, size, ingestion timestamp, contract details, and processing paths. Previously, the analytical row contained only a subset of metadata fields. The expanded row makes intake data fully queryable for downstream analytics without joining back to the platform record.
* **PHI filename excluded from analytical catalog.** The original filename is no longer passed to or stored in the analytical catalog. Filenames are treated as protected health information and remain exclusively in the access-controlled platform record. This applies to the job parameters as well - the filename is not included in job run parameters, which may be visible in run management interfaces.
* **Explicit schema on catalog writes.** The analytical catalog writes now use explicit column schemas rather than relying on type inference. This prevents intermittent failures when processing results contain all-null columns (for example, pure insert or delete batches with no old or new values), which previously caused the job to fail on type inference.
* **Additional file metadata parameters.** The processing job now receives content type, file hash, size, ingestion timestamp, and optional contract metadata as parameters from the platform when triggered. These parameters populate the analytical catalog row directly.

**What you need to do:**

* **No action required for most users.** The analytical catalog now contains richer file metadata automatically. If you consume the analytical intake tables, the files table now includes additional columns for content type, file hash, size, ingestion timestamp, contract ID, contract version, schema fingerprint, and landing path.
* **If you depend on filename in the analytical catalog**, note that filename is no longer available there. Use the platform file detail endpoint to retrieve the filename when needed.

</details>

<details>

<summary>v0.9.430 - Platform API: Atlas Voice Runtime - Unified Model Configuration and Tool-Use Directive (July 2026)</summary>

#### Atlas Voice Runtime - Unified Model Configuration and Tool-Use Directive

The Atlas voice runtime now reads its backing model from the same environment-driven configuration as the real-time speech-to-speech runtime, and every Atlas phase agent receives an explicit tool-use directive in its system preamble.

**What changed:**

* **Unified model configuration.** The Atlas voice runtime previously used its own hardcoded model while the real-time speech-to-speech runtime followed the deployment's selected model. Atlas now reads the same deployment-level selection, so supported model changes apply consistently to both realtime providers.
* **Tool-use directive in system preamble.** The Atlas runtime now prepends a tool-use directive to every phase agent's system preamble. The directive instructs the model to call its attached tools for any information they can supply - appointment availability, records, scheduling, account or clinical details - rather than guessing, answering from memory, or telling the caller it cannot access the data. This addresses observed behavior where the model would decline to use an attached tool and instead claim it could not see the relevant information. The directive is phrased defensively so it remains accurate even when no tools are attached.

**What you need to do:**

* **No action required.** Atlas calls now follow the deployment's selected realtime model and retain the prior default when no override is configured. The tool-use directive is applied automatically to Atlas voice sessions.

</details>

<details>

<summary>v0.9.429 - Platform API: Automatic Async Intake Processing (July 2026)</summary>

#### Automatic Async Intake Processing

For workspaces with optional asynchronous intake enabled, an uploaded file can now start processing automatically. The processing path uses short-lived, run-scoped authorization instead of a customer-managed long-lived secret.

**What changed:**

* **Automatic start attempt after upload.** In an enabled workspace, landing a file attempts to start its asynchronous validation and materialization path without a separate client request.
* **Short-lived authorization.** Processing writes status back with scoped, short-lived authorization. Customers do not provision or rotate a long-lived processing secret.
* **Best-effort start.** If asynchronous processing does not start, the upload can still succeed and remain in `received` status for reconciliation or manual recovery. Poll file status rather than treating upload success as proof of processing completion.
* **Synchronous mode unchanged.** Workspaces without the optional asynchronous mode continue to validate uploads synchronously and return `curated` or `rejected`.

**What you need to do:**

* **No action required for most users.** Enabled workspaces attempt to start processing on upload without additional credential provisioning. Contact Amigo to confirm whether asynchronous intake is enabled for your workspace.
* **If you were manually starting processing**, that separate step is no longer necessary in an enabled workspace.

</details>

<details>

<summary>v0.9.428 - Platform API: Optional Async Intake Processing (July 2026)</summary>

#### Optional Async Intake Processing

Console file uploads to Customer Data Intake now support an optional asynchronous processing mode that Amigo enables per workspace.

**What changed:**

* **New processing mode for file uploads.** In an enabled workspace, a file uploaded through Console is stored and returned with status `received` instead of being immediately validated. Asynchronous processing performs conformance checks, validation, and diffing before writing the final `curated` or `rejected` verdict.
* **New `received` status.** The file status field can now return `received` in addition to `curated` and `rejected`. A file in `received` status has been persisted and is awaiting async processing. Poll the file detail endpoint to observe the final verdict.
* **Synchronous mode remains available.** Workspaces without asynchronous intake continue to validate uploads synchronously and return a terminal `curated` or `rejected` status immediately.
* **Deduplication unchanged.** Byte-identical re-uploads for the same dataset still return the existing file record regardless of processing mode.

**What you need to do:**

* **No action required for most users.** If you want asynchronous intake enabled for a workspace, contact your Amigo account team.
* **If you consume file status values**, update your integration to handle the `received` status. A successfully processed file transitions to `curated` or `rejected`; continue polling or use the documented recovery path when it remains `received`.

</details>

<details>

<summary>v0.9.427 - Platform API: Configurable Realtime Voice Model (July 2026)</summary>

#### Configurable Realtime Voice Model

The real-time speech-to-speech voice runtime now supports deployment-level model selection instead of one hardcoded model.

**What changed:**

* **Deployment-level model selection.** The realtime voice provider now reads the model selected for its deployment and falls back to the prior default when no override is configured. This lets Amigo roll out supported realtime model versions without changing the public call contract.
* **No API contract change.** Request and response schemas are unchanged. Conversation behavior remains model-dependent and can vary when Amigo selects a different supported model for the deployment.

**What you need to do:**

* **No action required.** The provider retains the same default model unless Amigo configures a supported deployment override. Contact Amigo when a workflow requires a different realtime model family.

</details>

<details>

<summary>v0.9.426 - Platform API: Trigger Pause and Resume Now Correctly Toggle Activation State (July 2026)</summary>

#### Trigger Pause and Resume Now Correctly Toggle Activation State

The trigger pause and resume operations now correctly flip the trigger's active state. Previously, pausing or resuming a trigger could silently fail to change the activation state because the request was routed through the general field-edit path, which does not modify lifecycle fields.

**What changed:**

* **Pause and resume are now dedicated lifecycle operations.** Pausing a trigger sets it to inactive, and resuming sets it to active. These operations are handled as distinct lifecycle transitions rather than general field updates, so the activation state change is always applied.
* **Next fire time is recomputed on resume.** When a trigger with a cron schedule is resumed, its next fire time is recalculated from the current moment. This means a resumed trigger starts firing from now rather than back-firing for any scheduled slots it missed while paused.
* **No change to the update endpoint.** The general trigger update endpoint continues to handle user-editable fields (name, schedule, and so on). Activation state is not modifiable through the update endpoint - use the pause and resume operations instead.

**What you need to do:**

* **No action required.** If you were calling the pause or resume endpoints and observing that triggers remained in their previous state, that behavior is now fixed. Pause and resume work as expected.
* **If you were working around this bug** by directly updating trigger fields to change activation state, you can remove that workaround and use the standard pause and resume operations.

</details>

<details>

<summary>v0.9.425 - Platform API: Trace Export Now Includes Voice Infrastructure Spans (July 2026)</summary>

#### Trace Export Now Includes Voice Infrastructure Spans

The `POST /traces:export` endpoint now exports per-call voice infrastructure spans alongside the existing tool-call spans, giving you end-to-end observability over both agent tool execution and voice isolation lifecycle events.

**What changed:**

* **Voice infrastructure spans included.** The trace export now returns `voice.*` infrastructure spans - allocation, media attachment, and teardown events for per-call voice isolation - in addition to the existing `gen_ai.*` tool-call spans. Both span families are exported as OTLP/HTTP JSON and follow the same allowlist-based attribute filtering.
* **New exported attributes.** Six new attributes are included in the allowlist for voice infrastructure spans: the allocated media server identity, the node it landed on, allocation latency, which media leg attached, the teardown reason, and the server lifetime. These are operational infrastructure attributes and never contain patient data.
* **Span naming for voice spans.** Voice infrastructure spans use their operation type as the span name directly (e.g. the allocation, attach, or reap operation), rather than the `<operation> <tool>` convention used for tool-call spans.
* **Zero-duration point events.** Voice infrastructure spans represent instants (allocation, attachment, teardown) rather than intervals, so they render as zero-duration point events in your trace viewer. This accurately reflects that these are discrete lifecycle moments, not timed operations.

**What you need to do:**

* **No action required.** If you are already consuming the trace export endpoint, voice infrastructure spans will appear automatically in your OTLP collector alongside tool-call spans. The response shape and pagination behavior are unchanged.
* **Update span filters if needed.** If your trace pipeline filters spans by name or attributes, you may want to add rules for the new voice infrastructure span names and attributes to route them appropriately in your observability tooling.

</details>

<details>

<summary>v0.9.424 - Platform API: Conversation List Performance Improvements (July 2026)</summary>

#### Conversation List Performance Improvements

The `GET /v1/{workspace_id}/conversations` endpoint is now significantly faster for workspaces with large call volumes. Two changes reduce list latency from multi-second to sub-second for typical page sizes.

**What changed:**

* **Concurrent source fetches.** Text and voice conversation sources are now fetched concurrently instead of sequentially. Previously, the unfiltered conversation list paid text latency plus voice latency in series; now both run in parallel.
* **Batched entity resolution.** Per-call entity metadata is now resolved in a single indexed lookup scoped to the current page of results, rather than joining against the full workspace entity dataset in the main query. Resolution time now scales with page size, not workspace size.

**What you need to do:**

* **No action required.** These are transparent performance improvements. Response shape, pagination, and filtering behavior are unchanged. You should observe faster response times on the conversation list endpoint, especially in workspaces with large numbers of calls.

</details>

<details>

<summary>v0.9.423 - Platform API: Status Write-Back Route and Delta Metadata Catalog for Customer Data Intake (July 2026)</summary>

#### Status Write-Back Route and Delta Metadata Catalog for Customer Data Intake

The customer data intake pipeline now includes a status write-back route that lets the async processing job advance a file to its terminal status, and a Delta metadata catalog that tracks file lifecycle, row-level changes, and schema changes across intake datasets.

**What changed:**

* **New status write-back endpoint.** `POST /v1/{workspace_id}/intake/files/{file_id}/status` advances a file's status after processing completes. The request body accepts `status`, `error_reason` (max 2000 characters, must not contain patient data), `curated_path`, and `cdc_path`. The processing job authenticates with the workspace's API key (Bearer), so the update is scoped to the correct workspace. Returns the updated file row on success, or 404 if the file does not exist in the workspace.
* **Delta metadata catalog.** Three new catalog tables track intake file lifecycle, row-level changes per dataset version, and schema changes. These tables provide a durable audit trail of every file's processing outcome and the data changes it introduced.
* **Processing job updated.** The async processing job now references the write-back route and catalog tables. The actual write-back call is activated in a follow-up cutover alongside secret provisioning and file-arrival trigger wiring - until then, processing verdicts are logged for observability.

**What you need to do:**

* **No action required for existing integrations.** The new endpoint and catalog are additive. The processing job does not call the write-back route until the cutover is complete, so existing workflows are unaffected.
* **If you are building custom intake integrations**, you can use the new status write-back endpoint to advance file status programmatically. Authenticate with a workspace API key and include only the fields relevant to your verdict (`curated_path` and `cdc_path` are optional and should be omitted for failed or held verdicts).

</details>

<details>

<summary>v0.9.422 - Platform API: HMAC-Signed Inbound Webhooks for Channel Bindings (July 2026)</summary>

#### HMAC-Signed Inbound Webhooks for Channel Bindings

Inbound-turn webhooks for messaging channel bindings (SMS, email, and iMessage) are now HMAC-signed with per-use-case secrets. Each messaging use case receives a signing secret at creation time, and every inbound-turn webhook POST includes one or more signature headers so the receiving service can verify authenticity.

**What changed:**

* **Webhook signing on create.** When you create an SMS, email, or iMessage use case, the response now includes a `webhook_secret` field containing the signing secret for that binding. This secret is returned exactly once at creation time and is not retrievable afterward - store it securely when you receive it.
* **Signature header on inbound webhooks.** Every inbound-turn webhook POST from a messaging channel binding now includes an `X-AMIGO-CHANNEL-MANAGER-WEBHOOK-SIGNATURE` header containing an HMAC-SHA256 hex digest computed over the request body using the use case's signing secret. Your webhook receiver should verify this signature to confirm that the request originated from the platform.
* **Secret rotation endpoint.** A new `POST /v1/use-case/{id}/webhook-secret` endpoint rotates the signing secret for a messaging use case. The response includes the new `webhook_secret` (returned once, not re-readable) and an `old_secret_lasts_until` timestamp. During the 30-minute grace window after rotation, the platform sends two signature headers - one signed with the new secret and one with the old - so your receiver can verify against either while you reconfigure. After the grace window expires, only the new secret is used.
* **Automatic secret rotation on webhook URL change.** When you update a use case's `webhook_url` via the update endpoint, the signing secret is automatically rotated with no grace window (since the destination itself changed, in-flight requests to the old URL are moot). The new secret is returned in the update response's `webhook_secret` field.
* **Secret cleanup on delete.** When a messaging use case is deleted, its signing secret is also removed.
* **Voice channels excluded.** Voice use cases (outbound voice, inbound voice, ringless voicemail) do not have webhook secrets because they do not POST inbound turns. Calling the rotation endpoint on a voice use case returns a 400 error.
* **Create response shape change.** The create use case response for messaging channels now includes `webhook_secret` and no longer includes `updated_at` (which was redundant with `created_at` for a just-created resource). The update and get responses continue to include `updated_at`.

**What you need to do:**

* **Capture the webhook secret on use case creation.** The secret is returned once in the create response. Store it in your secrets management system so your inbound webhook receiver can verify signatures.
* **Verify inbound webhook signatures.** Update your webhook receiver to compute an HMAC-SHA256 hex digest of the raw request body using the stored secret and compare it against the `X-AMIGO-CHANNEL-MANAGER-WEBHOOK-SIGNATURE` header. Reject requests where no header matches.
* **Handle dual signatures during rotation.** After calling the rotation endpoint, your receiver may see two signature headers for up to 30 minutes. Verify against both - accept the request if either signature matches.
* **Capture the new secret when updating webhook URLs.** If you change a use case's `webhook_url`, the response includes a new `webhook_secret`. Update your receiver configuration with the new secret immediately (there is no grace window for URL changes).

</details>

<details>

<summary>v0.9.421 - Platform API: Update Use Case Endpoint for Channel Bindings (July 2026)</summary>

#### Update Use Case Endpoint for Channel Bindings

A new `PUT /v1/use-case/{id}` endpoint allows you to update mutable fields on an existing use case without recreating it. This is particularly useful for rotating inbound webhook URLs on channel bindings.

**What changed:**

* **New update endpoint.** `PUT /v1/use-case/{id}` accepts a partial update for a use case. The request body is a discriminated union on `channel` (matching the create request shape), so the correct set of updatable fields is determined by the channel type. The `channel` field in the body must match the use case's existing channel - a mismatch returns a 409 error.
* **Updatable fields by channel.** The `description` field is updatable on all channel types (supply `null` to clear it, omit to leave it unchanged). SMS, email, and iMessage bindings also support updating `webhook_url` to rotate the inbound turn destination. Email bindings additionally support updating `sender_email_alias` (the display name rendered before the sender address in outbound messages).
* **Validation.** At least one updatable field besides `channel` must be provided. Description is limited to 2000 characters. The `webhook_url` must be a valid HTTP URL. The `sender_email_alias` must be 4-40 characters after whitespace trimming, or `null` to clear. Invalid requests return a 422 error.
* **Voice channels.** Outbound voice, inbound voice, and ringless voicemail use cases support updating `description` only. Voice binding resources (such as telephony application identifiers and region configuration) are immutable.
* **Response.** The endpoint returns the full use case response (matching the GET response shape) with the updated fields reflected.

**What you need to do:**

* **Use this endpoint to rotate webhook URLs.** If you need to change where inbound messages are forwarded for an SMS, email, or iMessage binding, send a PUT request with the new `webhook_url` instead of deleting and recreating the use case.
* **Include the `channel` field in every update request.** The channel is required to select the correct request variant, even though it cannot be changed.

</details>

<details>

<summary>v0.9.420 - Platform API: Pre-Warmed Media Routes for Per-Call Isolation (July 2026)</summary>

#### Pre-Warmed Media Routes for Per-Call Isolation

Isolated media servers now pre-register their routing identity at startup - before entering the warm pool - so their network route is live before the platform assigns them to a call. This closes a race condition where the telephony provider could attempt to connect the media stream before the route was populated, causing silent no-audio on the affected call.

**What changed:**

* **Route pre-warming.** Each isolated media server publishes its per-server routing identity at startup, before it signals readiness. The routing layer maps the server's address into the live route table while it is still in the warm pool, so by the time the platform allocates it for a call the route already exists. Previously, the route was created as a side-effect of allocation, leaving a brief window where the telephony provider's media-stream connection could arrive before the route was live.
* **Caller-aware orphan protection.** The self-reap watchdog that reclaims unused allocated servers now recognizes when a caller's media stream is already connected (waiting on hold music during preparation). A server with a connected caller is never reaped, even if the agent leg has not attached yet. If the caller disconnects before the agent leg connects (for example, by hanging up during hold), the timer re-arms and the server is still reclaimed - so pre-warming does not leak idle servers.
* **No change to the allocation API contract.** Callers of the allocation path receive the same response shape. The routing token is now read back from the server's self-assigned identity rather than being stamped at allocation time, but the returned host and token fields are unchanged.
* **No change to warm-pool sizing or autoscaling behavior.** The only difference is when the route becomes live (at server startup rather than at allocation).

**What you need to do:**

* **No action required.** The change is fully internal to the per-call isolation path. Calls that use per-call isolation benefit automatically. Calls on the shared path are unaffected.

</details>

<details>

<summary>v0.9.419 - Platform API: Fix Double-Encoding of REST Integration Path Parameters (July 2026)</summary>

#### Fix Double-Encoding of REST Integration Path Parameters

REST integration endpoint path parameters that contain special characters (such as Auth0 user IDs with `|` separators) are no longer double-encoded when the platform constructs the upstream request URL.

**What changed:**

* **Path parameter encoding fix.** When a REST integration endpoint path template contains a parameter with reserved characters (for example, `auth0|abc123`), the platform now preserves the single round of percent-encoding applied by the URI template expansion. Previously, the URL construction step re-encoded already-encoded characters - turning `%7C` into `%257C` - which caused upstream services to receive the literal `%7C` string instead of the intended `|` character. This caused key lookups to fail on services like Auth0 that use pipe-delimited identifiers.
* **Path template validation on create and update.** REST integration endpoint path templates are now validated at creation and update time to reject RFC 6570 reserved-expansion operators (`{+var}`, `{#var}`, `{.var}`, `{/var}`, `{;var}`, `{?var}`, `{&var}`). Only simple `{var}` placeholders are permitted. Reserved-expansion operators bypass percent-encoding of model-supplied values, which could allow path traversal. This validation ensures that all parameter values in the expanded path are safely encoded.
* **No change to existing endpoints with simple placeholders.** Endpoints that use only simple `{var}` path placeholders continue to work without modification.

**What you need to do:**

* **No action required for most users.** If you have REST integration endpoints with path parameters containing special characters (such as Auth0 `|` keys) that were failing, they should now work correctly without any changes to your configuration.
* **Review any endpoints using reserved-expansion operators.** If you have endpoint path templates that use operators like `{+var}` or `{/var}`, these will now be rejected on update. Replace them with simple `{var}` placeholders.

</details>

<details>

<summary>v0.9.418 - Platform API: Per-Binding Inbound Webhook URL for Channel Bindings (July 2026)</summary>

#### Per-Binding Inbound Webhook URL for Channel Bindings

Channel bindings (SMS, email, and iMessage) now support a configurable per-binding webhook URL that controls where inbound messages are forwarded. Previously, all inbound turns were routed to a single platform-wide destination. Each binding can now target a different consumer endpoint, enabling multi-tenant and multi-service routing patterns.

**What changed:**

* **New `webhook_url` field on channel binding creation.** When creating a use case with an SMS, email, or iMessage channel binding, a `webhook_url` field is now required. This URL receives the inbound turn payload for that binding. The receiver must implement the platform's inbound turn contract.
* **Per-binding routing.** Each channel binding routes its inbound messages independently. Two bindings on different use cases can point to different consumer endpoints, so a single deployment can fan out inbound traffic to multiple downstream services.
* **`webhook_url` in responses.** The `webhook_url` is returned on all use case read endpoints (get and list), so callers can confirm where each binding's inbound turns are routed.
* **No change to outbound behavior.** Outbound message sending is unaffected. Only the inbound forwarding destination is configurable.
* **Voicemail status forwarding removed.** The voicemail webhook no longer forwards status events to the platform API. Voicemail status transitions are persisted locally and logged, but are no longer relayed to a central endpoint.

**What you need to do:**

* **Include `webhook_url` when creating channel bindings.** All new SMS, email, and iMessage use case creation requests must include a `webhook_url` field containing a valid HTTPS URL. Requests without this field will be rejected.
* **Existing bindings are unaffected.** Previously created bindings have been backfilled to the prior default destination. No action is needed for existing use cases.

</details>

<details>

<summary>v0.9.417 - Platform API: OTLP Trace Export Endpoint (July 2026)</summary>

#### OTLP Trace Export Endpoint

A new read-only endpoint allows workspaces to export durable tool-call trace spans as OpenTelemetry Protocol (OTLP/HTTP JSON) spans, enabling external observability collectors and partner monitoring stacks to pull trace data from the platform.

**What changed:**

* **New trace export endpoint.** A paginated pull endpoint exports the workspace's durable `gen_ai.*` tool-call trace spans over a configurable time window as OTLP/JSON-encoded spans. The response contains standard OTLP `resourceSpans` envelopes that can be forwarded directly to any OTLP-compatible collector. The endpoint is read-only and never writes data.
* **Dark launch behind feature flag.** The endpoint is gated behind the `OTEL_TRACE_EXPORT_ENABLED` environment flag. When the flag is off, the endpoint returns 404. The endpoint is always registered so the API contract remains stable across environments.
* **Admin or owner role required.** Trace spans carry tool-call metadata, so the endpoint requires admin or owner credentials for parity with other sensitive data access paths.
* **PHI-safe by construction.** Exported attributes use a fail-closed allowlist of tool-call metadata fields (operation name, tool name, integration, endpoint, protocol, latency, success status, and call ID). Raw error text and any non-allowlisted fields are excluded, so no patient data leaves the trust boundary through this endpoint.
* **Paginated pull API.** The request accepts a time window (`start_time`, `end_time`), a page size limit (1-1000, default 500), and an opaque continuation token for paging. The response includes `has_more` and `continuation_token` fields for cursor-based pagination.
* **OTLP/JSON encoding.** Spans follow the OTLP/HTTP JSON encoding conventions: 64-bit integers and timestamps are decimal strings, trace IDs are 32 lowercase hex characters, span IDs are 16 lowercase hex characters, and each attribute value uses the typed `AnyValue` oneof encoding.
* **Resource and scope metadata.** Each response groups spans under a single resource with `service.name` and `amigo.workspace.id` attributes, scoped under the `amigo.world.trace` instrumentation scope.

**What you need to do:**

* **No action required.** The endpoint is dark by default. Contact your account team to enable OTLP trace export for your workspace if you want to integrate with an external observability collector.

</details>

<details>

<summary>v0.9.416 - Platform API: HIPAA Audit Parity for MCP Surface Tools and REST Data Query Invoke (July 2026)</summary>

#### HIPAA Audit Parity for MCP Surface Tools and REST Data Query Invoke

Surface configuration tools on the MCP server and the REST workspace data query invoke endpoint now emit HIPAA audit events, closing audit gaps between transport surfaces.

**What changed:**

* **MCP surface tool audit rows.** The `create_surface`, `reshape_surface`, and `create_surface_from_template` MCP tools now emit a per-invocation HIPAA audit event carrying the real session credential and MCP transport indicator. Previously, these tools were audited only under the system surrogate credential, making MCP-originated surface writes indistinguishable from REST API-key writes in the audit log. The new audit row is in addition to the existing surrogate row, so no existing audit data is lost.
* **REST workspace data query invoke audit.** The REST endpoint for invoking a workspace data query now emits a HIPAA audit event on every invocation - successful executions, not-found responses, and execution errors are all audited. The audit event records the workspace, query ID, actor credential, and pass/fail status. This brings the REST invoke path to parity with the MCP invoke path, which was already audited.
* **Cross-entity surface creation documented.** Surface configuration tools (create, reshape, create from template) intentionally allow the caller to target any entity within the workspace, matching the behavior of the REST surface creation endpoint. Only the outbound-contact delivery tool restricts the target to the caller's own entity. This is not a behavioral change - the cross-entity posture was already in effect - but it is now explicitly documented in the tool behavior.

**What you need to do:**

* **No action required.** Audit events are emitted automatically. Audit log consumers will see additional rows for MCP surface tool invocations and REST data query invocations. No configuration changes are needed.

</details>

<details>

<summary>v0.9.415 - Platform API: Write-Event Provenance and Audit Action Labels for MCP Clinical Writes (July 2026)</summary>

#### Write-Event Provenance and Audit Action Labels for MCP Clinical Writes

Clinical and operational write tools invoked through the MCP surface now carry correct provenance attribution and per-tool HIPAA audit action labels, ensuring that MCP-originated writes are distinguishable from voice-originated writes in both the event history and the audit log.

**What changed:**

* **MCP write provenance.** Clinical write events created through the MCP surface are now attributed to the MCP channel rather than the voice channel. This means event history and downstream systems can distinguish whether a schedule, cancellation, refill, insurance write, or other clinical action originated from a partner agent (MCP) or the in-house voice agent. The voice path is unchanged.
* **Outbound EHR sync for MCP writes.** MCP-originated clinical writes are now eligible for outbound EHR sync. Previously, writes from the MCP surface could silently fail to propagate to the connected EHR. Schedule, cancel, refill, insurance, and other clinical writes through MCP now sync to the EHR through the same connector pipeline used by voice-originated writes.
* **Per-tool HIPAA audit action labels.** Each clinical write tool now emits a HIPAA audit event with a tool-specific action label (e.g., a scheduling write is audited as a clinical scheduling action rather than a generic enrichment write). Surface delivery is audited with its own distinct action label. This makes it easier to filter audit logs by clinical action type.
* **No behavioral change for voice path.** The in-house voice agent path continues to work identically. Provenance attribution, outbound sync eligibility, and audit labels for voice-originated writes are unchanged.

**What you need to do:**

* **No action required.** If you have MCP write tools enabled, provenance and audit labels are applied automatically. Audit log consumers that filter by action label will now see more specific labels for clinical writes and surface deliveries.

</details>

<details>

<summary>v0.9.414 - Platform API: Clinical and Operational Write Tools on MCP Surface (July 2026)</summary>

#### Clinical and Operational Write Tools on MCP Surface

The MCP world-tools surface now supports 12 clinical and operational write tools, enabling external and partner agents to perform scheduling, patient management, insurance, medication, and operational actions through the same MCP server used for read tools. This is a dark launch behind a feature flag, available only to provider-principal sessions.

**What changed:**

* **12 write tools added to the MCP surface.** The MCP server now registers write tools for patient creation, patient updates, unified patient save (create-or-update), appointment scheduling, appointment cancellation, appointment confirmation, appointment rescheduling, insurance creation, prescription refill requests, call logging, triage logging, and ticket creation. These tools are available alongside the existing read tools on the same MCP server.
* **Provider-principal only.** Write tools are restricted to provider-principal sessions. Non-provider sessions cannot discover or invoke write tools.
* **Dark launch behind feature flag.** Write tools are registered behind the existing MCP feature flag for write operations. No separate enablement is required beyond the write tools flag.
* **Shared write logic.** The write tools execute the same logic used by the voice agent path, ensuring consistent behavior across both surfaces. Patient creation includes duplicate detection by identifier. Appointment scheduling supports slot references from prior search results. Insurance creation fires outbound sync to the connected EHR. Prescription refills reference the original prescription.
* **Dual-entity writes for appointments.** Appointment lifecycle operations (book, cancel, confirm, reschedule) write events to both the appointment entity and the linked patient entity, keeping both projections in sync.
* **Outbound EHR sync.** Write tools that create or modify clinical data fire outbound sync events, so changes propagate to the connected EHR through the standard connector pipeline. Triage logging is an exception - it records the triage outcome without firing outbound sync.
* **Confirmation-gated confidence.** Write tools support an optional confirmation level (confirmed, mentioned, or inferred) that maps to a confidence score on the written event. This enables downstream systems to distinguish between data the patient explicitly confirmed versus data mentioned in passing.

**What you need to do:**

* **No action required.** If you have MCP write tools enabled and are using provider-principal sessions, the 12 write tools are automatically available. No configuration changes are needed.

</details>

<details>

<summary>v0.9.413 - Platform API: HIPAA Audit Logging for MCP Read Surface (July 2026)</summary>

#### HIPAA Audit Logging for MCP Read Surface

Every PHI-bearing read invocation on the MCP world-tools read surface is now HIPAA-audited, matching the audit coverage already in place for MCP write operations.

**What changed:**

* **Per-invoke audit on world-model reads.** Each call to a world-model entity read tool through the MCP server now emits a HIPAA audit event recording the workspace, actor, tool name, and whether the call succeeded or failed. Both successful reads and failed reads are audited.
* **Per-invoke audit on workspace data query reads.** Each invocation of a workspace data query through the MCP read surface now emits a HIPAA audit event. Rejected invocations - such as attempts to run a non-read-only query through the read surface - are also audited with a failure status.
* **Discovery is not audited.** Listing available tools or queries (discovery operations) does not touch PHI and is not audited. Only actual data-returning invocations are logged.
* **Same audit pattern as writes.** Read audit events follow the same structure and delivery path as the existing write-side audit events, recording the transport, tool name, pass/fail status, and credential identifier.
* **No change in local development.** When the audit subsystem is not configured (typical in local development), read invocations continue without audit logging, matching existing write-side behavior.

**What you need to do:**

* **No action required.** If you have the MCP world-tools feature flag enabled, read invocations are now automatically audited. No configuration changes are needed. Audit events appear in the same audit log used by MCP write operations.

</details>

<details>

<summary>v0.9.412 - Platform API: Batch Enrichment Write Tool on MCP Surface (July 2026)</summary>

#### Batch Enrichment Write Tool on MCP Surface

The MCP world-tools write surface now supports a batch enrichment tool that writes multiple enrichment values to a single entity in one call, with all-or-nothing validation.

**What changed:**

* **New `put_entity_enrichment_many` tool.** Entity-anchored MCP credentials can now set several enrichment values on their anchor entity in a single call. The tool accepts a list of key-value items and validates every item against the workspace's enrichment registry before writing any of them. If any item fails validation, nothing is written.
* **Same anchor and scope rules.** The batch tool enforces the same single-entity anchor constraint as the existing `put_entity_enrichment` tool - the batch targets only the caller's own anchor entity and cannot span multiple entities.
* **Per-item optional fields.** Each item in the batch can optionally include `source`, `source_system`, and `confidence` fields. Value type is resolved from the enrichment registry, matching the behavior of the single-item tool.
* **Idempotency with payload awareness.** The batch tool accepts an `idempotency_key` like the single-item tool, but folds the full batch payload into the dedup key. This means a changed batch under the same idempotency key re-applies rather than returning the prior result, while an identical retry is safely deduplicated.
* **Audit logging.** Each batch call is audit-logged with the tool name and a comma-separated list of enrichment keys, following the same HIPAA audit pattern as single-item writes. Both successful and failed writes are recorded.
* **Dark launch.** This tool is registered behind the same feature flag as the existing MCP write tools. No separate enablement is required.

**What you need to do:**

* **No action required.** If you have already enabled MCP write tools, the batch enrichment tool is automatically available. External agents can use it to reduce round trips when setting multiple enrichment values on the same entity.

</details>

<details>

<summary>v0.9.411 - Platform API: Workspace Data Queries on MCP Read Surface (July 2026)</summary>

#### Workspace Data Queries on MCP Read Surface

The MCP world-tools read surface now includes workspace data queries (`wsq_*`) alongside the existing world-model entity read tools. External and partner agents can discover and invoke a workspace's registered read-only data queries through the same MCP server.

**What changed:**

* **Two new MCP tools registered.** When the world-tools MCP feature flag is enabled, the MCP server now registers `list_workspace_data_queries` and `invoke_workspace_data_query` in addition to the existing world-model read tools. These tools let external agents discover available read-only queries and run them by name with typed parameters.
* **Read-only enforcement.** Only queries whose SQL is a single read-only statement (SELECT or WITH) are listed or runnable through this surface. Queries with write capabilities are excluded from discovery and rejected at invoke time even if called by name. Read-only access is also enforced at the database transaction level as a second safety layer, blocking any write that the static SQL analysis cannot detect.
* **Workspace-scoped execution.** Query execution is constrained to the authenticated workspace, matching the behavior of the REST invoke endpoint. The workspace is determined from the authenticated MCP context and is never caller-supplied.
* **Same feature flag.** These tools are registered behind the same `MCP_WORLD_TOOLS_ENABLED` feature flag that governs the world-model entity read tools. No separate enablement is required.

**What you need to do:**

* **No action required.** If you have already enabled the MCP world-tools feature flag, workspace data queries are automatically available on the read surface. Only read-only queries are exposed - no configuration changes are needed to protect write-capable queries.

</details>

<details>

<summary>v0.9.410 - Platform API: Per-Call Media Stream Host Routing (July 2026)</summary>

#### Per-Call Media Stream Host Routing

When a call uses the per-call media isolation path, the TwiML media stream endpoint now routes to the allocated per-call media server rather than the shared host. This is the activation step for the routing information stored during call setup (v0.9.409).

**What changed:**

* **Media stream host follows per-call allocation.** For calls on the per-call isolation path, the media stream URL in the generated TwiML now points to the allocated per-call media server. Previously, the allocated routing was stored but not consumed - this release activates consumption. All call legs (caller, operator, and agent) use the same resolution, so media streams for a given call are routed consistently.
* **No change for non-allowlisted workspaces.** Workspaces not on the per-call routing allowlist continue to use the default host derived from the incoming request. The generated TwiML is byte-identical to previous behavior when the per-call path is not active.
* **Dark launch.** This change is deployed behind the existing workspace allowlist gate. Only workspaces already enrolled in the per-call routing allowlist are affected. The routing change is transparent to API consumers - no changes to request or response formats.

**What you need to do:**

* **No action required.** This is an infrastructure change with no visible effect on API behavior. Workspaces on the per-call routing allowlist will begin routing media streams to the allocated per-call server. No API, SDK, or configuration changes are needed.

</details>

<details>

<summary>v0.9.409 - Platform API: Per-Call Media Server Allocation at Call Setup (July 2026)</summary>

#### Per-Call Media Server Allocation at Call Setup

The per-call media routing path now allocates and pins an isolated media server when the agent leg is created, rather than deferring allocation to a later stage. This ensures the call's media routing is determined before the agent leg joins the conference.

**What changed:**

* **Allocate-then-pin at agent leg creation.** When a call enters the per-call isolation path (workspace is allowlisted and per-call routing is enabled), the platform now allocates an isolated media server and pins its routing information during agent leg setup. The pinned routing is stored alongside the call's cached conference data and forwarded to downstream webhooks.
* **Idempotent across retries.** If the telephony provider retries the agent leg creation (for example, due to a timeout), the previously allocated server and routing are reused from the call's cached data. A retry never allocates a second server, preventing orphaned resources.
* **Graceful failure on pool exhaustion.** If the media server pool is exhausted or the allocation fails, the agent leg returns a retryable failure rather than a hard error. The call follows the existing retry contract, and callers experience a retry rather than a dropped call. A metric is emitted for observability.
* **No change for non-allowlisted workspaces.** Workspaces not on the per-call routing allowlist continue on the legacy path. The call setup parameters and cached conference data remain byte-identical to previous behavior when the per-call path is not active.
* **Dark launch.** This change is deployed as a dark feature. The allocated routing information is stored and forwarded but is not yet consumed by downstream media stream endpoints. A follow-up release will activate consumption.

**What you need to do:**

* **No action required.** This is a dark infrastructure change with no visible effect on API behavior or call quality. Workspaces on the per-call routing allowlist will begin allocating media servers at agent leg setup, but the allocation is not yet consumed downstream.

</details>

<details>

<summary>v0.9.408 - Platform API: Workspace-Scoped Voice Assignment Gate (July 2026)</summary>

#### Workspace-Scoped Voice Assignment Gate

The voice session assignment gate now supports workspace-level scoping, allowing per-call media routing to be enabled for a subset of workspaces rather than all-or-nothing.

**What changed:**

* **Workspace allowlist for voice assignments.** The per-call media routing gate can now be narrowed to a specific set of workspaces. When the allowlist is configured, only calls for workspaces in the allowlist take the per-call isolation path. Workspaces not in the allowlist continue on the legacy path. When the allowlist is empty or not configured, the global toggle governs all workspaces as before.
* **Graduated rollout support.** This enables graduated rollout of per-call media routing across your deployment. You can enable per-call routing for individual workspaces, validate behavior, and expand the allowlist incrementally before enabling it globally.
* **Startup validation.** If the workspace allowlist is configured but the per-call routing toggle is off, the service rejects the configuration at startup. The allowlist only narrows an already-enabled gate - it cannot enable routing on its own.
* **No change to default behavior.** If you are not using the workspace allowlist, per-call media routing behavior is unchanged. The global toggle continues to govern all workspaces.

**What you need to do:**

* **No action required for most deployments.** If you are not configuring workspace-scoped routing, behavior is unchanged.
* **For graduated rollouts:** Configure the workspace allowlist with the workspace IDs that should use per-call media routing. The global per-call routing toggle must also be enabled. Workspaces not in the allowlist will continue on the legacy path until they are added or the allowlist is cleared (at which point all workspaces use per-call routing).

</details>

<details>

<summary>v0.9.407 - Platform API: Atlas Call Intelligence Envelope Drops Quality Score (July 2026)</summary>

#### Atlas Call Intelligence Envelope Drops Quality Score

The lightweight call intelligence envelope emitted for Atlas voice calls no longer includes a quality score. Previously, the envelope carried a placeholder zero quality score, which could pollute aggregate quality metrics (such as average voice quality score) with a fake value. The quality score field is now omitted entirely, so envelope-only calls are excluded from quality score aggregations.

**What changed:**

* **Quality score omitted from envelope.** The call intelligence envelope emitted at the end of Atlas voice calls no longer includes a quality score. The field is left unset rather than carrying a placeholder zero. This means Atlas calls that have only an envelope record (no full post-call analysis) are excluded from quality score aggregations instead of dragging the average down with a fake zero.
* **No change to list membership.** Atlas voice calls continue to appear in conversation listings. The envelope still carries turn count, duration, completion reason, direction, service, and final state - all the fields needed for list membership.
* **No change to analysis summaries.** The envelope continues to carry empty analysis summaries, as before. Full post-call analysis for Atlas calls is planned as a follow-up.

**What you need to do:**

* **No action required.** The change is applied automatically. If you previously observed Atlas calls contributing a zero quality score to aggregate metrics, those calls are now excluded from quality score calculations.
* **Quality scores remain unavailable for Atlas envelope-only calls.** Atlas calls that have only an envelope record will show no quality score rather than a zero. Full call intelligence analysis for Atlas calls will be available in a future release.

</details>

<details>

<summary>v0.9.406 - Platform API: Atlas Voice Calls in Conversation Listings (July 2026)</summary>

#### Atlas Voice Calls in Conversation Listings

Atlas voice calls now emit a minimal call intelligence record at the end of each call, so they appear in conversation listings alongside in-house pipeline calls. Previously, Atlas calls emitted lifecycle events (start/end) but did not produce a call intelligence record, which meant they could be missing from views that derive call membership from intelligence data.

**What changed:**

* **Call intelligence envelope emitted for Atlas calls.** When an Atlas voice call ends, the platform now emits a lightweight call intelligence record containing the fields needed for conversation list membership: turn count, duration, completion reason, direction, service, and final state. This record is emitted independently of the call-ended event, so a failure in one does not block the other.
* **Calls appear in conversation listings.** Atlas voice calls now surface in conversation listings (including the Agent Forge `forge conversation list` command and Developer Console call logs) even when the call has no full post-call analysis. The listing entry includes turn count, duration, and completion reason.
* **No analysis data in the envelope.** The envelope record carries empty analysis summaries and omits quality scores, so envelope-only calls do not affect aggregate quality metrics. It is not a substitute for full call intelligence analysis - it exists solely to ensure list membership. Full post-call analysis for Atlas calls is planned as a follow-up.
* **Fail-open design.** If the call intelligence envelope cannot be emitted (for example, due to a transient service issue), the voice call completes normally. The envelope emission is best-effort and never blocks or drops a live call.

**What you need to do:**

* **No action required.** Atlas voice calls now automatically appear in conversation listings. No configuration changes are needed.
* **Quality scores are not available on envelope-only calls.** The envelope record does not include a quality score or analysis summaries. Do not expect quality assessments on Atlas calls until full call intelligence analysis is available in a future release.

</details>

<details>

<summary>v0.9.405 - Platform API: Atlas Per-State Tool Visibility and Reluctance Fix (July 2026)</summary>

#### Atlas Per-State Tool Visibility and Reluctance Fix

The Atlas runtime now surfaces each state's available tools by name in the compiled state-machine prompt and explicitly instructs the model that all listed tools are available and working. This eliminates a class of live-call reluctance where the model would decline to use an attached tool or claim it could not access a system the tool covers.

**What changed:**

* **Per-state tool lists in the prompt.** Each conversation state in the compiled Atlas prompt now includes a `tools` field listing the tool names available in that state. The model sees exactly which tools it can call at each point in the conversation, removing ambiguity about tool availability.
* **Explicit anti-reluctance instruction.** The state-machine prompt now instructs the model that all listed tools are genuinely available and working on the current call. The model is forbidden from telling the caller it cannot do something, cannot access a system, or does not have access to information that one of its tools provides. The model must call the tool instead.
* **No configuration changes.** The per-state tool visibility and anti-reluctance instruction are applied automatically to all Atlas voice sessions. No changes to context graphs, skills, or service configuration are needed.

**What you need to do:**

* **No action required.** The prompt changes are applied automatically. If you previously observed the model declining to use available tools or claiming it lacked access to systems during Atlas voice calls, those behaviors should no longer occur.

</details>

<details>

<summary>v0.9.404 - Platform API: Atlas In-Flight Write-Tool Deduplication (July 2026)</summary>

#### Atlas In-Flight Write-Tool Deduplication

Atlas voice sessions now guard against double-writes during live calls. If the model re-invokes an identical write tool (same tool name and arguments) while a previous invocation is still executing, the duplicate is short-circuited instead of executing a second time. This prevents life-critical double-writes for operations such as scheduling, insurance updates, and medication changes.

**What changed:**

* **In-flight write deduplication.** When the model calls a write tool during an Atlas voice session, the runtime tracks the call. If the model issues an identical write call (same name and arguments) before the first completes, the duplicate receives an immediate response indicating the operation is already in progress. The model is instructed not to retry.
* **Per-call scope.** The deduplication guard is scoped to each individual call. Each call tracks its own in-flight writes independently, so concurrent calls do not interfere with each other.
* **Automatic release.** Once the original write tool invocation completes (whether it succeeds or fails), the guard is released. A later identical write call will execute normally.
* **Write tool set from platform configuration.** The set of tools considered "write" tools is the same set used by the platform's existing tool execution safety controls. No additional configuration is needed.
* **No behavior change for read tools.** Tools not classified as write tools are unaffected and can be called concurrently without deduplication.

**What you need to do:**

* **No action required.** The in-flight write deduplication is applied automatically to all Atlas voice sessions. No configuration changes are needed.
* **Review write tool classifications.** If you have custom tools that perform writes but are not classified as write tools, ensure they are correctly classified so the deduplication guard applies to them.

</details>

<details>

<summary>v0.9.403 - Platform API: Atlas Voice Call Lifecycle (July 2026)</summary>

#### Atlas Voice Call Lifecycle

Atlas voice calls now participate in the full call lifecycle. Each Atlas voice call mints a call entity and emits start and end events, so Atlas calls appear in conversation listings, entity timelines, and duration reporting alongside in-house pipeline calls.

**What changed:**

* **Call entity created per call.** Every Atlas voice call now creates a call entity when the call begins, matching the behavior of the in-house voice pipeline. The call entity is projected to entity timelines and conversation listings.
* **Start and end events emitted.** Atlas voice calls emit call-started and call-ended events with duration, turn count, and completion reason. These events flow through the same world-model path as in-house calls, so Atlas calls have full duration accounting and completion tracking.
* **Calls appear in conversation listings.** Atlas voice calls now surface in conversation listings (including the Agent Forge `forge conversation list` command and Developer Console call logs) with the same metadata as in-house pipeline calls.
* **Fail-open design.** If the call entity or lifecycle event cannot be emitted (for example, due to a transient service issue), the voice call continues without interruption. Lifecycle events are best-effort and never block or drop a live call.
* **Completion reason tracking.** The call-ended event records whether the call completed normally, timed out, or ended due to an error, giving you clear diagnostics in call history.

**What you need to do:**

* **No action required.** Atlas voice calls now automatically appear in conversation listings and entity timelines. No configuration changes are needed.
* **Expect Atlas calls in conversation listings.** If you were previously running Atlas voice calls, those calls will now appear in conversation listings and entity timelines. Review your call history to confirm Atlas calls are surfacing as expected.

</details>

<details>

<summary>v0.9.402 - Platform API: Atlas Voice Billing Metering (July 2026)</summary>

#### Atlas Voice Billing Metering

Atlas voice sessions now emit billable token-usage events for every model response during a call. This brings Atlas voice billing to parity with the in-house voice pipeline - Atlas calls appear in standard usage reporting and are metered like any other conversation turn.

**What changed:**

* **Per-response billing events.** Every model response during an Atlas voice call now emits a billable audio token-usage event. The event captures input tokens, output tokens, and cached input tokens for the response, and is tagged with the call session and workspace.
* **Parity with in-house pipeline billing.** Atlas voice calls now flow through the same usage-metering path as in-house pipeline calls. Usage appears in the same reports and dashboards, so billing visibility is consistent across all voice runtimes.
* **No behavior change.** Call handling, audio output, and agent execution are unchanged. Billing events are emitted asynchronously and never block or interrupt the audio stream.

**What you need to do:**

* **No action required.** Atlas voice calls are now metered automatically. Usage will appear in your standard billing reports alongside other conversation turns.
* **Expect Atlas voice usage in reports.** If you were previously running Atlas voice calls in a dark or preview configuration, those calls will now generate billable usage events. Review your usage dashboards to confirm Atlas call volumes.

</details>

<details>

<summary>v0.9.401 - Platform API: Atlas Voice Tool Roster Logging (July 2026)</summary>

#### Atlas Voice Tool Roster Logging

Atlas voice sessions now log the full list of attached platform tools at the start of every session. This gives operators a clear signal to distinguish "the model chose not to use an available tool" from "the tool was never attached to the session" when reviewing live or historical calls.

**What changed:**

* **Tool roster logged at session start.** When an Atlas voice session begins, the platform logs the names and count of all platform tools (skills and surface tools) attached to the agent. Tool names are configuration data, not patient data, so the log entry is PHI-safe.
* **Debugging tool binding gaps.** If a call review shows a tool was never invoked, the roster log confirms whether the tool was present on the session. A missing tool in the roster indicates a binding or provisioning gap rather than model reluctance.
* **No behavior change.** Tool attachment and agent execution are unchanged. This is an observability improvement only.

**What you need to do:**

* **No action required.** The tool roster is logged automatically on every Atlas voice session. No configuration changes are needed.
* **Use the roster for debugging.** When investigating why a tool was not used during a call, check the session start log for the tool roster to confirm the tool was attached.

</details>

<details>

<summary>v0.9.400 - Platform API: Atlas Single State-Machine Agent for Voice (July 2026)</summary>

#### Atlas Single State-Machine Agent for Voice

Atlas voice now uses a single state-machine agent architecture instead of multi-agent handoffs. The context graph is compiled into one agent whose prompt embeds the conversation states and in-prompt transitions, and the model navigates them in-context without handoff tools. This eliminates the transfer-tool reluctance that caused voice calls to stall in the entry phase.

**What changed:**

* **Single agent with embedded states.** Atlas voice calls now run as a single agent whose system prompt contains all conversation states, their instructions, and transition conditions. The model moves between states naturally during the conversation without invoking transfer tools. This is the recommended architecture for single-task voice agents.
* **No handoff tools on voice.** Transfer tools are no longer generated for Atlas voice calls. The model navigates states in-context using the transition conditions embedded in its prompt, which removes the transfer-tool reluctance that left the model parked in the entry phase during live calls.
* **Full platform tool set attached.** The complete platform tool set (skills and surface tools) is attached directly to the single agent, consistent with previous behavior.
* **Multi-agent handoffs preserved for cross-specialist branching.** The multi-agent handoff architecture remains available for scenarios that require genuine cross-specialist branching (multiple independent agents with different capabilities). Single-task voice calls use the state-machine form.
* **State-machine prompt format.** The prompt follows a structured format: a persona/guidelines preamble, a conversation flow guide explaining how to navigate states, a start state identifier, and the states rendered as structured data with IDs, instructions, and transition conditions.

**What you need to do:**

* **No action required.** Atlas voice calls automatically use the single state-machine agent architecture. No configuration changes are needed.
* **Expect improved state navigation.** If you observed Atlas voice calls stalling in the entry phase because the model would not invoke transfer tools, this update addresses the root cause by removing the need for transfer tools entirely.

</details>

<details>

<summary>Current Platform API Contract Summary</summary>

#### Current Platform API Contract Summary

The current Platform API OpenAPI contract is the source of truth for exact routes and schemas.

* Text sessions use `WS /v1/{workspace_id}/sessions/connect` with `Sec-WebSocket-Protocol` authentication.
* SMS opt-in consent enforcement: US numbers with A2P campaigns require opt-in before sending, and opt-out (STOP/HELP) keywords are managed by the platform.
* US/CA toll-free numbers: STOP, START, and UNSTOP keywords are handled by the platform. STOP creates an opt-out; START/UNSTOP reverses it. Keyword messages are never forwarded to the agent.
* SMS opt-out enforcement applies to all number types (toll-free, long code, short code). If a recipient has opted out, the send endpoint returns 422.
* SMS opt-outs are permanent from the platform's perspective - an opt-in resend cannot reverse a STOP. Re-subscription requires a fresh, recipient-initiated consent path (or START/UNSTOP for toll-free). The full opt-out history is retained for audit.
* Ringless voicemail is configured through channel use cases, service bindings, and Amigo-managed channel infrastructure; there are no current public workspace-scoped voicemail send/list endpoints.
* Public integration management is REST integration CRUD plus child endpoint CRUD and per-endpoint test operations; endpoints are addressed by endpoint ID.
* Desktop integrations are system-provisioned and are not created, updated, or deleted through the public REST integrations CRUD surface.
* Unified run reads use `GET /v1/{workspace_id}/runs`, `GET /v1/{workspace_id}/runs/{run_id}`, and `GET /v1/{workspace_id}/runs/{run_id}/trajectory`; the former conversation-only and framework-only list endpoints are retired.
* Non-voice operator takeover can stage the next reply with `POST /v1/{workspace_id}/runs/{run_id}/authored-turn`.
* API-key creation clients can discover the current role and permission matrix from `GET /v1/{workspace_id}/api-keys/permission-catalog`.
* External-user identity is linked to a world entity through the external identity bindings API and resolved by stable subject key at conversation start.
* Active event-based triggers match live workspace events at most once, without replay or later reconciliation.
* Real-time voice controls live under `voice_config.realtime`; the older `realtime_voice` shortcut is deprecated.
* Scribe sessions support generated notes, summaries, and checklists, plus note finalization.
* Historical Classic conversation metadata can be imported through `POST /v1/{workspace_id}/world/migration/conversations`.
* Conversation detail supports `include_tool_calls=true` for per-turn tool-call details, including failure messages when available.
* Skill `input_schema` create/update validation enforces the supported LLM tool-schema subset. The same validation also applies to each static tool's `input_schema` on skill create and update.
* Skills support optional `temperature` (0-1) and `top_p` (0-1) sampling parameters. Model-gated; set at most one.
* `ChannelKind` includes `imessage`.
* Use case service bindings accept `sms` and `imessage` channels.
* Outbound text endpoint supports `channel_kind='imessage'` with required `use_case_id`.
* A2P campaign submit and response no longer include `subscriber_opt_in` - the platform always treats campaigns as having subscriber opt-in.

</details>

## Versioning

Numbered Amigo API releases use [Semantic Versioning](https://semver.org/) identifiers. The current numbered release is **v0.9.544**. While the release line remains pre-1.0, minor releases can contain breaking changes; review this changelog and the current OpenAPI schema before upgrading.

### Independent Release Lines

The Amigo API, Agent Forge CLI, and SDK packages version independently. Agent Forge is optional client tooling, not a runtime requirement for direct API integrations. See the [Change Logs index](/api-reference/change-logs.md#current-sdk-packages) for current SDK versions and the [Agent Forge CLI changelog](/api-reference/change-logs/agent-forge.md) for its current release line.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.amigo.ai/api-reference/change-logs/amigo-api.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
