Agents
Introduction
Overview
LLM::Agent
is the recommended entry point for most use-cases. It provides a
class-level DSL for defining reusable, preconfigured assistants
with defaults for model, tools, schema, and instructions. Under
the hood it delegates to
LLM::Context,
so it has the same runtime surface: message history, streaming,
serialization, compaction, and concurrency.
How it works
An agent holds a conversation with a model. You send input with
LLM::Agent#talk,
the model responds, and if it requests tools the agent
executes them automatically and feeds the results back. It enables
a loop guard by default that detects repeated tool-call patterns
and blocks stuck execution. The default tool attempt budget is 25.
After that, the agent sends advisory errors back through the model
and keeps the loop in-band. Instructions are injected once unless
a system message is already present.
Why would I use it?
Agents manage the tool loop for you. They guard against infinite
loops, keep conversation state across turns, and let you define
reusable configurations at the class level. If you need manual
control over the tool loop, use
LLM::Context
directly instead.
Notes
Agents support the same concurrency strategies, compaction,
cancellation, and serialization as contexts. The trade-off between
a subclass and a direct instance is only in how the agent is
organized, not in what it can do. Tool loop execution can be
configured with concurrency: :sequential, :thread, :async,
:fiber, :fork, or :ractor.
Reusable agents
Overview
A subclass of
LLM::Agent
gives you a reusable agent with its own behavior. You define the model, tools, and other
attributes at the class level, and each instance picks them up
as defaults. Attributes can be overridden per-instance, and they
can be plain values, blocks, or Symbols that resolve to methods.
The class becomes a self-contained worker that you can instantiate
and talk to from anywhere.
How it works
A subclass declares its defaults with set. Each key is a
class-level accessor: model, tools, instructions, stream,
tracer, concurrency, schema, confirm. When you create a
new instance, any keyword arguments override the class-level
defaults.
class Agent < LLM::Agent
set model: "deepseek-v4-pro",
tools: [Shell]
end
llm = LLM.openai(key: ENV["KEY"])
agent = Agent.new(llm)
agent.talk "Run 'date'"
Why would I use it?
A subclass is useful when multiple parts of an application need to
call the same agent. The configuration and any helper methods live in one place. Define a
research! method that kicks off the agent’s work. The subclass
becomes a self-contained worker.
Notes
Attributes passed to set can be plain values, blocks, or
Symbols. A Symbol is evaluated as an instance method on the
subclass, so tracer: :set_tracer calls set_tracer on the
instance. A block like stream: -> { $stdout } is evaluated
when the attribute is first accessed.
Throwaway agents
Overview
An
LLM::Agent
instance is the simplest way to get started. You pass a provider and any configuration as keyword
arguments, and the agent runs the tool loop and manages state
just like a subclass would. This is the right choice when you
are prototyping, running a one-off task, or when the agent’s
configuration is determined at runtime.
How it works
A direct instance takes the same attributes as keyword arguments
to LLM::Agent.new. The first argument is always the provider.
Everything else is optional. The agent runs the tool loop
and manages state under the hood through a
LLM::Context.
llm = LLM.deepseek(key: ENV["KEY"])
agent = LLM::Agent.new(llm, stream: $stdout)
agent.talk "Hello world"
Why would I use it?
A direct instance is the right choice for quick experiments, one-shot tasks, or when defining a class would be overkill. It is also the right choice when the agent’s configuration is determined at runtime and building a class hierarchy would be overkill.
Notes
Direct instances accept all the same options as subclasses. The
difference is only in how the agent is organized, not in what it
can do. Under the hood,
LLM::Agent
creates a
LLM::Context
that manages the message history.