Skip to content

feat(voice): per-restaurant voice_agent_name#23

Merged
ByteStreams-AI merged 2 commits intomainfrom
feat/voice-agent-name
May 4, 2026
Merged

feat(voice): per-restaurant voice_agent_name#23
ByteStreams-AI merged 2 commits intomainfrom
feat/voice-agent-name

Conversation

@ByteStreams-AI
Copy link
Copy Markdown
Owner

@ByteStreams-AI ByteStreams-AI commented May 4, 2026

Summary

  • Adds nullable voice_agent_name column on restaurants (migration 0014). When set, the prompt instructs the LLM to introduce itself by this name verbatim if asked; null falls through to whatever the model invents (currently "Ruth" on Sui's calls).
  • Threads through RestaurantLookupbuildPromptContextPromptContextPROMPT_TEMPLATE (one new conditional line at the top of the persona section).
  • Pronunciation is handled by storing what should be spoken — operators spell phonetically ("My-tay" instead of "Maite") or embed inline SSML phoneme tags for exact IPA. Both work in ElevenLabs.
  • Seeds Sui's Sushi with "Maya" so local + cloud test calls answer with a stable name.
  • Backfills the canonical schema doc with voice_id (was added in 0013 but never reflected) so the doc stays the source of truth it claims to be.

Test plan

  • pnpm ci:fast — 280/280 (one new prompt-template test covering set / phonetic-respelling / whitespace-only-treated-as-null)
  • Migration applied locally; types regenerated
  • After deploy + cloud db push: update restaurants set voice_agent_name = 'Maya' where slug = 'suis-sushi', place call, ask "what's your name?", confirm "Maya"
  • Set voice_agent_name = 'My-tay' on a test restaurant, confirm ElevenLabs pronounces it that way

Operational note

The cloud deploy workflow does NOT push migrations (known gap, tracked in PR #16). After this merges, run pnpm supabase db push --linked against the cloud project before relying on the new column — select voice_agent_name from restaurants limit 1 to verify.

🤖 Generated with Claude Code

Greptile Summary

Adds a nullable voice_agent_name column on restaurants and threads it end-to-end — migration → DB types → RestaurantLookupbuildPromptContextPromptContextPROMPT_TEMPLATE — so operators can give the voice agent a stable, pronounceable name. The custom Mustache renderer's existing isTruthy guard correctly treats null and whitespace-only values as falsy, preventing the Identity section from rendering when no name is configured.

Confidence Score: 5/5

Clean, well-scoped feature addition — safe to merge after the manual cloud migration step noted in the PR.

No P0 or P1 findings. The rendering logic is correct (non-greedy section regex + interpolate pass handles two {{#agent_name}} sections cleanly), the migration is idempotent, types are in sync, and all branching paths are covered by tests.

No files require special attention.

Important Files Changed

Filename Overview
packages/shared/src/voice/prompt.ts Adds {{#agent_name}} Identity section and threads the name into the "are you a real person?" disclosure; rendering logic is correct given renderSections non-greedy regex and subsequent interpolate pass.
supabase/functions/_shared/context.ts Selects voice_agent_name from DB, threads it through RestaurantLookup into PromptContext; straightforward plumbing with no logic issues.
supabase/migrations/0014_voice_agent_name.sql Adds nullable voice_agent_name column with add column if not exists guard; clean and idempotent.
packages/shared/test/voice/prompt.test.ts New tests cover named/unnamed/phonetic/whitespace-only cases and the "are you a real person?" disclosure variant; good coverage of the feature's branching logic.
packages/shared/src/voice/types.ts Adds `agent_name: string
packages/shared/src/supabase/types.ts Adds `voice_agent_name: string
supabase/seed.sql Seeds Sui's Sushi with voice_agent_name = 'MeSee'; code and tests are internally consistent.
developer/dialtone-schema.sql Backfills voice_id (added in 0013 but missed here) and adds voice_agent_name; keeps the canonical schema doc in sync.

Reviews (3): Last reviewed commit: "feat(voice): swap seed to MeSee + streng..." | Re-trigger Greptile

Comment thread supabase/migrations/0014_voice_agent_name.sql
Comment thread supabase/functions/_shared/context.ts
Adds a nullable `voice_agent_name` column on `restaurants` and threads
it through `RestaurantLookup` → `PromptContext` → `PROMPT_TEMPLATE`.
When set, the LLM introduces itself by this name verbatim if the
caller asks; when null it picks a name on its own (current behavior,
where it had been answering "Ruth").

Pronunciation is handled by storing what the agent should *say*:
operators spell phonetically (e.g. "My-tay" instead of "Maite") or
embed inline SSML phoneme tags for exact IPA control. ElevenLabs
honors both. Single-column design — promote to two columns later if
we ever add a non-voice display surface that needs the real spelling.

- Migration 0014_voice_agent_name.sql
- Seed: Sui's Sushi → "Maya"
- Updates canonical schema doc to also list voice_id (added in 0013
  but missing from the doc until now).
- Three new prompt-template tests: name set, phonetic respelling,
  whitespace-only treated as null.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@ByteStreams-AI ByteStreams-AI force-pushed the feat/voice-agent-name branch from 2cebf79 to 8f600c7 Compare May 4, 2026 18:31
- Seed: voice_agent_name "Maya" → "MeSee" (matches the operator's
  ElevenLabs pronunciation-dictionary entry for "Mici").
- Promote agent_name guidance to a top-level # Identity block (was a
  single conditional line). Explicit override of the catch-all
  "automated assistant" canned reply when asked your name — that
  disclosure now stays scoped to the "are you a real person?" path.
- Thread agent_name into the "are you a real person?" canned line:
  becomes "I'm MeSee, an automated assistant taking the order — is
  that okay?" when set; falls back to the original wording when null.

Two new prompt-template tests cover both branches; whitespace-only
treated as null. 281/281 unit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@ByteStreams-AI ByteStreams-AI merged commit 8369025 into main May 4, 2026
2 checks passed
@ByteStreams-AI ByteStreams-AI deleted the feat/voice-agent-name branch May 4, 2026 18:43
ByteStreams-AI added a commit that referenced this pull request May 4, 2026
Adds open follow-up #6 capturing the flake observed on PR #24's full
lane. Same test passed on PR #23's run an hour earlier; the failing
PR #24 diff is prompt text only — zero impact on the reservation
availability path. The flake is real and worth tracking, but not
caused by the prompt change.

Captures: where it lives, what triggers it, the proposed real fix
(deterministic slot anchor instead of `now + 5h`).
ByteStreams-AI added a commit that referenced this pull request May 4, 2026
PR #24's full lane started failing on
  vapi_check_availability > returns alternative slots when the
  requested time is unavailable
even though the diff is prompt text only. Same code paths passed on
PR #23 an hour earlier. Two compounding test-setup bugs:

1. Sub-minute drift between iso and date+time. `localDateTimeFromNow`
   returned a millisecond-precision iso but minute-precision date+time.
   The blocked reservation was inserted with iso (e.g., 00:14:35.123Z)
   while the availability call used date+time (00:14:00). The 35-sec
   offset turned the +90-min candidate (which exactly abuts the
   reservation's end) into an apparent overlap, killing the only
   candidate that should have been a valid alternative.

2. UTC midnight boundary. When CI runs late in Chicago afternoon,
   now+5h crosses the UTC date boundary. The DB function's
   alternatives walk constrains candidates to the same UTC day as the
   requested slot, so the negative offsets (-90/-60/-30) all skip,
   leaving only +30/+60/+90 — and with bug #1, none of those produce
   an alternative.

Fix:
- Round localDateTimeFromNow down to the minute so iso/date/time
  align; eliminates bug #1 for every caller of this helper.
- Add tomorrowAtHour(12) helper for tests that need a deterministic
  slot fully inside one UTC day and well inside operating hours.
  Switch the alternatives test to use it.

The DB function is correct; the test setup was the bug. Marks the
AGENTS.md follow-up as fixed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ByteStreams-AI added a commit that referenced this pull request May 4, 2026
* fix(voice): mandate "anything else?" + broaden finalize trigger

M11 ramp surfaced a wedge: caller orders two items, both land in cart
correctly via add_item_to_order, but finalize_order is never called.
Cart sits at $16.98 untouched, the call goes silent.

Root cause: the prompt told the LLM to call finalize_order "when the
customer says they're done" but never instructed it to ASK if they
were done. The model confirmed each item, called add_item_to_order,
then waited indefinitely for the caller to volunteer "that's it" —
which most callers don't say.

Two prompt fixes (no code path changes):

- Step 4f: ALWAYS ask "Anything else?" after each add_item_to_order.
  Not optional, not tone-dependent. Going silent wedges the call.
- Step 5: broaden the done-signal recognizer from explicit "that's
  it" / "I'm done" to also include implicit no-answers ("no thanks",
  "nope", "no that's all"). Treats any "no" answer to "anything
  else?" as a done signal.

Two new prompt-template tests lock both behaviors so a future prompt
edit can't silently regress.

Captured live during a Sui's call where call_id
019df454-3639-7000-8ed1-acce72d91492 had Cucumber Roll +
California Roll w/ white rice in raw_payload.cart but tool_log ended
at the second add_item_to_order.

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

* docs: log reservation alternative-slot test as time-of-day flaky

Adds open follow-up #6 capturing the flake observed on PR #24's full
lane. Same test passed on PR #23's run an hour earlier; the failing
PR #24 diff is prompt text only — zero impact on the reservation
availability path. The flake is real and worth tracking, but not
caused by the prompt change.

Captures: where it lives, what triggers it, the proposed real fix
(deterministic slot anchor instead of `now + 5h`).

* test(reservations): pin alternative-slot test to deterministic slot

PR #24's full lane started failing on
  vapi_check_availability > returns alternative slots when the
  requested time is unavailable
even though the diff is prompt text only. Same code paths passed on
PR #23 an hour earlier. Two compounding test-setup bugs:

1. Sub-minute drift between iso and date+time. `localDateTimeFromNow`
   returned a millisecond-precision iso but minute-precision date+time.
   The blocked reservation was inserted with iso (e.g., 00:14:35.123Z)
   while the availability call used date+time (00:14:00). The 35-sec
   offset turned the +90-min candidate (which exactly abuts the
   reservation's end) into an apparent overlap, killing the only
   candidate that should have been a valid alternative.

2. UTC midnight boundary. When CI runs late in Chicago afternoon,
   now+5h crosses the UTC date boundary. The DB function's
   alternatives walk constrains candidates to the same UTC day as the
   requested slot, so the negative offsets (-90/-60/-30) all skip,
   leaving only +30/+60/+90 — and with bug #1, none of those produce
   an alternative.

Fix:
- Round localDateTimeFromNow down to the minute so iso/date/time
  align; eliminates bug #1 for every caller of this helper.
- Add tomorrowAtHour(12) helper for tests that need a deterministic
  slot fully inside one UTC day and well inside operating hours.
  Switch the alternatives test to use it.

The DB function is correct; the test setup was the bug. Marks the
AGENTS.md follow-up as fixed.

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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