Class: LittleGhost::Runtime
- Inherits:
-
Object
- Object
- LittleGhost::Runtime
- Defined in:
- lib/little_ghost/runtime.rb,
lib/little_ghost/runtime/hook.rb,
lib/little_ghost/runtime/hooks/artifacts.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 fresh participants and Tools. By default, it also gets a Runtime-created Workspace and Sandbox that the Run owns. Instances supplied by the application remain caller-owned.
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 Sandboxes::Unrestricted uses host permissions and is not a security boundary for untrusted work.
Shared stores, resolvers, hooks, subscribers, providers, and resource factories may receive concurrent calls. Calls can overlap on different threads, or fibers can take turns entering the same object on one thread. Extensions must protect shared mutable state without relying on thread identity. One SessionStore instance serializes calls for the same Session. A store must provide its own coordination across processes.
See Running in Production for choosing a concurrency backend and protecting shared extensions.
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
Instance Attribute Summary collapse
-
#code_mode_configuration ⇒ Object
readonly
Default code-mode declaration for enabled agents.
-
#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_declaration ⇒ Object
readonly
Configured Sandbox provider symbol, callable, or declaration.
-
#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.
-
#task_runner ⇒ Object
readonly
:nodoc:.
-
#workspace_declaration ⇒ Object
readonly
Configured Workspace provider symbol, callable, or declaration.
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, agent_stream_path: []) ⇒ 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, include_agent_events_by_default: false) ⇒ Object
Creates a Run that owns any workspace and sandbox built for the request.
-
#build_sandbox(workspace:, invocation: nil) ⇒ Object
Instantiates the configured sandbox around
workspace, or an unrestricted sandbox by default. -
#build_workspace(invocation: nil) ⇒ Object
Instantiates the configured workspace, or a root-scoped Workspace by default.
-
#default_error_message(error, _run) ⇒ Object
:nodoc:.
-
#embed(**arguments) ⇒ Object
:call-seq: embed(model:, inputs:, settings: {}, limits: {}, cancellation_token: Support::CancellationToken.new, deadline: nil) -> Embeddings::Response.
-
#error_message(error, run) ⇒ Object
:nodoc:.
-
#generate(**arguments) ⇒ Object
:call-seq: generate(model:, messages:, result_schema: nil, settings: {}, structured_result_repair_attempts: 1, cancellation_token: Support::CancellationToken.new, deadline: nil) -> RunResult.
-
#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_execution(run) ⇒ Object
:nodoc:.
-
#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.
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 156 157 158 159 160 161 162 163 164 165 166 167 168 169 |
# File 'lib/little_ghost/runtime.rb', line 98 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 @task_runner = Support::TaskRunner.new( backend: @settings.fetch(:concurrency_backend, :auto) ) report_startup(status: "starting") @startup_reported = true @root = canonical_application_root(@settings.fetch(:root)) @skill_resource_root = @settings[:skill_resource_root] @workspace_declaration = @settings.fetch(:workspace) @sandbox_declaration = @settings.fetch(:sandbox) @code_mode_configuration = @settings[:code_mode] @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 @model_operations = ModelOperations.new(model_resolver:) @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
#code_mode_configuration ⇒ Object (readonly)
Default code-mode declaration for enabled agents.
92 93 94 |
# File 'lib/little_ghost/runtime.rb', line 92 def code_mode_configuration @code_mode_configuration end |
#configuration ⇒ Object (readonly)
Configuration object used to construct this Runtime.
70 71 72 |
# File 'lib/little_ghost/runtime.rb', line 70 def configuration @configuration end |
#loader ⇒ Object (readonly)
Loader used for conventional application definitions.
76 77 78 |
# File 'lib/little_ghost/runtime.rb', line 76 def loader @loader end |
#model_resolver ⇒ Object (readonly)
Resolver that turns model roles and targets into executable Models.
84 85 86 |
# File 'lib/little_ghost/runtime.rb', line 84 def model_resolver @model_resolver end |
#prompt_paths ⇒ Object (readonly)
Ordered directories searched for prompt templates.
78 79 80 |
# File 'lib/little_ghost/runtime.rb', line 78 def prompt_paths @prompt_paths end |
#root ⇒ Object (readonly)
Canonical application root.
74 75 76 |
# File 'lib/little_ghost/runtime.rb', line 74 def root @root end |
#runtime_hooks ⇒ Object (readonly)
Runtime hooks called around request and session preparation.
94 95 96 |
# File 'lib/little_ghost/runtime.rb', line 94 def runtime_hooks @runtime_hooks end |
#sandbox_declaration ⇒ Object (readonly)
Configured Sandbox provider symbol, callable, or declaration.
90 91 92 |
# File 'lib/little_ghost/runtime.rb', line 90 def sandbox_declaration @sandbox_declaration end |
#session_store ⇒ Object (readonly)
Shared store used to open per-Run Sessions.
86 87 88 |
# File 'lib/little_ghost/runtime.rb', line 86 def session_store @session_store end |
#settings ⇒ Object (readonly)
Settings snapshot used by new Runs.
72 73 74 |
# File 'lib/little_ghost/runtime.rb', line 72 def settings @settings end |
#skill_paths ⇒ Object (readonly)
Ordered directories searched for skill definitions.
80 81 82 |
# File 'lib/little_ghost/runtime.rb', line 80 def skill_paths @skill_paths end |
#skill_resource_root ⇒ Object (readonly)
Root used for skill-owned resources, when configured.
82 83 84 |
# File 'lib/little_ghost/runtime.rb', line 82 def skill_resource_root @skill_resource_root end |
#task_runner ⇒ Object (readonly)
:nodoc:
95 96 97 |
# File 'lib/little_ghost/runtime.rb', line 95 def task_runner @task_runner end |
#workspace_declaration ⇒ Object (readonly)
Configured Workspace provider symbol, callable, or declaration.
88 89 90 |
# File 'lib/little_ghost/runtime.rb', line 88 def workspace_declaration @workspace_declaration end |
Instance Method Details
#build(**overrides) ⇒ Object
Creates a sibling runtime with explicit setting overrides.
172 173 174 175 176 177 178 179 180 |
# File 'lib/little_ghost/runtime.rb', line 172 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, agent_stream_path: []) ⇒ Object
:nodoc:
293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 |
# File 'lib/little_ghost/runtime.rb', line 293 def build_agent( agent_class_or_name, run:, model: nil, tools: [], agent_path: Subagents::AgentPath::ROOT, agent_stream_path: [] ) 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:, agent_stream_path:) end |
#build_assembly(assembly_class_or_name, run:, **options) ⇒ Object
Builds an Agent through AgentFactory or constructs another Assembly for run.
312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 |
# File 'lib/little_ghost/runtime.rb', line 312 def build_assembly(assembly_class_or_name, run:, **) # :nodoc: agent_stream_path = .delete(:agent_stream_path) || [] 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:, agent_stream_path:, **) 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).bind_agent_stream_path(agent_stream_path) end if !assembly_class_or_name.is_a?(Class) || assembly_class_or_name <= Agent return build_agent(assembly_class_or_name, run:, agent_stream_path:, **) 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).bind_agent_stream_path(agent_stream_path) 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, include_agent_events_by_default: false) ⇒ Object
Creates a Run that owns any workspace and sandbox built for the request.
include_agent_events_by_default is trusted stream policy for the Run
returned by this build. It applies only when the Invocation omits
include_agent_events and must not be forwarded to auxiliary Runs built
while preparing the request.
215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 |
# File 'lib/little_ghost/runtime.rb', line 215 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, include_agent_events_by_default: false ) 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 = [] invocation = parse(payload) workspace ||= build_workspace(invocation:).tap { |resource| owned_resources << resource } sandbox ||= build_sandbox(workspace:, invocation:).tap { |resource| owned_resources << resource } run = Run.new( invocation:, runtime: self, agent_class:, entrypoint_class:, execution_class:, cancellation_token:, workspace:, sandbox:, include_agent_events_by_default: ) 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:, invocation: nil) ⇒ Object
Instantiates the configured sandbox around workspace, or an unrestricted
sandbox by default.
277 278 279 280 281 282 283 284 285 286 287 288 289 290 |
# File 'lib/little_ghost/runtime.rb', line 277 def build_sandbox(workspace:, invocation: nil) declaration = sandbox_declaration return Sandboxes::Unrestricted.new(workspace:) unless declaration provider, = component_provider(declaration) provider = Sandbox.resolve_provider(provider) if provider.is_a?(Symbol) = .slice(*Sandbox::Policy::COMMON_KEYS) .each_key { |key| .delete(key) } policy_value = .delete(:policy) if policy_value || !.empty? [:policy] = Sandbox::Policy.coerce(policy_value, **) end build_component(provider, , runtime: self, invocation:, workspace:) end |
#build_workspace(invocation: nil) ⇒ Object
Instantiates the configured workspace, or a root-scoped Workspace by default.
258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 |
# File 'lib/little_ghost/runtime.rb', line 258 def build_workspace(invocation: nil) declaration = workspace_declaration unless declaration paths = artifacts_enabled? ? {artifacts: "artifacts"} : {} return Workspace.new(root: root, paths:) end provider, = component_provider(declaration) provider = Workspace.resolve_provider(provider) if provider.is_a?(Symbol) [:root] = resolve_component_path([:root]) if .key?(:root) [:root] ||= root.to_s if provider == Workspace if artifacts_enabled? && provider == Workspace [:paths] = {artifacts: "artifacts"}.merge(.fetch(:paths, {})) end build_component(provider, , runtime: self, invocation:) end |
#default_error_message(error, _run) ⇒ Object
:nodoc:
416 417 418 419 420 421 422 423 424 425 |
# File 'lib/little_ghost/runtime.rb', line 416 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 "I hit an error while generating a response. Please retry." end |
#embed(**arguments) ⇒ Object
:call-seq:
embed(model:, inputs:, settings: {}, limits: {}, cancellation_token: Support::CancellationToken.new, deadline: nil) -> Embeddings::Response
Embeds text through this Runtime's model resolver.
Returns an Embeddings::Response without creating a Run or invoking runtime hooks. See LittleGhost.embed for the operation contract.
205 206 207 |
# File 'lib/little_ghost/runtime.rb', line 205 def (**arguments) @model_operations.(**arguments) end |
#error_message(error, run) ⇒ Object
:nodoc:
407 408 409 410 411 412 413 414 |
# File 'lib/little_ghost/runtime.rb', line 407 def (error, run) # :nodoc: runtime_hooks.each do |hook| = hook.(error, run) return if end (error, run) end |
#generate(**arguments) ⇒ Object
:call-seq:
generate(model:, messages:, result_schema: nil, settings: {}, structured_result_repair_attempts: 1, cancellation_token: Support::CancellationToken.new, deadline: nil) -> RunResult
Generates one response through this Runtime's model resolver.
Returns a RunResult without creating a Run or invoking runtime hooks. See LittleGhost.generate for the operation contract.
194 195 196 |
# File 'lib/little_ghost/runtime.rb', line 194 def generate(**arguments) @model_operations.generate(**arguments) end |
#model_for(agent_class, run) ⇒ Object
:nodoc:
344 345 346 347 |
# File 'lib/little_ghost/runtime.rb', line 344 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:
349 350 351 352 353 354 355 356 |
# File 'lib/little_ghost/runtime.rb', line 349 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:
384 385 386 387 388 389 390 391 392 393 394 395 396 397 |
# File 'lib/little_ghost/runtime.rb', line 384 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.
183 184 185 |
# File 'lib/little_ghost/runtime.rb', line 183 def parse(payload) payload.is_a?(@invocation_class) ? payload : @invocation_class.new(payload) end |
#prepare_execution(run) ⇒ Object
:nodoc:
373 374 375 376 |
# File 'lib/little_ghost/runtime.rb', line 373 def prepare_execution(run) # :nodoc: runtime_hooks.each { |hook| hook.prepare_execution(run) } run end |
#prepare_interjection(run, payload) ⇒ Object
:nodoc:
378 379 380 381 382 |
# File 'lib/little_ghost/runtime.rb', line 378 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:
368 369 370 371 |
# File 'lib/little_ghost/runtime.rb', line 368 def prepare_run(run) # :nodoc: runtime_hooks.each { |hook| hook.prepare_run(run) } run end |
#resolve_agent(value) ⇒ Object
:nodoc:
427 428 429 |
# File 'lib/little_ghost/runtime.rb', line 427 def resolve_agent(value) # :nodoc: resolve_agent_class(value) end |
#service_name ⇒ Object
The stable service name attached to runtime telemetry.
340 341 342 |
# File 'lib/little_ghost/runtime.rb', line 340 def service_name @settings&.[](:service_name) || default_service_name end |
#session_actor_for(invocation) ⇒ Object
:nodoc:
399 400 401 |
# File 'lib/little_ghost/runtime.rb', line 399 def session_actor_for(invocation) # :nodoc: @session_actor ? @session_actor.call(invocation) : invocation.actor_id end |
#session_history(run, session, fallback:) ⇒ Object
:nodoc:
358 359 360 361 362 363 364 365 366 |
# File 'lib/little_ghost/runtime.rb', line 358 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:
403 404 405 |
# File 'lib/little_ghost/runtime.rb', line 403 def template_locals(run:, agent:) # :nodoc: {invocation: run.invocation, run:, agent:}.merge(agent.prompt_locals) end |