Messages

Brute’s conversation log is a plain Array. Each entry is a Brute::Message — an immutable Data value that is Brute’s canonical, framework-agnostic message format.

Brute::Message = Data.define(:role, :content, :tool_calls, :tool_call_id)
Field Type Notes
role Symbol :user, :assistant, :system, or :tool (string roles are symbolized)
content String the text; may be "" on a tool-call assistant turn
tool_calls Array of Brute::ToolCall or nil assistant turns that call tools
tool_call_id String or nil links a :tool result back to its call
Brute::ToolCall = Data.define(:id, :name, :arguments)   # arguments is always a Hash

Building a log

Brute.log returns an Array extended with role-tagging sugar (the Brute::Messages module):

log = Brute.log
log.user("list the files")
log.assistant("")            # ... with tool calls, usually
log.tool("a.rb b.rb", tool_call_id: "tc1")

log.first.role       # => :user
log.first.content    # => "list the files"

You can also build messages directly:

Brute::Message.new(role: :user, content: "hi")

Brute::Message.new(role: :assistant, content: "", tool_calls: [
  Brute::ToolCall.new(id: "tc1", name: "shell", arguments: { "command" => "ls" }),
])

Brute::Message.new(role: :tool, content: "result", tool_call_id: "tc1")

tool_calls accepts hashes and coerces them into ToolCall, so a message rebuilt from parsed JSON just works:

Brute::Message.new(**JSON.parse(line, symbolize_names: true))

Helpers

message.tool_call?   # => true when tool_calls is present and non-empty
message.to_h         # plain Hash, nils dropped, tool calls as hashes — JSON-ready

to_h round-trips: Brute::Message.new(**m.to_h) == m. This is exactly what SessionLog writes to and reads from disk.

Duck typing

Nothing in Brute’s stack calls anything beyond #role, #content, #tool_calls, #tool_call_id, and #to_h. Brute::Message is the canonical implementation, but any object exposing those methods can ride in env[:messages]. That is the seam that keeps Brute framework-agnostic — and the reason a library’s own message objects can pass straight through if you’d rather not convert them.

The conversion between Brute::Message and a specific LLM library’s format is handled by a MessageTransport.


This site uses Just the Docs, a documentation theme for Jekyll.