Skip to content

Repository files navigation

SummitAgent

Ruby Gem Version License: MIT

A small Ruby framework for building tool-using LLM agents, built around a bounded ReAct loop (reason → act → observe, repeat). Built around a marketing-team use case for a tech conference (illustrated here with a fictional "NovaConf" brand): each team gets a system prompt plus a shared set of brand guardrails, and the agent can call tools (e.g. looking up event dates) mid-conversation instead of guessing.

How a turn flows

User input
   │
   ▼
Agent#run ──context──▶ Backend#generate ──schema──▶ Registry (tool lookup)
   ▲                         │
   │                    tool_call?
   │                         ▼
   │                    Tool#execute
   │                         │
   └──── tool result recorded in Context, loop continues ────┘
                              │
                    no more tool calls
                              ▼
                        final answer

Backend is the only seam swapped per environment — OllamaBackend today, anything implementing Backends::Base#generate(context, tools:) tomorrow. Everything else (Agent, Registry, Tool, Context) is backend-agnostic.

lib/summit_agent/
├── agent.rb                    # the ReAct loop
├── registry.rb                 # tool lookup, decoupled from concrete tool classes
├── tool.rb                     # abstract seam concrete tools plug into
├── tools/calendar_tool.rb
├── backends/base.rb            # abstract seam for LLM providers
├── backends/ollama_backend.rb  # real adapter — local, free, tool-calling capable
├── prompt_builder.rb           # assembles team prompt + guardrails
├── context.rb                  # conversation state
└── message.rb

There's also python_rag_agent/ — a separate CrewAI/RAG prototype exploring vector-based brand-guideline retrieval, kept deliberately independent of the Ruby path for now (see Design Decisions below).

Usage

Requires Ollama running locally with a tool-calling-capable model pulled (e.g. ollama pull gemma4:12b):

ollama serve  # if not already running
bin/summit-agent --team community_team --message "When is opening night?"
registry = SummitAgent::Registry.new
registry.register(SummitAgent::Tools::CalendarTool)

backend = SummitAgent::Backends::OllamaBackend.new(model: "gemma4:12b")
agent = SummitAgent::Agent.new(backend: backend, registry: registry)
agent.run(team_name: "community_team", user_input: "When is opening night?")

Any backend implementing Backends::Base#generate(context, tools:) works — OllamaBackend is the first real one; see Design Decisions for what building it actually revealed.

Development

After checking out the repo, run bin/setup to install dependencies. bin/console gives an interactive prompt.

bundle exec rspec      # 11 examples — unit tests only, no network or Ollama required
bundle exec standardrb # lint; CI fails the build on any offense

CI (.github/workflows/main.yml) runs both on every push. The OllamaBackend spec tests message/schema translation and response parsing directly — no live model needed to run the suite; only bin/summit-agent needs Ollama running.

Design Decisions

Notes on why this is built the way it is, and what I'd change next.

Bounded ReAct loop. Agent#run implements a ReAct-style Thought → Action → Observation loop rather than a single-shot prompt, so the agent can pull in live data mid-response instead of guessing. The loop is capped at max_iterations: every extra turn is a real API call, so the cap is a cost ceiling as much as a safety net. Trade-off: a task needing more tool calls than the cap gets cut off mid-way rather than completing. Next: surface the iteration count in the final error so a caller can distinguish "gave up" from "hit the limit."

Tool registry as a plugin seam — now with a real input contract. Agent#run only knows Registry's three-method interface — it never references CalendarTool directly. to_llm_schema used to export only name/description, with no input_schema — no formal contract for a tool's arguments. Fixed once a real backend needed it: Tool now declares input_schema (JSON Schema), and Registry includes it in every tool's schema. Still only one concrete Tool, so the plugin seam itself remains unproven until a second one exists.

PromptBuilder has no seam. PromptBuilder.build calls File.read and YAML.load_file directly — no seam between assembling a prompt and finding one on disk. Its own spec reaches through the real filesystem, which means it only passes from the repo root; it's really an integration test wearing a unit test's badge. Next: extract a PromptSource seam with a FileSource adapter in production and an in-memory adapter in tests.

Backends::Base — resolved from hypothetical to real. OllamaBackend is the first adapter that calls an actual model (a local Ollama instance, tool-calling capable, no API cost). Building it immediately proved the point of the "one real adapter" rule: it surfaced a real bug that the two prior stand-ins (MockBackend, CLIBackend) couldn't have caught, because neither had real conversational memory to get wrong. Agent#run executed a tool but never recorded the assistant's own tool-call request in Context — only the tool's result. Every real chat API needs that request in history, or the model has no memory of having already asked for the tool and just asks again, forever. Fixed by having Context#add_message carry an optional tool_calls payload, appended right before the tool executes. Next: a second real adapter (AnthropicBackend) to see whether Backends::Base's shape holds for a provider with a genuinely different wire format.

Ruby agent and Python RAG prototype stay separate, for now. python_rag_agent/ explores CrewAI + vector-search retrieval of brand guidelines as an independent prototype, with its own tool-schema convention (MCP-style) that doesn't share config/brand_guardrails.yml with the Ruby side. This is an open decision, not an oversight — convergence only makes sense once the RAG approach proves it earns its place. Worth an ADR once a second cross-language tool shows up, so the reasoning doesn't get re-litigated from scratch.

License

The gem is available as open source under the terms of the MIT License.

About

ReAct-style Ruby LLM agent framework — real local tool-calling via Ollama, documented architecture decisions

Topics

Resources

Code of conduct

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages