Skip to content

feat(workflows): port built-in data-capture workflows from python#2007

Merged
toubatbrian merged 2 commits into
brian/expressive-modefrom
claude/port-workflows-js-qa2164
Jul 9, 2026
Merged

feat(workflows): port built-in data-capture workflows from python#2007
toubatbrian merged 2 commits into
brian/expressive-modefrom
claude/port-workflows-js-qa2164

Conversation

@toubatbrian

@toubatbrian toubatbrian commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Description

Ports the built-in workflow tasks from python livekit-agents (livekit/agents/beta/workflows/) to the stable workflows namespace, following the warm_transfer.ts pattern (functional core + thin class wrapper). Stacked on #1982 (brian/expressive-mode) because these tasks depend on its Instructions modality redesign (resolveTemplate, audio/text variants).

Changes Made

  • email_address.tsGetEmailTask / createGetEmailTask: regex validation, character-by-character read-back, dynamic confirm_email_address tool injection
  • name.tsGetNameTask / createGetNameTask: first/middle/last collection flags, nameFormat, spelling verification, placeholder-arg cleanup ("null", quoted values, no-letters rejection)
  • phone_number.tsGetPhoneNumberTask / createGetPhoneNumberTask: E.164-style validation, grouped read-back
  • dob.tsGetDOBTask / createGetDOBTask: two-digit-year normalization, future-date rejection, optional update_time tool via includeTime
  • address.tsGetAddressTask / createGetAddressTask: template/persona instruction pattern shared with email
  • dtmf_inputs.tsGetDtmfTask / createGetDtmfTask: wired to rtc-node RoomEvent.DtmfReceived, local debounce helper mirroring python's utils.aio.debounced, stop-event handling, user/agent state-change pause/resume
  • credit_card.tsGetCreditCardTask / createGetCreditCardTask: composes internal card-number (Luhn), expiration, and security-code sub-tasks plus the cardholder GetNameTask through TaskGroup; shared decline_card_capture / restart_card_collection tools and the restart loop
  • utils.tsDtmfEvent enum with formatDtmf / dtmfEventToCode, and a shared resolveWorkflowInstructions mirroring python's WorkflowInstructions.resolve on top of Instructions.resolveTemplate
  • task_group.tsTaskGroup.add now accepts () => AgentTask<any>; the previous AgentTask<unknown> signature rejected typed tasks (TS invariance), which the credit-card composition needs
  • Changeset added (port-builtin-workflows.md); all new tasks exported from the workflows namespace

Review follow-ups (fixed, candidates for upstreaming to python):

  • GetCreditCardTask skips TaskGroup chat-context summarization on realtime-model sessions (would otherwise fail after all card details were collected)
  • Task-level overrides (llm, tts, stt, vad, turnDetection, tools, allowInterruptions) are forwarded from createGetCreditCardTask into the card sub-tasks

Intentional JS adaptations:

  • Time values are milliseconds per repo convention (dtmfInputTimeout defaults to 4000)
  • GetDOBResult uses structured DateOfBirth / TimeOfBirth objects instead of python's datetime.date / time (avoids timezone pitfalls)
  • Email/address accept instructions?: InstructionParts | Instructions | string (python's WorkflowInstructions class becomes the existing InstructionParts interface)
  • Invalid DTMF digits are logged and ignored instead of throwing inside the room-event callback

Pre-Review Checklist

  • Build passes: pnpm build, pnpm lint, pnpm format:check green; pnpm api:update for the core package fails identically on the base branch (pre-existing api-extractor export * as limitation), so no report changes
  • AI-generated code reviewed: Removed unnecessary comments and ensured code quality
  • Changes explained: All changes are properly documented and justified above
  • Scope appropriate: All changes relate to the PR title (the TaskGroup.add signature change is required by the credit-card composition, explained above)
  • Video demo: N/A — validated via cue-cli wire-level assertions with per-command audio recordings instead (see Testing)

Testing

  • All unit tests pass — full agents suite (1250 tests) green
  • cue-cli e2e (voice mode, TCP transport, LiveKit inference gateway models) — every task driven through its full conversational flow with wire-level assertions on function_tools_executed + debug_message lifecycle probes (task_started/task_result emitted via AgentSession._emitDebugMessage):
    • GetEmailTask: spoken "john dot doe at gmail dot com" → update_email_address → confirm → result john.doe@gmail.com
    • GetNameTask: name + spelling → update_name → confirm → {firstName: "Brian", lastName: "Yin"}
    • GetPhoneNumberTask: spoken digits → update_phone_number → confirm → +14155551234
    • GetDOBTask: "January fifteenth, nineteen ninety" → update_dob → confirm → {year: 1990, month: 1, day: 15}
    • GetAddressTask: full US address → update_address → confirm → composed address string
    • GetDtmfTask: spoken digits → record_inputs"1 2 3 4"
    • GetCreditCardTask: full 4-sub-task TaskGroup run → composed result {issuer: "Visa", cardNumber, expirationDate: "04/30", securityCode: "321", cardholderName}; the mismatch → re-ask path (confirm_card_number with an STT-garbled repeat) and the decline_card_captureCardCaptureDeclinedError propagation through TaskGroup were also exercised organically
  • restaurant_agent.ts / realtime_agent.ts playground validation not run (additive change; no existing pipeline code paths modified except the TaskGroup.add type signature)

Additional Notes

The class wrappers (new GetEmailTask().run()) delegate only run(), matching WarmTransferTask; compositions that need updateChatCtx/updateTools on the task (e.g. inside TaskGroup) should use the create*Task functional forms, as createGetCreditCardTask does internally.

Two observations from e2e (pre-existing, not introduced by this PR — reproduced independent of the workflow code):

  1. In console/TCP mode, the SessionHost event listeners register slightly after the first activity's onEnter starts, so events emitted very early in onEnter (e.g. _emitDebugMessage) can be lost.
  2. Sporadic unhandled promise rejections with reason undefined occur during AgentTask handoff/activity-drain phases (a plain basic_agent console session produces none; any multi-AgentTask session produces a few). Non-fatal when handled, but under Node's default --unhandled-rejections=throw it crashes the process — worth a follow-up.

Ports the built-in workflow tasks from python livekit-agents
(livekit/agents beta/workflows) to the stable workflows namespace,
following the warm_transfer.ts pattern (functional core + thin class
wrapper):

- GetEmailTask / createGetEmailTask (email_address.ts)
- GetNameTask / createGetNameTask (name.ts)
- GetPhoneNumberTask / createGetPhoneNumberTask (phone_number.ts)
- GetDOBTask / createGetDOBTask (dob.ts)
- GetAddressTask / createGetAddressTask (address.ts)
- GetDtmfTask / createGetDtmfTask (dtmf_inputs.ts)
- GetCreditCardTask / createGetCreditCardTask (credit_card.ts), composed
  of internal card number / expiration date / security code sub-tasks
  plus the cardholder name task via TaskGroup

workflows/utils.ts gains the DtmfEvent enum with formatDtmf /
dtmfEventToCode helpers and a shared resolveWorkflowInstructions that
mirrors python's WorkflowInstructions.resolve on top of
Instructions.resolveTemplate. TaskGroup.add now accepts factories
returning typed AgentTask<T> so typed sub-tasks compose without casts.

Notable JS adaptations: time values are milliseconds (dtmfInputTimeout
defaults to 4000ms), GetDOBResult uses structured DateOfBirth /
TimeOfBirth objects instead of datetime.date/time, and DTMF input uses
rtc-node's RoomEvent.DtmfReceived.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q3ZnFWr54AZysyyf4g5Dyd
@toubatbrian toubatbrian requested a review from a team as a code owner July 9, 2026 07:18
@changeset-bot

changeset-bot Bot commented Jul 9, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 5af519a

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 36 packages
Name Type
@livekit/agents Patch
@livekit/agents-plugin-anam Patch
@livekit/agents-plugin-anthropic Patch
@livekit/agents-plugin-assemblyai Patch
@livekit/agents-plugin-baseten Patch
@livekit/agents-plugin-bey Patch
@livekit/agents-plugin-cartesia Patch
@livekit/agents-plugin-cerebras Patch
@livekit/agents-plugin-deepgram Patch
@livekit/agents-plugin-did Patch
@livekit/agents-plugin-elevenlabs Patch
@livekit/agents-plugin-fishaudio Patch
@livekit/agents-plugin-google Patch
@livekit/agents-plugin-hedra Patch
@livekit/agents-plugin-hume Patch
@livekit/agents-plugin-inworld Patch
@livekit/agents-plugin-lemonslice Patch
@livekit/agents-plugin-liveavatar Patch
@livekit/agents-plugin-livekit Patch
@livekit/agents-plugin-minimax Patch
@livekit/agents-plugin-mistral Patch
@livekit/agents-plugin-mistralai Patch
@livekit/agents-plugin-neuphonic Patch
@livekit/agents-plugin-openai Patch
@livekit/agents-plugin-perplexity Patch
@livekit/agents-plugin-phonic Patch
@livekit/agents-plugin-resemble Patch
@livekit/agents-plugin-rime Patch
@livekit/agents-plugin-runway Patch
@livekit/agents-plugin-sarvam Patch
@livekit/agents-plugin-silero Patch
@livekit/agents-plugin-soniox Patch
@livekit/agents-plugin-tavus Patch
@livekit/agents-plugins-test Patch
@livekit/agents-plugin-trugen Patch
@livekit/agents-plugin-xai Patch

Not sure what this means? Click here to learn what changesets are.

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

@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@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 1 additional finding.

Open in Devin Review

chatgpt-codex-connector[bot]

This comment was marked as resolved.

- Skip TaskGroup chat-context summarization when the session runs a
  realtime model: TaskGroup.onEnter requires a standard LLM and would
  otherwise fail after all card details were collected.
- Forward task-level overrides (llm, tts, stt, vad, turnDetection,
  tools, allowInterruptions) from createGetCreditCardTask into the card
  sub-tasks; previously they attached only to the outer placeholder
  task and sub-tasks silently ran with session defaults.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q3ZnFWr54AZysyyf4g5Dyd
@toubatbrian toubatbrian merged commit cf15251 into brian/expressive-mode Jul 9, 2026
1 of 2 checks passed
@toubatbrian toubatbrian deleted the claude/port-workflows-js-qa2164 branch July 9, 2026 14:59
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.

4 participants