Core Concepts
Define an Agent in a Ruby class, then call it with .ask.
class CustomerSupportAgent < LittleGhost::Agent model "openrouter:openai/gpt-5.6-luna" system_prompt "Answer customer questions clearly." tools HelpCenterLookupTool end run = CustomerSupportAgent.ask("Where is my order?") run.response
From there, add only what the work needs. Give the agent a tool. Let it ask a specialist for help. Or coordinate several agents while the rest of your application keeps making the same call.
An Agent owns one model loop
An Agent defines one model-driven behavior. It chooses the model, supplies the instructions and tools, and carries one request through to an answer.
The class holds the behavior you want to reuse. Each call brings its own input, history, context, settings, and attachments. Request data never needs to live on the class.
CustomerSupportAgent ├── model selection ├── system prompt ├── HelpCenterLookupTool └── limits and optional capabilities
An Agent can return text or checked, structured data. You can add streaming, saved conversations, or callbacks later. None of them are required to begin.
A Tool connects the model to Ruby
A Tool is one focused thing an agent can ask your application to do. It has a name, a description, an input schema, and the Ruby code that does the work.
class HelpCenterLookupTool < LittleGhost::Tool description "Look up a help center entry by topic." input_schema( type: "object", properties: {topic: {type: "string"}}, required: ["topic"], additionalProperties: false ) def call(input) {"refunds" => "Refunds are available within 30 days."} .fetch(input.fetch("topic")) end end
LittleGhost checks the model’s arguments, calls the Tool, and gives the result back to the model. The schema checks shape, not permission. Check permission inside the Tool using identity and account information from your application.
Tools follows that path from model input to application code, including run-scoped bindings, concurrency, retries, and sandbox delegation.
A Run owns one top-level execution
Every .ask or .stream_ask creates a Run. Think of it as the record of one trip through LittleGhost. It opens what the request needs, records how the work ended, and closes the resources it owns.
run = CustomerSupportAgent.ask("Where is order 481?") run.completed? # => true run.response # One possible response: Order 481 is out for delivery. run.usage # => normalized token usage run.result # => the complete LittleGhost::RunResult
The Agent defines reusable behavior; the Run records what happened this time.
Follow one request
One Run owns the trip from request to result:
Run ├── Invocation: caller input, history, and application context ├── RunContext: mutable working state for this execution └── Agent and Tools ──> RunResult
An Invocation is the request in LittleGhost’s standard shape. Its context contains current request values supplied by your application. A Tool can read those values through run.invocation.context when it checks permission.
A Session stores conversation state between Runs when persistence is configured. The RunContext carries mutable working state in context.state during one Run. LittleGhost loads saved Session state before adding the current Invocation context. Recheck saved values before using them for permission decisions.
A Tool’s Binding gives the Tool access to objects created for this run, including the Agent, Run, Workspace, and Sandbox. These objects are separate from arguments chosen by the model. Tools explains the binding; Workspaces and Sandboxes explains delegated files and child processes.
The final RunResult keeps the complete assembly result. Its text is the final text answer. Its output returns structured data when the Agent declared a result schema, and text otherwise. The top-level Run#response is always the caller-facing text.
See how a call ended
Top-level calls normally return a Run, even when execution fails. The terminal event carries the same outcome when you stream:
| What happened | Run outcome | Terminal event | What Ruby does |
|---|---|---|---|
| The assembly completed | completed |
:run_stop |
Returns the Run |
| Model, provider, or assembly execution failed | failed |
:run_error |
Returns the Run; inspect run.error |
| The deadline stopped work | partial |
:run_partial |
Returns the Run with any response produced so far |
| Cancellation stopped work | cancelled |
:run_cancel |
Returns the Run without a response |
Tool input or a ToolError failed |
The model may recover | No terminal event by itself | Gives a safe error result back to the model |
| Input, configuration, or resources failed before a Run could start | No Run exists | None | Raises the exception |
Unexpected Tool exception messages are hidden from the model. The original exception remains available to application callbacks and diagnostics.
Failures while closing resources, delivering events, or reporting instrumentation sit outside the normal result path. They raise a Ruby exception because LittleGhost can no longer promise that it delivered a clean ending. Running in Production covers supervision and shutdown.
An Assembly can look like one Agent
One model loop is not always enough. LittleGhost calls any unit that a caller can invoke like an Agent an Assembly.
An Agent is the smallest Assembly. Workflow, Swarm, and Graph coordinate several participants while preserving the same entrypoints:
CustomerSupportAgent.ask(question) ResponseWorkflow.ask(question) ProblemSolverSwarm.ask(question) SupportFlowGraph.ask(question)
That shared calling style is what makes composition feel natural. A controller, job, or CLI does not need to know whether one Agent answered or a whole support process worked together.
Choose who controls the next step
The coordination types differ mainly in who decides what happens next:
| Need | Choose | Who controls the next step? |
|---|---|---|
| One model-driven behavior | Agent | The active model loop |
| A model should delegate a named task | Subagent | The parent model |
| Ruby should enforce ordering or branching | Workflow | The workflow’s Ruby code |
| Specialists should choose permitted handoffs | Swarm | The active agent |
| Allowed routes should be visible in advance | Graph | Declared nodes and edges |
Subagents bring in a specialist
A subagent is a specialist that a parent Agent can call for help. The parent model chooses when to delegate, reads the result, and then continues its own answer.
class CustomerSupportAgent < LittleGhost::Agent model "openrouter:openai/gpt-5.6-luna" subagent ResearchAgent, kind: "research" end
Use a subagent when delegation is part of one model’s decision-making. Use a Workflow when application code must guarantee that a step happens.
When an Agent also uses code mode, subagent controls stay in the Agent’s conversation. Code-mode programs can compose ordinary Tools, while spawning, messaging, and checking on subagents remain decisions for the parent model.
Workflows make Ruby the coordinator
A Workflow coordinates work with ordinary Ruby. Its perform method can call an Agent or another Assembly, read a result, choose a branch, or run independent steps together.
invoke prepares a lazy child call. Reading .output runs an intermediate child. Return the final invoke itself, without reading its output, so that answer can stream to the caller.
class ResponseWorkflow < LittleGhost::Workflow private def perform research = invoke(ResearchAgent).output invoke CustomerSupportAgent, input: <<~PROMPT #{input.text} Research: #{research} PROMPT end end
Workflow children receive the caller’s history and application context by default. Pass history: [], context: {}, or redacted values when a participant should receive less.
Swarms let agents hand work to one another
A Swarm is a group of Agents that can hand work to one another. One member is active at a time. It can answer the caller or choose one of its allowed specialists.
class ProblemSolverSwarm < LittleGhost::Swarm member TriageAgent member BillingAgent member AccountAgent start TriageAgent handoff TriageAgent, to: [BillingAgent, AccountAgent] end
A Swarm is intentionally agent-to-agent. Its members are Agents, not other kinds of Assembly. Caller history and application context stay hidden unless a member opts in. A handoff message comes from another model, so a receiving Agent should use it as context rather than proof that an action is permitted.
Graphs make routes visible
A Graph connects named Assembly nodes with declared edges. Nodes can contain Agents, Workflows, Swarms, or other Graphs.
class SupportFlowGraph < LittleGhost::Graph node :triage, TriageAgent node :billing, BillingAgent node :general, CustomerSupportAgent node :respond, CustomerSupportAgent start :triage edge :triage, :billing do |state| state.result(:triage).output == "billing" end edge :triage, :general edge :billing, :respond edge :general, :respond finish :respond end
Graph nodes receive the original task and results from the nodes immediately before them. They do not receive caller history or application context unless their declarations opt in. Compose Agents explains parallel routes, joins, input mapping, and data boundaries.
One result, even when several agents help
Every assembly produces the same top-level Run and final RunResult. Composite assemblies also keep a size-limited record of the participants that ran:
run = SupportFlowGraph.ask("Why was I charged twice?") run.response run.result.steps run.result.trajectory.transitions
This record shows which participants ran. Compose Agents explains builders, detailed routing records, and live events from nested Agents.
The pieces now fit together: Agents define behavior. Tools connect them to Ruby. Runs record one execution. Assemblies let the system grow without changing the caller.
Continue with Models and Providers to choose model targets and configure provider connections. When you need several agents to work together, Compose Agents builds on the same concepts.