Skip to content

[TH-9519] fix(harness): ask for the ElevenLabs key under the name LiveKit's plugin reads - #299

Open
khushalsonawat wants to merge 4 commits into
feat/TH-8804-source-subfolderfrom
feat/TH-9519-eleven-api-key
Open

khushalsonawat wants to merge 4 commits into
feat/TH-8804-source-subfolderfrom
feat/TH-9519-eleven-api-key

Conversation

@khushalsonawat

@khushalsonawat khushalsonawat commented Nov 11, 2026 •

Copy link
Copy Markdown
Member

Description

The credential check asked for ELEVENLABS_API_KEY for LiveKit's ElevenLabs plugin, but the plugin reads ELEVEN_API_KEY, so an honest voice agent's key never reached it and the run failed. The check now asks for the name the plugin reads, and a constructor that passes its own api_key= is judged by what it passes.

Paired with future-agi/future-agi#3959, whose preflight probe accepts either name. Merge order: future-agi#3959 first, or both together.

Type of Change

  • Bug fix (non-breaking change which fixes an issue)

Changes

  • The plugin row asks for ELEVEN_API_KEY, and both ELEVEN_ and ELEVENLABS_ names are labelled ElevenLabs text-to-speech.
  • api_key= on a LiveKit plugin constructor (_plugin_key_names, read with ast):
    • none: the plugin's own name, as today;
    • a non-empty literal: nothing;
    • os.environ["NAME"]: the code check already requires NAME;
    • os.getenv("NAME", d) or os.environ.get(...): NAME is required when d is absent, None, empty or a placeholder, and nothing when d is a real literal;
    • anything the check can't read (a variable, a settings object, **opts, a bare getenv, a lowercase name): the plugin's own name, and for ElevenLabs both names. That over-asks, never under-asks, for the plugin's names.
  • A name an SDK constructor requires keeps required even where another line gives it a default.
  • A reserved name passed as the key (getenv("ALK_X"), getenv("HOST")): the customer can never supply it, so the check asks for the plugin's own name instead of crashing or calling the agent ready. Cartesia, Deepgram TTS and OpenAI realtime then read their own variable. OpenAI LLM, Deepgram STT and ElevenLabs refuse an explicit None, so for them the run fails at the plugin either way; asking at least says so before the build.
  • oss/simulation-acceptance/.env.example lists ELEVEN_API_KEY.

Testing

  • tests/test_harness_credentials.py: 112 passed (26 new); tests/harness/test_runtime_repair.py: 213 passed. Python 3.12.3.
  • Planted bugs: 13, each caught by its test.
  • Reviews: design in four rounds (one reviewer each; round 4 not clear, its fix taken without a fifth round on the Coordinator's call). Change review: one reviewer, cleared. Its Medium (the reserved-name crash) and test gaps were fixed in 62f81e2d. Fix-only checks since:
    • 62f81e2d, two reviewers: both clear. B's Medium (a reserved key made the agent "ready") was answered in 5ad07d83, whose one-reviewer check was clear but showed four plugins fall back to their own variable.
    • f6e9660f asks for the plugin's own name. Two reviewers: both clear, 0 High, 0 Medium. Results are in the comments.

Known limits

  • An unreadable api_key= that reads a name other than the plugin's own (STT(api_key=getenv("MY_DG").strip()), **{"api_key": ...}, from os import environ as e) asks for the plugin's name, not the name the code reads. This was true before this change too.
  • A real literal default, getenv("DG_KEY", "dummy"), counts as a key, so nothing is asked. Behind a reserved name, getenv("ALK_X", "sk-..."), it doesn't: the plugin's own name is asked for, though the plugin would use the literal.
  • ElevenLabs is asked for both names when its key can't be read, though the installed plugin reads only ELEVEN_API_KEY.
  • os.environ["ALK_X"] as the key still asks for nothing: the agent fails at that lookup, so no name would help.
  • Over-asking by design: Cartesia and Deepgram's TTS fall back to their own variable on an empty key, so api_key=getenv("CART") is asked for CART even with CARTESIA_API_KEY supplied.
  • Labels: a plugin-required name like MY_DEEPGRAM shows as agent runtime configuration, and ElevenLabs' STT is labelled text-to-speech.
  • A customer who saved the key as ELEVENLABS_API_KEY adds it again as ELEVEN_API_KEY.
  • Same file: fix(harness): review follow-ups for #269 and #276 (TH-8952, TH-9490) #297 edits credentials.py elsewhere; no function overlaps.

Related Issues

Refs: TH-9519

…gin reads

The credential check listed ELEVENLABS_API_KEY for the ElevenLabs plugin,
but the plugin reads ELEVEN_API_KEY, so a supplied key never reached it.
The plugin row now asks for ELEVEN_API_KEY and both prefixes are labelled
ElevenLabs.

A constructor that passes its own api_key= is now judged by what it
passes, read with ast: a literal or a defaulted read drops the plugin's
name, a read of a named variable asks for that name instead, and anything
unreadable keeps the plugin's row (both names for ElevenLabs). A name
declared by an SDK constructor keeps its requirement even with a default.

TH-9519

Signed-off-by: khushalsonawat <khushalsonawat@gmail.com>
A plugin constructor whose api_key= reads a reserved name, such as the
gateway's ALK_ key, made the credential check raise: the name is left out
of the findings, then indexed. It is now skipped. Two more unreadable
shapes are tested: a lowercase getenv name and a blank literal.

TH-9519

Signed-off-by: khushalsonawat <khushalsonawat@gmail.com>
…e plugin's key

The #299 check found the test only asserted the reserved name was absent, so
it allowed either answer. It now pins the one chosen: nothing is asked for.
The plugin is handed the reserved name's value (None in the agent), and
LiveKit treats an explicit None as given rather than falling back to its own
variable, so asking for OPENAI_API_KEY or DEEPGRAM_API_KEY could never help.
The guard's comment says so. Asking for the plugin's own name now fails the
test.

TH-9519

Signed-off-by: khushalsonawat <khushalsonawat@gmail.com>
…ved name

When an agent passes a reserved name as a LiveKit plugin's key
(api_key=os.getenv("ALK_X")), the check skipped it and called the agent
ready. The #299 checks showed several plugins (Cartesia, Deepgram TTS, OpenAI
realtime) then read their own variable, which the check no longer asked for.
It now asks for the plugin's own name, as the module's rule says: err towards
asking. The test covers OpenAI LLM, Deepgram STT, Cartesia TTS and the OpenAI
realtime model.

TH-9519

Signed-off-by: khushalsonawat <khushalsonawat@gmail.com>
@khushalsonawat

Copy link
Copy Markdown
Member Author

Fix-only checks since the change review:

  • 62f81e2 (skip a reserved name passed as the key), two reviewers:
    • A: clear, 0 High, 0 Medium, 2 Low.
    • B: clear, 0 High, 1 Medium, 1 Low. B's Medium: the agent was then marked ready with nothing asked.
  • 5ad07d8 (pin "nothing asked" in the test), one reviewer: clear, 0 High, 1 Medium, 1 Low. The Medium: the comment was wrong for four constructors. Cartesia STT and TTS, Deepgram TTS and OpenAI realtime fall back to their own variable, so skipping hid a name that would make the run work.
  • f6e9660 (ask for the plugin's own name), two reviewers:
    • A: clear, 0 High, 0 Medium, 2 Low. A checked 11 constructors against the installed 1.8.3 plugins.
    • B: clear, 0 High, 0 Medium, 2 Low. B ran 80 constructor cases.
    • Planting back the old skip fails 4 tests. Narrowing the check to ALK_ names fails the HOST case.
    • Lows kept as known limits: a usable literal default behind a reserved name is now asked for; ElevenLabs is asked for two names when its key can't be read; the guard after record() is now an unused backstop.

This branch has not been deployed

No deployments
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