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.