Database

Introduction

Overview

Persistence lets an agent outlive a single session. The conversation history, model name, and compaction status are serialized as JSON that can be stored in a file, a database column, or transmitted over a network. Three storage backends are available:

  • Filesystem: save and restore from a JSON file on disk
  • ActiveRecord: persist state in a database column using acts_as_agent
  • Sequel: persist state in a database column using plugin :agent

All three use the same serialization mechanism under the hood.

How it works

LLM::Context implements to_h and to_json for serialization and restore for deserialization. Save writes the current state, restore loads it back and picks up where the conversation left off. The ORM wrappers automate this; each talk or ask call persists the updated state back to the column automatically.

Why would I use it?

Without persistence, every agent starts with a blank conversation. Persistence enables long-running agents that survive process restarts, debugging sessions that resume mid-investigation, and conversation history that can be queried alongside application data.

Notes

The saved JSON can be stored in a file, a database column, or transmitted over the network. The ORM integrations use the same underlying serialization as filesystem persistence.

Filesystem

Overview

A conversation that ends when the process exits is not very useful. Serialization saves the context (message history, model name, compaction status) as JSON that can be stored in a string, a file, or a database column. Restore it later, in a different process or on a different machine, and pick up where you left off.

How it works

LLM::Context implements to_h and to_json for serialization and restore for deserialization. The serialized state includes the message history, model name, and compaction status. Save and restore work with file paths or in-memory strings. You can also serialize to a JSON string for database storage or network transmission:

llm = LLM.deepseek(key: ENV["KEY"])

# Save to file
agent1 = LLM::Agent.new(llm)
agent1.talk "remember my name is robert"
agent1.save(path: "agent.json")

# Restore from file
agent2 = LLM::Agent.new(llm, stream: $stdout)
agent2.restore(path: "agent.json")
agent2.talk "what's my name?"

# Or serialize to a JSON string
json = agent1.save
agent3.restore(string: json)

Why would I use it?

Filesystem persistence is the foundation for long-running agents that outlive a single session. Save a debugging session mid-investigation and resume it tomorrow. Store a completed conversation as evidence or training data. Pass context between processes, between machines, or through a job queue.

Notes

LLM::Agent delegates serialization to its internal context. The saved JSON can be stored in a file, a database column, or transmitted over the network.

ActiveRecord

Overview

ActiveRecord models use acts_as_agent to install the agent wrapper. The method accepts a block for configuring agent defaults and an options hash for storage format. Most defaults such as model, tools, instructions, schema, and concurrency can be set through the block. Tracer and stream can also be set here rather than through legacy convention methods.

How it works

When you want to add agent persistence to an ActiveRecord model, call acts_as_agent in the model class. The data column stores the full agent state (conversation history, model name, compaction status) as JSON. On first call, a fresh agent is created and the conversation starts from scratch.

On subsequent calls, the stored state is restored and the conversation continues. Every talk or ask call persists the updated state back to the column automatically.

The data_column: option lets you use a different column name. The format: option controls the storage type. Use :string for a text column (any database) or :jsonb for a native PostgreSQL JSONB column (recommended for PostgreSQL). For :jsonb, ActiveRecord handles JSON typecasting automatically.

The block yields an LLM::Agent instance. Set agent defaults in the block using the set method or individual accessors. Anything you can set on an agent can be configured here – model, tools, instructions, schema, concurrency, tracer, stream, and confirm:

Note that tracer and stream are set directly in the block rather than through legacy set_tracer / set_context convention methods. The block style is the recommended approach for all agent configuration. Only set_provider is required as a private method.

# Migration for the data column
class CreateAgents < ActiveRecord::Migration[7.1]
  def change
    create_table :agents do |t|
      t.string :name
      t.text :data   # stores the serialized agent state
      t.timestamps
    end
  end
end

# Or with PostgreSQL JSONB
class CreateAgents < ActiveRecord::Migration[7.1]
  def change
    create_table :agents do |t|
      t.string :name
      t.jsonb :data  # native JSON storage, supports indexing
      t.timestamps
    end
  end
end

# Model setup
require "active_record"
require "llm"
require "llm/active_record"

class Agent < ApplicationRecord
  acts_as_agent(format: :jsonb) do |agent|
    agent.model "deepseek-v4-pro"
    agent.instructions "solve the user's query"
    agent.tools [Research, FinalizeResearch, ActOnResearch]
    agent.tracer LLM::Tracer::Logger.new(llm, io: $stdout)
  end

  private

  def set_provider
    LLM.deepseek(key: ENV["KEY"])
  end
end

agent = Agent.create!(name: "researcher")
agent.talk "perform research"
# state is saved to the data column automatically

Why would I use it?

The ActiveRecord wrapper integrates with your existing models. Agent state is automatically persisted after each conversation turn using the same create!, save!, and query methods you already use.

Notes

The format option defaults to :string. Use :json or :jsonb for PostgreSQL. JSONB is recommended; it supports indexing and is more efficient for querying. The data_column option defaults to :data – create a migration to add a TEXT or JSONB column to your table.

The model requires a set_provider private method that returns an LLM::Provider instance. Legacy set_context and set_tracer convention methods also work for backwards compatibility, but the block style (agent.tracer ..., agent.stream ...) is preferred for all agent-level configuration.

Sequel

Overview

Sequel models use plugin :agent to install the agent wrapper. The plugin accepts the same options as acts_as_agent and follows the same conventions for provider resolution and state persistence. On first call, a fresh agent is created and the conversation starts from scratch. On subsequent calls, the stored state is restored and the conversation continues automatically.

How it works

When you want to add agent persistence to a Sequel model, register the plugin in the model class. The data column stores the full agent state as JSON, same structure as ActiveRecord. On first call, a fresh agent is created. On subsequent calls, the stored state is restored and the conversation continues. Every talk or ask call persists the updated state.

The data_column: and format: options work identically to ActiveRecord. For :jsonb, Sequel loads the pg_json extension automatically and handles JSON typecasting. The block yields an LLM::Agent instance. Configure agent defaults in the block – model, tools, instructions, tracer, stream, and confirm all go here:

As with ActiveRecord, tracer and stream are configured in the block rather than through legacy convention methods. The block style is the recommended approach.

require "sequel"
require "llm"
require "llm/sequel/plugin"

# Migration for the data column
DB.create_table :agents do
  primary_key :id
  String :name
  String :data       # stores the serialized agent state
  DateTime :created_at
  DateTime :updated_at
end

# Or with PostgreSQL JSONB
DB.create_table :agents do
  primary_key :id
  String :name
  column :data, :jsonb  # native JSON storage
  DateTime :created_at
  DateTime :updated_at
end

# Model setup
class Agent < Sequel::Model
  plugin(:agent, format: :jsonb) do |agent|
    agent.model "deepseek-v4-pro"
    agent.instructions "solve the user's query"
    agent.tools [Research, FinalizeResearch, ActOnResearch]
    agent.tracer LLM::Tracer::Logger.new(llm, io: $stdout)
  end

  private

  def set_provider
    LLM.deepseek(key: ENV["KEY"])
  end
end

agent = Agent.create(name: "researcher")
agent.talk "perform research"
# state is saved to the data column automatically

Why would I use it?

The Sequel plugin integrates with your existing models. It follows the same conventions as ActiveRecord, so switching between the two requires minimal code changes.

Notes

The format option defaults to :string. Use :json or :jsonb for PostgreSQL. Sequel loads the pg_json extension automatically and wraps JSON values for native storage. JSONB is recommended. The data_column option defaults to :data.

The model requires a set_provider private method that returns an LLM::Provider instance. Legacy set_context and set_tracer convention methods also work for backwards compatibility, but configuring tracer, stream, and other agent options in the block is the preferred approach.