A surprising amount of what a voice agent does has nothing to do with talking. Looking up who is calling. Checking consent and whether it is a legal hour to call them back. Writing the summary. Syncing the CRM. Sending the follow-up text. Working out what the call cost.
None of that needs the caller on the line. Bookends is the voice agent pattern that moves it out of the live conversation, to before the first word and after the last.
Use it when
Almost always. This is the one pattern nearly every production agent should have.
Before the call: identify the caller from their number, load their history and preferences, check consent and the calling window for outbound calls, pick the right agent and language. All of it hides inside ring time.
After the call: summary, structured extraction (what was ordered, what was promised), CRM sync, follow-up text or email, call scoring, and the cost receipt for billing.
For an agency, the after half is what the client actually sees. Transcripts are nice. A summary in their CRM within a minute of hanging up is what renews the contract.
Latency
The briefing hides inside ring time. The wrap-up adds nothing to the call, because the caller is already gone.
Build it on LiveKit
The before half runs in the job entrypoint, before the session starts. The after half is a shutdown callback.
from livekit.agents import AgentServer, AgentSession, JobContext
server = AgentServer()
@server.rtc_session()
async def entrypoint(ctx: JobContext) -> None:
# Before the first word: hidden inside ring time.
caller = await crm.lookup(phone=caller_number(ctx))
if not await consent.allowed(caller):
return
session = AgentSession(...)
# After the last word: the caller is gone, take your time.
async def wrap_up() -> None:
summary = await summarise(session.history)
await crm.log_call(caller.id, summary)
await sms.send(caller.phone, follow_up_text(summary))
ctx.add_shutdown_callback(wrap_up)
await session.start(agent=Receptionist(caller=caller), room=ctx.room)
ShipVoice Pro already does caller identity, the dial gate, call reporting and the end-of-call notification this way.
How it combines
Bookends wrap every other pattern. The briefing decides which agent picks up, Solo for a new caller, the VIP desk for a regular. The wrap-up collects what every phase of a Relay produced and what the Observer flagged. Attach caller memory and guardrails as capabilities here: memory loads in the briefing and saves in the wrap-up, and compliance checks run before any outbound dial.
Common questions
What should a voice agent do before the call starts?
Identify the caller from their number, load their history, check consent and the calling window for outbound calls, and pick the right agent and language. All of this fits inside ring time.
What should happen after a voice AI call ends?
Write the summary, extract structured data, sync the CRM, send any follow-up text, score the call and record what it cost.
How do I run code after a LiveKit call ends?
Register a shutdown callback with ctx.add_shutdown_callback in the job entrypoint. It runs after the session ends, when the caller is already gone.
Sources and further reading
Mahimai Raja
Mahimai builds production voice agents on LiveKit for small businesses and maintains the open-source livekit-starter. He is building ShipVoice, the voice AI platform for agencies. Find him on X or at [email protected].