Brute
A framework-agnostic coding agent for Ruby. Rack-style middleware pipelines for agent turns, a full set of coding tools, and session persistence — bring your own LLM library.
Brute treats an agent turn the way Rack treats an
HTTP request: an env hash flowing through a middleware stack toward a terminal
app. The middleware handles the agentic machinery — the tool loop, session
persistence, the system prompt, iteration guards. The terminal app is a proc
you write with whatever LLM library you prefer (ruby_llm,
llm.rb, the official
openai or
anthropic gems, or raw
HTTP). Brute depends on none of them.
⚠️ Upgrading to v3? Brute 3.0.1 removes the hard dependency on
ruby_llmand replaces the oldBrute.rubyllm_toolshelper with a library-agnosticBrute.tools+Brute::MessageTransportAPI. This is a breaking change relative to the 3.0.0 gem — the 3.0.0 release accidentally shipped without the transport refactor. See CHANGELOG.md for the migration steps and pick a transport underBrute::MessageTransport::*that matches your LLM library.
Features
- Rack-style agent turns — compose middleware around your LLM call; the agentic loop is just another layer.
-
Bring your own LLM — no completion middleware and no LLM dependency;
MessageTransportclasses translate at the boundary for ruby_llm, llm.rb, openai, and anthropic. - A full coding toolset — read/write/patch/remove/search files, shell, HTTP fetch, todo lists, interactive questions, skills, sub-agents — executed concurrently with universal output truncation.
- Session persistence — the conversation log is JSONL on disk; reload it, fork it, or checkpoint and resume a crashed turn.
- Context management — sliding-window and tool-result compaction strategies with an optional summarizer, once the conversation fills the model's window.
- Skills — markdown instruction files implementing the Agent Skills specification, surfaced with progressive disclosure.
- Evals — grade whole assembled agents on what their turns did, with stubbed tools and budgets.
- Serve anywhere — wrap any agent as a Rack app behind Falcon or Puma.
Requirements
- Ruby >= 3.3
- An LLM library of your choice (optional — only your
runproc needs one)
Installation
Add Brute — plus whichever LLM library you plan to call — to your Gemfile:
gem "brute"
# plus one of:
gem "ruby_llm"
# gem "llm.rb"
# gem "openai"
# gem "anthropic"Then execute:
bundle installUsage
Build an agent by chaining middleware and finishing with your LLM call:
require "brute"
require "ruby_llm"
agent = Brute.agent
.use(Brute::Middleware::SessionLog, path: "tmp/session.jsonl")
.use(Brute::Middleware::SystemPrompt)
.use(Brute::Middleware::Loop::ToolResult)
.use(Brute::Middleware::MaxIterations)
.use(Brute::Middleware::DefaultToolPipeline, tools: Brute::Tools::ALL)
.run do |env|
response = provider.complete(
Brute::MessageTransport::RubyLLM.dump_all(env[:messages]),
tools: rubyllm_tools(env[:tools]), model: model,
)
Brute::MessageTransport::RubyLLM.wrap_each(response) { |m| env[:messages] << m }
end
agent.start("What files are in the current directory? List them.")Everything about the LLM — provider, model, credentials — lives inside the
run proc. Swap the proc and the same stack runs on any library; the
examples directory
ships this exact agent four times over, runnable with nix run ./examples/ruby-llm.
Documentation
Full documentation lives at general-intelligence-systems.github.io/brute, organized as tutorials, how-to guides, and concepts, plus an auto-generated API reference. To work on the docs locally:
docs/bin/serve.sh # http://localhost:4000/brute/Support
Please open an issue for bugs, questions, and feature requests.
Roadmap
Deprecated public names (see gem kit deprecations and
DEPRECATIONS.md) are removed in major versions only. Notable
changes land in CHANGELOG.md, which follows
Keep a Changelog.
Contributing
Contributions are welcome. Pull requests should come with tests.
nix develop # or: bundle install
bin/test # the full suite (scampi TAP output)Before renaming or deleting anything public, read
DEPRECATIONS.md — public names are deprecated with
GemKit::Deprecate, never deleted outright. The release process and versioning
rules are in RELEASE.md.