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.



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 a snapshot 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 a standalone snapshot and returns its 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 definition snapshot.



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 a snapshot to completion.



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

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

#definitionObject

Returns an immutable, recursively snapshotted definition.



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 a snapshot.



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

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.



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 a snapshot.

#stream_ask(message, **options) ⇒ Object

Lazily streams a standalone snapshot.



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 current snapshot and returns this mutable builder.



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

def validate!
  definition
  self
end