Skip to content

fix(agent): emit guardrail halt explanation to client stream (#30770)#30808

Open
Bartok9 wants to merge 1 commit into
NousResearch:mainfrom
Bartok9:fix/30770-guardrail-halt-emit-to-stream
Open

fix(agent): emit guardrail halt explanation to client stream (#30770)#30808
Bartok9 wants to merge 1 commit into
NousResearch:mainfrom
Bartok9:fix/30770-guardrail-halt-emit-to-stream

Conversation

@Bartok9

@Bartok9 Bartok9 commented May 23, 2026

Copy link
Copy Markdown
Contributor

Summary

When _tool_guardrail_halt_decision fires the turn loop appended the halt
message to messages (conversation history) but never called
_fire_stream_delta, leaving SSE / TUI clients with a silent stream close
indistinguishable from a crash.

Root Cause

# Before (conversation_loop.py ~3432)
if agent._tool_guardrail_halt_decision is not None:
    ...
    final_response = agent._toolguard_controlled_halt_response(decision)
    agent._emit_status(...)
    messages.append({'role': 'assistant', 'content': final_response})  # history only
    break

final_response was written to internal message history but never pushed to
the stream consumers (stream_delta_callback, TTS, gateway SSE).

Fix

Add a single _fire_stream_delta(final_response) call before messages.append:

agent._fire_stream_delta(final_response)   # ← emit to all stream consumers
messages.append({'role': 'assistant', 'content': final_response})

This matches how the normal (non-guardrail) response path delivers text to clients.

Testing

  • Added test_guardrail_halt_emits_final_response_to_stream to
    tests/agent/test_tool_guardrails.py — verifies _fire_stream_delta is
    called with the halt explanation text whenever _tool_guardrail_halt_decision
    is set.
python -m pytest tests/agent/test_tool_guardrails.py -v
# 1 passed

Fixes #30770

@alt-glitch alt-glitch added type/bug Something isn't working comp/agent Core agent loop, run_agent.py, prompt builder P1 High — major feature broken, no workaround labels May 23, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

Competing fix: PRs #30807 and #30813 also address #30770. This is the cleanest minimal approach (single _fire_stream_delta call). #30813 additionally covers stream flush and interim message adapters with a reusable helper.

…earch#30770)

When _tool_guardrail_halt_decision fires, the conversation loop appended
the halt message to `messages` (so it persisted to history) but never
called `_fire_stream_delta`, leaving SSE / TUI clients with a silent
stream close indistinguishable from a crash.

Fix: call `agent._fire_stream_delta(final_response)` immediately before
the messages.append so every registered stream consumer (display, TTS,
gateway SSE) receives the explanation.

Adds a regression test that verifies _fire_stream_delta is called with
the halt text whenever _tool_guardrail_halt_decision is set.

Fixes NousResearch#30770
@Bartok9 Bartok9 force-pushed the fix/30770-guardrail-halt-emit-to-stream branch from f788491 to 91f84f9 Compare May 27, 2026 20:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/agent Core agent loop, run_agent.py, prompt builder P1 High — major feature broken, no workaround type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

guardrail_halt exits silently — no final assistant message delivered to client

2 participants