# Amigo API

Release history for the Amigo API (backend services).

{% hint style="warning" %}
**Two API Surfaces**: Starting with v0.9.0, Amigo exposes two distinct APIs:

* **Classic API** (`api.amigo.ai`): organization-scoped runtime API (conversations, users, tools, simulations)
* **Platform API** (`api.platform.amigo.ai`): workspace-scoped configuration API (agents, skills, services, integrations, operators, FHIR)

See the [Developer Guide](https://docs.amigo.ai/developer-guide) for full documentation on both APIs.
{% endhint %}

<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.191 - Platform API: Realtime Metric Store with Hot Values and Delta History (June 2026)</summary>

#### Realtime Metric Store with Hot Values and Delta History

The metric store now serves hot metric values from a low-latency cache and lands durable history through the event pipeline, replacing the previous batch-only read path. The metric catalog endpoint also returns additional fields that were previously internal-only.

**What changed:**

* **Hot metric reads.** Metric values are now written to a fast in-memory cache on every metric projection and served directly from that cache for dashboard and call detail queries. This eliminates the delay between metric computation and metric availability in the UI. Cached values expire automatically after a rolling window, so idle workspaces do not accumulate stale data.
* **Automatic cache pruning for active workspaces.** Workspaces with continuous metric writes now prune expired entries from the hot cache on each write cycle. Previously, per-entry expiration only applied when the entire cache key expired due to inactivity. Active workspaces whose cache key was continuously refreshed could accumulate unbounded stale entries. The pruning runs in batches to bound per-write overhead.
* **Delta history pipeline.** Durable metric history now flows through the event pipeline into the analytical store, replacing the previous direct-write path. This decouples metric durability from the API request path, improving write latency for metric projection calls.
* **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 API changes are required. Metric values are now available with lower latency in dashboards and call detail views. 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.205 - Platform API: Sync Status Moved to Real-Time Cache (June 2026)</summary>

#### Sync Status Moved to Real-Time Cache

Data source sync status fields are now served from a low-latency cache written by the connector sync engine, replacing the previous database-backed write path. This improves sync status freshness and removes a synchronous database write from the polling hot path.

**What changed:**

* **Sync status served from cache.** The `last_sync_at`, `last_sync_status`, and `last_sync_event_count` fields on data source responses are now read from the same real-time cache that the connector sync engine writes after each poll cycle. Previously, these fields were written to the durable store during each poll, adding latency to the sync path and occasionally lagging behind the actual connector state.
* **No schema changes.** The data source response shape is unchanged. The `last_sync_at`, `last_sync_status`, and `last_sync_event_count` fields continue to appear on all data source endpoints (get, list, status) with the same types and semantics. When no sync has occurred yet, `last_sync_at` and `last_sync_status` are `null` and `last_sync_event_count` is `0`, matching previous behavior.
* **Internal sync status endpoint removed.** The internal `POST /data-sources/sync-status` endpoint has been removed. This was an internal endpoint used by the connector sync engine and was not part of the public API. The sync engine now writes status directly to the cache without an intermediate API call.
* **Sync status no longer searchable.** Data source search no longer matches against sync status values. Searches continue to match against name, ID, source type, and health status.
* **Connector source list includes cache-backed sync fields.** The connector source breakdown endpoint now reads sync status from the same cache, so sync timestamps and status values on connector analytics responses are consistent with the data source detail endpoints.

**What you need to do:**

No API changes are required. If you previously relied on searching data sources by sync status text (e.g., searching for "error" to find failed syncs), use the health status field or filter by `is_active` instead. All other data source queries and responses work as before with improved freshness.

</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.190 - Platform API: Simplified Call Detail Source Resolution (June 2026)</summary>

#### Simplified Call Detail Source Resolution

The call detail endpoint now uses a streamlined 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.
* **SQL warehouse warmup with retry.** The agent engine now retries the initial connection to the analytics warehouse with backoff before accepting sessions. If the warehouse remains unreachable after multiple attempts, startup fails. Previously, a single failed warmup attempt was silently ignored.
* **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:**

* **Multi-source turn resolution.** The voice conversation detail endpoint now resolves conversation turns from the active session store first, then falls back to the durable conversation turn records, and finally checks the analytics warehouse session logs. This ensures that turns are available for active calls, recently completed calls, and historical calls regardless of where the data currently resides.
* **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 streamlined.** 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.
* **Removed internal state-save endpoint.** The internal endpoint previously used by the agent engine to persist text conversation state checkpoints has been removed. All session persistence now flows through the interaction log path.
* **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 enforce a strict HIPAA-compliant idle timeout (default 15 minutes). Staging and development workspaces use a relaxed idle timeout (default 8 hours), reducing friction during development and testing workflows where sessions were previously expiring too quickly.

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 configurable via environment variables with validation constraints:

* Production timeout must not exceed the HIPAA 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>

<details>

<summary>v0.9.99 - Verbatim Rubrics for Standard Quality Metrics (May 2026)</summary>

#### Verbatim Rubrics for LLM Judge Prompts

All 11 standard quality metrics (patient sentiment, conversational naturalness, conciseness, information accuracy, safety, and the six outcome metrics) now use the full verbatim rubric text from the authored specification instead of paraphrased summaries. This affects the `ai_query_prompt` field on builtin metric definitions.

The previous prompts condensed multi-paragraph rubrics into short narrative summaries, which stripped specific scoring anchors and signal definitions the LLM judge needs to score consistently. The updated prompts include the complete scoring bands, per-band anchor phrases, and severity definitions exactly as authored.

For the voice judge, the 10 dimension rubric blocks now include the full signal-to-severity mapping tables from the voice quality spec. Each dimension lists the specific signals to match against and the severity to assign, replacing the previous narrative descriptions. Severity labels are uppercase (CRITICAL, FLAG, WARNING) so the judge can anchor its output to them. A new scoring rule prevents the judge from upgrading or downgrading severity relative to what the spec assigns to each signal.

#### Increased Prompt Length Limit

The `ai_query_prompt` field on metric definitions now accepts up to 8,000 characters (previously 2,000). The higher limit accommodates full verbatim rubrics for standard quality judges.

**Breaking change:** None. The `ai_query_prompt` max length increase is backward-compatible. Existing custom metrics are unaffected. Standard metric prompt content has changed but standard metrics are platform-managed and not user-editable.

</details>

<details>

<summary>v0.9.98 - Simulation Bridge: Context Graph Versioning and Raised Limits (May 2026)</summary>

#### Context Graph Version Resolution

The simulation bridge endpoint now resolves the active context graph version instead of reading from the top-level context graph stub. Previously, the bridge prompt could reference stale states, tools, and behaviors that no longer matched what the voice agent actually runs. The bridge now reads the pinned version from the service's release version set, matching the same resolution path used by the agent engine at runtime.

If no version pin is configured, the bridge falls back to the latest version and logs a warning so operators can detect configuration drift.

#### Raised Concurrency and Scenario Limits

The simulation bridge now supports higher limits for power users running large-scale test suites:

| Parameter                                    | Previous Limit | New Limit |
| -------------------------------------------- | -------------- | --------- |
| Maximum scenarios per run                    | 20             | 50        |
| Maximum concurrency                          | 5              | 20        |
| Maximum concurrent bridge runs per workspace | 3              | 10        |

The maximum turns per scenario remains at 40. These limits apply to bridge runs initiated through the API or Developer Console. Agent Forge CLI users who call session primitives directly are not affected by these caps.

#### Updated Generation Model

Scenario generation and caller simulation now use an upgraded model for improved scenario diversity and more realistic caller behavior.

</details>

<details>

<summary>v0.9.97 - Absolute Upload URLs and Environment Configuration (May 2026)</summary>

#### Absolute Upload URLs

Upload links returned by the intake link endpoints now include fully qualified absolute URLs instead of relative paths. Previously, the `upload_url` field in link responses contained a relative path, requiring callers to construct the full URL themselves. Now the response includes the complete URL ready for sharing.

If the platform environment is not fully configured for upload link generation, the intake link endpoints return a `503 Service Unavailable` error with a clear message instead of returning a broken relative URL.

#### Surface URL Configuration

Surface delivery (SMS and email) now uses a dedicated forms URL configuration, separate from the API base URL. This allows surface URLs and upload URLs to be served from different domains. Error messages for misconfigured delivery have been simplified.

</details>

<details>

<summary>v0.9.96 - Shareable Upload Links (May 2026)</summary>

#### Shareable Upload Links for Customer Data Intake

Operators can now generate shareable upload links that give non-technical customers drag-and-drop file upload access - no API key or login required.

**New endpoints:**

| Method   | Path                                                | Description             |
| -------- | --------------------------------------------------- | ----------------------- |
| `POST`   | `/v1/{workspace_id}/intake/links`                   | Create an upload link   |
| `GET`    | `/v1/{workspace_id}/intake/links`                   | List upload links       |
| `DELETE` | `/v1/{workspace_id}/intake/links/{link_id}`         | Revoke an upload link   |
| `GET`    | `/v1/{workspace_id}/intake/links/{link_id}/uploads` | List uploads for a link |

Each link is scoped to a workspace and customer, with configurable expiration (1-720 hours, default 7 days) and a maximum upload count (1-10,000 files, default 100). Links can be revoked at any time.

#### Public Upload Page

The generated link opens a self-contained upload page at `/upload/{link_token}` with:

* Drag-and-drop file upload with progress indicators
* Client-side validation for file type and size
* Support for PDF, Word, PowerPoint, JPEG, and PNG files up to 100 MB each
* Clear error states for expired, revoked, or exhausted links

The link token itself is the authentication mechanism. No API credentials are exposed to the end user.

#### Link Lifecycle

Links report one of four statuses: `active`, `expired`, `revoked`, or `exhausted`. Uploaded files are tracked with the same metadata as API-submitted intake files, including SHA-256 hash, size, content type, and scan status.

</details>

<details>

<summary>v0.9.95 - Greeting Debounce Window (May 2026)</summary>

#### Post-Greeting Transcript Debounce

Voice sessions now apply a short debounce window after the agent greeting finishes playing, during which late-arriving user transcripts are still discarded. Speech-to-text engines can finalize transcripts for words spoken *during* the greeting with a delay that extends past the moment the greeting audio ends. Without this window, those stale fragments could reach the agent and contaminate the first real conversational turn.

The debounce applies to all transcript processing paths including barge-in detection, end-of-turn handling, and pre-fetch. No configuration is required - the protection is automatic for all voice sessions.

**Behavioral effect:**

* Callers who speak over the greeting no longer have their words echoed back or acted on after the greeting completes
* The first real agent turn starts cleanly, without leftover speech fragments from the greeting period
* Barge-in remains suppressed through the debounce window, consistent with greeting protection behavior

</details>

<details>

<summary>v0.9.94 - Patient Update: Gender, Language, and Structured Address (May 2026)</summary>

#### Expanded Patient Update Fields

The `patient_update` world tool now accepts additional demographic and address fields, giving agents the ability to update more patient information during a conversation without requiring a separate EHR workflow.

**New fields:**

| Field            | Description                                      |
| ---------------- | ------------------------------------------------ |
| `gender`         | Patient gender (male, female, other, or unknown) |
| `language`       | Preferred language (e.g. English, Spanish)       |
| `address_line_1` | Street address line                              |
| `city`           | City                                             |
| `state`          | State name                                       |
| `postal_code`    | Postal code                                      |

#### Structured Address Support

Addresses can now be provided as individual components (`address_line_1`, `city`, `state`, `postal_code`) instead of a single free-text string. Structured fields take precedence over the free-text `address` field when both are provided. Existing address data is preserved when unrelated fields are updated, so a phone number change no longer clears a previously stored address.

#### Gender and Language Carry-Forward

When `gender` or `language` is not provided in an update, the existing value is carried forward from the current patient record. This prevents unrelated updates from clearing demographic data.

</details>

<details>

<summary>v0.9.93 - Dashboard Filter Binding and Per-Panel Row Limits (May 2026)</summary>

#### Dashboard Filter Binding

The dashboard execute endpoint now binds filter values as named SQL parameters to panel queries. Filters declared in the dashboard definition are matched against `filter_values` supplied in the execute request. If a filter value is provided, it is bound; if not, the filter's default value is used. Unknown keys in `filter_values` are silently ignored, so clients can safely pass stale keys after a dashboard definition changes.

Filter IDs are validated on create and update: IDs must start with a lowercase letter, contain only lowercase letters, digits, and underscores, and be at most 64 characters. The reserved identifier `ws_id` cannot be used as a filter ID.

#### Per-Panel Row Limits

Panels can now declare a `max_rows` field that overrides the system default row limit for that panel's query. This allows data-heavy panels (tables, exports) to request more rows while keeping chart panels lightweight.

**Validation**

Creating or updating a dashboard with invalid or reserved filter IDs now returns `422 Unprocessable Entity` with a descriptive error message.

</details>

<details>

<summary>v0.9.90 - Standard Quality Metrics: 11 Builtin LLM-Judge Metrics (May 2026)</summary>

#### Standard Quality Metrics

The platform now ships 11 builtin quality metrics that evaluate every AI agent conversation using LLM-based judgment. These metrics run automatically on a daily schedule against call transcript summaries and require no configuration.

**5 universal metrics** apply to every service:

| Metric                     | Type             | What It Measures                                                                              |
| -------------------------- | ---------------- | --------------------------------------------------------------------------------------------- |
| Patient Sentiment          | Categorical      | How the patient felt about the interaction with the agent (positive, neutral, negative)       |
| Conversational Naturalness | Numerical (1-10) | Whether the agent sounds like a person or a scripted system                                   |
| Conciseness                | Numerical (1-10) | Whether the agent kept responses appropriately brief                                          |
| Information Accuracy       | Boolean          | Whether the agent accurately relayed information from tool calls                              |
| Safety                     | Categorical      | Whether a safety concern arose and how it was handled (no\_event, handled, warning, critical) |

**6 product-type outcome metrics** compute only for services tagged with the matching product type:

| Metric              | Product Type | Categories                                              |
| ------------------- | ------------ | ------------------------------------------------------- |
| Outcome: Scheduling | `scheduling` | escalated, resolved, no\_action                         |
| Outcome: Outbound   | `outbound`   | escalated, resolved, opted\_out, no\_action, no\_answer |
| Outcome: Coaching   | `coaching`   | escalated, resolved, no\_action                         |
| Outcome: Intake     | `intake`     | escalated, resolved, no\_action                         |
| Outcome: Triage     | `triage`     | escalated, resolved, no\_action                         |
| Outcome: Support    | `support`    | escalated, resolved, no\_action                         |

Product-type filtering uses service tags. Services tagged with a matching product type have the corresponding outcome metric computed for their calls. Services without a matching tag are excluded from that outcome metric. Universal metrics apply to all services regardless of tags.

#### Metric Model Update

The metric definition model adds a new field:

| Field                      | Type                    | Description                                                                                                                                                                    |
| -------------------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `applies_to_product_types` | array of string or null | Product types this metric applies to. When set, the metric only computes for calls on services tagged with one of these types. When null, the metric applies to every service. |

All 11 standard quality metrics are returned by the metric settings and metric definitions endpoints alongside existing builtin metrics. They appear as `builtin: true` with `extraction_mode: "ai_query"` and daily granularity.

#### Writes and Idempotency

Standard quality metric results are written idempotently. Retries or reruns for the same time window overwrite previous results rather than duplicating rows. Metric values appear in the same metric values endpoints and dashboard views as all other metrics.

</details>

<details>

<summary>v0.9.89 - Phone Number Provisioning: Rest-of-World Support and Number Types (May 2026)</summary>

#### Phone Number Types

Phone number search and provisioning now support three number types: **local**, **mobile**, and **toll-free**. The `number_type` parameter is required on both the available-number search and the provisioning request. The area code filter is now validated to only apply to `local` numbers (returns 422 for other types).

The phone number response now includes a `number_type` field so you can see the type without additional lookups.

#### Rest-of-World Regulatory Bundle Discovery

Provisioning is no longer limited to US and CA. The `country` parameter now accepts any ISO 3166-1 alpha-2 country code. Required bundles are resolved dynamically based on the country, number type, and business end-user type:

* **US**: SHAKEN/STIR + CNAM bundles required (any number type)
* **CA**: CNAM bundle required (any number type)
* **Other countries**: regulatory compliance bundles are discovered automatically per country and number type

All required bundles must be approved before a number can be provisioned. The 409 error response now directs callers to inspect the bundle list to see which are missing or not yet approved.

#### Bundle List Filtering

The bundle list endpoint now supports optional filters:

| Parameter                     | Type        | Description                                                                       |
| ----------------------------- | ----------- | --------------------------------------------------------------------------------- |
| `policy_sid`                  | string      | Filter by exact policy SID                                                        |
| `iso_country` + `number_type` | string pair | Resolve and return only bundles required for that country/number-type combination |

`policy_sid` is mutually exclusive with the country/number-type pair. When no filters are provided, all bundles for the setup are returned.

#### Migration

* The `country` parameter on provisioning and available-number search no longer restricts to US/CA - any valid two-letter country code is accepted
* `number_type` is now a required parameter on available-number search and provisioning requests
* Phone number responses include the new `number_type` field

</details>

<details>

<summary>v0.9.88 - Voice Judge: Audio-Native Quality Evaluation (May 2026)</summary>

#### Voice Quality: Audio-Native Per-Call Scoring

The platform now evaluates voice agent call quality directly from audio recordings, scoring each call across 10 dimensions that measure the voice experience independent of conversational logic or prompting. Scores range from 0.0 to 1.0 per dimension, with severity classifications (Critical, Flag, Warning) and quoted evidence from the audio.

The 10 voice quality dimensions:

| Dimension                     | Priority | What It Measures                                                                  |
| ----------------------------- | -------- | --------------------------------------------------------------------------------- |
| Latency and Dead Air          | P0       | Response latency between turns, prolonged silence                                 |
| Pronunciation                 | P0       | Correct pronunciation of medical terms, drug names, dates, numbers, patient names |
| Clarity                       | P0       | Speech intelligibility and clean audio                                            |
| Filler and Silence Management | P1       | Graceful handling of processing pauses, appropriate verbal acknowledgments        |
| Interruption Handling         | P1       | Clean barge-in behavior, smooth recovery after being interrupted                  |
| Audio Consistency             | P1       | Absence of volume spikes, pitch anomalies, or mid-word cutoffs                    |
| Pacing                        | P2       | Conversational speech rate with appropriate pauses                                |
| Warmth and Tone               | P2       | Emotional appropriateness matched to patient state                                |
| Accent and Language Quality   | P2       | Language match and accent naturalness                                             |
| Voice Identity                | P2       | Consistent agent voice and persona across the call                                |

Each dimension includes an evidence quote describing exactly what was heard and when, so results are auditable rather than opaque scores.

Scoring runs as a scheduled batch job. Results are available per-service through a new API endpoint.

#### Platform API: Voice Judge Results Endpoint

New endpoint for retrieving per-call voice quality scores for a service:

| Method | Path                                                          | Description                               |
| ------ | ------------------------------------------------------------- | ----------------------------------------- |
| GET    | `/v1/{workspace_id}/services/{service_id}/voice-judge/recent` | Recent voice-judge results (newest first) |

Query parameters:

| Parameter | Type    | Default | Description                |
| --------- | ------- | ------- | -------------------------- |
| `limit`   | integer | 20      | Max rows to return (1-100) |

The response includes per-dimension scores, an overall composite score, severity counts, and the raw judge output with evidence quotes for UI drill-in.

Returns 503 if the analytics warehouse is unavailable, distinguishing infrastructure failures from "no evaluations yet" (which returns 200 with an empty list).

#### Metrics: 10 Voice Quality Metrics

Ten new builtin metrics aggregate per-call voice quality scores into per-workspace per-period averages:

* `voice_judge_latency_dead_air`
* `voice_judge_pronunciation`
* `voice_judge_clarity`
* `voice_judge_filler_silence`
* `voice_judge_interruption_handling`
* `voice_judge_audio_consistency`
* `voice_judge_pacing`
* `voice_judge_warmth_tone`
* `voice_judge_accent_quality`
* `voice_judge_voice_identity`

All are numerical metrics with daily granularity, unit `score`, and valid range \[0.0, 1.0]. Available through the existing metric store endpoints.

</details>

<details>

<summary>v0.9.87 - Soft Escalation and Empathy Detection Accuracy (May 2026)</summary>

#### Escalation: Soft Escalation Keeps Agent on the Call

When a caller requests to speak with a human in a non-emergency context, the platform now performs a **soft escalation** instead of immediately suspending the agent. The agent stays on the call, acknowledges the caller's request, and lets them know an operator is being contacted. Operators are notified through the standard escalation flow and can take over when ready.

Previously, all escalations suspended the agent immediately, which could leave the caller in silence while waiting for an operator to join. Soft escalation eliminates that gap - the caller continues to hear the agent until the operator is connected.

Hard escalation behavior is unchanged: crisis-level detections still suspend the agent, but the current sentence now finishes playing before the agent goes silent. This prevents mid-sentence audio cutoffs that could be jarring for the caller.

#### Emotion Detection: Stricter Empathy Tier 2 Activation

Tier 2 empathy (full empathy mode) now requires agreement between both the categorical emotion model and the dimensional valence model before activating based on a distress emotion label alone. Previously, a distress label from a single model was sufficient to trigger Tier 2, which could cause false activations on calm, low-energy speech - particularly on telephony-quality audio where the categorical model can default to distress classifications.

This change reduces false Tier 2 activations on routine calls. When Tier 2 activates incorrectly, it caps speech speed for the remainder of the call, making the agent sound unnaturally slow. The fix aligns the Tier 2 activation logic with the stricter conjunctive pattern already used for Tier 3.

No API changes. Both changes affect runtime behavior only.

</details>

<details>

<summary>v0.9.86 - Simulation and Playground Entity Visibility (May 2026)</summary>

#### World Model: Entity State Includes Simulation and Playground Events

Entity state projections now include events from simulation and playground sessions. Previously, these events were filtered out along with other non-production sources, which meant that entities created or modified during tool testing, text playground sessions, or simulation runs did not appear in the world model.

With this change, running a tool in the playground or executing a simulation that writes entity data will produce visible entity state - the same data the agent would see during a live conversation. This makes testing workflows more realistic and easier to validate.

Analytical pipelines - metrics, encounter detection, and gap detection - continue to exclude simulation and playground events. Production analytics are not affected.

No API changes. This is a data pipeline behavior change only.

</details>

<details>

<summary>v0.9.84 - Enriched Call Trace Analysis (May 2026)</summary>

#### Call Intelligence: Component Attribution

Post-call analysis now identifies which system component caused each behavioral issue during a call. Every misalignment between caller signals and agent responses is attributed to one of seven components: speech recognition, navigation model, emotion detection, state machine, tool execution, prompt logic, or turn-taking. Each attribution includes the specific issue, structural evidence, and a severity rating (critical, contributing, or minor).

This enables targeted debugging - instead of reviewing an entire call to understand what went wrong, you can see that a specific failure was caused by, for example, incorrect emotion detection or a state machine that failed to advance.

#### Call Intelligence: Enriched Execution Traces

Structured execution traces forwarded to the analysis pipeline now include richer per-turn data:

| Signal                    | Description                                                                                         |
| ------------------------- | --------------------------------------------------------------------------------------------------- |
| Tool summaries            | Structural shape of each tool call: name, input key names, output length, success/failure, duration |
| Reasoning step count      | Number of internal reasoning steps the agent took per turn                                          |
| Emotion label and valence | Detected caller emotion and emotional direction per turn                                            |
| Latency breakdown         | Navigation and engine timing per turn                                                               |
| STT confidence            | Speech recognition reliability score per turn                                                       |

All data is structural and categorical only. No patient names, dates of birth, medical record numbers, or raw tool input/output values are included in the analysis pipeline.

#### Call Intelligence: PHI-Safe Tool Reporting

Tool call reporting in execution traces has been redesigned around data minimization. Instead of forwarding raw tool inputs and outputs (which may contain patient information), traces now report only the structural shape: tool name, sorted input key names, output character length, success/failure flag, and execution duration. This eliminates the PHI surface area entirely rather than relying on redaction.

</details>

<details>

<summary>v0.9.83 - Judge Mode Metrics and Catalog Cleanup (May 2026)</summary>

#### Platform API: Judge Mode for Custom Metrics

Custom metric definitions now support **judge mode** - LLM-based evaluation of individual interactions using a rubric prompt you define. This lets you create metrics that score each call, encounter, or session against criteria specific to your organization.

**New definition fields:**

| Field         | Type     | Description                                                                                                           |
| ------------- | -------- | --------------------------------------------------------------------------------------------------------------------- |
| `prompt`      | `string` | Rubric prompt for LLM judge evaluation. Use `{$.path}` references to inject entity context data. Max 4000 characters. |
| `granularity` | `string` | `aggregate` (one value per time period, default) or `per_entity` (one value per interaction)                          |

**Validation rules for judge mode:**

* Metrics with a `prompt` must use `ai_query` extraction mode
* Metrics with `per_entity` granularity require a `prompt`
* The `prompt` must contain at least one `{$.path}` context variable
* Maximum 50 custom metric definitions per workspace

**Example:** A "clinical accuracy" judge metric could evaluate each call transcript against a rubric that checks whether the agent correctly verified allergies, confirmed medication dosages, and followed escalation protocols.

#### Platform API: Metric Catalog Response Changes

The `GET /v1/metrics/catalog` response schema has been updated:

**Added fields:**

| Field         | Type      | Description                                  |
| ------------- | --------- | -------------------------------------------- |
| `granularity` | `string`  | `aggregate` or `per_entity`                  |
| `has_prompt`  | `boolean` | Whether the metric uses LLM judge evaluation |

**Removed fields:**

| Field                | Notes                                 |
| -------------------- | ------------------------------------- |
| `source`             | No longer included in catalog entries |
| `channel_scope`      | No longer included in catalog entries |
| `period_granularity` | Replaced by `granularity`             |

#### Platform API: Freshness Endpoint Removed

The `GET /v1/metrics/freshness` endpoint has been removed.

</details>

<details>

<summary>v0.9.82 - Address-Aware Phone Number Provisioning (May 2026)</summary>

#### Platform API: Address Requirements on Phone Provisioning

Phone number search and provisioning now honor address requirements reported by the telephony provider, expanding the pool of numbers available for purchase.

**Search filtering:**

Available phone number search results are now filtered based on address compatibility rather than excluding all numbers with any address requirement:

* Searching in the same country as the setup's business address excludes numbers requiring a foreign address.
* Searching in a different country excludes numbers requiring a local (in-country) address.
* Numbers with no address requirement or flexible requirements are always included.

Previously, all numbers with any address requirement were excluded from search results.

**Provisioning validation:**

Phone number provisioning now validates address requirements before purchase and automatically attaches the setup's business address when required:

| Address Requirement | Behavior                                                                                                                 |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| `none`              | No address needed - number is purchased directly                                                                         |
| `any`               | Setup's business address is attached to the purchase                                                                     |
| `local`             | Setup's business address is attached. Returns `422` if the business address country does not match the number's country. |
| `foreign`           | Setup's business address is attached. Returns `422` if the business address country matches the number's country.        |

**New error responses:**

| Status | Condition                                                                              |
| ------ | -------------------------------------------------------------------------------------- |
| `409`  | Number is no longer available at purchase time                                         |
| `422`  | Number's address requirement is incompatible with the setup's business address country |

</details>

<details>

<summary>v0.9.81 - Country-Conditional Phone Number Bundles (May 2026)</summary>

#### Platform API: Country-Conditional Regulatory Bundles

Phone number provisioning now enforces country-specific regulatory bundle requirements. Instead of requiring all three Trust Hub verification steps (customer profile, SHAKEN/STIR, CNAM) upfront, the setup flow separates the customer profile from regulatory bundles, and each country specifies which bundles are required before provisioning.

**New endpoints:**

| Endpoint                                   | Method | Description                                                  |
| ------------------------------------------ | ------ | ------------------------------------------------------------ |
| `/v1/twilio-setup/{id}/bundle/shaken-stir` | POST   | Submit SHAKEN/STIR bundle (required for US numbers)          |
| `/v1/twilio-setup/{id}/bundle/cnam`        | POST   | Submit or update CNAM bundle (required for US + CA numbers)  |
| `/v1/twilio-setup/{id}/bundle`             | GET    | List all bundles for a setup with status and failure details |

**Country requirements:**

| Country | Required Bundles   |
| ------- | ------------------ |
| US      | SHAKEN/STIR + CNAM |
| CA      | CNAM only          |

**What changed:**

* **Setup creation no longer requires `cnam_display_name`** - CNAM is now submitted as a separate bundle after customer profile approval.
* **Bundles are submitted individually** - SHAKEN/STIR and CNAM are created via dedicated POST endpoints rather than being auto-advanced after customer profile approval.
* **Phone number provisioning gates on country-specific bundles** - A US number requires both SHAKEN/STIR and CNAM approved; a CA number requires only CNAM.
* **Bundle status includes failure details** - Each bundle carries human-readable failure strings from the latest rejection or noncompliant evaluation.
* **Pre-flight evaluations return actionable errors** - Bundle submission endpoints return `422` with structured failure details when Twilio's evaluation fails, so you can fix issues before waiting for async review.
* **CNAM display name is updatable** - POST to the CNAM bundle endpoint with a changed `display_name` updates the underlying data. Returns `409` if unchanged or if the bundle is under active review.
* **Use case creation gating simplified** - Only requires customer profile approval, not all bundles.
* **Phone number search gating simplified** - Only requires customer profile approval.
* **Country parameter restricted** - Phone number search and provisioning now accept only `US` or `CA`.

**Removed endpoints:**

| Endpoint                           | Replacement                                              |
| ---------------------------------- | -------------------------------------------------------- |
| `PATCH /v1/twilio-setup/{id}/cnam` | `POST /v1/twilio-setup/{id}/bundle/cnam`                 |
| `POST /v1/twilio-setup/{id}/retry` | Submit individual bundles via POST `/bundle/{kind}`      |
| `GET /v1/twilio-setup/{id}/event`  | `GET /v1/twilio-setup/{id}/bundle` for per-bundle status |

**Removed response fields:**

* Setup response no longer includes `shaken_stir`, `cnam`, `cnam_display_name`, `ready`, or `last_error`. Customer profile status is returned directly; bundle status is available via the bundles list endpoint.
* Phone number response no longer includes `shaken_stir_assignment_sid` or `cnam_assignment_sid`.

**Updated setup response:**

* `customer_profile` now includes `last_failure` (array of strings or null) for webhook rejection details.

</details>

<details>

<summary>v0.9.80 - Dashboard Embed BFF Proxy Auth (May 2026)</summary>

#### Platform API: Dashboard Embed BFF Proxy Auth

The `DashboardEmbed` component now supports cookie-based authentication through a backend-for-frontend (BFF) proxy. The `apiToken` prop is now optional - when omitted, the component uses same-origin credentials instead of a Bearer token.

This is useful when your application proxies dashboard API requests through its own backend, using session cookies or other server-side authentication rather than passing API tokens to the browser.

**What changed:**

* **`apiToken` is now optional** - Previously required. When omitted, the component sends requests with same-origin credentials and no Authorization header, allowing a BFF proxy to handle authentication server-side.
* **Cookie-based auth support** - Requests now include same-origin credentials, enabling session cookie authentication through a proxy.
* **No breaking changes** - Existing integrations that pass `apiToken` continue to work exactly as before.

</details>

<details>

<summary>v0.9.79 - Dashboard Workspace Scoping and Slug Uniqueness (May 2026)</summary>

#### Platform API: Dashboard Workspace Scoping

Dashboard panel queries now require a `:ws_id` parameter binding to enforce workspace-level data isolation. This prevents panels from accidentally querying data across workspaces.

**What changed:**

* **Write-time validation** - Creating or updating a dashboard with panel queries that do not reference `:ws_id` now returns `422 Unprocessable Entity` with a message identifying the offending panel. Previously, these queries were accepted and could return unscoped data at execution time.
* **Execute-time enforcement** - When executing a dashboard, any panel query missing the `:ws_id` binding returns an error result for that panel instead of running the query. Other panels in the same dashboard still execute normally.
* **Concurrent panel execution** - Panel queries now execute concurrently with bounded parallelism, improving dashboard load times when multiple panels are present.

#### Platform API: Dashboard Slug Uniqueness

Dashboard slugs are now enforced as unique within a workspace. Attempting to create or update a dashboard with a slug that is already in use by another active dashboard in the same workspace returns `409 Conflict`.

Previously, duplicate slugs could cause lookup failures when dashboards were retrieved by slug. Soft-deleted dashboards do not participate in the uniqueness constraint.

**What changed:**

* **Create** - Returns `409 Conflict` if the slug is already taken in the workspace.
* **Update** - Returns `409 Conflict` if the new slug collides with another active dashboard in the workspace.

</details>

<details>

<summary>v0.9.77 - Custom Phrase Escape Hatch and Adaptive Filler Timing (April 30, 2026)</summary>

#### Platform API: Custom Phrase for Progress Hints

Progress hints on tool call specs now support an optional `custom_phrase` field - an operator-authored filler utterance spoken while a slow tool executes. This is a narrow escape hatch for demo-critical tools where the standard generated fillers do not adequately cover the wait.

The field is gated to prevent misuse:

* **Minimum latency** - `expected_latency_ms` must be 4000 or higher. Custom phrasing is reserved for tools with demonstrably long waits.
* **Class required** - `progress_class` must be set. The class acts as the fallback when the custom phrase cannot be used (for example, when hints from multiple tools are merged).
* **Word limit** - The phrase must be 30 words or fewer to keep fillers concise.

When all gates are satisfied, the custom phrase is spoken on the first filler tick. Subsequent ticks fall back to the standard class-based templates. When progress hints from multiple concurrent tools are merged, custom phrases are dropped because they are tool-specific and cannot be meaningfully combined.

**What changed:**

* **`custom_phrase` field** - New optional string field on the progress hint object. Maximum 500 characters, at most 30 words. Validated on submission; requests that fail the gate conditions receive descriptive error messages.
* **Merge behavior** - When the platform merges progress hints across concurrent tools, custom phrases are excluded from the merged result. The merged hint uses the standard progress class templates.

#### Platform API: Adaptive Filler Timing

The voice agent now adapts when the first filler utterance fires based on the tool's declared expected latency. For tools that declare an expected wait, the first filler fires earlier in proportion to the expected duration, so callers hear acknowledgment sooner during moderate waits (4-9 seconds) rather than waiting for the default delay. Tools without a declared latency keep the existing timing.

**What changed:**

* **Earlier first filler** - Tools with a declared `expected_latency_ms` trigger their first filler utterance earlier, scaled to the expected wait. This prevents the scenario where a tool finishes before the caller hears any acknowledgment.
* **No change for unconfigured tools** - Tools without `expected_latency_ms` retain the existing default filler timing.

</details>

<details>

<summary>v0.9.76 - World Model FHIR Name Preservation (April 29, 2026)</summary>

#### Platform API: FHIR Name Data Preserved in World Events

World model events now preserve structured FHIR `name` data (the HumanName format used by EHR systems) in their full original shape. Previously, the structured name could be dropped during event processing, which meant downstream queries that accessed name components - given name, family name, prefixes, suffixes - would return empty results.

The platform still extracts a flat display name for quick lookups. When a display name is not explicitly provided, the platform derives one from the structured name by combining the first given name and family name. FHIR HumanName lists, plain string names, and explicit display names are all handled correctly.

**What changed:**

* **Structured name retention** - FHIR HumanName arrays are no longer removed from event data during processing. Queries that read name components (given names, family name, prefixes) from event data now return complete results.
* **Display name derivation** - When no explicit display name is set, the platform derives one from the first entry in a FHIR HumanName list or from a plain string name. This ensures display name is populated consistently regardless of the source data format.

</details>

<details>

<summary>v0.9.74 - Per-Workspace Audit Log Isolation (April 25, 2026)</summary>

#### Platform API: Audit Log Workspace Scoping

Audit log entries are now scoped to individual workspaces. Previously, certain auto-provisioning events were logged once without a workspace association. Now each workspace receives its own audit entry, so workspace-level audit queries return complete results without cross-workspace leakage.

**What changed:**

* **Per-workspace audit entries** - Operations that span multiple workspaces (such as user auto-provisioning) now emit a separate audit entry for each workspace involved. Each entry includes the workspace identifier and the user's role in that workspace.
* **Read-only isolation** - Audit log access is restricted to read-only queries scoped to the caller's workspace. Services cannot modify or delete audit records, and queries only return entries belonging to the requesting workspace.
* **Service role groups** - Service credentials are now assigned to role groups rather than referenced individually in access policies. Rotating a service credential no longer requires policy changes - the new credential is granted membership in the same role group.

</details>

<details>

<summary>v0.9.73 - Client Credentials OAuth2 &#x26; Secret Hashing (April 24, 2026)</summary>

#### Platform API: OAuth2 Client Credentials Grant

The identity service now fully supports the OAuth2 `client_credentials` grant type with secure secret management. Client secrets are hashed with a modern key derivation function (PBKDF2-SHA256, 210,000 iterations) before storage, replacing the previous approach.

**What changed:**

* **Credential creation** - Creating a `client_credentials` or `api_key` credential now generates a secret automatically and returns it in the response. The plaintext secret is shown once and cannot be retrieved again. The response includes both the credential metadata and the secret.
* **Secret rotation** - Rotating a `client_credentials` credential secret now uses the same secure hashing. API key rotation continues to work as before.
* **Token exchange** - The `client_credentials` token grant now verifies secrets against the new hash format. Verification runs off the main request thread to avoid blocking on the CPU-intensive hashing operation.
* **Backward compatibility** - Credentials created before this change (using the legacy hash format) continue to work. The system detects the hash format automatically and verifies accordingly. No migration is required for existing credentials.

</details>

<details>

<summary>v0.9.70 - Call Completion Reason Normalization &#x26; Validation (April 21, 2026)</summary>

#### Platform API: Standardized Call Completion Reasons

The `completion_reason` field on call intelligence records is now validated and normalized before storage. A canonical set of completion reasons is enforced across all producers (voice agent engine and API), and a database-level constraint prevents unknown values from entering the system.

The full set of valid completion reasons is: `completed`, `abandoned`, `escalated`, `transferred`, `timeout`, `error`, `voicemail`, `no_answer`, `caller_hangup`, `forwarded`, `terminal_state`, `warm_transfer_completed`, `no_inbound_audio`, `cancelled`.

**New value**: `cancelled` has been added for calls that are explicitly cancelled before completion.

**Alias mapping**: Legacy or provider-specific reason strings are automatically mapped to canonical values. For example, `max_duration` and `idle_timeout` both normalize to `timeout`. Unrecognized values are coerced to `null` and logged as warnings rather than causing write failures.

This change is backward-compatible. Existing calls with valid completion reasons are unaffected. Calls with previously stored non-canonical values will not be retroactively modified, but all new writes are validated.

</details>

<details>

<summary>v0.9.68 - Platform Health Monitoring &#x26; Entity Narrative Briefs (April 19, 2026)</summary>

#### Platform API: Platform Health Monitoring

Two new observability endpoints give operators real-time visibility into data pipeline health and end-to-end processing latency without querying the analytics warehouse directly.

**Connector Health** (`GET /v1/{workspace_id}/sensorium/connector-health`) returns per-source event health for every data source writing into the workspace. Each source entry includes events ingested in the last hour and last 24 hours, mean events per minute, the last ingested timestamp, and a freshness category:

* `fresh` - last event less than 5 minutes ago
* `stale` - last event 5 to 60 minutes ago
* `quiet` - last event more than 60 minutes ago
* `never` - no events in the last 24 hours

**Loop Latency** (`GET /v1/{workspace_id}/sensorium/loop-latency`) measures the end-to-end time from data ingestion (a world model event landing) to agent action (the agent acting on that data). Returns an overall median latency, total pair count, and an hourly sparkline (up to 168 hours / 7 days) with per-hour pair counts and median latency. The `window_hours` query parameter controls the lookback window (1-168, default 24).

Both endpoints are workspace-scoped and rate-limited under the standard read policy.

#### Platform API: Entity Narrative Briefs

New AI-powered narrative brief generation for patient entities. The platform reads the last 90 days of world model events for a patient, synthesizes them into a structured narrative summary with a versioned prompt, and returns both Markdown and structured JSON representations.

* `POST /v1/{workspace_id}/entities/{entity_id}/brief` - generates a fresh brief on demand. The brief is persisted as a world model event, cached for fast retrieval, and includes evidence pointers (the event IDs that informed the narrative), a confidence score (0.0-1.0), the event count that went into the brief, and a prompt version identifier for traceability. Returns 404 if the entity is not a patient or does not exist in the workspace.
* `GET /v1/{workspace_id}/entities/{entity_id}/brief` - returns the latest cached brief. Cache misses fall through to the event store and repopulate the cache transparently. When no brief has ever been generated, the response returns a consistent empty shape (null `event_id` and content fields) rather than a 404, so the UI can render a "not yet generated" state without error handling.

Briefs are scoped to patient entities in v1. Cohort, territory, and workspace-level briefs are planned for a future release.

</details>

<details>

<summary>v0.9.67 - WhatsApp Voice Notes, Desktop Sessions &#x26; Simulation Bridge (April 18, 2026)</summary>

#### Platform API: WhatsApp Voice Note Conversations

The voice agent now handles WhatsApp voice notes as a full conversation channel. Patients send a voice note, the platform transcribes it, runs the same reasoning engine pipeline (context graphs, tools, safety boundaries, world model), synthesizes the agent's spoken reply, and returns it as a voice note in the same WhatsApp thread.

Session continuity is keyed on the patient's phone number - no session ID management required. Switching between text and voice notes in the same thread preserves full conversation context. Concurrent voice notes from the same patient are serialized (409 if a turn is already in progress) to prevent race conditions in the reasoning engine.

The voice turn endpoint accepts any common audio format and returns OGG Opus. Audio payloads are capped at 20 MB with enforcement at both the API gateway and engine tiers.

#### Platform API: Desktop Session Proxy

New desktop session endpoints let API consumers and the Developer Console drive RDP desktop sessions without direct cloud infrastructure access. The platform proxies session lifecycle operations (create, screenshot, action, status, disconnect) to the desktop sidecar, handling authentication, workspace isolation, and audit logging transparently.

Sessions are workspace-scoped and limited to one active session at a time per sidecar. A distributed lock prevents multi-pod race conditions on session creation. All operations require admin-level permissions and emit PHI-access audit events.

#### Platform API: Simulation Bridge

New `POST /v1/{workspace_id}/simulations/bridge` endpoint that turns a natural language test objective into a set of autonomous test scenarios. The platform generates personas with distinct temperaments and conversation strategies from the objective, then runs each scenario end-to-end as an autonomous caller against the configured service.

Each bridge run creates a tracked coverage run with per-scenario sessions, so results are visible through the existing coverage graph and scoring APIs. Runs are capped at 3 concurrent per workspace (429 if exceeded) with a 30-minute overall timeout.

This is the REST API equivalent of `forge simulation bridge` - same scenario generation and autonomous execution, accessible without the CLI.

#### Platform API: Phone Number Use Case Management

Phone number provisioning now supports use case management. Each number can be assigned to specific use cases (A2P messaging, outbound voice, etc.), with the platform enforcing capability requirements per use case type. Numbers, use cases, and their assignments are managed through dedicated CRUD endpoints under channel management.

Available number search filters by country, area code (NANP countries only), and required capabilities. The provisioning flow validates that the business profile and trust products are fully approved before allowing number purchases.

#### Platform API: Trigger Input Override

The manual fire endpoint (`POST /v1/{workspace_id}/triggers/{trigger_id}/fire`) now accepts an optional JSON body with an `input` field. Values from the override are merged into the trigger's stored `input_template` at fire time, so webhook consumers and manual testers can supply dynamic values (phone numbers, entity IDs, custom parameters) without modifying the trigger configuration.

This is the mechanism webhook destinations use to pass extracted payload fields through to the trigger's action.

</details>

<details>

<summary>v0.9.66 - Progress Hints and Expanded EHR Projections (April 17, 2026)</summary>

#### Platform API: Tool-Wait Progress Hints

Agent engineers can now shape how the voice agent narrates tool waits on a per-state and per-tool basis. A new `ProgressHint` primitive describes the *shape* of a wait — a semantic `progress_class` (`lookup`, `write`, `external_call`, `compute`, `multi_step`), an `expected_latency_ms`, and a `mode` (`auto`, `silent`, `backchannel`, `verbal`) — instead of the previous per-state filler phrase lists. The engine generates the actual utterance at runtime from the hint plus tool semantics, turn emotion, and conversation context.

* **New field** `progress` (optional `ProgressHint`) on `ToolCallSpec` in `action_tool_call_specs` and `exit_condition_tool_call_specs`.
* **New field** `progress` (optional `ProgressHint`) on `ChannelOverride` (state-level per-channel override).
* **Resolution order**: per-tool hint > per-state channel override > engine default, merged field-wise so a tool only overrides the fields that actually differ.
* **Attempt-aware retries**: when a tool retries, the engine emits deterministic templated language keyed on `progress_class` — acknowledgement on attempt 1, brief apology on attempt 2, "still working" on attempt 3 — with no LLM call in the retry latency path.

{% hint style="warning" %}
**Breaking change (state-level only).** The fields `suppress_filler` and `filler_vocabulary` on state `ChannelOverride` and `ToolCallSpec` have been removed. Agent configs that set those fields must migrate to `progress`. Service-level `voice_config.filler_style`, `voice_config.filler_vocabulary`, and `voice_config.backchannel_delay_ms` are unchanged and continue to apply as defaults when no per-state/per-tool hint is set.
{% endhint %}

#### Platform API: Expanded EHR FHIR Projections

Charm-integrated workspaces now project every Charm entity into a queryable FHIR resource, closing gaps that forced scheduling and eligibility tools to fall back to partial data:

* **Practitioner**: members → FHIR Practitioner with NPI, role, licensed states, specialty, and active flag. Resolves previously dead `Practitioner/charm-{id}` references in `Appointment.participant`.
* **Location**: facilities → FHIR Location with address, timezone, geo position, phone, and hours of operation. Test/sentinel facilities project as `status=inactive`. Nearest-facility scheduling lookups now return real rows.
* **Coverage**: per-patient insurance → FHIR Coverage with subscriber DOB and name, effective/term period, relationship, payer reference, and a deterministic fingerprint for dedup. Unblocks payer eligibility checks.
* **Slot**: bookable Slots projected with `start`, `end`, `status`, and a schedule reference. Exposed via a new Slot view endpoint that resolves the provider display name from the Slot's schedule reference, so scheduling surfaces no longer need to join against the raw Schedule resource.
* **Extended Patient**: language, marital status, race/ethnicity (US Core extensions), emergency contact, deceased status, and an assigned-facility Location reference.
* **Extended Appointment**: appointment mode → serviceType, appointment timezone, member degree, member specialization, resource type, payer extensions, and cancelationReason on cancelled status.

</details>

<details>

<summary>v0.9.65 - Data-MCP Backend URL Override (April 17, 2026)</summary>

#### Data-MCP: Caller-Configurable Backend URL

Data-MCP callers can now choose which backend the MCP server proxies to on a per-request basis, instead of being pinned to the server's default deployment. A new `X-Amigo-Api-Url` request header specifies the target backend, validated against an allowlist on every request. Calls without the header continue to use the deployment's configured default, so existing integrations keep working unchanged.

The header value must match a host on the server's `AMIGO_API_URL_ALLOWLIST`. Unknown hosts return `400`. The allowlist always includes the deployment's configured default in addition to any hosts the operator explicitly adds, so the default backend never needs to be listed separately.

This unblocks multi-region clients and split-brain test setups that want one Data-MCP server to route queries to the appropriate regional Platform API without running multiple MCP servers. Token caches are now keyed by `(organization, key_id, backend_url)` so credentials issued for one backend cannot be reused against another by accident.

#### Platform API: Release Version Set Integrity Constraint

The `platform.services` table now enforces at the database layer that every service declares a release version set. A `CHECK` constraint rejects inserts or updates where `config.version_sets` is missing or empty, closing a gap where migrations, fixtures, or direct SQL could leave a service without a resolvable release tag and cause the voice agent runtime to `500` when resolving tool versions.

No changes to the public API surface. Existing services with empty version sets were backfilled to `{"release": {}}`, which resolves to "use latest agent and context graph version," before the constraint was applied.

</details>

<details>

<summary>v0.9.64 - Epidemiology REST Endpoints (April 17, 2026)</summary>

#### Platform API: Epidemiology REST Endpoints

Six new workspace-scoped endpoints under `/v1/{workspace_id}/epidemiology/` expose the population-health analytics tables shipped in v0.9.63 directly over HTTP. Same statistical methodology (Wilson CI, Byar CI, Charlson CCI, Pearson χ² / PMI / Jaccard / lift); no SQL or Delta access needed to query them.

* `GET /v1/{workspace_id}/epidemiology/prevalence` - active-condition prevalence with Wilson 95% CI. Filter by `code`, `code_system` (`ICD10`, `SNOMED`, `ICD9`, `OTHER`), `age_bucket` (`0-17`, `18-34`, `35-49`, `50-64`, `65-79`, `80+`), `gender`. Toggle `min_support_only=true` to hide small-cell cohorts (observed < 5).
* `GET /v1/{workspace_id}/epidemiology/trends` - monthly incidence time series for a specific code. Requires `code`; accepts `start_month` / `end_month` in `YYYY-MM` format plus the same cohort filters.
* `GET /v1/{workspace_id}/epidemiology/cohort-drivers` - Standardized Prevalence Ratio (SPR = observed / expected) per `(age_bucket, gender)` cohort with Byar 95% CI. Requires `code`. `SPR > 1` means the cohort over-indexes for that code. Defaults to `min_support_only=true`.
* `GET /v1/{workspace_id}/epidemiology/cooccurrence` - co-occurring active-condition code pairs with Jaccard, lift, PMI, and Pearson χ². Filter by `code` to constrain one side of the pair. Sort by `lift` (default), `pmi`, `chi_square`, or `jaccard`.
* `GET /v1/{workspace_id}/epidemiology/patient-burden` - per-patient condition features including Charlson Comorbidity Index (Quan 2005 ICD-10) and age-adjusted CCI (Charlson 1994). Filter by `min_charlson`, `age_min` / `age_max`, or `gender`. Sort by any numeric feature column (default `age_adjusted_charlson_index`).
* `GET /v1/{workspace_id}/epidemiology/patient-burden/{patient_entity_id}` - full burden row for a single patient plus per-category Charlson flags across all 17 Quan (2005) ICD-10 categories (myocardial infarction, CHF, peripheral vascular, cerebrovascular, dementia, chronic pulmonary, rheumatic, peptic ulcer, mild liver, diabetes +/- complications, hemiplegia, renal, malignancy, moderate/severe liver, metastatic, HIV/AIDS).

All endpoints enforce workspace isolation at the SQL-parameter level and rate-limit under the standard `read_rate_limit` policy. Server returns `503` when the analytics warehouse is not configured for the deployment, `502` on a downstream query failure, and `404` for an unknown patient on the detail endpoint.

The six analytics tables are also advertised to the Insights Agent's `sql_query` tool and the Data-MCP server, so agents and MCP clients can compose the epidemiology outputs with any other world-model query in the same call.

</details>

<details>

<summary>v0.9.63 - Epidemiology Analytics &#x26; Inbound Webhook URL Canonicalization (April 16, 2026)</summary>

#### Platform API: Epidemiology Analytics Tables

New population-health analytics pipeline producing six layered output tables from the world model's L1 clinical projections. Each table is explainable, cite-able, and suitable for feeding into risk stratification models without a black-box step in between.

* **`patient_condition_fact`** - exploded patient × condition grain with onset date, age at onset, code system normalized to `ICD10` / `SNOMED` / `ICD9`, and coarse age cohort buckets. The base table every downstream measure joins against.
* **`disease_trends_monthly`** - incident case counts per code per month per cohort. Use for outbreak detection, seasonality curves, and trend charts.
* **`disease_prevalence_current`** - active-condition prevalence with Wilson 95% confidence interval. Small-sample-robust (Wilson 1927, Brown-Cai-DasGupta 2001): the interval stays in `[0, 1]` even for tiny cohorts.
* **`disease_cohort_drivers`** - Standardized Prevalence Ratio (SPR) per `(code, cohort)` with Byar 95% confidence interval (Breslow & Day 1987). The canonical epidemiology measure for "does this cohort over- or under-index for this condition?" - an interpretable alternative to feature importance from a black-box model.
* **`patient_condition_burden`** - per-patient Charlson Comorbidity Index using Quan (2005) ICD-10 mappings, plus age-adjusted CCI per Charlson (1994). Feature-ready input for risk stratification; MLflow / AutoML / SQL rules consume it downstream.
* **`condition_cooccurrence_pairs`** - co-occurring code pairs with Jaccard similarity, lift, pointwise mutual information, expected count, and chi-square. Drives correlation-matrix visualizations and comorbidity pattern mining.

All association and ratio measures emit a `min_support_met` flag at the standard small-cell suppression threshold (observed count ≥ 5); UIs and downstream queries should filter on it to avoid reporting unstable rates. Tables are scoped per workspace and land in `analytics_{env}.public.*`.

Methodology is published and cited inline: Wilson (1927), Breslow & Day (1987), Charlson et al. (1987, 1994), Quan et al. (2005).

#### Platform API: Inbound Webhook URL Canonicalization

The inbound webhook receive URL moved from `/hooks/{workspace_id}/{destination_id}` to `/v1/{workspace_id}/hooks/{destination_id}` to match the versioned, workspace-scoped path convention used across the rest of Platform API. The URL is server-generated and returned on webhook destination create and get responses, so newly created destinations return the new URL automatically. Integrations that persisted an old URL should re-fetch the destination record and update their external webhook configuration.

</details>

<details>

<summary>v0.9.62 - Per-Key Entity Enrichment (April 16, 2026)</summary>

#### Platform API: Per-Key Entity Enrichment

New first-class API for extending world model entities with custom, source-attributed values. Previously, extra attributes on an entity were collapsed into an opaque blob with no source, confidence, or audit trail. Enrichment values are now per-key events with the same provenance, supersedes chain, and confidence resolution as every other world model event.

Each write validates against a workspace-scoped registry of allowed keys. Admins declare the keys their workspace tracks (entity type, value type, optional enum values, minimum confidence, PII flag) before any source can write them. Unknown keys are rejected at write time; enum violations, wrong value types, and sub-floor confidence values return `400` with a specific message. The same write API handles manual entries, connector backfills, forms, and agent-extracted values, differentiated only by `source` and `confidence`.

Reads return the current winner per `(entity, key)`, selected by confidence class (authoritative > human-approved > AI-verified > uncertain) with recency as the tiebreaker. A history endpoint walks the full supersedes chain so any value can be traced back to the source event, timestamp, and confidence that produced it.

New endpoints:

* `PUT /v1/{workspace_id}/world/entities/{entity_id}/enrichment/{key}` - write a single value with source, confidence, and optional `effective_at`. Returns the event ID and ingested timestamp.
* `GET /v1/{workspace_id}/world/entities/{entity_id}/enrichment` - list current winners for an entity with full provenance (value, value\_type, confidence, source, source\_system, effective\_at, event\_id).
* `GET /v1/{workspace_id}/world/entities/{entity_id}/enrichment/{key}/history` - supersedes chain for one key, newest first, with `is_current` and `supersedes` pointers.
* `POST /v1/{workspace_id}/world/enrichment-keys` - register a key (`entity_type`, `key`, `value_type`, optional `allowed_values`, `min_confidence`, `is_pii`).
* `GET /v1/{workspace_id}/world/enrichment-keys` - list registered keys, optionally filtered by `entity_type`.
* `PATCH /v1/{workspace_id}/world/enrichment-keys/{key_id}` - update `allowed_values`, `source_hint`, `description`, `min_confidence`, or `is_pii`. `key` and `value_type` are immutable; create a new key and migrate if those need to change.
* `DELETE /v1/{workspace_id}/world/enrichment-keys/{key_id}` - unregister a key. Historical events remain in the append-only log; future writes against that key return `400`.

Value types: `string`, `number`, `boolean`, `date`, `enum`, `json`. Writes require the `admin` role or above (`Workspace:Update`). Reads are rate-limited.

Agent-extracted values that do not match a registered key are silently dropped, so they do not pollute the event stream or win over governed values. This gives admins full control over which attributes are tracked without blocking agent-side capture.

See the [Data & World Model](https://docs.amigo.ai/developer-guide/platform-api/data-world-model#entity-enrichment) developer guide for the full request contract and the [World Model](https://docs.amigo.ai/docs/data/world-model#entity-enrichment) conceptual overview.

</details>

<details>

<summary>v0.9.61 - Customer Data Intake (April 16, 2026)</summary>

#### Platform API: Customer Data Intake

New direct upload channel for approved customer integrations to stream PHI documents into a workspace landing zone. Complements the connector system for cases connectors cannot cover: bulk historical loads, partner document drops, and upstream systems that prefer to push on their own cadence.

* `POST /v1/{workspace_id}/intake/files` - streams a single file body into workspace-isolated storage. Returns upload `id`, `volume_path`, server-computed `sha256`, `size_bytes`, and `scan_status`.
* Two-layer authentication: the standard platform-api key or JWT plus a per-customer HMAC signature over a canonical request string (`sha256:content_type:customer_slug:timestamp`). Timestamp freshness window prevents replay.
* Metadata travels in `x-amigo-intake-*` headers so large payloads stream without JSON buffering. Server recomputes SHA-256 during the stream; a mismatch deletes the partial object and returns `422`.
* Every accepted upload writes an append-only audit row (workspace, customer slug, storage path, filename, content type, hash, size, uploader actor, received-at, scan status) and emits a signal event for downstream pipelines to consume.
* New permission scopes: `intake:write` (submit uploads) and `intake:read` (read the audit log). Neither is included in the default `member` role.
* TLS-only transport and workspace-isolated storage meet HITRUST and HIPAA requirements for PHI ingestion.

Malware scanning, built-in parsing for the full format list (PDF, CCDA, FHIR bundles, CSVs), and a self-service portal for rotating customer HMAC secrets are on the near-term roadmap and will ship without changes to the upload contract.

See the [Data Intake](https://docs.amigo.ai/developer-guide/platform-api/intake) developer guide for the full request contract and the [Customer Data Intake](https://docs.amigo.ai/docs/data/customer-data-intake) conceptual overview.

</details>

<details>

<summary>v0.9.60 - Warm Hand-Off, Agent Trace, Call Analysis &#x26; Webhook Filtering (April 15, 2026)</summary>

#### Platform API: Warm Hand-Off

New warm transfer flow for operator escalation. When a patient asks to speak with a human, the agent dials the call center into the existing conference, puts the patient on hold, briefs the operator with full conversation context, then connects them. The patient never repeats themselves.

Three conference phases driven by tool calls:

* **Normal** - Agent and patient in conversation
* **Briefing** - Agent and operator connected, patient on hold. The operator receives a real-time AI briefing with situation summary, patient context, and recommended actions. The operator can ask the agent questions or instruct it to resume with the patient.
* **Connected** - Operator and patient connected, agent removed from the call

Barge-in is automatically disabled during the briefing phase to prevent the operator's speech from interrupting the agent's context transfer. The agent is stopped and removed from the conference once the operator takes over.

#### Platform API: Agent Trace APIs

Two new debugging endpoints for agent conversation analysis:

* `GET /calls/{id}/trace` - Per-turn execution log with actions, tools, state transitions, emotions, inner thoughts, and latency. Includes barge-in details (interrupted text, discarded utterances).
* `GET /calls/{id}/prompts` - Full LLM prompts (system prompt, conversation history, tool definitions) for each turn.

Equivalent endpoints are available for simulation sessions. Per-turn prompts are emitted as events for analytics queries.

#### Platform API: Call Trace Analysis

New `GET /calls/{id}/trace-analysis` endpoint providing audio-native intelligence analysis of completed calls. Returns emotional arc, key decision moments with causal attribution, counterfactual analysis, coaching recommendations, and interaction dynamics. Analysis is computed asynchronously after call completion, so the endpoint returns `status: pending` while processing and `status: unavailable` if analysis is not configured.

#### Platform API: Decision Trace

Agent reasoning is now classified with structured signal and effect types on each conversation turn. Every turn carries a `signal_kind` (what triggered the agent's response, such as caller speech, tool result, or state transition) and `effect_kind` (what the agent did: spoke, invoked a tool, navigated, or escalated). Decision events are emitted at call end for analytics, with three new metrics: turns per call, tool invocation rate, and state transition rate.

#### Platform API: Webhook Event Type Validation

Inbound webhook destinations now enforce `accepted_event_types` filtering. When a destination defines an event type whitelist, the platform extracts the event type from the signed request body (checking `type`, `event_type`, `event`, and `action` fields in priority order) and rejects payloads that don't match with a 422 response. Event type extraction uses only the signed body; unsigned headers are excluded to prevent spoofing via replay attacks with modified headers.

#### Platform API: Webhook Trigger Retry

Webhook destinations now support configurable retry on transient trigger fire failures via the `retry_attempts` field. When a connection timeout or network error occurs during trigger execution, the platform retries with exponential backoff (0.5s base delay). Only transient connection-level errors are retried; application errors that may have persisted data are not retried, preventing duplicate events from non-idempotent operations.

#### Platform API: Web Chat Audio Input

New `POST /step-audio` endpoint on text sessions allows web chat clients to send audio recordings instead of text. Audio is transcribed server-side and fed through the same reasoning pipeline as text input, so users can mix voice and text messages in the same session.

#### Platform API: Minimum TTS Speed

New `min_tts_speed` field on voice configuration sets a floor for the empathy engine's speed modulation. The empathy system reduces TTS speed after detecting caller distress, but without a floor it could make the agent sound unnaturally slow. The minimum speed prevents over-modulation while preserving the warmth response.

#### Platform API: Insurance Payer Search

New `world_payer_search` tool for mid-call insurance payer lookup. Queries FHIR payer resources synced from the EHR with fuzzy matching (alias expansion for common abbreviations like "Blue Cross" to "BCBS", token overlap, and sequence matching). The agent can verify a patient's stated insurance carrier against the practice's accepted payer list during the call.

#### Platform API: Simulation Tags

Simulation runs and sessions now accept an optional `tags` parameter, an array of strings for labeling and filtering. Tags are persisted and returned through create and list endpoints.

#### Platform API: Surface Re-Delivery

Surfaces (agent-generated forms) can now be re-delivered after initial delivery. Previously, the deliver endpoint rejected surfaces that had already been sent. Now only terminal statuses (completed, expired, archived) and pending review block re-delivery. This allows resending form links when a patient loses the original.

#### Platform API: Context Graph Tool Validation

Pushing a context graph now validates that all referenced built-in integration tool IDs exist. Invalid tool references are rejected at push time rather than failing at runtime during calls.

</details>

<details>

<summary>v0.9.58 - Voice Pipeline, Semantic Search &#x26; Connector Architecture (April 13, 2026)</summary>

#### Platform API: Barge-In Improvements

Three changes to voice pipeline barge-in detection:

1. **Recognized speech required** - Barge-in now always requires actual recognized words from the speech-to-text engine. Previously, sustained background noise classified as speech activity for 2 seconds could trigger an interrupt with zero recognized words. This eliminates false interruptions from coughs, breathing, HVAC, and background conversation.
2. **Short phrase support** - The minimum speech duration for barge-in has been lowered significantly. Short responses like "yes," "no," and "okay" (200-400ms) can now trigger barge-in. Previously these finished before reaching the duration threshold and were only processed as end-of-turn events.
3. **Faster end-of-turn interrupt** - When the speech engine detects end-of-turn with a complete transcript, the system now interrupts the agent's audio immediately from the recognition listener rather than waiting for the transcript processing queue. This eliminates 50-200ms of latency on short-phrase interrupts.

#### Platform API: Dynamic Call Forwarding

The `forward_call` tool now accepts an optional `phone_number` parameter (E.164 format) for ad-hoc call forwarding to any number. Previously, call forwarding was limited to pre-configured numbers from EHR location data or static workspace settings. The resolution priority is: explicit phone number (if provided) > location-based lookup (if location\_id provided) > static workspace fallback. Workspaces only need `forward_call_enabled` in their voice configuration to use the tool - no pre-configured forwarding number is required.

#### Platform API: Semantic Entity Search

Entity queries now support semantic similarity search via vector embeddings. The `?semantic=<query>` parameter on the generic query endpoint performs cosine similarity against entity embeddings, which encode the full projected state (demographics, clinical data, relationships). This finds entities by meaning without requiring exact field matches, so searching "elderly patient with diabetes" returns relevant matches even when the exact phrasing differs.

A `?tags=<tag1,tag2>` parameter supports array overlap filtering on entity tags. Both parameters compose with existing filters (entity\_type, source, text search, confidence thresholds).

#### Platform API: Memory Analytics Sample Facts

The memory analytics endpoint now returns a `sample_facts` field on each dimension in the response. Sample facts are representative examples drawn from distinct entities using a windowed sampling approach that guarantees per-dimension coverage. Text is truncated for cache safety.

#### Platform API: Connector Configuration in Workspace Settings

Connector definitions are now managed through workspace settings with typed configuration models. Each connector definition includes a sync strategy field with strict validation, preventing a class of misconfiguration bugs (incorrect strategy values, missing fields). The data source response now includes an `is_stale` field computed from the connector's last successful sync and its configured cadence. Configuration changes are picked up by the connector runner's config refresh loop.

#### Platform API: Asynchronous Entity Projection

Entity state projection is now decoupled from the event write path. Writing an event no longer blocks on recomputing the entity's full state. Events insert immediately, and a background projection process recomputes entity state asynchronously. This reduces write latency for high-throughput scenarios (bulk imports, concurrent agent sessions) while maintaining the same eventual consistency guarantee: entity state reflects all events within seconds of the write.

#### Platform API: Event-Driven Review Queue

The review queue now processes flagged events immediately via event-driven notifications instead of polling on a fixed interval. When the world model writer commits a low-confidence event, it publishes a notification that the review loop picks up and processes within seconds. A periodic safety-net poll (every 5 minutes) catches any events missed by the real-time path. Stale item expiration runs on its own cadence, separate from the review evaluation cycle.

#### Classic API: Tool Repo Files Endpoint Method Change

The tool repository file retrieval endpoint has changed from `GET` to `POST` with a JSON request body. The `ref` and `file_paths` parameters that were previously sent as form/query parameters are now sent in the JSON body. The operation ID (`get-tool-repo-files`) remains unchanged.

#### Classic API: Prompt Log Error Tracking

Prompt logs now include an `errored` field that indicates whether the LLM interaction produced an error. This allows filtering prompt logs to identify failed interactions without downloading and inspecting the full log content.

#### Classic API: Dynamic Behavior Webhook Fix

The `dynamic_behavior_set.version_changed` webhook event now sends the correct body payload. Previously, the webhook body did not match the documented schema when a dynamic behavior set version was created or updated.

#### Platform API: Computer Use RDP Hardening

The Computer Use execution tier now supports production-grade Windows RDP connections with enterprise network configurations:

* **RD Gateway traversal** for reaching desktops behind corporate firewalls
* **RemoteApp mode** for single-application access (launching one application rather than a full desktop session)
* **Terminal farm load balancing** for connecting to pools of application servers
* **Connection health monitoring** between automation rounds with automatic reconnect on transient failures
* **Desktop integration configuration** with 8 new fields for RDP gateway, RemoteApp, load balancing, and security settings

#### Platform API: Computer Use Agent Intelligence

The Computer Use execution tier now includes three intelligence layers that improve reliability on multi-step desktop automation tasks:

* **Context management** - Old screenshots are compacted to text descriptions while retaining the most recent images for visual continuity. This enables long-horizon tasks (more than 3-4 steps) that previously exhausted the model's context window.
* **Reflection agent** - Between automation rounds, a lightweight classifier evaluates whether the task is progressing, stuck in a loop, or complete. If stuck for three consecutive rounds, the task is terminated rather than looping indefinitely. Recovery suggestions are injected when the agent appears off-track.
* **Screen analysis** - UI elements and error states are extracted from screenshots and provided as structured grounding hints, improving click accuracy on complex forms and multi-panel layouts.

Two new desktop actions are also available: clipboard-based typing for Unicode and special characters, and drag operations for selection and drag-and-drop workflows.

#### Platform API: Typed Entity Response Fields

Entity API responses now include 19 typed top-level fields for common attributes: `name`, `phone`, `email`, `birth_date`, `gender`, `mrn`, `status`, `source`, `appointment_status`, `appointment_start`, `appointment_end`, `appointment_type`, `domain`, `industry`, `deal_stage`, `deal_amount`, `direction`, `duration_seconds`, and `call_sid`. These fields are available on all entity types (returning `null` when not applicable) and replace the need to parse the unstructured `state` object. The `state` field is now deprecated - it will continue to be returned for backward compatibility but consumers should migrate to the typed fields.

#### Platform API: Entity Projection Pipeline

Entity state projection now runs as a continuous streaming pipeline. Entity projections are computed from the event stream and updated continuously as new events arrive. This replaces the previous interval-based background task with a streaming architecture that reduces projection latency and scales independently of the agent engine.

#### Classic API: Cached Token Metering

A new `cached-token-count` usage event is now emitted alongside the existing `input-token-count` event. This tracks how many input tokens were served from the prompt cache versus computed fresh, enabling more granular cost analysis and cache hit rate monitoring.

</details>

<details>

<summary>v0.9.57 - Computer Use Execution Tier (April 13, 2026)</summary>

#### Platform API: Computer Use Execution Tier

Skills now support a fifth execution tier: **Computer Use**. This enables any agent skill to interact with desktop applications (browsers, remote desktop sessions, and VNC connections) through a multi-turn automation loop.

The agent connects to a desktop session, reads screen content, and performs actions (clicking, typing, navigating) to complete tasks in systems that do not expose APIs. This is particularly relevant in healthcare, where many EHR platforms, insurance portals, and practice management tools are accessible only through their web or desktop interfaces.

Key capabilities:

* **Browser automation** for web-based portals and applications
* **Remote desktop (RDP)** and **VNC** connections for legacy desktop applications
* **Multi-turn interaction loops** that handle multi-step UI workflows (login, navigate, fill forms, extract results)
* **Structured result extraction** - the skill returns structured data back to the calling agent, not raw screenshots

Computer Use tasks run with the same confidence scoring, audit logging, and review pipeline as other tool types. The execution tier is configured per skill, so workspaces control which skills have desktop access.

The five skill execution tiers are now: Direct (single LLM call), Orchestrated (multi-turn with tools), Autonomous (extended agent loops), Browser (headless web automation), and Computer Use (full desktop automation).

</details>

<details>

<summary>v0.9.56 - EHR Integration Improvements &#x26; Appointment Data Quality (April 13, 2026)</summary>

#### Platform API: Expanded EHR Outbound Write-Back

The outbound sync engine now supports write-back to additional EHR systems via SMART Backend Services (FHIR). The new handler supports both deferred mode (queues changes until the target system enables write access) and live mode (immediate FHIR resource creation). This brings the total number of EHR systems with bidirectional sync to four.

#### Platform API: Connector Authentication Expansion

The SMART Backend Services authentication flow now supports additional JWT signing configurations for service account authentication. This expands the set of EHR systems and cloud healthcare services that can use machine-to-machine authentication for data exchange.

#### Platform API: Appointment Cancellation Handling

Appointment projections now correctly detect cancellations synced from EHR systems. Previously, cancellations were only recognized when sent as explicit cancellation events. Now, appointments with a cancelled status are detected regardless of the event type used by the source system. This fixes a data quality issue where EHR-synced cancellations could appear as active appointments.

</details>

<details>

<summary>v0.9.52 - Data-Platform-First Memory (April 10, 2026)</summary>

#### Platform API: Data-Platform-First Memory

Memory dimension scores and entity facts are now served from pre-computed tables populated by a background pipeline, replacing the previous inline extraction approach. This eliminates query-time computation overhead and provides faster, more consistent reads.

Key changes:

* **Dimension endpoint** (`GET /v1/{ws}/memory/{entity_id}/dimensions`) reads pre-aggregated per-dimension stats (fact count, average confidence, source count, latest fact timestamp)
* **Facts endpoint** (`GET /v1/{ws}/memory/{entity_id}/facts`) now includes an `extracted_text` field on each fact: a pre-computed human-readable summary of the fact's content. Previously, callers had to parse the raw event data themselves.
* **Analytics endpoint** (`GET /v1/{ws}/memory/analytics`) is unchanged; it still queries live event data for workspace-level aggregation

The background pipeline runs as part of post-session processing. Newly ingested data is available in memory query results within seconds of pipeline completion. No API signature changes: existing integrations continue to work, and the `extracted_text` field is additive.

</details>

<details>

<summary>v0.9.51 - Behavior Settings, Sim Call Adapter &#x26; Outbound Prewarm (April 9, 2026)</summary>

#### Platform API: Behavior Settings

New workspace-level settings endpoint for managing dynamic behavior configurations:

| Endpoint                      | Method | Description                   |
| ----------------------------- | ------ | ----------------------------- |
| `/v1/{ws}/settings/behaviors` | `GET`  | Get current behavior settings |
| `/v1/{ws}/settings/behaviors` | `PUT`  | Update behavior settings      |

Stores tool-level behavioral directives with keyword, emotion, and state triggers. The Developer Console reads from this endpoint for its Behaviors management page.

#### Platform API: Simulation Call Adapter

Simulated coverage sessions now appear alongside real calls in the calls page. The call list endpoint supports an `include_simulated` parameter and `direction=simulated` filter. Call detail falls back to the simulation adapter when a call ID is not found in the real call store, so simulation sessions are accessible through the same call detail interface used for production calls.

#### Platform API: Streaming Playground

Playground and simulation sessions now stream LLM tokens in real time through the observer bus. Frontends connect via WebSocket and see tokens, tool calls, and state transitions as they happen, using the same streaming infrastructure as live voice sessions.

#### Performance: Outbound Call Prewarm

Outbound calls now prewarm the engine during the dialing/ringing phase (5-15 seconds before patient answers). When the patient picks up, the engine and greeting are already initialized. Eliminates approximately 3 seconds of dead air at the start of outbound calls.

#### Performance: Function Catalog Warmup

Platform function catalog discovery is now cached, eliminating a cold start delay on the first call in a workspace. Subsequent calls resolve the function catalog in under 1ms.

#### Platform API: LLM Extraction for Custom Memory Dimensions

Custom memory dimensions now support an `extraction_mode` field: `static` (JSONPath, deterministic) or `llm` (semantic extraction using the dimension's description as a prompt). Built-in dimensions use static extraction. Custom dimensions default to LLM extraction for handling nuanced, context-dependent information.

</details>

<details>

<summary>v0.9.49 - Memory Settings, Email Channel &#x26; Hazel EHR (April 8, 2026)</summary>

#### Platform API: Memory Dimension Settings

New workspace-level API for configuring memory dimensions. Six built-in dimensions (clinical, behavioral, operational, social, engagement, risk) ship as defaults. Workspaces can add custom dimensions, adjust weights, or deactivate built-ins without code changes.

| Endpoint                   | Method | Description                                |
| -------------------------- | ------ | ------------------------------------------ |
| `/v1/{ws}/settings/memory` | `GET`  | Get current memory dimension configuration |
| `/v1/{ws}/settings/memory` | `PUT`  | Update memory dimension configuration      |

Changes take effect on the next post-session processing run. The memory pipeline reads configuration dynamically.

#### Platform API: Email Channel

Native email delivery for surfaces via workspace-configured email provider. Surface delivery now supports email as a first-class channel alongside SMS and WhatsApp, with the same lifecycle tracking (delivered, opened, submitted) and real-time events.

#### Platform API: Hazel EHR Adapter

New EHR connector adapter for Hazel Health systems. Supports inbound polling (patients, visits, time slots, insurance records) and outbound write-back (visit creation, cancellation, rescheduling, insurance card upload, confirmation logging).

#### Fix: Soft Escalation Without Crisis Flag

Fixed a bug where the conversation monitor's `soft_escalate` action was silently discarded when the crisis flag was not set. Non-emergency operator handoff requests (e.g., caller asking to speak with a human) now correctly trigger escalation and fire the `call.escalated` event to the operator command center.

</details>

<details>

<summary>v0.9.48 - Simulation Coverage, Multi-Step Forms &#x26; Tool Migration (April 8, 2026)</summary>

#### Platform API: Simulation Coverage (Branch-and-Bound)

New coverage testing API that systematically explores context graph state space using branch-and-bound exploration. Replaces the previous exploration-based simulation model with a knowledge graph that tracks every turn individually.

| Endpoint                                            | Method | Description                                                                 |
| --------------------------------------------------- | ------ | --------------------------------------------------------------------------- |
| `/v1/{ws}/simulations/coverage/runs`                | `POST` | Create a coverage run with an ephemeral database branch for write isolation |
| `/v1/{ws}/simulations/coverage/runs/{id}/sessions`  | `POST` | Create a conversation session within a run                                  |
| `/v1/{ws}/simulations/coverage/sessions/{id}/step`  | `POST` | Step a session forward with a simulated user message                        |
| `/v1/{ws}/simulations/coverage/sessions/{id}/fork`  | `POST` | Fork a session into N children at a decision point (core B\&B primitive)    |
| `/v1/{ws}/simulations/coverage/sessions/{id}/score` | `POST` | Score a session against configured metrics                                  |
| `/v1/{ws}/simulations/coverage/runs/{id}/graph`     | `GET`  | Retrieve the coverage knowledge graph with topology overlay and ghost nodes |
| `/v1/{ws}/simulations/coverage/runs/{id}/complete`  | `POST` | Complete a run and clean up ephemeral branches                              |

The knowledge graph includes observed turns (per-state session counts, pass rates, turn counts) and a topology overlay with ghost nodes for context graph states never reached in any session. The fork primitive atomically clones parent session state N times, steps each with a different simulated message, and returns all observations.

{% hint style="warning" %}
**Breaking**: Previous simulation exploration endpoints (`sim_explorations`, `sim_conversations`) have been removed and replaced by the coverage API. Console updated in v2.22.0.
{% endhint %}

#### Platform API: Multi-Step Surface Forms

Surface forms with sections now render as stepped pages with progress bar, Next/Back navigation, and per-step field validation. Single-section and no-section forms continue to render as a single page (backward compatible).

#### Platform API: Gap Scanner Appointment Detection

The gap scanner now detects upcoming appointments using a batch query that joins entity relationships with appointment events at scan time. This runs once per workspace per tick and reads current event state directly, so appointment updates (cancellations, rescheduling) are reflected immediately.

#### Platform API: Legacy Tool Execution Removed

All workspaces have been migrated from the legacy serverless tool execution infrastructure to the platform's built-in tool system. The legacy tool executor and its associated configuration have been removed. Existing tool definitions continue to work; only the execution backend changed. This removes an external compute dependency and simplifies the deployment footprint.

</details>

<details>

<summary>v0.9.47 - Personas, Workflows &#x26; New Clinical Primitives (April 7, 2026)</summary>

#### Platform API: Personas

New first-class resource for cross-channel agent identity. A Persona defines who the agent is (name, role, background, communication style) independently of how it communicates (voice configuration, channel type). Services now reference a `persona_id` instead of embedding identity fields directly.

| Endpoint                 | Method   | Description                         |
| ------------------------ | -------- | ----------------------------------- |
| `/v1/{ws}/personas`      | `POST`   | Create a persona                    |
| `/v1/{ws}/personas`      | `GET`    | List personas (paginated, sortable) |
| `/v1/{ws}/personas/{id}` | `GET`    | Get a specific persona              |
| `/v1/{ws}/personas/{id}` | `PUT`    | Update a persona                    |
| `/v1/{ws}/personas/{id}` | `DELETE` | Delete a persona                    |

Services display the associated `persona_name` alongside the persona ID for easier identification.

#### Platform API: Environment & Workflow Settings

Two new workspace settings resources for configuration management:

* **Environment settings** (`GET/PUT /v1/{ws}/settings/environments`) - Per-environment configuration overlays (sandbox vs production). Services reference an environment, and the overlay provides tool and data source overrides. Audit-logged.
* **Workflow settings** (`GET/PUT /v1/{ws}/settings/workflows`) - Declarative multi-step workflow specifications stored at the workspace level. Enables structured, repeatable operational workflows that agents can follow.

#### Platform API: Queue & Ticket Tools

Two new built-in clinical tools:

| Tool            | Type  | Description                                                                                                                                                                                                                                       |
| --------------- | ----- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `queue_lookup`  | Read  | Reads outbound task entities from the world model, returning pending/scheduled tasks ordered by priority or next attempt time. Filterable by status (scheduled, dispatched, completed, failed, cancelled, snoozed).                               |
| `ticket_create` | Write | Creates a support ticket event for a patient entity. Writes a `ticket.created` event that flows through outbound sync to the workspace's ticketing or CRM system. Supports title, description, priority (low/normal/high/critical), and category. |

#### Platform API: Charm EHR Adapter

New EHR connector adapter for Charm systems. Supports patient polling, appointment reference data sync, and outbound appointment writes with CRM engagement tracking.

#### Platform API: Data Collection in Context Graphs

Context graphs can now include declarative data collection specifications. Instead of relying solely on the agent to identify missing data, context graph states can define what data must be collected and the system handles field rendering, validation, and world model integration automatically.

#### Breaking: Legacy Tool Aliases Removed

Legacy tool name aliases have been removed. All built-in clinical tools now use their canonical names only. Context graphs referencing the old names are automatically migrated.

#### Platform API: Multi-Number WhatsApp

WhatsApp messaging now supports multiple sender numbers per workspace with per-organization credential scoping. This enables workspaces to use different WhatsApp numbers for different services or departments.

</details>

<details>

<summary>v0.9.46 - Clinical Tools Expansion &#x26; Meditab EHR Adapter (April 6, 2026)</summary>

#### Platform API: New Clinical Tools

Three new built-in tools available during voice and text interactions:

| Tool                     | Type  | Description                                                                                                                                                                                                                     |
| ------------------------ | ----- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `log_call`               | Write | Records call outcome events (appointment booked, confirmed, declined, no answer, rescheduled, voicemail left, callback requested). Outcomes flow through outbound sync to source systems for reporting and workflow automation. |
| `reschedule_appointment` | Write | Reschedules an existing appointment to a new slot. Cancels the original and books the replacement atomically.                                                                                                                   |
| `save_patient`           | Write | Combined create-or-update operation. Updates an existing patient when entity ID is provided, or creates a new record with duplicate checking when not. Accepts flexible date formats and natural field names.                   |

#### Platform API: World Tool Prefix

Built-in clinical tools have been renamed to use a consistent naming convention. The previous tool names continue to work as legacy aliases, so no migration is required.

#### Platform API: Meditab EHR Adapter

New EHR connector adapter for Meditab systems. Uses SMART Backend Services authentication for standards-based connectivity. Supports appointment slot search and scheduling operations through the connector runner.

</details>

<details>

<summary>v0.9.45 - Database-Level Workspace Isolation (April 6, 2026)</summary>

#### Platform API: Database-Level Workspace Isolation

Workspace data isolation is now enforced at the database layer in addition to application-level access controls. Every data query is automatically scoped to the current workspace before execution, so no code path can access data across workspace boundaries.

This is a defense-in-depth measure for healthcare deployments where data isolation is a compliance requirement:

* **Fail-closed design** - If workspace context is missing from a request, the query returns zero rows rather than defaulting to unscoped access
* **Full coverage** - Applies to all data tables: events, entities, entity relationships, data sources, sessions, calls, surfaces, and configuration
* **Independent safety net** - Database-level enforcement cannot be bypassed by application code, even in the event of a bug in the API layer

Application-level access controls (API keys, role-based permissions) remain the primary access mechanism. The database layer provides an independent guarantee that data never leaks between workspaces.

#### Platform API: Observer Rate Limiting

The real-time observer connection endpoint now uses concurrent connection gauges with per-IP burst limits instead of flat rate limiting. This improves reliability for dashboards and monitoring tools that maintain persistent connections.

</details>

<details>

<summary>v0.9.44 - Branding Settings, Surface Rendering &#x26; Tool Repo Deprecation (April 6, 2026)</summary>

#### Platform API: Workspace Branding Settings

New branding settings endpoints let workspaces configure default visual branding for all patient-facing surfaces. Surface-level branding overrides workspace-level defaults, giving per-surface control when needed.

| Endpoint                     | Method | Description                               |
| ---------------------------- | ------ | ----------------------------------------- |
| `/v1/{ws}/settings/branding` | `GET`  | Get workspace default branding (any role) |
| `/v1/{ws}/settings/branding` | `PUT`  | Update workspace branding (admin/owner)   |

Branding configuration fields:

| Field              | Type   | Description                        |
| ------------------ | ------ | ---------------------------------- |
| `logo_url`         | string | Logo image URL (max 2048 chars)    |
| `primary_color`    | string | Primary brand color (max 32 chars) |
| `background_color` | string | Background color (max 32 chars)    |
| `font_family`      | string | Font family name (max 256 chars)   |

Workspace branding is automatically merged into surface rendering. Surfaces without their own branding inherit the workspace defaults. Changes are audit-logged.

#### Platform API: Dedicated Surface Rendering

Patient-facing surfaces now render through a dedicated web application instead of server-side HTML generation. The rendering app fetches the surface spec, saved field values, and merged branding via API, then renders a mobile-first form experience with:

* **Auto-save** - Individual fields are persisted as the patient fills them in
* **Workspace branding** - Logo, colors, and fonts applied automatically from workspace and surface-level settings
* **Improved error handling** - Distinct pages for expired, archived, and already-submitted surfaces

The patient-facing URL structure and token-based authentication are unchanged. Existing surface links continue to work.

#### Platform API: New Field Types

Two new surface field types for informational content within forms:

| Type    | Value     | Use Case                                  |
| ------- | --------- | ----------------------------------------- |
| Heading | `heading` | Section headers within longer forms       |
| Info    | `info`    | Read-only explanatory text between fields |

These are display-only fields; they do not collect data or appear in submissions.

#### Platform API: Surface Sections

Surfaces now support multi-page forms through sections. Each section groups fields into a page with its own title and description, creating a step-by-step experience for longer forms.

#### Classic API: Tool Repository Deprecated

All tool repository browsing endpoints have been removed. This includes branch listing, commit history, file browsing, directory listing, and diff viewing. Tool source code is now managed exclusively through the Agent Forge CLI and direct repository access.

The following endpoints are no longer available:

* `GET /v1/{org}/tool/repo/branch`
* `GET /v1/{org}/tool/repo/commit`
* `GET /v1/{org}/tool/repo/directory/{path}`
* `GET /v1/{org}/tool/repo/file`
* `GET /v1/{org}/tool/repo/file/{path}`
* `POST /v1/{org}/user/{id}/tool-repo-access`
* `DELETE /v1/{org}/user/{id}/tool-repo-access`

Tool publishing, versioning, and execution are unaffected.

</details>

<details>

<summary>v0.9.43 - Full-Fidelity Simulation &#x26; Call Intelligence (April 3, 2026)</summary>

#### Platform API: Full-Fidelity VoiceSim

VoiceSim now runs the full reasoning pipeline for each simulation step: real language model conversations, real tool execution, and real empathy classification. Previously, simulations used a simplified evaluation path. Now the same reasoning engine that powers live calls drives simulation, producing authentic results that match production behavior.

* **Real tool execution** - Simulated calls invoke the same tools as live calls (scheduling, patient lookup, FHIR operations), with results feeding back into the conversation loop.
* **Empathy classification** - Emotional responsiveness is evaluated in real time during simulation, so empathy-related configuration changes produce representative results.
* **Branch-isolated writes** - All tool-generated data writes during simulation go to an ephemeral database branch, preventing simulation artifacts from reaching production data.

#### Platform API: Sim Call Intelligence

Each VoiceSim evaluation step now produces call intelligence metrics: quality scores and analytics computed at session end. This is the same scoring pipeline used for live call monitoring, enabling direct comparison between simulated and production quality.

</details>

<details>

<summary>v0.9.42 - Voice Simulation &#x26; Egress IPs (April 2, 2026)</summary>

#### Platform API: Voice Simulation (VoiceSim)

New API for systematically exploring voice configuration space. VoiceSim evaluates configurations across 18 dimensions (barge-in, speed, empathy, safety, context, silence, tools, filler) against 8 built-in scenarios, scoring results with a quality oracle that penalizes dead air, greeting interruption, crisis response delays, and safety violations.

| Endpoint                      | Method | Description                                                                   |
| ----------------------------- | ------ | ----------------------------------------------------------------------------- |
| `/v1/{ws}/sims`               | `POST` | Create a simulation run                                                       |
| `/v1/{ws}/sims`               | `GET`  | List runs (newest first, paginated)                                           |
| `/v1/{ws}/sims/{id}/evaluate` | `POST` | Score a single (configuration, scenario) pair                                 |
| `/v1/{ws}/sims/{id}/sample`   | `POST` | LHS sample N points and evaluate all against all scenarios                    |
| `/v1/{ws}/sims/{id}`          | `GET`  | Run status and best results                                                   |
| `/v1/{ws}/sims/{id}/summary`  | `GET`  | Aggregated: best per scenario, penalty frequency, config diff from production |
| `/v1/{ws}/sims/{id}/points`   | `GET`  | Scored points (order by score or time)                                        |
| `/v1/{ws}/sims/{id}/complete` | `POST` | Mark run finished, clean up branches                                          |

Sim runs execute against isolated database branches for write safety. SSE workspace events provide live progress for the Developer Console UI.

#### Platform API: Egress IPs Endpoint

|              |                                                         |
| ------------ | ------------------------------------------------------- |
| **Endpoint** | `GET /v1/{workspace_id}/network/egress-ips`             |
| **Auth**     | Workspace API key (any role)                            |
| **Returns**  | Static NAT gateway IP addresses, region, usage guidance |

Customers can self-serve firewall allowlisting for EHR/CRM data source connectivity without contacting support.

</details>

<details>

<summary>v0.9.41 - Tool Testing Playground (April 2, 2026)</summary>

#### Platform API: Tool Testing Endpoints

New endpoints for testing HSM tools (world tools, skills, integrations) from the Developer Console without making a phone call. Test writes are source-isolated so no real EHR syncs, SMS deliveries, or surface deliveries occur.

|              |                                                                                                                                      |
| ------------ | ------------------------------------------------------------------------------------------------------------------------------------ |
| **Endpoint** | `POST /v1/{workspace_id}/tools/execute`                                                                                              |
| **Auth**     | Workspace API key (admin/owner only, requires `tools:test` scope)                                                                    |
| **Body**     | `tool_type` (clinical tool, skill, integration), `tool_name`, `service_id`, `input_params`, optional `entity_id`, optional `dry_run` |
| **Returns**  | Execution result, duration, tier, sub-tool call logs, blocked side effects                                                           |

|              |                                                                                                                    |
| ------------ | ------------------------------------------------------------------------------------------------------------------ |
| **Endpoint** | `GET /v1/{workspace_id}/services/{service_id}/tools/resolve`                                                       |
| **Auth**     | Workspace API key (admin/owner only)                                                                               |
| **Returns**  | List of available tools for the service with name, type, description, input schema, tier, and write classification |

**Safety guardrails:**

* All writes tagged with `source="tool_test"`, excluded from outbound sync allowlist
* Surface delivery is blocked for tool test sources
* Dry run mode simulates write operations without persistence

#### Agent Background Field Limit Increase

The `background` field on agent versions now accepts up to **10,000 characters** (previously 2,000). This accommodates rich persona descriptions, detailed domain knowledge, and extensive behavioral guidelines without truncation. Other text fields (name, description) retain their existing limits.

</details>

<details>

<summary>v0.9.40 - Source Overview, Pagination Totals &#x26; Query Improvements (April 1, 2026)</summary>

#### Source Pipeline Overview

New endpoint for inspecting the health of individual data sources in the pipeline:

|              |                                                                                                              |
| ------------ | ------------------------------------------------------------------------------------------------------------ |
| **Endpoint** | `GET /v1/{workspace_id}/pipeline/sources/{source_id}/overview`                                               |
| **Auth**     | Workspace API key (viewer+)                                                                                  |
| **Returns**  | Aggregated overview of a source's pipeline health: event counts by status, last sync time, and error summary |

Use this to monitor individual source health without polling the full pipeline status endpoint.

#### Pagination Totals

All paginated list responses across the Platform API now include a `total` field with the total count of matching records, not just the current page. This applies to every endpoint that returns `items`, `has_more`, and `continuation_token`.

#### Multi-Value Source Type Filtering

The data sources list endpoint (`GET /v1/{ws}/data-sources`) now accepts repeatable `source_type` query parameters for filtering by multiple source types in a single request (e.g., `?source_type=ehr&source_type=smart_fhir`).

#### FHIR Patient Sort Parameter

`GET /v1/{ws}/fhir/patients` now accepts a `sort_by` parameter with `+/-` prefix for sort direction. For example, `-last_event_at` returns patients sorted by most recent activity first, and `+created_at` returns patients sorted by creation date ascending.

</details>

<details>

<summary>v0.9.39 - HSM Extensions, Surface SMS Trust &#x26; Entity Recomputation (April 1, 2026)</summary>

#### Context Graph Action State Extensions

Three new optional fields on action states enable asynchronous workflows where the agent pauses mid-conversation, waits for external input, and resumes:

| Field                   | Description                                                                                                                                                                                           |
| ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `wait_for`              | Pause HSM navigation until a condition clears (`surface_submission` or `human_approval`). While waiting, the agent stays in the current state and makes empathetic small-talk.                        |
| `channel_overrides`     | Per-channel overrides for `objective`, `action_guidelines`, and `suppress_filler`. Keyed by channel kind (`voice`, `sms`). Allows the same context graph to behave differently across voice and text. |
| `surface_spec_template` | Surface spec auto-created on state entry. Uses the same schema as `POST /surfaces`. Entity ID defaults to the session's primary entity.                                                               |

Text sessions set `channel_kind = "sms"` on the engine session for channel override resolution. Wait conditions in text sessions use a dedicated event listener that blocks until `surface.submitted` or `surface.review_approved` arrives.

#### vCard Contact Cards for SMS Delivery

SMS surface delivery now sends a vCard contact card before the form link. iOS blocks clickable links in SMS from unknown senders; once the patient saves the contact, iOS trusts the sender and renders subsequent links as tappable.

The delivery endpoint performs a two-step send:

1. A vCard 3.0 attachment with the workspace's business name and phone number
2. The form link in a second message

vCard delivery is best-effort: if it fails, the form link is still sent. A new public endpoint serves the vCard file:

|              |                                                     |
| ------------ | --------------------------------------------------- |
| **Endpoint** | `GET /s/vcard/{workspace_slug}.vcf`                 |
| **Auth**     | None (public)                                       |
| **Response** | `text/vcard` with `Content-Disposition: attachment` |

#### Short Surface URLs

Surface links in SMS messages now use short redirect URLs (`/s/f/{surface_id}`) instead of the full HMAC-signed token URL. The short URL generates a fresh token server-side and issues a 302 redirect to the full surface page. This keeps SMS bodies compact and avoids carrier truncation of long URLs.

|               |                                                                |
| ------------- | -------------------------------------------------------------- |
| **Endpoint**  | `GET /s/f/{surface_id}`                                        |
| **Auth**      | None (public)                                                  |
| **Responses** | `302` redirect to `/s/{token}`, `404` not found, `410` expired |

The delivery response now includes `short_url` and `vcard_url` in the metadata.

#### Surface Entity Linking & State Recomputation

Surface lifecycle events (created, delivered, opened, submitted) are now linked to the entity on the event row, not just in the event data payload. This means surface events appear in entity timeline queries and contribute to entity state.

When a patient submits a surface, entity state recomputation runs within the same transaction. Submitted form data flows into entity demographic and clinical projections immediately, with no background job delay.

</details>

<details>

<summary>v0.9.38 - Text Agent Sessions &#x26; Surface Consent Gating (April 1, 2026)</summary>

#### Text Agent Sessions (SMS HSM Conversations)

The voice agent now supports full SMS-based conversations using the same context graph (HSM) engine that powers voice calls. A new `POST /voice-agent/create_outbound_text` endpoint creates an outbound text session that sends a greeting and conducts a multi-turn conversation over SMS.

| Parameter                     | Type              | Description                                       |
| ----------------------------- | ----------------- | ------------------------------------------------- |
| `phone_to` / `phone_from`     | string (E.164)    | Patient and agent phone numbers                   |
| `workspace_id` / `service_id` | string            | Workspace and service (agent) to run              |
| `entity_id`                   | string (optional) | World model entity ID for patient context loading |
| `surface_id`                  | string (optional) | Surface to deliver inline in the conversation     |
| `idempotency_key`             | string (optional) | Client dedup key (cached 5 minutes)               |

Rate limited to 20 per workspace per minute. Returns `session_id`, `status`, and `conversation_id`.

Inbound SMS (patients texting the agent's number) also creates text sessions automatically when no active session exists for that phone pair. The SMS webhook now validates that the target service supports text channels before spawning a session.

#### SMS Consent Enforcement

All outbound SMS, across both text agent sessions and surface delivery, now checks patient consent before sending. If a patient has opted out by texting STOP, UNSUBSCRIBE, CANCEL, END, or QUIT, the system blocks outbound messages and returns `403 Forbidden`.

Consent is tracked per patient per workspace, cached for fast lookups, and enforced at the application layer independently of carrier-level STOP handling.

#### Gap Scanner Auto-Delivery

The connector runner's gap scanner can now auto-deliver surfaces by triggering outbound text conversations. When `auto_deliver` is enabled in gap scanner settings and the surface channel is SMS, the scanner calls `create_outbound_text` to start a text conversation that delivers the surface inline, giving the patient context about why they are being asked rather than sending a bare link.

| Setting                   | Description                                              |
| ------------------------- | -------------------------------------------------------- |
| `auto_deliver`            | Enable automatic delivery of SMS surfaces (default: off) |
| `auto_deliver_service_id` | Service (agent) to use for the text conversation         |
| `sms_from_number`         | Agent phone number for outbound SMS                      |

Auto-delivery is best-effort: failures are logged but never block the scanner.

</details>

<details>

<summary>v0.9.37 - Platform API: API Key Rotation, Workspace Archive &#x26; Access Audit (April 1, 2026)</summary>

#### API Key Rotation

New `POST /api-keys/{key_id}/rotate` endpoint replaces a key's secret in a single atomic operation. The old secret stops working immediately and the response includes the new plaintext key exactly once. Accepts `duration_days` (1-90) for the rotated key's expiration. The rotated key inherits the original's name, role, and permissions.

API key responses now include a `last_used_at` timestamp for identifying stale keys.

#### Workspace Archive

New `POST /workspaces/{workspace_id}/archive` endpoint archives a workspace without deleting underlying data. Requires `owner` role and a slug confirmation in the request body to prevent accidental archival. Archived workspaces no longer appear in workspace listings.

#### Workspace Invitation Preview

New `GET /invitations/{invitation_id}/preview` endpoint returns a safe public view of a workspace invitation (workspace name, role, expiry, status) without requiring authentication. Designed for rendering invitation landing pages before the invited user signs in.

#### Workspace Access Audit Logging

All workspace membership and invitation operations now emit structured audit events:

* `workspace.member_added` - Member created or reactivated
* `workspace.invitation_sent` - Invitation created or resent
* `workspace.invitation_accepted` / `workspace.invitation_declined` - Invitation resolved
* `workspace.invitation_revoked` - Invitation revoked by admin

Each event records the actor entity, target entity/email, role, and contextual metadata (e.g., whether a member was reactivated vs newly created). Events are available through the audit log endpoints and compliance export.

</details>

<details>

<summary>v0.9.36 - Platform API: FHIR Resource History, Entity Cross-Referencing &#x26; Sync Filters (March 31, 2026)</summary>

#### FHIR Resource History

New endpoint `GET /fhir/resources/{resource_type}/{resource_id}/history` returns the version history for a single FHIR resource. Each history entry includes:

| Field                      | Description                                                            |
| -------------------------- | ---------------------------------------------------------------------- |
| `event_id`                 | Unique event identifier                                                |
| `event_type`               | Type of change (e.g., `create`, `update`)                              |
| `source` / `source_system` | Where the change originated                                            |
| `confidence`               | Data confidence level                                                  |
| `is_current`               | Whether this is the active version                                     |
| `fields_affected`          | List of FHIR field paths that changed compared to the previous version |
| `synced_at` / `sync_error` | Outbound sync status                                                   |
| `data`                     | Full FHIR resource payload at this version                             |

The `fields_affected` diff is computed server-side by comparing each version's data against its predecessor, walking nested objects to produce dot-path field names (e.g., `name.0.given`, `telecom.0.value`). Supports pagination via `limit` (1-50, default 10) and `offset` with `has_more` / `next_offset` in the response.

#### FHIR Entity Cross-Referencing

FHIR resource responses now include an `entity_id` field that links each FHIR resource to its corresponding world model entity. This applies to resource reads, creates, and updates, so every FHIR resource operation now returns the entity it resolved to (or `null` if no entity link exists yet). Enables direct navigation between FHIR resources and world model entities without a separate lookup.

#### Scoped Sync Failure Filtering

The FHIR sync failures endpoint (`GET /fhir/sync-failures`) now supports two new query parameters:

| Parameter            | Type   | Description                                                                       |
| -------------------- | ------ | --------------------------------------------------------------------------------- |
| `fhir_resource_type` | string | Filter failures to a specific FHIR resource type (e.g., `Patient`, `Appointment`) |
| `fhir_resource_id`   | string | Filter failures to a specific FHIR resource ID                                    |

The `total` field in the response now returns an accurate count matching the applied filters, rather than the number of returned items.

#### World Sync Queue Filters

The world sync events endpoint (`GET /world/sync`) adds the same two filters:

| Parameter            | Type   | Description                                         |
| -------------------- | ------ | --------------------------------------------------- |
| `fhir_resource_type` | string | Filter sync events to a specific FHIR resource type |
| `fhir_resource_id`   | string | Filter sync events to a specific FHIR resource ID   |

These complement the existing `status`, `data_source_id`, and `source_system` filters, enabling precise investigation of sync issues for individual resources.

#### Data Source Sync History

The data source sync history response now includes `fhir_resource_id` in failure records, making it possible to identify exactly which FHIR resource failed to sync from the data source detail view.

</details>

<details>

<summary>v0.9.35 - Voice Agent: Empathy-First Pipeline (March 31, 2026)</summary>

#### Empathy Tier Classification

The voice agent now classifies each caller turn into an empathy tier before generating a response. The classifier is rule-based (no LLM call, sub-100ms) and gates pipeline behavior, not just response style, but whether to speak, how much to say, and whether to advance the task at all.

| Tier | Name         | Behavior                                                          |
| ---- | ------------ | ----------------------------------------------------------------- |
| T0   | Functional   | Normal task-oriented pipeline                                     |
| T1   | Light Touch  | Empathy filler before task content                                |
| T2   | Full Empathy | 0.5s pause, empathy-first response, task secondary                |
| T3   | Hold Space   | 1s pause, pure empathy, zero task advancement, fillers suppressed |

Classification uses transcript keywords, emotion signals (valence, arousal, dominant emotion), and emotional trend across recent turns. Includes negation detection ("I'm not worried"), idiomatic exclusions ("dying to know"), and resolved-emotion handling ("I was worried but I'm fine now").

#### Empathy-Gated Filler and TTS

* **Anti-filler silence** - T2 inserts a 0.5s pause before any filler. T3 suppresses fillers entirely.
* **TTS presence mode** - Empathy fillers play at 0.85x speed with neutral emotion, avoiding uncanny vowel stretching on short phrases.
* **Filler taxonomy** - Fillers are now typed as `empathy`, `receipt`, or `working` based on the empathy tier, replacing the previous single filler category.

#### Empathy Baseline Decay

After distress, an empathy baseline decays at 0.15 per turn, preventing the agent from snapping back to a transactional tone immediately after a caller was upset. The baseline persists across turns and is reported in the `empathy_classified` observer event.

#### Engage Prompt Hardening

* Banned-word list prevents filler-plus-acknowledgement stacking (the agent no longer opens with "Okay" or "Sure" after a filler was already spoken).
* At T2+, the response generation prompt forces empathy-first responses. At T3, the entire response is empathy with zero task content.

</details>

<details>

<summary>v0.9.34 - Platform API: Surface SMS Delivery (March 31, 2026)</summary>

#### Real SMS Delivery for Surfaces

The `POST /surfaces/{id}/deliver` endpoint now sends surfaces to patients via SMS. When a phone number is provided as the `channel_address`, the platform resolves Twilio credentials from the workspace's configured phone numbers and sends the surface URL directly.

* **SMS delivery** - Surface URL sent via SMS with title as message body. Phone numbers validated against E.164 format.
* **Delivery metadata** - Response now includes `message_id` (provider SID), `from_number`, `delivery_provider`, and `delivered_at` for tracking and audit.
* **Voice agent integration** - The `create_surface` voice agent tool now supports delivery context, enabling the agent to create and deliver surfaces during live calls.

Previously, surfaces generated signed URLs that required manual distribution. Now the full create-to-deliver flow works end-to-end through the API.

</details>

<details>

<summary>v0.9.33 - Platform API: Search &#x26; Pagination Improvements (March 31, 2026)</summary>

#### Data Source Search

The `GET /data-sources` endpoint now accepts a `search` query parameter for filtering data sources by name, ID, source type, or sync status.

#### Patient Search Pagination

The `GET /fhir/patients` endpoint now returns proper pagination metadata:

* `has_more` (boolean) - whether additional results exist beyond the current page
* `next_offset` (integer) - offset for the next page, included only when `has_more` is true

Phone and MRN searches now also support offset-based pagination. The `q` parameter searches across name, MRN, phone, and entity ID.

#### Pipeline & Entity Query Hardening

* Pipeline endpoints now validate UUID parameters and return 400 on malformed IDs instead of 500
* Entity search and registry endpoints use consistent parameter cleaning

</details>

<details>

<summary>v0.9.32 - Platform API: Charm EHR Integration (March 30, 2026)</summary>

#### Charm EHR Inbound Adapter

New `charmhealth` connector type and external system for Charm Health EHR. Provides inbound data exchange via Charm's FHIR R4 API (US Core IG STU 3.1.1).

* **New source type** - `charmhealth` for inbound polling from Charm Health.
* **New connector type** - `charmhealth` for workspace configuration.
* **Dual-header authentication** - OAuth2 Bearer token + practice-level API key on every request. Token management handles exchange, refresh, and fallback to re-authentication.
* **Page-based pagination** - Adapts to Charm's non-standard integer page parameter instead of FHIR Bundle `next` links.
* **Adaptive rate limiting** - Automatic backoff with jitter on HTTP 429 responses, with configurable retry limits.
* **15 resource types** - Tiered by change frequency: Appointment (real-time), Patient/Encounter/Condition/MedicationRequest/Observation/AllergyIntolerance/Immunization/Goal (standard), Practitioner/Location/CareTeam/CarePlan/DiagnosticReport (slow), Organization (daily).
* **New config field** - `api_key_ssm_path` on connection config for the practice API key.

{% hint style="warning" %}
Outbound write-back is currently no-op (inbound-only). Full write-back support is pending licensing for Charm's proprietary write API.
{% endhint %}

</details>

<details>

<summary>v0.9.31 - Platform API: Self-Service SMART FHIR Provisioning (March 30, 2026)</summary>

#### Self-Service Secret Provisioning for SMART FHIR Data Sources

The `POST /data-sources` and `PUT /data-sources/{id}` endpoints now support inline secret provisioning for `smart_fhir` data sources. Pass `private_key_value` in `connection_config` and the API automatically provisions the key to secure storage and stores only an SSM path reference.

* **Inline key upload** - Include the PEM-encoded private key as `private_key_value` when creating or updating a data source. The API provisions it and replaces the field with `private_key_ssm_param_path`.
* **Response redaction** - API responses strip all inline secret fields (`*_value`, `*_pem`, `*_secret`, `*_password`) and mask SSM paths. A `private_key_ssm_param_path_configured: true` indicator confirms the key is provisioned.
* **Key rotation** - Send a new `private_key_value` on update to rotate the key. The old key is overwritten in secure storage.

This enables teams to connect new EHR systems entirely through the API or Developer Console without infrastructure involvement.

</details>

<details>

<summary>v0.9.28 - Platform API: Slot Search Time Preferences &#x26; User Actions Access (March 30, 2026)</summary>

#### Voice Agent: Time Preference Filtering

The `slot_search` tool now accepts an optional `time_preference` parameter (`morning`, `afternoon`, or `evening`) to filter appointment slots by time of day. When no slots match the preferred window exactly, the tool falls back to the nearest available time and indicates the fallback in the response.

Slot results are now cached per session, so `schedule_appointment` and `reschedule_appointment` can reference slots by index (`slot:0`, `slot:1`) without re-querying. The cache is isolated per voice session to prevent cross-session interference.

#### Classic API: User Actions Access

* New `enable_actions_access` boolean field on user records, returned in `GetUserInfo` responses. Controls whether a user can access actions features (tool repository browsing, action management). Defaults to `false`.

</details>

<details>

<summary>v0.9.25 - Outreach Optimization: Surface Insights, Fatigue Gating &#x26; Channel Optimization (March 29, 2026)</summary>

#### Voice Agent: `get_surface_insights` Tool

New context graph tool that queries a patient's surface analytics before creating new surfaces. Returns completion rate, preferred channel, pending surface count, and recent history. Enables agents to avoid surface fatigue: if a patient has multiple unfinished surfaces or low completion, the agent can collect data verbally instead.

#### Gap Scanner: Fatigue Gating

Three new gap scanner settings that prevent over-solicitation:

| Setting                | Default | Description                                                             |
| ---------------------- | ------- | ----------------------------------------------------------------------- |
| `max_pending_surfaces` | 3       | Skip entities with this many unfinished surfaces                        |
| `min_completion_rate`  | 0.0     | Skip entities whose completion rate is below this threshold             |
| `channel_optimization` | false   | Use each entity's historically preferred channel instead of the default |

#### Closed-Loop Feedback

Surface analytics now feed back into both agent-side and automated surface creation. The voice agent queries insights before creating surfaces, and the gap scanner checks pending counts and completion rates before generating outreach, preventing the common failure mode of sending more messages to patients who do not respond.

</details>

<details>

<summary>v0.9.24 - Platform API: Surface Analytics (March 29, 2026)</summary>

#### Surface Analytics (4 Endpoints)

Closed-loop intelligence for data collection surfaces under `GET /v1/{workspace_id}/analytics/surfaces/`:

| Endpoint                 | Description                                                                                                |
| ------------------------ | ---------------------------------------------------------------------------------------------------------- |
| `/completion-rates`      | Overall rate, time-bucketed trend, by-source breakdown (agent, gap scanner, manual). Filterable by entity. |
| `/channel-effectiveness` | Per-channel (SMS, email, WhatsApp, web, voice) completion rate and avg time-to-complete                    |
| `/field-abandonment`     | Per-field drop-off rate and save rate for abandoned surfaces                                               |
| `/entity/{entity_id}`    | Per-entity history: completion stats, preferred channel, recent surfaces                                   |

All support date range filtering (default 30 days, max 90) with `1h`/`1d`/`1w` intervals. Requires `viewer+` permissions.

Enables agents and gap scanners to optimize surface design. For example, switching to SMS after seeing low email completion rates, or removing a field that causes high abandonment.

</details>

<details>

<summary>v0.9.23 - Connector Runner: Automated Gap Scanner (March 29, 2026)</summary>

#### Gap Scanner Loop (New Background Loop)

Rule-based background loop on the connector runner that scans entities for missing data and creates surfaces automatically. This is the 8th background loop alongside poll, reconciliation, entity resolution, review, outbound subscriber, outbound dispatch, and config refresh.

**How it works:**

1. Scans workspaces with gap scanning enabled
2. Loads gap requirements from workspace settings (configurable rules)
3. Checks entity state against requirements using dot-notation path resolution
4. Creates surfaces for entities with missing data
5. Delivers via configured channel (SMS, email, etc.)
6. Sets a cooldown per entity to prevent notification fatigue (default 7 days)

#### Gap Scanner Settings API

| Endpoint                    | Description                                         |
| --------------------------- | --------------------------------------------------- |
| `GET /settings/gap-scanner` | Get gap scanner configuration (any role)            |
| `PUT /settings/gap-scanner` | Update configuration (admin/owner, partial updates) |

Settings include: `enabled` (default false), `scan_interval_seconds` (300), `appointment_lookahead_hours` (72), `cooldown_hours` (168), `max_surfaces_per_tick` (10), and `requirements` (list of gap detection rules).

Each requirement defines: entity type, trigger condition (`upcoming_appointment` or `recent_interaction`), required fields (dot-notation paths), delivery channel, and priority.

The scanner integrates with the connector runner's operational metrics and health status endpoint.

</details>

<details>

<summary>v0.9.22 - MFA, IP Allowlists &#x26; Mid-Call Surface Observation (March 29, 2026)</summary>

#### Platform API: Multi-Factor Authentication (MFA)

TOTP-based MFA with recovery codes. Meets HIPAA 164.312(d), SOC 2 CC6.1/CC6.6.

**User MFA endpoints:**

| Endpoint           | Description                                                                            |
| ------------------ | -------------------------------------------------------------------------------------- |
| `POST /mfa/enroll` | Generate TOTP secret + recovery codes. Returns `otpauth://` URI for authenticator app. |
| `POST /mfa/verify` | Confirm enrollment with a valid TOTP code                                              |

**Admin MFA endpoints** (requires `identity:admin`):

| Endpoint                            | Description                                    |
| ----------------------------------- | ---------------------------------------------- |
| `GET /admin/mfa/coverage`           | MFA enrollment statistics across the workspace |
| `POST /admin/mfa/reset/{entity_id}` | Reset MFA enrollment for a user                |

MFA can be enforced per SSO connection via `enforce_mfa`. Service accounts are exempt.

#### Platform API: IP Allowlists

Per-workspace CIDR-based IP restrictions on the token endpoint.

**Admin endpoints** (requires `identity:admin`):

| Endpoint                           | Description                              |
| ---------------------------------- | ---------------------------------------- |
| `POST /admin/ip-allowlists`        | Add a CIDR range                         |
| `GET /admin/ip-allowlists`         | List allowed ranges                      |
| `DELETE /admin/ip-allowlists/{id}` | Remove a range                           |
| `POST /admin/ip-allowlists/test`   | Test whether an IP matches the allowlist |

Cached for performance. Service accounts exempt. Fails open if cache unavailable.

#### Voice Agent: Mid-Call Surface Observation

The voice agent now passively observes surface submissions during active calls. When a patient submits a form the agent created, the agent receives a real-time notification and acknowledges it in the conversation (e.g., "I can see you just uploaded your insurance card, thank you").

Works through the workspace event stream: the agent subscribes during the call and filters for surface submissions matching surfaces it created.

</details>

<details>

<summary>v0.9.20 - Voice Agent: Mid-Call Surface Tools (March 29, 2026)</summary>

#### HSM Surface Tools (4 New)

Four context graph tools enabling voice agents to create and manage surfaces during live calls:

| Tool                   | Description                                                                             |
| ---------------------- | --------------------------------------------------------------------------------------- |
| `create_surface`       | Create a surface spec (entity, title, fields). Returns signed URL for patient delivery. |
| `deliver_surface`      | Mark surface as delivered to a channel address (phone/email). Records lifecycle event.  |
| `check_surface_status` | Poll current lifecycle status of a surface.                                             |
| `list_entity_surfaces` | List existing surfaces for an entity (filterable by status) to avoid duplicates.        |

All tools authenticate via the call's `agent_session` JWT for automatic workspace isolation. Errors are returned as structured messages rather than exceptions, so the agent can continue the call if a surface operation fails.

</details>

<details>

<summary>v0.9.19 - Platform API: Patient-Facing Surface Rendering (March 28, 2026)</summary>

#### Patient-Facing Endpoints

Token-authenticated patient-facing endpoints for surfaces. Patients access forms via HMAC-signed URL tokens, so no login is required.

| Endpoint                      | Description                                              |
| ----------------------------- | -------------------------------------------------------- |
| `GET /s/{token}`              | Render surface as mobile-first HTML (all 12 field types) |
| `POST /s/{token}/submit`      | Accept form submission (written at confidence 0.5)       |
| `PUT /s/{token}/fields/{key}` | Incremental auto-save for individual fields              |

* **Token-based access** - HMAC-SHA256 signed tokens with surface ID and expiration, no API key needed
* **Mobile-first rendering** - Server-side HTML generation, native device capabilities for photo/signature/file
* **Auto-save** - Fields save incrementally so patients don't lose progress
* **Rate limiting** - Per-IP: 30/min render, 10/min submit, 30/min auto-save
* **Atomic lifecycle** - Status transitions (opened, partial, completed) in single transactions to prevent race conditions
* **Field validation** - 10KB max per field value

Surface creation response now includes the signed `token` and full `url` when the token secret is configured.

</details>

<details>

<summary>v0.9.18 - Platform API: Surface Engine Foundations (March 28, 2026)</summary>

#### Surfaces (New Feature)

Agent-generated data collection interfaces delivered to patients through existing communication channels. Agents analyze entity state and data gaps, generate a `SurfaceSpec`, and the platform renders and delivers it.

**Models:**

| Model               | Description                                                                             |
| ------------------- | --------------------------------------------------------------------------------------- |
| `SurfaceSpec`       | What an agent generates: title, 1-100 fields, channel, expiration, entity association   |
| `SurfaceField`      | A data collection field with type, validation, conditional display, PHI flag            |
| `SurfaceSubmission` | What comes back: submitted field values, partial or complete status                     |
| `SurfaceStatus`     | Lifecycle: `created` -> `delivered` -> `opened` -> `partial` -> `completed` / `expired` |

**5 delivery channels:** SMS, WhatsApp, email, voice (IVR), web link.

**12 field types:** text, textarea, date, phone, email, number, select, multiselect, checkbox, photo, signature, file.

**Permissions:** `surfaces:read` (view specs and submissions) and `surfaces:write` (generate specs).

Submissions are written to the world model as events with source `surface` and flow through standard confidence gates, review queues, and entity resolution.

#### New Entity Type

`interaction` entity type added to the world model ontology for tracking surface lifecycle events as first-class entities.

</details>

<details>

<summary>v0.9.17 - Platform API: Audit Log Export (March 28, 2026)</summary>

#### Audit Log Export

Two new endpoints for exporting audit events as NDJSON files with time-limited download URLs:

| Endpoint             | Description                                                      |
| -------------------- | ---------------------------------------------------------------- |
| `POST /audit/export` | Create a new export for a date range. Returns export ID.         |
| `GET /audit/exports` | List all exports with status and download URLs (12-hour expiry). |

* NDJSON format, capped at 50,000 events per export
* Export action is itself audit-logged (with PHI flag if applicable)
* Returns `503` if export storage is not configured
* Requires `admin` or `owner` permissions

</details>

<details>

<summary>v0.9.16 - Platform API: Data Lineage, Retention Policies &#x26; Compliance Reporting (March 28, 2026)</summary>

#### Entity Data Lineage

`GET /world/entities/{entity_id}/lineage` - Full data lineage composing provenance (sources, confidence history, merge events) with outbound sync sinks (where data was sent, status) and review queue history (verdicts, reviewer actions).

#### Retention Policies

Per-workspace retention policies with HIPAA-compliant defaults (6 years / 2190 days):

| Endpoint                  | Description                                            |
| ------------------------- | ------------------------------------------------------ |
| `GET /settings/retention` | Get retention policy (any role)                        |
| `PUT /settings/retention` | Update retention policy (admin/owner, partial updates) |

Configurable per data type: call recordings, call transcripts, audit logs, world events, PHI data. Legal hold overrides all retention. Advisory in v1: policy is stored but no automated deletion.

#### Compliance Reporting

| Endpoint                        | Description                                                                                              |
| ------------------------------- | -------------------------------------------------------------------------------------------------------- |
| `GET /compliance/hipaa`         | HIPAA evidence report: audit stats, retention, encryption, API keys for configurable period (1-365 days) |
| `GET /compliance/access-review` | SOC 2 access review: all credentials with role, status, activity dates for attestation                   |

Both require `admin` or `owner` permissions.

</details>

<details>

<summary>v0.9.15 - Platform API: Unified Audit Trail &#x26; PHI Access Logging (March 28, 2026)</summary>

#### Unified Audit Trail

Enterprise compliance audit system. Every significant action across all services is recorded as an audit event with actor identity, credential, IP address, resource context, and PHI flag.

Audit events are written asynchronously via a buffered writer, so no latency is added to the request path.

#### PHI Access Logging

Automatic detection and logging of access to PHI-sensitive endpoints. 12 route patterns are recognized as PHI-sensitive, including entity reads, FHIR patient data, CRM contacts, call recordings, call intelligence, and audit reports themselves.

#### Audit Query API (4 Endpoints)

| Endpoint                                   | Description                                                                                                                   |
| ------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------- |
| `GET /audit`                               | List audit events with filters: service, action, actor, resource type, resource ID, PHI-only, date range                      |
| `GET /audit/phi-access`                    | PHI access report: who accessed what patient data, when, and from where. Required for HIPAA breach investigation (164.312(b)) |
| `GET /audit/entity/{entity_id}/access-log` | All audit events for a specific resource                                                                                      |
| `GET /audit/summary`                       | Summary statistics: total events, PHI access events, unique actors, services with activity                                    |

All endpoints require `admin` or `owner` permissions and are workspace-scoped.

</details>

<details>

<summary>v0.9.14 - Platform API: Session Enforcement (March 28, 2026)</summary>

#### Idle Timeout & Concurrent Session Limits

HIPAA-compliant session enforcement with background cleanup:

| Session Type                      | Idle Timeout | Concurrent Limit        | Notes                     |
| --------------------------------- | ------------ | ----------------------- | ------------------------- |
| User sessions (developer console) | 15 minutes   | 5 per entity/workspace  | HIPAA-compliant default   |
| Agent sessions (voice calls)      | 1 hour       | 50 per entity/workspace | Matches agent session TTL |

* Background process periodically revokes idle and expired sessions
* Concurrent limit enforced at session creation; oldest session evicted when limit reached
* Idle timeout configurable per-session (5 minutes to 24 hours)
* Sessions now track IP address and user agent for audit visibility

#### Session Management Endpoints

**User endpoints** (any authenticated user):

| Endpoint                        | Description                                 |
| ------------------------------- | ------------------------------------------- |
| `GET /sessions`                 | List active sessions for the current entity |
| `DELETE /sessions`              | Revoke all sessions (logout everywhere)     |
| `DELETE /sessions/{session_id}` | Revoke a specific session                   |

**Admin endpoints** (requires `identity:admin` scope):

| Endpoint                             | Description                                                        |
| ------------------------------------ | ------------------------------------------------------------------ |
| `GET /sessions/admin`                | List all active sessions with entity/workspace filters, pagination |
| `DELETE /sessions/admin/{entity_id}` | Revoke all sessions for a specific entity                          |

All revocation actions are audit-logged.

</details>

<details>

<summary>v0.9.13 - Account Lockout, Tool Repositories &#x26; Prompt Caching (March 28, 2026)</summary>

#### Platform API: Account Lockout & Brute Force Protection

Progressive account lockout on failed authentication attempts:

| Failures | Lock Duration                     |
| -------- | --------------------------------- |
| 5+       | 5 minutes                         |
| 10+      | 30 minutes                        |
| 20+      | Permanent (requires admin unlock) |

* **Per-IP rate limiting** on the token endpoint (60 requests/minute) for unauthenticated grant types (`api_key`, `client_credentials`, `google_oauth`)
* **Authenticated grants exempt** - `agent_session` and `refresh_token` grants bypass IP rate limiting since they already require valid credentials
* **Lockout checks** integrated into all grant types with entity-level and IP-level tracking
* **Successful auth clears counters** - lockout resets on valid authentication
* **Fail-open design** - if the cache is unavailable, auth proceeds normally

**Admin lockout endpoints** (requires `identity:admin` scope):

| Endpoint                                  | Description                            |
| ----------------------------------------- | -------------------------------------- |
| `POST /admin/lockout/unlock/{identifier}` | Unlock a locked account                |
| `GET /admin/lockout/locked-accounts`      | List all currently locked accounts     |
| `GET /admin/lockout/status/{identifier}`  | Check lockout status for an identifier |

Locked responses include `Retry-After` header for timed lockouts.

#### Classic API: Tool Repository Management

New endpoints for managing tool (action) source code repositories:

* **Repository access management** - Grant and revoke per-user access to tool repositories
* **Branch and commit history** - Browse branches and commits for tool repositories
* **File access** - Read files from tool repositories for code review and debugging
* **Access flag** - `enable_actions_access` field on user info for repository access gating

#### Classic API: Prompt Caching

LLM prompt caching for reduced latency and cost. The system separates conversation prompts into immutable history (cacheable across turns) and dynamic suffix (changes each turn), enabling cache hits on the static portion.

</details>

<details>

<summary>v0.9.12 - Analytics Engine: Command Center, Percentiles, Entity Intelligence, SSE (March 28, 2026)</summary>

#### Command Center

`GET /v1/{workspace_id}/command-center` - Single-pane workspace health dashboard.

Aggregates voice, pipeline, data quality, and identity metrics in a single call. Each section fails independently with `degraded_sections` reporting. Cached with 15s TTL. Alert derivation with 7 threshold checks.

| Section        | Key Metrics                                                                         |
| -------------- | ----------------------------------------------------------------------------------- |
| `voice`        | Active calls, escalated, calls today, avg quality score, escalation rate            |
| `pipeline`     | Source counts (healthy/degraded/failing), events last hour, outbound pending/failed |
| `data_quality` | Pending reviews, approval rate (7d), avg confidence, total entities, merges (7d)    |
| `identity`     | Active API keys, sessions, failed auths, locked accounts, MFA coverage              |
| `alerts`       | Level (warning/critical), code, message, section                                    |

#### Percentile Analytics

`GET /analytics/calls/advanced` - p50/p95/p99 for duration and quality, time series trends with p95 latency, breakdowns by service and call direction (inbound/outbound).

`GET /analytics/calls/comparison` - Period-over-period comparison. Takes two explicit date ranges and returns current metrics, previous metrics, and delta (absolute + percentage change).

#### Entity Intelligence (4 Endpoints)

| Endpoint                              | Description                                                       |
| ------------------------------------- | ----------------------------------------------------------------- |
| `GET /world/entities/{id}/graph`      | One-level relationship graph with neighbor metadata               |
| `GET /world/entities/{id}/provenance` | Data lineage, confidence history, merge events                    |
| `GET /world/entities/duplicates`      | Same\_as edges by ascending confidence, filterable by entity type |
| `GET /world/search`                   | ILIKE entity search with type, source, confidence filters         |

#### Real-Time SSE

`GET /v1/{workspace_id}/events/stream` - Server-Sent Events for real-time workspace updates.

Event types: `call.started`, `call.ended`, `call.escalated`, `pipeline.sync_completed`, `pipeline.error`, `review.submitted`, `alert`.

* 30s heartbeat keep-alive
* `Last-Event-ID` reconnection with replay from a circular buffer
* Backpressure handling: oldest events dropped on buffer overflow
* Falls back gracefully if the event system is unavailable

</details>

<details>

<summary>v0.9.11 - Call Intelligence Analytics Aggregation (March 27, 2026)</summary>

#### Analytics Endpoints (6 New)

Six analytics endpoints under `GET /v1/{workspace_id}/analytics/` that aggregate call intelligence data for dashboard visualizations:

| Endpoint                          | Description                                                                                   |
| --------------------------------- | --------------------------------------------------------------------------------------------- |
| `/analytics/call-quality`         | Quality score trends (avg/p50/p95), distribution (excellent/good/fair/poor), escalation rate  |
| `/analytics/emotion-trends`       | Dominant emotion distribution, valence/arousal trends over time                               |
| `/analytics/safety-trends`        | Escalation frequency, risk level distribution (low/medium/high/critical), safety match counts |
| `/analytics/latency`              | p50/p95/p99 by component (engine, TTFB, navigation, render), trends over time                 |
| `/analytics/tool-performance`     | Per-tool success/failure rates, failure trends, invocation counts                             |
| `/analytics/operator-performance` | Escalation rate trends, quality comparison (escalated vs non-escalated), connect time         |

**Common parameters** (all endpoints):

| Parameter               | Type               | Default | Description                                    |
| ----------------------- | ------------------ | ------- | ---------------------------------------------- |
| `days`                  | integer (1-90)     | 30      | Lookback window (overridden by explicit dates) |
| `date_from` / `date_to` | date               | none    | Explicit date range                            |
| `interval`              | `1h` / `1d` / `1w` | `1d`    | Time bucket for trend data                     |
| `service_id`            | string             | none    | Filter to a specific service                   |

All endpoints require `viewer+` permissions and are workspace-scoped.

</details>

<details>

<summary>v0.9.10 - Call Intelligence Endpoints (March 27, 2026)</summary>

#### Completed Call Intelligence

`GET /calls/{call_id}/intelligence` - Full intelligence profile for a completed call.

Joins persisted call intelligence summaries with per-turn data reconstructed from conversation history:

| Field                  | Description                                                                   |
| ---------------------- | ----------------------------------------------------------------------------- |
| `quality_score`        | Composite 0-100 penalty-based score                                           |
| `emotion_trajectory`   | Per-turn emotion, valence, and timestamps                                     |
| `risk_timeline`        | Per-turn risk score and conversation state                                    |
| `latency_profile`      | Per-turn latency waterfall (engine/nav/render/audio TTFB ms) + summary stats  |
| `tool_performance`     | Per-tool invocations, success/fail counts, avg duration                       |
| `conversation_quality` | Loop events and barge-in events with turn numbers                             |
| `*_summary`            | Full summaries (emotion, risk, latency, conversation, tool, safety, operator) |

Returns 404 if the call or intelligence data is not found.

#### Active Call Intelligence

`GET /calls/active/intelligence` - Active calls enriched with live intelligence overlay.

Reads call metadata and live intelligence snapshots (written after each caller speech turn):

| Field                | Description                           |
| -------------------- | ------------------------------------- |
| `current_emotion`    | Current detected emotion              |
| `current_valence`    | Current emotional valence             |
| `current_risk_score` | Current composite risk score          |
| `risk_trend`         | `rising`, `stable`, or `falling`      |
| `turn_count`         | Turns completed so far                |
| `escalation_active`  | Whether operator escalation is active |
| `current_state`      | Current context graph state           |

Supports `workspace_id` query parameter for filtering. Live intelligence data expires automatically when a call ends or the session is lost.

</details>

<details>

<summary>v0.9.9 - Call Intelligence Persistence (March 27, 2026)</summary>

#### Call Intelligence Summaries

The voice agent now computes and persists structured intelligence summaries at call end. Per-call operational telemetry is computed from in-memory session state and stored for analytics and dashboard use.

**Summaries persisted:**

| Summary                | Contents                                                                                 |
| ---------------------- | ---------------------------------------------------------------------------------------- |
| `emotion_summary`      | Dominant emotion, valence/arousal averages, peak negative, emotional shifts, final trend |
| `risk_summary`         | Composite risk score, level, contributing signals with weights                           |
| `latency_summary`      | Engine response time (avg/p50/p95), audio TTFB (avg/p50/p95), silence ratio              |
| `conversation_summary` | Turn count, states visited, loop count, barge-in count, completion reason                |
| `tool_summary`         | Total calls, success/failure counts, failure rate, per-tool breakdown                    |
| `safety_summary`       | Safety rule match count during the call                                                  |
| `operator_summary`     | Escalation status, operator timing, resolution                                           |

#### Composite Quality Score

Rule-based quality score (0-100) computed from intelligence summaries. Starts at 100 and applies penalties:

| Signal              | Threshold               | Penalty    |
| ------------------- | ----------------------- | ---------- |
| High latency        | p95 audio TTFB > 1000ms | -5 to -15  |
| Excessive silence   | Silence ratio > 0.2     | -10 to -20 |
| Caller barge-ins    | > 2 interruptions       | -5 to -15  |
| Agent loops         | Revisited states        | -10 to -20 |
| Operator escalation | Any                     | -10        |
| Tool failures       | Failure rate > 5%       | -5 to -15  |

Designed for dashboard filtering and trend analysis. The computation is pure (no I/O) - all data comes from in-memory session state. Write failures are logged but do not affect the caller or post-call processing.

#### Safety Match Tracking

The escalation handler now tracks safety rule matches during calls. The count is stored as `match_count` in `safety_summary` (with associated `actions`), and escalation status is tracked separately as `escalated` in `operator_summary`.

</details>

<details>

<summary>v0.9.8 - Pipeline Observability Dashboard (March 27, 2026)</summary>

#### Pipeline Dashboard API (9 Endpoints)

New read-only pipeline observability endpoints under `GET /v1/{workspace_id}/pipeline/` that power the pipeline dashboard. These combine live connector-runner operational state, cached source health data, and database aggregation queries.

| Endpoint                                  | Description                                                                                                        |
| ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
| `/pipeline/status`                        | Composite pipeline status: overall health, active polls, total events/entities, per-source status, all loop states |
| `/pipeline/sources`                       | Data sources with live health from cached poll results                                                             |
| `/pipeline/sources/{source_id}/history`   | Sync history bucketed by time window (`1h` or `1d`)                                                                |
| `/pipeline/sources/{source_id}/events`    | Paginated events for a specific source                                                                             |
| `/pipeline/outbound`                      | Per-sink outbound sync summary (synced/failed/pending counts)                                                      |
| `/pipeline/outbound/{data_source_id}/log` | Paginated outbound sync log for a specific sink                                                                    |
| `/pipeline/entity-resolution`             | Entity resolution metrics: merge counts + loop status                                                              |
| `/pipeline/review`                        | Review pipeline metrics: queue depth, approval rate, avg review time                                               |
| `/pipeline/throughput`                    | Event throughput time series across all sources                                                                    |

All endpoints require `viewer+` permissions and are workspace-scoped.

**Graceful degradation** - If the connector runner is temporarily unavailable, the dashboard still returns database-backed metrics (event counts, sync history, review stats). Only live loop states and active poll counts are omitted.

#### Connector Runner Health Monitoring

The connector runner now exposes an operational health snapshot covering all background loops and per-source connection status:

* **Overall status** - `healthy`, `degraded`, or `starting` based on aggregate loop health
* **Per-source poll metrics** - Last poll time, duration (ms), event count, error, connection health
* **Loop states** - Entity resolution, review, outbound dispatch, subscriber, reconciliation
* **Connection health tracking** - Consecutive poll errors tracked per source. Unhealthy after 3+ failures, auto-recovers on success
* **Real-time source health** - Cached poll results with 1-hour TTL, available through the pipeline dashboard endpoints

</details>

<details>

<summary>v0.9.7 - athenahealth EHR, SSO Login &#x26; Safety Filters (March 26, 2026)</summary>

#### athenahealth EHR Integration

New dedicated EHR connector type for athenahealth with bidirectional sync:

**Inbound** - Real-time data ingestion via FHIR Subscription webhooks. The connector runner receives event notifications from athenahealth, fetches the full resource, maps it to FHIR R4, and writes to the world model. Supported resource types: Patient, Appointment, Department, Provider, Medication, Allergy, Insurance.

**Outbound** - Write-back through the handler registry. Supported operations:

| Operation          | What It Does                                                 |
| ------------------ | ------------------------------------------------------------ |
| Patient create     | Create new patient record in athenahealth                    |
| Patient update     | Merge demographics with existing record                      |
| Appointment book   | Book appointment (patient resolved from entity external IDs) |
| Appointment cancel | Cancel existing appointment                                  |

The `athenahealth` connector type is now available in workspace configuration alongside `fhir_store`, `revolution`, and `hubspot`.

#### SSO Login via Identity Federation

The Platform API now supports single sign-on (SSO) via identity federation. Users authenticate with their organization's identity provider, and the platform issues a JWT in the same token format as API key authentication.

* **`google_oauth` grant type** on `POST /token` - Exchange an identity provider authorization code for an Amigo JWT
* **Multi-workspace selection** - Users with credentials on multiple workspaces receive a workspace list; re-call with `workspace_id` to complete authentication
* **Auto-provisioning** - Domain-based provision policies automatically create credentials for new users on first SSO login. Global policies expand to all workspaces
* **Refresh tokens** - Rotated on use with configurable idle timeout for compliance

#### Identity Admin API

New admin endpoints for identity management (requires `identity:admin` scope):

| Endpoint              | What It Does                                  |
| --------------------- | --------------------------------------------- |
| Credential CRUD       | Create, list, update, delete API credentials  |
| Credential rotation   | `POST /admin/credentials/{id}/rotate-secret`  |
| Provision policies    | CRUD for domain-based auto-provisioning rules |
| Federation identities | CRUD for SSO identity mappings                |
| User listing          | List users across workspaces                  |
| Audit log             | Query authentication and admin action history |

All list endpoints support pagination (`limit` + `has_more` + `continuation_token`), case-insensitive search, and sort (`+field`/`-field` syntax).

#### Per-Service Safety Filter Toggle

New `safety_filters_enabled` field on service configuration (defaults to `true`). When set to `false`, conversation monitoring (monitor concepts, triage, accumulation) is bypassed for that service while independent risk scoring remains active. Useful for non-clinical services or internal testing environments.

New accumulation tuning fields on safety configuration: `accumulation_mild_threshold` (minimum concern level counted toward accumulation, default 1) and `accumulation_fast_track_level` (concern level that bypasses accumulation and triggers immediate action, default 3).

</details>

<details>

<summary>v0.9.6 - Ontology Migration, World Tools &#x26; Write Safety (March 24, 2026)</summary>

#### Entity Type Ontology Migration (Breaking)

{% hint style="danger" %}
**Breaking change**: Entity types in the world model have migrated from domain-role names to ontological categories. The `entity_type` field on entities and in API responses has changed:

* `patient`, `practitioner`, `operator` → `person`
* `location` → `place`

Event-level FHIR resource types (`fhir_resource_type`) are **unchanged** - events still carry `Patient`, `Practitioner`, `Location`, etc. The `outbound_entity_types` field in data source configuration also remains as FHIR resource type names.
{% endhint %}

The world model now uses ontological entity types instead of domain-specific roles. A single `person` entity can represent a patient, practitioner, or both. The projection function detects roles from the underlying event data and produces role-aware output with the relevant sections for each role.

**Migration**: A SQL migration converts existing entity\_type values in entities, events, and entity\_graph rows. If you filter or query by entity\_type in your code, update the values:

| Before         | After                      |
| -------------- | -------------------------- |
| `patient`      | `person`                   |
| `practitioner` | `person`                   |
| `operator`     | `person`                   |
| `location`     | `place`                    |
| `organization` | `organization` (unchanged) |
| `appointment`  | `appointment` (unchanged)  |
| `deal`         | `deal` (unchanged)         |

#### Organization and Deal Entity Types

Organization and Deal are now first-class entity types in the world model with dedicated projections:

* **Organization** - Aggregates company data (name, phone, domain, industry, address, employee count) from CRM and EHR sources. Fixes entity resolution for CRM company records that were previously orphaned.
* **Deal** - Stores deal/opportunity data (name, amount, stage, pipeline, close date) as native entities instead of encoding through FHIR Basic resources.

Both types are included in entity resolution, auto-enrichment, and CRM views.

#### New FHIR Views Endpoint

* `GET /v1/{ws}/fhir/views/organizations` - FHIR Organization resources by data source

#### New Voice Agent Tools

Three new built-in tools for clinical data during live calls:

| Tool                    | Type  | What It Does                                                                    |
| ----------------------- | ----- | ------------------------------------------------------------------------------- |
| **Medication lookup**   | Read  | Retrieves a patient's active medications with dosage, frequency, and prescriber |
| **Prescription refill** | Write | Creates a refill request at voice confidence for review before EHR sync         |
| **Encounter lookup**    | Read  | Retrieves encounter history (visits, diagnoses, participating practitioners)    |

#### Duplicate Write-Tool Prevention

The voice agent now prevents duplicate write-tool invocations during async execution. When the agent re-invokes the same write tool with identical parameters while the first call is still in-flight (e.g., during barge-in recovery), the duplicate call is blocked and returns immediately. This prevents double-booking appointments, duplicate insurance records, and other data integrity issues from concurrent tool calls.

#### Session Event Injection Idempotency

The session event injection endpoint now deduplicates identical events within a 30-second window. When the platform API retries an injection due to a network timeout, the duplicate is detected and returns a `deduplicated` status instead of triggering a second agent response. This prevents the agent from responding to the same event twice.

#### Ontology Foundation

{% hint style="info" %}
This is a foundational change that does not affect current API contracts. Entity types and projections continue to work as before.
{% endhint %}

The world model now categorizes entity types into ontological categories (person, organization, place, event, transaction). This enables role-aware projections where merged entities (e.g., a patient who is also a CRM contact) produce output that includes all relevant role-specific sections. The projection registry dispatches to the correct projection based on entity type, making the system extensible without modifying core logic.

</details>

<details>

<summary>v0.9.5 - Developer Portal API Improvements (March 24, 2026)</summary>

#### Search on All List Endpoints

All Platform API list endpoints now support a `search` query parameter for case-insensitive filtering:

| Endpoint                    | Fields Searched         |
| --------------------------- | ----------------------- |
| `GET /v1/{ws}/skills`       | name, description, slug |
| `GET /v1/{ws}/integrations` | name, display\_name     |
| `GET /v1/{ws}/agents`       | name, description       |
| `GET /v1/{ws}/hsms`         | name                    |
| `GET /v1/{ws}/services`     | name, description       |

#### Delete Dependency Safety

Delete operations now check for downstream dependencies before proceeding. If a resource is referenced by another entity, the API returns `409 Conflict` with a list of referencing entity names.

| Resource      | Checks For         |
| ------------- | ------------------ |
| Skills        | HSM references     |
| Integrations  | Skill references   |
| HSMs / Agents | Service references |

#### Rate Limit Headers

All rate-limited endpoints now include response headers on every request (not just on 429 responses):

* `X-RateLimit-Limit` - Maximum requests per window
* `X-RateLimit-Remaining` - Requests remaining in current window
* `X-RateLimit-Reset` - Seconds until window resets

#### Standardized Error Codes

All error responses now include a machine-readable `error_code` field:

```json
{
  "error_code": "RESOURCE_NOT_FOUND",
  "message": "Skill not found",
  "details": {},
  "request_id": "req_abc123"
}
```

This applies to all domain exceptions and global error handlers.

#### Connector Entity Views

New endpoints for browsing connected data sources and their entities:

* `GET /v1/{ws}/world/connectors` - Overview of all connected data sources with entity counts, sync status, and health
* `GET /v1/{ws}/world/connectors/{data_source_id}/entities` - Paginated entity list per data source with type and name filtering
* `GET /v1/{ws}/world/connectors/{data_source_id}/resources` - FHIR resources by data source with resource type filtering

Entity list endpoints support `source` and `source_system` filters for scoping by data origin.

#### CRM Routes

New CRM-specific endpoints for workspaces with CRM integrations:

* `GET /v1/{ws}/crm/status` - Contact/company/deal counts, sync health, connector info
* `GET /v1/{ws}/crm/contacts` - Search contacts by name, email, or phone
* `GET /v1/{ws}/crm/contacts/{id}` - Full contact state with timeline
* `GET /v1/{ws}/crm/contacts/{id}/timeline` - Activity feed (calls, meetings, notes, syncs)
* `GET /v1/{ws}/crm/companies` - Search companies by name
* `GET /v1/{ws}/crm/companies/{id}` - Company detail
* `GET /v1/{ws}/crm/deals` - List deals with stage, amount, pipeline
* `GET /v1/{ws}/crm/deals/{id}` - Deal detail

#### Request Validation Hardening

{% hint style="warning" %}
**Potentially breaking**: Request fields that previously accepted arbitrary strings now enforce length bounds and format constraints. Requests with oversized or malformed fields will receive `422 Unprocessable Entity` responses.
{% endhint %}

All Platform API request models now enforce bounded string types and Literal enums:

| Constraint          | Applies To                                                                                    | Limit                      |
| ------------------- | --------------------------------------------------------------------------------------------- | -------------------------- |
| `NameString`        | Resource names (agents, skills, services, HSMs, integrations, data sources, monitor concepts) | 1-256 characters           |
| `DescriptionString` | Resource descriptions                                                                         | 0-2,000 characters         |
| `SearchString`      | All `search` query parameters                                                                 | 1-200 characters           |
| Literal enums       | Operator type, connection method, role, status, call mode, region, rule type                  | Restricted to valid values |
| E.164 validation    | Phone numbers in workspace test callers and purchase requests                                 | Standard E.164 format      |
| `max_length`        | Integration URLs, phone number notes, review queue reasons, batch IDs                         | Per-field limits           |

Search parameters are sanitized to prevent wildcard injection in queries.

#### Sort Parameter Support

All list endpoints now support a `sort_by` query parameter for controlling result ordering. Each resource type defines its own set of sortable fields. Format: `field_name` (ascending) or `-field_name` (descending).

</details>

<details>

<summary>v0.9.4 - Unified Connector Design (March 23, 2026)</summary>

#### Connector Type Vocabulary

The connector framework now uses formal types for data source and workspace configuration:

| Type             | Values                                                         | Purpose                                                         |
| ---------------- | -------------------------------------------------------------- | --------------------------------------------------------------- |
| `source_type`    | `ehr`, `fhir_store`, `crm`, `rest_api`, `webhook`, `file_drop` | Data source category; determines which adapter handles polling  |
| `connector_type` | EHR vendor types, `fhir_store`, `hubspot`, `salesforce`        | Workspace-level setting; determines outbound write-back routing |

#### Workspace `connector_type` (Breaking)

{% hint style="danger" %}
**Breaking change**: The workspace `ehr_type` field has been renamed to `connector_type`. The value `google_fhir` is now `fhir_store`. The `default_fhir_store_url` field has been removed from the Workspace model; FHIR store URLs are now configured in the data source's `connection_config`.
{% endhint %}

**Migration**: The rename is handled automatically on our end. Update any code that references `ehr_type` or `default_fhir_store_url` on workspace objects.

#### FHIR Store Promoted to Source Type

FHIR stores are now a first-class source type (`fhir_store`) instead of being treated as a special case of `ehr`. Data source configuration for FHIR stores uses typed fields:

* `fhir_store_url` - FHIR store endpoint URL
* `resource_types` - FHIR resource types to poll (defaults: Patient, Practitioner, Location, Appointment, Coverage, Encounter, Condition, AllergyIntolerance, MedicationRequest, Schedule)
* `poll_cadences` - Per-resource poll intervals in seconds
* `outbound_entity_types` - Entity types to write back

#### Multi-Sink Outbound

Workspaces can now write back to multiple external systems simultaneously. For example, a workspace can sync patient and appointment data to the EHR while syncing patient and contact data to the CRM. Each outbound sink is configured independently with its own entity types, and sync progress is tracked per-sink. A failure writing to one system does not block writes to others.

#### Cross-Source Entity Merge

When a workspace has multiple data sources, entity resolution now automatically detects and merges entities that represent the same real-world thing across sources. Patients are matched by phone number, email, or name + date of birth. Practitioners are matched by NPI or name + specialty. Merged entities produce a unified projection reflecting data from all connected systems.

#### Outbound Handler Registry

Outbound write-back routes through a handler registry that maps each sink's `connector_type` to the correct write-back handler, used by both the real-time sync path and the reconciliation safety net.

#### Configurable Poll Cadences

EHR and CRM data source configs now support a `poll_cadences` field: a dictionary mapping resource or object names to poll intervals in seconds. When omitted, adapters use their built-in defaults.

#### Removed

* `google_fhir` as a `connector_type` value (replaced by `fhir_store`)
* `ehr_type` field on Workspace (use `connector_type` instead)
* `default_fhir_store_url` field on Workspace (configure in data source `connection_config` instead)

</details>

<details>

<summary>v0.9.2 - Session Event Injection &#x26; Operator Guidance (March 23, 2026)</summary>

#### Session Event Injection

External systems can now inject events into active voice sessions in real time. The agent processes injected events and speaks a natural response without navigating the context graph state machine.

**New Endpoints:**

* `POST /voice-agent/sessions/{call_sid}/event` - Inject external event or guidance into active session (voice agent)
* `GET /v1/{workspace_id}/sessions/active` - List active voice sessions (platform API)
* `POST /v1/{workspace_id}/sessions/{call_sid}/inject` - Inject event into active session (platform API)

**Two event types:**

* `external_event` - Factual information (queues behind current speech)
* `guidance` - Instructions to the agent (interrupts current speech)

Cross-instance delivery ensures injection works regardless of which server instance handles the call. The connection reconnects automatically on transient infrastructure interruptions.

#### Operator Guidance

Operators can now send text guidance to the agent during live calls without taking over:

* `POST /v1/{workspace_id}/operators/{id}/send-guidance` - Send guidance scoped to operator identity (`Operator:Update` permission)

The agent receives the guidance as an injection event, interrupts its current speech, and acts on the instruction immediately. The caller does not know a human intervened.

#### Test-Call Scenarios

The test-call WebSocket endpoint now supports configurable scenarios for developer testing:

* `scenario` parameter: `inbound` (default), `outbound` (task context), `silent` (no greeting)
* `system_prompt` parameter: freeform prompt override for custom scenario testing
* `caller_id` and `outbound_task_entity_id` parameters for outbound scenario context

</details>

<details>

<summary>v0.9.0 - Voice Control Plane &#x26; Acoustic Intelligence (March 21, 2026)</summary>

#### Voice Control Plane

New workspace-level voice settings API for configuring the entire voice pipeline from a single endpoint.

**New Endpoints:**

* `GET /v1/workspaces/{workspace_id}/voice-settings`
* `PUT /v1/workspaces/{workspace_id}/voice-settings`

**Configurable Fields:** `voice_id`, `tone` (8 emotions), `speed`, `volume`, `language`, `keyterms` (speech recognition boost, max 200), `correction_categories` (domain hints for AI audio correction), `pronunciation_dict_id`, `sensitive_topics` (preemptive tone softening), `post_call_analysis_enabled`, `transcript_correction_enabled`.

#### Structured Action Model

Context Graph action states now use structured objects instead of plain strings.

**What Changed:**

* `actions` field: `list[string]` → `list[Action]` where `Action = { description: string, filler_hint: string | null }`
* `exit_conditions` filler field: `filler_hints: list[string]` → `filler_hint: string | null`
* Existing data is migrated automatically

**Filler hints** are semantic directions that guide filler generation alongside emotional context and latency state. They are not literal audio filler text.

#### Version Set Management (Platform API)

New endpoint for upserting version sets on workspace-scoped services:

* `PUT /v1/{workspace_id}/services/{service_id}/version-sets/{name}`

Includes server-side validation that pinned agent and context graph versions exist. The `edge` version set remains immutable (always latest).

#### Acoustic Intelligence Enhancements

* **Proactive emotional intelligence**: Detects sensitive topics from context graph actions and preemptively shifts to sympathetic tone before the caller shows distress
* **Tone momentum**: Maintains voice emotion continuity when real-time signals are weak, preventing jarring tone shifts
* **Enriched audio correction**: Speech comparison with confidence-gated spelling for improved transcript accuracy

#### Instant Greeting

Voice calls now begin generating the agent greeting during ring time, reducing perceived time-to-first-word to near-zero when the caller picks up.

#### Data Access (MCP)

New Model Context Protocol server for workspace data access. Compatible with any MCP client.

**Endpoint:** `POST /v1/mcp` (MCP Streamable HTTP transport)

**Available Tools:**

* `sql_query` - Execute read-only SELECT queries (1,000 row limit, 30s timeout)
* `describe_query` - Return query schema without execution
* `tables` - List available workspace tables
* `table_schema` - Column names and types for a table
* `sample_data` - Preview rows from a table
* `table_detail` - Extended table metadata and statistics
* `profile_column` - Statistical column profile (min, max, nulls, distinct, distribution)

Authentication: Bearer token plus `X-Workspace-Id` header. Stateless transport, so no session affinity is required.

{% hint style="info" %}
**New feature**: Structured Action model replaces plain string actions in Context Graphs. `actions` field changed from `list[string]` to `list[Action]`. Existing data is migrated automatically.
{% endhint %}

</details>

<details>

<summary>v0.8.4 - Data Pipeline &#x26; World Model (March 14, 2026)</summary>

#### Connector Runner

New data ingestion and sync engine providing a bidirectional pipeline between external systems (EHR platforms, REST APIs, FHIR stores) and the workspace world model.

**Capabilities:**

* Continuous, scheduled, manual, and webhook-triggered polling
* Deterministic entity resolution from FHIR references
* Confidence-gated EHR write-back (0.3 raw → 0.7 verified → 1.0 authoritative)
* Automated review loop for voice agent data quality
* Outbound call dispatch with business-hours gating

#### Data Sources & Unification Rules

New CRUD endpoints for managing external data feeds and field mapping rules:

* `POST/GET/PATCH/DELETE /v1/{workspace_id}/data-sources`
* Data source status and sync history endpoints
* `POST/GET/PATCH/DELETE /v1/{workspace_id}/unification-rules`

#### World Model

Unified entity graph aggregating data from all connected sources:

* Entity and event type registries with counts
* Entity query, detail, timeline, and relationship endpoints
* Sync queue management with retry capabilities
* Generic data query proxy with structured filtering

#### Safety & Monitor Concepts

New semantic monitoring system for conversation safety:

* `POST/GET/PATCH/DELETE /v1/{workspace_id}/monitor-concepts` - embedding-based concept detection
* Regulation templates with one-click apply
* Workspace safety configuration (triage, thresholds, accumulation windows)

#### Analytics & Review Queue

* Usage summary, call statistics, event breakdown, data quality endpoints
* Call listing with phone volume analytics
* Recording access with signed URLs
* QA review queue with approve/correct/reject workflow

</details>

<details>

<summary>v0.8.3 - Platform API &#x26; Operator System (March 10, 2026)</summary>

#### Platform API (New API Surface)

The **Platform API** at `api.platform.amigo.ai` is now available for workspace-scoped configuration and operations. This is a **separate API surface** from the Classic API, built for enterprise voice and traditional healthcare deployments.

**Key differences from Classic API:**

* **Workspace-scoped** (not organization-scoped)
* **API key authentication** (not per-user JWT)
* **Base URL:** `api.platform.amigo.ai/v1`

**Core resource endpoints:**

* Workspaces: tenant management and provisioning
* API Keys: workspace-scoped authentication with RBAC (owner, admin, member, viewer)
* Agents: persona definitions with immutable versioning
* Skills: LLM-backed companion agent capabilities with 4 execution tiers
* Services: agent plus context graph bindings with version sets
* Integrations: external API connections with health checks and endpoint testing
* Phone Numbers: voice line management, purchasing, and call forwarding

#### Operator System

Human-in-the-loop escalation management for live voice calls:

* Operator CRUD with availability tracking
* Call join (listen mode and takeover mode)
* Real-time mode switching: agent suspends during takeover, resumes on leave
* Escalation management (active, stats, lifecycle events)
* Operator dashboard and performance metrics
* Full audit log of all operator actions

#### Skill References

New endpoint to discover which context graphs and services depend on a skill:

* `GET /v1/{workspace_id}/skills/{skill_id}/references`

#### Call Forwarding

First-class call forwarding management on phone numbers:

* `PUT /v1/{workspace_id}/phone-numbers/{id}/forwarding`
* `DELETE /v1/{workspace_id}/phone-numbers/{id}/forwarding`

#### FHIR Clinical Data

FHIR endpoints for healthcare workspaces:

* Patient search, timeline, and clinical summary
* FHIR resource CRUD (search, get, create, update)
* Bundle import for bulk data loads
* Sync status and failure monitoring

</details>

<details>

<summary>v0.8.2 - SQL Validation Enhancement (February 18, 2026)</summary>

**SQL Validation Enhancement**

The `SubmitSQLQuery` endpoint now supports additional SQL syntax:

* `DESCRIBE TABLE EXTENDED <table> AS JSON`
* `DESCRIBE QUERY <select>`

These statements were previously rejected by the SQL validator.

</details>

<details>

<summary>v0.8.1 - ToolInvocation Inputs &#x26; API Key Webhook (February 11, 2026)</summary>

**ToolInvocation `inputs` Field**

Tool invocations now include the input parameters passed to the tool, for better debugging and auditing.

**What Changed**:

* New required field `inputs: dict[str, Any]` added to `ToolInvocation` model
* Exposed in `GetToolInvocations` and `SearchToolInvocations` response schemas
* Contains the full input parameters that were passed to the tool at invocation time

**API Key Expiration Webhook**

New webhook event type `api-key-expiration-soon` fires before API keys expire.

**Schema**: `APIKeyExpirationSoonWebhook` with fields: `type`, `org_id`, `api_key_id`, `expiration_time` (UTC datetime), `idempotent_key`

Subscribe via Settings → Webhooks and enable the 'API Key Expiration Soon' event type. Notifications fire at 14, 7, and 1 day before expiration.

{% hint style="warning" %}
**Deprecation**: The `ToolInvocation.logs` field is deprecated and will be removed from API responses in a future release.
{% endhint %}

**ToolInvocation `logs` Field Deprecated**

**Impact**: Low | **Action Required**: No immediate action

The `logs` field has been removed from the internal `ToolInvocation` model but is still returned in API responses defaulting to `[]`. This field will be removed from API responses in a future release.

</details>

<details>

<summary>v0.8.0 - GPT-5 Removal, Strict Output &#x26; Observability (February 4, 2026)</summary>

**HTTP Basic Authentication (Experimental)**

A new authentication method is available alongside the existing Bearer token flow.

**Format**: `Authorization: Basic base64({org_id}_{user_id}:{firebase_token})`

**Key Features**:

* Supports cross-organization authorization (user in org A authenticating against org B)
* Marked as experimental (subject to change)

**Prompt Log Tracings**

LLM prompt logs now include detailed tracing information for performance monitoring.

**New `TracingEvent` Fields**:

| Field                 | Type  | Description                      |
| --------------------- | ----- | -------------------------------- |
| `input_tokens`        | int   | Input token count                |
| `output_tokens`       | int   | Output token count               |
| `cached_tokens`       | int   | Cached token count               |
| `time_to_first_token` | float | Seconds to first token           |
| `total_duration`      | float | Total generation time in seconds |

Implemented across all LLM providers (OpenAI, Anthropic, AWS Bedrock, Google Gemini).

**OpenAI Strict Structured Output**

`strict: True` is now enabled on all OpenAI tool/function definitions, enforcing schema-compliant outputs. Internal schemas have been adjusted for compatibility.

**Arabic Accent Override Support**

Real-time voice conversations now support specifying Arabic accent variants via feature flags with improved language handling and error messages.

**Audio Keyterms in Voice Transcription**

The `keyterms` parameter is now passed to the transcription engine for English-language voice conversations, improving transcription accuracy for domain-specific terminology.

#### Breaking Changes

{% hint style="danger" %}
**Breaking change**: GPT-5 family models (`azure_gpt-5-2025-08-07`, `azure_gpt-5-mini-2025-08-07`, `azure_gpt-5-nano-2025-08-07`) have been removed. Migrate to `azure_gpt-5.1-2025-11-13` or OpenAI GPT-5.2.
{% endhint %}

**GPT-5 Family Models Removed**

**Impact**: High | **Action Required**: Yes, if using GPT-5 models

The following LLM types have been removed:

* `azure_gpt-5-2025-08-07`
* `azure_gpt-5-mini-2025-08-07`
* `azure_gpt-5-nano-2025-08-07`

**Migration**: Use `azure_gpt-5.1-2025-11-13` or OpenAI GPT-5.2 as replacements.

**`llm_provider_request_id` Renamed to Array**

**Impact**: Medium | **Action Required**: If parsing prompt log events

`FullResultAvailableEvent.llm_provider_request_id: str` changed to `llm_provider_request_ids: list[str]` - both the field name (singular → plural) and type (string → list of strings) have changed.

</details>

<details>

<summary>v0.7.0 - Permission System &#x26; Voice Configuration (January 30, 2026)</summary>

**Temporary Permission Grants**

Grant time-limited permissions to users without modifying their role. Temporary grants automatically expire, ideal for support escalations or temporary elevated privileges.

**New Endpoints**:

| Endpoint                                      | Method | Description                                 |
| --------------------------------------------- | ------ | ------------------------------------------- |
| `/v1/{org}/role/temporary_permission_grant/`  | POST   | Create a temporary permission grant         |
| `/v1/{org}/role/temporary_permission_grants/` | GET    | List and filter temporary permission grants |

**Key Features**:

* **Duration Control**: Grants last from 1 minute to 3 days (ISO8601 duration format)
* **Tags & Justification**: Attach metadata tags and require justification for audit trails
* **Filtering**: Query grants by user, permission, creator, expiration status, and tags
* **Permission Validation**: Creator must have greater privileges than the grant being issued

**Example Request** (Create Grant):

```json
POST /v1/{org}/role/temporary_permission_grant/
{
  "user_id": "support-agent-123",
  "duration": "PT2H",
  "permission_grant": {
    "permission_name": "Conversation:GetConversation",
    "conditions": {}
  },
  "tags": {"reason": "support_ticket", "ticket_id": "TICKET-456"},
  "justification": "Support agent needs access to review customer conversation for ticket TICKET-456"
}
```

**New Permissions**:

* `Role:CreateTemporaryPermissionGrant` - Required to create grants
* `Role:GetTemporaryPermissionGrant` - Controls which grants are visible in list queries

**Conversation Tags**

Tag conversations with key-value metadata for organization, filtering, and analytics.

**New Capabilities**:

* **Create with Tags**: Pass `tags` object when creating conversations
* **Filter by Tags**: Use `tags` query parameter in `GetConversations`
* **Modify Tags**: New `POST /{conversation_id}/tags/` endpoint for adding, updating, and removing tags

**Tag Constraints**:

* Maximum 20 tags per conversation
* Keys and values: alphanumeric characters, underscores, and spaces only
* Values can be `null` for flag-style tags

**Example** (Modify Tags):

```json
POST /v1/{org}/conversation/{conversation_id}/tags/
{
  "updates": {"department": "sales", "priority": "high"},
  "deletes": ["old_tag"]
}
```

**Change Tool Candidates Dynamic Behavior Action**

Dynamic behaviors now support the `change-tool-candidates` action type, allowing you to dynamically modify available tools based on conversation context.

**Example Configuration**:

```json
{
  "action": {
    "type": "change-tool-candidates",
    "tool_specs": [
      {
        "tool_id": "payment_processor",
        "version_constraint": ">=2.0.0"
      }
    ]
  }
}
```

**SSML Tag Support**

Audio fillers now support Speech Synthesis Markup Language (SSML) tags for fine-grained control over speech output in voice conversations.

**Supported Tags**:

* `<prosody>` - Control rate (0.6-1.5), volume (0.5-2.0)
* `<break>` - Insert pauses (e.g., `time="500ms"`)
* `<emotion>` - Apply emotional tone

**Constraints**: Audio fillers must be under 512 characters including SSML markup.

**Arabic Language Support**

Added `arabic` to supported voice languages for voice-enabled conversations.

**API Key Names**

API keys now support a `name` field for easier identification and management.

**Breaking Change**: The `name` parameter is now **required** when calling `CreateAPIKey`. Previously, a default name was generated automatically.

**Migration**:

```json
// Before (worked with default)
POST /v1/{org}/api_key/
{}

// After (name required)
POST /v1/{org}/api_key/
{
  "name": "Production Backend Key"
}
```

**Keyterms Field**

The `keyterms` field has been added to `GetService` and `GetUser` responses, providing extracted key terminology for context-aware interactions.

**New Permission: FinishConversation**

Added `Conversation:FinishConversation` permission for granular control over who can end conversations programmatically.

#### Breaking Changes

{% hint style="danger" %}
**Breaking changes in v0.7.0**: Voice config fields removed (`stability`, `similarity_boost`, `style`), deny permission grants removed, role inheritance removed, `CreateAPIKey` now requires a `name` parameter, and several legacy endpoints removed.
{% endhint %}

**Voice Configuration Simplified**

**Impact**: High | **Action Required**: Yes

Voice configuration has been simplified. Legacy voice configuration fields have been removed.

**What Changed**:

* Legacy fields removed from `VoiceConfig`: `stability`, `similarity_boost`, `style`
* Agent `voice_config` now only requires `voice_id`

**Migration**:

```json
// Updated voice_config format
"voice_config": {
  "voice_id": "your-voice-id"
}
```

1. Remove `stability`, `similarity_boost`, and `style` fields from voice configurations
2. Test voice output quality with the updated configuration

**Deny Permission Grants Removed**

**Impact**: High | **Action Required**: If using deny grants

The `"deny"` grant type has been removed from the permission system. Permissions now follow an allow-only model.

**What Changed**:

* `grant_type: "deny"` is no longer valid in role permission grants
* Existing deny grants will be ignored
* Permission checks use allow-only logic

**Migration**: Restructure role permissions to achieve the same access control using allow grants only. Consider using more specific conditions instead of broad allows with selective denies.

**Role Inheritance Removed**

**Impact**: Medium | **Action Required**: If using role inheritance

Roles no longer support inheritance from parent roles. Each role must define its complete permission set.

**What Changed**:

* `parent_role` field removed from role schema
* `CreateRole` and `ModifyRole` no longer accept parent role references
* Permissions are not inherited from other roles

**Migration**: Flatten inherited permissions directly into each role definition. Review and consolidate role hierarchies.

**GetRoles Response Schema Change**

**Impact**: Low | **Action Required**: If parsing `permission_grants` field

The backwards-compatibility `permission_grants` field has been removed from `GetRoles` responses. Use the `permissions` field instead.

**GetOrganizationMetrics Endpoint Removed**

**Impact**: Low | **Action Required**: If using this endpoint

The `GET /v1/{org}/metric/organization/` endpoint has been removed.

**External Event Message Field Rename**

**Impact**: Low | **Action Required**: If using external events

The `external_event_message` fields in `InteractWithConversation` request bodies have been renamed for consistency. Update your request payloads to use the corrected field names.

**CreateInvitedUser and UpdateUserInfo Endpoints Removed**

**Impact**: Low | **Action Required**: If using these endpoints

These legacy user management endpoints have been removed:

* `CreateInvitedUser` → Use `CreateUser` endpoint instead
* `UpdateUserInfo` → Use `ModifyUser` endpoint instead

#### Deprecations

{% hint style="warning" %}
**Deprecation**: The `tool_repo` field is deprecated. Use the `CreateToolVersion` endpoint to publish tool versions instead.
{% endhint %}

**tool\_repo Field**

The `tool_repo` field is deprecated and will be removed in a future release. Use the `CreateToolVersion` endpoint to publish tool versions instead.

**Audio Quality Adjustment**

The ability to adjust voice response audio quality (bitrate) has been deprecated. The platform now automatically selects optimal audio quality settings.

</details>

<details>

<summary>v0.6.1 - ToolCallState &#x26; Python 3.14 (November 20, 2025)</summary>

**ToolCallState Context Graph State Type**

The API supports a `ToolCallState` (type: `"tool-call"`), which enforces execution of a designated tool call before allowing state transitions. This provides deterministic control over agent workflows where specific tools must be invoked.

**Why This Matters**: Previous state types (ActionState, DecisionState, ReflectionState) allowed agents to opportunistically choose whether to call tools. ToolCallState guarantees a specific tool will be called, making it ideal for required data fetches, mandatory validations, or enforced workflow checkpoints.

**Key Features**:

* **Designated Tool Execution**: Specify exactly which tool must be called using `designated_tool`
* **Execution Guidance**: Provide `designated_tool_call_objective`, `designated_tool_call_context`, `designated_tool_call_guidances`, and `designated_tool_call_validations` to control how the agent uses the tool
* **Audio Support**: Configure audio fillers during tool call parameter generation with `designated_tool_call_params_generation_audio_fillers`
* **Additional Opportunistic Tools**: Allow supplementary tool calls via `tool_call_specs` while ensuring the designated tool is always executed
* **Persistence Requirement**: Designated tools must use `result_persistence: "persisted"` (validated at Context Graph creation)

**Example Configuration**:

```json
{
  "type": "tool-call",
  "name": "fetch_user_data",
  "next_state": "process_data",
  "designated_tool": {
    "tool_id": "get_user_profile",
    "version_constraint": ">=1.0.0",
    "additional_instruction": "Fetch the complete user profile",
    "result_persistence": "persisted"
  },
  "designated_tool_call_objective": "Retrieve user profile data for personalization",
  "designated_tool_call_context": "User has already authenticated and provided consent",
  "designated_tool_call_guidances": [
    "Use the user_id from the conversation context",
    "Request all profile fields including preferences"
  ],
  "designated_tool_call_validations": [
    "Verify user_id is present before calling",
    "Confirm response includes required fields: name, email, preferences"
  ],
  "designated_tool_call_params_generation_audio_fillers": [
    "Let me look up your information...",
    "Retrieving your profile..."
  ],
  "designated_tool_call_params_generation_audio_filler_triggered_after": 2.0,
  "tool_call_specs": []
}
```

**Use Cases**:

| Use Case                     | Why Use ToolCallState                               |
| ---------------------------- | --------------------------------------------------- |
| Required data fetches        | Ensure critical data is loaded before proceeding    |
| Mandatory compliance checks  | Guarantee validation steps are never skipped        |
| Workflow checkpoints         | Enforce specific actions at defined workflow stages |
| Authentication/authorization | Ensure security checks are always performed         |

**Related Documentation**:

* [Context Graph State Types](https://docs.amigo.ai/developer-guide/core-api/classic-api/agents-and-context-graphs) - Overview of all state types

**Python 3.14 Requirement for Tools**

The platform now requires Python 3.14 for all new tools and tool versions. This aligns tool development with the backend runtime environment upgrade completed in early November 2025.

**What Changed**:

* All new tools must specify `requires-python = ">=3.14"` in their `pyproject.toml`
* Ruff configuration must target Python 3.14: `target-version = "py314"`
* Existing deployed tools continue to function without modification

**Who This Affects**:

* ✅ **Action Required**: Developers creating new tools or publishing new tool versions
* ⚠️ **No Action Required**: Existing deployed tools and tool versions

**Migration Guide**:

Update your tool's `pyproject.toml` file before publishing new versions:

```toml
[project]
name = "my-tool"
version = "1.0.0"
requires-python = ">=3.14"  # Updated from >=3.13
# ... other settings

[tool.ruff]
target-version = "py314"  # Updated from py313
```

**Benefits**:

* Access to Python 3.14's performance improvements and new language features
* Consistent runtime environment between tool development and execution
* Better type checking and static analysis capabilities

</details>

<details>

<summary>v0.6.0 - Tool Call Result Persistence (November 13, 2025)</summary>

**Tool Call Result Persistence Implementation**

The `result_persistence` property gives you fine-grained control over tool call result visibility across agent interactions. This addresses the challenge of balancing context retention (persisting results for future reference) with performance (avoiding bloat from large outputs).

**Why This Matters**: Previously, all tool results were persisted regardless of size or relevance, leading to oversized conversation logs and degraded agent performance. Now you can optimize for your use case.

**What Changed**

**Persistence Behavior**

Tool call result persistence is now controlled by the `result_persistence` property, which accepts three modes:

| Mode                    | Output Storage                                          | Character Limit | Error on Exceed |
| ----------------------- | ------------------------------------------------------- | --------------- | --------------- |
| `"ephemeral"`           | Not persisted (only visible in current LLM interaction) | 20,000          | No              |
| `"persisted-preferred"` | Persisted if ≤ 5,000 characters, otherwise ephemeral    | 20,000          | No              |
| `"persisted"`           | Always persisted if ≤ 5,000 characters                  | 5,000           | Yes             |

{% hint style="danger" %}
**Breaking change**: `result_persistence` is now a required field on all tool call specs. Requests without this field return 400 Bad Request.
{% endhint %}

**Breaking Changes (Effective November 13, 2025)**

* **Required field**: `result_persistence` must be explicitly specified (previously had a default value of `"persisted"`)
* **Validation enforcement**: API requests without this field will fail with a 400 Bad Request error
* **Size validation**: Tool call results exceeding character limits will now fail for `"persisted"` mode (previously succeeded with truncation)

**Validation Rules**

* Designated tools in `ToolCallState` must use `result_persistence: "persisted"` (validation enforced at Context Graph creation)

**Migration Notes**

**Required Action** All tool call specifications in Context Graph definitions must now explicitly specify the `result_persistence` property. Configurations missing this field will be rejected.

**Recommended Values**

* Use `"persisted-preferred"` for most tools (provides graceful handling of large outputs)
* Use `"ephemeral"` for large datasets, file contents, or verbose debugging information
* Use `"persisted"` for critical context where outputs must always be available to the agent

**Related Documentation**

See the [Tool Result Persistence](https://docs.amigo.ai/developer-guide/core-api/tools#tool-result-persistence) section in the Developer Guide for detailed configuration examples and best practices.

</details>

## Versioning

The Amigo API follows [Semantic Versioning](https://semver.org/) with the format `MAJOR.MINOR.PATCH`:

* **MAJOR** version (currently 0): Pre-1.0 indicates active development. Breaking changes may occur between minor versions.
* **MINOR** version: New features, enhancements, or significant changes. May include breaking changes during pre-1.0 development.
* **PATCH** version: Bug fixes, documentation updates, and minor improvements without breaking changes.

| Product       | Version | Stage           | Compatibility                |
| ------------- | ------- | --------------- | ---------------------------- |
| **Amigo API** | v0.9.43 | Approaching 1.0 | Requires Agent Forge v0.3.0+ |

### Version Alignment

**Why different version numbers from Agent Forge?** The API and SDK evolve at different paces:

* **API versions** reflect backend feature maturity and breaking changes
* **SDK versions** track tooling capabilities and schema support
* **Compatibility** is maintained across reasonable version ranges

**Compatibility Policy**:

* Agent Forge v0.2.x supports API v0.6.0+
* Agent Forge v0.3.x supports API v0.7.0+
* Always use the latest SDK version for the best experience with new API features
* Backward compatibility for deployed agents is maintained even during breaking changes

### Version 1.0 Target

v1.0 release is expected when:

* Core API surface is stable with thorough testing
* Production deployments have validated the architecture
* Breaking change frequency has decreased significantly
* Feature set is considered complete for general availability


---

# Agent Instructions: 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:

```
GET https://docs.amigo.ai/api-reference/change-logs/amigo-api.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
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.
