# Agents
{: .no_toc }

Define reusable AI assistants with class-based configuration, runtime context, and prompt conventions
{: .fs-6 .fw-300 }

## Table of contents
{: .no_toc .text-delta }

1. TOC
{:toc}

---

After reading this guide, you will know:

* How to define agents with a class-based DSL
* How to use agents with plain Ruby chats and Rails-backed chats
* How runtime context works (`chat`, `inputs`, and lazy evaluation)
* How prompt conventions work in `app/prompts`
* Which methods are available on agent instances

## What Are Agents?

Agents are a class-based way to define a chat setup once and reuse it everywhere.

For example, instead of re-adding the same instructions and tools in every controller, job, or service, you define them once in an agent class and call that agent wherever you need it.

```ruby
class SupportAgent < RubyLLM::Agent
  model "gpt-5-nano"
  instructions "You are a concise support assistant."
  tools SearchDocs, LookupAccount
end

response = SupportAgent.new.ask "How do I reset my API key?"
```

In other words, an agent is a named wrapper around the same configuration you would otherwise apply progressively with `chat.with_*` calls (`with_instructions`, `with_tools`, `with_provider_options`, and so on).

Agents work in two modes:

* Plain Ruby mode via `.chat` (returns `RubyLLM::Chat`)
* Rails mode via `.create/.create!/.find` when `chat_model` is configured (returns your ActiveRecord chat model)

Example of Rails mode:

```ruby
class WorkAssistant < RubyLLM::Agent
  chat_model Chat  # this activates the Rails integration
  model "gpt-5-nano"
  instructions "You are a helpful assistant."
  tools SearchDocs, LookupAccount
end

chat = WorkAssistant.create!(user: current_user)
same_chat = WorkAssistant.find(chat.id)
```

## Defining an Agent

Create a class that inherits from `RubyLLM::Agent` and declare its configuration:

```ruby
# app/agents/work_assistant.rb
class WorkAssistant < RubyLLM::Agent
  model "gpt-5-nano"
  instructions "You are a helpful assistant."
  tools SearchDocs, LookupAccount
  temperature 0.2
  max_output_tokens 256
end
```

Supported class macros:

These macros use the same arguments you already know from `RubyLLM.chat(...)` and `Chat#with_*` methods.
For example, `model` maps to `RubyLLM.chat(model:, provider:, ...)`, `tools` maps to `with_tools`, `tool_options` maps to `with_tool_options`, `instructions` maps to `with_instructions`, and so on.

* `model`, and its `provider:` and `protocol:` options (see [Chat Basics](/next/chat/) and [Request Control](/next/chat-request-control/#choosing-the-wire-protocol))
* `tools` (see [Tools](/next/tools/))
* `tool_options` (see [Controlling Tool Execution](/next/tool-execution/))
* `instructions` (see [Chat Basics](/next/chat/))
* `temperature` (see [Chat Basics](/next/chat/))
* `max_output_tokens` (see [Request Control](/next/chat-request-control/))
* `thinking` (see [Thinking](/next/thinking/))
* `citations` (see [Citations](/next/citations/))
* `provider_options` (see [Request Control](/next/chat-request-control/))
* `headers` (see [Chat Basics](/next/chat/))
* `schema` (see [Chat Basics](/next/chat/))
* `fallbacks` (see [Model Fallbacks](/next/error-handling/#model-fallbacks))
* `context` (see [Configuration](/next/configuration/))
* `chat_model` (Rails-backed mode)
* `inputs` (declared runtime inputs)

`tools` sets which tools the agent's chats may call. Use `tool_options` for `choice`, `calls`, and `concurrency`:

```ruby
class WorkAssistant < RubyLLM::Agent
  tools SearchDocs, LookupAccount
  tool_options choice: :auto, calls: :one
end
```

`schema` supports:

* A schema class (for example `PersonSchema`) - same as `with_schema`
* A JSON schema hash - same as `with_schema`
* An inline DSL block with `schema do ... end` - agent-specific convenience

Inline DSL example:

```ruby
class CriticAgent < RubyLLM::Agent
  schema do
    string :verdict, enum: ["pass", "revise"]
    string :feedback
  end
end
```

### Model Fallbacks

Use `fallbacks` to give every chat created by the agent the same ordered fallback models:

```ruby
class WorkAssistant < RubyLLM::Agent
  model "gpt-4.1"
  fallbacks "gpt-4.1-mini", "claude-haiku-4-5"
end
```

You can also customize which errors trigger fallback:

```ruby
class WorkAssistant < RubyLLM::Agent
  model "gpt-4.1"
  fallbacks "gpt-4.1-mini",
            on: [RubyLLM::RateLimitError, RubyLLM::ServiceUnavailableError]
end
```

Fallbacks can be model IDs or `RubyLLM::Model` objects:

```ruby
class WorkAssistant < RubyLLM::Agent
  model "gpt-4.1"
  fallbacks RubyLLM.models.find("claude-haiku-4-5", :anthropic)
end
```

This works for both `WorkAssistant.chat` and Rails-backed agents configured with `chat_model`.

## Runtime Context and Inputs

Agents support runtime-evaluated values using blocks and lambdas.

Declare additional runtime inputs with `inputs`:

```ruby
class WorkAssistant < RubyLLM::Agent
  chat_model Chat
  inputs :workspace

  instructions { "You are helping #{workspace.name}" }
end
```

`chat` is always available in execution context:

* In `.chat` mode, `chat` is a `RubyLLM::Chat`
* In `.create/.create!/.find` mode, `chat` is your `chat_model` record

This enables Rails-style usage:

```ruby
class WorkAssistant < RubyLLM::Agent
  chat_model Chat

  instructions current_date_time: -> { Time.current.strftime("%B %d, %Y") },
    display_name: -> { chat.user.display_name_or_email },
    full_name: -> { chat.user.full_name.presence || chat.user.display_name_or_email }

  tools do
    [
      TodoTool.new(chat: chat),
      GoogleDriveListTool.new(user: chat.user),
      GoogleDriveSearchTool.new(user: chat.user),
      GoogleDriveReadTool.new(user: chat.user)
    ]
  end
end
```

Important: values that depend on runtime `chat` must be lazy (blocks/lambdas), not eager class-load expressions.

## Prompt Management and Conventions

Agents have prompt conventions built in. They use the same `app/prompts` templates as [Prompt Rendering](/next/prompt-rendering/), with class-based lookup layered on top.

### Default instructions prompt

Named agents automatically use their conventional instructions prompt when it exists:

```ruby
class WorkAssistant < RubyLLM::Agent
  chat_model Chat
end
```

RubyLLM looks for:

* `app/prompts/work_assistant/instructions.txt.erb`

If the file exists, it is rendered and used as instructions automatically. If it does not exist and you did not set `instructions`, the agent starts without system instructions. To require a prompt and fail loudly when it is missing, reference it explicitly:

```ruby
class WorkAssistant < RubyLLM::Agent
  chat_model Chat
  instructions { prompt("instructions") }
end
```

If that file does not exist, RubyLLM raises `RubyLLM::PromptNotFoundError`.

### Prompt shorthand with locals

You can pass locals directly:

```ruby
class WorkAssistant < RubyLLM::Agent
  chat_model Chat
  instructions display_name: -> { chat.user.display_name_or_email }
end
```

This also renders `instructions.txt.erb` for that agent path.

### Prompt helper in runtime blocks

Within execution context you can call:

```ruby
instructions { prompt("instructions", display_name: chat.user.display_name_or_email) }
```

### Naming conventions

Agent prompt path is derived from class name:

* `WorkAssistant` -> `app/prompts/work_assistant/...`
* `Admin::SupportAgent` -> `app/prompts/admin/support_agent/...`

Prompt extension defaults to `.txt.erb`.

For rendering a prompt directly outside an agent, use `RubyLLM.render_prompt`. See [Prompt Rendering](/next/prompt-rendering/).

## Using an Agent

### Plain Ruby chat

```ruby
chat = WorkAssistant.chat
response = chat.ask("Hello")

puts response.content
```

`WorkAssistant.chat(...)` returns a configured `RubyLLM::Chat`.

### Instance API

You can still instantiate and use an agent instance directly:

```ruby
agent = WorkAssistant.new
response = agent.ask("Hello")

response.cost.total # v1.15+
agent.cost.total    # v1.15+
```

Agent instances delegate the full `RubyLLM::Chat` instance API to the underlying chat object
(or to `to_llm` when using a Rails-backed chat model).

Delegated methods include:

* `model`, `messages`, `tools`, `provider_options`, `headers`, `schema`
* `cost` (v1.15+)
* `ask`, `say`, `complete`
* `add_message`, `each`
* `with_tools`, `without_tools`, `with_tool_options`, `without_tool_options`
* `with_model`, `with_temperature`, `without_temperature`, `with_thinking`, `without_thinking`, `with_citations`, `without_citations`, `with_context`, `without_context`
* `with_caching`, `without_caching`, `with_provider_options`, `without_provider_options`, `with_headers`, `without_headers`, `with_schema`, `without_schema`, `with_fallbacks`, `without_fallbacks`
* `before_message`, `after_message`, `before_tool_call`, `after_tool_result`, `before_fallback`, `after_fallback`

You can always access the wrapped chat object directly via `agent.chat`.

## Rails-Backed Agents

Set `chat_model` to use your ActiveRecord chat model:

```ruby
class WorkAssistant < RubyLLM::Agent
  chat_model Chat
  model "gpt-5-nano"
  instructions "You are a helpful assistant."
  tools SearchDocs, LookupAccount
end
```

Then you can:

```ruby
chat = WorkAssistant.create!(user: current_user)

chat = WorkAssistant.find(params[:id])

WorkAssistant.sync_instructions!(chat)
```

`create/create!/find` require `chat_model`. Calling them without it raises an error.

Instruction persistence contract in Rails mode:

* `create/create!` applies and persists instructions
* `find` applies instructions at runtime only (no persistence side effects)
* `sync_instructions!` explicitly persists the current agent instructions

### Using an Existing Chat Record
If you already have a `Chat` record, pass it to `Agent.new(chat:)` instead of calling `Agent.find`. This applies all agent configuration (instructions, tools, etc.) without an extra database query:

```ruby
chat_record = Chat.find(params[:id])
chat = WorkAssistant.new(chat: chat_record)
chat.ask("Hello")
```

## When to Use Agents vs `RubyLLM.chat`

Use `RubyLLM.chat` for one-off, inline conversations:

```ruby
chat = RubyLLM.chat(model: "gpt-5-nano")
chat.with_instructions "Explain this clearly."
```

Use agents when you want named, reusable behavior:

```ruby
class WorkAssistant < RubyLLM::Agent
  model "gpt-5-nano"
  instructions "You are a helpful assistant."
  tools SearchDocs, LookupAccount
end
```

Think of `RubyLLM.chat` as ad-hoc and `RubyLLM::Agent` as reusable application architecture.

## Agent vs `Chat#with_*`

These two styles are equivalent in capability, but optimized for different contexts.

Use progressive `Chat#with_*` when configuration is local and one-off:

```ruby
chat = RubyLLM.chat(model: "gpt-5-nano")
chat.with_instructions("You are a helpful assistant.")
chat.with_tools(SearchDocs, LookupAccount)
chat.ask("Help me find docs about callbacks.")
```

Use agents when that setup should be centralized and reused:

```ruby
class WorkAssistant < RubyLLM::Agent
  model "gpt-5-nano"
  instructions "You are a helpful assistant."
  tools SearchDocs, LookupAccount
end

WorkAssistant.new.ask("Help me find docs about callbacks.")
```

## Next Steps

* Learn about [Chat Basics](/next/chat/)
* Explore [Tools](/next/tools/)
* Review [Rails Integration](/next/rails/)
