Class: LittleGhost::Configuration

Inherits:
Object
  • Object
show all
Defined in:
lib/little_ghost/configuration.rb

Overview

Configure shared services and lookup rules before agents start. A configuration collects model profiles, persistence, paths, instrumentation, and runtime hooks for an application.

LittleGhost.configure do |config|
config.default_model :customer_support
config.service_name "support-api"
end

LittleGhost.configuration.default_model # => "customer_support"
LittleGhost.configuration.service_name  # => "support-api"

Prompt and skill lookup paths default to app/prompts and app/skills under the application root. Applications may append shared roots or replace the arrays entirely.

Configuration is a mutable builder, while each Runtime owns a settings snapshot. The first runtime for a root loads config/little_ghost.rb once; later mutations do not alter that runtime, and one configuration cannot load files for two different roots.

Session actor resolvers belong at an authentication boundary. Multi-tenant applications should derive actor identity from trusted authenticated state, not from an unverified request field.

Constant Summary collapse

FILE_LOAD_MUTEX =

:nodoc:

Mutex.new
CONFIGURATION_KEYS =

:nodoc:

%i[invocation service_name].freeze
DEFAULT_PROMPT_PATHS =

:nodoc:

["app/prompts"].freeze
DEFAULT_SKILL_PATHS =

:nodoc:

["app/skills"].freeze

Instance Method Summary collapse

Constructor Details

#initialize(values = {}) ⇒ Configuration

Starts a mutable builder with optional values.

Prompt paths default to app/prompts and skill paths to app/skills. Collection settings are copied so callers can safely reuse their input arrays after construction.



87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/little_ghost/configuration.rb', line 87

def initialize(values = {})
  @configuration_values = {
    prompt_paths: DEFAULT_PROMPT_PATHS.dup,
    skill_paths: DEFAULT_SKILL_PATHS.dup,
    skill_resource_root: nil,
    workspace: nil,
    sandbox: nil,
    instrumentation_subscribers: [],
    runtime_hooks: []
  }.merge(values)
  @configuration_values[:prompt_paths] = Array(@configuration_values[:prompt_paths]).dup
  @configuration_values[:skill_paths] = Array(@configuration_values[:skill_paths]).dup
  @configuration_values[:instrumentation_subscribers] = Array(
    @configuration_values[:instrumentation_subscribers]
  ).dup
  @configuration_values[:runtime_hooks] = Array(@configuration_values[:runtime_hooks]).dup
  @configuration_values[:provider_adapters] = @configuration_values.fetch(:provider_adapters, {}).dup
  @configuration_values[:catalog_sources] = Array(@configuration_values[:catalog_sources]).dup
  @configuration_values[:provider_credentials] ||= nil
end

Instance Method Details

#[](name) ⇒ Object

Looks up an arbitrary setting by symbol or string-compatible name.



292
293
294
# File 'lib/little_ghost/configuration.rb', line 292

def [](name)
  configuration_values.fetch(name.to_sym)
end

#[]=(name, value) ⇒ Object

Adds or replaces an arbitrary setting.



297
298
299
# File 'lib/little_ghost/configuration.rb', line 297

def []=(name, value)
  configuration_values[name.to_sym] = value
end

#catalog_source(source) ⇒ Object

Adds an explicit catalog source. Sources refresh only when callers invoke ModelResolver#refresh!.

Raises:

  • (ArgumentError)


248
249
250
251
252
253
254
# File 'lib/little_ghost/configuration.rb', line 248

def catalog_source(source)
  raise ArgumentError, "catalog source must be a Models::Catalog::Source" unless source.is_a?(Models::Catalog::Source)

  configuration_values[:catalog_sources] << source
  @resolved_model_resolver = nil
  source
end

#configure {|_self| ... } ⇒ Object

Yields this builder for setup and returns the same instance.

Yields:

  • (_self)

Yield Parameters:



109
110
111
112
# File 'lib/little_ghost/configuration.rb', line 109

def configure
  yield self if block_given?
  self
end

#default_model(value = :__read__) ⇒ Object

Fallback logical role for the default resolver.



156
157
158
159
160
161
162
# File 'lib/little_ghost/configuration.rb', line 156

def default_model(value = :__read__)
  return configuration_values[:default_model] if value == :__read__

  configuration_values[:default_model] = value.to_s
  reset_model_resolver
  value.to_s
end

#default_model=(value) ⇒ Object

Replaces the fallback logical role and normalizes it to a String.



165
166
167
# File 'lib/little_ghost/configuration.rb', line 165

def default_model=(value)
  default_model(value)
end

#instrument(subscriber) ⇒ Object

Adds an Instrumentation::Subscriber to each new runtime and returns it.



307
308
309
310
311
312
313
314
# File 'lib/little_ghost/configuration.rb', line 307

def instrument(subscriber)
  unless subscriber.is_a?(Instrumentation::Subscriber)
    raise ArgumentError, "instrumentation subscriber must be a LittleGhost::Instrumentation::Subscriber"
  end

  configuration_values[:instrumentation_subscribers] << subscriber
  subscriber
end

#load_file!(root: nil) ⇒ Object

:nodoc:



421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
# File 'lib/little_ghost/configuration.rb', line 421

def load_file!(root: nil) # :nodoc:
  requested_root = canonical_root(root || self.root)
  FILE_LOAD_MUTEX.synchronize do
    (@configuration_file_mutex ||= Mutex.new).synchronize do
      if @configuration_file_root
        return self if @configuration_file_root == requested_root

        raise ConfigurationError, "configuration file is already loaded for #{@configuration_file_root}"
      end

      path = File.join(requested_root, "config/little_ghost.rb")
      LittleGhost.with_configuration(self) { Kernel.load(path) } if File.file?(path)
      @configuration_file_root = requested_root
    end
  end

  self
end

#log_events_to(destination = :__read__) ⇒ Object

:call-seq:

log_events_to() -> :stdout, :stderr, nil
log_events_to(destination) -> destination

Sends structured framework events to :stdout or :stderr. This setting controls the process-wide Events console destination; the most recent setting replaces it without changing other event listeners. By default, events have no console destination. Passing nil disables console output. The console listener redacts sensitive values and writes one JSON object per line.



326
327
328
329
330
# File 'lib/little_ghost/configuration.rb', line 326

def log_events_to(destination = :__read__)
  return Events.console_output if destination == :__read__

  Events.console_output = destination
end

#log_events_to=(destination) ⇒ Object

Replaces the console destination for structured framework events.



333
334
335
# File 'lib/little_ghost/configuration.rb', line 333

def log_events_to=(destination)
  log_events_to(destination)
end

#model_resolver(value = :__read__) ⇒ Object

Installs a complete resolver override for subsequently built runtimes.



188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
# File 'lib/little_ghost/configuration.rb', line 188

def model_resolver(value = :__read__)
  if value != :__read__
    validate_model_resolver_class!(value)

    configuration_values[:model_resolver] = value
    @resolved_model_resolver = nil
    return value
  end

  @model_resolver_mutex ||= Mutex.new
  @model_resolver_mutex.synchronize do
    @resolved_model_resolver ||= begin
      providers = resolved_providers
      credential_resolver = configuration_values[:provider_credentials] || providers&.method(:credentials)
      configured = configuration_values[:model_resolver]
      if configured
        validate_model_resolver_configuration!
        configured.new(
          providers:,
          provider_adapters: configuration_values[:provider_adapters],
          catalog_sources: configuration_values[:catalog_sources],
          credential_resolver:
        )
      else
        profiles, file_default = resolved_models
        ModelResolver.new(
          providers:,
          profiles:,
          default_model: configuration_values.fetch(:default_model, file_default),
          provider_adapters: configuration_values[:provider_adapters],
          catalog_sources: configuration_values[:catalog_sources],
          credential_resolver:
        )
      end
    end
  end
end

#model_resolver=(value) ⇒ Object



226
227
228
# File 'lib/little_ghost/configuration.rb', line 226

def model_resolver=(value)
  model_resolver(value)
end

#models(value = :__read__) ⇒ Object

Logical model profiles for the default resolver. Role names cannot contain a colon because that syntax identifies a canonical model target.

Raises:

  • (ArgumentError)


141
142
143
144
145
146
147
148
# File 'lib/little_ghost/configuration.rb', line 141

def models(value = :__read__)
  return configuration_values[:models] if value == :__read__
  raise ArgumentError, "models must be a Hash" unless value.is_a?(Hash)

  configuration_values[:models] = value
  reset_model_resolver
  value
end

#models=(value) ⇒ Object

Replaces logical model profiles for subsequently built runtimes.



151
152
153
# File 'lib/little_ghost/configuration.rb', line 151

def models=(value)
  models(value)
end

#models_path(value = :__read__) ⇒ Object

Model YAML path. The conventional path is optional; an explicitly set path must exist when a runtime is built.



180
# File 'lib/little_ghost/configuration.rb', line 180

def models_path(value = :__read__) = configuration_path(:models_path, "models.yml", value)

#models_path=(value) ⇒ Object

Replaces the model YAML path for subsequently built runtimes.



183
184
185
# File 'lib/little_ghost/configuration.rb', line 183

def models_path=(value)
  models_path(value)
end

#nameObject

Replaces the service name attached to telemetry from new runtimes. :method: service_name= :call-seq:

service_name=(value) -> value


52
53
54
55
56
57
58
# File 'lib/little_ghost/configuration.rb', line 52

CONFIGURATION_KEYS.each do |name|
  define_method(name) do |value = :__read__|
    return configuration_values[name] if value == :__read__

    configuration_values[name] = value
  end
end

#prompt_pathsObject

Mutable prompt lookup paths, in precedence order.



385
# File 'lib/little_ghost/configuration.rb', line 385

def prompt_paths = configuration_values[:prompt_paths]

#prompt_paths=(value) ⇒ Object

Replaces prompt lookup paths with value converted to an Array.



388
389
390
# File 'lib/little_ghost/configuration.rb', line 388

def prompt_paths=(value)
  configuration_values[:prompt_paths] = Array(value)
end

#provider_adapter(name, callable = nil, &factory) ⇒ Object

Registers a provider adapter factory under name.



231
232
233
234
235
236
237
238
239
240
241
242
243
244
# File 'lib/little_ghost/configuration.rb', line 231

def provider_adapter(name, callable = nil, &factory)
  implementation = factory || callable
  if implementation.is_a?(Class)
    unless implementation <= Providers::Base
      raise ArgumentError, "provider adapter class must inherit LittleGhost::Providers::Base"
    end
  elsif !implementation.respond_to?(:call)
    raise ArgumentError, "provider adapter must be a Providers::Base class or callable factory"
  end

  configuration_values[:provider_adapters][name.to_s] = implementation
  @resolved_model_resolver = nil
  implementation
end

#provider_credentials(callable = nil, &resolver) ⇒ Object

Installs a trusted callable that returns credential options for a named provider connection when each executable model is constructed.

Raises:

  • (ArgumentError)


258
259
260
261
262
263
264
265
266
# File 'lib/little_ghost/configuration.rb', line 258

def provider_credentials(callable = nil, &resolver)
  value = resolver || callable
  return configuration_values[:provider_credentials] unless value
  raise ArgumentError, "provider credential resolver must be callable" unless value.respond_to?(:call)

  configuration_values[:provider_credentials] = value
  @resolved_model_resolver = nil
  value
end

#providers(value = :__read__) ⇒ Object

Trusted provider connections for the default or custom resolver.



122
123
124
125
126
127
128
129
130
131
132
# File 'lib/little_ghost/configuration.rb', line 122

def providers(value = :__read__)
  return configuration_values[:providers] if value == :__read__

  unless value.is_a?(Hash) || value.is_a?(Providers::Configuration)
    raise ArgumentError, "providers must be a Hash or LittleGhost::Providers::Configuration"
  end

  configuration_values[:providers] = value
  reset_model_resolver
  value
end

#providers=(value) ⇒ Object

Replaces trusted provider connections for subsequently built runtimes.



135
136
137
# File 'lib/little_ghost/configuration.rb', line 135

def providers=(value)
  providers(value)
end

#providers_path(value = :__read__) ⇒ Object

Provider YAML path. The conventional path is optional; an explicitly set path must exist when a runtime is built.



171
# File 'lib/little_ghost/configuration.rb', line 171

def providers_path(value = :__read__) = configuration_path(:providers_path, "providers.yml", value)

#providers_path=(value) ⇒ Object

Replaces the provider YAML path for subsequently built runtimes.



174
175
176
# File 'lib/little_ghost/configuration.rb', line 174

def providers_path=(value)
  providers_path(value)
end

#root(value = :__read__) ⇒ Object

:call-seq:

root() -> Pathname
root(path) -> Pathname

The resolved application root, defaulting to Dir.pwd.

Setting or reading an invalid root raises ConfigurationError. Symlinks are resolved so runtimes and lookup paths share one stable boundary.



375
376
377
378
379
380
381
382
# File 'lib/little_ghost/configuration.rb', line 375

def root(value = :__read__)
  if value != :__read__
    return configuration_values[:root] = canonical_root(value)
  end

  configured = configuration_values[:root]
  configured ? canonical_root(configured) : inferred_root
end

#root=(value) ⇒ Object

Replaces the application root after resolving it to a stable real path.



302
303
304
# File 'lib/little_ghost/configuration.rb', line 302

def root=(value)
  root(value)
end

#runtime_hook(hook_class) ⇒ Object

Adds a Runtime::Hook subclass to each new runtime and returns it.



338
339
340
341
342
343
344
345
# File 'lib/little_ghost/configuration.rb', line 338

def runtime_hook(hook_class)
  unless hook_class.is_a?(Class) && hook_class <= Runtime::Hook
    raise ArgumentError, "runtime_hook must be a LittleGhost::Runtime::Hook class"
  end

  configuration_values[:runtime_hooks] << hook_class
  hook_class
end

#sandboxObject

Sandbox declaration used for subsequently built runtimes.



117
118
# File 'lib/little_ghost/configuration.rb', line 117

def sandbox = configuration_values[:sandbox]
# Session-store declaration used for subsequently built runtimes.

#sandbox=(value) ⇒ Object

Selects the Sandbox subclass instantiated around each run's workspace.



274
275
276
# File 'lib/little_ghost/configuration.rb', line 274

def sandbox=(value)
  @configuration_values[:sandbox] = component_class(value, Sandbox, :sandbox)
end

#session_actor(value = :__read__, &resolver) ⇒ Object

:call-seq:

session_actor() -> callable, nil
session_actor(callable) -> callable
session_actor { |invocation| ... } -> callable

The callable that derives the persistence actor for each invocation.

Pass either a callable or a block. The configured resolver should use trusted authenticated identity in multi-tenant applications.

Raises:

  • (ArgumentError)


356
357
358
359
360
361
362
363
364
365
# File 'lib/little_ghost/configuration.rb', line 356

def session_actor(value = :__read__, &resolver)
  return configuration_values[:session_actor] if value == :__read__ && !resolver

  raise ArgumentError, "Provide a session actor resolver or a block, not both" if value != :__read__ && resolver

  configured = resolver || value
  raise ArgumentError, "session_actor must be callable" unless configured.respond_to?(:call)

  configuration_values[:session_actor] = configured
end

#session_storeObject

Session-store declaration used for subsequently built runtimes.



119
# File 'lib/little_ghost/configuration.rb', line 119

def session_store = configuration_values[:session_store]

#session_store=(value) ⇒ Object

Selects session persistence with a :provider and its constructor options.

The provider must be a SessionStore subclass. Runtime construction creates and owns the store instance.



282
283
284
285
286
287
288
289
# File 'lib/little_ghost/configuration.rb', line 282

def session_store=(value)
  unless value.is_a?(Hash)
    raise ArgumentError, "session_store must be a hash with a provider"
  end

  provider = value[:provider]
  @configuration_values[:session_store] = value.merge(provider: component_class(provider, SessionStore, :session_store))
end

#settings(root: nil) ⇒ Object

:nodoc:



408
409
410
411
412
413
414
415
416
417
418
419
# File 'lib/little_ghost/configuration.rb', line 408

def settings(root: nil) # :nodoc:
  requested_root = root && canonical_root(root)
  values = configuration_values.dup
  values[:prompt_paths] = Array(values[:prompt_paths]).dup
  values[:skill_paths] = Array(values[:skill_paths]).dup
  values[:instrumentation_subscribers] = Array(values[:instrumentation_subscribers]).dup
  values[:runtime_hooks] = Array(values[:runtime_hooks]).dup
  values[:model_resolver] = model_resolver
  values[:default_model] = values[:model_resolver].default_model
  values[:root] = requested_root || values[:root] || self.root
  values
end

#skill_pathsObject

Mutable skill lookup paths, in precedence order.



393
# File 'lib/little_ghost/configuration.rb', line 393

def skill_paths = configuration_values[:skill_paths]

#skill_paths=(value) ⇒ Object

Replaces skill lookup paths with value converted to an Array.



396
397
398
# File 'lib/little_ghost/configuration.rb', line 396

def skill_paths=(value)
  configuration_values[:skill_paths] = Array(value)
end

#skill_resource_rootObject

Optional trusted root exposed to skills for resource lookup.



401
# File 'lib/little_ghost/configuration.rb', line 401

def skill_resource_root = configuration_values[:skill_resource_root]

#skill_resource_root=(value) ⇒ Object

Replaces the trusted skill resource root for new runtimes.



404
405
406
# File 'lib/little_ghost/configuration.rb', line 404

def skill_resource_root=(value)
  configuration_values[:skill_resource_root] = value
end

#workspaceObject

Workspace declaration used for subsequently built runtimes.



115
116
# File 'lib/little_ghost/configuration.rb', line 115

def workspace = configuration_values[:workspace]
# Sandbox declaration used for subsequently built runtimes.

#workspace=(value) ⇒ Object

Selects the Workspace subclass instantiated for each run.



269
270
271
# File 'lib/little_ghost/configuration.rb', line 269

def workspace=(value)
  @configuration_values[:workspace] = component_class(value, Workspace, :workspace)
end