Skip to content

Port simulation text mode handling - #1751

Open
rosetta-livekit-bot[bot] wants to merge 11 commits into
mainfrom
port-simulation-text-mode
Open

Port simulation text mode handling#1751
rosetta-livekit-bot[bot] wants to merge 11 commits into
mainfrom
port-simulation-text-mode

Conversation

@rosetta-livekit-bot

@rosetta-livekit-bot rosetta-livekit-bot Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Summary

  • add hidden start --simulation flag to disable worker load-limit status changes during simulations
  • parse simulation dispatch metadata on jobs and expose simulation mode context
  • disable audio I/O plus STT/TTS/VAD for text simulations

Testing

  • pnpm exec prettier --check "agents/src/{cli,job,worker}.ts" "agents/src/voice/{agent_activity,agent_session}.ts"
  • pnpm --filter @livekit/agents build
  • pnpm --filter @livekit/agents lint (warnings only, pre-existing)
  • pnpm --filter @livekit/agents api:check (fails on existing API Extractor limitation: export * as ___ in dist/index.d.ts)

Ported from livekit/agents#6036

Original PR description

What

Worker load limit off under simulation. lk simulate now launches the agent with a new --simulation flag (supported on both python -m livekit.agents start and the legacy typer start; hidden from help in the latter). It sets simulation mode on AgentServer: _is_available() always accepts (draining still wins) and _update_worker_status() never reports WS_FULL, so a run can saturate the agent instead of being throttled by load_threshold. Deliberately not implemented by forcing load_threshold=inf, which ServerOptions validation rejects outside dev mode (and Cloud reverts custom thresholds anyway).

Text simulations skip STT/TTS. The simulate worker now stamps SimulationMode into SimulationDispatch (agents-private#68). SimulationContext.channel exposes it (unspecified ⇒ TEXT, since older dispatches were all text-only), and AgentSession.start() drops STT/TTS/VAD and disables RoomIO audio I/O when running under a TEXT-mode simulation — they're never initialized or connected, even though the user's code passes them.

Forward-compat: simulation_context() now parses the dispatch with ignore_unknown_fields=True, so future dispatch fields don't break older agents.

Tests

tests/test_simulation_text_channel.py:

  • channel property (unspecified ⇒ TEXT, AUDIO passthrough)
  • _is_available() under full load: rejected normally, accepted in simulation mode, draining still rejects
  • a real session.start() under a mocked TEXT-sim job context ends with stt/tts/vad = None

Verified the protojson produced by the Go worker handler round-trips into SimulationContext.channel == TEXT, including with unknown future fields injected.

Dependencies (draft until released)

Requires a livekit-protocol release containing SimulationMode (livekit/protocol#1613 via livekit/python-sdks#710) — livekit.agents.__init__ imports simulation eagerly, so the package needs the new bindings; bump the livekit-protocol floor in pyproject when it ships.

Related: livekit/agents-private#68, livekit/cloud-api-server#1892, lk CLI PR (livekit-cli).

toubatbrian and others added 11 commits May 28, 2026 14:43
…1525)

Co-authored-by: rosetta-livekit-bot[bot] <282703043+rosetta-livekit-bot[bot]@users.noreply.github.com>
Co-authored-by: u9g <jason.lernerman@livekit.io>
Agent.llmNode now returns ReadableStream<ChatChunk | string | FlushSentinel>,
but the agent_v2 hook overrides and AgentHookAdapter still declared the
narrower ChatChunk | string union, so passing super.llmNode as the fallback
failed to type-check. Widen the override return types and the adapter's
fallback/return signatures to include FlushSentinel.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Brian Yin <brian.yin@livekit.io>
Co-authored-by: rosetta-livekit-bot[bot] <282703043+rosetta-livekit-bot[bot]@users.noreply.github.com>
Co-authored-by: u9g <jason.lernerman@livekit.io>
Catch end-call close listener errors to avoid unhandled rejections during shutdown, and make public tool type guards return false for null inputs.
Co-authored-by: rosetta-livekit-bot[bot] <282703043+rosetta-livekit-bot[bot]@users.noreply.github.com>
Co-authored-by: rosetta-livekit-bot[bot] <282703043+rosetta-livekit-bot[bot]@users.noreply.github.com>
@changeset-bot

changeset-bot Bot commented Jun 10, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 0fbbbaf

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@rosetta-livekit-bot
rosetta-livekit-bot Bot requested a review from theomonnom June 10, 2026 23:54

@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: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 4 additional findings.

Open in Devin Review

Base automatically changed from 1.5.0 to main July 2, 2026 13:48

@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 2 new potential issues.

Open in Devin Review

Comment thread agents/src/generator.ts
Comment on lines +20 to +21
export function isAgent(obj: unknown): obj is AgentDefinition {
return typeof obj === 'object' && obj !== null && AGENT_DEFINITION_SYMBOL in obj;

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.

🚩 isAgent detection changed from structural to symbol-based — existing non-defineAgent exports will break

The isAgent function at agents/src/generator.ts:20-21 now checks for AGENT_DEFINITION_SYMBOL instead of doing a structural duck-type check (has entry function, optionally has prewarm function). This means any module that exports a plain { entry, prewarm } object without going through defineAgent() will no longer be recognized as an agent by the framework's worker/IPC layer. The new test at agents/src/generator.test.ts:17 explicitly verifies this: isAgent({ entry: async () => {} }) returns false. This is an intentional breaking change, but it could silently break users who export agent definitions without defineAgent(). The changeset doesn't call this out as a breaking change.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +4229 to +4233
updateTools: (tools) => {
ts._setTools(tools);
void this.onToolsetToolsChanged().catch((error) =>
this.logger.error({ error }, 'error re-advertising toolset tools'),
);

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.

🚩 onToolsetToolsChanged is fire-and-forget — concurrent pushes could race

At agents/src/voice/agent_activity.ts:4229-4233, when a toolset pushes new tools via updateTools, the handler calls this.onToolsetToolsChanged() as a fire-and-forget promise (void ... .catch(...)). If a toolset pushes tools rapidly (e.g., an MCP server reconnecting), multiple onToolsetToolsChanged calls could overlap. Each call invokes this.updateTools(current.tools) which modifies this.agent._toolCtx, sets up/closes toolsets, and updates the realtime session. The updateTools method itself is not guarded by a mutex, so concurrent invocations could interleave their reads of oldToolCtx and writes to agent._toolCtx. In practice, dynamic tool pushes are likely infrequent enough that this doesn't manifest, but it's worth noting for robustness.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

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.

1 participant