ask-agent
Agent runtime for the ask-rb ecosystem. The core agent loop: think → call tools → execute → feed back → repeat.
Ported from RubyLLM::Conductor into the Ask::Agent namespace.
Installation
gem "ask-agent"
Quick Start
require "ask-agent"
session = Ask::Agent::Session.new(
model: "gpt-4o",
tools: [Ask::Tools::Shell::Bash, Ask::Tools::Shell::Read]
)
response = session.run("What files are in the current directory?")
puts response
Components
| Component | File | Purpose |
|---|---|---|
Ask::Agent::Session |
session.rb | Full agent loop — message → tool calls → results → follow-up |
Ask::Agent::Loop |
loop.rb | Turn management, loop detection, max-turn guard |
Ask::Agent::ToolExecutor |
tool_executor.rb | Parallel/sequential tool execution with retry and abort |
Ask::Agent::Compactor |
compactor.rb | Context window management with proactive/overflow compaction |
Ask::Agent::Hooks |
hooks.rb | Before/after tool lifecycle callbacks |
Ask::Agent::Events |
events.rb | Data.define event types for streaming and monitoring |
Ask::Agent::Telemetry |
telemetry.rb | File-backed telemetry for error tracking |
Ask::Agent::Reflector |
reflector.rb | Assistant response self-evaluation |
Ask::Agent::MetaAgent |
meta_agent.rb | LLM-powered self-improvement from telemetry |
Ask::Agent::Configuration |
configuration.rb | Global config: model, turns, concurrency |
Events
Stream session execution in real-time:
session.on_event do |event|
case event
when Ask::Agent::Events::TextDelta
print event.content
when Ask::Agent::Events::ToolExecutionStart
puts "\nRunning #{event.name}..."
when Ask::Agent::Events::ToolExecutionEnd
puts " → #{event.duration_ms}ms #{event.is_error ? 'error' : 'ok'}"
end
end
Extensions
Opt-in safety modules:
- PermissionGate — Require approval for destructive tools (write, edit, bash, destroy)
- RateLimiter — Prevent runaway tool calls (configurable per-minute and per-turn limits)
- AuditLog — Immutable, append-only log of every tool call
extensions = [
Ask::Agent::Extensions::PermissionGate.new,
Ask::Agent::Extensions::RateLimiter.new(max_calls_per_minute: 30),
Ask::Agent::Extensions::AuditLog.new(path: "agent.log")
]
session = Ask::Agent::Session.new(
model: "gpt-4o",
tools: [...],
hooks: {
before_tool: extensions.map(&:method(:before_tool_call)),
after_tool: extensions.select { |e| e.respond_to?(:after_tool_call) }.map(&:method(:after_tool_call))
}
)
Configuration
Ask::Agent.configure do |c|
c.default_model = "claude-sonnet-4"
c.default_max_turns = 50
c.compactor_enabled = true
c.compactor_threshold = 0.8
c.parallel_tool_execution = true
c.max_tool_retries = 3
end
Persistence
store = Ask::Agent::Persistence::InMemory.new
session = Ask::Agent::Session.new(model: "gpt-4o", persistence: store)
session.run("Hello")
session.save # persisted to store
Development
bundle exec rake test
License
MIT