Class: Brute::Loop::AgentStream

Inherits:
LLM::Stream
  • Object
show all
Defined in:
lib/brute/loop/agent_stream.rb

Overview

Bridges llm.rb’s streaming callbacks to the host application.

Text and reasoning chunks fire immediately as the LLM generates them. Tool calls are collected but NOT executed — execution is deferred to the agent loop after the stream completes. This ensures text is never concurrent with tool execution.

After the stream finishes, the agent loop reads pending_tools to dispatch all tool calls concurrently, then fires on_tool_call_start once with the full batch.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(on_content: nil, on_reasoning: nil, on_question: nil) ⇒ AgentStream

Returns a new instance of AgentStream.



28
29
30
31
32
33
34
# File 'lib/brute/loop/agent_stream.rb', line 28

def initialize(on_content: nil, on_reasoning: nil, on_question: nil)
  @on_content = on_content
  @on_reasoning = on_reasoning
  @on_question = on_question
  @pending_tool_calls = []
  @pending_tools = []
end

Instance Attribute Details

#on_questionObject (readonly)

The on_question callback, needed by the agent loop to set thread/fiber-locals before tool execution.



38
39
40
# File 'lib/brute/loop/agent_stream.rb', line 38

def on_question
  @on_question
end

#pending_tool_callsObject (readonly)

Tool call metadata recorded during streaming, used by ToolUseGuard when ctx.functions is empty (nil-choice bug in llm.rb).



22
23
24
# File 'lib/brute/loop/agent_stream.rb', line 22

def pending_tool_calls
  @pending_tool_calls
end

#pending_toolsObject (readonly)

Deferred tool/error pairs: [(LLM::Function, error_or_nil), …] The agent loop reads these after the stream completes.



26
27
28
# File 'lib/brute/loop/agent_stream.rb', line 26

def pending_tools
  @pending_tools
end

Instance Method Details

#clear_pending_tool_calls!Object

Clear only the tool call metadata (used by ToolUseGuard after it has consumed the data for synthetic message injection).



57
58
59
# File 'lib/brute/loop/agent_stream.rb', line 57

def clear_pending_tool_calls!
  @pending_tool_calls.clear
end

#clear_pending_tools!Object

Clear the deferred execution queue after the agent loop has consumed and dispatched all tool calls.



63
64
65
# File 'lib/brute/loop/agent_stream.rb', line 63

def clear_pending_tools!
  @pending_tools.clear
end

#on_content(text) ⇒ Object



40
41
42
# File 'lib/brute/loop/agent_stream.rb', line 40

def on_content(text)
  @on_content&.call(text)
end

#on_reasoning_content(text) ⇒ Object



44
45
46
# File 'lib/brute/loop/agent_stream.rb', line 44

def on_reasoning_content(text)
  @on_reasoning&.call(text)
end

#on_tool_call(tool, error) ⇒ Object

Called by llm.rb per tool as it arrives during streaming. Records only — no execution, no threads, no queue pushes.



50
51
52
53
# File 'lib/brute/loop/agent_stream.rb', line 50

def on_tool_call(tool, error)
  @pending_tool_calls << { id: tool.id, name: tool.name, arguments: tool.arguments }
  @pending_tools << [tool, error]
end