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

# Archive: v0.9.100 - v0.9.249

Archived release history for the Amigo API. For current releases, see the [Amigo API changelog](/api-reference/change-logs/amigo-api.md).

<details>

<summary>v0.9.249 - Platform API: Call Trace Analysis Retry Guidance Updated (July 2026)</summary>

#### Call Trace Analysis Retry Guidance Updated

The call trace analysis endpoint now recommends a longer retry interval for calls in `pending` status.

**What changed:**

* **Retry guidance updated.** The `pending` status description on the call trace analysis endpoint previously recommended retrying after 15 minutes. The recommended retry interval is now 30 minutes, reflecting updated processing cadence for the analysis pipeline.

**What you need to do:**

* **Update polling intervals.** If your integration polls the call trace analysis endpoint for `pending` results on a 15-minute interval, increase it to 30 minutes. More frequent polling still works but will return `pending` for longer before results are available.

</details>

<details>

<summary>v0.9.248 - Platform API: Integration Model Simplified (July 2026)</summary>

#### Integration Model Simplified

The integration data model has been simplified. The previous two-layer structure (a parent integration record plus a separate child record for REST-specific fields) has been collapsed into a single integration resource. All integration fields - including `base_url` and `auth` - now live directly on the integration object.

**What changed:**

* **Flat integration model.** Integration responses no longer use an internal parent/child structure. The `base_url` and `auth` fields are returned directly on the integration resource alongside `name`, `display_name`, `enabled`, and other existing fields. This is a structural simplification with no change to the response shape or field names visible to API consumers.
* **`kind` field removed from integration responses.** The `kind` field is no longer returned in integration responses or accepted in create/update requests. All integrations are REST integrations. The `kind` filter has been removed from the list integrations endpoint.
* **`kind` field removed from internal integration payloads.** Internal integration payloads used during session initialization no longer include a `kind` field. If your code consumes internal integration data and checks or filters by `kind`, remove that logic.
* **No changes to endpoint structure.** Integration endpoints (the per-action definitions within an integration) are unchanged. They continue to reference the parent integration directly.

**What you need to do:**

* **Remove references to `kind`.** If your code reads, filters, or sets the `kind` field on integrations, remove those references. The field is no longer present in responses or accepted in requests.
* **No other migration required.** The visible response fields (`id`, `workspace_id`, `name`, `display_name`, `base_url`, `auth`, `enabled`, `endpoint_count`, `created_at`, `updated_at`) are unchanged. Existing API consumers that do not reference `kind` require no changes.

</details>

<details>

<summary>v0.9.247 - Platform API: Desktop Integration Type and Computer Use Skill Tier Removed (July 2026)</summary>

#### Desktop Integration Type and Computer Use Skill Tier Removed

The platform no longer supports desktop-type integrations or the computer use execution tier for skills. REST integrations remain the only supported integration kind. The desktop sidecar infrastructure for remote desktop connectivity is retained as a standalone component but is no longer managed through the platform API.

**What changed:**

* **Desktop integrations removed.** The `desktop` integration kind has been removed. Integrations endpoints no longer accept or return desktop-type integrations. The `kind` filter on the list integrations endpoint no longer includes `desktop` as a valid value. Existing desktop integration records are archived but no longer accessible through the API.
* **Computer use execution tier removed from skills.** Skills no longer support the `computer_use` execution tier. The `execution_tier` field has been removed from skill configuration. All skills now use the standard orchestrated execution model.
* **Desktop session endpoints removed.** All desktop session management endpoints have been removed from the API. These endpoints previously handled creating, managing, and terminating desktop automation sessions.
* **Task queue endpoints removed.** Task dispatch and task status endpoints associated with background skill execution have been removed.
* **Skill response model simplified.** The `execution_tier` and `desktop_integration` fields are no longer returned in skill responses.
* **Integration response model simplified.** The `kind` field is still present on integration responses but will always be `rest`. Desktop-specific fields (host, port, display dimensions, gateway configuration, remote app settings) are no longer returned.

**What you need to do:**

* **Remove references to desktop integrations.** If your code creates or filters integrations by `kind: desktop`, remove those references. Only `rest` integrations are supported.
* **Remove references to `execution_tier`.** If your code sets `execution_tier` on skill configurations, remove that field. The field is no longer accepted.
* **Remove references to `desktop_integration`.** If your skill configurations reference a `desktop_integration` name, remove that field.
* **Remove calls to desktop session endpoints.** If your code calls desktop session management endpoints, remove those calls.
* **Remove calls to task queue endpoints.** If your code calls task dispatch or task status endpoints, remove those calls.

</details>

<details>

<summary>v0.9.246 - Platform API: Simulation Case Eval Execution (July 2026)</summary>

#### Simulation Case Eval Execution

Simulation runs now automatically execute evaluation criteria (evals) defined on saved cases when a run completes. Eval results are returned inline on the run detail endpoint.

**What changed:**

* **Automatic eval execution on run completion.** When a simulation run finishes, the platform evaluates all eval definitions captured from the run's cases. Two eval types are supported: assertions (which validate conversation outcomes) and metric checks (which compare observed metric values against expectations).
* **Assertion eval kinds.** Assertion evals support several kinds: `transcript_contains` and `must_contain` check whether a phrase appears in the conversation transcript; `transcript_not_contains` and `must_not_contain` verify a phrase does not appear; `tool_called` checks whether a specific tool was invoked during the conversation; `final_state` checks whether the conversation ended in an expected state; and `llm_judge` uses an AI evaluator to judge the transcript against a natural-language criterion.
* **Metric eval checks.** Metric evals look up observed metric values for the run and compare them against configured expectations. Expectations can specify exact equality, numeric ranges (`gte`/`min`, `lte`/`max`), or string containment.
* **New fields on `GET /runs/{run_id}` response.** The run detail response now includes `eval_results` (array of eval result objects), `eval_result_count`, `eval_pass_count`, `eval_fail_count`, and `eval_error_count`.
* **Eval result object.** Each eval result includes `id`, `run_id`, `case_id`, `session_id`, `eval_key`, `eval_type`, `assertion_kind`, `metric_key`, `status` (passed, failed, pending, skipped, or error), `passed`, `score`, `expected`, `actual`, `rationale`, `details`, `definition`, and `created_at`.
* **Real-time events.** Eval results emit events through the standard event pipeline, so dashboards and listeners can react to eval outcomes as they are produced.

**What you need to do:**

* **No action required.** The new fields are additive. Existing integrations that do not read eval results are unaffected.
* **To use case evals,** define evals on your simulation cases (assertion or metric type with the appropriate kind, expected values, and parameters). When runs complete, eval results will appear automatically on the run detail response.
* **To track eval progress,** read the `eval_result_count`, `eval_pass_count`, `eval_fail_count`, and `eval_error_count` fields from the run detail to get summary counts without iterating the full results array.

</details>

<details>

<summary>v0.9.245 - Platform API: Context Graph State in Non-Streaming Turn Responses (July 2026)</summary>

#### Context Graph State in Non-Streaming Turn Responses

The non-streaming conversation turn endpoint now returns the current context graph state alongside the agent response, giving callers visibility into where the agent is in the conversation flow after each turn.

**What changed:**

* **New `context_graph_state` field on turn responses.** Non-streaming turn responses now include a `context_graph_state` field (object or null). When available, this field contains the agent's current position in the context graph, including the active node, visited states, and state-specific metadata. The field is `null` when the context graph state cannot be determined.
* **No change to streaming responses.** This field is only returned on non-streaming (REST) turn responses. Streaming turn interactions are not affected.

**What you need to do:**

* **No action required.** The new field is additive. Existing integrations that do not read `context_graph_state` are unaffected.
* **To use context graph state,** read the `context_graph_state` field from the turn response object. Use it to track conversation flow progression, render navigation indicators, or drive conditional logic based on the agent's current state.

</details>

<details>

<summary>v0.9.244 - Platform API: REST Integration Endpoints as Context Graph Tool IDs (July 2026)</summary>

#### REST Integration Endpoints as Context Graph Tool IDs

Context graph validation now accepts REST integration endpoints as valid tool references. Previously, tool IDs in context graph states could only reference built-in tools, platform functions (`fn_*`), workspace data queries (`wsq_*`), and skill slugs. Now, endpoints from enabled workspace REST integrations are also accepted.

**What changed:**

* **Integration endpoints accepted as tool IDs.** Context graph tool references using the `{integration_name}_{endpoint_name}` format are validated against the workspace's enabled REST integrations. Each enabled integration's endpoints are resolved during context graph version creation, so references to disabled or nonexistent integrations are rejected.
* **Validation scoped to workspace.** Integration endpoint resolution is scoped to the workspace that owns the context graph. Endpoints from integrations in other workspaces are not visible and will fail validation.

**What you need to do:**

* **No action required for existing context graphs.** This change only adds a new category of accepted tool IDs. Existing context graphs that do not reference integration endpoints are unaffected.
* **To use integration endpoints in context graphs,** ensure the target REST integration is enabled in your workspace and reference endpoints using the `{integration_name}_{endpoint_name}` format in your tool ID fields.

</details>

<details>

<summary>v0.9.243 - Platform API: API Key Name in Expiry Alerts (July 2026)</summary>

#### API Key Name in Expiry Alerts

API key expiration alerts now include the key name alongside the key ID, making it easier to identify which key is approaching expiry without looking it up manually.

**What changed:**

* **Key name included in expiry notifications.** Expiration alert messages now display the API key name as a separate field before the key ID. If no name was set on the key, the alert displays "Unknown" as the key name.
* **Webhook payload updated.** The API key expiry webhook payload now includes an optional `api_key_name` field (string or null). Existing webhook consumers are not affected - the field is additive and defaults to null when not present.

**What you need to do:**

* **No action required.** Expiry alerts will automatically include the key name going forward. If you consume API key expiry webhooks programmatically, you can optionally read the new `api_key_name` field from the payload.

</details>

<details>

<summary>v0.9.242 - Platform API: Integration Auth Redesign (July 2026)</summary>

#### Integration Auth Redesign

Integration authentication types have been restructured into four clear variants with a unified secret storage model. The previous six auth types (`api_key_header`, `bearer_token`, `oauth2_client_credentials`, `json_token_exchange`, `oauth2_jwt_bearer`, `bearer_token_exchange`) are replaced by four types with cleaner configuration surfaces.

**What changed:**

* **New auth types.** Four auth types replace the previous six:
  * `static_header` - Replaces `api_key_header` and `bearer_token`. A single secret value sent verbatim as one header. For bearer tokens, store the full `Bearer <token>` string as the secret value and set `header_name` to `Authorization`.
  * `oauth2_client_credentials` - RFC 6749 § 4.4 Client Credentials grant. Now supports a `client_auth_method` field (`basic` or `body`) to control how client credentials are sent to the token endpoint.
  * `oauth2_jwt_bearer` - RFC 7523 § 2.1 JWT Bearer Token grant. Now supports configurable signing algorithms (`RS256` through `ES512`), explicit `include_iat` and `include_jti` toggles, required `assertion_subject` field, and an `extra_claims` object for vendor-specific JWT extensions.
  * `custom_token_exchange` - Replaces `json_token_exchange` and `bearer_token_exchange`. A general-purpose pre-flight exchange with configurable request encoding (`json` or `form`), static and parameter-sourced headers and body fields, and a JSON Pointer path for extracting the token from the exchange response.
* **Unified secret storage.** All auth types now use a single `secret_value` field on the create/update request. The secret is stored at a single per-integration path. The previous per-type secret suffixes (`api_key`, `bearer_token`, `client_secret`, `private_key`, `exchange_secret`) are consolidated.
* **`secret_value` moved to top level.** The `secret_value` field is now a top-level field on the integration create/update request body, not nested inside the `auth` object. It is required when `auth` is provided.
* **JSON Pointer keys for static body fields.** Endpoint `static_body_fields` now use RFC 6901 JSON Pointer syntax (e.g. `/meta/tool_name`) instead of dot-notation (`meta.tool_name`).
* **RFC 6570 URI Templates for path parameters.** Endpoint paths now use RFC 6570 Level 1 URI Templates (e.g. `/patients/{patient_id}/appointments`) instead of the previous `{param}` convention. Behavior is the same - matched parameters are URL-encoded and consumed from the request params.
* **Auto-merged input schemas for custom token exchange.** When an integration uses `custom_token_exchange` auth with parameter mappings (`param_headers` or `param_body_fields`), the mapped parameters are automatically added to each endpoint's input schema. This ensures the agent knows to supply these values without manual schema duplication.
* **Error handling.** Integration endpoint calls now raise a structured error on any failure (auth errors, transport failures, HTTP error responses, response processing errors) instead of returning an error envelope in the success response. LLM-facing callers receive a JSON error payload so the agent session continues running.

**What you need to do:**

* **If you use `api_key_header` auth:** Change `type` to `static_header`. Move `secret_value` to the top-level request body.
* **If you use `bearer_token` auth:** Change `type` to `static_header`, set `header_name` to `Authorization`, and prepend `Bearer` to your secret value. Move `secret_value` to the top-level request body.
* **If you use `oauth2_client_credentials` auth:** Add `client_auth_method` (either `basic` or `body`). Move `secret_value` to the top-level request body.
* **If you use `json_token_exchange` auth:** Migrate to `custom_token_exchange`. Map your `token_request_client_id_field` and `token_request_client_secret_field` to `static_body_fields` entries (use `{secret}` placeholder for the secret). Set `response_token_path` to a JSON Pointer equivalent of your `token_response_path` (e.g. `access_token` becomes `/access_token`). Move `secret_value` to the top-level request body.
* **If you use `bearer_token_exchange` auth:** Migrate to `custom_token_exchange`. Map your `exchange_secret_header` and `exchange_secret_format` to `static_headers` entries. Map `exchange_param_headers` to `param_headers`. Set `response_token_path` to a JSON Pointer (e.g. `token` becomes `/token`). Move `secret_value` to the top-level request body.
* **If you use `oauth2_jwt_bearer` auth:** Add the now-required fields: `assertion_subject`, `assertion_algorithm`, `include_iat`, `include_jti`, and `extra_claims` (empty object `{}` if not needed). The `assertion_scopes_in_jwt` field is removed - use `extra_claims` with key `scope` instead. Move `secret_value` to the top-level request body.
* **If you use dot-notation `static_body_fields` on endpoints:** Convert to JSON Pointer syntax (e.g. `meta.tool_name` becomes `/meta/tool_name`).

</details>

<details>

<summary>v0.9.241 - Platform API: First-Class Simulation Suites (July 2026)</summary>

#### First-Class Simulation Suites

Simulation suites are now first-class resources with full CRUD support and a dedicated run endpoint. Previously, suites were derived dynamically from `suite:*` tags on saved cases. Suites are now independent, named collections that define an explicit case list and optional required tags for dynamic case matching.

**What changed:**

* **New suite endpoints.** Five new endpoints for managing simulation suites:
  * `POST /simulations/suites` - Create a suite with a name, description, explicit case IDs (max 500), required tags for dynamic matching, suite-level tags, and metadata.
  * `GET /simulations/suites` - List all suites in the workspace, ordered by creation date (newest first). Each suite includes a `case_count` reflecting the total resolved cases.
  * `GET /simulations/suites/{suite_id}` - Get a single suite by ID.
  * `PATCH /simulations/suites/{suite_id}` - Partially update a suite. At least one field is required.
  * `DELETE /simulations/suites/{suite_id}` - Delete a suite (204 on success).
* **Dedicated suite run endpoint.** `POST /simulations/suites/{suite_id}/run` executes all cases resolved by a suite as a benchmark batch. The suite's explicit case IDs and required tags are combined to select the case set. The `required_tags` field must not be provided in the request body for suite runs.
* **Suite support on benchmark endpoint.** `POST /simulations/benchmarks/run` now accepts an optional `suite_id` field. When provided, the benchmark resolves cases from the suite definition instead of using `required_tags`. `suite_id` and `required_tags` are mutually exclusive - providing both returns HTTP 422.
* **Updated suite response shape.** The suite list and detail responses now return full suite objects with `id`, `workspace_id`, `name`, `description`, `case_ids`, `required_tags`, `tags`, `metadata`, `case_count`, `created_by`, `created_at`, and `updated_at`. The previous tag-derived response shape (`tag`, `name`, `service_ids`, `latest_updated_at`) is replaced.
* **Updated benchmark response.** The benchmark run response now includes an optional `suite_id` field indicating which suite triggered the run, if any.
* **Stricter request validation.** Create and update request bodies for simulation cases and suites now reject extra fields (HTTP 422) instead of silently ignoring them.

**What you need to do:**

* **If you used the `GET /simulations/suites` endpoint:** Update your client to handle the new response shape. The response now returns full suite objects instead of tag-derived summaries. The `tag`, `service_ids`, and `latest_updated_at` fields are no longer present.
* **If you created suites by tagging cases with `suite:*` tags:** Existing `suite:*` tags on cases still work for filtering, but suites are now managed as independent resources. Create explicit suite resources to take advantage of the new suite management and run capabilities.
* **If you used the benchmark endpoint:** The `required_tags` field is now optional (it was previously required for meaningful results). You can use `suite_id` instead to run a suite-based benchmark.
* **If you sent extra fields in case create/update requests:** Review your request payloads. Extra fields that were previously ignored now cause a 422 validation error.

</details>

<details>

<summary>v0.9.240 - Platform API: Voice Conversation Turn Fallback to Durable Store (July 2026)</summary>

#### Voice Conversation Turn Fallback to Durable Store

Voice conversation detail now falls back to the durable analytical store when turn data is no longer available in the hot session cache. Previously, voice conversation detail only read turns from the hot cache, which meant conversations whose cache had expired returned an empty turns array even though turn data existed in the durable store.

**What changed:**

* **Automatic fallback for voice turns.** The voice conversation detail endpoint now uses the same two-tier turn resolution as text conversations - the hot session cache is checked first, and if no turns are found, the endpoint reads from the durable analytical store. This ensures that historical voice conversations retain their full turn history regardless of cache expiration.
* **Consistent behavior across channels.** Text and voice conversation detail endpoints now follow the same turn resolution path. Both channels check the hot cache first and fall back to the durable store, so turn availability is consistent regardless of conversation type.
* **No change to response shape.** The voice conversation detail response schema is unchanged. Conversations that previously returned an empty turns array due to cache expiration will now return their full turn history.

**What you need to do:**

* **No action required.** This is a backwards-compatible improvement. Voice conversation detail responses that previously returned empty turns for expired sessions will now include the full turn history. If your application handled empty turns arrays as a special case for older voice conversations, that workaround is no longer necessary.

</details>

<details>

<summary>v0.9.239 - Platform API: Simulation Case Scenario Backfill (July 2026)</summary>

#### Simulation Case Scenario Backfill

Simulation case responses now guarantee that the `scenario.instructions` field is always present and non-empty across all endpoints that return simulation cases.

**What changed:**

* **`scenario.instructions` always populated.** The list, get, create, and update simulation case endpoints now ensure that every returned case includes a non-empty `scenario.instructions` value. Previously, legacy cases that were created before the schema restructuring could return an empty or missing `instructions` field inside the `scenario` object.
* **Fallback for legacy cases.** Cases that have no scenario instructions - for example, cases created before the schema migration - now fall back to the case description. If neither is available, a default placeholder is returned. This prevents API consumers from receiving empty scenario content for older cases.
* **Database backfill included.** Existing legacy cases with empty scenario data have been backfilled so that their stored scenario content is consistent with the new guarantee.

**What you need to do:**

* **No action required.** This is a backwards-compatible fix. If your code already handles the `scenario.instructions` field from the v0.9.238 schema, it will continue to work. Cases that previously returned empty instructions now return meaningful content instead.
* **If you had workarounds for empty `scenario.instructions`:** You can safely remove any client-side fallback logic that handled missing or empty instructions, as the API now guarantees a non-empty value.

</details>

<details>

<summary>v0.9.238 - Platform API: Simulation Case Schema Refactored (July 2026)</summary>

#### Simulation Case Schema Refactored

The simulation case schema has been restructured to use a nested, composable shape. Flat top-level fields for scenario content, grounding, evaluation criteria, and provenance have been replaced with structured objects. This is a breaking change to the simulation case create, update, and response shapes.

**What changed:**

* **`scenario` replaces `scenario_instructions`, `initial_message`, and `temperament`.** These three fields are now nested inside a `scenario` object with keys `instructions`, `initial_message`, and `temperament`. The `scenario.instructions` field is required and must be non-empty. `initial_message` defaults to an empty string and `temperament` defaults to null.
* **`grounding` replaces `fixtures`.** The `fixtures` object has been replaced by a `grounding` object. Currently supports one optional field: `patient_entity_id` (UUID). The previous `fixtures.case_specific`, `fixtures.grounding_facts`, and `fixtures.schema` sub-fields are no longer accepted.
* **`evals` replaces `assertions`.** The `assertions` array has been replaced by an `evals` array. Each eval item is a discriminated union with a `type` field. Two types are supported: `metric` (with `metric_key`, `expected`, `params`, `weight`) and `assertion` (with `key`, `assertion_kind`, `description`, `expected`, `params`, `weight`). Maximum 100 items per case.
* **`metadata` replaces `provenance`.** The `provenance` string field has been removed. A free-form `metadata` object is now available for arbitrary key-value data. Cases created by the scenario generator or bridge include a `source` key in metadata (e.g. `"source": "bridge"`).
* **`constraints` and `target_spec` removed.** These top-level fields are no longer part of the case schema. Target specifications and constraints are now managed through evals and metadata.
* **Per-case entity ID resolution updated.** Benchmark runs that resolve a patient entity ID per case now read from `grounding.patient_entity_id` instead of `fixtures.case_specific.entity_id` or `fixtures.entity_id`.
* **Run tags updated.** Cases with a `source` value in metadata produce a `source:<value>` run tag instead of the previous `provenance:<value>` tag.

**What you need to do:**

* **If you create simulation cases via the API:** Restructure your request body. Move `scenario_instructions`, `initial_message`, and `temperament` into the `scenario` object. Move fixture data into `grounding`. Convert `assertions` to `evals` with the appropriate `type` discriminator. Replace `provenance` with a `metadata` object containing a `source` key if needed.
* **If you read simulation case responses:** Update your parsing to read from the new nested fields (`scenario.instructions`, `scenario.initial_message`, `scenario.temperament`, `grounding.patient_entity_id`, `evals`, `metadata`) instead of the removed top-level fields.
* **If you update simulation cases via PATCH:** Use the new field names (`scenario`, `grounding`, `evals`, `metadata`) in your update payloads. The old field names (`scenario_instructions`, `initial_message`, `temperament`, `fixtures`, `constraints`, `target_spec`, `assertions`, `provenance`) are no longer accepted.
* **If you rely on per-case entity IDs in benchmarks:** Set `grounding.patient_entity_id` on each case instead of embedding the entity ID in `fixtures`.

</details>

<details>

<summary>v0.9.237 - Platform API: Integration Endpoint Addressing Changed to ID (July 2026)</summary>

#### Integration Endpoint Addressing Changed to ID

Integration endpoint routes now use the endpoint's stable `id` instead of the endpoint `name` as the path parameter. The create-integration endpoint no longer accepts inline endpoints - endpoints must be added individually after the integration is created. The list-endpoints endpoint now supports search, multi-axis sorting, and cursor-based pagination.

**What changed:**

* **Endpoint path parameter changed from `endpoint_name` to `endpoint_id`.** The GET, PATCH, DELETE, and test routes for individual endpoints now use the endpoint's UUID (`endpoint_id`) instead of the endpoint name. The endpoint `id` is returned in the create and list responses.
* **Create integration no longer accepts inline endpoints.** The `endpoints` array has been removed from the create-integration request body. To add endpoints, use `POST /integrations/{integration_id}/endpoints` after creating the integration.
* **Endpoint responses now include `id`.** All endpoint responses (create, get, list, update) now include a stable `id` field (UUID) that uniquely identifies the endpoint.
* **`retry_on_status` is now a set.** The `retry_on_status` field on endpoint create, update, and response objects is now a set of integers (no duplicates) instead of an array. Duplicate status codes in requests are automatically deduplicated.
* **List endpoints now supports search and sorting.** The list-endpoints route accepts `search` (substring match on name or description), `sort_by` (repeated query param with `+field` or `-field` syntax; allowed fields: `name`, `created_at`, `updated_at`), `limit` (1-200, default 50), and `continuation_token` (opaque cursor for pagination).
* **Get integration returns `endpoint_count` instead of inline endpoints.** The integration response body now includes an `endpoint_count` integer instead of the full `endpoints` array. Use the list-endpoints route to retrieve endpoint details.
* **Get integration returns both REST and desktop kinds.** The get-integration route now returns a discriminated response that includes desktop integrations in addition to REST integrations.
* **Delete integration returns 409 when referenced by skills.** Deleting an integration that is referenced by one or more skills now returns `409 Conflict` with a message listing the referencing skill names, instead of silently succeeding.

**What you need to do:**

* **If you call endpoint routes using `endpoint_name` in the URL path:** Update to use the endpoint `id` (UUID) instead. Retrieve endpoint IDs from the create or list responses.
* **If you pass `endpoints` in the create-integration request body:** Remove the `endpoints` field. Create the integration first, then add endpoints individually using the create-endpoint route.
* **If you read `endpoints` from integration responses:** Use the `endpoint_count` field for summary information. Call the list-endpoints route to retrieve full endpoint details.
* **If you read `retry_on_status` as an ordered array:** Treat the field as an unordered set. Duplicate values are no longer preserved.
* **If you paginate the list-endpoints route:** The response shape now includes `items`, `has_more`, and `continuation_token` fields. Pass the `continuation_token` from the response back as a query parameter to retrieve the next page.

</details>

<details>

<summary>v0.9.236 - Platform API: Direct Execution Tier Removed (July 2026)</summary>

#### Direct Execution Tier Removed

The `direct` execution tier has been removed from the Skills API. Skills that previously used the direct tier - which executed a single integration endpoint call without a companion agent - should be reconfigured to use endpoint overrides or the `orchestrated` tier instead.

**What changed:**

* **`direct` removed from `execution_tier`.** The `execution_tier` field on skill create, update, and response objects now accepts only `orchestrated` and `computer_use`. Requests that specify `direct` are rejected.
* **`system_prompt` now required for all skills.** Previously, `system_prompt` was optional for direct-tier skills (since no companion agent was involved). With the direct tier removed, every skill requires a `system_prompt`.
* **Task state `tier` field no longer includes `direct`.** The `tier` field on task state responses now accepts `companion`, `desktop`, and `computer_use`. Tasks default to `companion` when no tier is recorded.
* **Former direct-tier skills migrated automatically.** Existing skills that used the direct tier have been migrated to use endpoint overrides. The underlying integration endpoint is now invoked directly by the agent as a tool rather than wrapped in a dedicated skill.

**What you need to do:**

* **If you create or update skills with `execution_tier: "direct"`:** Change to `orchestrated` (or `computer_use` if applicable) and provide a `system_prompt`. Alternatively, remove the skill and let the agent call the underlying integration endpoint directly.
* **If you read `execution_tier` from skill responses and handle `direct`:** Remove handling for the `direct` value. Only `orchestrated` and `computer_use` are returned.
* **If you read the `tier` field from task state responses and handle `direct`:** Remove handling for `direct`. The value is no longer returned.

</details>

<details>

<summary>v0.9.235 - Platform API: Integration Endpoint Config Reshaped (July 2026)</summary>

#### Integration Endpoint Config Reshaped

Integration endpoint configuration has been simplified. Response shaping now uses a single Jinja template instead of separate filter, key, and template fields. Request-side static injection uses a flat field map instead of a nested transform object. Retry and timeout settings are now direct fields instead of a nested config object.

**What changed:**

* **`response_template` replaces `response_filter`, `result_key`, and `result_template`.** Response shaping is now a single Jinja template string rendered in a sandboxed environment. The template receives the parsed JSON response as top-level variables. Missing fields must be handled explicitly with `| default("N/A")` - unhandled missing fields raise an error. Set to `null` to fall back to the raw JSON response.
* **`max_response_length` replaces `max_result_length`.** Same behavior (truncate rendered output to prevent token bloat, `0` = no limit), renamed for clarity.
* **`static_body_fields` replaces `request_transform`.** Static fields are now a flat key-value map merged into POST/PUT/PATCH request bodies. Keys support dot-notation to build nested objects (e.g. `meta.tool_name` becomes `{"meta": {"tool_name": ...}}`). The `inject` map and `wrap_params_key` from the previous `request_transform` object are no longer accepted.
* **`timeout_seconds` added.** Per-request HTTP timeout, configurable from 0 to 600 seconds. Default: `30.0`. Previously this was a fixed internal default.
* **`max_retries` and `retry_on_status` replace `retry_config`.** Retry settings are now direct fields on the endpoint instead of a nested object. Defaults are unchanged (`max_retries: 2`, `retry_on_status: [429, 502, 503, 504]`).
* **Test endpoint response reshaped.** The `after_filter` and `after_mapping` fields have been removed from the test endpoint response. A new `rendered` field shows the output after Jinja template rendering. `final_result` now contains the rendered output after truncation.

**What you need to do:**

* **If you set `response_filter`, `result_key`, or `result_template` on endpoints:** Replace these with a single `response_template` using Jinja syntax. For example, a template like `{{name}} - {{birthDate}}` replaces the old `result_template` with `{{field.path}}` placeholders. Add `| default("N/A")` to any field reference that might be missing.
* **If you set `max_result_length` on endpoints:** Rename to `max_response_length`. The value and behavior are unchanged.
* **If you use `request_transform` with `inject` or `wrap_params_key`:** Replace with `static_body_fields`. Move injected key-value pairs directly into the map. If you used `wrap_params_key`, restructure your endpoint configuration to use dot-notation keys in `static_body_fields` instead.
* **If you set `retry_config` on endpoints:** Replace with `max_retries` and `retry_on_status` as direct fields.
* **If you read `after_filter` or `after_mapping` from test endpoint responses:** Replace with `rendered`. The `final_result` field now matches `rendered` after truncation.

</details>

<details>

<summary>v0.9.234 - Platform API: Skill and Integration Delivery Fields Removed (July 2026)</summary>

#### Skill and Integration Delivery Fields Removed

The `delivery` and `urgency_keywords` fields have been removed from the Skill API. The `result_delivery` field has been removed from integration endpoint objects. Result delivery is now configured per state in the context graph rather than on individual skills or integration endpoints.

**What changed:**

* **`delivery` removed from skills.** The `delivery` field (values: `interrupt`, `queue`) has been removed from skill create, update, and response objects. Skills no longer carry a delivery mode - delivery behavior is determined by the context graph state that binds the tool.
* **`urgency_keywords` removed from skills.** The `urgency_keywords` field has been removed from skill create, update, and response objects. Urgency-based delivery escalation is no longer configured at the skill level.
* **`result_delivery` removed from integration endpoints.** The `result_delivery` field (values: `interrupt`, `queue`) has been removed from endpoint create and response objects. Integration endpoints no longer carry a delivery mode.
* **Delivery configured on context graph state bindings.** Result delivery is now a property of the context graph state's tool binding. The same tool can use different delivery modes in different states - for example, a tool can queue results silently during an intake state and interrupt immediately during a prescribing state. Existing configurations have been migrated automatically.

**What you need to do:**

* **If you set `delivery` or `urgency_keywords` when creating or updating skills:** Remove these fields from your request bodies. They are no longer accepted.
* **If you read `delivery` or `urgency_keywords` from skill responses:** Remove references to these fields. They are no longer included in responses.
* **If you set `result_delivery` when creating integration endpoints:** Remove this field from your request bodies. It is no longer accepted.
* **If you read `result_delivery` from endpoint responses:** Remove references to this field. It is no longer included in responses.
* **To configure delivery behavior:** Set the delivery mode on the tool binding within each context graph state where the tool is used.

</details>

<details>

<summary>v0.9.233 - Platform API: Integration Schema Redesign - REST/Desktop Split, Update Endpoint, Per-Endpoint CRUD (July 2026)</summary>

#### Integration Schema Redesign

Integrations have been restructured into variant-specific types. The public API now exclusively manages REST integrations and exposes new endpoints for updating integrations and managing individual endpoints. Desktop integrations are system-provisioned and not accessible through the public CRUD surface.

**What changed:**

* **`kind` replaces `protocol`.** Integration responses now include a `kind` field (value: `rest`) instead of the previous `protocol` field. The `protocol` query parameter has been removed from the list endpoint.
* **`builtin` field removed.** The `builtin` field has been removed from integration responses. All integrations returned by the public API are workspace-scoped rows created through the standard create flow.
* **`base_url` is now required on create.** The `base_url` field is required when creating an integration. Previously it defaulted to an empty string.
* **Update integration endpoint added.** `PATCH /integrations/{integration_id}` is now available. You can update `display_name`, `base_url`, `enabled`, and `auth` individually without deleting and recreating the integration. Auth updates require setting `auth_provided: true` in the request body to distinguish between "leave auth unchanged" and "clear auth".
* **Per-endpoint CRUD added.** Endpoints on a REST integration can now be managed individually:

  * `POST /integrations/{integration_id}/endpoints` - Add an endpoint
  * `GET /integrations/{integration_id}/endpoints` - List endpoints (paginated)
  * `GET /integrations/{integration_id}/endpoints/{endpoint_name}` - Get an endpoint
  * `PATCH /integrations/{integration_id}/endpoints/{endpoint_name}` - Update an endpoint
  * `DELETE /integrations/{integration_id}/endpoints/{endpoint_name}` - Delete an endpoint

  Endpoint names are immutable after creation. Adding, updating, or deleting endpoints updates the parent integration's `updated_at` timestamp.
* **List endpoint no longer accepts `protocol` filter.** The `protocol` query parameter has been removed from `GET /integrations`. The list endpoint now returns only REST integrations.

**What you need to do:**

* **If you read the `protocol` field from integration responses:** Replace references to `protocol` with `kind`. The value is `rest` for all integrations returned by the public API.
* **If you read the `builtin` field from integration responses:** Remove references to this field. It is no longer included in responses.
* **If you filter by `protocol` on the list endpoint:** Remove the `protocol` query parameter from your list requests. The endpoint now returns only REST integrations.
* **If you pass an empty `base_url` on create:** Provide a valid base URL. The field is now required and must be non-empty.
* **If you delete and recreate integrations to change settings:** Use the new `PATCH /integrations/{integration_id}` endpoint instead. This preserves the integration ID, endpoints, and associated secrets.
* **If you recreate integrations to add or remove endpoints:** Use the per-endpoint CRUD endpoints to manage endpoints individually without affecting the parent integration or other endpoints.

</details>

<details>

<summary>v0.9.232 - Platform API: Test Connection and Health Check Endpoints Removed (July 2026)</summary>

#### Test Connection and Health Check Endpoints Removed

The `POST /integrations/{integration_id}/test-connection` and `GET /integrations/health-check` endpoints have been removed from the Platform API. Integration health metadata fields have also been removed from integration responses.

**What changed:**

* **Test connection endpoint removed.** The `POST /integrations/{integration_id}/test-connection` endpoint has been removed. This endpoint previously probed an integration's auth resolution and upstream reachability without invoking a specific endpoint. Requests to this endpoint will return `404 Not Found`.
* **Health check endpoint removed.** The `GET /integrations/health-check` endpoint has been removed. This endpoint previously returned a bulk health summary of all enabled integrations in a workspace. Requests to this endpoint will return `404 Not Found`.
* **Health metadata fields removed from integration responses.** The `last_tested_at` and `last_test_status` fields have been removed from integration response objects. These fields previously reflected the most recent connection probe result. Integration list and get responses no longer include health metadata.
* **Stored health metadata cleared.** Previously persisted health metadata from connection probes has been removed from integration records. This is a data cleanup with no behavioral effect - the fields that surfaced this data have been removed from the API.

**What you need to do:**

* **If you call `POST /integrations/{integration_id}/test-connection`:** Remove these calls from your application. This endpoint no longer exists. Use the `POST /integrations/{integration_id}/endpoints/{endpoint_name}/test` endpoint to test specific integration endpoints instead.
* **If you call `GET /integrations/health-check`:** Remove these calls from your application. This endpoint no longer exists.
* **If you read `last_tested_at` or `last_test_status` from integration responses:** Remove references to these fields. They are no longer included in integration list or get responses.

</details>

<details>

<summary>v0.9.231 - Platform API: Integration Secrets Keyed by ID (July 2026)</summary>

#### Integration Secrets Keyed by ID

Integration auth secrets are now keyed by the integration's immutable ID rather than its user-supplied name. This means renaming an integration in a future release will not orphan its provisioned secrets.

**What changed:**

* **Secret paths use integration ID.** The platform now derives auth secret storage paths from the integration's stable UUID instead of its name. This is an internal change - callers never supply or see secret paths - but it affects the lifecycle guarantee: secrets are now permanently tied to the integration ID and cannot be orphaned by a name change.
* **Integration ID assigned before secret provisioning.** The integration ID is generated before any secrets are written, so the secret path and the database record always reference the same identifier. Previously, the ID was assigned at row insertion time, which could diverge from the path used during provisioning.

**What you need to do:**

* **No action required for most users.** Secret provisioning and resolution are fully managed by the platform. If you supply inline secret values (`api_key_value`, `bearer_token_value`, `client_secret_value`, `private_key_value`, `exchange_secret_value`) at creation time, they continue to work exactly as before.
* **If you have integrations created before this release:** Existing integrations created under the previous name-based scheme will be migrated automatically. No manual intervention is needed.

</details>

<details>

<summary>v0.9.230 - Platform API: Integration Secret Management Simplified, Update Endpoint Removed (July 2026)</summary>

#### Integration Secret Management Simplified, Update Endpoint Removed

Integration secret paths are now computed deterministically by the platform. The `PUT` update endpoint for integrations has been removed. Built-in integrations are no longer returned by the API - all integrations are workspace-scoped.

**What changed:**

* **Update integration endpoint removed.** The `PUT /integrations/{integration_id}` endpoint has been removed. Integrations can no longer be updated in place. To change an integration's configuration, delete and recreate it. This simplifies the integration lifecycle and eliminates edge cases around secret rotation during partial updates.
* **Secret paths are now deterministic.** Integration auth secrets are stored at platform-computed paths derived from the workspace, integration ID, and auth type. Callers no longer supply secret storage paths - the platform computes them automatically. The `ssm_param_path`, `token_ssm_param_path`, `client_secret_ssm_param_path`, `private_key_ssm_param_path`, and `exchange_secret_ssm_param_path` fields have been removed from the `AuthConfig` model. Inline secret values (`api_key_value`, `bearer_token_value`, `client_secret_value`, `private_key_value`, `exchange_secret_value`) remain the only way to supply secrets at creation time.
* **Built-in integrations removed from API responses.** List and get endpoints no longer include platform-defined built-in integrations (Google Maps, Google Search, Stedi, athenahealth). All integrations returned by the API are workspace-scoped rows created through the standard create flow. If your application filtered on the `builtin` field, those entries will no longer appear.
* **Built-in integrations no longer block delete/update.** Previously, attempting to delete or update a built-in integration returned `403 Forbidden`. Since built-ins are no longer returned, this guard is removed. All integrations returned by the API can be deleted by their owner.

**What you need to do:**

* **If you use `PUT /integrations/{integration_id}`:** This endpoint no longer exists. To modify an integration, delete the existing one and create a new one with the updated configuration. Requests to the old endpoint will return `405 Method Not Allowed`.
* **If you supply `*_ssm_param_path` fields in `AuthConfig`:** Remove these fields from your request bodies. The platform now rejects unknown fields on the auth config with `422 Unprocessable Entity`. Supply secrets using the inline `*_value` fields only.
* **If you depend on built-in integrations in list responses:** Built-in integrations are no longer returned. If your application relied on built-ins appearing in `GET /integrations`, you will need to create workspace-scoped equivalents through the standard create flow.
* **If you filter on the `builtin` field:** This field will always be `false` for all returned integrations. You can remove any client-side filtering logic based on this field.

</details>

<details>

<summary>v0.9.229 - Platform API: List Endpoints Search by ID and Default Sort by Updated Date (July 2026)</summary>

#### List Endpoints Search by ID and Default Sort by Updated Date

List endpoints for agents, skills, services, integrations, and context graphs now support searching by ID and default to sorting by last updated date (newest first).

**What changed:**

* **Search by ID.** The `search` parameter on list endpoints for agents, skills, services, integrations, and context graphs now matches against the resource ID in addition to the previously supported fields (name, description, slug, display name). This makes it easier to locate a specific resource when you have a partial or full ID.
* **Default sort changed to last updated.** List endpoints for agents, skills, services, integrations, and context graphs now return results sorted by last updated date (newest first) by default. Previously, some endpoints defaulted to sorting by creation date or name. The `sort` parameter continues to work as before if you need a different ordering.

**What you need to do:**

* **If you rely on default list ordering:** Be aware that results now appear in last-updated order by default. If your application depends on a specific sort order, pass the `sort` parameter explicitly.
* **If you search for resources by ID:** You can now paste a full or partial ID into the `search` parameter and it will match. No API changes are needed to use this - existing `search` calls will also match IDs automatically.
* **No breaking changes.** All existing query parameters and response shapes are unchanged.

</details>

<details>

<summary>v0.9.228 - Platform API: Simulation Case Management and Suites Endpoint (July 2026)</summary>

#### Simulation Case Management and Suites Endpoint

Simulation cases now support update, delete, and total count operations. A new suites listing endpoint returns all discoverable suites derived from `suite:*` case tags.

**What changed:**

* **Update simulation cases.** `PATCH /simulations/cases/{case_id}` partially updates editable fields on a durable simulation case. Updatable fields include service assignment, persona, description, scenario instructions, initial message, temperament, fixtures, constraints, target specification, assertions, tags, and provenance. At least one field must be provided. Reserved tag prefixes are validated on update. Returns the updated case, or `404` if the case does not exist. Requires `Service.update` permission.
* **Delete simulation cases.** `DELETE /simulations/cases/{case_id}` removes a simulation case. Returns `204` on success, or `404` if the case does not exist. Requires `Service.update` permission.
* **List simulation suites.** `GET /simulations/suites` returns all suites discovered from cases tagged with the `suite:` prefix. Each suite includes a human-readable name derived from the tag, the number of matching cases, distinct service IDs, and the most recent update timestamp. Suites are dynamic - they are derived from case tags rather than requiring explicit registration.
* **Case list includes total count.** The `GET /simulations/cases` endpoint now includes a `total` count in paginated responses, so clients can render pagination controls without issuing a separate count request.

**What you need to do:**

* **If you manage simulation cases programmatically:** You can now update and delete individual cases through the API. Use `PATCH` for partial updates and `DELETE` for removal.
* **If you organize cases into suites:** The new `GET /simulations/suites` endpoint provides suite discovery without needing to list and group cases client-side.
* **If you paginate case listings:** The `total` field is now available in the list response to support accurate pagination UI.
* **No breaking changes.** All existing endpoints continue to work as before. The new endpoints and fields are additive.

</details>

<details>

<summary>v0.9.227 - Platform API: Voice Sentiment Metric Removed (July 2026)</summary>

#### Voice Sentiment Metric Removed

The built-in `voice_sentiment` metric has been removed from the platform. This metric had zero active readers and is no longer computed or available in metric results.

**What changed:**

* **`voice_sentiment` metric removed.** The built-in voice sentiment metric is no longer included in the default metric definitions. It will no longer appear in metric query results, workspace metric settings, or the MCP server's available metrics list.
* **Extraction mode simplified.** The `ai_sentiment` extraction mode has been removed from the metric definition model. The supported extraction modes are now: `static`, `ai_classify`, `ai_extract`, `ai_query`, and `sql_expr`.
* **Custom metric validation updated.** The validation error message for unsupported extraction modes on custom metrics now lists `static`, `ai_query`, `ai_classify`, and `ai_extract` as the allowed modes.
* **Built-in metric count reduced.** The platform now ships 40 built-in metrics (previously 41).

**What you need to do:**

* **If you query `voice_sentiment` in dashboards or integrations:** Remove references to this metric key. Queries for `voice_sentiment` will return no results.
* **If you use the `ai_sentiment` extraction mode in custom metric definitions:** Migrate to `ai_classify` with appropriate labels, or use `ai_query` with a prompt that performs sentiment analysis. The `ai_sentiment` mode is no longer recognized.
* **If you reference voice sentiment in MCP tool calls:** Update your queries to use other available voice metrics such as `voice_quality_score`, `voice_escalation`, or `voice_completion_reason`.
* **No data migration required.** Historical metric values that were previously computed are unaffected, but no new values will be generated.

</details>

<details>

<summary>v0.9.226 - Platform API: Barge-In Events Preserved Individually in Call Intelligence (July 2026)</summary>

#### Barge-In Events Preserved Individually in Call Intelligence

Call intelligence now preserves individual barge-in event details instead of recording only an aggregate count. Each barge-in event includes the text that was interrupted and the timestamp of the interruption.

**What changed:**

* **Individual barge-in events in conversation summary.** The conversation summary section of call intelligence now includes a `barge_in_events` array alongside the existing `barge_in_count` field. Each entry contains `interrupted_text` (the agent utterance that was interrupted) and `timestamp` (ISO 8601 timestamp of the interruption, or null if unavailable).
* **Aggregate count still present.** The `barge_in_count` field continues to be populated and reflects the length of the `barge_in_events` array, so existing consumers that rely on the count are unaffected.

**What you need to do:**

* **If you consume call intelligence data:** You can now access per-event barge-in details in the conversation summary. The `barge_in_count` field remains unchanged, so no migration is required.
* **No breaking changes.** The `barge_in_events` array is additive. Existing integrations that read call intelligence data continue to work without modification.

</details>

<details>

<summary>v0.9.225 - Platform API: Call List Now Includes Intelligence-Only Calls (July 2026)</summary>

#### Call List Now Includes Intelligence-Only Calls

The call list endpoint now surfaces calls that have call intelligence data but no associated conversation record. Previously, the call list was driven exclusively by conversation records, so calls that were processed through the analytics pipeline but did not create a conversation were not visible in the list.

**What changed:**

* **Unified call list.** The call list is now built from a unified view that joins call intelligence records with conversation records. Calls that exist only in call intelligence - for example, calls ingested from external sources or processed through analytics without following the standard conversation lifecycle - now appear alongside traditional conversation-based calls.
* **Field resolution across sources.** Fields such as direction, duration, turn count, quality score, completion reason, final state, and source are resolved from whichever source has the most complete data. Call intelligence values take precedence where both sources provide a value, except for status and completion reason where conversation data takes precedence.
* **Detail endpoint supports intelligence-only calls.** Retrieving detail for a call that exists only as a call intelligence record returns all available metadata and scoring fields with an empty turns array, rather than returning a not-found error.
* **No changes to filtering.** Existing filters (status, direction, service, date range) continue to work. Status filtering considers the resolved status from both sources.

**What you need to do:**

* **If you consume the call list endpoint:** You may see additional calls that were not previously visible. These calls have an `id` derived from the call intelligence record rather than a conversation ID. All other fields follow the same schema.
* **If you rely on the `call_intelligence_batch_reader` callback pattern (internal integrations):** This callback has been removed. Intelligence data is now resolved directly in the list query. No action is needed if you use the public API.
* **No breaking changes to response schema.** The response shape is unchanged. New calls include the same fields as conversation-based calls, with `null` values for conversation-specific fields (such as `caller_id` and `phone_number`) when no conversation record exists.

</details>

<details>

<summary>v0.9.224 - Platform API: Integration Auth Rejects Platform-Managed SSM Paths Without Inline Secrets (July 2026)</summary>

#### Integration Auth Rejects Platform-Managed SSM Paths Without Inline Secrets

The platform now rejects integration auth configurations that reference a platform-managed SSM path without providing the corresponding inline secret value. Previously, it was possible to copy a platform-managed SSM path from an API response (or another integration) into a create or update request without supplying the actual secret material. This would create an integration that appeared configured but could not authenticate, because the path was auto-generated and the secret was not actually provisioned for the new integration.

**What changed:**

* **Platform-managed SSM paths require inline secrets.** When creating or updating an integration, if an auth field (such as `api_key_value`, `bearer_token_value`, `client_secret_value`, `private_key_value`, or `exchange_secret_value`) references a platform-managed SSM path but does not include the inline secret value, the API returns a validation error. The error message indicates which field is affected and instructs you to pass the secret value directly.
* **External SSM paths are unaffected.** If you manage your own SSM paths outside the platform, those paths continue to be accepted without an inline secret value.
* **Inline secret values work as before.** Passing the actual secret value directly (e.g. `api_key_value`, `bearer_token_value`) continues to work. The platform auto-provisions the secret to SSM and returns the managed path in the response.

**What you need to do:**

* **If you copy SSM paths from API responses into new integration requests:** Stop copying the `ssm_param_path`, `token_ssm_param_path`, `client_secret_ssm_param_path`, `private_key_ssm_param_path`, or `exchange_secret_ssm_param_path` values from responses. Instead, pass the original secret value in the inline field and let the platform provision it automatically.
* **If you use external (self-managed) SSM paths:** No changes needed. External paths are accepted as before.

</details>

<details>

<summary>v0.9.223 - Platform API: Dynamic Simulation Suite Tags (July 2026)</summary>

#### Dynamic Simulation Suite Tags

Suite tags in the simulation framework are now fully dynamic. Previously, suite tags had to be pre-registered alongside capability and agent-type tags. Now, any tag with the `suite:` prefix is accepted as a valid suite tag without prior registration. This makes it easier to create and iterate on test suites - just tag your cases with a new `suite:` value and it becomes a runnable suite immediately.

**What changed:**

* **Suite tags are no longer validated against a fixed registry.** Any `suite:*` tag is accepted on simulation cases. You can define new suites by tagging cases without updating a central registry.
* **Capability and agent-type tags are still validated.** Tags with the `capability:` and `agent_type:` prefixes continue to be validated against known values. Unrecognized capability or agent-type tags are still rejected to prevent typos from silently excluding cases from benchmarks and rollups.

**What you need to do:**

* No action is required. Existing `suite:` tags continue to work. You can now create new suites by applying any `suite:*` tag to your simulation cases.

</details>

<details>

<summary>v0.9.222 - Platform API: Conversation Turn Data Moved to Session Store, Call Analysis and Enrichment Fields Removed from API Responses (July 2026)</summary>

#### Conversation Turn Data Moved to Session Store

Per-turn conversation data is now served exclusively from the session store. The platform no longer reads turn data from the previous analytical table. This completes the migration to the session-based turn storage announced in earlier releases.

**What changed:**

* **Turn data served from session store only.** Call detail and conversation detail endpoints now resolve turns exclusively from the session store. If the session store is unavailable, the response includes all conversation metadata and scoring fields with an empty turns array, rather than falling back to the previous analytical table. This matches the existing behavior documented for historical calls whose turn data was not preserved in current stores.
* **Historical calls unaffected.** Calls whose turns were already in the session store continue to work identically. Calls whose turn data existed only in the previous analytical table will return an empty turns array in the detail response. All metadata, scoring, and summary fields remain available.

#### Call Analysis and Enrichment Fields Removed from API Responses

Several enrichment fields have been removed from call and conversation API responses. These fields were populated from analytical processing that has moved to the downstream analytics pipeline and are no longer written or returned by the platform API.

**What changed:**

* **`call_analysis` removed from call detail responses.** The `call_analysis` object (containing outcome classification, transport close details, and post-call analysis) is no longer included in call detail API responses. If you consumed this field, use the analytics pipeline or metric store for equivalent data.
* **`emotional_summary` removed from call detail responses.** The `emotional_summary` object is no longer included in call detail API responses.
* **`forwarding` removed from call detail responses.** The `forwarding` object is no longer included in call detail API responses.
* **`participants`, `states_visited`, `barge_in_events`, and `config` removed from call detail responses.** These enrichment arrays and objects are no longer included in call detail API responses.
* **`escalation_status` field type relaxed.** The `escalation_status` field on call summary and operator escalation responses is now a free-form string (max 64 characters) instead of a fixed set of values. Existing status values continue to work. Client code that validated against a strict set of allowed values should be updated to accept any string.

**What you need to do:**

* **If you read `call_analysis`, `emotional_summary`, `forwarding`, `participants`, `states_visited`, `barge_in_events`, or `config` from call detail responses:** These fields are no longer returned. Equivalent data is available through the analytics pipeline and metric store.
* **If you validate `escalation_status` against a fixed set of values:** Update your validation to accept any string up to 64 characters.
* **If you depend on turn data for historical calls:** Turns are now served exclusively from the session store. Historical calls that predate session store adoption will return an empty turns array with all other metadata intact.

</details>

<details>

<summary>v0.9.221 - Platform API: Endpoint-Level Overrides Removed, Google Maps Integration Split (July 2026)</summary>

#### Endpoint-Level Overrides Removed from Integrations

The `base_url` and `auth` fields on individual integration endpoints have been removed. All endpoints in an integration now inherit the integration-level base URL and auth configuration. This simplifies the integration model and removes ambiguity about which auth and URL apply to a given endpoint.

**What changed:**

* **`base_url` removed from endpoint configuration.** Individual endpoints no longer accept a `base_url` override. The integration-level `base_url` is used for all endpoints. If you previously used per-endpoint base URL overrides, split those endpoints into separate integrations with their own base URLs.
* **`auth` removed from endpoint configuration.** Individual endpoints no longer accept an `auth` override. The integration-level `auth` configuration is used for all endpoints. If you previously used per-endpoint auth overrides, split those endpoints into separate integrations with their own auth configurations.

#### Google Maps Built-in Integration Split into Two Integrations

The single `google_maps` built-in integration has been replaced by two separate built-in integrations: `google_maps_places` and `google_maps_routes`. This change reflects the removal of per-endpoint base URL overrides - the Places API and Routes API live at different hosts and now require separate integrations.

**What changed:**

* **`google_maps` integration replaced.** The `google_maps` built-in integration no longer exists. It has been replaced by `google_maps_places` (Places API endpoints) and `google_maps_routes` (Routes API endpoints including directions).
* **`google_maps_places` integration.** Contains all place search, geocoding, and place detail endpoints. Display name: "Google Maps - Places".
* **`google_maps_routes` integration.** Contains the directions endpoint. Display name: "Google Maps - Routes".
* **Same authentication.** Both integrations use the same API key authentication and the same `GOOGLE_MAPS_API_KEY` environment variable. No credential changes are needed.

**What you need to do:**

* **If you use per-endpoint `base_url` or `auth` overrides:** Split those endpoints into separate integrations, each with its own integration-level base URL and auth configuration. These fields are no longer accepted on endpoint objects.
* **If you reference `google_maps` by name:** Update references to use either `google_maps_places` or `google_maps_routes` depending on which endpoints you use. The `google_maps` integration name is no longer valid.
* **If you use the directions endpoint:** This endpoint has moved from `google_maps` to `google_maps_routes`. Update any code that looks up the directions tool by integration name.

</details>

<details>

<summary>v0.9.220 - Platform API: Text Interact Turn Persistence Failures Now Surface Explicitly (July 2026)</summary>

#### Text Interact Turn Persistence Failures Now Surface Explicitly

The text interact endpoint now returns explicit error responses when turn persistence fails, instead of returning a silent success.

**What changed:**

* **Non-streaming text interact returns 502 on persistence failure.** If the agent generates a response but the platform fails to persist the conversation turn, the endpoint now returns a `502 Bad Gateway` with a message indicating that conversation history may be incomplete. Previously, this scenario returned a `200 OK`, which masked the data loss.
* **Streaming text interact emits an error event on persistence failure.** If turn persistence fails during a streaming interaction, the stream now emits an SSE `error` event with a message indicating that conversation history may be incomplete. Previously, the stream closed silently without signaling the failure.
* **Voice session persistence failures logged at error severity.** Voice session sync failures are now logged at error severity instead of warning severity, improving observability for persistence issues across all channels.

**What you need to do:**

If you consume the text interact endpoint, update your error handling to account for `502` responses (non-streaming) or `error` SSE events (streaming) that indicate persistence failures. These responses mean the agent's reply was generated and delivered, but the turn may not appear in conversation history. You may want to retry the interaction or notify the user that the conversation state could be stale.

</details>

<details>

<summary>v0.9.219 - Platform API: SMART Backend Services Auth Type Removed from Integrations (July 2026)</summary>

#### SMART Backend Services Auth Type Removed from Integrations

The `smart_backend_services` authentication type has been removed from the integrations framework. This auth flow is now handled by the connector runner service. Integrations that previously used SMART Backend Services auth should migrate to use the connector runner for SMART-authenticated connections.

**What changed:**

* **`smart_backend_services` auth type removed.** The `smart_backend_services` option is no longer accepted in the integration auth configuration `type` field. The supported auth types are now: `api_key_header`, `bearer_token`, `oauth2_client_credentials`, `json_token_exchange`, `oauth2_jwt_bearer`, and `bearer_token_exchange`.
* **`assertion_algorithm` field removed.** The `assertion_algorithm` field on the auth configuration model has been removed. This field was only used with the `smart_backend_services` auth type.
* **`assertion_kid` field removed.** The `assertion_kid` field on the auth configuration model has been removed. This field was only used with the `smart_backend_services` auth type.

**What you need to do:**

If you have integrations configured with `smart_backend_services` auth, migrate them to use the connector runner for SMART Backend Services authentication. The connector runner now owns this auth flow end-to-end. Contact support if you need guidance on migrating specific integrations.

</details>

<details>

<summary>v0.9.218 - Platform API: GCP WIF Auth Type Removed from Integrations (July 2026)</summary>

#### GCP WIF Auth Type Removed from Integrations

The `gcp_wif` authentication type has been removed from the integrations framework. Integrations that previously used GCP Workload Identity Federation auth should migrate to an alternative auth type.

**What changed:**

* **`gcp_wif` auth type removed.** The `gcp_wif` option is no longer accepted in the integration auth configuration `type` field. The supported auth types are now: `api_key_header`, `bearer_token`, `oauth2_client_credentials`, `json_token_exchange`, `oauth2_jwt_bearer`, `smart_backend_services`, and `bearer_token_exchange`.
* **`gcp_scopes` field removed.** The `gcp_scopes` field on the auth configuration model has been removed. This field was only used with the `gcp_wif` auth type.

**What you need to do:**

If you have integrations configured with `gcp_wif` auth, update them to use one of the remaining supported auth types before your next deployment. The supported auth types are now: `api_key_header`, `bearer_token`, `oauth2_client_credentials`, `json_token_exchange`, `oauth2_jwt_bearer`, and `bearer_token_exchange`. Contact support if you need guidance on migrating specific integrations.

</details>

<details>

<summary>v0.9.217 - Platform API: Agent Harness Approval Flag Update (July 2026)</summary>

#### Agent Harness Approval Flag Update

The non-interactive agent harness backend now uses a single consolidated flag for bypassing approval prompts and sandbox restrictions, replacing the previous separate flags.

**What changed:**

* **Consolidated approval and sandbox bypass.** The non-interactive harness now passes a single combined flag to bypass both approval prompts and sandbox restrictions. Previously, these were configured as two separate flags (one for approval suppression and one for sandbox access). The new flag is functionally equivalent - agent behavior and execution results are unchanged.

**What you need to do:**

No action is required. This is an internal harness configuration change. Agent runs, audit events, and result formats are unaffected.

</details>

<details>

<summary>v0.9.216 - Platform API: Additional Agent Harness Backend (July 2026)</summary>

#### Additional Agent Harness Backend

The agent execution layer now supports a second harness backend for non-interactive batch execution, in addition to the existing interactive harness. The new harness can be selected per workspace and is fully integrated with the existing job scheduling and audit event pipeline.

**What changed:**

* **New harness option.** Workspaces can now configure a second agent harness backend. The harness selection is set through workspace environment configuration and applies to all agent runs dispatched from that workspace. The new backend operates in non-interactive mode with full sandbox access, producing the same audit event structure and result format as the existing harness.
* **Audit events.** Runs using the new harness emit audit events with harness-specific event types, following the same structure used by the existing harness. Each event carries a tool identifier, input summary, sequence number, and timestamp. The harness name is recorded on the run result so downstream systems can distinguish which backend produced the output.
* **Model selection.** The new harness respects the same agent model configuration used by the existing harness. When an agent model is specified in workspace settings, it is passed through to the harness backend automatically.
* **Environment configuration.** Two new optional environment variables control the API credentials for the new harness. These are only required when the new harness is selected and are ignored for workspaces using the existing harness.

**What you need to do:**

No changes are required for existing workspaces. To use the new harness backend, update your workspace agent harness configuration. If you consume audit events or run results, the `harness_name` field on the result now reflects which backend executed the run.

</details>

<details>

<summary>v0.9.215 - Platform API: Customer Data Zone and Catalog Reference Updates (June 2026)</summary>

#### Customer Data Zone and Catalog Reference Updates

The Data-MCP SQL query tool, the fallback SQL query tool used by the agent runtime, and the research query service now recognize the customer data zone catalogs. Catalog references across all LLM tool prompts have been updated to use fully qualified names.

**What changed:**

* **Customer data zone available in SQL queries.** The read-only SQL query tools now accept queries against the customer data zone. These views use definer-rights access control and are automatically filtered to the caller's workspace, so queries return only data belonging to the authenticated workspace. Both production and staging catalogs are supported.
* **Fully qualified catalog references.** All SQL tool descriptions presented to the agent now use fully qualified three-part catalog references instead of abbreviated names. This eliminates ambiguity when the agent constructs queries and ensures correct catalog resolution across environments.
* **Analytics catalog consolidation reflected in tool prompts.** Tool descriptions now reference the consolidated analytics catalog for metrics, billing, memory, scheduling, and call trace data, replacing references to individual legacy catalogs that were retired in the May 2026 consolidation.

**What you need to do:**

No API changes are required. If you use Data-MCP or the agent's SQL query capabilities, the customer data zone is now queryable alongside existing catalogs. Queries against the customer data zone are workspace-scoped automatically - no manual filtering is needed.

</details>

<details>

<summary>v0.9.214 - Platform API: Call Intelligence Served from Session Artifacts (June 2026)</summary>

#### Call Intelligence Served from Session Artifacts

Call intelligence data on voice conversation endpoints is now resolved from the hot session cache first, with automatic fallback to the durable analytical store. This replaces the previous approach of joining against a dedicated database table on every read.

**What changed:**

* **Tiered call intelligence resolution.** Single-call detail and conversation list endpoints now read call intelligence artifacts (quality scores, completion reasons, duration, direction, analytics summaries) from the fast session cache. When cached data is unavailable - for example, after cache expiration or for older conversations - the platform falls back to the durable analytical store automatically. No intelligence data is lost during the transition.
* **Batch intelligence lookups for conversation lists.** The conversation list endpoint now resolves call intelligence for an entire page of results in a single batch lookup rather than issuing per-conversation queries. This reduces list endpoint latency, especially for pages with many voice conversations.
* **Consistent intelligence fields.** The `quality_score`, `completion_reason`, `final_state`, `duration_seconds`, `direction`, `source`, `service_id`, and all analytics summary fields (`risk_summary`, `latency_summary`, `conversation_metrics`, `tool_summary`, `safety_summary`, `operator_summary`) continue to appear on conversation detail and list responses with the same types and semantics. Field precedence is unchanged: conversation-level values take priority where present, with intelligence artifacts filling in supplementary data.
* **Status and filter behavior.** Conversation list status filters now evaluate against conversation header fields only, rather than joining intelligence data into the filter predicate. This makes total counts and pagination more predictable. The `direction=playground` filter continues to work but playground-originated conversations are no longer represented as voice conversation summaries.

**What you need to do:**

No API changes are required. Response shapes are unchanged. Conversation list and detail endpoints return the same fields with improved latency and consistency. If you filter conversations by status, results may differ slightly for edge cases where the intelligence record previously overrode the conversation header status.

</details>

<details>

<summary>v0.9.213 - Platform API: Generic Agent Harness Configuration (June 2026)</summary>

#### Generic Agent Harness Configuration

The platform now supports configuring which agent harness and model are used for agent task execution. Previously, the platform assumed a single hardcoded agent harness. You can now specify the harness type and model independently, enabling support for additional agent harness backends as they become available.

**What changed:**

* **`agent_harness` configuration field.** A new `agent_harness` field controls which agent harness backend is used for task execution. The default value is `claude-code`, preserving existing behavior for current deployments.
* **`agent_model` configuration field.** A new `agent_model` field specifies the model passed to the agent harness. This replaces the previous model configuration field and provides a harness-agnostic way to select models.
* **Backward compatibility.** Existing model configuration continues to work. If only the previous model field is set, the platform uses it as the agent model automatically. The new fields take precedence when both are specified.

**What you need to do:**

No immediate action is required. Existing configurations continue to work without changes. To use a different agent harness or model, set the new `agent_harness` and `agent_model` configuration fields in your environment. The previous model configuration field remains supported for backward compatibility.

</details>

<details>

<summary>v0.9.212 - Platform API: Outbound Calls Require workspace_id and service_id (June 2026)</summary>

#### Outbound Calls Require workspace\_id and service\_id

The outbound call endpoint now requires `workspace_id` and `service_id` on every request. These fields were previously optional strings and are now required UUID values.

**What changed:**

* **`workspace_id` is required.** The `workspace_id` field on the outbound call request body is now a required UUID. Previously it was an optional string that defaulted to `null`.
* **`service_id` is required.** The `service_id` field on the outbound call request body is now a required UUID. Previously it was an optional string that defaulted to `null`.
* **Assignment always created.** Outbound calls now always create a provisional assignment using the provided workspace and service identifiers. Previously, calls without workspace and service IDs skipped assignment creation silently.

**What you need to do:**

Update any outbound call integrations to include both `workspace_id` and `service_id` as valid UUIDs in every request. Requests that omit either field or provide non-UUID values will be rejected with a validation error.

</details>

<details>

<summary>v0.9.211 - Platform API: FHIR Protocol Coerced to REST (June 2026)</summary>

#### FHIR Protocol Coerced to REST

The FHIR integration protocol has been removed as a distinct protocol type. All existing FHIR integrations are automatically coerced to REST. FHIR integrations were already handled identically to REST integrations at runtime - this change removes the unnecessary distinction.

**What changed:**

* **FHIR protocol removed.** The `protocol` field on integrations no longer accepts `fhir` as a value. Valid values are now `rest` and `desktop`. Existing integrations that were previously configured as `fhir` have been migrated to `rest` automatically.
* **Creatable protocol restricted to REST.** The integration creation endpoint accepts only `rest` as the protocol value. The `desktop` protocol remains system-provisioned and cannot be created via the public API.
* **No behavioral change.** FHIR integrations were already using the same HTTP-based integration path as REST integrations, including auth resolution, endpoint configuration, result templates, and connection probes. This change reflects that existing behavior in the data model.

**What you need to do:**

No action is required. If you have existing FHIR integrations, they have been migrated to REST automatically. If you have automation that creates integrations with `protocol: "fhir"`, update it to use `protocol: "rest"` instead.

</details>

<details>

<summary>v0.9.210 - Platform API: Open Query Write Boundary and Function Delete Cleanup (June 2026)</summary>

#### Open Query Write Boundary and Function Delete Cleanup

The open query tool (`fn_query`) now delegates read/write enforcement to the data warehouse permission layer instead of applying a client-side keyword blocklist. Deleting a Python or UDTF function now also removes the corresponding warehouse artifact.

**What changed:**

* **Open query write boundary moved to warehouse permissions.** The open query tool previously rejected SQL containing write keywords (INSERT, UPDATE, DELETE, MERGE, etc.) using a regex filter. This filter has been removed. Read/write enforcement is now handled by the warehouse permission layer - the agent's service identity has read access to the production catalog and full access only to the sandbox schema. Write statements against non-sandbox surfaces are denied by the warehouse with a permission error. This eliminates false positives from the old regex (e.g., column names containing "update") and provides a single, consistent enforcement point.
* **Open query tool description updated.** The tool description presented to the agent now explains that writes succeed only against the sandbox schema and that the warehouse denies writes elsewhere. The previous "read-only" framing has been removed.
* **LIMIT injection scoped to SELECT/WITH.** The automatic `LIMIT 100` safety cap is now applied only to SELECT and WITH (CTE) queries. Write statements (INSERT, UPDATE, DELETE, MERGE) are no longer modified with a trailing LIMIT, which would have caused a syntax error.
* **Function delete removes warehouse artifact.** Deleting a Python or UDTF function now drops the corresponding warehouse artifact before removing the registry row. The drop is idempotent - if the artifact is already gone (e.g., from a partial previous deploy), the delete still succeeds. If the warehouse is unavailable, the delete fails with a 503 error and the registry row is preserved so the operation can be retried. SQL and AI functions have no warehouse artifact, so their delete behavior is unchanged.

**What you need to do:**

No API changes are required. If you previously relied on the open query tool rejecting write keywords, note that write enforcement is now handled by warehouse permissions. Writes against the sandbox schema will now succeed through `fn_query`; writes against other schemas will return a permission error from the warehouse rather than a client-side validation error. Function deletes for Python and UDTF types are now fully clean - no orphan warehouse artifacts are left behind.

</details>

<details>

<summary>v0.9.208 - Platform API: Simulation Branch Isolation Removed (June 2026)</summary>

#### Simulation Branch Isolation Removed

The simulation API no longer supports database branch-based write isolation. Simulation sessions now write through the standard event pipeline with source tagging, and downstream consumers filter non-production sources automatically.

**What changed:**

* **`branch_name` field removed from session creation.** The `branch_name` parameter on the create simulation session endpoint has been removed. Requests that previously included `branch_name` should omit it. The field is no longer accepted.
* **Branch management endpoints removed.** The `POST /simulations/branches`, `GET /simulations/branches`, and `DELETE /simulations/branches/{branch_name}` endpoints have been removed. Branch lifecycle management is no longer part of the simulation API.
* **Simulation write isolation simplified.** Simulation sessions continue to tag all writes with a simulation source, ensuring that simulation data is excluded from production sync, outbound EHR writes, gap detection, and billing. The tagging mechanism is unchanged - only the underlying isolation strategy has been simplified.
* **No changes to simulation session behavior.** Creating sessions, stepping through conversations, forking sessions, and scoring all work exactly as before. The only difference is that `branch_name` is no longer a parameter and branch endpoints no longer exist.

**What you need to do:**

Remove any `branch_name` fields from simulation session creation requests. Remove any calls to the branch management endpoints (`POST /simulations/branches`, `GET /simulations/branches`, `DELETE /simulations/branches/{branch_name}`). No other changes are required - simulation sessions continue to be isolated from production data through source tagging.

</details>

<details>

<summary>v0.9.207 - Platform API: Enum Drift Fixes for Conversations, Metrics, Review Queue, and Channels (June 2026)</summary>

#### Enum Drift Fixes for Conversations, Metrics, Review Queue, and Channels

Several API surfaces now accept a wider set of valid values, fixing 500 errors caused by enum mismatches between stored data and API response schemas.

**What changed:**

* **SMS channel type added.** The channel type enumeration now includes `sms` as a valid value. Surfaces and conversations using SMS channels are now correctly represented in API responses.
* **Review queue completed action values expanded.** The `completed_action` field on review queue items now accepts both verb and past-tense forms: `approve`, `reject`, `correct`, `approved`, `rejected`, and `corrected`. Previously, only the past-tense forms were accepted, which caused errors when items stored with verb-form values were returned through the API.
* **Voice conversation status handling hardened.** Voice conversation status resolution now maps unexpected status values to valid statuses instead of passing unrecognized values through. Conversations with statuses outside the expected set are mapped to a safe default, preventing serialization errors in conversation list and detail responses.
* **Categorical metric grouping simplified.** Dashboard metric queries for categorical metrics now group by category value only, removing a dependency on a legacy grouping field. This fixes errors when the legacy field is absent from metric records.

**What you need to do:**

No API changes are required. These fixes resolve intermittent 500 errors on conversation, review queue, metric, and channel endpoints. If you previously encountered server errors when listing or viewing conversations, review items, or dashboard metrics, those errors should now be resolved.

</details>

<details>

<summary>v0.9.206 - Platform API: Call Intelligence Artifact Contract Centralized (June 2026)</summary>

#### Call Intelligence Artifact Contract Centralized

The terminal call intelligence artifact - the payload emitted when a call or text session ends - now uses a single, validated contract across all channels and the metric projection endpoint. This replaces the previous pattern where each channel assembled its own payload dictionary with ad-hoc field sets.

**What changed:**

* **Strict artifact validation.** The terminal call intelligence artifact is now validated against a single schema before it is emitted or projected. Missing required fields, out-of-range scores, and unknown extra fields are rejected at build time rather than silently stored. This applies to voice calls, text sessions, and simulation sessions equally.
* **Invalid direction and source values are now rejected.** Previously, unrecognized call direction values fell back to "inbound" and unrecognized source values were silently dropped. Both fields now reject invalid values with an error. If you were relying on the fallback behavior for non-standard direction or source values, those calls will now fail to emit call intelligence until the values are corrected.
* **Workspace ID included in the artifact.** The artifact now carries the workspace identifier as a required field. The metric projection endpoint validates that the workspace in the artifact matches the workspace in the request path, returning a 422 error on mismatch.
* **Metric projection accepts the shared artifact schema.** The internal metric projection endpoint now accepts the same artifact schema used by the event pipeline. The previous endpoint-specific request model has been replaced by the shared contract. All fields, types, and constraints are identical.
* **Improved error handling for call intelligence persistence.** When the event pipeline emits successfully but the hot cache write fails, the platform now distinguishes between the two failure modes. Metric projection proceeds if the durable event was emitted, even when the cache write fails. Previously, any persistence failure would skip metric projection entirely.
* **Playground calls excluded from metrics.** Calls originating from the Developer Console playground are now explicitly excluded from metric projection. Previously, playground calls could be projected as production metrics depending on how the source value was interpreted.

**What you need to do:**

No API changes are required for standard integrations. If you consume call intelligence events downstream, the payload shape is unchanged - the same fields are present with the same types. If you were sending non-standard direction or source values through a custom integration, those values must now match the canonical set (direction: "inbound" or "outbound"; source: "real", "simulation", or "playground").

</details>

<details>

<summary>v0.9.204 - Platform API: Call Intelligence Enum Normalization (June 2026)</summary>

#### Call Intelligence Enum Normalization

Call intelligence data now normalizes call direction and call source values at write time, eliminating inconsistencies caused by legacy data or mismatched labels.

**What changed:**

* **Direction and source normalization at write time.** Call intelligence events now normalize direction and source values before they are stored. Historical aliases and legacy labels are mapped to their canonical equivalents automatically. For example, calls that were previously recorded with inconsistent direction labels now resolve to the correct canonical value. Unrecognized direction values fall back to "inbound" to satisfy storage constraints; unrecognized source values are dropped rather than stored with an incorrect label.
* **Shared normalization across read and write paths.** Both the call intelligence writer and the call listing read path now use the same normalization logic. Previously, the read path maintained its own alias mapping. This change ensures that direction and source values are consistent regardless of whether you are viewing a call in the Developer Console, querying the API, or consuming analytics events.
* **Legacy data cleanup.** A data migration corrects historical rows that contained non-canonical direction or source values. Calls affected by the legacy labeling inconsistency will now display correct values in dashboards and API responses without any action required.
* **Monitoring for drift.** The platform now tracks normalization events so the team can verify that no new sources of inconsistent values appear. Once the migration has been active long enough to confirm zero drift, the legacy fallback paths will be removed.

**What you need to do:**

No API changes are required. If you previously filtered or grouped calls by direction or source and noticed inconsistent labels, those values are now normalized. Dashboards and analytics that aggregate by direction or source will produce more accurate results.

</details>

<details>

<summary>v0.9.203 - Platform API: Frozen Conversation Status Removed (June 2026)</summary>

#### Frozen Conversation Status Removed

The `frozen` conversation status has been removed from the platform. Text conversations now use only `active` and terminal statuses (`closed`, `completed`, `failed`). Runtime session ownership is managed through the hot session cache rather than a persisted lifecycle status.

**What changed:**

* **`frozen` status removed.** The `frozen` value is no longer returned in conversation status fields and is no longer accepted as a filter value when listing conversations. Conversations that would previously have been frozen now remain `active` in the durable store while releasing runtime resources through cache expiration.
* **`resumed` session status removed.** The `session_status` field on text session creation responses no longer returns `resumed`. The field now returns either `created` or `already_active`.
* **`reactivated` field removed.** The `reactivated` boolean field has been removed from conversation thread resolution and text session creation responses. Conversations no longer transition between frozen and active states.
* **Simplified conversation lifecycle.** Active conversations release runtime resources through automatic cache expiration rather than explicit freeze/thaw transitions. When a new message arrives for a conversation whose session has expired, a new runtime session is created transparently. The conversation's durable identity and history are preserved across session boundaries.

**What you need to do:**

* If you filter conversations by `status=frozen`, remove that filter. Active non-terminal conversations all have `active` status.
* If you check for `session_status: "resumed"` in text session responses, update your code to handle only `created` and `already_active`.
* If you read the `reactivated` field from conversation materialization or session responses, remove that dependency. The field is no longer present.

</details>

<details>

<summary>v0.9.202 - Platform API: MCP Integration Protocol Removed (June 2026)</summary>

#### MCP Integration Protocol Removed

The MCP (Model Context Protocol) integration protocol has been removed from the platform. Integrations now support `rest`, `fhir`, and `desktop` protocols only.

**What changed:**

* **`mcp` protocol removed.** The `mcp` protocol option is no longer accepted when creating or updating integrations. The `protocol` field now accepts `rest`, `fhir`, or `desktop` (where `desktop` remains system-provisioned only).
* **MCP-specific fields removed.** The `mcp_transport`, `mcp_command`, `mcp_args`, `mcp_url`, and `mcp_headers` fields have been removed from integration create, update, and response models. These fields are no longer accepted in requests or returned in responses.
* **Connection test simplified.** The test-connection endpoint no longer includes MCP-specific probe logic. Connection probes exercise auth resolution and send a HEAD request to `base_url` for REST/FHIR integrations.
* **Creatable protocol set narrowed.** The set of protocols available for user-created integrations is now `rest` and `fhir`. The `desktop` protocol continues to be system-provisioned only.

**What you need to do:**

* If you have integrations using the `mcp` protocol, migrate them to `rest` or `fhir` before updating. Existing MCP integrations will no longer be recognized by the platform.
* Remove any `mcp_transport`, `mcp_command`, `mcp_args`, `mcp_url`, or `mcp_headers` fields from your integration create and update requests.

</details>

<details>

<summary>v0.9.201 - Platform API: Strict Context Graph Tool Reference Validation (June 2026)</summary>

#### Strict Context Graph Tool Reference Validation

Context graph version creation now validates every tool reference against deployed workspace resources. Previously, tool references were checked against skill slugs and built-in tool IDs but did not verify that referenced platform functions or workspace data queries actually existed. This change catches misconfigured context graphs before they reach production.

**What changed:**

* **Platform function references validated at deploy time.** Tool references using the `fn_` prefix (e.g. `fn_check_eligibility`) are now verified against the workspace's deployed platform functions. If a referenced function does not exist, the context graph version creation request is rejected with a validation error listing the unresolved references.
* **Workspace data query references validated at deploy time.** Tool references using the `wsq_` prefix (e.g. `wsq_recent_appointments`) are now verified against the workspace's deployed workspace data queries. Missing queries cause the same validation rejection.
* **Skill references validated against enabled skills.** Skill-based tool references continue to be validated against the workspace's skill registry, and now require the skill to be enabled. Disabled skills cause a validation error.
* **Built-in legacy functions removed.** Five previously built-in function tools (`fn_entity_confidence`, `fn_caller_history`, `fn_patient_summary`, `fn_patient_memory_snapshot`, `fn_patient_dimension_summary`) have been removed from the platform. These functions are no longer auto-registered for every workspace. If your agent uses any of these, deploy equivalent platform functions in your workspace before updating your context graph. The open query (`fn_query`) and write (`fn_write`) tools remain available as built-in tools.

**What you need to do:**

* If your context graphs reference `fn_entity_confidence`, `fn_caller_history`, `fn_patient_summary`, `fn_patient_memory_snapshot`, or `fn_patient_dimension_summary`, deploy these as platform functions in your workspace before creating new context graph versions. Existing deployed context graph versions continue to work, but new versions referencing these tools will fail validation.
* Ensure all `fn_*` and `wsq_*` tool references in your context graphs correspond to deployed platform functions or workspace data queries in the same workspace.
* Review any disabled skills referenced by context graphs - they must be enabled to pass validation.

</details>

<details>

<summary>v0.9.200 - Platform API: Call Intelligence Migrated to Session Store and Event Pipeline (June 2026)</summary>

#### Call Intelligence Migrated to Session Store and Event Pipeline

Call intelligence data now flows through the session store and event pipeline instead of being written directly to the analytical database. This change improves write latency for call intelligence, makes intelligence data available immediately after a call ends, and unifies the data path for voice, text, and simulation sessions.

**What changed:**

* **Session store is the primary call intelligence store.** Call intelligence summaries are now written to the fast session cache and emitted through the durable event pipeline, replacing the previous direct database write. Intelligence data is available for API reads immediately after the call ends, without waiting for analytical pipeline ingestion.
* **Metric projection receives the full artifact.** The internal metric projection endpoint now receives the complete call intelligence artifact directly from the agent engine, instead of reading it back from the database after a write. This eliminates a read-after-write dependency and removes the previous 404 response when the database row had not yet been committed. The projection endpoint no longer returns a not-found status.
* **Unified intelligence path for all session types.** Voice calls, text sessions, and simulation sessions all emit call intelligence through the same session store boundary. Previously, each session type used a slightly different write path. The unified path ensures consistent data shape, timing, and event semantics across all channels.
* **Call intelligence reads from session store.** The call intelligence detail endpoint now reads from the session store hot cache instead of querying the analytical database. This provides lower-latency reads and consistent data availability across all session types.
* **Simulation intelligence simplified.** Simulation sessions no longer write intelligence data to the analytical database directly. Intelligence flows through the session store and event pipeline, then metric projection is requested through the standard platform API path. This matches the production call intelligence flow.

**What you need to do:**

No API changes are required. The call intelligence response shape is unchanged. If you consume call intelligence data through the API, you may notice improved availability - intelligence data appears immediately after a call ends rather than after analytical pipeline processing.

</details>

<details>

<summary>v0.9.199 - Platform API: Text Conversation Turns from Session Logs (June 2026)</summary>

#### Text Conversation Turns from Session Logs

Text conversation detail now reads turn data from the session log pipeline instead of inline conversation state. This aligns text conversations with the same turn resolution path used by voice conversations, providing consistent turn data across both channels.

**What changed:**

* **Turn resolution from session logs.** The text conversation detail endpoint now reads turn history from the session store (hot path) with automatic fallback to the durable analytical store for historical conversations. Previously, text conversation turns were read from inline conversation state. This change means text conversations benefit from the same turn resolution pipeline that voice conversations already use.
* **Consistent turn format.** Text and voice conversation detail responses now produce turns through the same rendering logic. Turn structure, timestamp formatting, and role mapping are identical across both channels.
* **Improved error handling.** Turn read failures now return a 502 status with a descriptive error message ("Conversation turns unavailable" or "Conversation turns invalid") instead of failing silently or returning malformed data. Transient session store read failures are handled gracefully - the endpoint falls back to the durable store rather than returning an error.

**What you need to do:**

No API changes are required. The conversation detail response shape is unchanged. If you consume text conversation turns, you may notice improved turn availability for historical conversations that previously returned empty or incomplete turn data.

</details>

<details>

<summary>v0.9.198 - Platform API: Parallel Tool Registration During Session Initialization (June 2026)</summary>

#### Parallel Tool Registration During Session Initialization

Session initialization now registers workspace tools and data warehouse functions concurrently using a structured task group, replacing the previous sequential-then-deferred approach. This reduces session startup latency and simplifies error handling.

**What changed:**

* **Concurrent tool registration.** Workspace tool registration and data warehouse function registration now run fully in parallel during session initialization. Previously, one registration path ran in the foreground while the other was deferred to a background task. Both paths now complete before the session proceeds, ensuring all tools are available to the agent from the first interaction.
* **Consistent error handling.** Both registration paths now use the same non-fatal error handling. If either registration fails, the failure is logged as a warning and the session continues without those tools. Previously, the two paths used different error handling strategies, which could produce inconsistent behavior when one path failed.
* **Tool snapshot consistency.** Because both registration paths complete before the session's tool list is finalized, all registered tools are guaranteed to be visible to the agent immediately. Previously, deferred registration could complete after the tool list snapshot was taken, causing some tools to be silently unavailable until the next session.

**What you need to do:**

No API changes are required. Sessions will initialize faster and all registered tools will be consistently available from the first turn. If you previously observed intermittent tool availability at session start, this change resolves that behavior.

</details>

<details>

<summary>v0.9.197 - Platform API: Fail-Fast Text Session Routing (June 2026)</summary>

#### Fail-Fast Text Session Routing

Text session routing and conversation directory operations now use strict fail-fast error handling. Previously, cache unavailability was silently absorbed and routing fell back to alternate lookup paths or returned empty results. Now, infrastructure errors propagate immediately so operators see one clear failure mode instead of silent behavior changes.

**What changed:**

* **Cache is a required dependency.** Text session management and conversation routing now require the hot-path cache at initialization. Passing a missing or null cache reference raises an error at startup rather than being silently accepted.
* **No silent fallback on cache errors.** Cache read and write errors (connection failures, timeouts) now propagate to the caller instead of being suppressed. Previously, errors were caught and the system returned empty results or skipped writes. This could mask infrastructure problems and cause sessions to silently stop routing. Errors are now surfaced so monitoring and alerting can catch them.
* **Consent lookup errors propagate.** SMS consent cache lookups and writes follow the same fail-fast pattern. Infrastructure errors are no longer absorbed - callers see the failure and can handle it explicitly.
* **Deprecated compatibility wrapper removed.** The legacy `resolve_or_create` conversation directory method has been removed. Callers should use the epoch-aware resolution method, which provides correct actor-epoch state for reactivated conversations.
* **Improved diagnostics for session engine cursor errors.** When session load cursors are incoherent, the error now includes the specific cursor values and emits a metric, making diagnosis faster.

**What you need to do:**

No API changes are required. If you operate self-hosted infrastructure, ensure the active-session cache is healthy and monitored. Cache unavailability will now surface as explicit errors rather than degraded routing behavior. If you were using the deprecated `resolve_or_create` conversation directory method through internal tooling, migrate to the epoch-aware resolution method.

</details>

<details>

<summary>v0.9.196 - Platform API: Simplified Metrics Hot Path (June 2026)</summary>

#### Simplified Metrics Hot Path

The metrics pipeline has been simplified by removing the per-entity metric cache from the session store. Metric values now flow exclusively through the event pipeline and the realtime metric store, reducing write overhead on the session hot path.

**What changed:**

* **Per-entity metric caching removed from the session store.** The session store no longer maintains a separate per-entity metric cache. Previously, per-entity metrics were written to both the session-level cache and the event pipeline. Now, metric values are written only to the event pipeline and served from the realtime metric store. This reduces per-session write volume and simplifies the data flow.
* **Session store scope narrowed.** The session store now handles conversation turns and call intelligence data only. Metric storage and retrieval are handled entirely by the realtime metric store, which was introduced in v0.9.191.

**What you need to do:**

No API changes are required. If you consume metrics through dashboards, call detail views, or the metric catalog endpoint, behavior is unchanged - metrics continue to be available with the same low latency provided by the realtime metric store. The only difference is internal: the redundant session-level metric cache has been removed.

</details>

<details>

<summary>v0.9.195 - Platform API: Realtime Call Intelligence Metrics for Text Sessions (June 2026)</summary>

#### Realtime Call Intelligence Metrics for Text Sessions

Text sessions now project call intelligence metrics in realtime when a session ends, matching the behavior already in place for voice calls.

**What changed:**

* **Realtime metric projection for text sessions.** When a text session completes, the platform now projects a terminal call intelligence row through the same realtime projection path used by voice calls. This means text session quality scores, conversation analytics, and custom metrics appear in dashboards and call detail views with the same low latency as voice call metrics. Previously, text session metrics relied on a separate path that could delay visibility.
* **Projection is best-effort.** Metric projection for text sessions is secondary to the committed source data. If projection fails, the source call intelligence row is unaffected and metrics will be available once the analytical pipeline catches up. A failed projection does not cause the text session cleanup to fail.

**What you need to do:**

No API changes are required. Text session metrics now appear in dashboards and analytics with the same timeliness as voice call metrics. If you consume call intelligence data for text sessions, you may notice that metric values are available sooner after session completion.

</details>

<details>

<summary>v0.9.194 - Platform API: Resilient Call List Normalization (June 2026)</summary>

#### Resilient Call List Normalization

The call list endpoint now normalizes call direction and call source values before returning results, preventing unexpected values from causing errors in the response.

**What changed:**

* **Call direction normalization.** Call direction values are now validated and normalized before inclusion in call list responses. Legacy or non-standard direction values are mapped to their canonical equivalents where a known alias exists. Unrecognized values are omitted rather than causing a server error.
* **Call source normalization.** Call source values in the call list now pass through the same normalization already applied elsewhere in the API. Previously, raw values were returned directly, which could include non-standard entries. Unrecognized source values are now omitted from the response.

**What you need to do:**

No API changes are required. Call list responses now return consistent, validated values for direction and source fields. If you previously encountered errors when listing calls with unexpected direction or source values, those calls will now appear with normalized values or with those fields set to null.

</details>

<details>

<summary>v0.9.193 - Platform API: Deterministic Event Pipeline and Strict Session Journal Validation (June 2026)</summary>

#### Deterministic Event Pipeline and Strict Session Journal Validation

The session persistence engine now enforces strict journal metadata on all entries, emits deterministic event identifiers for downstream idempotency, and removes the request-path durable store fallback.

**What changed:**

* **Deterministic event identifiers.** Turn and call intelligence events emitted to the analytics pipeline now carry stable, deterministic identifiers derived from each entry's journal position. If a retry re-emits the same event, downstream consumers deduplicate automatically. This makes the emission path at-least-once without risk of duplicate analytical records.
* **Flush-before-cache ordering.** The event pipeline now confirms delivery before writing to the active-session cache. Previously, a cache write could succeed while the durable event delivery silently failed, leaving a gap in the analytical store. Flush failures now propagate to the caller so the synced cursor is not advanced prematurely.
* **Strict journal metadata validation.** Session entries loaded from the active-session cache must carry canonical journal metadata - a journal kind, schema version, and index. Entries missing any of these fields are rejected at load time instead of being silently accepted with a fallback index. This eliminates an ambiguous migration-window code path where entries without journal metadata could produce inconsistent replay ordering.
* **Durable store replay removed from request path.** The session engine no longer falls back to the analytical data store when the active-session cache is empty. Historical replay and bulk backfill are handled by offline data repair jobs and analytical projections, keeping per-request latency predictable. The session engine constructor no longer accepts optional data-store or replay parameters.
* **Unified turn-session identity for text and audio turns.** Text and audio turn endpoints now derive session identifiers, cache keys, and conversation identifiers through a shared identity module. The derivation logic is unchanged - existing sessions continue without interruption - but the consolidation removes duplicated validation across the two endpoints.
* **Conversation detail read path simplified.** The voice conversation detail endpoint no longer queries the analytical data store as a fallback when turn data is absent from the active-session cache. If turns are expected but not found, the response includes available metadata with an empty turns array and emits an observability breadcrumb. This matches the behavior already documented for historical calls in v0.9.192.

**What you need to do:**

No API changes are required. Session persistence, event emission, and conversation detail responses use the same schemas as before. The changes affect internal reliability and consistency guarantees:

* Retry-safe event emission means analytics pipelines no longer need custom deduplication logic for turn and call intelligence events.
* Calls where turn data has expired from the active-session cache now consistently return an empty turns array in the detail response, rather than attempting a slow fallback query.

</details>

<details>

<summary>v0.9.192 - Platform API: Listed Call Entity Fallback for Call Detail (June 2026)</summary>

#### Listed Call Entity Fallback for Call Detail

The call detail endpoint now resolves detail for all calls shown in the call list, even when turn-level data is not available in durable stores.

**What changed:**

* **Listed entity fallback.** When a call appears in the call list but has no durable turn-level record (for example, older simulation or playground calls that predate the current session storage pipeline), the call detail endpoint now returns a minimal detail response built from the call's metadata and intelligence data. Previously, these calls returned a 404 from the detail endpoint despite being visible in the list. The response includes all available scalar fields (status, duration, direction, quality score, summaries, recording information) with an empty turns array.
* **Consistent list-to-detail resolution.** Every call returned by the call list endpoint is now guaranteed to resolve through the call detail endpoint. This eliminates the inconsistency where some listed calls - particularly historical simulation and playground entries - could not be inspected.
* **Graceful degradation.** If the metadata lookup encounters a transient error, the endpoint falls through to the existing 404 response rather than returning a 500. This keeps the fallback path safe for callers that retry on not-found responses.

**What you need to do:**

No API changes are required. Call detail responses use the same schema as before. Calls that previously returned 404 despite appearing in the call list will now return a valid detail response. The `turns` array will be empty for calls where turn-level data was not preserved, and recording information will be included when available.

</details>

<details>

<summary>v0.9.191 - Platform API: Realtime Metric Values and Catalog Metadata (June 2026)</summary>

#### Realtime Metric Values and Catalog Metadata

The metric store now makes recent values available with lower latency and returns additional public metadata from the metric catalog.

**What changed:**

* **Lower-latency metric reads.** Recent metric values become available to dashboards and call detail views without waiting for a later batch refresh.
* **Expanded metric catalog fields.** The metric catalog endpoint now returns `source`, `custom_source_key`, `period_granularity`, and `latency_tier` on each catalog entry. These fields were previously available only in workspace settings. The `key` field validation is also now explicit in the response schema (lowercase alphanumeric with underscores, 1-64 characters).
* **Renamed product surface.** The metric store is now referred to as the "Realtime Metric Store" throughout the API and dashboard surfaces, replacing the previous "Universal Metric Store" name. The default metrics overview dashboard title has been updated accordingly.

**What you need to do:**

No request changes are required. If you consume the metric catalog endpoint, you can optionally use the new `source`, `period_granularity`, and `latency_tier` fields for filtering or display.

</details>

<details>

<summary>v0.9.190 - Platform API: Simplified Call Detail Source Resolution (June 2026)</summary>

#### Simplified Call Detail Source Resolution

The call detail endpoint now uses a simplified resolution path that removes redundant fallback layers and resolves call data from fewer, more predictable sources.

**What changed:**

* **Simplified source chain.** Call detail resolution now follows a shorter, deterministic path: the primary voice service, then durable conversation storage, then simulation records. Previously, the endpoint checked additional intermediate stores that duplicated data already available through the durable path. Removing these redundant layers reduces per-request latency and eliminates edge cases where stale intermediate data could shadow more complete records.
* **Legacy turn store removed.** Historical conversation turns stored in an older per-turn format are no longer queried. Turn data is now served exclusively from the hot session cache or the durable session log. Calls that predate both stores may return without turn-level detail. This change has no effect on calls created after the session log pipeline was enabled.
* **Consistent detail dependencies.** All call and conversation detail endpoints now share a single set of read-path dependencies initialized at startup, rather than constructing them per request. This ensures consistent behavior across the call detail, conversation detail, and turn-backfill paths.
* **Simulation calls resolved independently.** Simulation session detail is now treated as a distinct source rather than a fallback in the real-call chain. If a call identifier matches a simulation session, it is resolved directly without first attempting voice or durable lookups. This avoids unnecessary queries for identifiers that were never real calls.

**What you need to do:**

No API changes are required. Call detail responses have the same schema and fields as before. If you have calls from before the durable session log was enabled, those calls may return without conversation turns - this was already the case for most of these calls in practice.

</details>

<details>

<summary>v0.9.189 - Platform API: Fail-Fast Startup Validation for Hot-Path Dependencies (June 2026)</summary>

#### Fail-Fast Startup Validation for Hot-Path Dependencies

The platform now validates all critical runtime dependencies at startup and refuses to serve traffic if any required dependency is unavailable. This applies to both the Platform API and the Connector Runner.

**What changed:**

* **Startup connection verification.** Services that depend on a fast session cache now verify connectivity at startup with a bounded timeout. If the cache is unreachable, the service fails immediately with a clear error instead of starting and silently dropping writes or returning degraded responses during live conversations.
* **Required event pipeline validation.** In deployed environments, services that emit world events now validate that the event pipeline is fully configured at startup. If the event pipeline endpoint or target is missing, the service refuses to start. This prevents silent data loss where events would be dropped because the pipeline was partially configured.
* **Conversation directory cache lookup.** The conversation resolution path now checks the active-session cache before querying the durable store. When a conversation is found in the cache and is active, the system refreshes its time-to-live and returns immediately without hitting the database. This reduces per-message latency on the hot path for active conversations.

**What you need to do:**

No API changes are required. These are infrastructure-level reliability improvements. Services will now fail loudly at startup if dependencies are misconfigured, rather than degrading silently at runtime. If you manage your own deployment configuration, ensure that all required environment parameters for the session cache and event pipeline are set before deploying.

</details>

<details>

<summary>v0.9.188 - Platform API: Text Session Latency and Error Handling Improvements (June 2026)</summary>

#### Text Session Latency and Error Handling Improvements

Text sessions now load conversation history exclusively from the fast cache on the live interaction path, and the text interact endpoint returns clearer error responses when agent processing fails.

**What changed:**

* **Cache-only session loading on the live path.** Text session turns no longer fall back to the durable analytics store when the fast cache is empty. The durable store query could add significant latency to individual turn requests. Live sessions now load from the fast cache only, keeping per-turn response times predictable. Durable store replay remains available for offline recovery and batch scenarios where latency constraints are relaxed.
* **Turn count in text interact response.** The text interact response now includes a `turn_count` field reflecting how many turns have been completed in the session. The conversation turn count is reconciled between the session engine and the upstream response, using the higher value to ensure accuracy.
* **Explicit error responses.** The text interact endpoint now returns structured HTTP error responses instead of silently producing empty results. Agent timeouts return a 504 (Gateway Timeout), agent processing failures return a 502 (Bad Gateway), and sessions where the agent produces no response also return a 502 with a descriptive message. Previously, some of these failure modes could result in a successful response with an empty message list.
* **Deferred terminal artifacts for text sessions.** Conversation summaries and call intelligence data are now written only when a text session is finalized, not after every turn. Active turns still sync session state incrementally, but analytical artifacts are terminal - writing them mid-conversation was both slow and semantically incorrect. This reduces per-turn processing overhead.
* **Post-call metric projection authentication.** The post-call metric projection pipeline now supports agent-scoped authentication tokens in addition to service account tokens. This allows metric projection to proceed using the session's existing credentials when available, reducing the need to mint additional service tokens.
* **Cleanup ordering for agent sessions.** Agent session credentials are now revoked after all post-call processing is complete, rather than at the start of cleanup. This ensures that workspace-scoped cleanup operations (such as metric projection) can use the session's credentials through their completion.

**What you need to do:**

No API changes are required. Clients that consume the text interact response can optionally read the new `turn_count` field. Clients that previously treated empty message lists as a soft failure should be aware that these cases now return explicit HTTP errors (502 or 504). The response schema is otherwise unchanged.

</details>

<details>

<summary>v0.9.187 - Platform API: Restored Call Detail Turns from Durable Storage (June 2026)</summary>

#### Restored Call Detail Turns from Durable Storage

The call detail endpoint now recovers conversation turns from durable storage when the primary source returns a valid call record but its turn data has expired.

**What changed:**

* **Turn backfill from durable storage.** When the call detail endpoint returns a call record that has metadata (status, duration, participants) but no conversation turns, the platform now checks durable conversation storage for turn data. If turns are found, they are merged into the call detail response. This addresses cases where short-lived caches expire before the call detail is requested, which previously resulted in structurally valid responses with empty turn lists.
* **Additional fallback for fully missing calls.** Calls that are not found in the primary call store are now also looked up in durable conversation storage before falling back to the entity-only projection. This expands the window during which call details with full turn history remain available.
* **Conversation lookup by entity ID.** When a call cannot be found by its call identifier in conversation storage, the platform now resolves the underlying call reference from the entity store and retries the conversation lookup. This handles cases where the call was recorded under a telephony-assigned identifier that differs from the entity ID used in the API.

**What you need to do:**

No API changes are required. The call detail endpoint returns the same response shape. Calls that previously returned empty turn lists may now return full conversation history. This is a backward-compatible improvement - clients that already handle turns will display richer data automatically.

</details>

<details>

<summary>v0.9.186 - Platform API: Metrics Pipeline Ordering and Transport Resilience (June 2026)</summary>

#### Metrics Pipeline Ordering and Transport Resilience

The realtime metrics pipeline now uses improved ordering rules when resolving duplicate metric values, and the event transport handles additional failure modes gracefully.

**What changed:**

* **Improved metric value ordering.** When multiple metric value events arrive for the same metric, the pipeline now considers the metric's effective period as a secondary ordering signal after the computed timestamp. This ensures that a recomputed metric for a newer period takes precedence over an older-period retry, even if the retry arrived later. Previously, only the computed timestamp and arrival time were used for ordering.
* **Deterministic tiebreaker for retries.** The final tiebreaker for metric value deduplication now uses ascending event identity order instead of descending. This produces stable, repeatable results during replay without relying on event identity as a freshness signal. The change only affects the rare case where all other ordering signals are identical.
* **Transport error handling.** The event transport now catches timeout and connection failures during flush and converts them into structured send errors. Previously, these failure types could propagate as unhandled exceptions. Other unexpected errors still propagate normally. This is a fail-closed design - callers receive an explicit error rather than a silent retry.
* **Workspace-scoped query validation.** Query validation for workspace-scoped analytical queries now uses stricter pattern matching to prevent false matches against similarly named catalog paths. This reduces the risk of an unscoped query accidentally bypassing workspace isolation.

**What you need to do:**

No API changes are required. Metric values are resolved using the same endpoints and response shapes. The ordering improvement may change which value is selected as the latest in rare cases where duplicate events with identical computed timestamps existed. Transport error handling changes are internal to the event pipeline and do not affect API callers.

</details>

<details>

<summary>v0.9.185 - Platform API: Canonical Session Replay for Text Sessions (June 2026)</summary>

#### Canonical Session Replay for Text Sessions

The session engine now replays text session history from a canonical journal projection, replacing the previous row-order-based fallback. Returning text sessions load interaction history from a cleaned, indexed projection that guarantees deterministic replay order regardless of how historical data was originally ingested.

**What changed:**

* **Journal-indexed replay.** Each interaction log entry now carries a stable journal index that defines its absolute position in the session timeline. When a returning session is loaded from the durable store, entries are ordered by journal index rather than ingestion timestamp. This eliminates rare ordering inconsistencies that could occur when entries shared identical timestamps.
* **Canonical projection filtering.** The durable store query now filters to only return entries that carry canonical journal metadata. Legacy entries without journal metadata are excluded from cold replay unless they have been explicitly migrated through a backfill process. This ensures the session engine always receives well-structured, typed entries.
* **Replay limit.** Cold session replay is now capped at a defensive upper bound to prevent unbounded query results for abnormally large sessions. Normal text sessions are orders of magnitude smaller than this limit. If the limit is reached, the engine logs a warning and continues with the available entries.
* **Improved error classification for durable store failures.** Failures when reading from the durable store are now classified as transient or permanent based on the error type. Transient failures (timeouts, temporary unavailability) return an empty result and allow the session to proceed without historical context. Permanent failures (authentication errors, missing resources, malformed queries) are raised immediately so they surface during development rather than silently degrading in production.
* **Session state derivation from full interaction log.** Session state - including current navigation position, visited states, message count, and state group boundaries - is now derived from the complete interaction log after replay, rather than being accumulated incrementally during entry loading. This produces consistent state regardless of whether the session was loaded from cache or the durable store.
* **Journal metadata on sync writes.** New interaction log entries written during session sync now include journal kind, schema version, and journal index metadata. This ensures that entries written by the current engine version are immediately readable by the canonical projection without requiring backfill.

**What you need to do:**

No API changes are required. The session engine continues to use the same two-tier storage model (fast cache for active sessions, durable store for recovery). Text sessions that were active before this change will continue to work - the engine falls back gracefully when journal metadata is not yet present. Historical sessions will gain canonical replay ordering as the backfill process runs.

</details>

<details>

<summary>v0.9.184 - Platform API: Deduplicated Voice Call List (June 2026)</summary>

#### Deduplicated Voice Call List

The voice conversation list endpoint now returns exactly one row per logical call, eliminating duplicate entries that could appear when multiple conversation records existed for the same call.

**What changed:**

* **One row per call.** The voice conversation list now deduplicates records that share the same underlying call identity. When multiple records exist for a single call, the endpoint selects the most recently updated record. This prevents the call list from showing the same call multiple times.
* **Deterministic ordering.** The deduplication uses a stable tiebreaker so results are consistent across repeated requests. Records are ranked by last-updated time, then creation time, then record identity.

**What you need to do:**

No API changes are required. The response shape is unchanged. If your call list previously showed duplicate rows for certain calls, those duplicates are now resolved automatically.

</details>

<details>

<summary>v0.9.183 - Platform API: Strict Session Persistence Validation (June 2026)</summary>

#### Strict Session Persistence Validation

The voice agent engine now validates all session persistence dependencies at startup and fails fast if any required backing service is unavailable. Previously, persistence operations used a best-effort strategy that silently swallowed errors during conversation writes, turn storage, and metric recording. This could mask configuration problems and lead to data loss that was only discovered after the fact.

**What changed:**

* **Startup validation for session persistence.** The agent engine now verifies that all required session persistence services are reachable and healthy before accepting any sessions. If a required dependency is unavailable at startup, the engine fails immediately with a clear error rather than starting in a degraded state.
* **Errors propagate during conversation writes.** Conversation persistence, turn storage, call intelligence writes, and session metric updates now propagate errors to the caller instead of catching and logging them silently. This ensures that write failures are visible and can trigger appropriate retry or alerting behavior.
* **Required dependency warmup with retry.** The agent runtime now retries initial persistence and analytics dependency checks with backoff before accepting sessions. If a required dependency remains unavailable, startup fails instead of silently entering a degraded state.
* **Session cleanup runs unconditionally.** Text session cleanup now runs in a `finally` block, ensuring session resources are released even when session log synchronization fails or times out.
* **Improved observability for sync failures.** Session state synchronization failures now emit structured log fields and increment a dedicated metric counter, making it easier to detect and alert on persistence issues in monitoring dashboards.

**What you need to do:**

No API changes are required. If your deployment previously started successfully with misconfigured persistence backends, it may now fail at startup with a clear error message indicating which dependency is unreachable. Ensure all required backing services are available and healthy before starting the agent engine.

</details>

<details>

<summary>v0.9.182 - Platform API: Restored Voice Conversation Reads (June 2026)</summary>

#### Restored Voice Conversation Reads

Voice conversation detail now reads turn data from multiple sources to ensure complete transcript availability across all conversation ages.

**What changed:**

* **Lifecycle-aware turn resolution.** The voice conversation detail endpoint now recovers turns across active, recently completed, and historical calls. This improves transcript availability without changing the response shape.
* **Consistent turn ordering for summaries.** Voice conversation summary queries now return deterministic results when multiple analytical records exist for the same call. The endpoint selects the most recent record per call, eliminating duplicate rows that could inflate conversation lists.
* **Accurate turn counts.** The voice conversation detail response now reports turn counts derived from the resolved turn data rather than relying solely on a stored counter. This fixes cases where the turn count could show zero for conversations whose turns had migrated between storage tiers.

**What you need to do:**

No changes required. The conversation detail endpoint returns the same response shape. Turn data is now more reliably populated for conversations across all lifecycle stages.

</details>

<details>

<summary>v0.9.181 - Platform API: Simplified Metrics Hot Path (June 2026)</summary>

#### Simplified Metrics Hot Path

The metric store now serves hot metric values from a low-latency cache instead of a federated database serving layer. Metric projection writes values to the cache with a TTL and simultaneously flushes durable metric events into the analytics pipeline for long-term history. This simplifies the metrics architecture and reduces read latency for dashboard and API consumers.

**What changed:**

* **Hot metric reads from cache.** All metric value endpoints - list latest values, get values by key, get metric trend, and get subject metrics - now read from a fast in-memory cache instead of querying a federated serving table. Read latency is lower and no longer depends on the availability of the federated database layer.
* **Metric projection writes to cache and event pipeline.** When a call-intelligence row is projected into metrics, the platform extracts metric samples, writes them to the hot cache with a time-to-live, and flushes durable metric events through the event pipeline into the analytics data warehouse. The cache is the bounded hot window; older values are available from the data warehouse.
* **Idempotent projection.** Metric projection is idempotent based on deterministic metric value identifiers. Duplicate projections for the same source artifact are detected in the cache and skipped, so retries do not inflate metric counts.
* **Removed on-demand metric evaluation endpoint.** The `POST /{metric_key}/evaluate` endpoint has been removed. On-demand metric preview was previously available for AI query metrics over call intelligence. This capability may return in a future release through a different surface.
* **Removed generic artifact projection endpoint.** The internal `POST /metrics/artifacts/project` endpoint has been removed. Metric projection is now handled exclusively through the call-intelligence projection path.
* **`queued_jobs` field deprecated.** The `queued_jobs` field in metric projection responses is now always `0` and is deprecated. It will be removed in a future version.
* **Dashboard metric queries updated.** Dashboard templates that previously queried metric values and freshness from a federated serving table now query the analytics pipeline outputs directly. No dashboard configuration changes are required - templates are updated automatically.

**What you need to do:**

* If you were using the `POST /{metric_key}/evaluate` endpoint for on-demand metric preview, remove those calls. This endpoint is no longer available.
* If you were using the internal `POST /metrics/artifacts/project` endpoint, migrate to the call-intelligence projection path.
* If you were reading `queued_jobs` from metric projection responses, stop relying on this field. It is deprecated and always returns `0`.
* No changes are needed for metric read endpoints. The same query parameters and response shapes are returned - only the underlying serving layer has changed.

</details>

<details>

<summary>v0.9.180 - Platform API: Simplified Session Journal Persistence (June 2026)</summary>

#### Simplified Session Journal Persistence

Session persistence has been simplified across all session types. The platform now treats the interaction log as the single source of truth for session state, removing redundant state checkpoints and mutable cursor records that were previously maintained alongside the log.

**What changed:**

* **Interaction log is the sole session record.** Session state - including navigation position, visited states, and message history - is now derived entirely from the interaction log on load. The platform no longer maintains a separate mutable checkpoint record alongside the log. This eliminates a class of consistency issues where the checkpoint and log could diverge after partial write failures.
* **Text conversation state persistence simplified.** Text sessions no longer perform a separate state-save step during session cleanup. Instead, new interaction log entries are flushed through the session store, and all downstream state is derived from the log on the next session load. This reduces the number of writes during session teardown and removes the version-conflict error path that could occur when two actors raced to save state.
* **Conversation reactivation simplified.** Reactivating a frozen text conversation no longer requires reading or writing a version counter. The platform transitions the conversation status directly, and the next session load reconstructs state from the interaction log. If a reactivated session has no cached log entries available (for example, after cache expiry), the platform logs a warning so operators can monitor for sessions that may need durable journal replay.
* **Durable event emission failures now propagate.** When the platform emits session journal entries to the durable event pipeline, failures are no longer silently swallowed. If the durable write fails, the error propagates so callers do not advance their sync cursor past unsent entries. This ensures at-least-once delivery to the durable store. Cache write failures after a successful durable emit are logged but do not cause errors, since the durable record is already safe.
* **Simulation trace derived from logs.** Simulation trace data (turn counts, visited states) is now computed directly from the cached interaction log entries rather than read from a separate cursor record. This simplifies the trace endpoint and ensures trace data is always consistent with the actual interaction history.

**What you need to do:**

No changes required. The simplified persistence model applies automatically to all session types - text, voice, and simulation. Sessions resume correctly from the interaction log, and the behavioral improvement - fewer writes, no version conflicts during state save, stronger delivery guarantees for journal events - applies without configuration changes.

</details>

<details>

<summary>v0.9.179 - Platform API: Durable Session State Fallback (May 2026)</summary>

#### Durable Session State Fallback

The session state engine now supports a two-tier storage model. Session state is read from the hot cache first, and if no data is found (for example, after the cache entry expires), the engine falls back to a durable store populated by the platform's event pipeline. This ensures that returning sessions can always resume from where they left off, even after extended inactivity.

**What changed:**

* **Durable fallback for session state.** When the fast cache does not contain session data for a conversation, the engine now queries a durable store that is continuously populated from conversation events. Previously, if the cache entry had expired, the session would start fresh with no prior interaction history. Now, interaction history is recovered from the durable store and the session resumes correctly.
* **Automatic state derivation on fallback load.** When session state is loaded from the durable store, the engine derives the agent's navigation position - current state, message count, visited states, and returning-user status - directly from the interaction history. This removes the dependency on a separate cursor record when resuming from the durable store.
* **Cache-first performance.** Active sessions continue to load from the fast cache with no additional latency. The durable store is only queried when the cache is empty, so the common case (resuming an active session) is unaffected.

**What you need to do:**

No changes required. The durable fallback applies automatically to all session types - text, voice, and simulation. Sessions that previously lost state after cache expiration will now resume correctly.

</details>

<details>

<summary>v0.9.178 - Platform API: Voice Session State Persistence (May 2026)</summary>

#### Voice Session State Persistence

Voice calls now use the same unified session state engine that was previously introduced for text sessions (v0.9.176) and simulations (v0.9.177). Session state for voice calls - including interaction history and navigation position - is now persisted incrementally through the shared state engine.

**What changed:**

* **Unified state persistence for voice calls.** Voice sessions now persist their interaction state through the same incremental session state engine used by text sessions and simulations. Previously, voice sessions used a separate persistence path. The unified approach ensures that voice call data flows through the same analytical pipeline, and session state is stored and retrieved consistently across all session types.
* **Automatic state sync on call completion.** When a voice call ends, the platform syncs the session state to the persistent store before flushing call intelligence data. This ensures that the full interaction history is available for post-call analytics, metric evaluation, and review queue processing.
* **Graceful error handling.** If the state sync encounters an error during call teardown, the failure is logged and the rest of the call cleanup - including call intelligence emission and transcript persistence - continues without interruption. No call data is lost due to a state sync failure.

**What you need to do:**

No changes required. Voice calls automatically use the unified state engine. The behavioral improvement - consistent state persistence across all session types - applies automatically.

</details>

<details>

<summary>v0.9.177 - Platform API: Unified Session State Engine for Simulations (May 2026)</summary>

#### Unified Session State Engine for Simulations

Simulation sessions now use the same unified session state engine that was introduced for text sessions in v0.9.176. Session state for simulations - including interaction history, navigation position, write authorization, and state group tracking - is persisted incrementally rather than as a monolithic blob.

**What changed:**

* **Incremental state persistence for simulations.** Simulation sessions (text playground, bridge runs, and forked sessions) now persist interaction log entries incrementally after each step, matching the behavior already in place for text sessions. Previously, simulation sessions serialized and replaced the entire session state on every step. The new approach reduces write volume and eliminates edge cases where concurrent fork branches could interfere with each other's state.
* **Session forking uses incremental copies.** When a simulation session is forked into multiple branches, each branch receives an independent copy of the parent session's interaction history. Branches no longer share mutable references to the parent's state, preventing cross-branch contamination during concurrent execution.
* **Session cleanup removes all state.** Destroying a simulation session now removes both the incremental interaction history and the session cursor, ensuring no orphaned state remains after cleanup.
* **Trace and prompt endpoints read from incremental store.** The simulation trace and prompt log endpoints now read interaction history from the incremental session store rather than the legacy monolithic blob. The trace reconstruction logic also handles both the current and previous log entry type identifiers, so sessions that span a deployment boundary render correctly.
* **Lightweight session metadata.** Simulation-specific metadata (workspace, service, branch, entity binding) is now stored separately from the interaction log. This reduces the payload size for metadata lookups that do not need the full interaction history.

**What you need to do:**

No changes required. Existing simulation sessions that were created before this update will continue to work - the platform handles both the previous and current persistence formats transparently. Active sessions will migrate to the new format on their next step. The behavioral improvement - faster step persistence, safer forking, and cleaner cleanup - applies automatically.

</details>

<details>

<summary>v0.9.176 - Platform API: Unified Session State Engine for Text Sessions (May 2026)</summary>

#### Unified Session State Engine for Text Sessions

Text sessions now use a unified session state engine that persists the authoritative interaction log incrementally after each turn, rather than reconstructing session state from durable conversation turns on every request.

**What changed:**

* **Incremental state persistence.** After each agent turn, only the new interaction log entries are appended to the session store. Previously, the platform replayed the full conversation turn history into the engine on every request. The new approach reduces resume latency and eliminates state-reconstruction edge cases.
* **Cursor-based session resume.** The agent's navigation position - active context graph state, wait condition, message index, and entity-level write scope - is persisted as a cursor alongside the interaction log. On resume, the cursor is restored directly rather than being inferred from turn metadata. If the persisted state name no longer exists in the current context graph (e.g., after a graph update between sessions), the agent falls back to the default state gracefully.
* **Returning user detection.** Sessions that load prior interaction history are automatically marked as returning-user sessions, so prompt rendering skips first-time-meeting framing without requiring explicit flags.
* **Backward-compatible log deserialization.** Sessions that were persisted under the previous turn-based format are automatically converted to the new interaction log format on load. No migration step is required.

**What you need to do:**

No changes required. Existing text sessions and conversations continue to work. Sessions persisted under the previous format are migrated transparently on next resume. The behavioral improvement - faster resumes and more reliable state continuity - applies automatically.

</details>

<details>

<summary>v0.9.175 - Platform API: Workspace Data Queries CRUD Overhaul (May 2026)</summary>

#### Workspace Data Queries CRUD Overhaul

The workspace data queries REST surface has been redesigned around standard CRUD semantics with stable UUID-based addressing. Queries are now created with POST, partially updated with PATCH, and addressed by `query_id` (UUID) instead of `name` in all endpoints.

**Breaking changes:**

* **`PUT /{name}` replaced by `POST` (create) and `PATCH /{query_id}` (update).** The upsert-by-name deploy endpoint has been removed. Create a query with `POST /v1/{workspace_id}/data_queries` (returns `201 Created` with a server-generated `id`), then update it with `PATCH /v1/{workspace_id}/data_queries/{query_id}`.
* **All per-query endpoints now use `query_id` (UUID) instead of `name`.** This includes GET, PATCH, DELETE, and invoke. The `name` field remains on the query object and is unique per workspace, but it is no longer part of the URL path.
* **`POST /{name}/test` removed.** The test-invoke endpoint has been removed. Use the standard invoke endpoint for both testing and production execution.
* **`input_schema` removed from responses.** The JSON Schema that was derived from `parameters[]` is no longer returned. Consumers can derive the schema locally from the parameter list if needed.
* **Test telemetry fields removed.** The `last_test_at`, `last_test_status`, `last_test_error`, and `last_test_duration_ms` fields have been removed from query responses.

**New behavior:**

* **`last_invoked_at` field.** Query responses now include `last_invoked_at`, which records the timestamp of the most recent successful invocation (whether via the HTTP invoke endpoint or the agent runtime). NULL until first invocation.
* **PATCH partial updates.** The update endpoint accepts any subset of fields. Only fields present in the request body are changed; omitted fields retain their current values. When `parameters` is included, the entire parameter list is replaced. Validation runs against the post-update state, so placeholder/parameter parity is enforced even when only one side changes.
* **`name` is mutable.** Queries can be renamed via PATCH. A rename that collides with an existing name returns `409 Conflict`.
* **Create returns `409 Conflict` for duplicate names.** Previously, PUT would silently overwrite. Now, attempting to create a query with a name that already exists returns a conflict error.
* **Cascade delete.** Deleting a query automatically removes its parameter rows. No separate cleanup is needed.
* **List endpoint returns `parameter_count` instead of full parameters.** The list response now includes a `parameter_count` integer per query instead of the full parameter list. Use GET by ID to fetch full parameter details.
* **`deployed_at` and `deployed_by` are now non-nullable.** These fields are always present on query responses.

**What you need to do:**

* Update API clients to use `POST` for creation and `PATCH /{query_id}` for updates instead of `PUT /{name}`.
* Update all per-query endpoint calls to use the query's UUID (`id` field from create/list responses) instead of `name` in the URL path.
* Remove any references to the test-invoke endpoint or test telemetry fields.
* If you were consuming `input_schema` from responses, derive it locally from the `parameters` array.

</details>

<details>

<summary>v0.9.174 - Platform API: Audit Events Emitted via Event Pipeline (May 2026)</summary>

#### Audit Events Emitted via Event Pipeline

Audit events are now emitted through the platform's unified event pipeline instead of being written directly to the data store. This aligns audit data with the same event infrastructure used by other platform subsystems, improving consistency and enabling downstream consumers to process audit events alongside other domain events.

**What changed:**

* **Unified event delivery.** Audit events - including PHI access records, route-handler audit logs, and compliance-relevant actions - now flow through the platform event pipeline. Each audit event carries a domain, event type, entity reference, and source identifier, matching the structure used by other event types in the platform.
* **Buffered, best-effort delivery.** The audit writer continues to buffer events in memory and flush them periodically or when the buffer reaches capacity. Fire-and-forget semantics are preserved - audit logging never blocks request processing. If an individual event fails to emit, the failure is logged and processing continues.
* **Pipeline flush after emit.** After emitting buffered events, the writer explicitly flushes the event pipeline to ensure events are delivered before the buffer is cleared. This ordering guarantee is compliance-critical.

**What you need to do:**

No changes required. Audit events continue to be generated automatically by PHI access middleware and explicit audit calls in route handlers. Downstream consumers that read audit data from the event pipeline will begin receiving events in the new format.

</details>

<details>

<summary>v0.9.173 - Platform API: Python and UDTF Platform Functions Now Invoke User Code (May 2026)</summary>

#### Python and UDTF Platform Functions Now Invoke User Code

Python scalar and UDTF (table-valued) platform functions now correctly invoke the authored `def main(...)` entry point at execution time. Previously, deploying a Python or UDTF function would register the function successfully, but invoking it would return NULL (scalar) or raise a runtime error (UDTF) because the user-defined `main` function was never called.

With this release:

* **Python scalar functions** - The platform appends a trampoline call that invokes `main` with the declared parameters and returns its result. Functions that previously returned NULL silently now execute as intended.
* **UDTF functions** - The platform wraps the authored body inside a handler class with an `eval` method that calls `main` and yields its results. The `HANDLER` clause is now included in the generated DDL. Functions that previously raised a missing-name error now execute correctly.
* **UDTF invocation syntax** - Table-valued functions are now invoked directly in the `FROM` clause rather than wrapped in a `TABLE(...)` expression, which resolves errors for registered table-valued functions.

**Validation improvements:**

* Function bodies that do not declare a top-level `def main(...)` are now rejected at deploy time with a clear error, rather than being accepted and failing silently at invocation.
* Parameter names are validated as legal identifiers at deploy time, preventing malformed names from producing invalid generated code.

**What you need to do:**

No changes required. Existing Python and UDTF functions that already declare `def main(...)` will begin executing correctly. Functions that were returning NULL or erroring will now produce the expected results. If you have functions deployed without a `def main(...)` entry point, redeploying them will surface a validation error prompting you to add one.

</details>

<details>

<summary>v0.9.172 - Platform API: Expanded Entity Fields in FHIR API Views (May 2026)</summary>

#### Expanded Entity Fields in FHIR API Views

The FHIR API views for patients, practitioners, appointments, and slots now expose additional entity fields that were previously only available through lower-level data access. These fields are returned directly in the standard FHIR entity views, so agents, integrations, and front-end applications can access them without extra lookups.

**Patient view - new fields:**

| Field                       | Type              | Description                                     |
| --------------------------- | ----------------- | ----------------------------------------------- |
| `address_state`             | string (nullable) | State from the patient's address                |
| `address_postal_code`       | string (nullable) | Postal/ZIP code from the patient's address      |
| `address_city`              | string (nullable) | City from the patient's address                 |
| `primary_payer_name`        | string (nullable) | Name of the patient's primary insurance payer   |
| `practice_payer_id`         | string (nullable) | Practice-specific payer identifier              |
| `member_id`                 | string (nullable) | Insurance member identifier                     |
| `policyholder_name`         | string (nullable) | Name of the insurance policyholder              |
| `policyholder_relationship` | string (nullable) | Relationship of the policyholder to the patient |
| `policyholder_dob`          | string (nullable) | Date of birth of the policyholder               |

**Practitioner view - new fields:**

| Field                 | Type               | Description                                         |
| --------------------- | ------------------ | --------------------------------------------------- |
| `member_id`           | string (nullable)  | Provider member identifier                          |
| `scheduling_eligible` | boolean (nullable) | Whether the practitioner is eligible for scheduling |

**Appointment view - new fields:**

| Field              | Type               | Description                                        |
| ------------------ | ------------------ | -------------------------------------------------- |
| `cancel_reason`    | string (nullable)  | Reason for appointment cancellation                |
| `appointment_type` | string (nullable)  | Type or category of the appointment                |
| `duration_minutes` | integer (nullable) | Duration of the appointment in minutes             |
| `modality`         | string (nullable)  | Appointment modality (e.g., in-person, telehealth) |

**Slot view - new fields:**

| Field              | Type              | Description                                                  |
| ------------------ | ----------------- | ------------------------------------------------------------ |
| `provider_id`      | string (nullable) | Unique identifier for the provider associated with the slot  |
| `facility_id`      | string (nullable) | Unique identifier for the facility where the slot is offered |
| `specialty`        | string (nullable) | Provider specialty for the slot                              |
| `visit_type_ids`   | string (nullable) | Identifiers for visit types accepted in the slot             |
| `visit_type_names` | string (nullable) | Display names for visit types accepted in the slot           |

All new fields are additive and nullable. Existing queries and integrations continue to work without changes. The slot view's `provider_name` field now also falls back to the practitioner display name when the previously used source is unavailable.

No client changes are required.

</details>

<details>

<summary>v0.9.171 - Platform API: Cross-Entity Appointment Patient Name Resolution (May 2026)</summary>

#### Cross-Entity Appointment Patient Name Resolution

Appointment entities in the world model now resolve the patient display name automatically when the source system does not include it in the appointment data. Previously, if the upstream system omitted the patient name from appointment records, the `appointment_patient_display` field remained empty even when the patient's name was available elsewhere in the world model.

With this release, the entity snapshot pipeline performs a cross-entity lookup: when an appointment references a patient but has no display name, the platform resolves the patient's identity through the entity linkage layer and populates the appointment's patient display name from the linked patient record. This happens automatically during entity processing with no configuration required.

**Behavior:**

* If the appointment data already includes a patient display name, that value is preserved as-is.
* If the patient display name is missing but the appointment references a patient, the platform resolves the name from the linked patient entity's demographic data.
* If neither source provides a name, the field remains empty.

No client changes are required. Existing queries and integrations that read `appointment_patient_display` will see improved data completeness for appointments where the source system omits patient names.

</details>

<details>

<summary>v0.9.170 - Platform API: Slot Practitioner and Facility Identifiers (May 2026)</summary>

#### Slot Practitioner and Facility Identifiers

Slot entities in the world model now include practitioner and facility identifiers alongside the existing practitioner display name. These typed identifiers let agents and integrations match slots to specific providers and locations without relying on display-name string matching.

**New fields on slot entities:**

| Field                  | Type              | Description                                                     |
| ---------------------- | ----------------- | --------------------------------------------------------------- |
| `slot_practitioner_id` | string (nullable) | Unique identifier for the practitioner associated with the slot |
| `slot_facility_id`     | string (nullable) | Unique identifier for the facility where the slot is offered    |

These fields are populated automatically during entity sync when the source system provides practitioner and facility references on availability data. Existing slots without this data will have null values for both fields.

**Primary insurance projection.** Insurance data for person entities is now also sourced from Coverage resources when available, with a fallback to inline insurance data on the patient resource. The fields returned are unchanged - `patient_primary_payer_name`, `patient_practice_payer_id`, `patient_member_id`, `patient_policyholder_name`, `patient_policyholder_relationship`, and `patient_policyholder_dob` - but coverage from dedicated insurance records takes precedence when both sources are present. Primary coverage (lowest order value) is preferred over secondary.

No client changes are required. Existing queries and integrations continue to work. The new slot fields are additive and nullable.

</details>

<details>

<summary>v0.9.169 - Platform API: On-Demand Forecast Computation for Population Health (May 2026)</summary>

#### On-Demand Forecast Computation

The population health forecast endpoints now compute forecast fan points on demand instead of reading from pre-materialized tables. Each request fits a statistical model to the current patient topology snapshot, generates 28 historical and 12 forecast data points with bootstrapped 95% confidence bands, and returns the same response shape as before.

**What changed:**

* **Territory and district forecast fan points** are now computed at request time from the live patient topology. The response schema is unchanged - callers receive the same fields (`run_id`, `scenario`, `t`, `ym`, `median`, `lower_95`, `upper_95`, `observed`, `is_historical`) in the same structure.
* **Cluster forecast fan points** are also computed on demand. Every (cluster, focus area) combination is generated dynamically, so new clusters or focus areas added to the topology are immediately available without a pipeline rerun.
* **Deterministic results.** Forecasts for the same scope and target are deterministic across requests and across platform replicas. Two requests for the same view return identical data.
* **Tenant isolation.** Topology data used for forecast computation is validated against the requesting workspace, with defense-in-depth checks that reject any rows not belonging to the authorized tenant.

**Supported targets:** T2D, Overall, BMI, HTN, CHF, Asthma, CKD, COPD.

**Migration notes:** No client changes are required. The forecast fan and cluster forecast endpoints return the same response shapes. Pre-materialized forecast tables are no longer read.

</details>

<details>

<summary>v0.9.168 - Platform API: Email Channel and SES Setup Management (May 2026)</summary>

#### Email Channel and SES Setup Management

The Channel Manager now supports email as a first-class channel alongside voice and ringless voicemail. Organizations can configure verified sending domains, create email use cases with reputation-isolated sending pools, and manage the full lifecycle through new API endpoints.

**New: SES Setup endpoints**

SES Setups represent a verified sending and receiving domain. Each setup provisions an isolated reputation boundary and domain identity for email delivery.

* **`POST /v1/ses-setup`** - Create a new SES setup. Provisions the reputation isolation boundary and domain identity, then returns the DNS records the customer must publish (DKIM CNAMEs, MAIL FROM MX and SPF, DMARC TXT, and inbound MX). Returns 201 on success, 409 if the tenant name or domain already exists.
  * Request fields:
    * `tenant_name` (required, string, 1-64 chars, alphanumeric with hyphens and underscores) - Logical name for reputation and suppression isolation
    * `domain_identity` (required, string, 1-255 chars) - Domain to verify for sending and receiving (e.g. `mail.customer.com`)
  * Response includes `id`, `tenant_name`, `domain_identity`, `dns_checked_at` (null until first refresh), `dns_records` (array of records with `address`, `record`, `type`, and `verified` fields), and timestamps.
* **`GET /v1/ses-setup`** - List all SES setups. Returns cached verification status per setup without triggering a live DNS check. Response includes `items` array with `id`, `tenant_name`, `domain_identity`, `dns_verified`, `dns_checked_at`, and timestamps.
* **`GET /v1/ses-setup/{setup_id}`** - Get a single SES setup with a live DNS verification refresh. Checks DKIM, MAIL FROM, DMARC, and inbound MX status in real time and updates the cached verification state. Response includes the full DNS record list with per-record `verified` flags.
* **`DELETE /v1/ses-setup/{setup_id}`** - Delete an SES setup and its associated domain identity and reputation boundary. Returns 204 on success. Refuses with 409 if any email use cases still reference the setup - those must be deleted first.

**New: Email channel for use cases**

The `POST /v1/use-case` endpoint now accepts `"email"` as a channel value. The request body is a discriminated union on `channel` - each channel variant carries only its own fields.

* Email use case request fields:
  * `channel` (required, literal `"email"`)
  * `entity_name` (required, string, 1-31 chars, letters/spaces/hyphens)
  * `name` (required, string, 1-31 chars, letters/spaces/hyphens)
  * `description` (optional, string, max 2000 chars)
  * `ses_setup_id` (required, UUID) - SES setup to bind to. Must have DNS fully verified.
  * `sender_email_address` (required, email) - From address for outbound sends. Domain part must match the setup's domain identity.
  * `email_type` (required, `"transactional"` or `"marketing"`) - Outbound classification. Selects the reputation-isolated sending pool family. Immutable after creation.
* Email use case response adds: `ses_setup_id`, `configuration_set_name`, `sender_email_address`, `email_type`, and `tier`.
* `tier` represents the current reputation lane (`onboarding`, `standard`, `premium`, or `suspended`). New use cases always start at `onboarding` and are promoted based on send, bounce, and complaint performance.
* Validation enforces that the sender email domain matches the setup's domain identity (422 with `sender_domain_mismatch` error if not), and that DNS is fully verified on the setup before use case creation (409 if not).

**Updated: Use case list and delete**

* **`GET /v1/use-case`** now accepts an optional `ses_setup_id` query parameter to filter use cases bound to a specific SES setup. The response is a discriminated union - email use cases include SES-specific fields while voice use cases include Twilio-specific fields.
* **`DELETE /v1/use-case/{use_case_id}`** now handles email use cases by tearing down the associated sending configuration and reputation pool binding before removing the database record.

**Updated: Use case request field constraints**

* `entity_name` and `name` fields on use case creation are now constrained to 1-31 characters and must contain only letters, spaces, and hyphens (previously up to 256 characters with no pattern restriction). This ensures the combined identifier fits within downstream naming limits.
* Use case responses are now a discriminated union on `channel`. Twilio-served use cases return `twilio_setup_id`; email use cases return `ses_setup_id`, `configuration_set_name`, `sender_email_address`, `email_type`, and `tier`. The previous flat response shape with an optional `twilio_setup_id` is replaced.

</details>

<details>

<summary>v0.9.167 - Platform API: Outbound Call Creation API (May 2026)</summary>

#### Outbound Call Creation API

The Platform API now exposes a public endpoint for initiating outbound voice calls from a workspace phone number. Previously, outbound calls could only be triggered through internal systems. With this release, API consumers can programmatically place outbound calls to any E.164 phone number using a caller ID registered in their workspace.

**What changed:**

* **New `POST /calls/outbound` endpoint.** Creates an outbound voice call. The request specifies the destination number, the caller ID (which must belong to the workspace), and optional fields for service selection, system prompt override, and idempotency. The response includes the call identifier and initial call status.
* **Request fields:**
  * `phone_to` (required, string) - Destination phone number in E.164 format (e.g. `+18005551234`)
  * `phone_from` (required, string) - Caller ID phone number in E.164 format. Must be registered in the workspace.
  * `service_id` (optional, string) - Service ID for the voice agent to use
  * `system_prompt` (optional, string) - System prompt override for this call
  * `idempotency_key` (optional, string) - Client-provided idempotency key. Auto-generated if omitted.
  * `outbound_task_entity_id` (optional, string) - Outbound task entity ID for completion feedback
* **Response fields:**
  * `call_sid` (string) - Call identifier for the outbound call
  * `status` (string) - Initial call status (typically "queued")
* **Validation.** Both `phone_to` and `phone_from` are validated as E.164 format. The `phone_from` number must belong to the requesting workspace or the request is rejected with a 403.
* **Idempotency.** Clients can supply an `idempotency_key` to safely retry requests without creating duplicate calls. If omitted, the platform generates one automatically.
* **Rate limiting.** The endpoint is subject to write rate limits.
* **Error responses:** 400 (invalid phone format), 403 (caller ID not in workspace), 429 (rate limit), 502 (upstream error), 503 (outbound calls not configured).

</details>

<details>

<summary>v0.9.166 - Platform API: Slot Visit Types and Provider Specialty (May 2026)</summary>

#### Slot Visit Types and Provider Specialty

Slot entities in the world model now include the provider's specialty and the visit types accepted for that slot. This gives the voice agent and booking UI richer context when presenting available appointments - for example, "Dr. Chen (Cardiologist) - Tele Visit or In-Person Review at 10:00 AM" - without requiring additional lookups against practitioner or configuration entities.

**What changed:**

* **Slot specialty.** Each slot now carries the provider's specialty (e.g., "Physician", "Cardiologist") when the connected EHR supplies it. The value is attached during availability sync and projected as a typed field on the slot entity.
* **Slot visit type IDs and names.** Slots now include the list of visit types the provider accepts at the associated facility. Visit types are represented as two parallel pipe-delimited fields - one for IDs and one for human-readable names - so consumers can match on either. For example, IDs might be `100001000000008083|100001000000008085` with corresponding names `Tele Visit|Review`.
* **Enrichment from booking preferences.** Visit type data is sourced from the provider's booking preferences, which the connector caches during its regular sync cycle. When cached data is not yet available, slots continue to emit without visit type fields (all new fields are nullable), preserving backward compatibility with existing consumers.
* **No breaking changes.** All new fields are optional and nullable. Existing integrations continue to work without modification.

</details>

<details>

<summary>v0.9.165 - Platform API: Full Observer WebSocket Event Types for SDK Pipeline (May 2026)</summary>

#### Full Observer WebSocket Event Types for SDK Pipeline

The observer WebSocket event stream now includes typed payloads for all 20 real-time event types, up from the previous 6. SDKs and clients consuming the observer stream can now handle every event with compile-time type safety and discriminated unions.

**New event types:**

* **Session lifecycle.** `session_info`, `session_start`, and `session_end` events provide session metadata at connect time, mark when a session begins (with initial state and trace context), and report final duration, turn count, and completion reason when a session ends.
* **State transitions.** `state_transition` events fire when the agent moves between context graph states, including the previous and next state names, transition type, and optional annotation.
* **Streaming transcript deltas.** `agent_transcript_delta` events deliver incremental agent speech tokens as they are generated, enabling real-time streaming displays.
* **Latency and timing.** `latency` events report end-to-end time-to-first-byte, engine processing time, navigation time, render time, and audio time-to-first-byte. `nav_timing` events break down navigation and rendering durations with optional token counts, model identifier, and retry metadata.
* **Emotion and empathy.** `emotion` events carry dominant emotion, valence, arousal, trend, coherence, per-segment scores, acoustic features, and language sentiment. `compound_emotion` events report blended emotion scores per turn. `empathy_classified` events indicate the selected empathy tier, whether the agent should pause, and filler suppression state.
* **Barge-in.** `barge_in` events report caller interruptions, including the text that was interrupted, any discarded pending utterances, and cumulative barge-in count.
* **Participants.** `participant_joined` and `participant_left` events track when callers, agents, or operators enter or leave a conference, with role and display information.
* **Voice context.** `voice_context_applied` events indicate the current TTS emotion, speed, volume, filler state, emotion detection state, and reasoning behind the voice parameter selection.

**No breaking changes.** All existing event types (`user_transcript`, `agent_transcript`, `tool_call_started`, `tool_call_completed`, `forward_call_resolved`, `speaker_muted`) remain unchanged. The new types are additive. Clients that do not handle a particular event type can safely ignore it.

</details>

<details>

<summary>v0.9.163 - Platform API: Cluster Forecast Observed Values and Calendar Axis (May 2026)</summary>

#### Cluster Forecast Observed Values and Calendar Axis

The cluster forecast endpoint now returns observed values and calendar month labels alongside forecast data, enabling richer visualizations with actual-vs-predicted comparisons and human-readable time axes.

**What changed:**

* **Observed values.** Each forecast data point can now include an `observed` field containing the actual measured value for that period. This allows clients to render scatter plots or overlay lines showing real data against the forecast fan.
* **Calendar month labels.** Each forecast data point can now include a `ym` field containing a year-month string (e.g., `"2026-05"`) for use as a human-readable x-axis label instead of a numeric time index.
* **Backward compatible.** Both new fields are optional and nullable. Workspaces running older data pipelines that do not produce these fields continue to receive responses in the existing format with no disruption. The endpoint probes for field availability and adapts the response automatically.

</details>

<details>

<summary>v0.9.162 - Platform API: Desktop Integration Protocol (May 2026)</summary>

#### Desktop Integration Protocol

The integration protocol field now supports `desktop` as a valid value, alongside `rest`, `fhir`, and `mcp`. This allows integrations that connect to desktop applications to be represented with a dedicated protocol type rather than being mapped to a generic alternative.

**What changed:**

* **New protocol value.** The `protocol` field on integration responses now includes `desktop` in its set of allowed values. Desktop integrations can be created and returned with `protocol: "desktop"` instead of requiring a workaround protocol assignment.
* **No breaking changes.** Existing integrations using `rest`, `fhir`, or `mcp` protocols are unaffected. The new value is additive.

</details>

<details>

<summary>v0.9.161 - Platform API: Reliable Surface Creation (May 2026)</summary>

#### Reliable Surface Creation

Surface creation now writes to the primary data store before emitting downstream events, eliminating a race condition where newly created surfaces could briefly return 404 errors.

**What changed:**

* **Write-order fix.** Previously, creating a surface emitted an event to the async pipeline first and wrote to the primary store second. If a client or the agent attempted to read the surface before the primary write completed, the request would fail with a 404. Surface creation now writes to the primary store first, so the surface is immediately queryable after the create call returns.
* **No API changes.** The surface creation endpoint accepts and returns the same request and response shapes. Existing integrations require no changes.

</details>

<details>

<summary>v0.9.160 - Platform API: Dedicated Connector Configuration Storage (May 2026)</summary>

#### Dedicated Connector Configuration Storage

Connector definitions are now stored as individual records per connector instead of being embedded in workspace settings. This improves query performance, enables per-connector update timestamps, and supports future features like connector-level audit trails and granular access control.

**What changed:**

* **Per-connector records.** Each connector definition is now stored as its own record with individual `created_at` and `updated_at` timestamps. Previously, all connectors for a workspace were stored together in the workspace settings payload, meaning any single connector change required rewriting the entire collection.
* **Simplified connector-runner refresh.** The connector sync engine now reads from the derived data source index directly, removing a fallback code path that handled workspaces that had not yet been migrated to the structured connector format. All workspaces now use the same read path.
* **No API changes.** The connector settings endpoints (`GET` and `PUT`) continue to accept and return the same request and response shapes. Existing integrations, including Agent Forge workflows, require no changes.
* **Automatic migration.** Existing connector configurations are migrated automatically. No manual steps are required.

</details>

<details>

<summary>v0.9.159 - Platform API: Incremental Sync for Charm EHR Connector (May 2026)</summary>

#### Incremental Sync for Charm EHR Connector

The Charm FHIR connector now supports incremental sync. Instead of fetching all records on every sync cycle, the connector tracks the last successful sync time per resource type and only requests records updated since that point.

**What changed:**

* **Cursor-based incremental fetch.** After a successful sync, the connector stores a per-resource-type cursor representing the sync timestamp. On subsequent polls, only records updated after that timestamp are fetched from the Charm FHIR API. This significantly reduces data transfer volume and sync duration for workspaces with large patient populations.
* **Automatic full re-fetch safety net.** Cursors expire after 7 days. If a connector is paused or disabled for longer than that window, the next sync automatically performs a full fetch to ensure no records are missed. Corrupt or invalid cursors are also detected and trigger a full fetch with a diagnostic warning.
* **No configuration required.** Incremental sync is enabled automatically for all Charm FHIR connections. Existing connectors will perform one full fetch on their next sync cycle, then switch to incremental mode going forward.

This change reduces sync times and API usage for active Charm integrations without any changes to connector configuration or API usage.

</details>

<details>

<summary>v0.9.158 - Platform API: World Event Source Consistency and Connector Emission Cleanup (May 2026)</summary>

#### World Event Source Consistency and Connector Emission Cleanup

This release improves the consistency and accuracy of event attribution in the world model, and removes unnecessary data emission from the connector sync pipeline.

**What changed:**

* **Consistent event source attribution.** Events written to the world model by the voice agent now use a standardized source identifier instead of a freeform string. This ensures that all voice-agent-originated events are correctly categorized and filterable in downstream analytics, dashboards, and audit logs. Previously, some events could be tagged with a generic label, making it harder to distinguish their origin.
* **Connector runner no longer emits raw records without unification rules.** When a data source has no unification rules configured, the connector sync pipeline now skips event emission entirely instead of writing unprocessed records into the world model. This prevents accumulation of unstructured data that could not be meaningfully resolved or used by the platform. Data sources without unification rules still sync normally - records are fetched and logged - but no world events are created until rules are configured.

These changes improve data quality in the world model and reduce noise in event streams for workspaces with partially configured connectors. No API changes are required.

</details>

<details>

<summary>v0.9.157 - Platform API: Voice Engine Actor Semantics and Per-Utterance TTS (May 2026)</summary>

#### Voice Engine Actor Semantics and Per-Utterance TTS

The voice agent engine now enforces stricter actor semantics during conversations, eliminating a class of timing issues where filler audio could overlap or conflict with primary agent responses.

**What changed:**

* **Per-utterance voice parameters.** TTS emotion and speed settings are now resolved per utterance rather than applied globally to the speaker. This prevents a race condition where a late-arriving emotion update from one response could bleed into a subsequent response. Each utterance carries its own voice parameters, and the speaker restores its baseline after playback completes.
* **Retry filler coordination.** When the reasoning engine retries after producing unparseable output, filler audio is now coordinated through the same signal-based pipeline as all other conversation events. This replaces a separate filler path that could produce overlapping audio. The retry filler respects the same cooldown and muting rules as other filler types.
* **New retry signal type.** The conversation orchestrator recognizes a new signal for reasoning retries, allowing it to manage filler timing during retries using the same priority and deadline logic applied to other transitions. This signal is treated as a phase boundary, meaning it cleanly ends the current processing quantum before starting the retry filler.
* **Queue drain without interruption.** The speaker now supports draining queued utterances without cutting off the currently playing audio. This is used during call suspension to let in-progress speech finish naturally while clearing pending items from the queue.

These changes improve audio continuity during complex multi-step conversations, especially when the agent is processing tool results, handling retries, or transitioning between conversation phases. No API changes are required.

</details>

<details>

<summary>v0.9.155 - Platform API: Real-Time Surface Events in Text Sessions (May 2026)</summary>

#### Real-Time Surface Events in Text Sessions

Text sessions now receive surface lifecycle events - such as form submissions and review approvals - in real time via the workspace event stream. Previously, the agent could only detect surface completions by polling. With this release, the session automatically subscribes to relevant workspace events when a surface is created, and delivers matching events to the agent as soon as they occur.

This reduces the delay between a patient completing a surface and the agent reacting to it, improving responsiveness in multi-step workflows that depend on collected data.

The subscription is resilient to transient disconnections and reconnects automatically with backoff. Surface status polling remains available as a fallback. No API changes are required - existing text session and surface endpoints work as before.

</details>

<details>

<summary>v0.9.154 - Platform API: Inline Operator State for Immediate Visibility (May 2026)</summary>

#### Inline Operator State for Immediate Visibility

Newly created operators now appear in operator list queries immediately after creation. Previously, a new operator could be invisible in list results until background processing caught up, because the data used by list queries was populated asynchronously.

With this release, the platform writes operator state - including roles, profile, and availability - at creation time, so list and filter operations return the new operator right away. The same applies to operator updates: changes to availability status or profile fields are reflected in list queries immediately rather than after a processing delay.

No API changes are required. Existing create and update endpoints behave the same; the improvement is in how quickly results are reflected in read operations.

</details>

<details>

<summary>v0.9.153 - Platform API: PCA Coordinates on Patient Topology (May 2026)</summary>

#### PCA Coordinates on Patient Topology

The patient topology endpoint now returns three-dimensional PCA coordinates (`pca_x`, `pca_y`, `pca_z`) alongside the existing UMAP coordinates for each patient. PCA coordinates provide an alternative dimensionality-reduction projection that can be used for 3D visualization or analytical workflows where linear projections are preferred.

All three fields are optional and may be `null` if PCA data is not available for a given patient. Existing integrations are unaffected - the UMAP coordinates and all other fields remain unchanged.

**New response fields on each patient topology row:**

| Field   | Type               | Description      |
| ------- | ------------------ | ---------------- |
| `pca_x` | `number` or `null` | PCA x-coordinate |
| `pca_y` | `number` or `null` | PCA y-coordinate |
| `pca_z` | `number` or `null` | PCA z-coordinate |

</details>

<details>

<summary>v0.9.152 - Platform API: Text Session Transport and Event-Driven Trigger Scheduler (May 2026)</summary>

#### Buffered Text Session Transport

Text sessions now use a buffered message transport instead of a subscription-based model. Messages sent before the session actor finishes initializing are preserved and processed once the actor starts consuming, eliminating the previous timing dependency where the first inbound message could be lost if it arrived during session setup.

This also improves resilience to transient infrastructure errors during message consumption. The actor retries on transient failures with backoff and terminates the session gracefully after repeated consecutive errors, rather than silently dropping messages.

Additionally, the `complete` action now sends a final message (if present) and deactivates the session in a single step, ensuring the patient always receives the agent's closing message before the session ends.

#### Event-Driven Trigger Scheduler

The trigger scheduler now reacts to trigger mutations immediately instead of waiting for the next polling cycle. When a trigger is created, updated, paused, resumed, or deleted through the API, the scheduler wakes and re-evaluates due triggers right away. A periodic heartbeat remains as a safety net if the notification is missed.

No API changes are required. Existing trigger CRUD endpoints work as before - the scheduler simply picks up changes faster.

#### Workspace Event Signal Routing

The agent engine now recognizes workspace events (such as surface submissions and review approvals) as structured signals natively, without requiring a secondary reclassification step. This simplifies event routing and ensures workspace events are handled consistently with other signal types.

No changes to the workspace event format or API. Existing event payloads are handled transparently.

</details>

<details>

<summary>v0.9.151 - Platform API: Typed SSE Event Schemas (May 2026)</summary>

#### Typed Event Schemas for Real-Time Streaming

The Platform API SSE streaming endpoint now publishes fully typed event schemas in the OpenAPI specification. SDK consumers (including the TypeScript SDK generated via `openapi-typescript`) automatically receive discriminated union types for every workspace and observer event, enabling compile-time exhaustiveness checks when handling real-time events.

**Workspace events** are delivered over the SSE stream and cover the following domains:

| Domain   | Events                                                                                                                                                                                                                                          |
| -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Call     | `call.started`, `call.ended`, `call.escalated`                                                                                                                                                                                                  |
| Surface  | `surface.created`, `surface.delivered`, `surface.updated`, `surface.archived`, `surface.reshaped`, `surface.submitted`, `surface.field_saved`, `surface.opened`, `surface.pending_review`, `surface.review_approved`, `surface.review_rejected` |
| Operator | `operator.registered`, `operator.status_changed`, `operator.profile_updated`, `operator.joined_call`, `operator.left_call`, `operator.mode_changed`, `operator.wrap_up`                                                                         |
| Pipeline | `pipeline.sync_completed`, `pipeline.error`                                                                                                                                                                                                     |

**Observer events** are delivered over the call observation WebSocket and include:

| Event                   | Description                 |
| ----------------------- | --------------------------- |
| `user_transcript`       | Caller speech transcription |
| `agent_transcript`      | Agent speech output         |
| `tool_call_started`     | Tool execution began        |
| `tool_call_completed`   | Tool execution finished     |
| `forward_call_resolved` | Call forwarding resolved    |
| `speaker_muted`         | Speaker mute state changed  |

Each event type has a fixed schema with domain-specific fields. The discriminator field (`event_type` for workspace events, `type` for observer events) lets clients switch on the event kind and access typed payloads without runtime parsing.

The `publish_workspace_event` interface now accepts typed event models in addition to the existing dictionary format. Both produce identical wire format. Existing integrations using the dictionary format continue to work without changes.

No existing events were removed or renamed. Clients that do not use the new typed schemas are unaffected.

</details>

<details>

<summary>v0.9.150 - Platform API: Population Health Forecast Fan Enhancements (May 2026)</summary>

#### Observed Values and Period Labels on Forecast Fan Points

The population health forecast fan endpoint now returns two additional fields on each data point:

| Field      | Type           | Description                                                         |
| ---------- | -------------- | ------------------------------------------------------------------- |
| `observed` | number or null | Actual observed value for the period (populated on historical rows) |
| `ym`       | string or null | Year-month label for the data point (populated on historical rows)  |

These fields let dashboard consumers distinguish historical observations from forecasted values and display period labels without deriving them from timestamps. When present, frontends can render historical data points as distinct markers on the forecast fan chart.

No existing fields were removed or renamed. Clients that do not use the new fields are unaffected.

</details>

<details>

<summary>v0.9.148 - Platform API: Population Health Stratified Fits and Notes Rollup Endpoints (May 2026)</summary>

#### Stratified Fits Endpoint

A new endpoint returns per-cohort, per-region model fits that drive the dashboard's outcome-by-segment comparison view. Each row includes outcome identifiers, cohort and region labels, sample size, base rate, AUROC, intercept, model coefficients, and whether the fit is a pooled fallback. Results can be filtered by outcome key.

| Method | Path                                                   | Description                                     |
| ------ | ------------------------------------------------------ | ----------------------------------------------- |
| GET    | `/v1/{workspace_id}/population-health/stratified-fits` | List stratified model fits by cohort and region |

**Query parameters:**

| Parameter     | Type              | Description                                  |
| ------------- | ----------------- | -------------------------------------------- |
| `outcome_key` | string (optional) | Filter to a single outcome (1-64 characters) |

**Response fields (per item):**

| Field                | Type            | Description                             |
| -------------------- | --------------- | --------------------------------------- |
| `workspace_id`       | string          | Workspace identifier                    |
| `outcome_key`        | string          | Outcome identifier                      |
| `outcome_name`       | string or null  | Human-readable outcome name             |
| `cohort`             | string          | Cohort label                            |
| `region`             | string          | Region label                            |
| `n`                  | integer or null | Sample size                             |
| `base_rate`          | number or null  | Base rate for the cohort-region segment |
| `auroc`              | number or null  | Area under the ROC curve                |
| `intercept`          | number or null  | Model intercept                         |
| `coefficients_json`  | string or null  | Serialized model coefficients           |
| `is_pooled_fallback` | boolean or null | Whether this fit is a pooled fallback   |

#### Notes Rollup Endpoint

A new endpoint provides a cohort-wide (or per-cluster / per-district) rollup of NLP note extractions joined onto patient risk predictions. The rollup surfaces which narrative signals - symptoms, concerns, medications, sentiment, and social determinants of health - correlate with higher predicted risk.

| Method | Path                                                | Description                           |
| ------ | --------------------------------------------------- | ------------------------------------- |
| GET    | `/v1/{workspace_id}/population-health/notes/rollup` | Get cohort-wide NLP extraction rollup |

**Query parameters:**

| Parameter  | Type              | Description                                     |
| ---------- | ----------------- | ----------------------------------------------- |
| `cluster`  | string (optional) | Filter to a specific cluster (1-64 characters)  |
| `district` | string (optional) | Filter to a specific district (1-64 characters) |

**Response fields:**

| Field                    | Type    | Description                                                                             |
| ------------------------ | ------- | --------------------------------------------------------------------------------------- |
| `workspace_id`           | string  | Workspace identifier                                                                    |
| `total_notes`            | integer | Total notes included in the rollup                                                      |
| `cohort_mean_risk`       | number  | Mean predicted risk across the cohort                                                   |
| `sentiment_distribution` | array   | Distribution of sentiment categories with counts, percentages, mean risk, and risk lift |
| `top_symptoms`           | array   | Most frequent symptoms with counts, percentages, mean risk, and risk lift               |
| `top_concerns`           | array   | Most frequent concerns with counts, percentages, mean risk, and risk lift               |
| `top_medications`        | array   | Most frequent medications with counts, percentages, mean risk, and risk lift            |
| `sdoh`                   | object  | Social determinants of health distributions (activity, smoking, diet)                   |

Each category entry includes:

| Field       | Type    | Description                                                |
| ----------- | ------- | ---------------------------------------------------------- |
| `value`     | string  | Category label                                             |
| `n`         | integer | Number of patients in this category                        |
| `pct`       | number  | Percentage of total                                        |
| `mean_risk` | number  | Mean predicted risk for this category                      |
| `risk_lift` | number  | Difference between category mean risk and cohort mean risk |

</details>

<details>

<summary>v0.9.147 - Platform API: Signal-Driven Text Conversations (May 2026)</summary>

#### Signal-Driven Text Session Architecture

Text conversations (SMS) now use a signal-driven architecture that matches the model used for voice calls. All external events - inbound messages, delivery status updates, form submissions, review approvals, and timeouts - are processed sequentially through a single event loop per session. This eliminates race conditions that could occur when multiple events arrived concurrently under the previous model.

**Delivery status tracking** - Outbound SMS messages now include a delivery status callback. The platform receives delivery confirmations (sent, delivered) and failure notifications (failed, undelivered) from the telephony provider and routes them into the active session. Failed deliveries are logged and surfaced in session metrics.

**New SMS status webhook** - A new endpoint receives delivery status callbacks from the telephony provider and publishes them as structured events to the active text session. Status updates include delivery confirmation, failure notifications with error codes, and intermediate states. The endpoint validates provider signatures before processing.

**Form and review event handling** - Text sessions now natively handle form submission and review approval events. When a patient completes a form or a review is approved, the event is routed directly into the text session's event loop, clearing any pending wait conditions and continuing the conversation without polling.

**Entity resolution for outbound sessions** - Outbound text sessions can now resolve patient context by entity ID in addition to phone number lookup. This means outbound conversations initiated with a known patient ID load the full patient context immediately, rather than relying solely on phone number matching.

**Session lifecycle events** - Text sessions now emit structured workspace events at session start and session end, including session duration, turn count, completion reason, and final state. These events are available through the existing workspace event infrastructure.

**Call intelligence for text** - Text session analytics records now include a channel indicator distinguishing text sessions from voice sessions, enabling filtered reporting across conversation types.

</details>

<details>

<summary>v0.9.146 - Platform API: Signal-Driven Operator Lifecycle (May 2026)</summary>

#### Operator Takeover and Transfer Lifecycle

The operator escalation system now uses a signal-driven lifecycle model that processes all operator state changes sequentially, eliminating race conditions from concurrent events such as duplicate disconnect callbacks or simultaneous transfer and hangup signals.

**Operator modes** - Operators can join a call in **takeover** mode (agent is suspended, operator speaks directly with the caller) or **listen** mode (agent continues running, operator monitors silently). Mode can be switched at any time during the call.

**Warm transfer** - Operators can initiate warm transfers to bring a third party onto the call. The transfer follows a structured hold-dial-briefing-handoff sequence with automatic timeout handling at each stage. If the transfer target does not answer or disconnects during briefing, the caller is automatically taken off hold and the call resumes.

**Handback context** - When an operator in takeover mode leaves, the conversation that occurred during the human segment is summarized and injected into the agent's context so the agent resumes with full awareness of what was discussed.

**Crash recovery** - Operator state is persisted on every transition. If the hosting infrastructure restarts mid-escalation, the operator session recovers from the last known state and re-applies timeouts, ensuring in-progress calls are not disrupted.

**Automatic timeout safety** - Every non-terminal operator state has a deadline. If the expected next event does not arrive within the window, the system transitions to a safe state automatically - resuming the agent, releasing holds, or cleaning up partial transfers.

**Idempotent signal handling** - Duplicate signals (such as disconnect notifications arriving from both a webhook and a WebSocket) are handled idempotently. The system checks the current state before transitioning, so duplicate events are safely ignored.

</details>

<details>

<summary>v0.9.144 - Platform API: Admin Workspace Access Endpoint (May 2026)</summary>

#### List Workspaces With Admin or Owner Access

A new endpoint returns only the workspaces where the authenticated user holds an admin or owner role, filtering out workspaces where they have lower-level access.

| Method | Path                          | Description                                |
| ------ | ----------------------------- | ------------------------------------------ |
| GET    | `/v1/workspaces/admin-access` | List workspaces with admin or owner access |

The response uses the same shape as the existing `/v1/workspaces/access` endpoint - a list of workspace access records including role, scopes, and workspace summary. Results are sorted alphabetically by workspace name.

This is useful for building admin-only views such as workspace management dashboards, where you need to show only the workspaces the current user can administer.

</details>

<details>

<summary>v0.9.143 - Platform API: Workspace Environment Conversion (May 2026)</summary>

#### Convert Workspaces Between Staging and Production

Two new workspace endpoints let you convert a workspace between staging and production environments after creation.

| Method | Path                                               | Description                      |
| ------ | -------------------------------------------------- | -------------------------------- |
| GET    | `/v1/workspace/{workspace_id}/environment-check`   | Pre-check environment conversion |
| POST   | `/v1/workspace/{workspace_id}/convert-environment` | Convert workspace environment    |

The **pre-check** endpoint returns compliance warnings for the target environment without making any changes. Converting from staging to production warns about stricter idle session timeouts, HITRUST compliance controls, enhanced audit retention, and production credential handling. Converting from production to staging warns about relaxed compliance controls and reduced audit retention.

The **convert** endpoint performs the actual conversion. It requires a `target` field (`production` or `staging`) and a `confirm_slug` field that must match the workspace slug, preventing accidental conversions. The workspace response includes metadata recording when the conversion occurred and the previous environment.

Both endpoints require `Workspace.update` permission (admin or owner).

</details>

<details>

<summary>v0.9.142 - Platform API: Default Staging Environment, Viewer Workspace Creation, and CSV Intake Support (May 2026)</summary>

#### Workspace Creation Defaults to Staging

The `environment` field on workspace creation is now optional and defaults to `staging`. Previously, the field was required and callers had to specify `production`, `staging`, or `development` explicitly. Existing integrations that already pass an environment value are unaffected.

#### Viewers Can Create Workspaces

The Viewer role now includes permission to create workspaces. Previously, only Editors and Admins could create workspaces. This lets read-only team members set up their own staging or development workspaces without requiring elevated access.

#### CSV Files Supported in Data Intake

Intake links now accept CSV file uploads (`text/csv`) in addition to the previously supported formats (PDF, JPEG, PNG, Word, and PowerPoint). The intake upload form also now filters the file picker to only show allowed file types, and displays human-readable labels for all supported formats including those not in the built-in label list.

</details>

<details>

<summary>v0.9.141 - Platform API: Use Cases, Outbound Voice, and Ringless Voicemail Restructured (May 2026)</summary>

#### Use Cases Are Now Top-Level Resources

Use cases have moved from being scoped under a Twilio setup to top-level resources with their own endpoints. Each use case now carries a channel type (`outbound_voice`, `inbound_voice`, or `ringless_voicemail`), a business entity name, and a descriptive name. For telephony channels, you supply the Twilio setup ID at creation time to bind the use case to that provider.

**New endpoints:**

| Method | Path                         | Description                                                     |
| ------ | ---------------------------- | --------------------------------------------------------------- |
| POST   | `/v1/use-case`               | Create a use case                                               |
| GET    | `/v1/use-case`               | List use cases (filterable by entity, channel, or Twilio setup) |
| DELETE | `/v1/use-case/{use_case_id}` | Delete a use case                                               |

**Removed endpoints:**

| Method | Path                                                 |
| ------ | ---------------------------------------------------- |
| POST   | `/v1/twilio-setup/{setup_id}/use-case`               |
| GET    | `/v1/twilio-setup/{setup_id}/use-case`               |
| DELETE | `/v1/twilio-setup/{setup_id}/use-case/{use_case_id}` |

The use case response shape has changed: `type` is replaced by `channel`, the `setup_id` field is now `twilio_setup_id` (nullable), and new fields `entity_name`, `description`, and `channel` are included.

#### Outbound Voice Phone Number Selection Moved

Phone number selection for outbound calling has moved to a dedicated endpoint. The use case ID is now passed in the request body instead of the URL path, and only `outbound_voice` use cases are accepted.

| Before                                                                        | After                                         |
| ----------------------------------------------------------------------------- | --------------------------------------------- |
| POST `/v1/twilio-setup/{setup_id}/use-case/{use_case_id}/phone-number/select` | POST `/v1/outbound-voice/phone-number/select` |

#### Ringless Voicemail Endpoints Restructured

Ringless voicemail send and list endpoints have moved to top-level paths. The send endpoint now accepts `use_case_id` as a form field instead of a URL path parameter. A new list endpoint supports filtering by use case, setup, status, and recipient phone number.

| Before                                                                       | After                         |
| ---------------------------------------------------------------------------- | ----------------------------- |
| POST `/v1/twilio-setup/{setup_id}/use-case/{use_case_id}/ringless-voicemail` | POST `/v1/ringless-voicemail` |
| (none)                                                                       | GET `/v1/ringless-voicemail`  |

#### Phone Number Assignment Changes

The assign endpoint (`PUT .../use-case/{use_case_id}`) now accepts the top-level use case ID and returns `204 No Content` instead of the phone number object. The uniqueness constraint is now per-channel rather than per-use-case-type. The `inbound_voice` channel is now supported for assignment.

#### Webhook Paths Updated

Vendor webhook endpoints have moved to a consolidated `/v1/webhook/` prefix:

| Before                                      | After                                                          |
| ------------------------------------------- | -------------------------------------------------------------- |
| POST `/twilio-setup/webhook`                | POST `/v1/webhook/twilio/trust-hub`                            |
| POST `/v1/voicedrop/webhook/{voicemail_id}` | POST `/v1/webhook/voicedrop/ringless-voicemail/{voicemail_id}` |

#### Migration Notes

* Update use case CRUD calls to use the new `/v1/use-case` paths
* Update outbound voice phone number selection to use `/v1/outbound-voice/phone-number/select` with a request body
* Update ringless voicemail sends to use `/v1/ringless-voicemail` with `use_case_id` as a form field
* The assign phone number endpoint now returns `204` instead of `200` with a body
* Webhook receiver URLs must be updated to the new `/v1/webhook/` prefix paths

</details>

<details>

<summary>v0.9.139 - Appointment Reason in World Model (May 2026)</summary>

#### Appointment Reason Now Available as a First-Class Field

The world model now stores the patient-facing reason for an appointment as a dedicated field on appointment entities. Previously, the visit reason was only accessible by inspecting the raw FHIR resource. It is now surfaced directly, so the voice agent can reference why a patient is booked (for example, "you're scheduled for a follow-up") without additional data lookups.

This field is distinct from the cancellation reason, which only populates when an appointment is cancelled.

The new field is available through platform functions, world tools, and data access queries. No API request or response schema changes are required. Existing integrations continue to work without modification.

</details>

<details>

<summary>v0.9.135 - Global Provision Policy and Team Access Seeding (May 2026)</summary>

#### Global Viewer Baseline

The identity provisioning system now supports a global provision policy that automatically grants viewer-level access to all active workspaces for users in a specified domain. When a user signs in via SSO, the system evaluates global policies and provisions baseline workspace memberships before the user reaches any workspace. This means new team members see every workspace immediately on first login without manual invitation.

#### Team Access Seeding

A new operational script allows platform administrators to pre-seed workspace memberships from a configuration file. Team members can be assigned owner access across all workspaces or admin access on specific workspaces. The seeding process only upgrades roles - it never downgrades an existing membership. Every membership change is recorded in the audit log for compliance traceability.

Workspace names in the configuration must exactly match the workspace display name (case-insensitive). The script validates all names before applying changes and exits with an error on any mismatch.

Usage supports both staging and production environments, with a dry-run mode for previewing changes before applying them.

#### Provisioning Capacity Increase

The provisioning system now supports users with access to a larger number of workspaces. The previous limits have been raised to accommodate global policies that expand to all active workspaces in a deployment. First-login provisioning performance remains within acceptable bounds - subsequent logins skip already-provisioned workspaces and complete quickly regardless of workspace count.

</details>

<details>

<summary>v0.9.134 - Patient Insurance and Provider Identifiers in World Model (May 2026)</summary>

#### Patient Primary Insurance

The world model now surfaces primary insurance information directly on patient entities. When a patient has primary insurance coverage on file, the agent's patient context includes:

| Field                       | Description                                     |
| --------------------------- | ----------------------------------------------- |
| `payer_name`                | Name of the primary insurance payer             |
| `practice_payer_id`         | Practice-specific payer identifier              |
| `member_id`                 | Member ID used for eligibility lookups          |
| `policyholder_name`         | Name of the policyholder on the plan            |
| `policyholder_relationship` | Relationship of the policyholder to the patient |

These fields appear as a `primary_insurance` block in the patient summary provided to the agent during conversations. If a patient has no insurance coverage on file, the block is omitted.

This removes the need for agents to query a separate coverage entity when handling insurance-related workflows such as eligibility checks, benefits verification, or appointment scheduling that requires payer information.

#### Practitioner Member ID

Practitioner entities now include a stable practice-level identifier. This identifier is used as the join key when resolving which provider is associated with an appointment or availability slot, enabling the agent to reliably link scheduling data to the correct practitioner without parsing reference strings.

</details>

<details>

<summary>v0.9.133 - Ringless Voicemail (May 2026)</summary>

#### Ringless Voicemail Delivery

The Channel Management API now supports sending ringless voicemails - pre-recorded audio messages deposited directly into a recipient's voicemail box without ringing their phone.

**New use case type:** `ringless_voicemail` is now a valid use case type when creating use cases for a Twilio setup. Phone numbers assigned to a ringless voicemail use case must have voice capability and must be US numbers.

**New endpoint:** `POST /v1/twilio-setup/{setup_id}/use-case/{use_case_id}/ringless-voicemail`

Send a ringless voicemail by submitting a multipart form with:

| Field                    | Type     | Description                             |
| ------------------------ | -------- | --------------------------------------- |
| `recipient_phone_number` | `string` | US E.164 phone number (`+1XXXXXXXXXX`)  |
| `audio`                  | `file`   | MP3 audio file, 10-60 seconds, max 8 MB |

Returns `201 Created` with:

| Field                 | Type     | Description                                        |
| --------------------- | -------- | -------------------------------------------------- |
| `voicemail_id`        | `string` | Unique identifier for tracking delivery status     |
| `sender_phone_number` | `string` | E.164 phone number the voicemail will be sent from |

A sender phone number is selected randomly from all numbers assigned to the use case.

**Delivery status lifecycle:** Each voicemail starts as `pending` and transitions to one of four terminal states based on carrier feedback:

| Status          | Description                                              |
| --------------- | -------------------------------------------------------- |
| `pending`       | Submitted, awaiting delivery confirmation                |
| `delivered`     | Successfully deposited in the recipient's voicemail box  |
| `not_delivered` | Carrier reported non-delivery after attempt              |
| `skipped`       | Delivery skipped (e.g., recipient on a do-not-call list) |
| `failed`        | Upstream failure during the send attempt                 |

**Validation:**

* Audio must be a valid MP3 between 10 and 60 seconds (returns `422` otherwise)
* Audio must be under 8 MB (returns `413` otherwise)
* Use case must be of type `ringless_voicemail` (returns `409` otherwise)
* At least one phone number must be assigned to the use case (returns `404` otherwise)

**Phone number assignment changes:** Assigning a phone number to a `ringless_voicemail` use case now validates that the number has voice capability and is a US number. Non-US numbers return `409`. The sender is automatically registered with the upstream delivery provider during assignment.

</details>

<details>

<summary>v0.9.127 - Download Intake Uploads (May 2026)</summary>

#### Download Files from Intake Links

Operators can now download files that were submitted through intake links. A new endpoint on the intake links resource returns the raw file bytes with a `Content-Disposition: attachment` header, prompting the browser to save the file rather than render it.

The download is scoped through the intake link - an operator can only download uploads visible on the corresponding upload listing endpoint. If the upload row or the underlying file no longer exists (for example, after a right-to-be-forgotten deletion), the endpoint returns a 404 with the same detail as a missing upload, so callers cannot distinguish between the two cases.

**Endpoint:** `GET /intake/links/{link_id}/uploads/{upload_id}/download`

| Parameter   | Type       | Description            |
| ----------- | ---------- | ---------------------- |
| `link_id`   | path, UUID | Intake link identifier |
| `upload_id` | path, UUID | Upload identifier      |

**Response:** The raw file bytes with the original content type and filename. The response includes `Cache-Control: private, no-store` and `X-Content-Type-Options: nosniff` headers. Non-ASCII filenames are encoded per RFC 5987.

**Error responses:**

| Status | Condition                                  |
| ------ | ------------------------------------------ |
| 404    | Link, upload, or underlying file not found |
| 502    | Storage backend unavailable                |

**Audit:** Downloads are logged as PHI access events in the audit trail.

</details>

<details>

<summary>v0.9.125 - Caller ID for Simulation Sessions (May 2026)</summary>

#### Simulated Caller ID on Session Create

The simulation session creation endpoint now accepts an optional `caller_id` parameter. When provided, the platform forwards this phone number as the caller identity for the simulation session, enabling patient resolution during simulated conversations. This lets you test caller-specific behavior - such as greeting a known patient by name or loading their clinical context - without making a real phone call.

When `caller_id` is omitted or blank, the platform falls back to its default simulation identity, which produces no patient match. This matches the existing behavior before this change.

The parameter is available on both the create-session and create-test-conversation endpoints. Maximum length is 64 characters.

</details>

<details>

<summary>v0.9.123 - Manual Sync Trigger for Data Sources (May 2026)</summary>

#### On-Demand Data Source Sync

A new endpoint lets you manually trigger a sync for any data source in a workspace, bypassing the normal scheduling cadence. This is useful when you have just configured a new data source or pushed data to an upstream system and want the platform to pick up changes immediately rather than waiting for the next scheduled poll.

**New endpoint:**

`POST /data-sources/{data_source_id}/sync`

Returns **202 Accepted** with the data source ID and a timestamp once the sync request is queued. The sync itself runs asynchronously - the response does not wait for the sync to complete.

**Response body (202):**

| Field            | Type     | Description                        |
| ---------------- | -------- | ---------------------------------- |
| `status`         | string   | Always `"started"`                 |
| `data_source_id` | string   | The data source that was triggered |
| `triggered_at`   | datetime | When the sync request was accepted |

**Error responses:**

| Status | Description                                                                                                                                                                               |
| ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 400    | Invalid data source ID format                                                                                                                                                             |
| 404    | Data source not found in the workspace                                                                                                                                                    |
| 409    | Data source is already mid-sync. The response body includes `last_poll_at` and `last_poll_duration_ms` when available, so clients can display how long the current sync has been running. |
| 429    | Rate limit exceeded                                                                                                                                                                       |
| 503    | The sync backend is temporarily unreachable - retry after a short delay                                                                                                                   |

The 409 conflict check is best-effort. Because the status check and trigger are separate operations, a scheduled sync can start between them. Clients should treat 409 as a UX hint rather than a hard guarantee.

The endpoint requires write permissions and is subject to the standard write rate limit. A sync trigger is recorded in the workspace audit log.

</details>

<details>

<summary>v0.9.122 - Workspace Environment Session Idle Timeout (May 2026)</summary>

#### Per-Environment Session Idle Timeout

Session idle timeout is now determined by the workspace environment. Production workspaces use a stricter 15-minute default intended for regulated operating policies. Staging and development workspaces use a relaxed 8-hour default, reducing friction during development and testing workflows where sessions were previously expiring too quickly. Timeout configuration alone does not establish HIPAA compliance.

The idle timeout is resolved at token issuance time based on the workspace's environment setting. When a session is refreshed, the platform checks the stored timeout to determine whether the session has expired, then resolves a fresh timeout from the current workspace environment for the new token. This means environment changes take effect on the next token refresh rather than retroactively invalidating active sessions.

Bootstrap tokens (issued before workspace selection) use the production timeout by default. If the workspace environment cannot be determined, the platform fails safe to the stricter production timeout.

The idle timeout is set through deployment configuration with these validation constraints:

* Production timeout must not exceed the configured ceiling of 900 seconds
* Staging timeout must be greater than or equal to the production timeout
* Both values must be between 1 and 86400 seconds (24 hours)

No changes to the token request or response shape. Existing integrations continue to work without modification. The Developer Console login reason banner (introduced in v2.60.0) continues to display the appropriate inactivity message when a session expires.

</details>

<details>

<summary>v0.9.120 - Async Simulation Bridge Generation (May 2026)</summary>

#### Async Generation Mode for Simulation Bridge

The simulation bridge endpoint now supports an `async_generation` flag. When set to `true`, the endpoint returns immediately with a `run_id` instead of blocking for the full duration of target-spec inference and scenario generation. The generation work - including optional target-spec inference, scenario generation, and bridge execution - runs as a tracked background task. Clients poll the run status via the existing GET endpoint to track progress.

This eliminates the long synchronous wait (typically 40-60 seconds) that could trip proxy and ingress read timeouts for browser-based clients and BFF proxies. CLI and script clients that prefer the synchronous flow can continue using the default behavior (`async_generation: false`).

If any step of the background pipeline fails - target-spec inference, scenario generation, run persistence, or bridge execution - the run is marked as failed with a descriptive reason visible through the GET endpoint. Clients polling for status will see a terminal failed state rather than a run stuck in progress.

#### Response Shape

When `async_generation` is `true`, the response returns with `status: "running"`, an empty `scenarios` list, and no inferred target spec. The generated scenarios and effective target spec are available on the run row once the background task completes.

</details>

<details>

<summary>v0.9.116 - Tool Progress Orchestration (May 2026)</summary>

#### Intelligent Tool Progress Handling

The voice agent now uses a dedicated progress orchestration system during tool execution instead of the legacy filler loop. When a tool call starts, the platform resolves a progress hint for that specific tool - including the expected latency, progress style, and any custom phrasing configured for the tool - and passes it to the orchestrator. The orchestrator uses this information to deliver contextually appropriate progress responses while the tool runs, rather than falling back to generic filler audio.

This replaces the previous approach where a background monitor would periodically check on tool execution and emit filler responses on a fixed cadence. The new system is deadline-aware and adapts its progress behavior based on per-tool configuration, resulting in more natural conversations during tool calls that take varying amounts of time.

Tools that do not have explicit progress configuration continue to work without changes - the orchestrator falls back to default progress behavior.

</details>

<details>

<summary>v0.9.114 - DPoP Sender-Constrained Tokens (May 2026)</summary>

#### DPoP (Demonstration of Proof-of-Possession)

The token endpoint now supports DPoP sender-constrained tokens per RFC 9449. Clients can bind access tokens and refresh tokens to a cryptographic key pair, so stolen tokens cannot be used without the corresponding private key.

To opt in, generate an EC key pair (P-256, P-384, or P-521) and include a signed DPoP proof JWT in the `DPoP` header when requesting tokens. The proof must include the HTTP method, request URL, issued-at timestamp, and a unique identifier. The platform validates the proof signature, checks for replay, and binds the issued tokens to the client's public key.

When a token is DPoP-bound:

* The token response returns `token_type: "DPoP"` instead of `"Bearer"`.
* The access token includes a confirmation claim (`cnf`) containing the key thumbprint.
* Refresh token grants require a valid DPoP proof signed by the same key.
* Refresh tokens are **not rotated** on use - sender-constraining eliminates the replay risk that rotation mitigates, which also eliminates race conditions when multiple tabs or clients refresh concurrently.

DPoP is supported on both credential-based and SSO (Google OAuth) token grants. Bootstrap tokens used during multi-workspace selection do not support DPoP binding.

Clients that do not send a `DPoP` header continue to receive standard Bearer tokens with the existing rotation behavior. No migration is required.

**Supported algorithms**: ES256, ES384, ES512

**Key requirements**:

* DPoP proof `typ` must be `dpop+jwt`
* Proofs must not contain an `exp` claim
* Proofs are valid for up to 2 minutes from `iat`
* Each proof `jti` can only be used once (replay protection)

</details>

<details>

<summary>v0.9.113 - Real-Time Event Emission for Conversations and Outbound Sync (May 2026)</summary>

#### Conversation Events

When a voice conversation is created, the platform now emits a real-time event to the analytics pipeline. This event includes metadata such as call direction, participant count, turn count, duration, and completion status. Downstream consumers - including dashboards, metric computations, and integrations - receive conversation data with lower latency than the previous polling-based approach.

This applies to all voice conversations regardless of direction (inbound and outbound). No API changes are required.

#### Outbound Sync Events

Outbound sync operations now emit completion and failure events to the analytics pipeline. When an outbound sync finishes successfully or encounters an error, the platform publishes an event that downstream analytics and monitoring systems can consume. This makes it easier to track sync health, detect persistent failures, and build alerting around data synchronization without polling the sync status endpoint.

Both event types are emitted asynchronously and do not affect the latency or reliability of the originating API call. If event emission fails, the primary operation still succeeds and the failure is logged internally.

</details>

<details>

<summary>v0.9.112 - Call Intelligence Events (May 2026)</summary>

#### Call Intelligence Event Emission

Call intelligence data is now emitted as a platform event at the end of every call session. Previously, call intelligence was written to the transactional store but was not available as an event for downstream analytics pipelines. Now, when a call completes and intelligence is generated, the platform emits a `call.intelligence` event that feeds the analytics pipeline, metric computations, and any downstream consumers.

This means standard metrics that depend on call intelligence data (such as quality scores, conversation summaries, and risk assessments) now receive data with lower latency and higher reliability.

</details>

<details>

<summary>v0.9.110 - Intake Upload Validation and Duplicate Detection (May 2026)</summary>

#### Magic Byte Validation

Files uploaded through intake links are now validated against their declared content type using magic byte detection. The platform reads the first bytes of the uploaded file and verifies that the actual file content matches the declared type. If the file content does not match an allowed type, or if the declared content type conflicts with the detected content, the upload is rejected with a 422 error.

This prevents scenarios where a file is renamed or mislabeled to bypass content type restrictions. Image uploads require valid magic bytes; document formats that share container signatures (such as Office XML formats) are handled correctly.

#### Duplicate Upload Detection

Intake upload responses now include a `duplicate_of` field. When an uploaded file has the same content hash as a previously uploaded file in the same workspace, the response includes the ID and timestamp of the original upload. The duplicate file is still accepted and stored - this is informational only, letting integrators and the upload UI surface duplicate warnings without blocking the upload.

The upload UI shows an "Uploaded (duplicate)" indicator when a duplicate is detected.

</details>

<details>

<summary>v0.9.106 - Session Tags in List and Graph Responses (May 2026)</summary>

#### Session Tags Surfaced in Simulation Responses

Simulation session listings and graph responses now include a `tags` array on each session object. Tags carry metadata such as the scenario index and temperament assigned to a session, letting consumers join a session back to its parent run scenario and display persona information without making a separate per-session detail request.

This applies to both the list-sessions endpoint and the session graph endpoint. Sessions without tags return an empty array.

</details>

<details>

<summary>v0.9.105 - Expanded Entity Data Model for Clinical Workflows (May 2026)</summary>

#### Richer Patient Demographics

Patient entities now surface structured address fields (line, city, state, postal code, country), marital status, preferred language, race, ethnicity, and emergency contact information (name, phone, relationship) as typed entity columns. These fields are available to platform functions, agent tools, and the world model API without parsing nested resource blobs.

US state values are normalized to two-letter USPS abbreviations during ingestion. Both patient addresses and location addresses use the same normalization, so licensed-state filters like `state IN ('VA', 'MD', 'WV')` match consistently.

#### Expanded Appointment Data

Appointment entities now include:

* **Participant references and display names** for patient, practitioner, and location - the agent can show "Dr. Smith at Bristol" without additional lookups
* **Modality** (in-person, telehealth, etc.) from the service type
* **Duration** in minutes
* **Timezone** for display in the patient's local time
* **Provider metadata** including degree and specialization
* **Series and encounter identifiers** for linking related appointments
* **Cancellation reason** when an appointment is cancelled

#### Structured Location Data

Location (facility) entities now expose:

* **Typed address fields** (line, city, state, postal code, country) for direct queries
* **Geographic coordinates** (latitude and longitude) enabling proximity-based facility search without parsing nested resource data
* **Timezone** for per-facility appointment display
* **Facility identifier** used by availability and booking endpoints

#### Facility-Linked Availability Slots

Availability slots now carry a facility identifier, allowing the agent to filter provider availability by facility and match patients to nearby locations. Provider display names are resolved during availability polling so slot data includes the provider's name without a secondary lookup.

#### State Compatibility

The backward-compatible entity state structure now includes a `place` section for location entities alongside the existing `demographics` and `scheduling` sections. All new fields are also available in the flat entity state for consumers that read either format.

</details>

<details>

<summary>v0.9.104 - Simulation Bridge v2: Target Specs, Coverage-Driven Exploration, and Forking (May 2026)</summary>

#### Target Spec Support

The simulation bridge now accepts a structured target specification that defines what a simulation run should test. A target spec includes:

* **Desired states** - conversation flow states the agent should reach during the simulation
* **Non-desired states** - states the agent should avoid, with hard (fail) or soft (flag) severity
* **Ordered pathways** - sequences of states the agent should walk through in order
* **Completion criteria** - states that must be reached and tools that must be called for a scenario to pass

When a target spec is provided, each completed session is scored against it: 100 if all criteria passed, 0 if a hard-forbidden state was entered, 50 for partial completion with specific misses reported in the score rationale.

You can supply a target spec explicitly when calling the bridge, or set `auto_target_spec` to have the platform infer one from your natural-language objective. When inferred, the response includes the generated spec and a rationale explaining the inference.

#### Target Spec Planning Endpoint

A new `/bridge/plan` endpoint lets you preview what target spec the platform would infer from a natural-language objective before running a simulation. The response includes the inferred spec, a rationale, and the available states and tools on the service. This supports an edit-then-run workflow: review the inferred spec, adjust desired states or pathways, then POST the final spec to `/bridge`.

| Detail       | Value                                                             |
| ------------ | ----------------------------------------------------------------- |
| Method       | `POST`                                                            |
| Path         | `/simulations/bridge/plan`                                        |
| Auth         | API key with write permission                                     |
| Request body | `service_id`, `objective` (natural-language testing goal)         |
| Response     | `target_spec`, `rationale`, `available_states`, `available_tools` |

#### Coverage-Driven Exploration

A new `exploration` parameter on the bridge endpoint enables coverage-driven turn selection. When `coverage_driven_selection` is enabled, the platform evaluates candidate caller utterances each turn and selects the one most likely to reach uncovered states or advance an ordered pathway. This produces higher state coverage without increasing the number of scenarios.

#### Session Forking

When `exploration.enable_forking` is enabled and the top two candidate utterances aim at different conversation states, the platform forks the session into parallel branches that explore both paths. Forking is bounded by a per-scenario budget (`max_forks_per_scenario`, default 2, max 5) and a maximum fork depth to prevent runaway branching.

Forked sessions inherit the parent's conversation history and state, and are scored independently on completion. The run's result now reports both root sessions and forked children, with forked sessions tagged for identification.

#### Run Replay Support

Bridge runs now persist the full request body, objective, and generated scenarios so runs can be replayed without regenerating scenarios. A new endpoint returns the full run payload including replay data:

| Detail   | Value                                                        |
| -------- | ------------------------------------------------------------ |
| Method   | `GET`                                                        |
| Path     | `/simulations/runs/{run_id}`                                 |
| Auth     | API key with read permission                                 |
| Response | Run metadata plus `objective`, `bridge_request`, `scenarios` |

To replay a run, re-POST the `bridge_request` from the response to `/bridge`. The persisted request includes the effective target spec (inferred or explicit) so replays are deterministic - they reuse the same spec rather than re-inferring against potentially changed service configuration.

#### Run Tagging

Bridge runs are now automatically tagged based on their configuration:

* `bridge` - all bridge runs (unchanged)
* `target_spec` - runs with an explicit or inferred target spec
* `auto_plan` - runs where the target spec was inferred from the objective
* `forking` - runs with forking enabled

Session tags now include `scenario_index:N` linking each session to its scenario in the run's persisted scenario list, enabling the frontend to join sessions to personas without duplicating scenario data on every session.

#### Bridge Response Changes

The `/bridge` response now includes two additional fields when `auto_target_spec` was used:

* `inferred_target_spec` - the target spec that was inferred from the objective
* `inferred_target_spec_rationale` - explanation of the inference

Both are null when the target spec was explicit or absent.

#### Completion Event Changes

The bridge completion event emitted for observability now reports `total_sessions` (roots plus forked children), `forked_sessions`, and per-session metadata including `scenario_index`, `is_fork_child`, and `recommend_degraded`. The previous `successful_scenarios` field is renamed to `successful_parent_scenarios` for clarity.

**Breaking changes:** The bridge completion event field `successful_scenarios` is renamed to `successful_parent_scenarios`. Downstream consumers that parse this event should update their field references.

</details>

<details>

<summary>v0.9.102 - Upload Page Moved to Forms App (May 2026)</summary>

#### Upload Page Served from Forms App

The customer-facing file upload page is now served by the forms application instead of the API backend. Upload links now point to the forms app path (`/s/upload/{token}`) and the page is rendered as a modern React application with drag-and-drop support, upload progress indicators, and proper loading and error states.

The API backend retains the file upload endpoint and now exposes a new JSON info endpoint that the forms app consumes server-side to resolve link metadata (display name, allowed file types, size limits). The previous server-rendered HTML upload page has been removed from the API.

#### Upload URL Format Change

Upload URLs returned by the Create Link and List Links endpoints now use the forms app base URL with the `/s/upload/{token}` path pattern instead of the previous API-hosted `/upload/{token}` path. Existing unexpired links will continue to work, but newly generated links use the updated format. If you store or display upload URLs, update any URL pattern matching or validation logic accordingly.

#### Removed Environment Variable

The `API_PUBLIC_URL` environment variable is no longer used. Upload link URL generation now relies solely on `FORMS_PUBLIC_URL`. Deployments that previously set `API_PUBLIC_URL` can remove it.

#### New Info Endpoint

A new endpoint returns link metadata as JSON, replacing the previous HTML page render:

| Detail      | Value                                                                                       |
| ----------- | ------------------------------------------------------------------------------------------- |
| Method      | `GET`                                                                                       |
| Path        | `/upload/{link_token}/info`                                                                 |
| Auth        | None (token-authenticated)                                                                  |
| Response    | JSON with display name, customer slug, max upload size, and allowed content types           |
| Error codes | `invalid_token` (400), `link_not_found` (404), `link_expired` (410), `link_exhausted` (410) |

#### Removed Endpoint

The `GET /upload/{link_token}` endpoint that returned the server-rendered HTML upload page has been removed. The upload file submission endpoint (`POST /upload/{link_token}/files`) remains unchanged.

**Breaking changes:**

* Upload URLs now use a different base path. Integrations that parse or validate upload URLs should update their patterns.
* The `GET /upload/{link_token}` HTML page endpoint no longer exists. Direct API consumers that rendered this page in iframes or webviews should point to the new forms app URL instead.
* The `API_PUBLIC_URL` environment variable is no longer read.

</details>

<details>

<summary>v0.9.100 - Greeting Latency Optimization (May 2026)</summary>

#### Faster First-Message Delivery

The voice agent now delivers the greeting to the caller while speech-to-text initialization runs in the background. Previously, the greeting was blocked until the speech recognition pipeline was fully connected and configured, adding unnecessary delay before the caller heard anything.

With this change, the agent enqueues the greeting immediately after the caller joins, and speech recognition connects concurrently. Since callers typically take 3-5 seconds to respond after hearing the greeting, the speech pipeline is ready well before any caller speech arrives. This reduces first-message latency to under 2 seconds in typical conditions.

If the speech recognition pipeline fails to initialize within its timeout window, the session terminates cleanly rather than hanging indefinitely.

#### Greeting Latency Metric

A new `voice_agent.greeting.ttfb_ms` metric tracks time-to-first-byte specifically for the greeting message, separate from the general turn-level latency metric. The latency metadata sent to clients now includes an `is_greeting` field so integrators can distinguish greeting latency from subsequent turn latency.

**Breaking change:** None. The greeting behavior is unchanged from the caller's perspective - it just arrives faster. The new `is_greeting` field in latency metadata is additive.

</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.amigo.ai/api-reference/change-logs/amigo-api/amigo-api-archive-v0-9-100-249.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.
