little_ghost 0.2.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +72 -74
  3. data/docs/guides/assemblies.md +286 -0
  4. data/docs/guides/core_concepts.md +159 -135
  5. data/docs/guides/getting_started.md +114 -83
  6. data/docs/guides/production.md +187 -0
  7. data/docs/guides/prompt_views.md +132 -0
  8. data/lib/little_ghost/ag_ui/adapter.rb +3 -3
  9. data/lib/little_ghost/agent/delegation.rb +35 -8
  10. data/lib/little_ghost/agent/tool_loop.rb +2 -1
  11. data/lib/little_ghost/agent.rb +280 -326
  12. data/lib/little_ghost/agent_builder.rb +20 -4
  13. data/lib/little_ghost/agent_factory.rb +3 -0
  14. data/lib/little_ghost/{agent_interruptions.rb → agent_interjections.rb} +12 -12
  15. data/lib/little_ghost/assembly.rb +345 -0
  16. data/lib/little_ghost/assembly_builder.rb +497 -0
  17. data/lib/little_ghost/assembly_execution.rb +535 -0
  18. data/lib/little_ghost/configuration.rb +263 -39
  19. data/lib/little_ghost/content.rb +5 -5
  20. data/lib/little_ghost/data_map.rb +209 -0
  21. data/lib/little_ghost/errors.rb +10 -2
  22. data/lib/little_ghost/execution.rb +206 -0
  23. data/lib/little_ghost/graph.rb +930 -0
  24. data/lib/little_ghost/message.rb +4 -4
  25. data/lib/little_ghost/model_resolver.rb +2 -2
  26. data/lib/little_ghost/prompt_resolver.rb +2 -0
  27. data/lib/little_ghost/run.rb +190 -64
  28. data/lib/little_ghost/run_context.rb +33 -20
  29. data/lib/little_ghost/run_result.rb +22 -11
  30. data/lib/little_ghost/runtime/hook.rb +9 -4
  31. data/lib/little_ghost/runtime.rb +134 -36
  32. data/lib/little_ghost/sandbox.rb +1 -1
  33. data/lib/little_ghost/session.rb +12 -23
  34. data/lib/little_ghost/session_store.rb +9 -5
  35. data/lib/little_ghost/session_stores/agent_core_memory.rb +64 -56
  36. data/lib/little_ghost/session_stores/filesystem.rb +261 -0
  37. data/lib/little_ghost/session_stores/memory.rb +7 -0
  38. data/lib/little_ghost/subagents/manager.rb +42 -42
  39. data/lib/little_ghost/support/executor.rb +14 -2
  40. data/lib/little_ghost/support/loader.rb +2 -2
  41. data/lib/little_ghost/support.rb +15 -3
  42. data/lib/little_ghost/swarm.rb +439 -0
  43. data/lib/little_ghost/tool.rb +88 -20
  44. data/lib/little_ghost/tools/write_todos.rb +6 -1
  45. data/lib/little_ghost/tracing/open_telemetry.rb +14 -3
  46. data/lib/little_ghost/unrestricted_sandbox.rb +1 -1
  47. data/lib/little_ghost/version.rb +1 -1
  48. data/lib/little_ghost/workflow.rb +224 -90
  49. data/lib/little_ghost.rb +36 -25
  50. metadata +17 -5
@@ -1,33 +1,62 @@
1
- # Getting Started with LittleGhost
1
+ # Getting Started
2
2
 
3
- This guide builds a customer support agent that checks a small help center before answering. It focuses on the Ruby objects needed for one useful run: a tool, an agent, and the call that starts it.
3
+ 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.
4
4
 
5
- LittleGhost runs inside your Ruby process. It does not choose how your application is hosted or where these definitions live.
5
+ ## Install the gem
6
6
 
7
- ## Install LittleGhost
8
-
9
- LittleGhost requires Ruby 3.3 or newer. Add the gem to your `Gemfile`:
7
+ LittleGhost requires Ruby 3.3 or newer. Add the gem to your `Gemfile`, install it, and set a provider credential:
10
8
 
11
9
  ```ruby
12
10
  gem "little_ghost"
13
11
  ```
14
12
 
15
- Install the bundle and set a provider credential:
16
-
17
13
  ```sh
18
14
  $ bundle install
19
- $ export OPENAI_API_KEY="..."
15
+ $ export OPENROUTER_API_KEY="..."
20
16
  ```
21
17
 
22
- With `OPENAI_API_KEY` present, LittleGhost can connect the `openai` provider name used below. OpenRouter credentials work as well. Use application secret management outside a local shell, and do not commit provider credentials.
18
+ Use your application's secret manager outside a local shell, and never commit provider credentials.
19
+
20
+ 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](production.md).
23
21
 
24
- ## Give the agent a tool
22
+ ## See your first answer
25
23
 
26
- Start by requiring LittleGhost and defining a narrow help center lookup:
24
+ Create `customer_support_agent.rb`:
27
25
 
28
26
  ```ruby
29
27
  require "little_ghost"
30
28
 
29
+ class CustomerSupportAgent < LittleGhost::Agent
30
+ model "openrouter:openai/gpt-5.6-luna"
31
+ system_prompt "Answer customer questions clearly and concisely."
32
+ end
33
+
34
+ run = CustomerSupportAgent.ask("Can I change the address on my order?")
35
+
36
+ if run.completed?
37
+ puts run.response
38
+ else
39
+ warn "Support request ended as #{run.outcome}: #{run.error&.class}"
40
+ end
41
+ ```
42
+
43
+ Run the file and you have a working AI feature:
44
+
45
+ ```sh
46
+ $ ruby customer_support_agent.rb
47
+ ```
48
+
49
+ `CustomerSupportAgent.ask` creates a `LittleGhost::Run` for this request. When the work finishes, the Run holds the outcome and response.
50
+
51
+ The inline prompt keeps this first example easy to see in one place. When the instructions grow, [Prompts as Views](prompt_views.md) moves them into a conventional ERB file without adding setup to the Agent.
52
+
53
+ 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.
54
+
55
+ ## Connect the agent to your application
56
+
57
+ The first agent can answer general questions. A **tool** gives it a focused operation backed by your Ruby code:
58
+
59
+ ```ruby
31
60
  class HelpCenterLookupTool < LittleGhost::Tool
32
61
  HELP_CENTER_ENTRIES = {
33
62
  "refunds" => "Refunds are available within 30 days of purchase.",
@@ -50,61 +79,86 @@ class HelpCenterLookupTool < LittleGhost::Tool
50
79
  end
51
80
  ```
52
81
 
53
- A tool gives the model one application operation with a name, description, and validated JSON input. LittleGhost validates the input before calling `#call` and turns the returned value into model context.
54
-
55
- The schema checks shape, not authorization. A tool that reads customer data or performs an action must enforce the application's trust rules inside its implementation.
56
-
57
- ## Define the agent
58
-
59
- Now describe the agent's behavior and make the lookup available to it:
82
+ Make the tool available to the agent and tell the model when to use it:
60
83
 
61
84
  ```ruby
62
85
  class CustomerSupportAgent < LittleGhost::Agent
63
86
  description "Answers customer support questions."
64
- model "openai:gpt-5.6-luna"
87
+ model "openrouter:openai/gpt-5.6-luna"
65
88
  system_prompt <<~PROMPT
66
89
  Answer clearly and do not invent company guidance.
67
- Use the help center lookup tool before stating company guidance.
90
+ Check the help center before stating company guidance.
68
91
  PROMPT
69
-
70
92
  tools HelpCenterLookupTool
71
93
  end
94
+
95
+ run = CustomerSupportAgent.ask(
96
+ "I bought an item two weeks ago. Can I get a refund?"
97
+ )
98
+
99
+ run.response
100
+ # One possible response:
101
+ # Refunds are available within 30 days, so your purchase is eligible.
72
102
  ```
73
103
 
74
- An agent class is a reusable behavior definition. It owns its prompt, tools, model selection, limits, and other capabilities. This agent names an OpenAI connection and model directly, so the first example does not need a separate model profile.
104
+ LittleGhost checks the model's arguments before it calls `HelpCenterLookupTool#call`. The schema checks shape, not permission. If a tool reads customer data or changes something, authorize that work from trusted application context. The tool's result then becomes context for the model.
105
+
106
+ ### Use trusted context for private data
75
107
 
76
- ## Ask a question
108
+ Model tool arguments are untrusted, even after their shape has been checked. Pass identity and permissions from your application's authentication boundary instead.
77
109
 
78
- Call the agent class to run one request to completion:
110
+ While an Agent is working, LittleGhost binds each Tool instance to the current Run. The Tool can read trusted request values through its `run` accessor:
79
111
 
80
112
  ```ruby
81
- run = CustomerSupportAgent.ask(
82
- "I bought an item two weeks ago. Can I get a refund?"
83
- )
113
+ class OrderStatusTool < LittleGhost::Tool
114
+ ORDER_STATUSES = {
115
+ ["user-7", "account-2", "481"] => "out for delivery"
116
+ }.freeze
84
117
 
85
- if run.completed?
86
- puts run.response
87
- else
88
- warn "Support request ended as #{run.outcome}: #{run.error&.class}"
89
- end
90
- ```
118
+ description "Look up an order that belongs to the current customer."
119
+ input_schema(
120
+ type: "object",
121
+ properties: {order_number: {type: "string"}},
122
+ required: ["order_number"],
123
+ additionalProperties: false
124
+ )
91
125
 
92
- `CustomerSupportAgent.ask` creates and consumes a `LittleGhost::Run`. A successful run exposes its final text through `#response`; it also retains the normalized result, outcome, usage, messages, and any terminal error.
126
+ def call(input)
127
+ lookup = [
128
+ run.invocation.actor_id,
129
+ run.invocation.context.fetch("account_id"),
130
+ input.fetch("order_number")
131
+ ]
132
+
133
+ ORDER_STATUSES.fetch(lookup) do
134
+ raise LittleGhost::ToolError, "Order not found"
135
+ end
136
+ end
137
+ end
93
138
 
94
- The model can call `HelpCenterLookupTool` with `{"topic":"refunds"}` and answer along these lines:
139
+ class CustomerSupportAgent < LittleGhost::Agent
140
+ tools HelpCenterLookupTool, OrderStatusTool
141
+ end
95
142
 
96
- ```text
97
- Refunds are available within 30 days, so a purchase from two weeks ago is eligible.
143
+ run = CustomerSupportAgent.ask(
144
+ "Where is order 481?",
145
+ actor_id: "user-7",
146
+ context: {account_id: "account-2"}
147
+ )
98
148
  ```
99
149
 
100
- Model wording and tool selection are not deterministic. The prompt directs the agent to ground company guidance in the validated lookup; applications that must enforce a lookup should put that ordering in a workflow.
150
+ 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. The model cannot replace them through its tool arguments.
151
+
152
+ That is enough to authorize the first Tool safely. [Core Concepts](core_concepts.md) names the request and working-state objects behind `run`, and [Running in Production](production.md) explains what changes when you add saved conversations.
101
153
 
102
154
  ## Stream the same agent
103
155
 
104
- Use `.stream_ask` when a console, HTTP response, or user interface should receive progress while the run is active:
156
+ Use `.stream_ask` when a console, HTTP response, or user interface should receive progress as it happens:
105
157
 
106
158
  ```ruby
107
- CustomerSupportAgent.stream_ask("Can I get a refund?").each do |event|
159
+ stream = CustomerSupportAgent.stream_ask("Can I get a refund?")
160
+
161
+ run = stream.each do |event|
108
162
  case event.type
109
163
  when :text_delta
110
164
  print event.data.fetch(:text)
@@ -112,53 +166,30 @@ CustomerSupportAgent.stream_ask("Can I get a refund?").each do |event|
112
166
  warn event.data.fetch(:message)
113
167
  end
114
168
  end
115
- ```
116
-
117
- The stream yields `LittleGhost::StreamEvent` objects. Text, tool activity, usage, traces, and terminal lifecycle facts share this interface, so callers do not need provider-specific response handling.
118
-
119
- Both calls use the same agent definition and active LittleGhost configuration. Core Concepts explains reusable runtimes when an application needs more control over shared services.
120
-
121
- ## Add a model role when the application grows
122
-
123
- Direct targets keep a small application easy to read. A larger application can give the same selection a stable role, then change the underlying provider, model, and defaults without editing each agent class:
124
-
125
- ```ruby
126
- LittleGhost.configure do |config|
127
- config.providers = {
128
- openai: {adapter: :openai, api_key: ENV.fetch("OPENAI_API_KEY")}
129
- }
130
- config.models = {
131
- customer_support: {
132
- target: "openai:gpt-5.6-luna",
133
- settings: {temperature: 0.2}
134
- }
135
- }
136
- config.default_model = :customer_support
137
- end
138
169
 
139
- class CustomerSupportAgent < LittleGhost::Agent
140
- model :customer_support
141
- end
170
+ puts "\n#{run.response}" if run.completed?
171
+ warn run.error.class.name if run.failed?
142
172
  ```
143
173
 
144
- An agent may also keep a small amount of model-specific configuration beside its behavior:
174
+ 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.
145
175
 
146
- ```ruby
147
- class DeliberateSupportAgent < LittleGhost::Agent
148
- model(
149
- provider: "openai",
150
- model: "gpt-5.6-luna",
151
- reasoning_effort: "high"
152
- )
153
- end
154
- ```
155
-
156
- Here, `provider` names a configured connection and every other key after `model` is a trusted model setting. Provider connections and model profiles may instead come from independent YAML files under `config/little_ghost`, or from paths selected in `LittleGhost.configure`. Inline declarations take precedence over explicit paths, which take precedence over conventional files; environment-based selection and the built-in default are the final fallback. `LittleGhost::Agent` and `LittleGhost::Configuration` document the complete shapes and precedence.
176
+ ## Give the code a home
157
177
 
158
- ## Fit LittleGhost into your application
178
+ LittleGhost does not require an application layout. Keep definitions beside related application code, or use these optional conventions:
159
179
 
160
- LittleGhost does not require an application layout. Keep agents and tools beside related application code when your framework or loader already has a home for them. If you want LittleGhost to eager-load these definitions, `app/agents` and `app/tools` are available conventions, and every lookup path is configurable.
180
+ ```text
181
+ app/
182
+ ├── agents/
183
+ │ └── customer_support_agent.rb
184
+ ├── assemblies/
185
+ │ └── response_workflow.rb
186
+ ├── prompts/
187
+ │ └── customer_support/
188
+ │ └── system.erb
189
+ └── tools/
190
+ └── help_center_lookup_tool.rb
191
+ ```
161
192
 
162
- Hosting remains the surrounding application's responsibility. A Rails controller, Rack endpoint, background job, CLI, or another Ruby entrypoint can call the same agent APIs shown above.
193
+ You now have the smallest useful LittleGhost application: one Agent, one Tool, and one familiar Ruby call.
163
194
 
164
- Read [Core Concepts](core_concepts.md) next for model selection, reusable runtimes, subagents, workflows, sessions, and the boundaries between them. The API reference covers exact signatures for `LittleGhost::Agent`, `LittleGhost::Tool`, `LittleGhost::Configuration`, and `LittleGhost::Run`.
195
+ 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](core_concepts.md) next and grow this Agent into a larger system.
@@ -0,0 +1,187 @@
1
+ # Running in Production
2
+
3
+ The Agent or Assembly you ran in a script can move into a controller, job, CLI, or service without changing shape. A long-running application usually adds stable model names, shared services, conversation history, background execution, and observability.
4
+
5
+ ## Select models by application role
6
+
7
+ A direct target keeps a small definition self-contained:
8
+
9
+ ```ruby
10
+ class CustomerSupportAgent < LittleGhost::Agent
11
+ model "openrouter:openai/gpt-5.6-luna"
12
+ end
13
+ ```
14
+
15
+ As an application grows, a **model role** gives that choice a stable application name:
16
+
17
+ ```ruby
18
+ # config/initializers/little_ghost.rb
19
+ LittleGhost.configure do |config|
20
+ config.providers = {
21
+ openrouter: {
22
+ adapter: :openrouter,
23
+ api_key: ENV.fetch("OPENROUTER_API_KEY")
24
+ }
25
+ }
26
+ config.models = {
27
+ customer_support: {
28
+ target: "openrouter:openai/gpt-5.6-luna",
29
+ settings: {temperature: 0.2}
30
+ }
31
+ }
32
+ config.default_model = :customer_support
33
+ end
34
+
35
+ class CustomerSupportAgent < LittleGhost::Agent
36
+ model :customer_support
37
+ end
38
+ ```
39
+
40
+ Provider connections and model roles can also live in YAML files under `config/little_ghost`, or in files you select explicitly. Values set in Ruby take priority. An explicitly selected file comes next, followed by conventional files and environment defaults. See `LittleGhost::Configuration` when you need every supported source and override.
41
+
42
+ Prompts, caller input and history, tool results, and attachments may leave the application for the selected external provider. Select providers from trusted configuration and account for their retention and data-residency policies.
43
+
44
+ ## Configure once, call from anywhere
45
+
46
+ The `LittleGhost.configure` block above is the entire initializer. Controllers and jobs can call your Agent and Assembly classes directly.
47
+
48
+ Then call the Agent directly from a controller or job:
49
+
50
+ ```ruby
51
+ class SupportQuestionsController < ApplicationController
52
+ def create
53
+ run = CustomerSupportAgent.ask(
54
+ params.require(:question),
55
+ actor_id: current_user.id,
56
+ context: {account_id: current_user.account_id}
57
+ )
58
+
59
+ if run.completed?
60
+ render json: {answer: run.response}
61
+ else
62
+ render json: {error: "Support request failed"}, status: :bad_gateway
63
+ end
64
+ end
65
+ end
66
+ ```
67
+
68
+ On the first class-level call, LittleGhost prepares model resolution, loading, prompt lookup, persistence, hooks, and factories. Later calls reuse those application services automatically.
69
+
70
+ Each `.ask` creates a fresh top-level Run with fresh bound participants and Tools. Reusing application services does not create conversation history. Pass a stable `session_id` only when a later request should continue an earlier conversation.
71
+
72
+ The controller supplies identity and account access from authenticated application state. The model cannot replace those values through its prompt or tool arguments. A background job uses the same direct calling style.
73
+
74
+ Configure LittleGhost before the first Agent or Assembly call. Once application services start successfully, the configuration is locked so every request sees one stable setup.
75
+
76
+ ## Preserve conversation with Sessions
77
+
78
+ A **Session** lets one request continue an earlier conversation. Pass the same session ID and trusted actor ID with each related call:
79
+
80
+ ```ruby
81
+ run = CustomerSupportAgent.ask(
82
+ "What did we decide about my refund?",
83
+ session_id: "conversation-42",
84
+ actor_id: authenticated_user.id
85
+ )
86
+ ```
87
+
88
+ Take `actor_id` from authenticated application state. A session ID alone does not prove who the caller is, and a nil actor does not separate tenants. Built-in persistence drops system messages, temporary messages, and private reasoning. If you customize persistence, decide what else is safe to store.
89
+
90
+ A session is checkpointed when its store write succeeds. The in-memory store lasts only as long as one process. Choose a durable `SessionStore` when conversations must survive a restart or continue on another process.
91
+
92
+ {LittleGhost::SessionStores::Filesystem}[rdoc-ref:LittleGhost::SessionStores::Filesystem] is a built-in durable choice for a trusted local or shared filesystem. Set its root to the application-managed directory that holds session data:
93
+
94
+ ```ruby
95
+ LittleGhost.configure do |config|
96
+ config.session_store = {
97
+ provider: LittleGhost::SessionStores::Filesystem,
98
+ root: "/var/lib/customer_support/sessions"
99
+ }
100
+ end
101
+ ```
102
+
103
+ Every Run has a session ID so LittleGhost can checkpoint its progress. If you do not supply one, LittleGhost generates a new ID for that call. Because your application does not reuse that generated ID, it does not create conversation continuity. A persistent SessionStore may still save working state under it before the Run finishes, so keep request context safe to store or filter sensitive fields in your store.
104
+
105
+ ## Stream or supervise long-running work
106
+
107
+ `.stream_ask` runs on the caller's thread and yields `StreamEvent` values as the answer arrives:
108
+
109
+ ```ruby
110
+ stream = CustomerSupportAgent.stream_ask(question)
111
+
112
+ run = stream.each do |event|
113
+ publish(event) if event.type == :text_delta
114
+ end
115
+
116
+ record_outcome(run.outcome, error_type: run.error&.class&.name)
117
+ ```
118
+
119
+ Use `start_execution` when the caller must stay free for other work, or when you want to deliver an interjection to an active response:
120
+
121
+ ```ruby
122
+ execution = agent.start_execution(message: question) do |event|
123
+ event_buffer << event
124
+ end
125
+
126
+ execution.interject(message: "Include the latest ledger entry")
127
+ execution.wait(deadline: Time.now + 30)
128
+ execution.run.completed?
129
+ ```
130
+
131
+ The event block runs on the worker thread, so keep it quick. Cancellation, deadlines, and `close` ask the work to stop; they cannot forcibly end arbitrary provider or tool code. They also cannot undo actions that already happened.
132
+
133
+ ## Treat tools as application boundaries
134
+
135
+ A tool schema checks the shape of model-supplied input. Your application still owns permission checks, safe retries, rate limits, tenant boundaries, and auditing.
136
+
137
+ Use the Tool binding's `run` to read current, application-established values from `run.invocation.context`. Do not make permission decisions from model arguments.
138
+
139
+ Treat `RunContext#state` as mutable working and Session state. Revalidate anything restored from an earlier request. Synchronize access when parallel Tools share mutable state, or mark every Tool that reads or changes it as `exclusive true`.
140
+
141
+ A `ToolError` message is visible to the model, so keep it safe to share. LittleGhost hides unexpected exception messages from model-facing results.
142
+
143
+ When a step retries, its tool calls may happen again too. Prefer read-only work, idempotency keys, or operations that are safe to repeat.
144
+
145
+ ## Choose workspace and sandbox behavior explicitly
146
+
147
+ A Workspace gives one Run a place for files. A Sandbox decides how filesystem and process operations happen there.
148
+
149
+ `LittleGhost::UnrestrictedSandbox` uses the host machine with the Ruby process's permissions. It does not contain untrusted code. Expose only the tools the model needs, and use a real isolation boundary when untrusted code must run.
150
+
151
+ The Run closes workspaces and sandboxes that LittleGhost creates for it. If your application passes an existing instance instead, your application keeps ownership and must close it when its own lifecycle ends.
152
+
153
+ ## Instrument without leaking the application
154
+
155
+ LittleGhost emits events as a request starts, calls a model or tool, moves between assembly steps, retries, and finishes. Instrumentation subscribers and OpenTelemetry exporters can send those events to your monitoring system.
156
+
157
+ An external telemetry service may receive application identifiers and event data. Redact sensitive values before they leave your boundary. Avoid attributes with many unique values, such as raw order or request IDs. Replacing one identifier does not make the rest of the data anonymous.
158
+
159
+ A composite `RunResult` includes short step summaries and trajectory queries. Keep detailed provider errors and sensitive diagnostics in trusted monitoring channels, not in model or user responses.
160
+
161
+ ## Keep ownership and failure visible
162
+
163
+ One top-level Run owns the workspace and sandbox that LittleGhost creates for it, plus application resources registered with `run.register`. It closes those resources after success, failure, a partial response, or cancellation. Existing workspace or sandbox instances passed by the application remain caller-owned.
164
+
165
+ Ordinary execution failures appear on the Run and its final event. Cleanup, event delivery, or instrumentation can still raise an exception: once those boundaries fail, LittleGhost cannot promise a clean ending.
166
+
167
+ ## Advanced: work with Runtime directly
168
+
169
+ A Runtime is the internal home for shared model resolution, loading, persistence, hooks, and resource factories. Most applications never need to handle it: `LittleGhost.configure` and class-level `.ask` are enough.
170
+
171
+ Use `LittleGhost.runtime` when an extension needs the shared object itself. Construct a separate Runtime only when one process deliberately hosts an isolated LittleGhost setup:
172
+
173
+ ```ruby
174
+ configuration = LittleGhost::Configuration.new(root: isolated_root)
175
+ runtime = LittleGhost::Runtime.new(configuration: configuration)
176
+ agent = CustomerSupportAgent.new(runtime: runtime)
177
+ ```
178
+
179
+ An explicit Runtime is an independent configuration snapshot. It does not replace LittleGhost's shared default.
180
+
181
+ One Runtime can serve independent calls from several threads. Each call gets its own Run, participants, Tools, and Runtime-created workspace and sandbox. An Agent or Assembly already bound to an active Run must stay with that Run.
182
+
183
+ Within one SessionStore instance, LittleGhost serializes calls sharing a Session. Multi-process deployments need coordination from their store. Custom stores, identity and credential resolvers, model resolvers, hooks, instrumentation subscribers, providers, and resource factories may receive concurrent calls and must be thread-safe.
184
+
185
+ Runtime has no shutdown step. Shared services supplied by the application keep their own lifecycle. Shut those services down with the rest of your application. If you installed process-wide instrumentation subscribers, flush or shut down `LittleGhost::Instrumentation` during application shutdown.
186
+
187
+ For exact constructors, options, events, extension contracts, and error behavior, continue into the API reference for `LittleGhost::Configuration`, `LittleGhost::Runtime`, `LittleGhost::Run`, `LittleGhost::Execution`, `LittleGhost::Session`, `LittleGhost::Tool`, and `LittleGhost::StreamEvent`.
@@ -0,0 +1,132 @@
1
+ # Prompts as Views
2
+
3
+ A short prompt fits nicely inside an Agent class. As the instructions grow, move them into a **prompt view**: an ERB file that LittleGhost finds and renders for the Agent.
4
+
5
+ This keeps the Agent easy to scan. It also gives shared instructions and application values a natural home.
6
+
7
+ ## Start with the inline prompt
8
+
9
+ The Agent from Getting Started keeps its first instruction close to the model:
10
+
11
+ ```ruby
12
+ class CustomerSupportAgent < LittleGhost::Agent
13
+ model "openrouter:openai/gpt-5.6-luna"
14
+ system_prompt "Answer customer questions clearly and concisely."
15
+ end
16
+ ```
17
+
18
+ Inline prompts are a good fit while the whole instruction is one thought.
19
+
20
+ ## Move a growing prompt into a view
21
+
22
+ Remove `system_prompt` from the class:
23
+
24
+ ```ruby
25
+ class CustomerSupportAgent < LittleGhost::Agent
26
+ model "openrouter:openai/gpt-5.6-luna"
27
+ tools HelpCenterLookupTool, OrderStatusTool
28
+ end
29
+ ```
30
+
31
+ Then create `app/prompts/customer_support/system.erb`:
32
+
33
+ ```erb
34
+ You help customers understand their orders and account.
35
+
36
+ Answer clearly and concisely.
37
+ Never invent company guidance. Check the help center when policy matters.
38
+ Use the order status tool before making a claim about a private order.
39
+ ```
40
+
41
+ That is enough. `CustomerSupportAgent` becomes `customer_support`, so LittleGhost looks for `customer_support/system.erb` under `app/prompts`.
42
+
43
+ The prompt is still a system instruction sent to the selected model provider. Keeping it in a view improves organization; it does not keep the content inside your process.
44
+
45
+ ## Give the view application values
46
+
47
+ Use `prompt_local` for a value the application owns:
48
+
49
+ ```ruby
50
+ class CustomerSupportAgent < LittleGhost::Agent
51
+ prompt_local :company_name, "Northstar"
52
+ end
53
+ ```
54
+
55
+ The local is available by name in the view:
56
+
57
+ ```erb
58
+ You are a customer support agent for <%= company_name %>.
59
+ Answer clearly and concisely.
60
+ ```
61
+
62
+ A block can resolve a trusted value for each Agent instance. Add it to the Agent class too:
63
+
64
+ ```ruby
65
+ class CustomerSupportAgent < LittleGhost::Agent
66
+ prompt_local(:policy_version) { SupportPolicy.current_version }
67
+ end
68
+ ```
69
+
70
+ Prompt views also receive `invocation`, `run`, and `agent`. Reach for those when the instruction truly depends on the current request. Keep user wording in the caller message unless you deliberately want it inside the system instruction.
71
+
72
+ Every rendered value may be sent to the model provider. Pass only data that belongs in the prompt.
73
+
74
+ ## Share a small partial
75
+
76
+ Partials keep repeated instructions in one place. Create `app/prompts/shared/_voice.erb`:
77
+
78
+ ```erb
79
+ Use a warm, direct voice for <%= company_name %>.
80
+ Prefer one clear next step over a long list of possibilities.
81
+ ```
82
+
83
+ Render it from the Agent's system view:
84
+
85
+ ```erb
86
+ You are a customer support agent for <%= company_name %>.
87
+
88
+ <%= partial "shared/voice", locals: {company_name: company_name} %>
89
+ ```
90
+
91
+ The underscore marks a partial. Its locals are explicit, so it does not quietly inherit everything available to the parent view.
92
+
93
+ ## Override the convention when it helps
94
+
95
+ Most named Agents can rely on their conventional path. Use `system_template` when a class should read a differently named view:
96
+
97
+ ```ruby
98
+ class BillingSupportAgent < LittleGhost::Agent
99
+ system_template "customer_support/billing"
100
+ end
101
+ ```
102
+
103
+ LittleGhost chooses one prompt source in this order:
104
+
105
+ 1. An inline `system_prompt`
106
+ 2. An explicit `system_template`
107
+ 3. The Agent's conventional `system.erb` view
108
+
109
+ Applications can add prompt lookup roots through `Configuration#prompt_paths`. Earlier roots win, which is useful when one trusted application layer overrides a shared prompt package.
110
+
111
+ ## Treat views as application code
112
+
113
+ Prompt views run as ERB inside the Ruby process. They can call Ruby, so keep every prompt directory application-controlled and non-user-writable. Never turn a request or model-supplied path into a prompt root.
114
+
115
+ `TrustedPath` exists for the uncommon case where trusted application code selects a request-specific root. It records a trust decision; it does not make an untrusted directory safe.
116
+
117
+ ## Keep request composition separate
118
+
119
+ A prompt view defines reusable instructions for one Agent. A Workflow may still build request-specific input for that Agent:
120
+
121
+ ```ruby
122
+ invoke CustomerSupportAgent, input: <<~MESSAGE
123
+ #{input.text}
124
+
125
+ Verified research:
126
+ #{research}
127
+ MESSAGE
128
+ ```
129
+
130
+ The Workflow is composing this request. `CustomerSupportAgent` still receives its own system prompt view when it runs.
131
+
132
+ Continue with [Running in Production](production.md) to configure model roles, preserve sessions, supervise execution, and connect observability.
@@ -142,10 +142,10 @@ module LittleGhost
142
142
  "little_ghost.model_retry",
143
143
  source.data.merge(superseded_message_id:).compact
144
144
  )
145
- when :agent_interrupt_delivered
145
+ when :agent_interjection_delivered
146
146
  output << custom(
147
- "little_ghost.agent_interrupt_delivered",
148
- source.data.slice(:interruption_ids, :batch_key).compact
147
+ "little_ghost.agent_interjection_delivered",
148
+ source.data.slice(:interjection_ids, :batch_key).compact
149
149
  )
150
150
  when :subagent
151
151
  output << custom("little_ghost.subagent", source.data.fetch(:event, source.data))