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

# API History: Archive 8

Retained API history, archive 8, with original entry headings and links back to current integration guidance.

Historical entries retain their original release/source context. Use [Current Upgrade Guidance](/api-reference/change-logs/upgrade-guide.md) for current integration boundaries and [API](/api-reference/change-logs/amigo-api.md) for the recent history and archive index. An older entry does not establish current availability.

<details>

<summary>v0.9.482 - Platform API: Channel-Manager SMS Transport (July 2026)</summary>

#### Channel-Manager SMS Transport <a href="#channel-manager-sms-transport" id="channel-manager-sms-transport"></a>

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 <a href="#test-credential-allowlist-for-text-sessions" id="test-credential-allowlist-for-text-sessions"></a>

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 <a href="#google-drive-source-sync" id="google-drive-source-sync"></a>

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 <a href="#google-drive-intake-source-registration-and-batch-tracking" id="google-drive-intake-source-registration-and-batch-tracking"></a>

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 <a href="#patient-intake-fields-in-connector-runner-sync" id="patient-intake-fields-in-connector-runner-sync"></a>

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 <a href="#patient-contact-enrichment-in-connector-runner-sync" id="patient-contact-enrichment-in-connector-runner-sync"></a>

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 <a href="#oauth2-scope-enforcement-on-channel-manager-routes" id="oauth2-scope-enforcement-on-channel-manager-routes"></a>

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 <a href="#at-least-once-delivery-for-connector-runner-clinical-data-sync" id="at-least-once-delivery-for-connector-runner-clinical-data-sync"></a>

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 href="#live-voice-fleet-capacity-status-endpoint" id="live-voice-fleet-capacity-status-endpoint"></a>

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 <a href="#fail-closed-resource-indicator-validation-on-token-grants" id="fail-closed-resource-indicator-validation-on-token-grants"></a>

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 <a href="#observer-rate-limiting-fix-for-rejected-connections" id="observer-rate-limiting-fix-for-rejected-connections"></a>

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 <a href="#channel-manager-oauth2-enforcement-and-list-endpoint-removal" id="channel-manager-oauth2-enforcement-and-list-endpoint-removal"></a>

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 <a href="#memory-extraction-v1-prompt-gate-suppress-and-floor-controls" id="memory-extraction-v1-prompt-gate-suppress-and-floor-controls"></a>

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 <a href="#test-caller-recognition-and-permission-gate" id="test-caller-recognition-and-permission-gate"></a>

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 <a href="#feature-flag-rollout-substrate-health-endpoint" id="feature-flag-rollout-substrate-health-endpoint"></a>

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 <a href="#oauth2-token-issuance-endpoint" id="oauth2-token-issuance-endpoint"></a>

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 <a href="#multi-type-document-schemas-for-customer-data-intake" id="multi-type-document-schemas-for-customer-data-intake"></a>

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 <a href="#memory-extraction-error-tracking" id="memory-extraction-error-tracking"></a>

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 <a href="#document-owned-version-history-for-customer-data-intake" id="document-owned-version-history-for-customer-data-intake"></a>

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 <a href="#fail-closed-external-principal-session-correlation" id="fail-closed-external-principal-session-correlation"></a>

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 <a href="#oauth-2.1-resource-server-plumbing-for-the-mcp-boundary" id="oauth-2.1-resource-server-plumbing-for-the-mcp-boundary"></a>

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 <a href="#shadow-rollout-defaults-graduated-to-on" id="shadow-rollout-defaults-graduated-to-on"></a>

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 <a href="#batch-entity-resolver-and-always-on-mcp-phi-audit" id="batch-entity-resolver-and-always-on-mcp-phi-audit"></a>

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 <a href="#runtime-agnostic-control-structure-invariant-shadow" id="runtime-agnostic-control-structure-invariant-shadow"></a>

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 <a href="#scheduling-precondition-shadow" id="scheduling-precondition-shadow"></a>

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 <a href="#runtime-agnostic-shadow-guardrail-evaluation" id="runtime-agnostic-shadow-guardrail-evaluation"></a>

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 <a href="#caller-agnostic-hipaa-write-audit-shadow" id="caller-agnostic-hipaa-write-audit-shadow"></a>

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 <a href="#mandatory-write-scope-for-agent-originated-writes" id="mandatory-write-scope-for-agent-originated-writes"></a>

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 <a href="#atlas-per-turn-conversation-persistence" id="atlas-per-turn-conversation-persistence"></a>

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 <a href="#in-flight-write-tool-deduplication-for-realtime-voice" id="in-flight-write-tool-deduplication-for-realtime-voice"></a>

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 <a href="#graceful-busy-handling-at-voice-capacity-limits" id="graceful-busy-handling-at-voice-capacity-limits"></a>

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 <a href="#realtime-voice-model-default-and-startup-validation" id="realtime-voice-model-default-and-startup-validation"></a>

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 <a href="#oauth2-client-management-for-machine-to-machine-authentication" id="oauth2-client-management-for-machine-to-machine-authentication"></a>

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 <a href="#advanced-stats-dashboard-and-operator-performance-now-served-from-analytical-projection" id="advanced-stats-dashboard-and-operator-performance-now-served-from-analytical-projection"></a>

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 <a href="#latency-and-tool-performance-analytics-now-served-from-analytical-projection" id="latency-and-tool-performance-analytics-now-served-from-analytical-projection"></a>

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 <a href="#emotion-trends-and-call-comparison-now-served-from-analytical-projection" id="emotion-trends-and-call-comparison-now-served-from-analytical-projection"></a>

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 <a href="#typed-call-quality-analytics-response" id="typed-call-quality-analytics-response"></a>

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 <a href="#session-ended-world-event-with-patient-link" id="session-ended-world-event-with-patient-link"></a>

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 <a href="#per-case-scoring-for-simulation-benchmark-runs" id="per-case-scoring-for-simulation-benchmark-runs"></a>

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 <a href="#lower-variance-simulation-inference" id="lower-variance-simulation-inference"></a>

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 <a href="#short-lived-access-token-ttls" id="short-lived-access-token-ttls"></a>

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 <a href="#justified-ai-metric-verdicts-in-simulation-eval-results" id="justified-ai-metric-verdicts-in-simulation-eval-results"></a>

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 <a href="#targeted-force-on-demand-tool-selection-for-atlas-voice" id="targeted-force-on-demand-tool-selection-for-atlas-voice"></a>

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 <a href="#document-ingestion-mode-for-customer-data-intake" id="document-ingestion-mode-for-customer-data-intake"></a>

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 <a href="#reject-deterministic-fillers-on-blocking-tools-at-version-create" id="reject-deterministic-fillers-on-blocking-tools-at-version-create"></a>

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 <a href="#per-channel-voice-use-case-bindings-and-customizable-voice-urls" id="per-channel-voice-use-case-bindings-and-customizable-voice-urls"></a>

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 <a href="#simulation-eval-verdicts-per-conversation" id="simulation-eval-verdicts-per-conversation"></a>

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 <a href="#atlas-voice-runtime-graph-scoped-tool-selection" id="atlas-voice-runtime-graph-scoped-tool-selection"></a>

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 <a href="#atlas-voice-runtime-cold-start-telemetry-split" id="atlas-voice-runtime-cold-start-telemetry-split"></a>

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 <a href="#intake-cdc-processing-idempotent-retry-and-reprocess" id="intake-cdc-processing-idempotent-retry-and-reprocess"></a>

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>


---

# 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.concurrence.com/api-reference/change-logs/amigo-api/amigo-api-history-08.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.
