pgmem
Guides

LiveKit / voice agents

Wire pgmem into a LiveKit voice agent.

pgmem is built for stateless voice workers: durable state lives in Postgres, no compaction runs on the response path, and there's no dependency on a long-lived process. If a worker crashes, scales out, or is replaced mid-call, resume restores the session without losing the conversation.

Prerequisites

Steps

Construct PgMem once, at worker startup

It holds the pool and is shared across calls — never per-turn.

mem = await PgMem.create(dsn, embedder=embedder, llm=llm)

Resume or create per call

Persist the session.uuid on your job; resume it if the worker reconnects, otherwise start fresh.

class SupportAgent(Agent):
    async def on_turn(self, turn):
        session = (
            await mem.session.resume(self.session_id, self.group_id)
            if self.session_id
            else await mem.session.create(group_id=self.group_id, user_id=self.user_id)
        )
        self.session_id = session.uuid

Keep only retrieval on the turn

Record the input, compile, reply, record the output — then compact off the turn. Nothing here calls an LLM except your own reply generation.

        await session.add_turn(role="user", content=turn.text)
        context = await session.compile_context(query=turn.text)

        reply = await self.respond(context.messages)   # your model call
        await session.add_turn(role="assistant", content=reply)

        await session.compact_async()   # runs off-turn, returns immediately
        return reply

Verify

Kill the worker mid-call and bring it back: mem.session.resume(session_id, group_id) restores the active execution state and recent turns, so the agent continues where it left off. A brand-new call gets a fresh session and retrieves only the durable continuity it needs.

Gotchas

Do not await heavy work before returning the reply. add_turn is a cheap write, but compact_async, add_episode, and session.end (promotion) must stay off the response path — for voice, a blocked turn is a dropped beat in the conversation.

  • A construction-time injected_context_provider runs on the turn — for live state prefer the per-call injected_context= so the fetch is concurrent.
  • The repo has a fuller wiring sketch at docs/livekit-integration.md.

On this page