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
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:
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:
- Reads your agent’s tools — names and schemas — off the
agentobject 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. - Learns which tools Egma answers for in this simulation.
- 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. - Leaves every other tool exactly as it was.
Placement
Callmockable 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 room →
mockablereturns 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) →
ToolErroris 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 callingmockable 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 theegma logger:
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
Thelivekit-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 ownmock_tools with a guard you write yourself:
- 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.
mockableexists to own that coupling so your side stays one line.