Skip to content

feat(phonic): add update_options for mid-session config changes - #7089

Merged
tinalenguyen merged 4 commits into
livekit:mainfrom
Phonic-Co:arun/phonic-update-config-py
Sep 2, 2026
Merged

feat(phonic): add update_options for mid-session config changes#7089
tinalenguyen merged 4 commits into
livekit:mainfrom
Phonic-Co:arun/phonic-update-config-py

Conversation

@arunwpm-work

@arunwpm-work arunwpm-work commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Motivation

The Phonic realtime plugin sends its config once and has no way to change config fields mid-session. A multi-step agent (e.g. an interview that advances topics via handoff) can't, for example, switch default_language to Spanish for the next section: the Agent handoff (_update_session) only threads instructions / chat_ctx / tools into the reset, everything else is fixed at RealtimeModel construction, and update_options was a no-op.

Phonic's reset message already accepts a full config, so the capability exists on the wire β€” this just exposes it via the standard update_options method. (Companion to the same change in agents-js: livekit/agents-js#2407.)

Change

Implement update_options (previously a no-op) on the Phonic RealtimeSession, and add a forwarding update_options on the RealtimeModel (apps hold the model). It changes any config-level field mid-session and applies it immediately via a Phonic reset; unset fields keep their current values. Instructions/tools stay driven by the Agent handoff.

  • update_options exposes each config-level field as its own keyword-only NotGivenOr param (default_language=…, voice=…, boosted_keywords=…, …) β€” matching the openai/google realtime plugins β€” so callers pass only what changes. tool_choice (the base param the framework sends each turn) is ignored: Phonic has no tool_choice (same as aws/nvidia).
  • The handoff reset and update_options share a single _send_mid_session_reset() helper. update_options is synchronous (like other plugins), so the reset is fired as a background task and coalesced.
  • Language switching: the API rejects a default_language that also appears in additional_languages. When the default changes (and additional_languages isn't passed), the previous default is rotated into additional_languages and the new one dropped β€” e.g. {default: en, additional: [es]} + update_options(default_language="es") β†’ {default: es, additional: [en]}. Keeps the language set intact.

Usage

model = phonic.realtime.RealtimeModel(
    default_language="en",
    additional_languages=["es"],
    multilingual_mode="request",
)
session = AgentSession(llm=model)

# e.g. in the next task's on_enter, or the advancing tool handler:
model.update_options(default_language="es")
# -> immediate reset; the next reply is in Spanish

Validation

  • uv run ruff format --check / ruff check pass; mypy is clean on the change (the one standalone phonic.AsyncPhonic error pre-exists on main and resolves in CI's full env).
  • Verified end-to-end against the Phonic test env (via the JS companion): a task that calls update_options(default_language="es") switches a live session to Spanish β€” the server accepts the es reset and the next reply is in Spanish.

arunwpm-work and others added 2 commits September 1, 2026 14:22
Add update_config() on the Phonic RealtimeModel/RealtimeSession to change config
fields mid-session (e.g. default_language, voice, boosted_keywords, no-input-poke
settings). The merged config is applied immediately by sending a Phonic reset, so
it can be called around a task advance β€” e.g.
model.update_config({"default_language": "es"}) β€” to switch the language for the
next reply. Fields left unset keep their current values; instructions and tools
remain driven by the Agent handoff. The handoff reset and update_config share a
single _send_mid_session_reset() helper.

When the default language changes (and additional_languages isn't set explicitly),
the previous default is rotated into additional_languages so the overall language
set stays intact β€” the API rejects a default that also appears in
additional_languages.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Address review: use the standard `update_options` method instead of a new
`update_config`. The session's `update_options` accepts `tool_choice` (the base
param the framework sends each turn β€” ignored, Phonic has no tool_choice) plus a
`config` dict of any config-level fields; the model's `update_options(config=...)`
forwards it. Language rotation and the immediate reset are unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@arunwpm-work arunwpm-work changed the title feat(phonic): add update_config for mid-session config changes feat(phonic): add update_options for mid-session config changes Sep 2, 2026
@arunwpm-work
arunwpm-work marked this pull request as ready for review September 2, 2026 07:50
@arunwpm-work
arunwpm-work requested a review from a team as a code owner September 2, 2026 07:50

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note

This report is out of date. Scroll down for Devin Review's latest report on this PR.

Devin Review found 3 potential issues.

3 flags not posted on this PR by your GitHub settings β€” view them in Devin Review. (Configure)

Devin Review

Comment on lines +427 to +428
for sess in self._sessions:
sess.update_options(config=config)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟑 Only one active session resets

With multiple active sessions, the first update_options call mutates their shared options. Later sessions detect no change and keep the old server configuration.

Prompt for agents
Fix RealtimeModel.update_options and RealtimeSession option ownership in livekit-plugins/livekit-plugins-phonic/livekit/plugins/phonic/realtime/realtime_model.py. Sessions currently share realtime_model._opts, so forwarding to the first session changes the values observed by every remaining session and suppresses their resets. Give each session independent option state or separate option mutation from per-session reset scheduling, while keeping model-level updates as defaults for future sessions and applying each update to every active session.
Devin Review

Was this helpful? React with πŸ‘ or πŸ‘Ž to provide feedback.

Comment on lines +835 to +837
self._options_reset_task = asyncio.create_task(
self._apply_options_reset(), name="phonic-options-reset"
)

@devin-ai-integration devin-ai-integration Bot Sep 2, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟑 Reset failures disappear in background

When send_reset fails, _options_reset_task retains an unobserved exception. Callers receive no error event, and the session keeps its previous configuration.

Devin Review

Was this helpful? React with πŸ‘ or πŸ‘Ž to provide feedback.

…field change

Address review:
- Rename PhonicSessionConfigUpdate -> PhonicConfig.
- Rebuild the cached _configs_for_tools/_tool_definitions when update_options
  changes a tool-related field (configs_for_tools/forbid_speech_after_tool_call/
  phonic_tools), via a shared _rebuild_tool_definitions() helper, so the reset
  carries the new tool behavior instead of the previously-serialized one (Devin
  review).
- Name the rotation locals previous_default_language / new_default_language.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…fig arg)

Per review (Tina, LiveKit): expose each config field as its own keyword-only
NotGivenOr param on update_options β€” matching the openai/google realtime plugins β€”
instead of a single `config: PhonicConfig` arg. The PhonicConfig TypedDict is
removed. The model forwards each field to its sessions; the session collects the
given values and applies them (rotation, change-detection, tool rebuild, coalesced
reset all unchanged).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

3 flags not posted on this PR by your GitHub settings β€” view them in Devin Review. (Configure)

Devin Review

Comment on lines +951 to +953
self._options_reset_task = asyncio.create_task(
self._apply_options_reset(), name="phonic-options-reset"
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟨 Reset exceptions can leak configuration

Unretrieved _options_reset_task failures can trigger automatic exception logging. Provider exceptions can expose prompts, endpoint headers, or customer configuration outside PII-tagged fields.

Devin Review

Was this helpful? React with πŸ‘ or πŸ‘Ž to provide feedback.

@tinalenguyen
tinalenguyen merged commit ac506d1 into livekit:main Sep 2, 2026
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants