Class: LittleGhost::AssemblyBuilder

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

Overview

Builds an Assembly when its participants or routes are discovered at runtime.

Class definitions are the usual, easier-to-find way to declare behavior. Builders expose the underlying dynamic form while preserving the same ask, stream_ask, call, and stream interface:

graph = LittleGhost::GraphBuilder.new(id: "support_flow")
graph.node :triage, TriageAgent
graph.node :respond, CustomerSupportAgent
graph.start :triage
graph.edge :triage, :respond
graph.finish :respond
graph.validate!

run = graph.ask("Can I get a refund?")

A builder remains mutable. Each build or invocation snapshots declaration containers and referenced Assembly definitions, so later builder changes affect only future executions. Executable Ruby closures and the external objects they reference remain live trusted application code.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(id: nil, description: nil, runtime: nil, base: nil) ⇒ AssemblyBuilder

Creates a mutable builder with optional identity, runtime, and base class.



41
42
43
44
45
46
47
# File 'lib/little_ghost/assembly_builder.rb', line 41

def initialize(id: nil, description: nil, runtime: nil, base: nil)
  @mutex = Mutex.new
  @assembly_id = id&.to_s&.dup&.freeze
  @description = description&.to_s&.dup&.freeze
  @runtime = runtime
  @base = base
end

Instance Attribute Details

#runtimeObject (readonly)

Optional Runtime reused by standalone executions from this builder.



38
39
40
# File 'lib/little_ghost/assembly_builder.rb', line 38

def runtime
  @runtime
end

Instance Method Details

#as_tool(**options) ⇒ Object

Exposes a snapshot as a Tool.



125
# File 'lib/little_ghost/assembly_builder.rb', line 125

def as_tool(**options) = build.as_tool(**options)

#ask(message, **options) ⇒ Object

Executes a standalone snapshot and returns its Run.



108
# File 'lib/little_ghost/assembly_builder.rb', line 108

def ask(message, **options) = build.ask(message, **options)

#assembly_id(value = nil) ⇒ Object

Reads or assigns the stable Assembly identifier.



50
51
52
53
54
55
# File 'lib/little_ghost/assembly_builder.rb', line 50

def assembly_id(value = nil)
  return @mutex.synchronize { @assembly_id || default_assembly_id } if value.nil?

  @mutex.synchronize { @assembly_id = String(value).dup.freeze }
  self
end

#assembly_kindObject

Returns :agent, :workflow, :swarm, or :graph.



66
# File 'lib/little_ghost/assembly_builder.rb', line 66

def assembly_kind = self.class.assembly_kind

#build(runtime: self.runtime, run: nil) ⇒ Object

Builds one execution instance from the current definition snapshot.



100
101
102
103
104
105
# File 'lib/little_ghost/assembly_builder.rb', line 100

def build(runtime: self.runtime, run: nil)
  snapshot = definition
  return (runtime || run.runtime).build_assembly(snapshot, run:) if run

  snapshot.implementation.new(runtime:)
end

#call(input = nil, **options) ⇒ Object

Executes a snapshot to completion.



119
120
# File 'lib/little_ghost/assembly_builder.rb', line 119

def call(input = nil, **options) = build.call(input, **options)
# Streams a snapshot as StreamEvent objects.

#definitionObject

Returns an immutable, recursively snapshotted definition.



75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/little_ghost/assembly_builder.rb', line 75

def definition
  stack = Thread.current[:little_ghost_assembly_definition_stack] ||= []
  identity = base || self
  if stack.include?(identity)
    raise ConfigurationError, "assembly definitions cannot contain themselves recursively"
  end
  stack << identity
  state = @mutex.synchronize { snapshot_state }
  implementation = build_implementation(state)
  prepare_implementation!(implementation)
  validate_implementation!(implementation)
  seal_implementation!(implementation)
  implementation.freeze
  AssemblyDefinition.new(
    kind: assembly_kind,
    assembly_id: implementation.assembly_id,
    description: implementation.description,
    implementation:
  )
ensure
  stack&.pop if stack&.last.equal?(identity)
  Thread.current[:little_ghost_assembly_definition_stack] = nil if stack && stack.empty?
end

#description(value = nil) ⇒ Object

Reads or assigns the human-readable description.



58
59
60
61
62
63
# File 'lib/little_ghost/assembly_builder.rb', line 58

def description(value = nil)
  return @mutex.synchronize { @description || base_description } if value.nil?

  @mutex.synchronize { @description = String(value).dup.freeze }
  self
end

#start_execution(payload, &block) ⇒ Object

Starts a supervised execution from a snapshot.



123
124
# File 'lib/little_ghost/assembly_builder.rb', line 123

def start_execution(payload, &block) = build.start_execution(payload, &block)
# Exposes a snapshot as a Tool.

#stream(input = nil, **options) ⇒ Object

Streams a snapshot as StreamEvent objects.



121
122
# File 'lib/little_ghost/assembly_builder.rb', line 121

def stream(input = nil, **options) = build.stream(input, **options)
# Starts a supervised execution from a snapshot.

#stream_ask(message, **options) ⇒ Object

Lazily streams a standalone snapshot.



111
112
113
114
115
116
# File 'lib/little_ghost/assembly_builder.rb', line 111

def stream_ask(message, **options)
  snapshot = definition
  Enumerator.new do |events|
    snapshot.implementation.new(runtime:).stream_ask(message, **options).each { |event| events << event }
  end
end

#validate!Object

Validates the current snapshot and returns this mutable builder.



69
70
71
72
# File 'lib/little_ghost/assembly_builder.rb', line 69

def validate!
  definition
  self
end