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/

Next Steps


This site uses Just the Docs, a documentation theme for Jekyll.