Skip to main content
The egma Python package lets Egma answer your agent’s tool calls during a simulation, without touching anything in production. Add one call after your agent is built and Egma intercepts the tools it has mock answers for. Every other tool — and every room with no Egma in it — runs exactly as if the package were not installed. This solves a real problem: a simulation that reaches your real tools has real side effects. It books the appointment, sends the message, charges the card. And a real backend only ever shows you the branch its data happens to be on — “the calendar is full”, “the lookup fails”, “the booking API errors” are where voice agents die in production, but none of them can be ordered up on demand from a real backend.

Install

Or with uv:
Requires Python 3.11 or newer and livekit-agents>=1.6.7,<1.7.
The livekit-agents version is pinned to one minor version intentionally. The interception mechanism uses LiveKit’s own mock_tools, which lives in the framework’s testing namespace and carries no stability promise. The pin plus a live smoke test — a real session in a real room — is how this package verifies the mechanism still works before you find out in a simulation. See Version pinning below.

The one API: mockable

Import mockable and call it once in your agent’s entrypoint, after the agent object exists and before session.start:
That is the whole integration.

What mockable does

mockable reads the dispatch metadata on ctx.job once. In any room without an Egma participant in it — which is every production room — it returns immediately having touched nothing. Your tools are the same objects, called the same way, with no wrapper between them and the model. This is enforced by a test in the package (tests/test_inert.py), not just described in documentation. In a simulation, mockable:
  1. Reads your agent’s tools — names and schemas — off the agent object and sends them to Egma as a census. Mock authoring in Egma starts from your real tool names and signatures, not from your memory of them.
  2. Learns which tools Egma answers for in this simulation.
  3. Installs a courier in front of each of those tools using LiveKit’s own mock_tools. Couriers go into a side table LiveKit keeps per session; they do not touch your agent, your agent’s class, or your tool registry. The model keeps seeing your real tool schemas throughout.
  4. Leaves every other tool exactly as it was.
Every covered tool call lands on the simulation record with its arguments, its answer, how long it took, and which mock tool answered it. Tools that ran their real implementation are also marked, so you always know whether a simulation was fully isolated.

Placement

Call mockable after the agent object exists and before session.start. The census is the first message sent, so an Egma that is not in the room is discovered before any tool call rather than halfway through a test. If your application hands off between several Agent classes, call mockable once per class you want covered.

Production safety guarantee

The inert path is not a best-effort claim — it is a construction property with a test behind it.
  • No Egma participant in the roommockable returns immediately. Zero added latency, no side effects, no wrapped tools.
  • Egma present but unreachable mid-simulation → the real tool runs. A room that lost its Egma participant behaves as a room that never had one.
  • Egma explicitly refuses a call (unknown tool, malformed payload) → ToolError is raised so the model hears a tool that failed and can respond accordingly. Falling open on a refusal would mean a simulation has real side effects on Egma’s behalf, which is never the right outcome.

How tool matching works

Mock tools are matched by tool name, strictly. Egma sends a list of the names it will answer for in this simulation. A courier is installed for each of those names, whether or not the agent currently has a tool by that name. Matching happens per call, by name, against the side table LiveKit keeps. A tool you attach to the agent after calling mockable is still intercepted on its first call — because couriers are matched by name at call time, not at registration time. Its arguments may be incomplete on the record (since there was no signature to read them through when the courier was made), and Egma marks that call so you can see it. The transport used for tool calls is LiveKit RPC over the room you are already connected to. No new endpoint, no new credential, nothing new to expose.

Logging

Everything this package says goes to the egma logger:
At INFO level, the line after the census names how many tools your agent has and how many Egma answers for in this simulation. This is the first thing to check when wiring an agent up for the first time.

Version pinning

The livekit-agents dependency is pinned to >=1.6.7,<1.7. This is deliberate, not an oversight. The interception mechanism uses mock_tools from LiveKit’s testing namespace. That API carries no stability promise. The pin means an unread minor version cannot arrive by itself and silently break interception. Raising the ceiling is a deliberate act with a live smoke test as evidence — a real session in a real room, proving that interception actually happened. If mock_tools ever moves in a future version, the documented fallback is Agent.update_tools(...) with function_tool(raw_schema=...) wrappers built from the real tools’ schemas. That is a heavier mechanism — it owns schema fidelity, the real implementations, and re-wrapping after handoffs — which is exactly why it is the fallback and not the current approach.

Before you install anything: the interim recipe

If you need tool isolation today without the Egma SDK, you can use LiveKit’s own mock_tools with a guard you write yourself:
This is production-safe by the same logic — no Egma in the room means the guard is false and nothing is wrapped. But it has real limitations:
  • One canned answer per tool for every simulation. You cannot author different answers for different tests without editing your agent’s source code.
  • Nothing about those tool calls reaches Egma’s record: no arguments, no answers, no timings, no coverage stamp. Graders that read tool facts have nothing to read.
  • No declared delay, so latency numbers from a mocked run will flatter you.
  • The guard couples your agent to the exact shape of Egma’s dispatch metadata today. If that shape changes, your agent breaks in a way your own tests would not catch. mockable exists to own that coupling so your side stays one line.
Use this as a bridge while migrating, not as a permanent solution.