# Boxcars: A Ruby Framework for Composable LLM-Powered Applications

> Boxcars is a Ruby gem that gives Rails and Ruby teams a consistent programming model for assembling LLM-powered systems from composable tool objects, supporting multiple LLM providers, native tool calling, MCP integration, and SQL/ActiveRecord data access with read-only defaults.

**BoxcarsAI/boxcars** — Building applications with composability using Boxcars with LLM's.

- Repository: https://github.com/BoxcarsAI/boxcars
- Stars: 461 · Forks: 42
- Language: Ruby
- License: MIT
- Published: 2026-09-10 · Updated: 2026-09-10 · Language: en
- Canonical page: https://hysenlabs.com/projects/boxcarsai-boxcars

## The Boxcars Programming Model: Boxcar, Train, Engine, and StationAgent

Boxcars introduces four core abstractions that map cleanly onto typical Rails application concepts. A Boxcar is a single-purpose tool: a unit that does one thing, such as running a calculation, executing a web search, querying a database, or calling an API. An Engine provides LLM text generation for any Boxcar that needs it. A Train takes a list of Boxcars and an Engine and orchestrates them to solve a multi-step problem, breaking the task into pieces each Boxcar can handle. A StationAgent sits above Train and adds lifecycle callbacks, agent-as-tool nesting, handoffs between agents, and event streaming.

This hierarchy is intentionally shallow. Most applications use one or two levels: either a single Boxcar for a specific tool call, or a Train or StationAgent for multi-step work. The README frames this as lower cognitive load for Ruby teams: instead of learning a new framework paradigm, developers use the same object-based composition they already apply to service objects and PORO patterns.

The Train abstraction comes in two modes. Boxcars::ZeroShot uses the legacy text-based ReAct pattern, where the model emits a plan as text and the framework parses it. Boxcars::ToolTrain uses native provider tool calling, where the model selects tools via the provider's structured output API. For new integrations, ToolTrain is the recommended path.

## Installing Boxcars and Wiring Up a First Tool

Boxcars requires Ruby 3.3 or newer. For a standard Bundler project, add the gem to your Gemfile:

```ruby
gem 'boxcars'
```

Then install with:

```bash
bundle install
```

Or install directly without Bundler:

```bash
gem install boxcars
```

The base gem is intentionally lean. Provider-specific and tooling dependencies are optional. Add only the gems you plan to use:

```ruby
gem "openai", ">= 0.30"
gem "ruby-anthropic"
gem "google_search_results"
```

If you attempt to use a feature without its optional gem installed, Boxcars raises a setup error that names the gem to add. This fail-fast behavior means misconfigured dependencies surface at startup rather than mid-request.

A single Boxcar is the simplest entry point:

```ruby
calc = Boxcars::Calculator.new
puts calc.run("What is pi to the fourth power divided by 22.1?")
```

A multi-tool Train adds a search Boxcar alongside the calculator and lets the LLM decide which tool to call for a given query:

```ruby
boxcars = [Boxcars::Calculator.new, Boxcars::GoogleSearch.new]
train = Boxcars.train.new(boxcars: boxcars)
puts train.run("What is pi times the square root of the average temperature in Austin TX in January?")
```

A StationAgent with lifecycle management is the top-level option for agents that need callback hooks or handoff support between agents.

## SQL and ActiveRecord Integration with Read-Only Defaults

One of Boxcars' more distinctive design decisions is its read-only default for SQL and ActiveRecord operations. Both the SQL Boxcar and the ActiveRecord Boxcar reject write operations by default, raising Boxcars::SecurityError if the LLM attempts an INSERT, UPDATE, or DELETE. This default exists because an LLM-generated SQL query, left unrestricted, could modify or delete data based on a misunderstood instruction.

To allow writes, you explicitly disable read-only mode or provide an approval callback. The approval callback pattern lets you inspect and confirm a proposed write before it executes, which is the safer path for applications that need LLM-driven data modification.

The MCP integration follows a similar composable pattern. MCP servers are connected over stdio, and their tools are merged with local Boxcars in a single tool-calling runtime. This means an agent can call both a local calculator Boxcar and a remote MCP tool in the same Train, without the developer needing to write different orchestration code for each.

For vector search, Boxcars supports a pgvector backend (requiring the pg and pgvector gems). This enables retrieval-first workflows where an embedding search narrows the context before a follow-up tool call uses the retrieved content.

## Provider Flexibility and the Engine Abstraction

The Engine abstraction in Boxcars decouples LLM provider selection from tool and agent logic. The same Boxcar and Train code runs against any supported provider by swapping the Engine. Supported providers include OpenAI (defaulting to gpt-5-mini as of the current upgrade notes), Anthropic (via ruby-anthropic), Groq, Gemini, Ollama, Perplexity, Together, and Cerebras. OpenAI-compatible endpoints use the official OpenAI client path, which also covers Groq, Gemini (via OpenAI compatibility), Ollama, Google, Cerebras, and Together.

The README notes that Boxcars::Cohere now defaults to command-a-03-2025 because legacy command-r model IDs were retired by Cohere. This is the kind of provider-side change that breaks applications pinned to specific model names. The Engine abstraction contains the impact: only the Cohere engine configuration changes, not the downstream Boxcars or Trains that use it.

For teams that want to run entirely local models, Ollama is on the supported list. A local Ollama server can back any Engine-using Boxcar without any modification to the tool logic, which is useful for development environments or for deployments with data-residency requirements that prohibit sending data to cloud APIs.

## StationAgent: Lifecycle Hooks, Handoffs, and Event Streaming

StationAgent extends the Train concept with features that matter for production agent deployments. Lifecycle callbacks let you hook into agent start, tool call, tool result, and completion events. This is useful for logging, observability, and enforcing time or cost budgets on agent runs.

Agent-as-tool nesting means one StationAgent can call another as a tool. A top-level orchestrator agent can delegate sub-tasks to specialized agents, which is the pattern used for multi-agent systems where each agent has a defined domain (one handles customer data, another handles billing logic). Handoffs go further: they allow one agent to pass control to another with shared context, rather than receiving a response back and continuing.

Event streaming surfaces intermediate results during a long agent run. Rather than waiting for the full response before displaying anything, applications can stream tool call events and partial results to the user as they arrive. For Rails applications with Hotwire, this enables real-time agent progress without websocket boilerplate.

The Agents Guide in docs/agents_guide.md covers these features in detail. The Boxcars Guide in docs/boxcars_guide.md covers Engines, Boxcars, Trains, MCP configuration, observability, and JSON Schema enforcement for structured outputs.

## Boxcars vs. LangChain for Ruby Applications

LangChain is primarily a Python framework. Ruby teams that want LangChain-style LLM composition have historically needed to call a Python service from Ruby or use a thin Ruby binding to LangChain's Python core. Boxcars is a native Ruby implementation, which means it runs in the same process as a Rails application, uses standard Ruby gem conventions, and does not require a Python environment.

The README states directly that Boxcars is inspired by LangChain and brings a Ruby-first design. The key difference in approach is that Boxcars favors composition through explicit Boxcar objects over LangChain's chain-and-agent hierarchy. A Rails developer implementing a Boxcar for a domain-specific tool follows the same pattern as writing a service object: one class, one responsibility.

For teams evaluating both, the deciding factor is typically the language. Python teams with existing LangChain integrations have no migration path to Boxcars. Ruby teams with existing Rails applications get a framework that integrates with ActiveRecord, background jobs, and Rails conventions without requiring a parallel Python service.

## Conclusion

Boxcars fits Ruby and Rails teams who want to add LLM-powered features to existing applications without switching languages or adopting a heavy new abstraction. Its Boxcar, Train, and StationAgent model integrates naturally with Rails service objects and background jobs, and its read-only SQL default prevents accidental data mutation. It is a poor fit for teams without a Ruby environment: the framework is Ruby-only and there is no Python equivalent. Before adopting it, verify that your LLM provider is on the supported list, review the UPGRADING.md for any breaking changes since your target version, and confirm that the optional gem set you need matches the features you plan to use. The last push was on 2026-09-25, and the project is MIT-licensed.

## FAQ

### What LLM providers does Boxcars support?

Boxcars supports OpenAI, Anthropic, Groq, Gemini, Ollama, Perplexity, Together, Cerebras, and Cohere through the Engine abstraction. Each provider requires adding the corresponding optional gem to your Gemfile.

### Is Boxcars safe to use with a live database?

Both the SQL and ActiveRecord Boxcars default to read-only mode and raise Boxcars::SecurityError on any write operation. To allow writes, you must explicitly disable read-only mode or provide an approval callback that inspects the proposed write.

### Does Boxcars support MCP servers?

Yes. The MCP integration connects MCP servers over stdio and merges their tools with local Boxcars in a single tool-calling runtime. An agent can call both local and MCP-provided tools within the same Train or StationAgent.

## Sources

- [BoxcarsAI/boxcars on GitHub](https://github.com/BoxcarsAI/boxcars)
- [Issues](https://github.com/BoxcarsAI/boxcars/issues)
- [License: MIT](https://github.com/BoxcarsAI/boxcars/blob/main/LICENSE)
- [README](https://github.com/BoxcarsAI/boxcars/blob/main/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/boxcarsai-boxcars
