Class: LittleGhost::Runtime
- Inherits:
-
Object
- Object
- LittleGhost::Runtime
- Defined in:
- lib/little_ghost/runtime.rb,
lib/little_ghost/runtime/hook.rb
Overview
Owns the shared services that assemblies reuse across many Runs.
Most applications do not construct this class. Configure LittleGhost once
and call a named Agent or Assembly; the first standalone call lazily builds
LittleGhost.runtime, and later calls reuse it automatically. Each call
still receives a fresh Run, bound participants, Tools, workspace, and
sandbox.
Construct Runtime directly when one process intentionally hosts an isolated LittleGhost setup:
configuration = LittleGhost::Configuration.new(
root: Dir.pwd,
providers: {
openrouter: {adapter: :openrouter, api_key: ENV.fetch("OPENROUTER_API_KEY")}
},
models: {customer_support: {target: "openrouter:openai/gpt-5.6-luna"}},
default_model: :customer_support,
service_name: "support-api"
)
runtime = LittleGhost::Runtime.new(configuration: configuration)
CustomerSupportAgent.new(runtime: runtime)
.ask("Where is order 481?")
.response
Explicit construction snapshots the supplied Configuration but does not replace LittleGhost's shared default Runtime.
A Runtime may build independent Runs concurrently. Each Run gets its own bound participants and Tools. By default, Runtime also creates a workspace and sandbox owned by that Run. Existing workspace or sandbox instances passed by the application remain caller-owned. Other supplied objects may receive calls from several threads, so custom stores, resolvers, hooks, subscribers, providers, and resource factories must be thread-safe. One SessionStore instance serializes calls sharing a Session; multi-process deployments need coordination provided by their store.
Advanced construction and ownership
Normal construction reads the application's configured definitions and
builds shared model resolution, persistence, hooks, and resource factories.
The settings form and #build are lower-level extension points for deriving
another Runtime from an existing configuration snapshot.
#build_run creates a workspace and sandbox when needed. Once the Run owns them, it closes them; if construction stops halfway through, Runtime closes the partial resources. Startup failures are reported to instrumentation and then raised. Session actor resolution must use authenticated application identity. The default UnrestrictedSandbox uses host permissions and is not a security boundary for untrusted work.
Runtime has no shutdown operation. Runs close resources created for their request. The application shuts down shared services and process-wide Instrumentation subscribers with the rest of the process.
Defined Under Namespace
Classes: Hook
Instance Attribute Summary collapse
-
#configuration ⇒ Object
readonly
Configuration object used to construct this Runtime.
-
#loader ⇒ Object
readonly
Loader used for conventional application definitions.
-
#model_resolver ⇒ Object
readonly
Resolver that turns model roles and targets into executable Models.
-
#prompt_paths ⇒ Object
readonly
Ordered directories searched for prompt templates.
-
#root ⇒ Object
readonly
Canonical application root.
-
#runtime_hooks ⇒ Object
readonly
Runtime hooks called around request and session preparation.
-
#sandbox_class ⇒ Object
readonly
Configured Sandbox implementation, or
nilfor the default. -
#session_store ⇒ Object
readonly
Shared store used to open per-Run Sessions.
-
#settings ⇒ Object
readonly
Settings snapshot used by new Runs.
-
#skill_paths ⇒ Object
readonly
Ordered directories searched for skill definitions.
-
#skill_resource_root ⇒ Object
readonly
Root used for skill-owned resources, when configured.
-
#workspace_class ⇒ Object
readonly
Configured Workspace implementation, or
nilfor the default.
Instance Method Summary collapse
-
#build(**overrides) ⇒ Object
Creates a sibling runtime with explicit setting overrides.
-
#build_agent(agent_class_or_name, run:, model: nil, tools: [], agent_path: Subagents::AgentPath::ROOT) ⇒ Object
:nodoc:.
-
#build_assembly(assembly_class_or_name, run:, **options) ⇒ Object
Builds an Agent through AgentFactory or constructs another Assembly for
run. -
#build_run(payload, agent_class: nil, assembly_class: nil, entrypoint_class: nil, execution_class: nil, cancellation_token: Support::CancellationToken.new, workspace: nil, sandbox: nil) ⇒ Object
Creates a Run that owns any workspace and sandbox built for the request.
-
#build_sandbox(workspace:) ⇒ Object
Instantiates the configured sandbox around
workspace, or an unrestricted sandbox by default. -
#build_workspace ⇒ Object
Instantiates the configured workspace, or a root-scoped Workspace by default.
-
#default_error_message(error, _run) ⇒ Object
:nodoc:.
-
#error_message(error, run) ⇒ Object
:nodoc:.
-
#initialize(configuration:, settings: nil) ⇒ Runtime
constructor
Starts a runtime from
configurationor an existing settings snapshot. -
#model_for(agent_class, run) ⇒ Object
:nodoc:.
-
#open_session(run) ⇒ Object
:nodoc:.
-
#open_subagent_session(run, conversation_id) ⇒ Object
:nodoc:.
-
#parse(payload) ⇒ Object
Coerces an application payload into the configured Invocation class.
-
#prepare_interjection(run, payload) ⇒ Object
:nodoc:.
-
#prepare_run(run) ⇒ Object
:nodoc:.
-
#resolve_agent(value) ⇒ Object
:nodoc:.
-
#service_name ⇒ Object
The stable service name attached to runtime telemetry.
-
#session_actor_for(invocation) ⇒ Object
:nodoc:.
-
#session_history(run, session, fallback:) ⇒ Object
:nodoc:.
-
#template_locals(run:, agent:) ⇒ Object
:nodoc:.
Constructor Details
#initialize(configuration:, settings: nil) ⇒ Runtime
Starts a runtime from configuration or an existing settings snapshot.
89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 |
# File 'lib/little_ghost/runtime.rb', line 89 def initialize(configuration:, settings: nil) @startup_started_at = monotonic_time @startup_phase = "configuration" @startup_reported = false begin raise ArgumentError, "configuration must be a LittleGhost::Configuration" unless configuration.is_a?(Configuration) @configuration = configuration if settings @settings = settings else bootstrap_root = canonical_application_root(configuration.root) configuration.load_file!(root: bootstrap_root) @settings = configuration.settings(root: bootstrap_root) end report_startup(status: "starting") @startup_reported = true @root = canonical_application_root(@settings.fetch(:root)) @skill_resource_root = @settings[:skill_resource_root] @workspace_class = @settings.fetch(:workspace) @sandbox_class = @settings.fetch(:sandbox) @runtime_hooks = build_runtime_hooks(@settings[:runtime_hooks]) @startup_phase = "instrumentation" subscribe_instrumentation(@settings[:instrumentation_subscribers]) emit_startup(:runtime_start) @startup_phase = "loader" @loader = @settings[:loader] || Support::Loader.new(root: @root) loader.setup loader.eager_load @startup_phase = "model_resolver" @invocation_class = @settings[:invocation] || Invocation @model_resolver = @settings.fetch(:model_resolver) @default_model = @settings.fetch(:default_model, "default").to_s @startup_phase = "session_store" @session_store = build_session_store(@settings[:session_store]) @session_actor = @settings[:session_actor] @startup_phase = "prompts" @prompt_paths = build_lookup_paths(:prompt_paths) @skill_paths = build_lookup_paths(:skill_paths) @startup_phase = "agent_factory" @agent_factory = AgentFactory.new( runtime: self, prompt_paths: @prompt_paths, resolve_agent: method(:resolve_agent_class) ) @startup_phase = "complete" emit_startup(:runtime_stop, outcome: "ready") report_startup(status: "ready") rescue => error unless @startup_reported report_startup(status: "starting") @startup_reported = true end emit_startup(:runtime_stop, outcome: "failed", error:) Instrumentation.flush report_startup(status: "failed", error:) raise end end |
Instance Attribute Details
#configuration ⇒ Object (readonly)
Configuration object used to construct this Runtime.
64 65 66 |
# File 'lib/little_ghost/runtime.rb', line 64 def configuration @configuration end |
#loader ⇒ Object (readonly)
Loader used for conventional application definitions.
70 71 72 |
# File 'lib/little_ghost/runtime.rb', line 70 def loader @loader end |
#model_resolver ⇒ Object (readonly)
Resolver that turns model roles and targets into executable Models.
78 79 80 |
# File 'lib/little_ghost/runtime.rb', line 78 def model_resolver @model_resolver end |
#prompt_paths ⇒ Object (readonly)
Ordered directories searched for prompt templates.
72 73 74 |
# File 'lib/little_ghost/runtime.rb', line 72 def prompt_paths @prompt_paths end |
#root ⇒ Object (readonly)
Canonical application root.
68 69 70 |
# File 'lib/little_ghost/runtime.rb', line 68 def root @root end |
#runtime_hooks ⇒ Object (readonly)
Runtime hooks called around request and session preparation.
86 87 88 |
# File 'lib/little_ghost/runtime.rb', line 86 def runtime_hooks @runtime_hooks end |
#sandbox_class ⇒ Object (readonly)
Configured Sandbox implementation, or nil for the default.
84 85 86 |
# File 'lib/little_ghost/runtime.rb', line 84 def sandbox_class @sandbox_class end |
#session_store ⇒ Object (readonly)
Shared store used to open per-Run Sessions.
80 81 82 |
# File 'lib/little_ghost/runtime.rb', line 80 def session_store @session_store end |
#settings ⇒ Object (readonly)
Settings snapshot used by new Runs.
66 67 68 |
# File 'lib/little_ghost/runtime.rb', line 66 def settings @settings end |
#skill_paths ⇒ Object (readonly)
Ordered directories searched for skill definitions.
74 75 76 |
# File 'lib/little_ghost/runtime.rb', line 74 def skill_paths @skill_paths end |
#skill_resource_root ⇒ Object (readonly)
Root used for skill-owned resources, when configured.
76 77 78 |
# File 'lib/little_ghost/runtime.rb', line 76 def skill_resource_root @skill_resource_root end |
#workspace_class ⇒ Object (readonly)
Configured Workspace implementation, or nil for the default.
82 83 84 |
# File 'lib/little_ghost/runtime.rb', line 82 def workspace_class @workspace_class end |
Instance Method Details
#build(**overrides) ⇒ Object
Creates a sibling runtime with explicit setting overrides.
158 159 160 161 162 163 164 165 166 |
# File 'lib/little_ghost/runtime.rb', line 158 def build(**overrides) values = @settings.merge(overrides) values[:root] = canonical_application_root(values.fetch(:root)) values[:loader] = loader unless overrides.key?(:loader) || overrides.key?(:root) self.class.new( configuration:, settings: values ) end |
#build_agent(agent_class_or_name, run:, model: nil, tools: [], agent_path: Subagents::AgentPath::ROOT) ⇒ Object
:nodoc:
229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 |
# File 'lib/little_ghost/runtime.rb', line 229 def build_agent( agent_class_or_name, run:, model: nil, tools: [], agent_path: Subagents::AgentPath::ROOT ) agent_class_or_name = agent_class_or_name.definition if agent_class_or_name.is_a?(AgentBuilder) if agent_class_or_name.is_a?(AssemblyDefinition) unless agent_class_or_name.kind == :agent raise ConfigurationError, "agent definition must have kind :agent" end agent_class_or_name = agent_class_or_name.implementation end @agent_factory.build(agent_class_or_name, run:, model:, tools:, agent_path:) end |
#build_assembly(assembly_class_or_name, run:, **options) ⇒ Object
Builds an Agent through AgentFactory or constructs another Assembly for run.
247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 |
# File 'lib/little_ghost/runtime.rb', line 247 def build_assembly(assembly_class_or_name, run:, **) # :nodoc: if assembly_class_or_name.is_a?(AssemblyBuilder) assembly_class_or_name = assembly_class_or_name.definition end if assembly_class_or_name.is_a?(Class) && assembly_class_or_name <= Assembly assembly_class_or_name = assembly_class_or_name.definition end if assembly_class_or_name.is_a?(AssemblyDefinition) return build_agent(assembly_class_or_name, run:, **) if assembly_class_or_name.kind == :agent raise ArgumentError, "composite assembly definitions do not accept agent build options" unless .empty? return assembly_class_or_name.implementation.new(run:, runtime: self) end if !assembly_class_or_name.is_a?(Class) || assembly_class_or_name <= Agent return build_agent(assembly_class_or_name, run:, **) end unless assembly_class_or_name <= Assembly raise ConfigurationError, "assembly must inherit from LittleGhost::Assembly" end unless .empty? raise ArgumentError, "composite assemblies do not accept agent build options" end assembly_class_or_name.new(run:, runtime: self) end |
#build_run(payload, agent_class: nil, assembly_class: nil, entrypoint_class: nil, execution_class: nil, cancellation_token: Support::CancellationToken.new, workspace: nil, sandbox: nil) ⇒ Object
Creates a Run that owns any workspace and sandbox built for the request.
174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 |
# File 'lib/little_ghost/runtime.rb', line 174 def build_run( payload, agent_class: nil, assembly_class: nil, entrypoint_class: nil, execution_class: nil, cancellation_token: Support::CancellationToken.new, workspace: nil, sandbox: nil ) entrypoint_class ||= assembly_class || agent_class raise ArgumentError, "entrypoint_class is required" unless entrypoint_class execution_class ||= assembly_class || entrypoint_class agent_class ||= entrypoint_class if entrypoint_class <= Agent owned_resources = [] workspace ||= build_workspace.tap { |resource| owned_resources << resource } sandbox ||= build_sandbox(workspace:).tap { |resource| owned_resources << resource } run = Run.new( invocation: parse(payload), runtime: self, agent_class:, entrypoint_class:, execution_class:, cancellation_token:, workspace:, sandbox: ) owned_resources.each { |resource| run.register(resource) } prepare_run(run) rescue if run run.close else close_resources(owned_resources) end raise end |
#build_sandbox(workspace:) ⇒ Object
Instantiates the configured sandbox around workspace, or an unrestricted
sandbox by default.
222 223 224 225 226 |
# File 'lib/little_ghost/runtime.rb', line 222 def build_sandbox(workspace:) return sandbox_class.new(workspace:) if sandbox_class UnrestrictedSandbox.new(workspace:) end |
#build_workspace ⇒ Object
Instantiates the configured workspace, or a root-scoped Workspace by default.
214 215 216 217 218 |
# File 'lib/little_ghost/runtime.rb', line 214 def build_workspace return workspace_class.new if workspace_class Workspace.new(root: root) end |
#default_error_message(error, _run) ⇒ Object
:nodoc:
345 346 347 348 349 350 351 352 353 354 |
# File 'lib/little_ghost/runtime.rb', line 345 def (error, _run) # :nodoc: return error. if error.is_a?(UnsupportedInputError) return error. if error.is_a?(ToolLoopError) return "The model reached its output limit before completing a response. Please retry with a narrower request." if error.is_a?(OutputLimitError) if error.is_a?(MalformedToolCallError) return "The model returned an invalid tool call before completing the response. Please retry with a narrower request." end "Agent failed: #{error.class}" end |
#error_message(error, run) ⇒ Object
:nodoc:
336 337 338 339 340 341 342 343 |
# File 'lib/little_ghost/runtime.rb', line 336 def (error, run) # :nodoc: runtime_hooks.each do |hook| = hook.(error, run) return if end (error, run) end |
#model_for(agent_class, run) ⇒ Object
:nodoc:
278 279 280 281 |
# File 'lib/little_ghost/runtime.rb', line 278 def model_for(agent_class, run) # :nodoc: selection = agent_class.model_selection(run.invocation) || @default_model model_resolver.resolve(selection, invocation: run.invocation, context: run) end |
#open_session(run) ⇒ Object
:nodoc:
283 284 285 286 287 288 289 290 |
# File 'lib/little_ghost/runtime.rb', line 283 def open_session(run) # :nodoc: Session.new( id: run.invocation.session_id, actor_id: session_actor_for(run.invocation), store: session_store, operation_id: run.operation_id ) end |
#open_subagent_session(run, conversation_id) ⇒ Object
:nodoc:
313 314 315 316 317 318 319 320 321 322 323 324 325 326 |
# File 'lib/little_ghost/runtime.rb', line 313 def open_subagent_session(run, conversation_id) # :nodoc: parent_link = Subagents::Manager.parent_link(run.session) Session.new( id: Subagents::Manager.conversation_session_id(conversation_id), actor_id: session_actor_for(run.invocation), store: session_store, operation_id: run.operation_id, metadata: { "little_ghost_kind" => "subagent_conversation", "little_ghost_parent_link" => parent_link, "little_ghost_conversation_id" => conversation_id } ) end |
#parse(payload) ⇒ Object
Coerces an application payload into the configured Invocation class.
169 170 171 |
# File 'lib/little_ghost/runtime.rb', line 169 def parse(payload) payload.is_a?(@invocation_class) ? payload : @invocation_class.new(payload) end |
#prepare_interjection(run, payload) ⇒ Object
:nodoc:
307 308 309 310 311 |
# File 'lib/little_ghost/runtime.rb', line 307 def prepare_interjection(run, payload) # :nodoc: runtime_hooks.reduce(payload) do |prepared, hook| hook.prepare_interjection(run, prepared) end end |
#prepare_run(run) ⇒ Object
:nodoc:
302 303 304 305 |
# File 'lib/little_ghost/runtime.rb', line 302 def prepare_run(run) # :nodoc: runtime_hooks.each { |hook| hook.prepare_run(run) } run end |
#resolve_agent(value) ⇒ Object
:nodoc:
356 357 358 |
# File 'lib/little_ghost/runtime.rb', line 356 def resolve_agent(value) # :nodoc: resolve_agent_class(value) end |
#service_name ⇒ Object
The stable service name attached to runtime telemetry.
274 275 276 |
# File 'lib/little_ghost/runtime.rb', line 274 def service_name @settings&.[](:service_name) || default_service_name end |
#session_actor_for(invocation) ⇒ Object
:nodoc:
328 329 330 |
# File 'lib/little_ghost/runtime.rb', line 328 def session_actor_for(invocation) # :nodoc: @session_actor ? @session_actor.call(invocation) : invocation.actor_id end |
#session_history(run, session, fallback:) ⇒ Object
:nodoc:
292 293 294 295 296 297 298 299 300 |
# File 'lib/little_ghost/runtime.rb', line 292 def session_history(run, session, fallback:) # :nodoc: stored = session.history runtime_hooks.each do |hook| history = hook.session_history(run, stored:, fallback:) return normalize_history(history) unless history.nil? end session.history(fallback:) end |
#template_locals(run:, agent:) ⇒ Object
:nodoc:
332 333 334 |
# File 'lib/little_ghost/runtime.rb', line 332 def template_locals(run:, agent:) # :nodoc: {invocation: run.invocation, run:, agent:}.merge(agent.prompt_locals) end |