A Ruby server implementation of the AG-UI protocol — plus the CopilotKit runtime surface its React client expects — so you can drive a CopilotKit frontend from Ruby (Falcon/Rails) with any LLM.
require "ag_ui"
require "ag_ui/terminals/ruby_llm"
RubyLLM.configure { |c| c.anthropic_api_key = ENV.fetch("ANTHROPIC_API_KEY") }
terminal = AgUi::Terminals::RubyLLM.new(model: "anthropic/claude-sonnet-4-5")
# AgUi.agent gives you a Rack app serving /info, /agent/:id/run (SSE), /connect,
# /stop. The block is the per-run handler: open the SSE stream and drive a
# STANDARD Brute agent — the AG-UI middleware stack over your terminal.
app = AgUi.agent(agent_id: "default") do |env|
input = env["ag_ui.input"]
agent = Brute.agent
.use(AgUi::Middleware::SystemPrompt,
prompt: "You are a helpful assistant.", context: input.context)
.use(AgUi::Middleware::ForwardedProps, props: input.forwarded_props)
.use(Brute::Middleware::Loop::ToolResult)
.use(Brute::Middleware::MaxIterations, max_iterations: 10)
.use(AgUi::Middleware::ToolRouter, tools: input.tools, server_tools: [])
.run(terminal)
env["ag_ui.stream"].open(thread_id: input.thread_id, run_id: input.run_id) do |stream|
stream.run_started
agent.start(AgUi::Messages.to_brute(input.messages), events: AgUi::EventBridge.new(stream))
stream.run_finished
rescue => e
stream.run_error(message: e.message, code: e.class.name)
end
end
# mount it wherever the client's runtimeUrl points.
run app- Schema-exact wire protocol — every event/type definition is generated
from the reference Python SDK's models (
data/generate-ag-ui-schema.py), validated with json_schemer before it hits the wire, and conformance-tested byte-for-byte against the official SDK test expectations. - Falcon-native SSE streaming —
Protocol::HTTP::Body::Writablebodies with Async fibers: true streaming with backpressure, no buffering. - The CopilotKit runtime surface —
GET /info(capability envelope),POST /agent/:id/run,/connect(replay + live attach),/stop(run cancellation), and the bare-run rootHttpAgentexpects. - The client-tool multi-run model — frontend tools (
useFrontendTool,useHumanInTheLoop) are advertised schema-only, emitted asTOOL_CALL_*, and the run ends cleanly for the browser to execute and re-run. - Server tools with an agentic turn loop —
{name:, description:, parameters:, handler:}tools execute inline and the model continues the same run (brute'sLoop::ToolResult+MaxIterations). - A2UI generative UI —
render_a2uiinjection, catalog fetch with retry/degrade, the tool-call →ACTIVITY_SNAPSHOTtransform, semantic component validation (a port of the officiala2ui_toolkit), and in-run regeneration on invalid surfaces. - Extended thinking →
REASONING_*, multimodal attachments, context injection, and dynamic suggestions (forwardedProps.toolChoice). - LLM-agnostic core — the LLM lives in a terminal callable at the bottom
of a brute middleware pipeline; a
ruby_llm terminal ships as the
reference adapter (
require "ag_ui/terminals/ruby_llm").
bundle add ag-uiThe gem requires Ruby ≥ 3.3. The reference terminal additionally needs
ruby_llm; the server runs on any Rack 3 host, with Falcon recommended for
native streaming.
HTTP/SSE Triage → SSEStream → dispatch (Rack, Falcon-native)
turn SystemPrompt → ForwardedProps →
Loop::ToolResult → MaxIterations →
A2ui → ToolRouter → terminal (brute pipeline)
terminal your LLM call (ruby_llm adapter included)
One run = one RunAgentInput POST. The pipeline seeds the conversation from
the request (history, tools, context), streams events through an
EventBridge into the SSE body as they happen, and ends the run per the
AG-UI contract (RUN_FINISHED / RUN_ERROR). A pluggable RunStore
(in-memory included) records every run for /connect replay, live
mid-run attach, and /stop cancellation.
Runnable servers in examples/ (served with
ratalada):
echo.rb— transport smoke test, no LLMclaude.rb— full runtime surface: Claude streaming, a demo server tool, optional extended thinking (THINKING_BUDGET=2048)bare.rb— the bareHttpAgentendpoint shapea2ui_analyst.rb— A2UI agent for the vendored showcase canvas
Beyond the unit + conformance suite (bin/test), the repo vendors two real
CopilotKit apps as browser-driven regression harnesses — see
verification/README.md for the runbook: chat,
frontend tools, human-in-the-loop, and an A2UI canvas, all driven against
this server with the Node runtime both in and out of the path.
bin/setup # bundle install (nix users: `nix develop` provides the shell)
bin/test # scampi — specs are co-located in each file's __END__ block
bin/console # irb with the gem loadedReleasing (builds, pushes to rubygems, tags v<version> and pushes the tag):
bin/increment-version <major|minor|patch>
bin/release-gemMIT — see LICENSE.