Class: LittleGhost::Runtime
- Inherits:
-
Object
- Object
- LittleGhost::Runtime
- Defined in:
- lib/little_ghost/runtime.rb,
lib/little_ghost/runtime/hook.rb
Overview
Prepare the shared services that agents and workflows use across many runs. A runtime owns model resolution, loading, persistence, lookup paths, hooks, and resource factories for one Ruby setup.
configuration = LittleGhost::Configuration.new(
root: Dir.pwd,
providers: {
openai: {adapter: :openai, api_key: ENV.fetch("OPENAI_API_KEY")}
},
models: {customer_support: {target: "openai:gpt-5.6-luna"}},
default_model: "customer_support",
service_name: "support-api"
)
runtime = LittleGhost::Runtime.new(configuration: configuration)
runtime.service_name # => "support-api"
runtime.root == Pathname.new(File.realpath(Dir.pwd)) # => true
Without explicit settings, construction canonicalizes the root, loads
config/little_ghost.rb once through the Configuration, snapshots settings,
configures instrumentation, eager-loads application constants, and builds the
selected model resolver and session store. Supplying settings is the
lower-level path used to create a sibling runtime from an existing snapshot.
Reuse a runtime across runs. build_run creates any missing workspace and
sandbox, transfers ownership only after both are built, and closes partial
resources if construction fails. build creates a sibling with explicit
overrides and reuses the loader only when the application root is unchanged.
Startup emits structured lifecycle instrumentation; a failed phase emits a failure event, flushes instrumentation, and re-raises the original exception. Session actor resolution must use trusted authenticated identity for tenant isolation. The default UnrestrictedSandbox is convenient application plumbing, not a security boundary for untrusted work.
Defined Under Namespace
Classes: Hook
Instance Attribute Summary collapse
-
#configuration ⇒ Object
readonly
The snapshotted setup and materialized services used by new runs.
-
#loader ⇒ Object
readonly
The snapshotted setup and materialized services used by new runs.
-
#model_resolver ⇒ Object
readonly
The snapshotted setup and materialized services used by new runs.
-
#prompt_paths ⇒ Object
readonly
The snapshotted setup and materialized services used by new runs.
-
#root ⇒ Object
readonly
The snapshotted setup and materialized services used by new runs.
-
#runtime_hooks ⇒ Object
readonly
The snapshotted setup and materialized services used by new runs.
-
#sandbox_class ⇒ Object
readonly
The snapshotted setup and materialized services used by new runs.
-
#session_store ⇒ Object
readonly
The snapshotted setup and materialized services used by new runs.
-
#settings ⇒ Object
readonly
The snapshotted setup and materialized services used by new runs.
-
#skill_paths ⇒ Object
readonly
The snapshotted setup and materialized services used by new runs.
-
#skill_resource_root ⇒ Object
readonly
The snapshotted setup and materialized services used by new runs.
-
#workspace_class ⇒ Object
readonly
The snapshotted setup and materialized services used by new runs.
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_run(payload, agent_class:, entrypoint_class: agent_class, workspace: nil, sandbox: nil) ⇒ Object
Creates a Run and transfers ownership of newly created workspace and sandbox resources to it.
-
#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_interruption(run, payload) ⇒ Object
:nodoc:.
-
#prepare_run(run) ⇒ Object
:nodoc:.
-
#resolve_agent(value) ⇒ Object
:nodoc:.
-
#service_name ⇒ Object
The low-cardinality service name attached to runtime telemetry.
-
#session_actor_for(invocation) ⇒ 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.
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 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 |
# File 'lib/little_ghost/runtime.rb', line 48 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_builder" @agent_builder = AgentBuilder.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)
The snapshotted setup and materialized services used by new runs.
43 44 45 |
# File 'lib/little_ghost/runtime.rb', line 43 def configuration @configuration end |
#loader ⇒ Object (readonly)
The snapshotted setup and materialized services used by new runs.
43 44 45 |
# File 'lib/little_ghost/runtime.rb', line 43 def loader @loader end |
#model_resolver ⇒ Object (readonly)
The snapshotted setup and materialized services used by new runs.
43 44 45 |
# File 'lib/little_ghost/runtime.rb', line 43 def model_resolver @model_resolver end |
#prompt_paths ⇒ Object (readonly)
The snapshotted setup and materialized services used by new runs.
43 44 45 |
# File 'lib/little_ghost/runtime.rb', line 43 def prompt_paths @prompt_paths end |
#root ⇒ Object (readonly)
The snapshotted setup and materialized services used by new runs.
43 44 45 |
# File 'lib/little_ghost/runtime.rb', line 43 def root @root end |
#runtime_hooks ⇒ Object (readonly)
The snapshotted setup and materialized services used by new runs.
43 44 45 |
# File 'lib/little_ghost/runtime.rb', line 43 def runtime_hooks @runtime_hooks end |
#sandbox_class ⇒ Object (readonly)
The snapshotted setup and materialized services used by new runs.
43 44 45 |
# File 'lib/little_ghost/runtime.rb', line 43 def sandbox_class @sandbox_class end |
#session_store ⇒ Object (readonly)
The snapshotted setup and materialized services used by new runs.
43 44 45 |
# File 'lib/little_ghost/runtime.rb', line 43 def session_store @session_store end |
#settings ⇒ Object (readonly)
The snapshotted setup and materialized services used by new runs.
43 44 45 |
# File 'lib/little_ghost/runtime.rb', line 43 def settings @settings end |
#skill_paths ⇒ Object (readonly)
The snapshotted setup and materialized services used by new runs.
43 44 45 |
# File 'lib/little_ghost/runtime.rb', line 43 def skill_paths @skill_paths end |
#skill_resource_root ⇒ Object (readonly)
The snapshotted setup and materialized services used by new runs.
43 44 45 |
# File 'lib/little_ghost/runtime.rb', line 43 def skill_resource_root @skill_resource_root end |
#workspace_class ⇒ Object (readonly)
The snapshotted setup and materialized services used by new runs.
43 44 45 |
# File 'lib/little_ghost/runtime.rb', line 43 def workspace_class @workspace_class end |
Instance Method Details
#build(**overrides) ⇒ Object
Creates a sibling runtime with explicit setting overrides.
117 118 119 120 121 122 123 124 125 |
# File 'lib/little_ghost/runtime.rb', line 117 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:
179 180 181 182 183 184 185 186 187 |
# File 'lib/little_ghost/runtime.rb', line 179 def build_agent( agent_class_or_name, run:, model: nil, tools: [], agent_path: Subagents::AgentPath::ROOT ) @agent_builder.build(agent_class_or_name, run:, model:, tools:, agent_path:) end |
#build_run(payload, agent_class:, entrypoint_class: agent_class, workspace: nil, sandbox: nil) ⇒ Object
Creates a Run and transfers ownership of newly created workspace and sandbox resources to it.
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 |
# File 'lib/little_ghost/runtime.rb', line 134 def build_run( payload, agent_class:, entrypoint_class: agent_class, workspace: nil, sandbox: nil ) 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:, 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.
172 173 174 175 176 |
# File 'lib/little_ghost/runtime.rb', line 172 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.
164 165 166 167 168 |
# File 'lib/little_ghost/runtime.rb', line 164 def build_workspace return workspace_class.new if workspace_class Workspace.new(root: root) end |
#default_error_message(error, _run) ⇒ Object
:nodoc:
251 252 253 254 255 256 257 258 259 260 |
# File 'lib/little_ghost/runtime.rb', line 251 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:
242 243 244 245 246 247 248 249 |
# File 'lib/little_ghost/runtime.rb', line 242 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:
194 195 196 197 |
# File 'lib/little_ghost/runtime.rb', line 194 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:
199 200 201 202 203 204 205 206 |
# File 'lib/little_ghost/runtime.rb', line 199 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:
219 220 221 222 223 224 225 226 227 228 229 230 231 232 |
# File 'lib/little_ghost/runtime.rb', line 219 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.
128 129 130 |
# File 'lib/little_ghost/runtime.rb', line 128 def parse(payload) payload.is_a?(@invocation_class) ? payload : @invocation_class.new(payload) end |
#prepare_interruption(run, payload) ⇒ Object
:nodoc:
213 214 215 216 217 |
# File 'lib/little_ghost/runtime.rb', line 213 def prepare_interruption(run, payload) # :nodoc: runtime_hooks.reduce(payload) do |prepared, hook| hook.prepare_interruption(run, prepared) end end |
#prepare_run(run) ⇒ Object
:nodoc:
208 209 210 211 |
# File 'lib/little_ghost/runtime.rb', line 208 def prepare_run(run) # :nodoc: runtime_hooks.each { |hook| hook.prepare_run(run) } run end |
#resolve_agent(value) ⇒ Object
:nodoc:
262 263 264 |
# File 'lib/little_ghost/runtime.rb', line 262 def resolve_agent(value) # :nodoc: resolve_agent_class(value) end |
#service_name ⇒ Object
The low-cardinality service name attached to runtime telemetry.
190 191 192 |
# File 'lib/little_ghost/runtime.rb', line 190 def service_name @settings&.[](:service_name) || default_service_name end |
#session_actor_for(invocation) ⇒ Object
:nodoc:
234 235 236 |
# File 'lib/little_ghost/runtime.rb', line 234 def session_actor_for(invocation) # :nodoc: @session_actor ? @session_actor.call(invocation) : invocation.actor_id end |
#template_locals(run:, agent:) ⇒ Object
:nodoc:
238 239 240 |
# File 'lib/little_ghost/runtime.rb', line 238 def template_locals(run:, agent:) # :nodoc: {invocation: run.invocation, run:, agent:}.merge(agent.prompt_locals) end |