little_ghost 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +19 -5
- data/docs/guides/core_concepts.md +167 -38
- data/docs/guides/getting_started.md +8 -4
- data/lib/little_ghost/agent/delegation.rb +34 -7
- data/lib/little_ghost/agent/tool_loop.rb +2 -1
- data/lib/little_ghost/agent.rb +115 -156
- data/lib/little_ghost/agent_builder.rb +20 -4
- data/lib/little_ghost/agent_factory.rb +3 -0
- data/lib/little_ghost/assembly.rb +311 -0
- data/lib/little_ghost/assembly_builder.rb +459 -0
- data/lib/little_ghost/assembly_execution.rb +452 -0
- data/lib/little_ghost/errors.rb +8 -0
- data/lib/little_ghost/execution.rb +206 -0
- data/lib/little_ghost/graph.rb +911 -0
- data/lib/little_ghost/run.rb +120 -32
- data/lib/little_ghost/run_result.rb +22 -11
- data/lib/little_ghost/runtime/hook.rb +7 -2
- data/lib/little_ghost/runtime.rb +64 -6
- data/lib/little_ghost/sandbox.rb +1 -1
- data/lib/little_ghost/support/executor.rb +14 -2
- data/lib/little_ghost/support/loader.rb +2 -2
- data/lib/little_ghost/support.rb +15 -3
- data/lib/little_ghost/swarm.rb +431 -0
- data/lib/little_ghost/tool.rb +32 -6
- data/lib/little_ghost/tracing/open_telemetry.rb +13 -2
- data/lib/little_ghost/unrestricted_sandbox.rb +1 -1
- data/lib/little_ghost/version.rb +1 -1
- data/lib/little_ghost/workflow.rb +198 -73
- data/lib/little_ghost.rb +11 -4
- metadata +11 -4
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module LittleGhost
|
|
4
|
+
# An immutable, executable snapshot produced by an AssemblyBuilder.
|
|
5
|
+
AssemblyDefinition = Data.define(:kind, :assembly_id, :description, :implementation) do
|
|
6
|
+
def initialize(kind:, assembly_id:, description:, implementation:)
|
|
7
|
+
super(
|
|
8
|
+
kind: kind.to_sym,
|
|
9
|
+
assembly_id: assembly_id.to_s.dup.freeze,
|
|
10
|
+
description: description.to_s.dup.freeze,
|
|
11
|
+
implementation:
|
|
12
|
+
)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Builds an Assembly when its participants or routes are discovered at runtime.
|
|
17
|
+
#
|
|
18
|
+
# Class definitions are the usual, easier-to-find way to declare behavior.
|
|
19
|
+
# Builders expose the underlying dynamic form while preserving the same
|
|
20
|
+
# +ask+, +stream_ask+, +call+, and +stream+ interface:
|
|
21
|
+
#
|
|
22
|
+
# graph = LittleGhost::GraphBuilder.new(id: "support_flow")
|
|
23
|
+
# graph.node :triage, TriageAgent
|
|
24
|
+
# graph.node :respond, CustomerSupportAgent
|
|
25
|
+
# graph.start :triage
|
|
26
|
+
# graph.edge :triage, :respond
|
|
27
|
+
# graph.finish :respond
|
|
28
|
+
# graph.validate!
|
|
29
|
+
#
|
|
30
|
+
# run = graph.ask("Can I get a refund?")
|
|
31
|
+
#
|
|
32
|
+
# A builder remains mutable. Each build or invocation snapshots declaration
|
|
33
|
+
# containers and referenced Assembly definitions, so later builder changes
|
|
34
|
+
# affect only future executions. Executable Ruby closures and the external
|
|
35
|
+
# objects they reference remain live trusted application code.
|
|
36
|
+
class AssemblyBuilder
|
|
37
|
+
# Optional Runtime reused by standalone executions from this builder.
|
|
38
|
+
attr_reader :runtime
|
|
39
|
+
|
|
40
|
+
# Creates a mutable builder with optional identity, runtime, and base class.
|
|
41
|
+
def initialize(id: nil, description: nil, runtime: nil, base: nil)
|
|
42
|
+
@mutex = Mutex.new
|
|
43
|
+
@assembly_id = id&.to_s&.dup&.freeze
|
|
44
|
+
@description = description&.to_s&.dup&.freeze
|
|
45
|
+
@runtime = runtime
|
|
46
|
+
@base = base
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Reads or assigns the stable Assembly identifier.
|
|
50
|
+
def assembly_id(value = nil)
|
|
51
|
+
return @mutex.synchronize { @assembly_id || default_assembly_id } if value.nil?
|
|
52
|
+
|
|
53
|
+
@mutex.synchronize { @assembly_id = String(value).dup.freeze }
|
|
54
|
+
self
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Reads or assigns the human-readable description.
|
|
58
|
+
def description(value = nil)
|
|
59
|
+
return @mutex.synchronize { @description || base_description } if value.nil?
|
|
60
|
+
|
|
61
|
+
@mutex.synchronize { @description = String(value).dup.freeze }
|
|
62
|
+
self
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Returns +:agent+, +:workflow+, +:swarm+, or +:graph+.
|
|
66
|
+
def assembly_kind = self.class.assembly_kind
|
|
67
|
+
|
|
68
|
+
# Validates the current snapshot and returns this mutable builder.
|
|
69
|
+
def validate!
|
|
70
|
+
definition
|
|
71
|
+
self
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Returns an immutable, recursively snapshotted definition.
|
|
75
|
+
def definition
|
|
76
|
+
stack = Thread.current[:little_ghost_assembly_definition_stack] ||= []
|
|
77
|
+
identity = base || self
|
|
78
|
+
if stack.include?(identity)
|
|
79
|
+
raise ConfigurationError, "assembly definitions cannot contain themselves recursively"
|
|
80
|
+
end
|
|
81
|
+
stack << identity
|
|
82
|
+
state = @mutex.synchronize { snapshot_state }
|
|
83
|
+
implementation = build_implementation(state)
|
|
84
|
+
prepare_implementation!(implementation)
|
|
85
|
+
validate_implementation!(implementation)
|
|
86
|
+
seal_implementation!(implementation)
|
|
87
|
+
implementation.freeze
|
|
88
|
+
AssemblyDefinition.new(
|
|
89
|
+
kind: assembly_kind,
|
|
90
|
+
assembly_id: implementation.assembly_id,
|
|
91
|
+
description: implementation.description,
|
|
92
|
+
implementation:
|
|
93
|
+
)
|
|
94
|
+
ensure
|
|
95
|
+
stack&.pop if stack&.last.equal?(identity)
|
|
96
|
+
Thread.current[:little_ghost_assembly_definition_stack] = nil if stack && stack.empty?
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Builds one execution instance from the current definition snapshot.
|
|
100
|
+
def build(runtime: self.runtime, run: nil)
|
|
101
|
+
snapshot = definition
|
|
102
|
+
return (runtime || run.runtime).build_assembly(snapshot, run:) if run
|
|
103
|
+
|
|
104
|
+
snapshot.implementation.new(runtime:)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Executes a standalone snapshot and returns its Run.
|
|
108
|
+
def ask(message, **options) = build.ask(message, **options)
|
|
109
|
+
|
|
110
|
+
# Lazily streams a standalone snapshot.
|
|
111
|
+
def stream_ask(message, **options)
|
|
112
|
+
snapshot = definition
|
|
113
|
+
Enumerator.new do |events|
|
|
114
|
+
snapshot.implementation.new(runtime:).stream_ask(message, **options).each { |event| events << event }
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Executes a snapshot to completion.
|
|
119
|
+
def call(input = nil, **options) = build.call(input, **options)
|
|
120
|
+
# Streams a snapshot as StreamEvent objects.
|
|
121
|
+
def stream(input = nil, **options) = build.stream(input, **options)
|
|
122
|
+
# Starts a supervised execution from a snapshot.
|
|
123
|
+
def start_execution(payload, &block) = build.start_execution(payload, &block)
|
|
124
|
+
# Exposes a snapshot as a Tool.
|
|
125
|
+
def as_tool(**options) = build.as_tool(**options)
|
|
126
|
+
|
|
127
|
+
protected
|
|
128
|
+
|
|
129
|
+
attr_reader :base
|
|
130
|
+
|
|
131
|
+
def snapshot_state
|
|
132
|
+
{
|
|
133
|
+
assembly_id: @assembly_id || default_assembly_id,
|
|
134
|
+
description: @description || base_description,
|
|
135
|
+
base:
|
|
136
|
+
}
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def configure_implementation(implementation, state)
|
|
140
|
+
implementation.assembly_id(state.fetch(:assembly_id))
|
|
141
|
+
implementation.description(state.fetch(:description))
|
|
142
|
+
implementation
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def validate_implementation!(_implementation) = nil
|
|
146
|
+
|
|
147
|
+
def prepare_implementation!(_implementation) = nil
|
|
148
|
+
|
|
149
|
+
def snapshot_mutators = %i[assembly_id description]
|
|
150
|
+
|
|
151
|
+
def seal_implementation!(implementation)
|
|
152
|
+
mutators = snapshot_mutators + implementation.singleton_methods.grep(/=\z/)
|
|
153
|
+
implementation.instance_variables.each do |name|
|
|
154
|
+
value = implementation.instance_variable_get(name)
|
|
155
|
+
implementation.instance_variable_set(name, deep_freeze_snapshot_value(value))
|
|
156
|
+
end
|
|
157
|
+
implementation.methods.grep(/_value\z/).each do |reader|
|
|
158
|
+
writer = :"#{reader}="
|
|
159
|
+
next unless implementation.respond_to?(writer)
|
|
160
|
+
|
|
161
|
+
implementation.public_send(writer, deep_freeze_snapshot_value(implementation.public_send(reader)))
|
|
162
|
+
mutators << writer
|
|
163
|
+
end
|
|
164
|
+
mutators.uniq.each do |name|
|
|
165
|
+
implementation.define_singleton_method(name) do |*arguments, **keywords, &block|
|
|
166
|
+
if !name.to_s.end_with?("=") && arguments.empty? && keywords.empty? && !block
|
|
167
|
+
super()
|
|
168
|
+
else
|
|
169
|
+
raise FrozenError, "can't modify immutable Assembly definition"
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def deep_freeze_snapshot_value(value)
|
|
176
|
+
case value
|
|
177
|
+
when Hash
|
|
178
|
+
value.each do |key, child|
|
|
179
|
+
deep_freeze_snapshot_value(key)
|
|
180
|
+
deep_freeze_snapshot_value(child)
|
|
181
|
+
end
|
|
182
|
+
value.freeze
|
|
183
|
+
when Array
|
|
184
|
+
value.each { |child| deep_freeze_snapshot_value(child) }
|
|
185
|
+
value.freeze
|
|
186
|
+
when String
|
|
187
|
+
value.freeze
|
|
188
|
+
when Data
|
|
189
|
+
value.members.each { |member| deep_freeze_snapshot_value(value.public_send(member)) }
|
|
190
|
+
value.freeze
|
|
191
|
+
else
|
|
192
|
+
value
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def snapshot_reference(value)
|
|
197
|
+
return value.definition if value.is_a?(AssemblyBuilder)
|
|
198
|
+
return value.definition if value.is_a?(Class) && value <= Assembly
|
|
199
|
+
|
|
200
|
+
value
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def snapshot_base_class(value)
|
|
204
|
+
return unless value
|
|
205
|
+
|
|
206
|
+
snapshot = value.dup
|
|
207
|
+
source_name = value.name
|
|
208
|
+
snapshot.define_singleton_method(:name) { source_name } if source_name
|
|
209
|
+
snapshot.define_singleton_method(:assembly_source_class) { value }
|
|
210
|
+
value.instance_variables.each do |name|
|
|
211
|
+
snapshot.instance_variable_set(name, Support.deep_dup(value.instance_variable_get(name)))
|
|
212
|
+
end
|
|
213
|
+
value.methods.grep(/_value\z/).each do |reader|
|
|
214
|
+
writer = :"#{reader}="
|
|
215
|
+
next unless snapshot.respond_to?(writer)
|
|
216
|
+
|
|
217
|
+
snapshot.public_send(writer, Support.deep_dup(value.public_send(reader)))
|
|
218
|
+
end
|
|
219
|
+
snapshot.assembly_id(value.assembly_id)
|
|
220
|
+
snapshot.description(value.description)
|
|
221
|
+
if value <= Agent
|
|
222
|
+
path = value.logical_path
|
|
223
|
+
snapshot.define_singleton_method(:logical_path) { path }
|
|
224
|
+
end
|
|
225
|
+
snapshot
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def base_description = base&.description.to_s
|
|
229
|
+
def default_assembly_id = base&.assembly_id || assembly_kind.to_s
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
# Builds an Agent definition from declarations made at runtime.
|
|
233
|
+
# Supported Agent class-DSL calls are recorded and replayed into each
|
|
234
|
+
# immutable snapshot.
|
|
235
|
+
class AgentBuilder < AssemblyBuilder
|
|
236
|
+
DECLARATIONS = %i[
|
|
237
|
+
model limits result_schema capture_diagnostics system_template system_prompt
|
|
238
|
+
tools prompt_local after_initialize before_invocation after_invocation
|
|
239
|
+
before_model after_model after_model_error before_tool after_tool
|
|
240
|
+
manage_context detect_tool_loops skills subagent subagents
|
|
241
|
+
subagent_long_poll_duration agent_as_tool agents_as_tools
|
|
242
|
+
assembly_as_tool assemblies_as_tools
|
|
243
|
+
].freeze # :nodoc:
|
|
244
|
+
|
|
245
|
+
class << self
|
|
246
|
+
# :nodoc:
|
|
247
|
+
def assembly_kind = :agent
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
# Creates a mutable dynamic Agent definition.
|
|
251
|
+
def initialize(**options)
|
|
252
|
+
super
|
|
253
|
+
@operations = []
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
# Reads or assigns the Agent identifier.
|
|
257
|
+
def agent_id(value = nil)
|
|
258
|
+
value.nil? ? assembly_id : assembly_id(value)
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
# Records supported Agent class-DSL declarations for the next snapshot.
|
|
262
|
+
def method_missing(name, *arguments, **keywords, &block)
|
|
263
|
+
return super unless DECLARATIONS.include?(name)
|
|
264
|
+
|
|
265
|
+
@mutex.synchronize { @operations << [name, arguments, keywords, block] }
|
|
266
|
+
self
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
# Reports the declarative methods supported by Agent.
|
|
270
|
+
def respond_to_missing?(name, include_private = false)
|
|
271
|
+
DECLARATIONS.include?(name) || super
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
protected
|
|
275
|
+
|
|
276
|
+
def snapshot_mutators = super + [:agent_id, *DECLARATIONS]
|
|
277
|
+
|
|
278
|
+
def snapshot_state
|
|
279
|
+
operations = @operations.map do |name, arguments, keywords, block|
|
|
280
|
+
[name, Support.deep_dup(arguments).freeze, Support.deep_dup(keywords).freeze, block].freeze
|
|
281
|
+
end
|
|
282
|
+
super.merge(operations: operations.freeze)
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
def build_implementation(state)
|
|
286
|
+
implementation = snapshot_base_class(state.fetch(:base)) || Class.new(Agent)
|
|
287
|
+
configure_implementation(implementation, state)
|
|
288
|
+
state.fetch(:operations).each do |name, arguments, keywords, block|
|
|
289
|
+
if keywords.empty?
|
|
290
|
+
implementation.public_send(name, *arguments, &block)
|
|
291
|
+
else
|
|
292
|
+
implementation.public_send(name, *arguments, **keywords, &block)
|
|
293
|
+
end
|
|
294
|
+
end
|
|
295
|
+
implementation
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
def prepare_implementation!(implementation)
|
|
299
|
+
declarations = implementation.assembly_tool_declarations.map do |declaration|
|
|
300
|
+
declaration.merge(assembly: snapshot_reference(declaration.fetch(:assembly))).freeze
|
|
301
|
+
end
|
|
302
|
+
implementation.assembly_tool_declarations_value = declarations.freeze
|
|
303
|
+
subagents = implementation.subagent_declarations.map do |declaration|
|
|
304
|
+
declaration.merge(agent: snapshot_reference(declaration.fetch(:agent))).freeze
|
|
305
|
+
end
|
|
306
|
+
implementation.subagent_declarations_value = subagents.freeze
|
|
307
|
+
end
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
# Builds a Workflow whose Ruby composition block is supplied at runtime.
|
|
311
|
+
class WorkflowBuilder < AssemblyBuilder
|
|
312
|
+
class << self
|
|
313
|
+
# :nodoc:
|
|
314
|
+
def assembly_kind = :workflow
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
# Declares the Ruby composition body for dynamic Workflow executions.
|
|
318
|
+
def perform(&block)
|
|
319
|
+
raise ArgumentError, "perform requires a block" unless block
|
|
320
|
+
|
|
321
|
+
@mutex.synchronize { @performer = block }
|
|
322
|
+
self
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
protected
|
|
326
|
+
|
|
327
|
+
def snapshot_mutators = super + [:perform]
|
|
328
|
+
|
|
329
|
+
def snapshot_state = super.merge(performer: @performer)
|
|
330
|
+
|
|
331
|
+
def build_implementation(state)
|
|
332
|
+
implementation = snapshot_base_class(state.fetch(:base)) || Class.new(Workflow)
|
|
333
|
+
configure_implementation(implementation, state)
|
|
334
|
+
if (performer = state.fetch(:performer))
|
|
335
|
+
implementation.define_method(:perform) { performer.call(self) }
|
|
336
|
+
implementation.send(:private, :perform)
|
|
337
|
+
end
|
|
338
|
+
implementation
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
def validate_implementation!(_implementation)
|
|
342
|
+
raise ConfigurationError, "workflow builder must declare perform" unless @performer || base
|
|
343
|
+
end
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
# Builds a Swarm at runtime while keeping its members Agent-only.
|
|
347
|
+
class SwarmBuilder < AssemblyBuilder
|
|
348
|
+
class << self
|
|
349
|
+
# :nodoc:
|
|
350
|
+
def assembly_kind = :swarm
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
# Creates a mutable dynamic Swarm definition.
|
|
354
|
+
def initialize(**options)
|
|
355
|
+
super
|
|
356
|
+
@declarations = []
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
%i[member start max_steps handoff max_handoff_repeats].each do |name|
|
|
360
|
+
define_method(name) do |*arguments, **keywords, &block|
|
|
361
|
+
@mutex.synchronize { @declarations << [name, arguments, keywords, block] }
|
|
362
|
+
self
|
|
363
|
+
end
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
protected
|
|
367
|
+
|
|
368
|
+
def snapshot_mutators = super + %i[member start max_steps handoff max_handoff_repeats]
|
|
369
|
+
|
|
370
|
+
def snapshot_state = super.merge(declarations: @declarations.map { |value| value.dup.freeze }.freeze)
|
|
371
|
+
|
|
372
|
+
def build_implementation(state)
|
|
373
|
+
implementation = snapshot_base_class(state.fetch(:base)) || Class.new(Swarm)
|
|
374
|
+
configure_implementation(implementation, state)
|
|
375
|
+
replay(implementation, state.fetch(:declarations))
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
def validate_implementation!(implementation) = implementation.swarm_definition!
|
|
379
|
+
|
|
380
|
+
def prepare_implementation!(implementation)
|
|
381
|
+
members = implementation.swarm_members_value.to_h do |id, member|
|
|
382
|
+
copied = Support.deep_dup(member.to_h)
|
|
383
|
+
[id, Swarm::Member.new(**copied.merge(agent: snapshot_reference(member.agent)))]
|
|
384
|
+
end
|
|
385
|
+
implementation.swarm_members_value = members.freeze
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
private
|
|
389
|
+
|
|
390
|
+
def replay(implementation, declarations)
|
|
391
|
+
declarations.each do |name, arguments, keywords, block|
|
|
392
|
+
arguments = arguments.map { |value| snapshot_reference(value) }
|
|
393
|
+
implementation.public_send(name, *arguments, **keywords, &block)
|
|
394
|
+
end
|
|
395
|
+
implementation
|
|
396
|
+
end
|
|
397
|
+
|
|
398
|
+
def snapshot_reference(value)
|
|
399
|
+
if value.is_a?(AssemblyBuilder) && !value.is_a?(AgentBuilder)
|
|
400
|
+
raise ConfigurationError, "swarm members must be Agent definitions"
|
|
401
|
+
end
|
|
402
|
+
if value.is_a?(AssemblyDefinition) && value.kind != :agent
|
|
403
|
+
raise ConfigurationError, "swarm members must be Agent definitions"
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
super
|
|
407
|
+
end
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
# Builds a Graph from nodes and routes discovered at runtime.
|
|
411
|
+
class GraphBuilder < AssemblyBuilder
|
|
412
|
+
class << self
|
|
413
|
+
# :nodoc:
|
|
414
|
+
def assembly_kind = :graph
|
|
415
|
+
end
|
|
416
|
+
|
|
417
|
+
# Creates a mutable dynamic Graph definition.
|
|
418
|
+
def initialize(**options)
|
|
419
|
+
super
|
|
420
|
+
@declarations = []
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
%i[node start edge finish max_steps fork join error_edge].each do |name|
|
|
424
|
+
define_method(name) do |*arguments, **keywords, &block|
|
|
425
|
+
@mutex.synchronize { @declarations << [name, arguments, keywords, block] }
|
|
426
|
+
self
|
|
427
|
+
end
|
|
428
|
+
end
|
|
429
|
+
|
|
430
|
+
# Renders the current validated snapshot as Mermaid flowchart text.
|
|
431
|
+
def to_mermaid = definition.implementation.to_mermaid
|
|
432
|
+
|
|
433
|
+
protected
|
|
434
|
+
|
|
435
|
+
def snapshot_mutators = super + %i[node start edge finish max_steps fork join error_edge]
|
|
436
|
+
|
|
437
|
+
def snapshot_state = super.merge(declarations: @declarations.map { |value| value.dup.freeze }.freeze)
|
|
438
|
+
|
|
439
|
+
def build_implementation(state)
|
|
440
|
+
implementation = snapshot_base_class(state.fetch(:base)) || Class.new(Graph)
|
|
441
|
+
configure_implementation(implementation, state)
|
|
442
|
+
state.fetch(:declarations).each do |name, arguments, keywords, block|
|
|
443
|
+
arguments = arguments.map { |value| value.is_a?(AssemblyBuilder) ? value.definition : value }
|
|
444
|
+
implementation.public_send(name, *arguments, **keywords, &block)
|
|
445
|
+
end
|
|
446
|
+
implementation
|
|
447
|
+
end
|
|
448
|
+
|
|
449
|
+
def validate_implementation!(implementation) = implementation.validate!
|
|
450
|
+
|
|
451
|
+
def prepare_implementation!(implementation)
|
|
452
|
+
nodes = implementation.graph_nodes_value.to_h do |id, node|
|
|
453
|
+
copied = Support.deep_dup(node.to_h)
|
|
454
|
+
[id, Graph::Node.new(**copied.merge(assembly: snapshot_reference(node.assembly)))]
|
|
455
|
+
end
|
|
456
|
+
implementation.graph_nodes_value = nodes.freeze
|
|
457
|
+
end
|
|
458
|
+
end
|
|
459
|
+
end
|