Getting Started

In this guide, you’ll run an agent, connect it to a small help center, and stream its answer. The whole feature stays in ordinary Ruby.

Install the gem

LittleGhost requires Ruby 3.3 or newer. Add the gem to your Gemfile, install it, and set a provider credential:

gem "little_ghost"
$ bundle install
$ export OPENROUTER_API_KEY="..."

Use your application’s secret manager outside a local shell, and never commit provider credentials.

This guide uses OpenRouter because one credential is enough to begin. LittleGhost can use other provider connections too; you will configure those in Running in Production.

See your first answer

Create customer_support_agent.rb:

require "little_ghost"

class CustomerSupportAgent < LittleGhost::Agent
  model "openrouter:openai/gpt-5.6-luna"
  system_prompt "Answer customer questions clearly and concisely."
end

run = CustomerSupportAgent.ask("Can I change the address on my order?")

if run.completed?
  puts run.response
else
  warn "Support request ended as #{run.outcome}: #{run.error&.class}"
end

Run the file and you have a working AI feature:

$ ruby customer_support_agent.rb

CustomerSupportAgent.ask creates a LittleGhost::Run for this request. When the work finishes, the Run holds the outcome and response.

The inline prompt keeps this first example visible in one place. When the instructions grow, Prompts as Views moves them into a conventional ERB file without adding setup to the Agent.

The selected external provider may receive system instructions, caller input, conversation history, tool results, and attachments. Model wording can vary, so use application code—not a prompt—when a rule must always hold.

Connect the agent to your application

The first agent can answer general questions. A tool gives it a focused operation backed by your Ruby code:

class HelpCenterLookupTool < LittleGhost::Tool
  HELP_CENTER_ENTRIES = {
    "refunds" => "Refunds are available within 30 days of purchase.",
    "shipping" => "Standard shipping takes three to five business days."
  }.freeze

  description "Look up a help center entry by topic."
  input_schema(
    type: "object",
    properties: {
      topic: {type: "string", enum: HELP_CENTER_ENTRIES.keys}
    },
    required: ["topic"],
    additionalProperties: false
  )

  def call(input)
    HELP_CENTER_ENTRIES.fetch(input.fetch("topic"))
  end
end

Make the tool available to the agent and tell the model when to use it:

class CustomerSupportAgent < LittleGhost::Agent
  description "Answers customer support questions."
  model "openrouter:openai/gpt-5.6-luna"
  system_prompt <<~PROMPT
    Answer clearly and do not invent company guidance.
    Check the help center before stating company guidance.
  PROMPT
  tools HelpCenterLookupTool
end

run = CustomerSupportAgent.ask(
  "I bought an item two weeks ago. Can I get a refund?"
)

run.response
# One possible response:
# Refunds are available within 30 days, so your purchase is eligible.

LittleGhost checks the model’s arguments before it calls HelpCenterLookupTool#call. The Tool’s result then becomes context for the model.

Use application context for private data

The schema checks shape, not permission. When a Tool reads private data or changes something, use identity and account information established by your application rather than asking the model to supply it.

While an Agent is working, LittleGhost binds each Tool instance to the current Run. The Tool can read request values through its run accessor:

class OrderStatusTool < LittleGhost::Tool
  ORDER_STATUSES = {
    ["user-7", "account-2", "481"] => "out for delivery"
  }.freeze

  description "Look up an order that belongs to the current customer."
  input_schema(
    type: "object",
    properties: {order_number: {type: "string"}},
    required: ["order_number"],
    additionalProperties: false
  )

  def call(input)
    lookup = [
      run.invocation.actor_id,
      run.invocation.context.fetch("account_id"),
      input.fetch("order_number")
    ]

    ORDER_STATUSES.fetch(lookup) do
      raise LittleGhost::ToolError, "Order not found"
    end
  end
end

class CustomerSupportAgent < LittleGhost::Agent
  tools HelpCenterLookupTool, OrderStatusTool
end

run = CustomerSupportAgent.ask(
  "Where is order 481?",
  actor_id: "user-7",
  context: {account_id: "account-2"}
)

Here, order_number came from the model. The application supplied actor_id and account_id after authenticating the caller. LittleGhost places those request values on run.invocation; context keys become strings.

Safety note: Treat model-selected Tool arguments like any other external input. Check permission using the current user and account before returning private data or performing a write.

That is enough to authorize the first Tool safely. Core Concepts names the request and working-state objects behind run, and Running in Production explains what changes when you add saved conversations.

Stream the same agent

Use .stream_ask when a console, HTTP response, or user interface should receive progress as it happens:

stream = CustomerSupportAgent.stream_ask("Can I get a refund?")

run = stream.each do |event|
  case event.type
  when :text_delta
    print event.data.fetch(:text)
  when :run_error
    warn event.data.fetch(:message)
  end
end

puts "\n#{run.response}" if run.completed?
warn run.error.class.name if run.failed?

The stream yields LittleGhost::StreamEvent values. Text, tool activity, usage, and completion all look the same across providers. When enumeration finishes, .each returns the same LittleGhost::Run that now holds the final outcome and response.

Give the code a home

LittleGhost does not require an application layout. Keep definitions beside related application code, or use these optional conventions:

app/
├── agents/
│   └── customer_support_agent.rb
├── assemblies/
│   └── response_workflow.rb
├── prompts/
│   └── customer_support/
│       └── system.erb
└── tools/
    └── help_center_lookup_tool.rb

You now have the smallest useful LittleGhost application: one Agent, one Tool, and one familiar Ruby call.

When the feature grows, the calling style stays the same. An assembly lets one or more agents work as a unit while keeping .ask and .stream_ask. Read Core Concepts next and grow this Agent into a larger system.