Repository navigation
feat(voice): add DecisionGuardrail to check turns with a decision model - #8504
Open
tinalenguyen wants to merge 3 commits into
Open
tinalenguyen wants to merge 3 commits into
tinalenguyen wants to merge 3 commits into
Conversation
DecisionGuardrail runs DecisionModel checks at three points in a session: - on_user_turn, before the agent replies: handlers return Block (no reply, optionally say something instead) or Steer (instructions for this reply). Final transcripts are evaluated eagerly so the result is usually ready at end of turn. - on_agent_sentence, between the LLM and the TTS / transcript: each sentence is checked before it is spoken. Block cancels the LLM, lets the sentences already allowed finish, and says the replacement after the reply. - on_agent_turn, after each agent message: checks only. on_error decides what a failed or timed-out check means (allow or block).
tinalenguyen
marked this pull request as ready for review
November 10, 2026 23:22
- Steer reaches realtime replies through the reply's instructions, since a realtime reply doesn't read the chat context. - An eager evaluation is reused only when the model saw the same input, so history checks see edits made in on_user_turn_completed. Eager history now starts from the agent's context rather than the session history. - The timeout covers the evaluation and an async handler together. - A decision without a result fails the whole check (on_error applies), so a handler reading a default can't let through what on_error="block" stops. - Guardrails close after the activity drains, so speech still playing out keeps its checks. - Logging an unsupported result records only its type, not its text.
Contributor
There was a problem hiding this comment.
Devin Review found 1 new potential issue.
1 flag not posted on this PR by your GitHub settings — view it in Devin Review. (Configure)
Comment on lines
+317
to
+323
| def _agent_history(self) -> llm.ChatContext: | ||
| # the context the reply is generated from, before on_user_turn_completed edits it | ||
| try: | ||
| return self._session.current_agent.chat_ctx | ||
| except RuntimeError: | ||
| return self._session.history | ||
|
|
Contributor
There was a problem hiding this comment.
🟡 Agent history helper lacks required documentation
The new _agent_history method has no docstring. Repository rules require documentation for every new method.
Suggested change
| def _agent_history(self) -> llm.ChatContext: | |
| # the context the reply is generated from, before on_user_turn_completed edits it | |
| try: | |
| return self._session.current_agent.chat_ctx | |
| except RuntimeError: | |
| return self._session.history | |
| def _agent_history(self) -> llm.ChatContext: | |
| """Return the active agent's chat context, or session history if inactive.""" | |
| try: | |
| return self._session.current_agent.chat_ctx | |
| except RuntimeError: | |
| return self._session.history |
Was this helpful? React with 👍 or 👎 to provide feedback.
Every check can already evaluate any mix of probabilities, choices and scores, and a hook can have several handlers, so one guardrail covers a session. The session holds it in a single slot, exposed as session.guardrail, and creating a second one raises instead of running next to the first unnoticed. Closing a guardrail frees the slot.
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds
DecisionGuardrail, which runsDecisionModelchecks at three points in a session. Each check is a decorated handler that gets the results and returns what should happen next.Hooks
on_user_turnon_user_turn_completed, before the reply is scheduledBlock,Steer,Noneon_agent_sentenceBlock,Noneon_agent_turnconversation_item_addedfor assistant messagesNoneon_user_turnBlock: no reply. The user's message stays in the history, andsayis spoken if given.Steer: adds a system message to this turn's context only. A preemptive reply is then discarded by the existing equivalence check and regenerated. With a realtime model, it is passed as the reply'sinstructionsinstead.on_agent_sentenceretain_format=True, so the released text is exactly what the LLM produced. They're checked concurrently and released in order.Block: cancels the LLM, so there's no more text and no tool calls after the blocked sentence. Sentences already allowed finish playing and are the only ones kept in the history.sayis spoken after the reply, unless it was interrupted or never played.on_error("allow"/"block") decides what a failed check means: the evaluation fails or times out, any decision comes back without a result, or the handler raises. The timeout covers the evaluation and an async handler together.Blockwins; otherwise everySteerapplies.Probability,ChoiceandScore, and a hook can have several handlers, so one guardrail covers a session. It is exposed assession.guardrail; creating a second one raises.Not in this PR
say()text and realtime-model replies don't go throughon_agent_sentence.Tests
tests/test_guardrail.py: unit tests for results, errors, timeouts, eager reuse, and the sentence gate.tests/test_guardrail_session.py: throughAgentSessionwith the fake models. CoversBlockandSteeron a user turn, a sentenceBlockmid-reply, andon_agent_turn.