| name | zooid-instrumentation |
| description | Build, instrument, and check the quality of Voice AI agents with the zooid toolkit — scaffolding new projects with `zooid init`, wiring up `instrument_voice_app()`/`VoiceTurnContext` and the provider wrappers, and verifying OpenTelemetry instrumentation quality via the Telemetry Trust Center MCP server. Use this whenever creating a new voice agent project, adding instrumentation to voice-agent code, or writing/editing code that uses zooid's OpenAI/ElevenLabs/Deepgram wrappers — to scaffold correctly, instrument correctly, and verify the instrumentation score before and after changes. |
| license | MIT |
| metadata | {"version":"0.2.0"} |
Zooid Voice AI Toolkit
Zooid is an OpenTelemetry-native observability suite for real-time Voice AI, built on
SigNoz. This skill covers the whole toolkit: scaffolding a new instrumented project,
wiring up tracing/metrics by hand, and continuously checking instrumentation quality
via the Telemetry Trust Center MCP server.
When to use it
- Starting a new voice agent project → use
zooid init (Scaffolding, below) instead
of hand-writing boilerplate.
- Writing or editing voice-agent code that uses
instrument_voice_app(),
VoiceTurnContext, or the OpenAI/ElevenLabs/Deepgram wrappers → follow the
Instrumentation Setup and Provider Wrappers sections so spans/attributes match what
the Trust Center expects.
- Adding or changing spans, span attributes, or resource attributes on existing
instrumentation → check the score before and after with the MCP tools, so you can
prove the change didn't regress.
Scaffolding a new agent (zooid init)
Don't hand-write a new voice agent from scratch — scaffold it:
zooid init my-agent --framework pipecat --mode local
This generates a runnable project with real (not placeholder) STT/LLM/TTS integration
code, already wired to instrument_voice_app() and VoiceTurnContext, plus a
docker-compose stack (SigNoz + the agent + the MCP server below) and a .mcp.json so
this skill's tools are available immediately. Key flags: --framework {pipecat,livekit,vapi}, --arch {cascade,s2s,hybrid}, --mode {local,cloud} (local
needs no cloud API keys), --stt/--llm/--tts to pick providers per stage.
Instrumentation setup
instrument_voice_app() is the one-line entry point — call it once at process start:
from zooid import instrument_voice_app
handle = instrument_voice_app(service_name="my-voice-agent", enable_trust_center=True)
It sets up the TracerProvider/MeterProvider, patches any installed provider SDKs, and
(with enable_trust_center=True, the default) starts the Trust Center span processor
and the background writer that keeps .zooid/context.json up to date for this
skill's MCP tools. Call handle.shutdown() on exit to flush exporters and stop the
writer cleanly.
Wrap each user turn in VoiceTurnContext and mark the three timing milestones — this
is what the Trust Center's voice-specific rules (V001–V005) check for:
from zooid import VoiceTurnContext
async with VoiceTurnContext(architecture_mode="hybrid") as turn:
transcript = await transcribe(audio)
turn.mark_stt_complete()
reply = await generate(transcript)
turn.mark_llm_first_token()
await synthesize(reply)
turn.mark_first_audio()
For work spawned off the main turn (background asyncio.create_task, WebSocket
callbacks), use create_traced_task() / traced_callback() instead of the raw asyncio
equivalents — they re-attach the current trace and voice-turn context, so spans aren't
orphaned.
Provider wrappers
instrument_voice_app() auto-patches whichever of these SDKs are installed — no
changes needed to your own provider calls:
- OpenAI — wraps
Completions.create (sync/async, streaming/non-streaming) as
openai.chat.completion; records llm.time_to_first_token_ms and calls
turn.mark_llm_first_token() on the active turn.
- ElevenLabs — wraps
generate() / text_to_speech.convert[_as_stream]() as
elevenlabs.tts.generate; records tts.time_to_first_byte_ms and calls
turn.mark_first_audio() — this is what actually records TTFA in most pipelines.
- Deepgram — wraps REST transcription as
deepgram.stt.transcribe, and live
connections via a transcript-event instrumentor; calls turn.mark_stt_complete() on
the final transcript.
Bring your own LLM: zooid init --llm custom --llm-model-name <name> --llm-base-url <url> targets any OpenAI-compatible endpoint (self-hosted, fine-tuned, a different
vendor). It reuses the same OpenAI wrapper — no new instrumentation code — and records
llm.provider as custom:<model-name> so it stays informative. This deliberately does
not extend to STT/TTS: unlike chat completions, there's no dominant compatible API
shape across STT/TTS vendors, so a free-text provider there would produce
unscoreable telemetry. For a new STT/TTS integration, set stt.provider /
tts.provider on the span by hand (below) instead.
If you're adding a new provider integration by hand (not covered by a wrapper), set
stt.provider / llm.provider / tts.provider on the relevant span yourself — the
Trust Center's voice rules check for these attributes.
Checking instrumentation quality (Trust Center MCP)
The Zooid Telemetry Trust Center continuously scores the OpenTelemetry
instrumentation of a running Voice AI agent (0–100, categorized Poor / Fair /
Good / Excellent) and generates concrete fixes.
This is not a general SigNoz telemetry query tool (SigNoz ships its own MCP
server for that). It exposes Zooid's domain-specific instrumentation-quality
logic only, and is read-only.
Workflow: call get_instrumentation_score() before your edit, make the change,
let the agent run briefly (it refreshes .zooid/context.json automatically), call
get_instrumentation_score() again. If the score dropped or new rules fail,
call get_fix_recommendations() and apply the snippets, then use
explain_score_delta to summarize what changed.
Setup
Projects scaffolded with zooid init already ship a .mcp.json pre-wired to the
local server:
{ "mcpServers": { "zooid-trust-center": { "type": "http", "url": "http://localhost:8000/mcp" } } }
docker compose up starts the server (zooid-mcp). No manual configuration.
Every scaffolded project also ships an AGENTS.md covering the same MCP tools and
conventions in the cross-tool AGENTS.md convention — so coding
agents that don't use Claude's Agent Skills format (Codex CLI, Cursor, etc.) get
equivalent guidance automatically, not just Claude Code.
Tools (all read-only)
-
get_instrumentation_score() → { score, category, sample_count, rules: [{ id, description, priority, passed }] }.
The current score and a per-rule pass/fail breakdown.
-
get_fix_recommendations() → [{ rule_id, severity, message, fix_snippet }].
Structured, actionable fixes for the rules that are currently failing. Apply the
fix_snippet where present.
-
explain_score_delta(before, after) → plain-language diff. Pass two snapshots
(each { score, category, failing_rules }, as found in .zooid/context.json);
it names which rules started/stopped failing and the net score change, and is
priority-aware — it calls out whichever severity tier drove the change rather than
just listing rule IDs flatly. Example output:
"Score improved by 22.8 points (70.0 -> 92.8), driven mainly by 1 CRITICAL fix
(V003). Category moved from Good to Excellent. Now passing: V001, V003."
.zooid/context.json
The running agent writes the latest full result here (overwritten on change):
{
"service_name": "...", "score": 93.6, "category": "Excellent",
"sample_count": 12, "unscorable_count": 0,
"rules": [{ "id": "V003", "description": "...", "priority": "CRITICAL", "passed": true }],
"failing_rules": ["R008", "V001"],
"recommendations": [{ "rule_id": "R008",
You can read this file directly if the MCP server isn't reachable — it holds the
same data the tools return.
Guidance
- Prioritize
CRITICAL and IMPORTANT failing rules; they weigh most in the score.
- Common voice fixes: ensure
voice.turn spans record voice.time_to_first_audio_ms
(call turn.mark_first_audio()), and set stt.provider / tts.provider /
voice.provider on the relevant spans.
- Prefer
zooid init over hand-scaffolding a new project — it already wires
instrumentation, the wrappers, and this skill's MCP config correctly.
- Do not fabricate scores — always read them from the tools or
.zooid/context.json.