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 copies its declarations and referenced Assembly definitions. Later builder changes affect future executions without changing an Assembly already built. Ruby closures and the objects they reference remain live 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.



63
64
65
66
67
68
69
# File 'lib/little_ghost/assembly_builder.rb', line 63

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.



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

def runtime
  @runtime
end

Instance Method Details

#as_tool(**options) ⇒ Object

Exposes the current declarations as a Tool.



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

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

#ask(message, **options) ⇒ Object

Executes the current declarations and returns their Run.



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

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

#assembly_id(value = nil) ⇒ Object

Reads or assigns the stable Assembly identifier.



72
73
74
75
76
77
# File 'lib/little_ghost/assembly_builder.rb', line 72

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.



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

def assembly_kind = self.class.assembly_kind

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

Builds one execution instance from the current declarations.



122
123
124
125
126
127
# File 'lib/little_ghost/assembly_builder.rb', line 122

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 the current declarations to completion.



141
142
# File 'lib/little_ghost/assembly_builder.rb', line 141

def call(input = nil, **options) = build.call(input, **options)
# Streams the current declarations as StreamEvent objects.

#definitionObject

Returns a complete definition for the builder's current declarations.



97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/little_ghost/assembly_builder.rb', line 97

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.



80
81
82
83
84
85
# File 'lib/little_ghost/assembly_builder.rb', line 80

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 the current declarations.



145
146
# File 'lib/little_ghost/assembly_builder.rb', line 145

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

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

Streams the current declarations as StreamEvent objects.



143
144
# File 'lib/little_ghost/assembly_builder.rb', line 143

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

#stream_ask(message, **options) ⇒ Object

Lazily streams an execution built from the current declarations.



133
134
135
136
137
138
# File 'lib/little_ghost/assembly_builder.rb', line 133

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 builder's current declarations and returns this builder.



91
92
93
94
# File 'lib/little_ghost/assembly_builder.rb', line 91

def validate!
  definition
  self
end