How review works
Prism splits each spoken reply into chunks of one sentence, or a few short ones. It sends each chunk in aprism.review.requested as soon as the model writes it. Prism doesn’t wait for your answer to one chunk before it sends the next, so you can check several chunks at once. Prism speaks the chunks in order, and each chunk waits until it and every chunk before it have an answer.
Answer each chunk with a prism.review.result and one of three actions:
Turn on review
Setprism_review in your first session.update. The first update fixes it, so a later update can’t turn review on or change its settings.
session.updated shows the settings in effect, with timeout_ms filled in.
Answer each chunk
This script turns on review and asks Juniper Cafe two things: when it closes, and whether the caller can get a refund. Staff at the counter handle refunds, so the check replaces any chunk about refunds with the cafe’s approved line, and lets everything else through. To try it without a microphone, the script sends the question as a typed turn. In your agent, the review handler goes into the event loop that already plays Prism’s audio. The script starts a separate check for each chunk, and its event loop doesn’t wait for the check. If a check fails, the script prints the error and sends no answer. Prism then handles that chunk ason_timeout says. The script stops when the reply ends, after 60 seconds, or on an error. A review error, such as review_timeout, affects only one chunk, so the script prints it and keeps going.
- Python
- Node.js
- Answer fast: Prism doesn’t speak until the first chunk has an answer, so the time your check takes adds to the caller’s wait. With an instant answer, review adds about one network round trip between your backend and Prism before the first audio.
- Run checks at the same time: Prism sends each chunk without waiting for your answer to the last one. A loop that waits for each check reads no events while it waits. Each chunk then waits for every check ahead of it, and so does the audio that the loop plays. With a 400 ms check, the fifth chunk can wait about 1.5 seconds and time out. Start each check on its own, as the script does.
- Replace the whole chunk: a chunk can hold a few short sentences, and your text takes the place of all of them. Write a replacement that stands on its own.
- Write replacements for the ear: Prism speaks your text as written. It reads numbers and symbols out as in any reply, and it doesn’t remove markdown, so write plain sentences.
- Answer each chunk once: Prism refuses a second answer, or a
review_idthat it never sent, withreview_not_found. Sendtextonly withreplace. Prism refuses a mismatch withinvalid_client_event, and the chunk still waits for an answer. - Expect a late chunk now and then: a chunk that Prism sent before your
replaceorstopreached it can still arrive. Prism ignores your answer to it, and you don’t need to track which chunks still count.
Choose what happens on a timeout
If your answer doesn’t arrive withintimeout_ms, Prism sends an error with code review_timeout and scope review, and on_timeout decides the chunk. Prism ignores an answer that comes later.
Set
timeout_ms a little above your check’s slowest normal answer. A long timeout keeps the caller waiting when your check stalls, and a short one times out answers that were on their way.
What the caller hears and what Prism remembers
Everything that Prism reports about a reviewed reply matches what the caller heard:- Text:
response.text.deltaandresponse.text.donehold only what the review let through. That’s the chunks that you allowed or that Prism spoke on a timeout, and your replacement text. Captions built from them match the audio. - The end of the reply: after
allowandreplace, the reply completes with reasonstop. Afterstop, it’s cancelled with reasonreview_stopped, and after a timeout withon_timeoutstop, with reasonreview_timeout. - History: the model sees only what the caller heard. After a replacement, the next reply knows that Prism said your line, not the model’s.
prism.conversationlists it.
response.done and the history to know what was said. When a reply starts on an early guess that the caller has finished, its chunks can come before its response.created. If the caller keeps talking, that reply never gets a response.created, and Prism drops its reviews. Answer each chunk by its review_id, without waiting for response.created.
Tool calls aren’t reviewed
Review covers what Prism says, not the tools that it calls. Prism sends a call as soon as the model writes it, even while the line before the call waits for your answer. So stopping that line usually doesn’t stop the call. A stop drops a call only when Prism hasn’t spoken any of the reply and the model hasn’t finished writing the call. Once Prism has spoken part of the reply, it also sends the calls that the model writes after the stop. Don’t rely on review to block a call. Check each call’s arguments in your backend before you run it. The line that Prism speaks before a tool call goes through review, and so does the reply that continues with the result. Tools covers the whole tool round.Next
Tools
Run each tool call in your backend, and continue the reply with the result.
WebSocket reference
Every field of the review events, settings, and errors.

