> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.agentduet.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.agentduet.com/_mcp/server.

# Changelog

Release history for the AgentDuet Python SDK.
Each release lists new features, behavior changes, fixes, and any action needed when you upgrade.
The current documentation targets [1.1.0b10](https://pypi.org/project/agentduet/1.1.0b10/).

The `1.1.0b*` versions are pre-releases. Install the documented version with `pip install agentduet==1.1.0b10`.
A plain `pip install agentduet` does not select pre-releases by default.

## 1.1.0b10 — 2026-10-26

### Documentation

- Updated the PyPI package description to list every trigger-condition option, including fallback recording and spoken drop messages.
- No SDK behavior changed from `1.1.0b9`.

### Known issue

`VoiceAgent.call_out()`, `dial()`, and `place_call()` use the wrong incoming audio track for agent-placed outgoing calls.
The call can connect and play model audio, but the model does not receive the callee's audio correctly.
Versions `1.1.0b2` through `1.1.0b10` are affected. The fix is not included in this release.

For outbound two-way audio, use `Session.make_call()` and `Call.dial()` directly.
Read the remote audio from `call.callee.audio_stream()`.
See the [outbound Quick Start](/introduction/quick-start#step-4-start-an-outgoing-call).

## 1.1.0b9 — 2026-10-26

### Added

- **Fallback call recordings:** Set `fallback_recording(True)` to record calls that the fallback connects without an agent.
  Register `@sm.on_call_recording` to receive recording details, then use `sm.download_recording()` to get stereo Ogg Opus audio.
- **Recording acknowledgment:** By default, a successful recording handler acknowledges and deletes the recording on the server.
  Use `auto_ack=False` to control deletion after you store the recording.
  `list_call_recordings()` lists pending recordings; `ack_call_recordings()` acknowledges them and returns a count.
- **Spoken drop messages:** Use `drop_message(text)` with `CallFallback.DROP` to play a message before the server ends the call.
  The message must contain 1 to 500 characters after trimming.

### Changed

- A notified call must be claimed within **10 seconds** of delivery, reduced from 60 seconds.
- At the session limit, the server waits up to **15 seconds** for a slot, reduced from 30 seconds.
  The claim period starts when the notification arrives.

**Upgrade action:** Call `session.process_call(noti)` before slow setup, database requests, or model connections.
Review your fallback policy because an unclaimed call can connect without the agent or end.

See [Trigger Conditions](/concepts/trigger-conditions) and [Event Handling](/concepts/event-handling).

## 1.1.0b8 — 2026-10-13

### Added

- **DDUET conversation handover:** Use `Session.hand_over_conversation()` to transfer a conversation to staff.
  Send the closing message first. You can include a staff-only summary.
  The method returns `ConversationHandoverResult`; inspect `success` and the error fields.

After handover, conversation messages stop reaching the agent and further sends return `FORBIDDEN`.
Only staff can assign the conversation back to the agent.
DDUET voice calls and remote function calls can still reach the agent.

### Changed

- Control connections now reconnect with full-jitter exponential backoff: a random delay within an increasing window, capped at 8 seconds.
- Heartbeat pings run every 15 seconds with a 5-second response timeout.

### Removed

- Removed `TriggerConditionsBuilder.call_log()` and `TriggerConditions.call_log`.
  Do not post a call-log message part from your application.

See [DDUET conversation handover](/concepts/whats-app-messaging#handing-over-a-dduet-conversation) and [connection recovery](/reference/advanced-topics#reconnection-and-failure-semantics).

## 1.1.0b7 — 2026-10-06

### Added

- **Server-bound sessions:** Incoming messages, call notifications, and function calls include an optional `session_id`.
  Reuse this ID when the server supplies one.
- **Participant attachment:** Pass `participant=Address(...)` to `open_session()` to attach the remote party immediately.
- **Session keep-alive:** Use `async with session.keep_alive():` during slow work to refresh an existing session slot.
  The default refresh interval is 20 seconds. It does not restore an expired slot.
- **Conversation association:** Echo an incoming `conversation` value on related `send_message()` and `make_call()` operations.
  On DDUET, this also identifies the conversation for the call-log entry.
- **Call fallback:** Configure `CallFallback.DROP` or `CONNECT_TO_DESTINATION` for calls that no agent claims.
- **Conversation ID aliases:** DDUET parsing reads `convoId` first and falls back to `sessionUid`.
  The `convo_id` and `dduet_convo_id` properties expose the same values under the new name.

### Changed

- Opening a new session without `participant=` does not yet create a server session or consume a session slot.
  The first claim or send creates it.
- The server applies fallback when a call remains unclaimed.
  An incoming call connects to its destination only when `inbound_call` is `ALL` and the fallback permits connection.
  Otherwise, the server ends the call.

**Upgrade action:** Reuse server-provided session and conversation IDs.
Set every trigger option that your application needs; each setup replaces the full configuration.
The call timing was reduced again in `1.1.0b9`, as listed above.

See [Sessions and Call Handoff](/concepts/sessions-and-handoff) and [Trigger Conditions](/concepts/trigger-conditions).

## 1.1.0b6 — 2026-10-05

### Added

- **DDUET remote function calls:** Register `@sm.on_function_call(name)` to receive an `IncomingFunctionCall`.
  Claim the call with `session.process_function_call(fn)` before doing the work.
  Answer with `session.send_function_result()` or reject it with `fn.reject()`.
- **Delivery reporting:** The SDK reports message receipt and processing outcomes to the service.
  Outcomes include successful handling, handler errors, and dropped messages.
  Applications do not need a new callback for these reports.

### Changed

Renamed identity fields to make message and call direction explicit:

| Previous name | Current name |
|---|---|
| `subscriber` on models and supported method parameters | `subscriber_id` |
| `IncomingMessage.participant` | `IncomingMessage.from_address` |
| `IncomingCallNotification.participant` | `IncomingCallNotification.from_address` |
| `OutgoingCallNotification.participant` | `OutgoingCallNotification.to_address` |

Notifications also expose the opposite address as `to_address` or `from_address`.
`Call.caller`, `Call.callee`, `Call.participant`, and `SendDduetMessage.participant` keep their names.

### Deprecated

- Previous identity properties remain as read-only aliases in this release and emit `DeprecationWarning`.
- The old `subscriber=` keyword remains accepted where its replacement is `subscriber_id=`.
  Passing both spellings raises `TypeError`.

### Fixed

- Fixed incoming event delivery stopping after the 100th frame.

**Upgrade action:** Use the current field and parameter names.
To find deprecated usage, run your tests with `-W error::DeprecationWarning`.

See [Participant Model](/concepts/participant-model) and [remote function calls](/concepts/whats-app-messaging#remote-function-calls-smon_function_call).

## 1.1.0b5 — 2026-09-14

### Added

- **DDUET media upload:** Use `Session.upload_media(UploadDduetMedia(...))` to upload a file.
  Reference the returned `UploadedMedia` in a message with `SendDduetMessage.compose()` or `UploadedMedia.as_part()`.
- **Media downloads through sessions:** `Session.download_media()` returns one attachment's bytes.
  `download_media_all()` returns ordered `DownloadedMedia` results, including individual failures.
- **Call origin:** `Call.origin` identifies whether the agent, subscriber, or external participant started the call.
  Only an agent-originated call uses `dial()`.

### Breaking changes

- Replaced the media URL helpers introduced in `1.1.0b4` with session upload and download methods.
  Removed `SessionManager.get_dduet_media_urls()`, `get_media_urls()`, `MediaUrl`, and the module-level `download_media()` helper.
- Renamed `DduetMediaUid` to `DduetMediaAttachment`.
  Attachments now carry the metadata and routing needed to download them.
- Removed `ba_uid` from outgoing DDUET messages.
  Open the session for the acting business account; the session supplies its identity on each send.
- Changed the serialized call origin from `_origin` to `origin`, using `AGENT`, `SUBSCRIBER`, or `PARTICIPANT`.
  Earlier JSON handoff payloads are incompatible with this format.

**Upgrade action:** Replace the removed media helpers and outgoing-message `ba_uid=` arguments.
Drain pending call handoffs before upgrading across the origin-format change.
Use the same SDK version on both nodes that exchange serialized calls.

See [DDUET media](/concepts/whats-app-messaging#downloading-and-uploading-dduet-media) and [Sessions and Call Handoff](/concepts/sessions-and-handoff).

## 1.1.0b4 — 2026-09-03

### Added

- Added DDUET media-download URL resolution and a download helper.

These original media APIs were replaced in `1.1.0b5`.
For current applications, use the [Session media methods](/concepts/whats-app-messaging#downloading-and-uploading-dduet-media).

## 1.1.0b3 — 2026-08-25

### Added

- **Token-provider authentication:** Pass a zero-argument sync or async function to `SessionManagerConfig.create(token_provider=...)`.
  It must return a non-empty bearer token.
- The SDK requests a fresh token before each control connection attempt and authenticated HTTP request.
- `TokenProviderError` reports a failed provider or an invalid token value.
  A provider failure during reconnection stops retries and surfaces through `run_forever()` and `sm.fatal_error`.

This is a third authentication option alongside API key authentication and mTLS.
Use an async provider for long waits, such as interactive sign-in.

See [Token provider authentication](/introduction/installation#token-provider-authentication).

## 1.1.0b2 — 2026-08-21

### Breaking changes

- Removed the nested DDUET `address` object introduced in `1.1.0b1`.
- Replaced `IncomingMessage.address` and `IncomingMessage.dduet_session_uid` with the typed `msg.dduet` view.
- Outgoing DDUET messages use `session_uid` and `participant` to address replies.
  The temporary outgoing `ba_uid` argument was later removed in `1.1.0b5`.
- `SendMessageResult.dduet_session_uid` reads the conversation identifier from the send response.

### Documentation

- Updated the DDUET quick start and reference tables to use the flat message format.
- Documented the `user_uid` and `ba_uid` comparison for identifying the message author.
  DDUET reply handlers should ignore messages authored by their own business-account staff.

WhatsApp message routing and voice calling were unaffected by the DDUET message-format change.
See [DDUET messages](/concepts/whats-app-messaging#dduet-chat-messages-dduetmessage-and-senddduetmessage).

## 1.1.0b1 — 2026-08-17

### Added

- **DDUET chat:** Added a messaging channel for text and media, including `Network.DDUET`, `Address.dduet()`, and `SendDduetMessage`.
- **Outgoing-call notifications:** Added `@sm.on_outgoing_call` for calls that the subscriber starts outside the SDK.
  Enable these notifications with `outbound_call(True)`.
- **Outgoing message idempotency:** Added `client_msg_id` to `session.send_message()`.
  Reuse the same ID when retrying one logical message so the server can identify a completed send.

### Breaking changes

- Introduced a nested DDUET `address` object for message routing.
  That format was replaced in `1.1.0b2` and must not be used with `1.1.0b10`.

## 1.0.0 — 2026-08-10

First stable release under the `agentduet` package name.
This release redesigns the earlier `telcoflow-sdk` API around connector notifications, subscriber sessions, and calls.

### Added

- `SessionManager` for the control connection and incoming call and message handlers.
- `Session` for claiming calls, creating outgoing calls, and sending messages for a subscriber.
- `Call` and `CallParty` for call control and separate caller and callee audio streams.
- `VoiceAgent` for model-adapter-based voice agents. Ready-made adapters ship in the separate `agentduet-adapters` package.
- `CallAudioConfig` for sample rate, outgoing buffer size, and incoming audio queue limits.
- Trigger conditions for connector routing, API key and mTLS authentication, and JSON call handoff between workers.

### Changed

- Call states are `NEW`, `OPENING`, `RINGING`, `LIVE`, and `TERMINATED`.
- Expected command failures return an unsuccessful `CommandResult`.
  State errors, closed calls, and full audio buffers raise exceptions.
- Call audio uses separate party tracks. The previous audio-mode setting is no longer required.

### Removed

- Removed the `ActiveCall` API and the SDK TTS methods introduced in `0.27.1`.
- Removed `CloseReason`, the public voice-connection property, and the top-level `BufferClosedError` export.

### Migration from telcoflow-sdk

1. Replace the package dependency and imports with `agentduet`.
2. Register notification handlers on `SessionManager`.
3. Open a session for the subscriber, then claim or create a call through that session.
4. Read party audio through `call.caller.audio_stream()` or `call.callee.audio_stream()`.
5. Catch `TransportError` for control-connection failures and `CallClosedError` when a voice call ends.
6. Replace `WSS_BASE_URL` with `AGENTDUET_BASE_URL`.

DDUET messaging and application-supplied `client_msg_id` were added in the `1.1.0` pre-release series, not in `1.0.0`.

## Earlier telcoflow-sdk releases

Versions `0.27.1` and earlier used the `telcoflow-sdk` package name.
The following entries describe that historical API. They are not instructions for the current `agentduet` release.

### 0.27.1 — 2026-05-06

**Added**

- Voice cloning through `ActiveCall.enable_tts(voice_id)` and `ActiveCall.send_tts(text)`.
- `CallEvent.CALL_ERROR`, `CallErrorCode.TTS_SESSION_ERROR`, and `ActiveCall.on_error()` for non-fatal call errors.

These SDK TTS methods were removed in `agentduet` 1.0.0.

### 0.27.0 — 2026-04-13

**Added**

- Channel selection on `ActiveCall.audio_stream(channel_id=...)` for mixed and separate party audio.
- `AudioMode.MIXED` and `AudioMode.ISOLATED` configuration.

Current AgentDuet calls use separate `CallParty` streams instead of this API.

### 0.26.1 — 2026-04-06

**Fixed**

- Restored control-connection reconnection after transient failures.

### 0.26.0 — 2026-03-26

**Fixed**

- Sent the interruption command to the server when an interruption occurred.

### 0.25.0 — 2026-03-25

**Changed**

- Renamed `ActiveCall.force_disconnect()` to the private `_force_close()` method.
- Added automatic cleanup when a call ends.

**Upgrade action:** Remove application calls to `force_disconnect()` and let the SDK manage cleanup.

### 0.24.0 — 2026-03-19

**Added**

- Support for 8000 Hz audio alongside 16000 Hz and 24000 Hz.

**Fixed**

- Minor bugs.

### 0.21.0 — 2026-03-16

**Changed**

- Renamed the command-response timeout parameter to `response_timeout_seconds`.

**Added**

- `BufferClosedError` and `ActiveCall.force_disconnect()` for the APIs available at that time.

### 0.20.0 — 2026-03-10

**Changed**

- Introduced enum-based event registration and improved signal and connection handling.
- Changed `ActiveCall.close()` so the agent leaves while the server manages media shutdown.

**Added**

- `ActiveCall.disconnect()` to end a call for all parties.

**Removed**

- Removed `ActiveCall.hangup()`, deprecated event constants, and `ActiveCall.is_connected()`.

### 0.18.0 — 2026-03-07

**Changed**

- An unanswered `connect()` reports `CALL_UNANSWERED` through `CallCommandError` in this historical API.
  Applications could handle the error and decide whether to retry or end the call.