AgentRuntime
Deterministic, policy-driven runtime for tool-using LLM agents in Ruby.
AgentRuntime is a control plane. It coordinates planning, policy validation, tool execution, and explicit state. It does not ship domain logic.
What this gem is
- A small runtime to coordinate LLM decisions with Ruby tool execution.
- A formal FSM workflow (
AgentFSM) with explicit states and history.
What this gem is not
- Not a domain toolkit (no broker APIs, HTTP clients, or storage).
- Not a prompt library.
- Not a memory system.
Strict usage rules (non-negotiable)
- Use
/generateonly for planning/decision outputs (Planner#plan). - Use
/chatonly during execution/finalization (Planner#chat_raw,Planner#chat). - The LLM never executes tools. Tools are Ruby callables and run in
Executor. - Tool results are injected as
role: "tool"messages only after execution. - Only
EXECUTEloops. All other states are single-shot. - Termination happens only on explicit signals:
decision.action == "finish",result[:done] == true, orMaxIterationsExceeded. - This gem does not add retries or streaming. Retry/streaming policy lives in
ollama-client.
If you violate any rule above, you are not using this gem correctly.
Narrative overview (kept here, kept strict)
AgentRuntime is a domain-agnostic runtime that separates reasoning from authority:
- LLM reasoning happens via
Planneronly. - Ruby owns policy and execution.
- Tools are gated and executed outside the LLM.
- State is explicit and inspectable.
- Failures are visible via explicit errors and optional audit logs.
Architecture (conceptual):
Your application → AgentRuntime → ollama-client → Ollama server
This overview is informative only. The strict rules above are the contract.
Core components (SRP map)
Planner: LLM interface (generate,chat,chat_raw). No tools. No side effects.Policy: validates decisions before execution.Executor: executes tools viaToolRegistryonly.ToolRegistry: maps tool names to Ruby callables.State: explicit, serializable state.Agent: simple decision loop usingPlanner#planand tools.AgentFSM: formal FSM with explicit states and transition history.AuditLog: optional logging of decisions and results.
API mapping
| Concern | Method | LLM endpoint | Where it belongs |
|---|---|---|---|
| Planning / decisions | Planner#plan |
/api/generate |
PLAN |
| Execution / tool calls | Planner#chat_raw |
/api/chat |
EXECUTE |
| Final response (optional) | Planner#chat |
/api/chat |
FINALIZE |
Executor never calls the LLM.
Prerequisites
agent_runtime depends on ollama-client. See PREREQUISITES.md.
Installation
Add this line to your application's Gemfile:
gem "agent_runtime"
And then execute:
bundle install
Or install it yourself as:
gem install agent_runtime
Usage
Single-step agent (Agent#step)
Use this for one-shot decisions or when you control the loop externally.
require "agent_runtime"
require "ollama_client"
tools = AgentRuntime::ToolRegistry.new({
"fetch" => ->(**args) { { data: "fetched", args: args } },
"execute" => ->(**args) { { result: "executed", args: args } }
})
client = Ollama::Client.new
schema = {
"type" => "object",
"required" => ["action", "params", "confidence"],
"properties" => {
"action" => { "type" => "string", "enum" => ["fetch", "execute", "finish"] },
"params" => { "type" => "object", "additionalProperties" => true },
"confidence" => { "type" => "number", "minimum" => 0, "maximum" => 1 }
}
}
planner = AgentRuntime::Planner.new(
client: client,
schema: schema,
prompt_builder: ->(input:, state:) {
"User request: #{input}\nContext: #{state.to_json}"
}
)
agent = AgentRuntime::Agent.new(
planner: planner,
policy: AgentRuntime::Policy.new,
executor: AgentRuntime::Executor.new(tool_registry: tools),
state: AgentRuntime::State.new,
audit_log: AgentRuntime::AuditLog.new
)
result = agent.step(input: "Fetch market data for AAPL")
puts result.inspect
Multi-step loop (Agent#run)
Use this when the agent should iterate until it emits finish or a tool marks
done: true. This loop uses /generate only (no chat).
result = agent.run(initial_input: "Find best PDF library for Ruby")
Formal FSM workflow (AgentFSM)
AgentFSM is the explicit FSM driver. It uses /generate for PLAN and
/chat for EXECUTE. Tool execution happens only in OBSERVE.
Tool calling in EXECUTE requires Ollama tool definitions. This gem does not
auto-convert ToolRegistry entries to Ollama::Tool objects. If you need tool
calling, subclass AgentFSM and return tool definitions from
build_tools_for_chat.
class MyAgentFSM < AgentRuntime::AgentFSM
def build_tools_for_chat
# Return Ollama::Tool definitions here
[]
end
end
agent_fsm = MyAgentFSM.new(
planner: planner,
policy: AgentRuntime::Policy.new,
executor: AgentRuntime::Executor.new(tool_registry: tools),
state: AgentRuntime::State.new,
tool_registry: tools,
audit_log: AgentRuntime::AuditLog.new
)
result = agent_fsm.run(initial_input: "Research Ruby memory management")
Tool safety model
- Tools are Ruby callables registered in
ToolRegistry. - LLM output never executes tools directly.
- Tool execution happens only in
Executor. - Tool results are recorded in state and (for FSM) injected as
role: "tool".
Examples
Quick Start
Start here: examples/complete_working_example.rb - A complete, runnable example demonstrating all features.
# Make sure Ollama is running: ollama serve
ruby examples/complete_working_example.rb
Available Examples
examples/complete_working_example.rb⭐ - Complete working example (recommended starting point)examples/fixed_console_example.rb- Minimal example for console useexamples/console_example.rb- Basic console exampleexamples/rails_example/- Rails integration exampleexamples/dhanhq_example.rb- Domain-specific example (requires DhanHQ gem)
See examples/README.md for detailed documentation on all examples.
Examples are not part of the public API.
Documentation
AGENTIC_WORKFLOWS.mdFSM_WORKFLOWS.mdSCHEMA_GUIDE.mdPREREQUISITES.md
Development
After checking out the repo, run:
bin/setup
To run tests:
rake spec
# or
bundle exec rspec
Test coverage reports are generated automatically. View the HTML report:
open coverage/index.html # macOS
xdg-open coverage/index.html # Linux
See TESTING.md for detailed testing and coverage information.
To run the console:
bin/console
Contributing
Bug reports and pull requests are welcome. Keep the API strict and small.
License
The gem is available as open source under the terms of the MIT License.