API Reference
Key classes and methods across the ask-rb ecosystem. For full documentation, see each gem’s README and YARD docs.
ask-core
The foundation gem. Source
Ask::Provider
class MyProvider < Ask::Provider
def api_base = "https://api.example.com/v1"
def headers = { "Authorization" => "Bearer #{@config.api_key}" }
def chat(messages, model:, tools: nil, temperature: nil, stream: nil, schema: nil, **params, &block)
def embed(text, model:)
def list_models
end
Ask::Provider.register(:name, MyProvider)
Ask::Provider.resolve(:name)
Ask::Provider.providers # => Hash of registered providers
Ask::Conversation
conv = Ask::Conversation.new
conv.system("text")
conv.user("text")
conv.assistant("text", tool_calls: [...])
conv.tool_result("text", tool_call_id: "id")
conv.messages # => Array of Ask::Message
conv.user_messages
conv.assistant_messages
conv.tool_messages
conv.system_messages
conv.to_a # => Array of hashes
Ask::Message
msg = Ask::Message.new(role: :user, content: "Hello")
msg.role # => :user
msg.content # => "Hello"
msg.tool_calls # => Array or nil
msg.tool_call_id # => String or nil
msg.user? # Boolean
msg.assistant? # Boolean
msg.tool_call? # Boolean
msg.tool_result? # Boolean
Ask::Stream / Ask::Chunk
stream = Ask::Stream.new { |chunk| print chunk.content }
stream.add(Ask::Chunk.new(content: "Hello"))
stream.finish!
stream.accumulated_text # => "Hello"
stream.to_s # => "Hello"
stream.accumulated_usage # => { input_tokens: 10, output_tokens: 20 }
stream.length # => 2
Ask::ModelCatalog
catalog = Ask::ModelCatalog.new([Ask::ModelInfo.new(id: "gpt-4o", provider: "openai")])
catalog.find("gpt-4o")
catalog.chat_models
catalog.embedding_models
catalog.by_provider("openai")
# Singleton
Ask::ModelCatalog.instance
Ask::ModelCatalog.find("gpt-4o")
Ask::ToolDef
tool = Ask::ToolDef.new(
name: "get_weather",
description: "Get current weather",
parameters: { type: "object", properties: { location: { type: "string" } }, required: ["location"] }
)
tool.frozen? # => true
tool.to_provider_format { |t| { type: "function", function: t.to_h } }
Ask::Result
Ask::Result.success("Data processed")
Ask::Result.failure("API returned 500")
Ask::Result.aborted("Cancelled")
Ask::Result.blocked("Permission denied")
result.success? # => true/false
result.error? # => true/false
result.to_h # => { content: "...", status: :success, metadata: {} }
Errors
Ask::ConfigurationError
Ask::UnknownProvider
Ask::ModelNotFound
Ask::InvalidRole
Ask::InvalidToolDefinition
Ask::ProviderError
Ask::ContextLengthExceeded
Ask::RateLimitError
Ask::Unauthorized
Ask::ServerError
Ask::ServiceUnavailable
Ask::ConversationError
Ask::StreamError
Ask::UnsupportedFeature
Ask::MissingCredential
Ask::InvalidCredential
ask-auth
Credential resolution. Source
Ask::Auth.resolve(:github_token)
Ask::Auth.resolve(:github_token, user: current_user)
Ask::Auth.configure do |c|
c.providers = [Ask::Auth::Providers::Env.new, Ask::Auth::Providers::File.new]
end
ask-tools
Tool framework. Source
class MyTool < Ask::Tool
description "Does something"
param :input, type: :string, desc: "Input value", required: true
def execute(input:)
Ask::Result.ok(data: "Processed #{input}")
end
end
Ask::Tools.register(MyTool)
Ask::Tools.all
Ask::Tools["my_tool"]
Ask::Tools.count
ask-agent
Agent loop. Source
Agent Definitions
# agents/health_check/agent.rb
class HealthCheckAgent < Ask::Agent::Definition
model "gpt-4o"
tools :bash, :read, :grep
schedule "every 5 minutes"
end
# Usage
agent = Ask::Agent.new("health_check")
agent.run("Check health")
Ask::Agent.definitions # => { "health_check" => [HealthCheckAgent, "/path/to/agents/health_check"] }
Ask::Agent.rediscover!
Low-Level Session API
session.on_event { |event| … } session.id session.total_cost session.turns
Ask::Agent.configure do |c| c.default_model = “claude-sonnet-4” c.default_max_turns = 50 c.parallel_tool_execution = true end
### Middleware (LLM Call Pipeline)
```ruby
Ask::Agent.configure do |c|
c.middleware.use :retry_on_failure, max_retries: 5
c.middleware.use :log_calls, logger: Rails.logger
c.middleware.use :default_settings, temperature: 0.7
end
# Custom middleware
class MyMiddleware < Ask::Agent::Middleware::Base
def around_request(provider, request)
# request is a Hash with :messages, :model, :tools, :temperature, :stream, :schema, :extra_params
Rails.logger.info "Calling #{request[:model]}"
result = yield
Rails.logger.info "Done"
result
end
end
Stream Transforms
Ask::Agent.configure do |c|
c.stream_transforms.use :thinking_separator
c.stream_transforms.use :text_buffer, min_size: 100
c.stream_transforms.use :extract_json
end
# Custom transform
class NoOp < Ask::Agent::StreamTransforms::Base
def call(chunk, &block)
yield chunk
end
end
```
### Ask.chat (convenience)
```ruby
# Quick one-shot chat — no Session setup needed
Ask.chat("Hello!")
Ask.chat("Tell me about X", model: "gpt-4o")
# With streaming
Ask.chat("Stream this") { |chunk| puts chunk.content if chunk.content }
Prompt Caching
# Enabled by default. Disable if needed.
Ask::Agent.configure do |c|
c.prompt_caching = false
end
# Per-session override
session = Ask::Agent::Session.new(model: "claude-sonnet-4", prompt_caching: false)
# Cache token metadata (available in response metadata)
# Anthropic: :cache_creation_input_tokens, :cache_read_input_tokens
# OpenAI: :cached_tokens
Scheduler
Ask::Agent.configure do |c|
c.scheduler.every "5 minutes", name: "task-name" do
Ask::Agent::Session.new(model: "gpt-4o").run("Do something")
end
c.scheduler.cron "0 9 * * 1-5", name: "weekday-task"
end
Ask::Agent::Scheduler.start
Ask::Agent::Scheduler.running?
Ask::Agent::Scheduler.jobs
Ask::Agent::Scheduler.job_by_name("task-name")
Ask::Agent::Scheduler.stop
ask-rails
Rails integration for building AI-powered applications. Source
Use ask-rails for: Adding AI capabilities to your Rails app for your users.
# Terminal
rails generate ask:install
# config/initializers/ask.rb
Ask::Agent.configure do |config|
config.default_model = ENV.fetch("ASK_DEFAULT_MODEL", "gpt-4o")
end
# app/agents/support_bot.rb
class Agents::SupportBot < ApplicationAgent
model "gpt-4o"
system_prompt "You help users with support questions."
end
# Anywhere in your app
agent = Ask::Agent.new("support_bot")
agent.run("How do I reset my password?")
# One-off conversations
session = Ask::Agent::Session.new(model: "gpt-4o")
session.run("Summarize this article") do |chunk|
puts chunk.content if chunk.content
end
Dependencies: ask-agent (pulls in ask-core, ask-llm-providers, ask-tools, ask-skills).
ask-rails-harness
Admin AI copilot for Rails apps. Source
Use ask-rails-harness for: Internal admin agents that inspect code, query DB, read logs.
# Gemfile
gem "ask-rails-harness"
# Terminal
rails generate ask_rails_harness:install
# config/routes.rb
authenticate :user, ->(u) { u.admin? } do
mount Ask::Rails::Harness::Engine, at: "/ask"
end
# Programmatic access
Ask::Rails::Harness.agent_session
Ask::Rails::Harness.agent_session(user: current_user)
Ask::Rails::Harness.configure do |c|
c.default_model = "gpt-4o"
c.max_turns = 50
end
# Auth
Ask::Rails::Harness::Auth.check = -> {
redirect_to main_app.login_path unless current_user&.admin?
}
# Engine routes (mounted at /ask)
# GET /ask → Admin chat UI
# POST /ask/sessions → Create session
# POST /ask/sessions/:id/messages → Send message (SSE streamed)
# GET /ask/sessions/:id/messages → Message history
Dependencies: ask-agent, ask-tools-shell, ask-auth, rails >= 7.1.
ask-tools-shell
Shell and filesystem tools. Source
Ask::Tools::Shell::Bash.new.call(command: "ls")
Ask::Tools::Shell::Read.new.call(path: "/etc/hosts")
Ask::Tools::Shell::Write.new.call(path: "file.txt", content: "data")
Ask::Tools::Shell::Edit.new.call(path: "file.txt", old_string: "old", new_string: "new")
Ask::Tools::Shell::Glob.new.call(pattern: "**/*.rb")
Ask::Tools::Shell::Grep.new.call(pattern: "class")
Ask::Tools::Shell::Code.new.call(code: "puts RUBY_VERSION")
ask-llm-providers
LLM providers. Source
Ask::Providers::OpenAI.new(api_key: "sk-...")
Ask::Providers::Anthropic.new(api_key: "sk-ant-...")
Ask::Providers::Google.new(api_key: "...")
Ask::Providers::Bedrock.new(...)
Ask::Providers::Ollama.new(...)
Ask::Providers::Mistral.new(api_key: "...")
Ask::Providers::Cloudflare.new(api_key: "...", account_id: "...")
Ask::Providers::OpenAI.capabilities
Ask::Providers::Ollama.local?
ask-skills
Skill discovery and management. Source
# Discover skills from all configured sources
registry = Ask::Skills.discover
registry.names # => ["rails_debug", "deploy_bot", ...]
registry["rails_debug"] # => Skill object
# Discover with per-agent skills (highest priority)
registry = Ask::Skills.discover(agent_dir: "agents/health_check")
# Load an arbitrary markdown file as a skill
skill = Ask::Skills.load_file("path/to/skill.md")
# Skill data object
skill.name # => "rails_debug"
skill.description # => "Debugging Rails apps"
skill.instructions # => full markdown body
skill.source # => "/path/to/SKILL.md"
skill.tags # => ["rails", "database", "debugging"]
skill.references # => ["references/migration_guide.md"]
skill.scripts # => ["scripts/db_check.sh"]
skill.assets # => ["assets/diagram.png"]
skill.siblings # => {"references" => [...], "scripts" => [...]}
```
### Enhanced Frontmatter
```markdown
---
name: rails_debug
description: Debug Rails database issues
tags: rails, database, debugging
version: 2
author: Myrr Labs
---
```
### Sibling Files
Skills can bundle reference documents, scripts, and assets alongside `SKILL.md`:
```
rails_debug/
├── SKILL.md
├── references/ → skill.references
│ ├── migration_guide.md
│ └── apis.md
├── scripts/ → skill.scripts
│ └── db_check.sh
└── assets/ → skill.assets
└── diagram.png
```
### CLI
```bash
askr skills list # All skills with descriptions and tags
askr skills show rails_debug # Full details + instructions + siblings
askr skills search deploy # Search by name, description, or tags
```
Discovery sources (highest priority first):
1. Per-agent: `agents/<name>/skills/` (when `agent_dir` given)
2. Shared project: `agents/shared/skills/`, `app/agents/shared/skills/`
3. Legacy project: `.agents/skills/` (backward compat)
4. User config: `~/.config/ask/skills/`
5. Installed gems
6. Built-in skills (`skill.design`, `skill.compose`)
## ask-sandbox-providers
Sandboxed execution. [Source](https://github.com/ask-rb/ask-sandbox-providers)
```ruby
Ask::Sandbox.provider = :docker
Ask::Sandbox.provider = Ask::Sandbox::Docker.new(image: "ruby:3.4-alpine")
Ask::Sandbox.provider = Ask::Sandbox::Daytona.new(api_key: "...")
Ask::Sandbox.provider = Ask::Sandbox::Cloudflare.new(worker_url: "...")
result = Ask::Sandbox.provider.call(["ruby", "-e", "puts 1+1"])
result.stdout # => "2\n"
result.exit_code # => 0
result.success? # => true
ask-state (in ask-core)
Pluggable state backend for key-value storage, distributed locking, message queues, and ordered lists. Source
# In-memory (default)
store = Ask::State::Memory.new
# Key-value with TTL
store.set("key", "value", ttl: 60)
store.get("key") # => "value"
store.delete("key")
store.set_if_not_exists("lock", "acquired") # atomic create
# Distributed locking
lock = store.acquire_lock("resource", ttl: 10)
store.release_lock("resource", lock) if lock
# Message queues
store.enqueue("queue-name", { task: "work" })
entry = store.dequeue("queue-name")
entry.value # => { task: "work" }
entry.id # => UUID
entry.enqueued_at # => Time
# Ordered lists with optional max length
store.list_append("sessions", "session-1", max_length: 100)
store.list_range("sessions", 0, -1)
store.list_remove("sessions", "session-1")
# Custom backend
class RedisAdapter < Ask::State::Adapter
def get(key) = redis.get(key)
def set(key, value, ttl: nil) = redis.set(key, value, ex: ttl)
# ... implement all methods
end
Data types: Ask::State::Lock (.id, .token, .expires_at, .expired?), Ask::State::QueueEntry (.id, .value, .enqueued_at).
ask-provider-tool (in ask-core)
Configuration for built-in tools that run on the provider’s infrastructure.
# Provider-executed tools (handled by OpenAI's servers)
Ask::ProviderTool.web_search(search_context_size: "high")
Ask::ProviderTool.file_search(vector_store_ids: ["vs_abc"], max_num_results: 10)
Ask::ProviderTool.code_interpreter(file_ids: ["file_1"])
# Custom provider tool
Ask::ProviderTool.new(
id: "openai.web_search",
name: "web_search",
args: { search_context_size: "medium" }
)
# Use with sessions
session = Ask::Agent::Session.new(
model: "gpt-4o",
tools: [Bash, Read, Ask::ProviderTool.web_search]
)
ask-schema
JSON Schema DSL. Source
schema = Ask::Schema.define do
string :name
integer :count
array :tags, type: :string
object :meta do
string :version
end
end
schema.to_json_schema
ask-mcp
MCP client and server. Source
client = Ask::MCP.from_stdio("npx", ["-y", "server-package"])
client.start
client.tools # => Hash of name → Ask::MCP::Tool
client.call_tool("tool_name", arg1: "value")
client.stop
ask-eval
LLM evaluation. Source
assert_faithful response, context: docs
assert_not_hallucinating response, context: docs
refute_bias response
refute_toxicity response
assert_correctness response, expected: expected
assert_contains response, "substring"
assert_regex response, /pattern/