Why Use Moss with LiveKit?
Moss delivers sub-10ms semantic retrieval, ensuring your voice agents respond naturally without noticeable delays. A session gives each call its own local index, so the agent can recall what was said earlier in the conversation with no cloud round trip.Required Tools
Integration Guide
1
Installation
Install the Moss SDK.
Versions below 1.9.0 will stop loading models when the legacy download path is retired. Upgrade to 1.9.0+ before then.
2
Environment Setup
Create a
.env file in your project root directory with your API keys.File: .env3
Creating the Knowledge Base with Moss
Before the agent can answer questions, build the long-term knowledge base. Run this script once to upload your documents to Moss as a cloud index.File: Run the builder:
build_index.py4
Building the Agent
This agent exposes Moss search as function tools. The LLM decides when to call them during a turn, reads the results, and uses them to answer, so it can search, refine, or skip retrieval on turns that don’t need it.Two tools are registered:
search_knowledge_basequeries the persistent knowledge base you built above (long-term context).search_conversationqueries this call’s session (short-term context) to recall something said earlier.
prewarm, with a cache_path. Each call reads
its knowledge base names from the job metadata and starts loading them before ctx.connect(), so
the loads run while the call connects. See Process model and index loading
for why.File: agent.pyThe session indexes each turn locally during the call and pushes to the cloud
at the end, so a later session opened with the same name resumes the
conversation, which is the basis for cross-agent handoff. See
Sessions for the full API.
5
Running the Agent
First, start the LiveKit server in development mode:Then, in a separate terminal, start your worker. The necessary VAD models will handle themselves or be downloaded automatically if needed by the plugin.
Process model and index loading
By default, LiveKit Agents runs each call in its own process. The worker keeps idle processes ready, runsprewarm once in each new process before a call is assigned to it, then gives the
process one call. The process exits when the call ends, so nothing loaded for a call carries
over to the next one.
That decides where each piece of Moss work goes:
prewarmcreates the client. It holds what every call needs: the VAD model, and aMossClientwith itscache_path.- The entrypoint loads the call’s indexes. Only the job knows which knowledge bases a call
needs. Read their names from the job metadata and start one
load_indexper index underasyncio.gatherbeforectx.connect(), so the loads run concurrently while the call connects. - Index loads stay out of
prewarm. It runs in every new process before any call is known, so loading every knowledge base there makes each process download, parse and hold all of them. It must also finish withininitialize_process_timeout, 10 seconds by default. cache_pathkeeps loads short. Without it, each load makes four round trips to the cloud, the last one downloading the index. With the index already in the cache it makes two and reads the index from disk. If the cloud is unreachable or answers with a temporary error, it serves the cached copy instead of failing; a 401, 403 or 404 still fails (Storage & Persistence). With a process per call, the embedding model opens fresh in every call and cannot open while the cloud is unreachable, so queries withalphaabove 0 fail during an outage; keyword-only queries (alpha=0) and queries that pass their own embedding keep working. Put the directory on storage that outlives the container.- The first load in a process also opens the embedding model, about half a second when the
model is on disk. The model is cached under
~/.cache/moss-models/. Keep that directory in the image or on persistent storage too, or every new container downloads the model (about 91 MB formoss-minilm) on its first call.
cache_path. See
Memory sizing to estimate a host’s memory. When many concurrent
calls use the same knowledge bases, two setups share that memory:
- The thread executor.
WorkerOptions(job_executor_type=JobExecutorType.THREAD)runs every call of a worker in one process, so calls share one client, one model and one copy of each index. Calls are no longer isolated: a crash or a blocking call affects every call on the worker. In this modeprewarmruns once per call, so keep the client at module level, as below. - A retrieval service per host. One process holds the client and the indexes and answers queries from every worker process on the host over a local connection. It fits when you want to keep one call per process, or when several services need the same indexes.
Retrieval and preemptive generation
LiveKit’sAgentSession runs preemptive generation by default: it starts drafting the reply
speculatively as soon as a final transcript arrives, before the turn is confirmed. In current LiveKit
Agents it is set through turn_handling (TurnHandlingOptions(preemptive_generation={"enabled": ...}));
the older top-level preemptive_generation argument is deprecated.
on_user_turn_completed runs later, at turn confirmation, after that draft has started. Injecting
retrieval there with turn_ctx.add_message makes the draft stale, so LiveKit discards and regenerates
it on every injected turn. So expose retrieval as a @function_tool (as the agent above does, and as
the Founding Agent runs in production): turn_ctx stays stable at the
boundary, and retrieval composes with preemptive generation. An ambient pattern that injects every turn
must instead turn preemptive generation off
(turn_handling=TurnHandlingOptions(preemptive_generation={"enabled": False})); see the
airline customer agent.