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
gem "ask-core" — zero dependencies, every ask-rb app depends on it.
Ask::Provider
require "ask"
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) = Ask::Message.new(role: :assistant, content: "ok")
def embed(text, model:) = [0.1, 0.2]
def list_models = ["a", "b"]
end
Ask::Provider.register(:my_provider, MyProvider)
Ask::Provider.resolve(:my_provider) # => MyProvider
Ask::Provider.providers.key?(:my_provider) # => true
Ask::Conversation
require "ask"
conv = Ask::Conversation.new
conv.system("text")
conv.user("text")
conv.assistant("text", tool_calls: [{ id: "c1", name: "tool", arguments: {} }])
conv.tool_result("text", tool_call_id: "c1")
conv.size # => 4
conv.to_a.map { |m| m[:role] } # => [:system, :user, :assistant, :tool]
conv.user_messages.size # => 1
Ask::Message
require "ask"
msg = Ask::Message.new(role: :user, content: "Hello")
msg.role # => :user
msg.content # => "Hello"
msg.tool_calls # => nil
msg.tool_call_id # => nil
msg.user? # => true
msg.assistant? # => false
msg.tool_call? # => false
msg.tool_result? # => false
Ask::Stream / Ask::Chunk
require "ask"
stream = Ask::Stream.new
stream.add(Ask::Chunk.new(content: "Hello"))
stream.add(Ask::Chunk.new(content: " world"))
stream.finish!
stream.accumulated_text # => "Hello world"
stream.length # => 2
stream.accumulated_usage # => {}
Ask::ModelCatalog
require "ask"
catalog = Ask::ModelCatalog.new([Ask::ModelInfo.new(id: "deepseek-v4-flash", provider: "deepseek")])
catalog.find("deepseek-v4-flash").provider # => "deepseek"
catalog.by_provider("deepseek").size # => 1
# The process-wide singleton (populated by ask-llm-providers when loaded)
Ask::ModelCatalog.instance.class # => Ask::ModelCatalog
Ask::ToolDef
require "ask"
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 } }
# => {type: "function",
# function:
# {name: "get_weather",
# description: "Get current weather",
# parameters:
# {type: "object",
# properties: {location: {type: "string"}},
# required: ["location"]},
# provider_params: {}}}
Ask::Result
The single result type for the whole ecosystem — defined in ask-core, used by providers, tools, and agents. Supports both the foundational API (success/failure/aborted/blocked) and the tool API (ok/error).
require "ask"
ok = Ask::Result.success("Data processed")
ok.success? # => true
ok.ok? # => true
ok.content # => "Data processed"
ok.output # => "Data processed"
ok.status # => :success
Ask::Result.ok(data: "processed").to_h
# => {ok: true, output: "processed", error: nil, metadata: {}}
Ask::Result.failure("API returned 500").status # => :error
Ask::Result.aborted("Cancelled").status # => :aborted
Ask::Result.blocked("Permission denied").status # => :blocked
Errors
require "ask"
Ask::RateLimitError < Ask::Error # => true
Ask::InvalidRole < Ask::Error # => true
Ask::MissingCredential < Ask::Error # => true
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
gem "ask-auth" — pulled in by every service gem and by ask-llm-providers.
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
gem "ask-tools" — no executable tools inside; add ask-tools-shell for shell and filesystem tools.
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
gem "ask-agent" — pulls in ask-core, ask-llm-providers, ask-tools, ask-skills, ask-state-providers, and ask-instrumentation.
Agent Definitions
# agents/health_check/agent.rb
module HealthCheck
class Agent < Ask::Agent::Definition
model "deepseek-v4-flash"
tools :bash, :read, :grep
schedule "every 5 minutes"
end
end
# Usage
agent = Ask::Agent.new("health_check")
agent.run("Check health")
Ask::Agent.definitions # => { "health_check" => [HealthCheck::Agent, "/path/to/agents/health_check"] }
Ask::Agent.rediscover!
Low-Level Session API
session.on_event { |event| ... }
session.id
session.total_cost
session.turn_count
session.total_input_tokens
session.total_output_tokens
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)
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: "deepseek-v4-flash")
# 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: "deepseek-v4-flash").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
gem "ask-rails" — requires Rails 7.1+ and ask-agent.
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", "deepseek-v4-flash")
end
# app/agents/support_bot/agent.rb
module SupportBot
class Agent < ApplicationAgent
model "deepseek-v4-flash"
tools :search_products
end
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: "deepseek-v4-flash")
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
gem "ask-rails-harness" — requires Rails 7.1+; pulls in ask-agent, ask-tools, ask-tools-shell, and ask-auth.
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 = "deepseek-v4-flash"
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
gem "ask-tools-shell" — depends on ask-tools and ask-sandbox-providers.
Ask::Tools::Bash.new.call(command: "ls")
Ask::Tools::Read.new.call(path: "/etc/hosts")
Ask::Tools::Write.new.call(path: "file.txt", content: "data")
Ask::Tools::Edit.new.call(path: "file.txt", old_string: "old", new_string: "new")
Ask::Tools::Glob.new.call(pattern: "**/*.rb")
Ask::Tools::Grep.new.call(pattern: "class")
Ask::Tools::Code.new.call(code: "puts RUBY_VERSION")
Ask::Tools::ApplyPatch.new.call(patchText: "--- a/file\n+++ b/file\n@@ ...")
## ask-llm-providers
LLM providers. Source
gem "ask-llm-providers" — all 33 providers in one gem; also loads the model catalog into Ask::ModelCatalog.
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
gem "ask-skills" — a dependency of ask-agent, so agent users get it automatically.
# 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. User config: `~/.config/ask/skills/`
4. Installed gems
5. Built-in skills (`skill.design`, `skill.compose`)
## ask-sandbox-providers
Sandboxed execution. [Source](https://github.com/ask-rb/ask-sandbox-providers)
`gem "ask-sandbox-providers"` — used by the Bash and Code tools in ask-tools-shell.
```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-providers
Pluggable state backends for key-value storage, session persistence, distributed locking, message queues, and ordered lists. Source
gem "ask-state-providers" — a dependency of ask-agent and ask-graph for session persistence and workflow checkpoints.
# In-memory (default, lives in ask-state-providers since v0.3.0)
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")
# Persistent backends
Ask::State::Providers::SQLite.new # file-backed (default: sessions.db)
Ask::State::Providers::SQLite.new(path: "state.sqlite")
Ask::State::Providers::Redis.new(url: ENV["REDIS_URL"])
Ask::State::Providers::Postgres.new(url: ENV["DATABASE_URL"])
Ask::State::Providers::MySQL.new(url: ENV["MYSQL_URL"])
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. Part of ask-core.
# 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: "deepseek-v4-flash",
tools: [Bash, Read, Ask::ProviderTool.web_search]
)
## ask-schema
JSON Schema DSL. Source
gem "ask-schema" — a dependency of ask-tools, which uses it for tool parameter schemas.
schema = Ask::Schema.create do
string :name
integer :count
array :tags, of: :string
object :meta do
string :version
end
end
schema.new("example").to_json_schema
ask-mcp
MCP client and server. Source
gem "ask-mcp" — needed by ask-web-search-mcp and ask-rails-harness-mcp, which both build on it.
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
gem "ask-eval" — Minitest plugin auto-loads with require "ask/eval/minitest".
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/