griptape-nodes 0.52.1__py3-none-any.whl → 0.54.0__py3-none-any.whl

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.
Files changed (71) hide show
  1. griptape_nodes/__init__.py +8 -942
  2. griptape_nodes/__main__.py +6 -0
  3. griptape_nodes/app/app.py +48 -86
  4. griptape_nodes/bootstrap/workflow_executors/local_workflow_executor.py +35 -5
  5. griptape_nodes/bootstrap/workflow_executors/workflow_executor.py +15 -1
  6. griptape_nodes/cli/__init__.py +1 -0
  7. griptape_nodes/cli/commands/__init__.py +1 -0
  8. griptape_nodes/cli/commands/config.py +74 -0
  9. griptape_nodes/cli/commands/engine.py +80 -0
  10. griptape_nodes/cli/commands/init.py +550 -0
  11. griptape_nodes/cli/commands/libraries.py +96 -0
  12. griptape_nodes/cli/commands/models.py +504 -0
  13. griptape_nodes/cli/commands/self.py +120 -0
  14. griptape_nodes/cli/main.py +56 -0
  15. griptape_nodes/cli/shared.py +75 -0
  16. griptape_nodes/common/__init__.py +1 -0
  17. griptape_nodes/common/directed_graph.py +71 -0
  18. griptape_nodes/drivers/storage/base_storage_driver.py +40 -20
  19. griptape_nodes/drivers/storage/griptape_cloud_storage_driver.py +24 -29
  20. griptape_nodes/drivers/storage/local_storage_driver.py +23 -14
  21. griptape_nodes/exe_types/core_types.py +60 -2
  22. griptape_nodes/exe_types/node_types.py +257 -38
  23. griptape_nodes/exe_types/param_components/__init__.py +1 -0
  24. griptape_nodes/exe_types/param_components/execution_status_component.py +138 -0
  25. griptape_nodes/machines/control_flow.py +195 -94
  26. griptape_nodes/machines/dag_builder.py +207 -0
  27. griptape_nodes/machines/fsm.py +10 -1
  28. griptape_nodes/machines/parallel_resolution.py +558 -0
  29. griptape_nodes/machines/{node_resolution.py → sequential_resolution.py} +30 -57
  30. griptape_nodes/node_library/library_registry.py +34 -1
  31. griptape_nodes/retained_mode/events/app_events.py +5 -1
  32. griptape_nodes/retained_mode/events/base_events.py +9 -9
  33. griptape_nodes/retained_mode/events/config_events.py +30 -0
  34. griptape_nodes/retained_mode/events/execution_events.py +2 -2
  35. griptape_nodes/retained_mode/events/model_events.py +296 -0
  36. griptape_nodes/retained_mode/events/node_events.py +4 -3
  37. griptape_nodes/retained_mode/griptape_nodes.py +34 -12
  38. griptape_nodes/retained_mode/managers/agent_manager.py +23 -5
  39. griptape_nodes/retained_mode/managers/arbitrary_code_exec_manager.py +3 -1
  40. griptape_nodes/retained_mode/managers/config_manager.py +44 -3
  41. griptape_nodes/retained_mode/managers/context_manager.py +6 -5
  42. griptape_nodes/retained_mode/managers/event_manager.py +8 -2
  43. griptape_nodes/retained_mode/managers/flow_manager.py +150 -206
  44. griptape_nodes/retained_mode/managers/library_lifecycle/library_directory.py +1 -1
  45. griptape_nodes/retained_mode/managers/library_manager.py +35 -25
  46. griptape_nodes/retained_mode/managers/model_manager.py +1107 -0
  47. griptape_nodes/retained_mode/managers/node_manager.py +102 -220
  48. griptape_nodes/retained_mode/managers/object_manager.py +11 -5
  49. griptape_nodes/retained_mode/managers/os_manager.py +28 -13
  50. griptape_nodes/retained_mode/managers/secrets_manager.py +8 -4
  51. griptape_nodes/retained_mode/managers/settings.py +116 -7
  52. griptape_nodes/retained_mode/managers/static_files_manager.py +85 -12
  53. griptape_nodes/retained_mode/managers/sync_manager.py +17 -9
  54. griptape_nodes/retained_mode/managers/workflow_manager.py +186 -192
  55. griptape_nodes/retained_mode/retained_mode.py +19 -0
  56. griptape_nodes/servers/__init__.py +1 -0
  57. griptape_nodes/{mcp_server/server.py → servers/mcp.py} +1 -1
  58. griptape_nodes/{app/api.py → servers/static.py} +43 -40
  59. griptape_nodes/traits/add_param_button.py +1 -1
  60. griptape_nodes/traits/button.py +334 -6
  61. griptape_nodes/traits/color_picker.py +66 -0
  62. griptape_nodes/traits/multi_options.py +188 -0
  63. griptape_nodes/traits/numbers_selector.py +77 -0
  64. griptape_nodes/traits/options.py +93 -2
  65. griptape_nodes/traits/traits.json +4 -0
  66. griptape_nodes/utils/async_utils.py +31 -0
  67. {griptape_nodes-0.52.1.dist-info → griptape_nodes-0.54.0.dist-info}/METADATA +4 -1
  68. {griptape_nodes-0.52.1.dist-info → griptape_nodes-0.54.0.dist-info}/RECORD +71 -48
  69. {griptape_nodes-0.52.1.dist-info → griptape_nodes-0.54.0.dist-info}/WHEEL +1 -1
  70. /griptape_nodes/{mcp_server → servers}/ws_request_manager.py +0 -0
  71. {griptape_nodes-0.52.1.dist-info → griptape_nodes-0.54.0.dist-info}/entry_points.txt +0 -0
@@ -1,8 +1,9 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import logging
4
+ from enum import StrEnum
4
5
  from queue import Queue
5
- from typing import TYPE_CHECKING, cast
6
+ from typing import TYPE_CHECKING, NamedTuple, cast
6
7
 
7
8
  from griptape_nodes.exe_types.connections import Connections
8
9
  from griptape_nodes.exe_types.core_types import (
@@ -15,11 +16,15 @@ from griptape_nodes.exe_types.core_types import (
15
16
  from griptape_nodes.exe_types.flow import ControlFlow
16
17
  from griptape_nodes.exe_types.node_types import BaseNode, ErrorProxyNode, NodeResolutionState, StartLoopNode, StartNode
17
18
  from griptape_nodes.machines.control_flow import CompleteState, ControlFlowMachine
19
+ from griptape_nodes.machines.dag_builder import DagBuilder
20
+ from griptape_nodes.machines.parallel_resolution import ParallelResolutionMachine
21
+ from griptape_nodes.machines.sequential_resolution import SequentialResolutionMachine
18
22
  from griptape_nodes.retained_mode.events.base_events import (
19
23
  ExecutionEvent,
20
24
  ExecutionGriptapeNodeEvent,
21
25
  FlushParameterChangesRequest,
22
26
  FlushParameterChangesResultSuccess,
27
+ ResultDetails,
23
28
  )
24
29
  from griptape_nodes.retained_mode.events.connection_events import (
25
30
  CreateConnectionRequest,
@@ -120,15 +125,29 @@ if TYPE_CHECKING:
120
125
  logger = logging.getLogger("griptape_nodes")
121
126
 
122
127
 
128
+ class DagExecutionType(StrEnum):
129
+ START_NODE = "start_node"
130
+ CONTROL_NODE = "control_node"
131
+ DATA_NODE = "data_node"
132
+
133
+
134
+ class QueueItem(NamedTuple):
135
+ """Represents an item in the flow execution queue."""
136
+
137
+ node: BaseNode
138
+ dag_execution_type: DagExecutionType
139
+
140
+
123
141
  class FlowManager:
124
142
  _name_to_parent_name: dict[str, str | None]
125
143
  _flow_to_referenced_workflow_name: dict[ControlFlow, str]
126
144
  _connections: Connections
127
145
 
128
146
  # Global execution state (moved from individual ControlFlows)
129
- _global_flow_queue: Queue[BaseNode]
147
+ _global_flow_queue: Queue[QueueItem]
130
148
  _global_control_flow_machine: ControlFlowMachine | None
131
149
  _global_single_node_resolution: bool
150
+ _global_dag_builder: DagBuilder
132
151
 
133
152
  def __init__(self, event_manager: EventManager) -> None:
134
153
  event_manager.assign_manager_to_request_type(CreateFlowRequest, self.on_create_flow_request)
@@ -169,9 +188,22 @@ class FlowManager:
169
188
  self._connections = Connections()
170
189
 
171
190
  # Initialize global execution state
172
- self._global_flow_queue = Queue[BaseNode]()
173
- self._global_control_flow_machine = None # Will be initialized when first flow starts
191
+ self._global_flow_queue = Queue[QueueItem]()
192
+ self._global_control_flow_machine = None # Track the current control flow machine
174
193
  self._global_single_node_resolution = False
194
+ self._global_dag_builder = DagBuilder()
195
+
196
+ @property
197
+ def global_single_node_resolution(self) -> bool:
198
+ return self._global_single_node_resolution
199
+
200
+ @property
201
+ def global_flow_queue(self) -> Queue[QueueItem]:
202
+ return self._global_flow_queue
203
+
204
+ @property
205
+ def global_dag_builder(self) -> DagBuilder:
206
+ return self._global_dag_builder
175
207
 
176
208
  def get_connections(self) -> Connections:
177
209
  """Get the connections instance."""
@@ -238,10 +270,12 @@ class FlowManager:
238
270
  def on_get_top_level_flow_request(self, request: GetTopLevelFlowRequest) -> ResultPayload: # noqa: ARG002 (the request has to be assigned to the method)
239
271
  for flow_name, parent in self._name_to_parent_name.items():
240
272
  if parent is None:
241
- return GetTopLevelFlowResultSuccess(flow_name=flow_name)
273
+ return GetTopLevelFlowResultSuccess(
274
+ flow_name=flow_name, result_details=f"Successfully found top level flow: '{flow_name}'"
275
+ )
242
276
  msg = "Attempted to get top level flow, but no such flow exists"
243
277
  logger.debug(msg)
244
- return GetTopLevelFlowResultSuccess(flow_name=None)
278
+ return GetTopLevelFlowResultSuccess(flow_name=None, result_details=msg)
245
279
 
246
280
  def on_get_flow_details_request(self, request: GetFlowDetailsRequest) -> ResultPayload:
247
281
  flow_name = request.flow_name
@@ -251,7 +285,6 @@ class FlowManager:
251
285
  # We want to get details for whatever is at the top of the Current Context.
252
286
  if not GriptapeNodes.ContextManager().has_current_flow():
253
287
  details = "Attempted to get Flow details from the Current Context. Failed because the Current Context was empty."
254
- logger.error(details)
255
288
  return GetFlowDetailsResultFailure(result_details=details)
256
289
  flow = GriptapeNodes.ContextManager().get_current_flow()
257
290
  flow_name = flow.name
@@ -261,14 +294,12 @@ class FlowManager:
261
294
  details = (
262
295
  f"Attempted to get Flow details for '{flow_name}'. Failed because no Flow with that name exists."
263
296
  )
264
- logger.error(details)
265
297
  return GetFlowDetailsResultFailure(result_details=details)
266
298
 
267
299
  try:
268
300
  parent_flow_name = self.get_parent_flow(flow_name)
269
301
  except ValueError:
270
302
  details = f"Attempted to get Flow details for '{flow_name}'. Failed because Flow does not exist in parent mapping."
271
- logger.error(details)
272
303
  return GetFlowDetailsResultFailure(result_details=details)
273
304
 
274
305
  referenced_workflow_name = None
@@ -276,10 +307,8 @@ class FlowManager:
276
307
  referenced_workflow_name = self.get_referenced_workflow_name(flow)
277
308
 
278
309
  details = f"Successfully retrieved Flow details for '{flow_name}'."
279
- logger.debug(details)
280
310
  return GetFlowDetailsResultSuccess(
281
- referenced_workflow_name=referenced_workflow_name,
282
- parent_flow_name=parent_flow_name,
311
+ referenced_workflow_name=referenced_workflow_name, parent_flow_name=parent_flow_name, result_details=details
283
312
  )
284
313
 
285
314
  def on_get_flow_metadata_request(self, request: GetFlowMetadataRequest) -> ResultPayload:
@@ -289,7 +318,6 @@ class FlowManager:
289
318
  # Get from the current context.
290
319
  if not GriptapeNodes.ContextManager().has_current_flow():
291
320
  details = "Attempted to get metadata for a Flow from the Current Context. Failed because the Current Context is empty."
292
- logger.error(details)
293
321
  return GetFlowMetadataResultFailure(result_details=details)
294
322
 
295
323
  flow = GriptapeNodes.ContextManager().get_current_flow()
@@ -301,14 +329,12 @@ class FlowManager:
301
329
  flow = obj_mgr.attempt_get_object_by_name_as_type(flow_name, ControlFlow)
302
330
  if flow is None:
303
331
  details = f"Attempted to get metadata for a Flow '{flow_name}', but no such Flow was found."
304
- logger.error(details)
305
332
  return GetFlowMetadataResultFailure(result_details=details)
306
333
 
307
334
  metadata = flow.metadata
308
335
  details = f"Successfully retrieved metadata for a Flow '{flow_name}'."
309
- logger.debug(details)
310
336
 
311
- return GetFlowMetadataResultSuccess(metadata=metadata)
337
+ return GetFlowMetadataResultSuccess(metadata=metadata, result_details=details)
312
338
 
313
339
  def on_set_flow_metadata_request(self, request: SetFlowMetadataRequest) -> ResultPayload:
314
340
  flow_name = request.flow_name
@@ -317,7 +343,6 @@ class FlowManager:
317
343
  # Get from the current context.
318
344
  if not GriptapeNodes.ContextManager().has_current_flow():
319
345
  details = "Attempted to set metadata for a Flow from the Current Context. Failed because the Current Context is empty."
320
- logger.error(details)
321
346
  return SetFlowMetadataResultFailure(result_details=details)
322
347
 
323
348
  flow = GriptapeNodes.ContextManager().get_current_flow()
@@ -329,16 +354,14 @@ class FlowManager:
329
354
  flow = obj_mgr.attempt_get_object_by_name_as_type(flow_name, ControlFlow)
330
355
  if flow is None:
331
356
  details = f"Attempted to set metadata for a Flow '{flow_name}', but no such Flow was found."
332
- logger.error(details)
333
357
  return SetFlowMetadataResultFailure(result_details=details)
334
358
 
335
359
  # We can't completely overwrite metadata.
336
360
  for key, value in request.metadata.items():
337
361
  flow.metadata[key] = value
338
362
  details = f"Successfully set metadata for a Flow '{flow_name}'."
339
- logger.debug(details)
340
363
 
341
- return SetFlowMetadataResultSuccess()
364
+ return SetFlowMetadataResultSuccess(result_details=details)
342
365
 
343
366
  def does_canvas_exist(self) -> bool:
344
367
  """Determines if there is already an existing flow with no parent flow.Returns True if there is an existing flow with no parent flow.Return False if there is no existing flow with no parent flow."""
@@ -368,13 +391,11 @@ class FlowManager:
368
391
  if self.does_canvas_exist():
369
392
  # We're trying to create the canvas. Ensure that parent does NOT already exist.
370
393
  details = "Attempted to create a Flow as the Canvas (top-level Flow with no parents), but the Canvas already exists."
371
- logger.error(details)
372
394
  result = CreateFlowResultFailure(result_details=details)
373
395
  return result
374
396
  # Now our parent exists, right?
375
397
  elif parent is None:
376
398
  details = f"Attempted to create a Flow with a parent '{request.parent_flow_name}', but no parent with that name could be found."
377
- logger.error(details)
378
399
 
379
400
  result = CreateFlowResultFailure(result_details=details)
380
401
 
@@ -383,7 +404,6 @@ class FlowManager:
383
404
  # We need to have a current workflow context to proceed.
384
405
  if not GriptapeNodes.ContextManager().has_current_workflow():
385
406
  details = "Attempted to create a Flow, but no Workflow was active in the Current Context."
386
- logger.error(details)
387
407
  return CreateFlowResultFailure(result_details=details)
388
408
 
389
409
  # Create it.
@@ -414,8 +434,9 @@ class FlowManager:
414
434
  details = f"{details} WARNING: Had to rename from original Flow requested '{request.flow_name}' as an object with this name already existed."
415
435
  log_level = logging.WARNING
416
436
 
417
- logger.log(level=log_level, msg=details)
418
- result = CreateFlowResultSuccess(flow_name=final_flow_name)
437
+ result = CreateFlowResultSuccess(
438
+ flow_name=final_flow_name, result_details=ResultDetails(message=details, level=log_level)
439
+ )
419
440
  return result
420
441
 
421
442
  # This needs to have a lot of branches to check the flow in all possible situations. In Current Context, or when the name is passed in.
@@ -428,7 +449,6 @@ class FlowManager:
428
449
  details = (
429
450
  "Attempted to delete a Flow from the Current Context. Failed because the Current Context was empty."
430
451
  )
431
- logger.error(details)
432
452
  result = DeleteFlowResultFailure(result_details=details)
433
453
  return result
434
454
  # We pop it off here, but we'll re-add it using context in a moment.
@@ -440,14 +460,12 @@ class FlowManager:
440
460
  flow = obj_mgr.attempt_get_object_by_name_as_type(flow_name, ControlFlow)
441
461
  if flow is None:
442
462
  details = f"Attempted to delete Flow '{flow_name}', but no Flow with that name could be found."
443
- logger.error(details)
444
463
  result = DeleteFlowResultFailure(result_details=details)
445
464
  return result
446
465
  if self.check_for_existing_running_flow():
447
466
  result = GriptapeNodes.handle_request(CancelFlowRequest(flow_name=flow.name))
448
467
  if not result.succeeded():
449
468
  details = f"Attempted to delete flow '{flow_name}'. Failed because running flow could not cancel."
450
- logger.error(details)
451
469
  return DeleteFlowResultFailure(result_details=details)
452
470
 
453
471
  # Let this Flow assume the Current Context while we delete everything within it.
@@ -457,7 +475,6 @@ class FlowManager:
457
475
  list_nodes_result = GriptapeNodes.handle_request(list_nodes_request)
458
476
  if not isinstance(list_nodes_result, ListNodesInFlowResultSuccess):
459
477
  details = f"Attempted to delete Flow '{flow.name}', but failed while attempting to get the list of Nodes owned by this Flow."
460
- logger.error(details)
461
478
  result = DeleteFlowResultFailure(result_details=details)
462
479
  return result
463
480
  node_names = list_nodes_result.node_names
@@ -466,7 +483,6 @@ class FlowManager:
466
483
  delete_node_result = GriptapeNodes.handle_request(delete_node_request)
467
484
  if isinstance(delete_node_result, DeleteNodeResultFailure):
468
485
  details = f"Attempted to delete Flow '{flow.name}', but failed while attempting to delete child Node '{node_name}'."
469
- logger.error(details)
470
486
  result = DeleteFlowResultFailure(result_details=details)
471
487
  return result
472
488
 
@@ -478,7 +494,6 @@ class FlowManager:
478
494
  list_flows_result = GriptapeNodes.handle_request(list_flows_request)
479
495
  if not isinstance(list_flows_result, ListFlowsInCurrentContextResultSuccess):
480
496
  details = f"Attempted to delete Flow '{flow_name}', but failed while attempting to get the list of Flows owned by this Flow."
481
- logger.error(details)
482
497
  result = DeleteFlowResultFailure(result_details=details)
483
498
  return result
484
499
  flow_names = list_flows_result.flow_names
@@ -489,7 +504,6 @@ class FlowManager:
489
504
  details = (
490
505
  f"Attempted to delete Flow '{child_flow_name}', but no Flow with that name could be found."
491
506
  )
492
- logger.error(details)
493
507
  result = DeleteFlowResultFailure(result_details=details)
494
508
  return result
495
509
  with GriptapeNodes.ContextManager().flow(flow=child_flow):
@@ -498,7 +512,6 @@ class FlowManager:
498
512
  delete_flow_result = GriptapeNodes.handle_request(delete_flow_request)
499
513
  if isinstance(delete_flow_result, DeleteFlowResultFailure):
500
514
  details = f"Attempted to delete Flow '{flow.name}', but failed while attempting to delete child Flow '{child_flow.name}'."
501
- logger.error(details)
502
515
  result = DeleteFlowResultFailure(result_details=details)
503
516
  return result
504
517
 
@@ -511,31 +524,33 @@ class FlowManager:
511
524
  if flow in self._flow_to_referenced_workflow_name:
512
525
  del self._flow_to_referenced_workflow_name[flow]
513
526
 
527
+ # Clean up ControlFlowMachine and DAG orchestrator for this flow
528
+ self._global_control_flow_machine = None
529
+ self._global_dag_builder.clear()
530
+
514
531
  details = f"Successfully deleted Flow '{flow_name}'."
515
- logger.debug(details)
516
- result = DeleteFlowResultSuccess()
532
+ result = DeleteFlowResultSuccess(result_details=details)
517
533
  return result
518
534
 
519
535
  def on_get_is_flow_running_request(self, request: GetIsFlowRunningRequest) -> ResultPayload:
520
536
  obj_mgr = GriptapeNodes.ObjectManager()
521
537
  if request.flow_name is None:
522
538
  details = "Attempted to get Flow, but no flow name was provided."
523
- logger.error(details)
524
539
  return GetIsFlowRunningResultFailure(result_details=details)
525
540
  flow = obj_mgr.attempt_get_object_by_name_as_type(request.flow_name, ControlFlow)
526
541
  if flow is None:
527
542
  details = f"Attempted to get Flow '{request.flow_name}', but no Flow with that name could be found."
528
- logger.error(details)
529
543
  result = GetIsFlowRunningResultFailure(result_details=details)
530
544
  return result
531
545
  try:
532
546
  is_running = self.check_for_existing_running_flow()
533
547
  except Exception:
534
548
  details = f"Error while trying to get status of '{request.flow_name}'."
535
- logger.error(details)
536
549
  result = GetIsFlowRunningResultFailure(result_details=details)
537
550
  return result
538
- return GetIsFlowRunningResultSuccess(is_running=is_running)
551
+ return GetIsFlowRunningResultSuccess(
552
+ is_running=is_running, result_details=f"Successfully checked if flow is running: {is_running}"
553
+ )
539
554
 
540
555
  def on_list_nodes_in_flow_request(self, request: ListNodesInFlowRequest) -> ResultPayload:
541
556
  flow_name = request.flow_name
@@ -544,7 +559,6 @@ class FlowManager:
544
559
  # First check if we have a current flow
545
560
  if not GriptapeNodes.ContextManager().has_current_flow():
546
561
  details = "Attempted to list Nodes in a Flow in the Current Context. Failed because the Current Context was empty."
547
- logger.error(details)
548
562
  result = ListNodesInFlowResultFailure(result_details=details)
549
563
  return result
550
564
  # Get the current flow from context
@@ -558,15 +572,13 @@ class FlowManager:
558
572
  details = (
559
573
  f"Attempted to list Nodes in Flow '{flow_name}'. Failed because no Flow with that name could be found."
560
574
  )
561
- logger.error(details)
562
575
  result = ListNodesInFlowResultFailure(result_details=details)
563
576
  return result
564
577
 
565
578
  ret_list = list(flow.nodes.keys())
566
579
  details = f"Successfully got the list of Nodes within Flow '{flow_name}'."
567
- logger.debug(details)
568
580
 
569
- result = ListNodesInFlowResultSuccess(node_names=ret_list)
581
+ result = ListNodesInFlowResultSuccess(node_names=ret_list, result_details=details)
570
582
  return result
571
583
 
572
584
  def on_list_flows_in_flow_request(self, request: ListFlowsInFlowRequest) -> ResultPayload:
@@ -576,7 +588,6 @@ class FlowManager:
576
588
  flow = obj_mgr.attempt_get_object_by_name_as_type(request.parent_flow_name, ControlFlow)
577
589
  if flow is None:
578
590
  details = f"Attempted to list Flows that are children of Flow '{request.parent_flow_name}', but no Flow with that name could be found."
579
- logger.error(details)
580
591
  result = ListFlowsInFlowResultFailure(result_details=details)
581
592
  return result
582
593
 
@@ -587,9 +598,8 @@ class FlowManager:
587
598
  ret_list.append(flow_name)
588
599
 
589
600
  details = f"Successfully got the list of Flows that are direct children of Flow '{request.parent_flow_name}'."
590
- logger.debug(details)
591
601
 
592
- result = ListFlowsInFlowResultSuccess(flow_names=ret_list)
602
+ result = ListFlowsInFlowResultSuccess(flow_names=ret_list, result_details=details)
593
603
  return result
594
604
 
595
605
  def get_flow_by_name(self, flow_name: str) -> ControlFlow:
@@ -623,7 +633,6 @@ class FlowManager:
623
633
  # First check if we have a current node
624
634
  if not GriptapeNodes.ContextManager().has_current_node():
625
635
  details = "Attempted to create a Connection with a source node from the Current Context. Failed because the Current Context was empty."
626
- logger.error(details)
627
636
  return CreateConnectionResultFailure(result_details=details)
628
637
 
629
638
  # Get the current node from context
@@ -634,7 +643,6 @@ class FlowManager:
634
643
  source_node = GriptapeNodes.NodeManager().get_node_by_name(source_node_name)
635
644
  except ValueError as err:
636
645
  details = f'Connection failed: "{source_node_name}" does not exist. Error: {err}.'
637
- logger.error(details)
638
646
 
639
647
  return CreateConnectionResultFailure(result_details=details)
640
648
 
@@ -644,7 +652,6 @@ class FlowManager:
644
652
  # First check if we have a current node
645
653
  if not GriptapeNodes.ContextManager().has_current_node():
646
654
  details = "Attempted to create a Connection with the target node from the Current Context. Failed because the Current Context was empty."
647
- logger.error(details)
648
655
  return CreateConnectionResultFailure(result_details=details)
649
656
 
650
657
  # Get the current node from context
@@ -655,7 +662,6 @@ class FlowManager:
655
662
  target_node = GriptapeNodes.NodeManager().get_node_by_name(target_node_name)
656
663
  except ValueError as err:
657
664
  details = f'Connection failed: "{target_node_name}" does not exist. Error: {err}.'
658
- logger.error(details)
659
665
  return CreateConnectionResultFailure(result_details=details)
660
666
 
661
667
  # The two nodes exist.
@@ -666,7 +672,6 @@ class FlowManager:
666
672
  self.get_flow_by_name(flow_name=source_flow_name)
667
673
  except KeyError as err:
668
674
  details = f'Connection "{source_node_name}.{request.source_parameter_name}" to "{target_node_name}.{request.target_parameter_name}" failed: {err}.'
669
- logger.error(details)
670
675
  return CreateConnectionResultFailure(result_details=details)
671
676
 
672
677
  target_flow_name = None
@@ -675,7 +680,6 @@ class FlowManager:
675
680
  self.get_flow_by_name(flow_name=target_flow_name)
676
681
  except KeyError as err:
677
682
  details = f'Connection "{source_node_name}.{request.source_parameter_name}" to "{target_node_name}.{request.target_parameter_name}" failed: {err}.'
678
- logger.error(details)
679
683
  return CreateConnectionResultFailure(result_details=details)
680
684
 
681
685
  # Cross-flow connections are now supported via global connection storage
@@ -696,14 +700,12 @@ class FlowManager:
696
700
  source_param = source_node.get_parameter_by_name(request.source_parameter_name)
697
701
  if source_param is None:
698
702
  details = f'Connection failed: "{source_node_name}.{request.source_parameter_name}" not found'
699
- logger.error(details)
700
703
  return CreateConnectionResultFailure(result_details=details)
701
704
 
702
705
  target_param = target_node.get_parameter_by_name(request.target_parameter_name)
703
706
  if target_param is None:
704
707
  # TODO: https://github.com/griptape-ai/griptape-nodes/issues/860
705
708
  details = f'Connection failed: "{target_node_name}.{request.target_parameter_name}" not found'
706
- logger.error(details)
707
709
  return CreateConnectionResultFailure(result_details=details)
708
710
  # Validate parameter modes accept this type of connection.
709
711
  source_modes_allowed = source_param.allowed_modes
@@ -711,19 +713,16 @@ class FlowManager:
711
713
  details = (
712
714
  f'Connection failed: "{source_node_name}.{request.source_parameter_name}" is not an allowed OUTPUT'
713
715
  )
714
- logger.error(details)
715
716
  return CreateConnectionResultFailure(result_details=details)
716
717
 
717
718
  target_modes_allowed = target_param.allowed_modes
718
719
  if ParameterMode.INPUT not in target_modes_allowed:
719
720
  details = f'Connection failed: "{target_node_name}.{request.target_parameter_name}" is not an allowed INPUT'
720
- logger.error(details)
721
721
  return CreateConnectionResultFailure(result_details=details)
722
722
 
723
723
  # Validate that the data type from the source is allowed by the target.
724
724
  if not target_param.is_incoming_type_allowed(source_param.output_type):
725
725
  details = f'Connection failed on type mismatch "{source_node_name}.{request.source_parameter_name}" type({source_param.output_type}) to "{target_node_name}.{request.target_parameter_name}" types({target_param.input_types}) '
726
- logger.error(details)
727
726
  return CreateConnectionResultFailure(result_details=details)
728
727
 
729
728
  # Ask each node involved to bless this union.
@@ -735,7 +734,6 @@ class FlowManager:
735
734
  details = (
736
735
  f'Connection failed : "{source_node_name}.{request.source_parameter_name}" rejected the connection '
737
736
  )
738
- logger.error(details)
739
737
  return CreateConnectionResultFailure(result_details=details)
740
738
 
741
739
  if not target_node.allow_incoming_connection(
@@ -746,7 +744,6 @@ class FlowManager:
746
744
  details = (
747
745
  f'Connection failed : "{target_node_name}.{request.target_parameter_name}" rejected the connection '
748
746
  )
749
- logger.error(details)
750
747
  return CreateConnectionResultFailure(result_details=details)
751
748
 
752
749
  # Based on user feedback, if a connection already exists in a scenario where only ONE such connection can exist
@@ -786,11 +783,9 @@ class FlowManager:
786
783
  delete_old_result = GriptapeNodes.handle_request(delete_old_request)
787
784
  if delete_old_result.failed():
788
785
  details = f"Attempted to connect '{source_node_name}.{request.source_parameter_name}'. Failed because there was a previous connection from '{old_source_node_name}.{old_source_param_name}' to '{old_target_node_name}.{old_target_param_name}' that could not be deleted."
789
- logger.error(details)
790
786
  return CreateConnectionResultFailure(result_details=details)
791
787
 
792
788
  details = f"Deleted the previous connection from '{old_source_node_name}.{old_source_param_name}' to '{old_target_node_name}.{old_target_param_name}' to make room for the new connection."
793
- logger.debug(details)
794
789
  try:
795
790
  # Actually create the Connection.
796
791
  self._connections.add_connection(
@@ -801,7 +796,6 @@ class FlowManager:
801
796
  )
802
797
  except ValueError as e:
803
798
  details = f'Connection failed: "{e}"'
804
- logger.error(details)
805
799
 
806
800
  # Attempt to restore any old connection that may have been present.
807
801
  if (
@@ -820,7 +814,6 @@ class FlowManager:
820
814
  create_old_connection_result = GriptapeNodes.handle_request(create_old_connection_request)
821
815
  if create_old_connection_result.failed():
822
816
  details = "Failed attempting to restore the old Connection after failing the replacement. A thousand pardons."
823
- logger.error(details)
824
817
  return CreateConnectionResultFailure(result_details=details)
825
818
 
826
819
  # Let the source make any internal handling decisions now that the Connection has been made.
@@ -836,7 +829,6 @@ class FlowManager:
836
829
  )
837
830
 
838
831
  details = f'Connected "{source_node_name}.{request.source_parameter_name}" to "{target_node_name}.{request.target_parameter_name}"'
839
- logger.debug(details)
840
832
 
841
833
  # Now update the parameter values if it exists.
842
834
  # check if it's been resolved/has a value in parameter_output_values
@@ -879,7 +871,7 @@ class FlowManager:
879
871
  if isinstance(target_node, ErrorProxyNode):
880
872
  target_node.set_post_init_connections_modified()
881
873
 
882
- result = CreateConnectionResultSuccess()
874
+ result = CreateConnectionResultSuccess(result_details=details)
883
875
 
884
876
  return result
885
877
 
@@ -893,7 +885,6 @@ class FlowManager:
893
885
  # First check if we have a current node
894
886
  if not GriptapeNodes.ContextManager().has_current_node():
895
887
  details = "Attempted to delete a Connection with a source node from the Current Context. Failed because the Current Context was empty."
896
- logger.error(details)
897
888
  return DeleteConnectionResultFailure(result_details=details)
898
889
 
899
890
  # Get the current node from context
@@ -904,7 +895,6 @@ class FlowManager:
904
895
  source_node = GriptapeNodes.NodeManager().get_node_by_name(source_node_name)
905
896
  except ValueError as err:
906
897
  details = f'Connection not deleted "{source_node_name}.{request.source_parameter_name}" to "{target_node_name}.{request.target_parameter_name}". Error: {err}'
907
- logger.error(details)
908
898
 
909
899
  return DeleteConnectionResultFailure(result_details=details)
910
900
 
@@ -913,7 +903,6 @@ class FlowManager:
913
903
  # First check if we have a current node
914
904
  if not GriptapeNodes.ContextManager().has_current_node():
915
905
  details = "Attempted to delete a Connection with a target node from the Current Context. Failed because the Current Context was empty."
916
- logger.error(details)
917
906
  return DeleteConnectionResultFailure(result_details=details)
918
907
 
919
908
  # Get the current node from context
@@ -924,7 +913,6 @@ class FlowManager:
924
913
  target_node = GriptapeNodes.NodeManager().get_node_by_name(target_node_name)
925
914
  except ValueError as err:
926
915
  details = f'Connection not deleted "{source_node_name}.{request.source_parameter_name}" to "{target_node_name}.{request.target_parameter_name}". Error: {err}'
927
- logger.error(details)
928
916
 
929
917
  return DeleteConnectionResultFailure(result_details=details)
930
918
 
@@ -936,7 +924,6 @@ class FlowManager:
936
924
  self.get_flow_by_name(flow_name=source_flow_name)
937
925
  except KeyError as err:
938
926
  details = f'Connection not deleted "{source_node_name}.{request.source_parameter_name}" to "{target_node_name}.{request.target_parameter_name}". Error: {err}'
939
- logger.error(details)
940
927
 
941
928
  return DeleteConnectionResultFailure(result_details=details)
942
929
 
@@ -946,7 +933,6 @@ class FlowManager:
946
933
  self.get_flow_by_name(flow_name=target_flow_name)
947
934
  except KeyError as err:
948
935
  details = f'Connection not deleted "{source_node_name}.{request.source_parameter_name}" to "{target_node_name}.{request.target_parameter_name}". Error: {err}'
949
- logger.error(details)
950
936
 
951
937
  return DeleteConnectionResultFailure(result_details=details)
952
938
 
@@ -956,14 +942,12 @@ class FlowManager:
956
942
  source_param = source_node.get_parameter_by_name(request.source_parameter_name)
957
943
  if source_param is None:
958
944
  details = f'Connection not deleted "{source_node_name}.{request.source_parameter_name}" Not found.'
959
- logger.error(details)
960
945
 
961
946
  return DeleteConnectionResultFailure(result_details=details)
962
947
 
963
948
  target_param = target_node.get_parameter_by_name(request.target_parameter_name)
964
949
  if target_param is None:
965
950
  details = f'Connection not deleted "{target_node_name}.{request.target_parameter_name}" Not found.'
966
- logger.error(details)
967
951
 
968
952
  return DeleteConnectionResultFailure(result_details=details)
969
953
 
@@ -975,7 +959,6 @@ class FlowManager:
975
959
  target_parameter=target_param,
976
960
  ):
977
961
  details = f'Connection does not exist: "{source_node_name}.{request.source_parameter_name}" to "{target_node_name}.{request.target_parameter_name}"'
978
- logger.error(details)
979
962
 
980
963
  return DeleteConnectionResultFailure(result_details=details)
981
964
 
@@ -987,7 +970,6 @@ class FlowManager:
987
970
  target_parameter=target_param.name,
988
971
  ):
989
972
  details = f'Connection not deleted "{source_node_name}.{request.source_parameter_name}" to "{target_node_name}.{request.target_parameter_name}". Unknown failure.'
990
- logger.error(details)
991
973
 
992
974
  return DeleteConnectionResultFailure(result_details=details)
993
975
 
@@ -1020,7 +1002,6 @@ class FlowManager:
1020
1002
  )
1021
1003
 
1022
1004
  details = f'Connection "{source_node_name}.{request.source_parameter_name}" to "{target_node_name}.{request.target_parameter_name}" deleted.'
1023
- logger.debug(details)
1024
1005
 
1025
1006
  # Check if either node is ErrorProxyNode and mark connection modification (deletes are always user-initiated)
1026
1007
  if isinstance(source_node, ErrorProxyNode):
@@ -1028,16 +1009,14 @@ class FlowManager:
1028
1009
  if isinstance(target_node, ErrorProxyNode):
1029
1010
  target_node.set_post_init_connections_modified()
1030
1011
 
1031
- result = DeleteConnectionResultSuccess()
1012
+ result = DeleteConnectionResultSuccess(result_details=details)
1032
1013
  return result
1033
1014
 
1034
1015
  async def on_start_flow_request(self, request: StartFlowRequest) -> ResultPayload: # noqa: C901, PLR0911, PLR0912
1035
1016
  # which flow
1036
1017
  flow_name = request.flow_name
1037
- debug_mode = request.debug_mode
1038
1018
  if not flow_name:
1039
1019
  details = "Must provide flow name to start a flow."
1040
- logger.error(details)
1041
1020
 
1042
1021
  return StartFlowResultFailure(validation_exceptions=[], result_details=details)
1043
1022
  # get the flow by ID
@@ -1045,12 +1024,10 @@ class FlowManager:
1045
1024
  flow = self.get_flow_by_name(flow_name)
1046
1025
  except KeyError as err:
1047
1026
  details = f"Cannot start flow. Error: {err}"
1048
- logger.error(details)
1049
1027
  return StartFlowResultFailure(validation_exceptions=[err], result_details=details)
1050
1028
  # Check to see if the flow is already running.
1051
1029
  if self.check_for_existing_running_flow():
1052
1030
  details = "Cannot start flow. Flow is already running."
1053
- logger.error(details)
1054
1031
  return StartFlowResultFailure(validation_exceptions=[], result_details=details)
1055
1032
  # A node has been provided to either start or to run up to.
1056
1033
  if request.flow_node_name:
@@ -1058,14 +1035,12 @@ class FlowManager:
1058
1035
  flow_node = GriptapeNodes.ObjectManager().attempt_get_object_by_name_as_type(flow_node_name, BaseNode)
1059
1036
  if not flow_node:
1060
1037
  details = f"Provided node with name {flow_node_name} does not exist"
1061
- logger.error(details)
1062
1038
  return StartFlowResultFailure(validation_exceptions=[], result_details=details)
1063
1039
  # lets get the first control node in the flow!
1064
1040
  start_node = self.get_start_node_from_node(flow, flow_node)
1065
1041
  # if the start is not the node provided, set a breakpoint at the stop (we're running up until there)
1066
1042
  if not start_node:
1067
1043
  details = f"Start node for node with name {flow_node_name} does not exist"
1068
- logger.error(details)
1069
1044
  return StartFlowResultFailure(validation_exceptions=[], result_details=details)
1070
1045
  if start_node != flow_node:
1071
1046
  flow_node.stop_flow = True
@@ -1081,8 +1056,7 @@ class FlowManager:
1081
1056
  try:
1082
1057
  if not result.succeeded():
1083
1058
  details = f"Couldn't start flow with name {flow_name}. Flow Validation Failed"
1084
- logger.error(details)
1085
- return StartFlowResultFailure(validation_exceptions=[])
1059
+ return StartFlowResultFailure(validation_exceptions=[], result_details=details)
1086
1060
  result = cast("ValidateFlowDependenciesResultSuccess", result)
1087
1061
 
1088
1062
  if not result.validation_succeeded:
@@ -1090,85 +1064,74 @@ class FlowManager:
1090
1064
  if len(result.exceptions) > 0:
1091
1065
  for exception in result.exceptions:
1092
1066
  details = f"{details}\n\t{exception}"
1093
- logger.error(details)
1094
1067
  return StartFlowResultFailure(validation_exceptions=result.exceptions, result_details=details)
1095
1068
  except Exception as e:
1096
1069
  details = f"Couldn't start flow with name {flow_name}. Flow Validation Failed: {e}"
1097
- logger.error(details)
1098
1070
  return StartFlowResultFailure(validation_exceptions=[e], result_details=details)
1099
1071
  # By now, it has been validated with no exceptions.
1100
1072
  try:
1101
- await self.start_flow(flow, start_node, debug_mode)
1073
+ await self.start_flow(flow, start_node, debug_mode=request.debug_mode)
1102
1074
  except Exception as e:
1103
1075
  details = f"Failed to kick off flow with name {flow_name}. Exception occurred: {e} "
1104
- logger.error(details)
1105
1076
  return StartFlowResultFailure(validation_exceptions=[e], result_details=details)
1106
1077
 
1107
1078
  details = f"Successfully kicked off flow with name {flow_name}"
1108
- logger.debug(details)
1109
1079
 
1110
- return StartFlowResultSuccess()
1080
+ return StartFlowResultSuccess(result_details=details)
1111
1081
 
1112
1082
  def on_get_flow_state_request(self, event: GetFlowStateRequest) -> ResultPayload:
1113
1083
  flow_name = event.flow_name
1114
1084
  if not flow_name:
1115
1085
  details = "Could not get flow state. No flow name was provided."
1116
- logger.error(details)
1117
1086
  return GetFlowStateResultFailure(result_details=details)
1118
1087
  try:
1119
1088
  flow = self.get_flow_by_name(flow_name)
1120
1089
  except KeyError as err:
1121
1090
  details = f"Could not get flow state. Error: {err}"
1122
- logger.error(details)
1123
1091
  return GetFlowStateResultFailure(result_details=details)
1124
1092
  try:
1125
- control_node, resolving_node = self.flow_state(flow)
1093
+ control_nodes, resolving_nodes = self.flow_state(flow)
1126
1094
  except Exception as e:
1127
1095
  details = f"Failed to get flow state of flow with name {flow_name}. Exception occurred: {e} "
1128
1096
  logger.exception(details)
1129
1097
  return GetFlowStateResultFailure(result_details=details)
1130
1098
  details = f"Successfully got flow state for flow with name {flow_name}."
1131
- logger.debug(details)
1132
- return GetFlowStateResultSuccess(control_node=control_node, resolving_node=resolving_node)
1099
+ return GetFlowStateResultSuccess(
1100
+ control_nodes=control_nodes, resolving_node=resolving_nodes, result_details=details
1101
+ )
1133
1102
 
1134
1103
  def on_cancel_flow_request(self, request: CancelFlowRequest) -> ResultPayload:
1135
1104
  flow_name = request.flow_name
1136
1105
  if not flow_name:
1137
1106
  details = "Could not cancel flow execution. No flow name was provided."
1138
- logger.error(details)
1139
1107
 
1140
1108
  return CancelFlowResultFailure(result_details=details)
1141
1109
  try:
1142
1110
  self.get_flow_by_name(flow_name)
1143
1111
  except KeyError as err:
1144
1112
  details = f"Could not cancel flow execution. Error: {err}"
1145
- logger.error(details)
1146
1113
 
1147
1114
  return CancelFlowResultFailure(result_details=details)
1148
1115
  try:
1149
1116
  self.cancel_flow_run()
1150
1117
  except Exception as e:
1151
1118
  details = f"Could not cancel flow execution. Exception: {e}"
1152
- logger.error(details)
1153
1119
 
1154
1120
  return CancelFlowResultFailure(result_details=details)
1155
1121
  details = f"Successfully cancelled flow execution with name {flow_name}"
1156
- logger.debug(details)
1157
1122
 
1158
- return CancelFlowResultSuccess()
1123
+ return CancelFlowResultSuccess(result_details=details)
1159
1124
 
1160
1125
  async def on_single_node_step_request(self, request: SingleNodeStepRequest) -> ResultPayload:
1161
1126
  flow_name = request.flow_name
1162
1127
  if not flow_name:
1163
1128
  details = "Could not advance to the next step of a running workflow. No flow name was provided."
1164
- logger.error(details)
1165
1129
 
1166
1130
  return SingleNodeStepResultFailure(validation_exceptions=[], result_details=details)
1167
1131
  try:
1168
1132
  self.get_flow_by_name(flow_name)
1169
1133
  except KeyError as err:
1170
1134
  details = f"Could not advance to the next step of a running workflow. No flow with name {flow_name} exists. Error: {err}"
1171
- logger.error(details)
1172
1135
 
1173
1136
  return SingleNodeStepResultFailure(validation_exceptions=[err], result_details=details)
1174
1137
  try:
@@ -1176,27 +1139,23 @@ class FlowManager:
1176
1139
  await self.single_node_step(flow)
1177
1140
  except Exception as e:
1178
1141
  details = f"Could not advance to the next step of a running workflow. Exception: {e}"
1179
- logger.error(details)
1180
1142
  return SingleNodeStepResultFailure(validation_exceptions=[], result_details=details)
1181
1143
 
1182
1144
  # All completed happily
1183
1145
  details = f"Successfully advanced to the next step of a running workflow with name {flow_name}"
1184
- logger.debug(details)
1185
1146
 
1186
- return SingleNodeStepResultSuccess()
1147
+ return SingleNodeStepResultSuccess(result_details=details)
1187
1148
 
1188
1149
  async def on_single_execution_step_request(self, request: SingleExecutionStepRequest) -> ResultPayload:
1189
1150
  flow_name = request.flow_name
1190
1151
  if not flow_name:
1191
1152
  details = "Could not advance to the next step of a running workflow. No flow name was provided."
1192
- logger.error(details)
1193
1153
 
1194
1154
  return SingleExecutionStepResultFailure(result_details=details)
1195
1155
  try:
1196
1156
  flow = self.get_flow_by_name(flow_name)
1197
1157
  except KeyError as err:
1198
1158
  details = f"Could not advance to the next step of a running workflow. Error: {err}."
1199
- logger.error(details)
1200
1159
 
1201
1160
  return SingleExecutionStepResultFailure(result_details=details)
1202
1161
  change_debug_mode = request.request_id is not None
@@ -1209,61 +1168,50 @@ class FlowManager:
1209
1168
  self.cancel_flow_run()
1210
1169
  except Exception as e_inner:
1211
1170
  details = f"Could not cancel flow execution. Exception: {e_inner}"
1212
- logger.error(details)
1213
1171
 
1214
1172
  details = f"Could not advance to the next step of a running workflow. Exception: {e}"
1215
- logger.error(details)
1216
1173
  return SingleNodeStepResultFailure(validation_exceptions=[e], result_details=details)
1217
1174
  details = f"Successfully advanced to the next step of a running workflow with name {flow_name}"
1218
- logger.debug(details)
1219
1175
 
1220
- return SingleExecutionStepResultSuccess()
1176
+ return SingleExecutionStepResultSuccess(result_details=details)
1221
1177
 
1222
1178
  async def on_continue_execution_step_request(self, request: ContinueExecutionStepRequest) -> ResultPayload:
1223
1179
  flow_name = request.flow_name
1224
1180
  if not flow_name:
1225
1181
  details = "Failed to continue execution step because no flow name was provided"
1226
- logger.error(details)
1227
1182
 
1228
1183
  return ContinueExecutionStepResultFailure(result_details=details)
1229
1184
  try:
1230
1185
  flow = self.get_flow_by_name(flow_name)
1231
1186
  except KeyError as err:
1232
1187
  details = f"Failed to continue execution step. Error: {err}"
1233
- logger.error(details)
1234
1188
 
1235
1189
  return ContinueExecutionStepResultFailure(result_details=details)
1236
1190
  try:
1237
1191
  await self.continue_executing(flow)
1238
1192
  except Exception as e:
1239
1193
  details = f"Failed to continue execution step. An exception occurred: {e}."
1240
- logger.error(details)
1241
1194
  return ContinueExecutionStepResultFailure(result_details=details)
1242
1195
  details = f"Successfully continued flow with name {flow_name}"
1243
- logger.debug(details)
1244
- return ContinueExecutionStepResultSuccess()
1196
+ return ContinueExecutionStepResultSuccess(result_details=details)
1245
1197
 
1246
1198
  def on_unresolve_flow_request(self, request: UnresolveFlowRequest) -> ResultPayload:
1247
1199
  flow_name = request.flow_name
1248
1200
  if not flow_name:
1249
1201
  details = "Failed to unresolve flow because no flow name was provided"
1250
- logger.error(details)
1251
1202
  return UnresolveFlowResultFailure(result_details=details)
1252
1203
  try:
1253
1204
  flow = self.get_flow_by_name(flow_name)
1254
1205
  except KeyError as err:
1255
1206
  details = f"Failed to unresolve flow. Error: {err}"
1256
- logger.error(details)
1257
1207
  return UnresolveFlowResultFailure(result_details=details)
1258
1208
  try:
1259
1209
  self.unresolve_whole_flow(flow)
1260
1210
  except Exception as e:
1261
1211
  details = f"Failed to unresolve flow. An exception occurred: {e}."
1262
- logger.error(details)
1263
1212
  return UnresolveFlowResultFailure(result_details=details)
1264
1213
  details = f"Unresolved flow with name {flow_name}"
1265
- logger.debug(details)
1266
- return UnresolveFlowResultSuccess()
1214
+ return UnresolveFlowResultSuccess(result_details=details)
1267
1215
 
1268
1216
  async def on_validate_flow_dependencies_request(self, request: ValidateFlowDependenciesRequest) -> ResultPayload:
1269
1217
  flow_name = request.flow_name
@@ -1272,14 +1220,12 @@ class FlowManager:
1272
1220
  flow = self.get_flow_by_name(flow_name)
1273
1221
  except KeyError as err:
1274
1222
  details = f"Failed to validate flow. Error: {err}"
1275
- logger.error(details)
1276
1223
  return ValidateFlowDependenciesResultFailure(result_details=details)
1277
1224
  if request.flow_node_name:
1278
1225
  flow_node_name = request.flow_node_name
1279
1226
  flow_node = GriptapeNodes.ObjectManager().attempt_get_object_by_name_as_type(flow_node_name, BaseNode)
1280
1227
  if not flow_node:
1281
1228
  details = f"Provided node with name {flow_node_name} does not exist"
1282
- logger.error(details)
1283
1229
  return ValidateFlowDependenciesResultFailure(result_details=details)
1284
1230
  # Gets all nodes in that connected group to be ran
1285
1231
  nodes = flow.get_all_connected_nodes(flow_node)
@@ -1292,13 +1238,14 @@ class FlowManager:
1292
1238
  if exceptions:
1293
1239
  all_exceptions = all_exceptions + exceptions
1294
1240
  return ValidateFlowDependenciesResultSuccess(
1295
- validation_succeeded=len(all_exceptions) == 0, exceptions=all_exceptions
1241
+ validation_succeeded=len(all_exceptions) == 0,
1242
+ exceptions=all_exceptions,
1243
+ result_details=f"Validated flow dependencies: {len(all_exceptions)} exceptions found",
1296
1244
  )
1297
1245
 
1298
1246
  def on_list_flows_in_current_context_request(self, request: ListFlowsInCurrentContextRequest) -> ResultPayload: # noqa: ARG002 (request isn't actually used)
1299
1247
  if not GriptapeNodes.ContextManager().has_current_flow():
1300
1248
  details = "Attempted to list Flows in the Current Context. Failed because the Current Context was empty."
1301
- logger.error(details)
1302
1249
  return ListFlowsInCurrentContextResultFailure(result_details=details)
1303
1250
 
1304
1251
  parent_flow = GriptapeNodes.ContextManager().get_current_flow()
@@ -1311,9 +1258,8 @@ class FlowManager:
1311
1258
  ret_list.append(flow_name)
1312
1259
 
1313
1260
  details = f"Successfully got the list of Flows in the Current Context (Flow '{parent_flow_name}')."
1314
- logger.debug(details)
1315
1261
 
1316
- return ListFlowsInCurrentContextResultSuccess(flow_names=ret_list)
1262
+ return ListFlowsInCurrentContextResultSuccess(flow_names=ret_list, result_details=details)
1317
1263
 
1318
1264
  # TODO: https://github.com/griptape-ai/griptape-nodes/issues/861
1319
1265
  # similar manager refactors: https://github.com/griptape-ai/griptape-nodes/issues/806
@@ -1326,7 +1272,6 @@ class FlowManager:
1326
1272
  flow_name = flow.name
1327
1273
  else:
1328
1274
  details = "Attempted to serialize a Flow to commands from the Current Context. Failed because the Current Context was empty."
1329
- logger.error(details)
1330
1275
  return SerializeFlowToCommandsResultFailure(result_details=details)
1331
1276
  if flow is None:
1332
1277
  # Does this flow exist?
@@ -1335,7 +1280,6 @@ class FlowManager:
1335
1280
  details = (
1336
1281
  f"Attempted to serialize Flow '{flow_name}' to commands, but no Flow with that name could be found."
1337
1282
  )
1338
- logger.error(details)
1339
1283
  return SerializeFlowToCommandsResultFailure(result_details=details)
1340
1284
 
1341
1285
  # Track all node libraries that were in use by these Nodes
@@ -1381,7 +1325,6 @@ class FlowManager:
1381
1325
  details = (
1382
1326
  f"Attempted to serialize Flow '{flow_name}'. Failed while attempting to list Nodes in the Flow."
1383
1327
  )
1384
- logger.error(details)
1385
1328
  return SerializeFlowToCommandsResultFailure(result_details=details)
1386
1329
 
1387
1330
  # Serialize each node
@@ -1389,7 +1332,6 @@ class FlowManager:
1389
1332
  node = GriptapeNodes.ObjectManager().attempt_get_object_by_name_as_type(node_name, BaseNode)
1390
1333
  if node is None:
1391
1334
  details = f"Attempted to serialize Flow '{flow_name}'. Failed while attempting to serialize Node '{node_name}' within the Flow."
1392
- logger.error(details)
1393
1335
  return SerializeFlowToCommandsResultFailure(result_details=details)
1394
1336
  with GriptapeNodes.ContextManager().node(node):
1395
1337
  # Note: the parameter value stuff is pass-by-reference, and we expect the values to be modified in place.
@@ -1401,7 +1343,6 @@ class FlowManager:
1401
1343
  serialize_node_result = GriptapeNodes.handle_request(serialize_node_request)
1402
1344
  if not isinstance(serialize_node_result, SerializeNodeToCommandsResultSuccess):
1403
1345
  details = f"Attempted to serialize Flow '{flow_name}'. Failed while attempting to serialize Node '{node_name}' within the Flow."
1404
- logger.error(details)
1405
1346
  return SerializeFlowToCommandsResultFailure(result_details=details)
1406
1347
 
1407
1348
  serialized_node = serialize_node_result.serialized_node_commands
@@ -1441,7 +1382,6 @@ class FlowManager:
1441
1382
  flows_in_flow_result = GriptapeNodes().handle_request(flows_in_flow_request)
1442
1383
  if not isinstance(flows_in_flow_result, ListFlowsInFlowResultSuccess):
1443
1384
  details = f"Attempted to serialize Flow '{flow_name}'. Failed while attempting to list child Flows in the Flow."
1444
- logger.error(details)
1445
1385
  return SerializeFlowToCommandsResultFailure(result_details=details)
1446
1386
 
1447
1387
  sub_flow_commands = []
@@ -1449,7 +1389,6 @@ class FlowManager:
1449
1389
  flow = GriptapeNodes.ObjectManager().attempt_get_object_by_name_as_type(child_flow, ControlFlow)
1450
1390
  if flow is None:
1451
1391
  details = f"Attempted to serialize Flow '{flow_name}', but no Flow with that name could be found."
1452
- logger.error(details)
1453
1392
  return SerializeFlowToCommandsResultFailure(result_details=details)
1454
1393
 
1455
1394
  # Check if this is a referenced workflow
@@ -1482,8 +1421,7 @@ class FlowManager:
1482
1421
  child_flow_result = GriptapeNodes().handle_request(child_flow_request)
1483
1422
  if not isinstance(child_flow_result, SerializeFlowToCommandsResultSuccess):
1484
1423
  details = f"Attempted to serialize parent flow '{flow_name}'. Failed while serializing child flow '{child_flow}'."
1485
- logger.error(details)
1486
- return SerializeFlowToCommandsResultFailure()
1424
+ return SerializeFlowToCommandsResultFailure(result_details=details)
1487
1425
  serialized_flow = child_flow_result.serialized_flow_commands
1488
1426
  sub_flow_commands.append(serialized_flow)
1489
1427
 
@@ -1505,7 +1443,7 @@ class FlowManager:
1505
1443
  referenced_workflows=referenced_workflows_in_use,
1506
1444
  )
1507
1445
  details = f"Successfully serialized Flow '{flow_name}' into commands."
1508
- result = SerializeFlowToCommandsResultSuccess(serialized_flow_commands=serialized_flow)
1446
+ result = SerializeFlowToCommandsResultSuccess(serialized_flow_commands=serialized_flow, result_details=details)
1509
1447
  return result
1510
1448
 
1511
1449
  def on_deserialize_flow_from_commands(self, request: DeserializeFlowFromCommandsRequest) -> ResultPayload: # noqa: C901, PLR0911, PLR0912, PLR0915 (I am big and complicated and have a lot of negative edge-cases)
@@ -1516,7 +1454,6 @@ class FlowManager:
1516
1454
  flow_name = flow.name
1517
1455
  else:
1518
1456
  details = "Attempted to deserialize a set of Flow Creation commands into the Current Context. Failed because the Current Context was empty."
1519
- logger.error(details)
1520
1457
  return DeserializeFlowFromCommandsResultFailure(result_details=details)
1521
1458
  else:
1522
1459
  # Issue the creation command first.
@@ -1528,25 +1465,21 @@ class FlowManager:
1528
1465
  case CreateFlowRequest():
1529
1466
  if not isinstance(flow_initialization_result, CreateFlowResultSuccess):
1530
1467
  details = f"Attempted to deserialize a serialized set of Flow Creation commands. Failed to create flow '{flow_initialization_command.flow_name}'."
1531
- logger.error(details)
1532
1468
  return DeserializeFlowFromCommandsResultFailure(result_details=details)
1533
1469
  flow_name = flow_initialization_result.flow_name
1534
1470
  case ImportWorkflowAsReferencedSubFlowRequest():
1535
1471
  if not isinstance(flow_initialization_result, ImportWorkflowAsReferencedSubFlowResultSuccess):
1536
1472
  details = f"Attempted to deserialize a serialized set of Flow Creation commands. Failed to import workflow '{flow_initialization_command.workflow_name}'."
1537
- logger.error(details)
1538
1473
  return DeserializeFlowFromCommandsResultFailure(result_details=details)
1539
1474
  flow_name = flow_initialization_result.created_flow_name
1540
1475
  case _:
1541
1476
  details = f"Attempted to deserialize Flow Creation commands with unknown command type: {type(flow_initialization_command).__name__}."
1542
- logger.error(details)
1543
1477
  return DeserializeFlowFromCommandsResultFailure(result_details=details)
1544
1478
 
1545
1479
  # Adopt the newly-created flow as our current context.
1546
1480
  flow = GriptapeNodes.ObjectManager().attempt_get_object_by_name_as_type(flow_name, ControlFlow)
1547
1481
  if flow is None:
1548
1482
  details = f"Attempted to deserialize a serialized set of Flow Creation commands. Failed to find created flow '{flow_name}'."
1549
- logger.error(details)
1550
1483
  return DeserializeFlowFromCommandsResultFailure(result_details=details)
1551
1484
  GriptapeNodes.ContextManager().push_flow(flow=flow)
1552
1485
 
@@ -1562,7 +1495,6 @@ class FlowManager:
1562
1495
  details = (
1563
1496
  f"Attempted to deserialize a Flow '{flow_name}'. Failed while deserializing a node within the flow."
1564
1497
  )
1565
- logger.error(details)
1566
1498
  return DeserializeFlowFromCommandsResultFailure(result_details=details)
1567
1499
  node_uuid_to_deserialized_node_result[serialized_node.node_uuid] = deserialized_node_result
1568
1500
 
@@ -1574,12 +1506,10 @@ class FlowManager:
1574
1506
  source_node_uuid = indirect_connection.source_node_uuid
1575
1507
  if source_node_uuid not in node_uuid_to_deserialized_node_result:
1576
1508
  details = f"Attempted to deserialize a Flow '{flow_name}'. Failed while attempting to create a Connection for a source node that did not exist within the flow."
1577
- logger.error(details)
1578
1509
  return DeserializeFlowFromCommandsResultFailure(result_details=details)
1579
1510
  target_node_uuid = indirect_connection.target_node_uuid
1580
1511
  if target_node_uuid not in node_uuid_to_deserialized_node_result:
1581
1512
  details = f"Attempted to deserialize a Flow '{flow_name}'. Failed while attempting to create a Connection for a target node that did not exist within the flow."
1582
- logger.error(details)
1583
1513
  return DeserializeFlowFromCommandsResultFailure(result_details=details)
1584
1514
 
1585
1515
  source_node_result = node_uuid_to_deserialized_node_result[source_node_uuid]
@@ -1596,7 +1526,6 @@ class FlowManager:
1596
1526
  create_connection_result = GriptapeNodes.handle_request(create_connection_request)
1597
1527
  if create_connection_result.failed():
1598
1528
  details = f"Attempted to deserialize a Flow '{flow_name}'. Failed while deserializing a Connection from '{source_node_name}.{indirect_connection.source_parameter_name}' to '{target_node_name}.{indirect_connection.target_parameter_name}' within the flow."
1599
- logger.error(details)
1600
1529
  return DeserializeFlowFromCommandsResultFailure(result_details=details)
1601
1530
 
1602
1531
  # Now assign the values.
@@ -1610,7 +1539,6 @@ class FlowManager:
1610
1539
  node = GriptapeNodes.ObjectManager().attempt_get_object_by_name_as_type(node_name, BaseNode)
1611
1540
  if node is None:
1612
1541
  details = f"Attempted to deserialize a Flow '{flow_name}'. Failed while deserializing a value assignment for node '{node_name}'."
1613
- logger.error(details)
1614
1542
  return DeserializeFlowFromCommandsResultFailure(result_details=details)
1615
1543
  with GriptapeNodes.ContextManager().node(node=node):
1616
1544
  # Iterate through each set value command in the list for this node.
@@ -1621,7 +1549,6 @@ class FlowManager:
1621
1549
  value = request.serialized_flow_commands.unique_parameter_uuid_to_values[unique_value_uuid]
1622
1550
  except IndexError as err:
1623
1551
  details = f"Attempted to deserialize a Flow '{flow_name}'. Failed while deserializing a value assignment for node '{node.name}.{parameter_name}': {err}"
1624
- logger.error(details)
1625
1552
  return DeserializeFlowFromCommandsResultFailure(result_details=details)
1626
1553
 
1627
1554
  # Call the SetParameterValueRequest, subbing in the value from our unique value list.
@@ -1631,7 +1558,6 @@ class FlowManager:
1631
1558
  )
1632
1559
  if set_parameter_value_result.failed():
1633
1560
  details = f"Attempted to deserialize a Flow '{flow_name}'. Failed while deserializing a value assignment for node '{node.name}.{parameter_name}'."
1634
- logger.error(details)
1635
1561
  return DeserializeFlowFromCommandsResultFailure(result_details=details)
1636
1562
 
1637
1563
  # Now the child flows.
@@ -1640,12 +1566,10 @@ class FlowManager:
1640
1566
  sub_flow_result = GriptapeNodes.handle_request(sub_flow_request)
1641
1567
  if sub_flow_result.failed():
1642
1568
  details = f"Attempted to deserialize a Flow '{flow_name}'. Failed while deserializing a sub-flow within the Flow."
1643
- logger.error(details)
1644
1569
  return DeserializeFlowFromCommandsResultFailure(result_details=details)
1645
1570
 
1646
1571
  details = f"Successfully deserialized Flow '{flow_name}'."
1647
- logger.debug(details)
1648
- return DeserializeFlowFromCommandsResultSuccess(flow_name=flow_name)
1572
+ return DeserializeFlowFromCommandsResultSuccess(flow_name=flow_name, result_details=details)
1649
1573
 
1650
1574
  def on_flush_request(self, request: FlushParameterChangesRequest) -> ResultPayload: # noqa: ARG002
1651
1575
  obj_manager = GriptapeNodes.ObjectManager()
@@ -1656,9 +1580,15 @@ class FlowManager:
1656
1580
  # Only flush if there are actually tracked parameters
1657
1581
  if node._tracked_parameters:
1658
1582
  node.emit_parameter_changes()
1659
- return FlushParameterChangesResultSuccess()
1583
+ return FlushParameterChangesResultSuccess(result_details="Parameter changes flushed successfully.")
1660
1584
 
1661
- async def start_flow(self, flow: ControlFlow, start_node: BaseNode | None = None, debug_mode: bool = False) -> None: # noqa: FBT001, FBT002, ARG002
1585
+ async def start_flow(
1586
+ self,
1587
+ flow: ControlFlow,
1588
+ start_node: BaseNode | None = None,
1589
+ *,
1590
+ debug_mode: bool = False,
1591
+ ) -> None:
1662
1592
  if self.check_for_existing_running_flow():
1663
1593
  # If flow already exists, throw an error
1664
1594
  errormsg = "This workflow is already in progress. Please wait for the current process to finish before starting again."
@@ -1668,13 +1598,13 @@ class FlowManager:
1668
1598
  if self._global_flow_queue.empty():
1669
1599
  errormsg = "No Flow exists. You must create at least one control connection."
1670
1600
  raise RuntimeError(errormsg)
1671
- start_node = self._global_flow_queue.get()
1601
+ queue_item = self._global_flow_queue.get()
1602
+ start_node = queue_item.node
1672
1603
  self._global_flow_queue.task_done()
1673
1604
 
1674
- # Initialize global control flow machine if needed
1675
- if self._global_control_flow_machine is None:
1676
- self._global_control_flow_machine = ControlFlowMachine()
1605
+ # Initialize global control flow machine and DAG builder
1677
1606
 
1607
+ self._global_control_flow_machine = ControlFlowMachine(flow.name)
1678
1608
  try:
1679
1609
  await self._global_control_flow_machine.start_flow(start_node, debug_mode)
1680
1610
  except Exception:
@@ -1685,15 +1615,13 @@ class FlowManager:
1685
1615
  def check_for_existing_running_flow(self) -> bool:
1686
1616
  if self._global_control_flow_machine is None:
1687
1617
  return False
1688
- if (
1689
- self._global_control_flow_machine._current_state is not CompleteState
1690
- and self._global_control_flow_machine._current_state
1691
- ):
1618
+ current_state = self._global_control_flow_machine.current_state
1619
+ if current_state and current_state is not CompleteState:
1692
1620
  # Flow already exists in progress
1693
1621
  return True
1694
1622
  return bool(
1695
- not self._global_control_flow_machine._context.resolution_machine.is_complete()
1696
- and self._global_control_flow_machine._context.resolution_machine.is_started()
1623
+ not self._global_control_flow_machine.context.resolution_machine.is_complete()
1624
+ and self._global_control_flow_machine.context.resolution_machine.is_started()
1697
1625
  )
1698
1626
 
1699
1627
  def cancel_flow_run(self) -> None:
@@ -1701,10 +1629,11 @@ class FlowManager:
1701
1629
  errormsg = "Flow has not yet been started. Cannot cancel flow that hasn't begun."
1702
1630
  raise RuntimeError(errormsg)
1703
1631
  self._global_flow_queue.queue.clear()
1704
- if self._global_control_flow_machine is not None:
1705
- self._global_control_flow_machine.reset_machine()
1706
1632
  # Reset control flow machine
1633
+ if self._global_control_flow_machine is not None:
1634
+ self._global_control_flow_machine.reset_machine(cancel=True)
1707
1635
  self._global_single_node_resolution = False
1636
+ self._global_dag_builder.clear()
1708
1637
  logger.debug("Cancelling flow run")
1709
1638
 
1710
1639
  GriptapeNodes.EventManager().put_event(
@@ -1716,7 +1645,7 @@ class FlowManager:
1716
1645
  self._global_flow_queue.queue.clear()
1717
1646
  if self._global_control_flow_machine is not None:
1718
1647
  self._global_control_flow_machine.reset_machine()
1719
- self._global_control_flow_machine = None
1648
+ # Reset control flow machine
1720
1649
  self._global_single_node_resolution = False
1721
1650
 
1722
1651
  # Clear all connections to prevent memory leaks and stale references
@@ -1735,9 +1664,9 @@ class FlowManager:
1735
1664
  """Get the next node from the global execution queue, or None if empty."""
1736
1665
  if self._global_flow_queue.empty():
1737
1666
  return None
1738
- node = self._global_flow_queue.get()
1667
+ queue_item = self._global_flow_queue.get()
1739
1668
  self._global_flow_queue.task_done()
1740
- return node
1669
+ return queue_item.node
1741
1670
 
1742
1671
  def clear_execution_queue(self) -> None:
1743
1672
  """Clear all nodes from the global execution queue."""
@@ -1756,7 +1685,7 @@ class FlowManager:
1756
1685
  # Internal execution queue helper methods to consolidate redundant operations
1757
1686
  async def _handle_flow_start_if_not_running(
1758
1687
  self,
1759
- flow: ControlFlow, # noqa: ARG002
1688
+ flow: ControlFlow,
1760
1689
  *,
1761
1690
  debug_mode: bool,
1762
1691
  error_message: str,
@@ -1765,42 +1694,47 @@ class FlowManager:
1765
1694
  if not self.check_for_existing_running_flow():
1766
1695
  if self._global_flow_queue.empty():
1767
1696
  raise RuntimeError(error_message)
1768
- start_node = self._global_flow_queue.get()
1697
+ queue_item = self._global_flow_queue.get()
1698
+ start_node = queue_item.node
1769
1699
  self._global_flow_queue.task_done()
1700
+ # Get or create machine
1770
1701
  if self._global_control_flow_machine is None:
1771
- self._global_control_flow_machine = ControlFlowMachine()
1702
+ self._global_control_flow_machine = ControlFlowMachine(flow.name)
1772
1703
  await self._global_control_flow_machine.start_flow(start_node, debug_mode)
1773
1704
 
1774
1705
  async def _handle_post_execution_queue_processing(self, *, debug_mode: bool) -> None:
1775
1706
  """Handle execution queue processing after execution completes."""
1776
1707
  if not self.check_for_existing_running_flow() and not self._global_flow_queue.empty():
1777
- start_node = self._global_flow_queue.get()
1708
+ queue_item = self._global_flow_queue.get()
1709
+ start_node = queue_item.node
1778
1710
  self._global_flow_queue.task_done()
1779
- if self._global_control_flow_machine is not None:
1780
- await self._global_control_flow_machine.start_flow(start_node, debug_mode)
1711
+ machine = self._global_control_flow_machine
1712
+ if machine is not None:
1713
+ await machine.start_flow(start_node, debug_mode)
1781
1714
 
1782
- async def resolve_singular_node(self, flow: ControlFlow, node: BaseNode, debug_mode: bool = False) -> None: # noqa: FBT001, FBT002, ARG002
1715
+ async def resolve_singular_node(self, flow: ControlFlow, node: BaseNode, *, debug_mode: bool = False) -> None:
1783
1716
  # Set that we are only working on one node right now! no other stepping allowed
1784
1717
  if self.check_for_existing_running_flow():
1785
1718
  # If flow already exists, throw an error
1786
1719
  errormsg = f"This workflow is already in progress. Please wait for the current process to finish before starting {node.name} again."
1787
1720
  raise RuntimeError(errormsg)
1721
+
1788
1722
  self._global_single_node_resolution = True
1789
- # Initialize global control flow machine if needed
1790
- if self._global_control_flow_machine is None:
1791
- self._global_control_flow_machine = ControlFlowMachine()
1792
- # Get the node resolution machine for the current flow!
1793
- self._global_control_flow_machine._context.current_node = node
1794
- resolution_machine = self._global_control_flow_machine._context.resolution_machine
1795
- # Set debug mode
1796
- resolution_machine.change_debug_mode(debug_mode)
1797
- # Resolve the node.
1723
+
1724
+ # Get or create machine
1725
+ self._global_control_flow_machine = ControlFlowMachine(flow.name)
1726
+ self._global_control_flow_machine.context.current_nodes = [node]
1727
+ resolution_machine = self._global_control_flow_machine.resolution_machine
1728
+ resolution_machine.change_debug_mode(debug_mode=debug_mode)
1798
1729
  node.state = NodeResolutionState.UNRESOLVED
1730
+ # Build the DAG for the node
1731
+ if isinstance(resolution_machine, ParallelResolutionMachine):
1732
+ self._global_dag_builder.add_node_with_dependencies(node)
1733
+ resolution_machine.context.dag_builder = self._global_dag_builder
1799
1734
  await resolution_machine.resolve_node(node)
1800
- # decide if we can change it back to normal flow mode!
1801
1735
  if resolution_machine.is_complete():
1802
1736
  self._global_single_node_resolution = False
1803
- self._global_control_flow_machine._context.current_node = None
1737
+ self._global_control_flow_machine.context.current_nodes = []
1804
1738
 
1805
1739
  async def single_execution_step(self, flow: ControlFlow, change_debug_mode: bool) -> None: # noqa: FBT001
1806
1740
  # do a granular step
@@ -1811,11 +1745,11 @@ class FlowManager:
1811
1745
  return
1812
1746
  if self._global_control_flow_machine is not None:
1813
1747
  await self._global_control_flow_machine.granular_step(change_debug_mode)
1814
- resolution_machine = self._global_control_flow_machine._context.resolution_machine
1748
+ resolution_machine = self._global_control_flow_machine.resolution_machine
1815
1749
  if self._global_single_node_resolution:
1816
- resolution_machine = self._global_control_flow_machine._context.resolution_machine
1817
- if resolution_machine.is_complete():
1818
- self._global_single_node_resolution = False
1750
+ resolution_machine = self._global_control_flow_machine.resolution_machine
1751
+ if resolution_machine.is_complete():
1752
+ self._global_single_node_resolution = False
1819
1753
 
1820
1754
  async def single_node_step(self, flow: ControlFlow) -> None:
1821
1755
  # It won't call single_node_step without an existing flow running from US.
@@ -1840,13 +1774,13 @@ class FlowManager:
1840
1774
  if not self.check_for_existing_running_flow():
1841
1775
  return
1842
1776
  # Turn all debugging to false and continue on
1843
- if self._global_control_flow_machine is not None:
1777
+ if self._global_control_flow_machine is not None and self._global_control_flow_machine is not None:
1844
1778
  self._global_control_flow_machine.change_debug_mode(False)
1845
1779
  if self._global_single_node_resolution:
1846
- if self._global_control_flow_machine._context.resolution_machine.is_complete():
1780
+ if self._global_control_flow_machine.resolution_machine.is_complete():
1847
1781
  self._global_single_node_resolution = False
1848
1782
  else:
1849
- await self._global_control_flow_machine._context.resolution_machine.update()
1783
+ await self._global_control_flow_machine.resolution_machine.update()
1850
1784
  else:
1851
1785
  await self._global_control_flow_machine.node_step()
1852
1786
  # Now it is done executing. make sure it's actually done?
@@ -1858,20 +1792,30 @@ class FlowManager:
1858
1792
  # Clear entry control parameter for new execution
1859
1793
  node.set_entry_control_parameter(None)
1860
1794
 
1861
- def flow_state(self, flow: ControlFlow) -> tuple[str | None, str | None]: # noqa: ARG002
1795
+ def flow_state(self, flow: ControlFlow) -> tuple[list[str] | None, list[str] | None]: # noqa: ARG002
1862
1796
  if not self.check_for_existing_running_flow():
1863
1797
  msg = "Flow hasn't started."
1864
1798
  raise RuntimeError(msg)
1865
1799
  if self._global_control_flow_machine is None:
1866
1800
  return None, None
1867
- current_control_node = (
1868
- self._global_control_flow_machine._context.current_node.name
1869
- if self._global_control_flow_machine._context.current_node is not None
1801
+ control_flow_context = self._global_control_flow_machine.context
1802
+ current_control_nodes = (
1803
+ [control_flow_node.name for control_flow_node in control_flow_context.current_nodes]
1804
+ if control_flow_context.current_nodes is not None
1870
1805
  else None
1871
1806
  )
1872
- focus_stack_for_node = self._global_control_flow_machine._context.resolution_machine._context.focus_stack
1873
- current_resolving_node = focus_stack_for_node[-1].node.name if len(focus_stack_for_node) else None
1874
- return current_control_node, current_resolving_node
1807
+ # focus_stack is no longer available in the new architecture
1808
+ if isinstance(control_flow_context.resolution_machine, ParallelResolutionMachine):
1809
+ current_resolving_nodes = [
1810
+ node.node_reference.name
1811
+ for node in control_flow_context.resolution_machine.context.task_to_node.values()
1812
+ ]
1813
+ return current_control_nodes, current_resolving_nodes
1814
+ if isinstance(control_flow_context.resolution_machine, SequentialResolutionMachine):
1815
+ focus_stack_for_node = control_flow_context.resolution_machine.context.focus_stack
1816
+ current_resolving_node = focus_stack_for_node[-1].node.name if len(focus_stack_for_node) else None
1817
+ return current_control_nodes, [current_resolving_node] if current_resolving_node else None
1818
+ return current_control_nodes, None
1875
1819
 
1876
1820
  def get_start_node_from_node(self, flow: ControlFlow, node: BaseNode) -> BaseNode | None:
1877
1821
  # backwards chain in control outputs.
@@ -1984,13 +1928,13 @@ class FlowManager:
1984
1928
  # check if it has an outgoing connection. We don't want it to (that means we get the most resolution)
1985
1929
  if node.name not in cn_mgr.outgoing_index:
1986
1930
  valid_data_nodes.append(node)
1987
- # ok now - populate the global flow queue
1931
+ # ok now - populate the global flow queue with node type information
1988
1932
  for node in start_nodes:
1989
- self._global_flow_queue.put(node)
1933
+ self._global_flow_queue.put(QueueItem(node=node, dag_execution_type=DagExecutionType.START_NODE))
1990
1934
  for node in control_nodes:
1991
- self._global_flow_queue.put(node)
1935
+ self._global_flow_queue.put(QueueItem(node=node, dag_execution_type=DagExecutionType.CONTROL_NODE))
1992
1936
  for node in valid_data_nodes:
1993
- self._global_flow_queue.put(node)
1937
+ self._global_flow_queue.put(QueueItem(node=node, dag_execution_type=DagExecutionType.DATA_NODE))
1994
1938
 
1995
1939
  return self._global_flow_queue
1996
1940