flock-core 0.4.520__py3-none-any.whl → 0.5.0b1__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.

Potentially problematic release.


This version of flock-core might be problematic. Click here for more details.

Files changed (103) hide show
  1. flock/cli/manage_agents.py +3 -3
  2. flock/components/__init__.py +28 -0
  3. flock/components/evaluation/__init__.py +9 -0
  4. flock/components/evaluation/declarative_evaluation_component.py +198 -0
  5. flock/components/routing/__init__.py +15 -0
  6. flock/{routers/conditional/conditional_router.py → components/routing/conditional_routing_component.py} +60 -49
  7. flock/components/routing/default_routing_component.py +103 -0
  8. flock/components/routing/llm_routing_component.py +208 -0
  9. flock/components/utility/__init__.py +15 -0
  10. flock/{modules/enterprise_memory/enterprise_memory_module.py → components/utility/memory_utility_component.py} +195 -173
  11. flock/{modules/performance/metrics_module.py → components/utility/metrics_utility_component.py} +101 -86
  12. flock/{modules/output/output_module.py → components/utility/output_utility_component.py} +49 -49
  13. flock/core/__init__.py +2 -8
  14. flock/core/agent/__init__.py +16 -0
  15. flock/core/agent/flock_agent_components.py +104 -0
  16. flock/core/agent/flock_agent_execution.py +101 -0
  17. flock/core/agent/flock_agent_integration.py +147 -0
  18. flock/core/agent/flock_agent_lifecycle.py +177 -0
  19. flock/core/agent/flock_agent_serialization.py +378 -0
  20. flock/core/component/__init__.py +15 -0
  21. flock/core/{flock_module.py → component/agent_component_base.py} +136 -35
  22. flock/core/component/evaluation_component_base.py +56 -0
  23. flock/core/component/routing_component_base.py +75 -0
  24. flock/core/component/utility_component_base.py +69 -0
  25. flock/core/config/flock_agent_config.py +49 -2
  26. flock/core/evaluation/utils.py +1 -1
  27. flock/core/execution/evaluation_executor.py +1 -1
  28. flock/core/flock.py +137 -483
  29. flock/core/flock_agent.py +151 -1018
  30. flock/core/flock_factory.py +94 -73
  31. flock/core/{flock_registry.py → flock_registry.py.backup} +3 -17
  32. flock/core/logging/logging.py +1 -0
  33. flock/core/mcp/flock_mcp_server.py +42 -37
  34. flock/core/mixin/dspy_integration.py +5 -5
  35. flock/core/orchestration/__init__.py +18 -0
  36. flock/core/orchestration/flock_batch_processor.py +94 -0
  37. flock/core/orchestration/flock_evaluator.py +113 -0
  38. flock/core/orchestration/flock_execution.py +288 -0
  39. flock/core/orchestration/flock_initialization.py +125 -0
  40. flock/core/orchestration/flock_server_manager.py +65 -0
  41. flock/core/orchestration/flock_web_server.py +117 -0
  42. flock/core/registry/__init__.py +39 -0
  43. flock/core/registry/agent_registry.py +69 -0
  44. flock/core/registry/callable_registry.py +139 -0
  45. flock/core/registry/component_discovery.py +142 -0
  46. flock/core/registry/component_registry.py +64 -0
  47. flock/core/registry/config_mapping.py +64 -0
  48. flock/core/registry/decorators.py +137 -0
  49. flock/core/registry/registry_hub.py +202 -0
  50. flock/core/registry/server_registry.py +57 -0
  51. flock/core/registry/type_registry.py +86 -0
  52. flock/core/serialization/flock_serializer.py +33 -30
  53. flock/core/serialization/serialization_utils.py +28 -25
  54. flock/core/util/input_resolver.py +29 -2
  55. flock/platform/docker_tools.py +3 -3
  56. flock/tools/markdown_tools.py +1 -2
  57. flock/tools/text_tools.py +1 -2
  58. flock/webapp/app/main.py +9 -5
  59. flock/workflow/activities.py +59 -84
  60. flock/workflow/activities_unified.py +230 -0
  61. flock/workflow/agent_execution_activity.py +6 -6
  62. flock/workflow/flock_workflow.py +1 -1
  63. {flock_core-0.4.520.dist-info → flock_core-0.5.0b1.dist-info}/METADATA +2 -2
  64. {flock_core-0.4.520.dist-info → flock_core-0.5.0b1.dist-info}/RECORD +67 -68
  65. flock/core/flock_evaluator.py +0 -60
  66. flock/core/flock_router.py +0 -83
  67. flock/evaluators/__init__.py +0 -1
  68. flock/evaluators/declarative/__init__.py +0 -1
  69. flock/evaluators/declarative/declarative_evaluator.py +0 -194
  70. flock/evaluators/memory/memory_evaluator.py +0 -90
  71. flock/evaluators/test/test_case_evaluator.py +0 -38
  72. flock/evaluators/zep/zep_evaluator.py +0 -59
  73. flock/modules/__init__.py +0 -1
  74. flock/modules/assertion/__init__.py +0 -1
  75. flock/modules/assertion/assertion_module.py +0 -286
  76. flock/modules/callback/__init__.py +0 -1
  77. flock/modules/callback/callback_module.py +0 -91
  78. flock/modules/enterprise_memory/README.md +0 -99
  79. flock/modules/mem0/__init__.py +0 -1
  80. flock/modules/mem0/mem0_module.py +0 -126
  81. flock/modules/mem0_async/__init__.py +0 -1
  82. flock/modules/mem0_async/async_mem0_module.py +0 -126
  83. flock/modules/memory/__init__.py +0 -1
  84. flock/modules/memory/memory_module.py +0 -429
  85. flock/modules/memory/memory_parser.py +0 -125
  86. flock/modules/memory/memory_storage.py +0 -736
  87. flock/modules/output/__init__.py +0 -1
  88. flock/modules/performance/__init__.py +0 -1
  89. flock/modules/zep/__init__.py +0 -1
  90. flock/modules/zep/zep_module.py +0 -192
  91. flock/routers/__init__.py +0 -1
  92. flock/routers/agent/__init__.py +0 -1
  93. flock/routers/agent/agent_router.py +0 -236
  94. flock/routers/agent/handoff_agent.py +0 -58
  95. flock/routers/default/__init__.py +0 -1
  96. flock/routers/default/default_router.py +0 -80
  97. flock/routers/feedback/feedback_router.py +0 -114
  98. flock/routers/list_generator/list_generator_router.py +0 -166
  99. flock/routers/llm/__init__.py +0 -1
  100. flock/routers/llm/llm_router.py +0 -365
  101. {flock_core-0.4.520.dist-info → flock_core-0.5.0b1.dist-info}/WHEEL +0 -0
  102. {flock_core-0.4.520.dist-info → flock_core-0.5.0b1.dist-info}/entry_points.txt +0 -0
  103. {flock_core-0.4.520.dist-info → flock_core-0.5.0b1.dist-info}/licenses/LICENSE +0 -0
@@ -24,13 +24,13 @@ from pydantic import BaseModel
24
24
  if TYPE_CHECKING:
25
25
  pass
26
26
 
27
- from flock.core.flock_registry import get_registry
27
+ from flock.core.registry import get_registry
28
28
  from flock.core.logging.logging import get_logger
29
29
 
30
30
  logger = get_logger("serialization.utils")
31
31
 
32
32
  # Remove this line to avoid circular import at module level
33
- # FlockRegistry = get_registry() # Get singleton instance
33
+ # registry = get_registry() # Get singleton instance
34
34
 
35
35
  # --- Serialization Helper ---
36
36
 
@@ -188,16 +188,16 @@ def collect_pydantic_models(
188
188
 
189
189
  def serialize_item(item: Any) -> Any:
190
190
  """Recursively prepares an item for serialization (e.g., to dict for YAML/JSON).
191
- Converts known callables to their path strings using FlockRegistry.
191
+ Converts known callables to their path strings using registry.
192
192
  Converts Pydantic models using model_dump.
193
193
  """
194
194
  # Import the registry lazily when needed
195
- from flock.core.flock_registry import get_registry
195
+ from flock.core.registry import get_registry
196
196
 
197
- FlockRegistry = get_registry()
197
+ registry = get_registry()
198
198
 
199
199
  if callable(item) and not isinstance(item, type):
200
- path_str = FlockRegistry.get_callable_path_string(
200
+ path_str = registry.get_callable_path_string(
201
201
  item
202
202
  ) # Use registry helper
203
203
  if path_str:
@@ -248,12 +248,12 @@ def serialize_item(item: Any) -> Any:
248
248
  elif isinstance(
249
249
  item, type
250
250
  ): # Handle type objects themselves (e.g. if stored directly)
251
- type_name = FlockRegistry.get_component_type_name(
251
+ type_name = registry.get_component_type_name(
252
252
  item
253
253
  ) # Check components first
254
254
  if type_name:
255
255
  return {"__component_ref__": type_name}
256
- type_name = FlockRegistry._get_path_string(
256
+ type_name = registry._get_path_string(
257
257
  item
258
258
  ) # Check regular types/classes by path
259
259
  if type_name:
@@ -274,13 +274,13 @@ def serialize_item(item: Any) -> Any:
274
274
 
275
275
  def deserialize_item(item: Any) -> Any:
276
276
  """Recursively processes a deserialized item (e.g., from YAML/JSON dict).
277
- Converts reference dicts back to actual callables or types using FlockRegistry.
277
+ Converts reference dicts back to actual callables or types using registry.
278
278
  Handles nested lists and dicts.
279
279
  """
280
280
  # Import the registry lazily when needed
281
- from flock.core.flock_registry import get_registry
281
+ from flock.core.registry import get_registry
282
282
 
283
- FlockRegistry = get_registry()
283
+ registry = get_registry()
284
284
 
285
285
  if isinstance(item, Mapping):
286
286
  if "__callable_ref__" in item and len(item) == 1:
@@ -288,7 +288,7 @@ def deserialize_item(item: Any) -> Any:
288
288
  try:
289
289
  # The registry's get_callable needs to handle lookup by simple name OR full path
290
290
  # Or we assume get_callable handles finding the right function from the simple name
291
- resolved_callable = FlockRegistry.get_callable(ref_name)
291
+ resolved_callable = registry.get_callable(ref_name)
292
292
  logger.debug(
293
293
  f"Deserialized callable reference '{ref_name}' to {resolved_callable}"
294
294
  )
@@ -307,7 +307,7 @@ def deserialize_item(item: Any) -> Any:
307
307
  elif "__component_ref__" in item and len(item) == 1:
308
308
  type_name = item["__component_ref__"]
309
309
  try:
310
- return FlockRegistry.get_component(type_name)
310
+ return registry.get_component(type_name)
311
311
  except KeyError:
312
312
  logger.error(
313
313
  f"Component reference '{type_name}' not found during deserialization."
@@ -318,7 +318,7 @@ def deserialize_item(item: Any) -> Any:
318
318
  try:
319
319
  # For general types, use get_type or fallback to dynamic import like get_callable
320
320
  # Using get_type for now, assuming it needs registration
321
- return FlockRegistry.get_type(type_name)
321
+ return registry.get_type(type_name)
322
322
  except KeyError:
323
323
  # Attempt dynamic import as fallback if get_type fails (similar to get_callable)
324
324
  try:
@@ -329,7 +329,7 @@ def deserialize_item(item: Any) -> Any:
329
329
  mod = importlib.import_module(module_name)
330
330
  type_obj = getattr(mod, class_name)
331
331
  if isinstance(type_obj, type):
332
- FlockRegistry.register_type(
332
+ registry.register_type(
333
333
  type_obj, type_name
334
334
  ) # Cache it
335
335
  return type_obj
@@ -356,12 +356,12 @@ def deserialize_component(
356
356
  data: dict | None, expected_base_type: type
357
357
  ) -> Any | None:
358
358
  """Deserializes a component (Module, Evaluator, Router) from its dict representation.
359
- Uses the 'type' field to find the correct class via FlockRegistry.
359
+ Uses the 'type' field to find the correct class via registry.
360
360
  """
361
361
  # Import the registry and COMPONENT_BASE_TYPES lazily when needed
362
- from flock.core.flock_registry import COMPONENT_BASE_TYPES, get_registry
362
+ from flock.core.registry import get_registry
363
363
 
364
- FlockRegistry = get_registry()
364
+ registry = get_registry()
365
365
 
366
366
  if data is None:
367
367
  return None
@@ -379,14 +379,17 @@ def deserialize_component(
379
379
  return None
380
380
 
381
381
  try:
382
- ComponentClass = FlockRegistry.get_component(type_name) # Use registry
382
+ ComponentClass = registry.get_component(type_name) # Use registry
383
383
  # Optional: Keep the base type check
384
- if COMPONENT_BASE_TYPES and not issubclass(
385
- ComponentClass, expected_base_type
386
- ):
387
- raise TypeError(
388
- f"Deserialized class {type_name} is not a subclass of {expected_base_type.__name__}"
389
- )
384
+ try:
385
+ from flock.core.component.agent_component_base import AgentComponent
386
+ if not issubclass(ComponentClass, AgentComponent):
387
+ logger.warning(
388
+ f"Deserialized class '{type_name}' is not a subclass of AgentComponent."
389
+ )
390
+ except ImportError:
391
+ # AgentComponent not available during setup
392
+ pass
390
393
 
391
394
  # Recursively deserialize the data *before* passing to Pydantic constructor
392
395
  # This handles nested callables/types within the component's config/data
@@ -45,7 +45,12 @@ def top_level_to_keys(s: str) -> list[str]:
45
45
 
46
46
 
47
47
  def resolve_inputs(
48
- input_spec: str, context: FlockContext, previous_agent_name: str
48
+ input_spec: str,
49
+ context: FlockContext,
50
+ previous_agent_name: str,
51
+ previous_agent_output:str,
52
+ previous_agent_handoff_strategy:str,
53
+ previous_agent_handoff_map:dict[str, str]
49
54
  ) -> dict:
50
55
  """Build a dictionary of inputs based on the input specification string and the provided context.
51
56
 
@@ -63,6 +68,28 @@ def resolve_inputs(
63
68
  eg. agent name: "idea_agent", variable: "ia_idea" (ia = idea agent)
64
69
  - or set hand off mode to strict to avoid conflicts.
65
70
  with strict mode, the agent will only accept inputs from the previous agent.
71
+
72
+
73
+ Strategy for passing data to the next agent.
74
+
75
+ Example:
76
+ ReviewAgent.next_agent = SummaryAgent
77
+ ReviewAgent(output = "text:str, keywords:list[str], rating:int")
78
+ SummaryAgent(input = "text:str, title:str")
79
+
80
+ 'append' means the difference in signature is appended to the next agent's input signature.
81
+ SummaryAgent(input = "text:str, title:str, keywords:list[str], rating:int")
82
+
83
+ 'override' means the target agent's signature is getting overriden.
84
+ SummaryAgent(input = "text:str, keywords:list[str], rating:int")
85
+
86
+ 'static' means the the target agent's signature is not changed at all.
87
+ If source agent has no output fields that match the target agent's input,
88
+ there will be no data passed to the next agent.
89
+ SummaryAgent(input = "text:str, title:str")
90
+
91
+ 'map' means the source agent's output is mapped to the target agent's input
92
+ based on 'handoff_map' configuration.
66
93
 
67
94
  Args:
68
95
  input_spec: Comma-separated input keys (e.g., "query" or "agent_name.property").
@@ -120,7 +147,7 @@ def resolve_inputs(
120
147
  inputs[key] = context.get_agent_definition(property_name)
121
148
  continue
122
149
 
123
- # Otherwise, attempt to look up a state variable with the key "agent_name.property"
150
+ # Otherwise, attempt to look up a state variable with the key "entity_name.property_name"
124
151
  inputs[key] = context.get_variable(f"{entity_name}.{property_name}")
125
152
  continue
126
153
 
@@ -1,5 +1,5 @@
1
+ import asyncio
1
2
  import subprocess
2
- import time
3
3
 
4
4
 
5
5
  def _check_docker_running():
@@ -16,7 +16,7 @@ def _check_docker_running():
16
16
  return False
17
17
 
18
18
 
19
- def _start_docker():
19
+ async def _start_docker():
20
20
  """Attempt to start Docker.
21
21
  This example first tries 'systemctl start docker' and then 'service docker start'.
22
22
  Adjust as needed for your environment.
@@ -37,7 +37,7 @@ def _start_docker():
37
37
  text=True,
38
38
  )
39
39
  # Give Docker a moment to start.
40
- time.sleep(3)
40
+ await asyncio.sleep(3)
41
41
  if _check_docker_running():
42
42
  print("Docker is now running.")
43
43
  return True
@@ -26,8 +26,7 @@ def markdown_split_by_headers(
26
26
  chunks = []
27
27
 
28
28
  # Process each section
29
- for i in range(len(headers)):
30
- current_header = headers[i]
29
+ for i, current_header in enumerate(headers):
31
30
  header_text = current_header.group(2).strip()
32
31
  header_level = len(current_header.group(1))
33
32
 
flock/tools/text_tools.py CHANGED
@@ -206,8 +206,7 @@ def text_split_code_by_functions(code: str) -> list[dict[str, Any]]:
206
206
  functions = []
207
207
 
208
208
  # Process each function
209
- for i in range(len(matches)):
210
- current_match = matches[i]
209
+ for i, current_match in enumerate(matches):
211
210
  function_name = current_match.group(2)
212
211
 
213
212
  # Determine function content
flock/webapp/app/main.py CHANGED
@@ -124,6 +124,10 @@ def mask_sensitive_value_web(value: str) -> str:
124
124
  if len(value) <= 4: return "••••"
125
125
  return value[:2] + "•" * (len(value) - 4) + value[-2:]
126
126
 
127
+ def create_hx_trigger_header(triggers: dict[str, Any]) -> str:
128
+ """Helper function to create HX-Trigger header with JSON serialization."""
129
+ return json.dumps(triggers)
130
+
127
131
  def get_show_secrets_setting_web(env_vars: dict[str, str]) -> bool:
128
132
  return env_vars.get(SHOW_SECRETS_KEY, "false").lower() == "true"
129
133
 
@@ -843,12 +847,12 @@ async def ui_load_flock_by_name_action(request: Request, selected_flock_filename
843
847
  if loaded_flock:
844
848
  success_message_text = f"Flock '{loaded_flock.name}' loaded from '{selected_flock_filename}'."
845
849
  response_headers["HX-Push-Url"] = "/ui/editor/execute?ui_mode=" + ui_mode_query
846
- response_headers["HX-Trigger"] = json.dumps({"flockLoaded": None, "notify": {"type": "success", "message": success_message_text}})
850
+ response_headers["HX-Trigger"] = create_hx_trigger_header({"flockLoaded": None, "notify": {"type": "success", "message": success_message_text}})
847
851
  context = get_base_context_web(request, success=success_message_text, ui_mode=ui_mode_query)
848
852
  return templates.TemplateResponse("partials/_execution_view_container.html", context, headers=response_headers)
849
853
  else:
850
854
  error_message_text = f"Failed to load flock file '{selected_flock_filename}'."
851
- response_headers["HX-Trigger"] = json.dumps({"notify": {"type": "error", "message": error_message_text}})
855
+ response_headers["HX-Trigger"] = create_hx_trigger_header({"notify": {"type": "error", "message": error_message_text}})
852
856
  context = get_base_context_web(request, error=error_message_text, ui_mode=ui_mode_query)
853
857
  context["error_message_inline"] = error_message_text # For direct display in partial
854
858
  return templates.TemplateResponse("partials/_load_manager_view.html", context, headers=response_headers)
@@ -874,13 +878,13 @@ async def ui_load_flock_by_upload_action(request: Request, flock_file_upload: Up
874
878
  if loaded_flock:
875
879
  success_message_text = f"Flock '{loaded_flock.name}' loaded from '{filename_to_load}'."
876
880
  response_headers["HX-Push-Url"] = f"/ui/editor/execute?ui_mode={ui_mode_query}"
877
- response_headers["HX-Trigger"] = json.dumps({"flockLoaded": None, "flockFileListChanged": None, "notify": {"type": "success", "message": success_message_text}})
881
+ response_headers["HX-Trigger"] = create_hx_trigger_header({"flockLoaded": None, "flockFileListChanged": None, "notify": {"type": "success", "message": success_message_text}})
878
882
  context = get_base_context_web(request, success=success_message_text, ui_mode=ui_mode_query)
879
883
  return templates.TemplateResponse("partials/_execution_view_container.html", context, headers=response_headers)
880
884
  else: error_message_text = f"Failed to process uploaded '{filename_to_load}'."
881
885
 
882
886
  final_error_msg = error_message_text or "Upload failed."
883
- response_headers["HX-Trigger"] = json.dumps({"notify": {"type": "error", "message": final_error_msg}})
887
+ response_headers["HX-Trigger"] = create_hx_trigger_header({"notify": {"type": "error", "message": final_error_msg}})
884
888
  context = get_base_context_web(request, error=final_error_msg, ui_mode=ui_mode_query)
885
889
  return templates.TemplateResponse("partials/_create_flock_form.html", context, headers=response_headers)
886
890
 
@@ -893,7 +897,7 @@ async def ui_create_flock_action(request: Request, flock_name: str = Form(...),
893
897
 
894
898
  new_flock = create_new_flock_service(flock_name, default_model, description, request.app.state)
895
899
  success_msg_text = f"New flock '{new_flock.name}' created. Navigating to Execute page. Configure properties and agents as needed."
896
- response_headers = {"HX-Push-Url": f"/ui/editor/execute?ui_mode={ui_mode_query}", "HX-Trigger": json.dumps({"flockLoaded": None, "notify": {"type": "success", "message": success_msg_text}})}
900
+ response_headers = {"HX-Push-Url": f"/ui/editor/execute?ui_mode={ui_mode_query}", "HX-Trigger": create_hx_trigger_header({"flockLoaded": None, "notify": {"type": "success", "message": success_msg_text}})}
897
901
  context = get_base_context_web(request, success=success_msg_text, ui_mode=ui_mode_query)
898
902
  return templates.TemplateResponse("partials/_execution_view_container.html", context, headers=response_headers)
899
903
 
@@ -7,15 +7,28 @@ from temporalio import activity
7
7
 
8
8
  from flock.core.context.context import FlockContext
9
9
  from flock.core.context.context_vars import FLOCK_CURRENT_AGENT, FLOCK_MODEL
10
- from flock.core.flock_agent import FlockAgent
11
- from flock.core.flock_registry import get_registry
12
- from flock.core.flock_router import HandOffRequest
10
+ from flock.core.registry import get_registry
11
+
12
+ # HandOffRequest removed - using agent.next_agent directly
13
13
  from flock.core.logging.logging import get_logger
14
14
  from flock.core.util.input_resolver import resolve_inputs
15
15
 
16
16
  logger = get_logger("activities")
17
17
  tracer = trace.get_tracer(__name__)
18
18
 
19
+ def apply_handoff_strategy(previous_agent_output:str, next_agent_input:str, previous_agent_handoff_strategy:str, previous_agent_handoff_map:dict[str, str]) -> str:
20
+ if previous_agent_handoff_strategy == "append":
21
+ return next_agent_input + previous_agent_output
22
+ elif previous_agent_handoff_strategy == "override":
23
+ return previous_agent_output
24
+ elif previous_agent_handoff_strategy == "static":
25
+ return next_agent_input
26
+ elif previous_agent_handoff_strategy == "map":
27
+ for key, value in previous_agent_handoff_map.items():
28
+ next_agent_input = next_agent_input.replace(key, value)
29
+ return next_agent_input
30
+ raise NotImplementedError
31
+
19
32
 
20
33
  @activity.defn
21
34
  async def run_agent(context: FlockContext) -> dict:
@@ -29,6 +42,9 @@ async def run_agent(context: FlockContext) -> dict:
29
42
  registry = get_registry()
30
43
 
31
44
  previous_agent_name = ""
45
+ previous_agent_output = ""
46
+ previous_agent_handoff_strategy = ""
47
+ previous_agent_handoff_map = {}
32
48
  if isinstance(context, dict):
33
49
  context = FlockContext.from_dict(context)
34
50
  current_agent_name = context.get_variable(FLOCK_CURRENT_AGENT)
@@ -53,8 +69,14 @@ async def run_agent(context: FlockContext) -> dict:
53
69
  iter_span.set_attribute("agent.name", agent.name)
54
70
  agent.context = context
55
71
  # Resolve inputs for the agent.
72
+ # Gets values from context, previous agent output, and handoff strategy.
56
73
  agent_inputs = resolve_inputs(
57
- agent.input, context, previous_agent_name
74
+ agent.input,
75
+ context,
76
+ previous_agent_name,
77
+ previous_agent_output,
78
+ previous_agent_handoff_strategy,
79
+ previous_agent_handoff_map
58
80
  )
59
81
  iter_span.add_event(
60
82
  "resolved inputs", attributes={"inputs": str(agent_inputs)}
@@ -78,72 +100,44 @@ async def run_agent(context: FlockContext) -> dict:
78
100
  exec_span.record_exception(e)
79
101
  raise
80
102
 
81
- # Determine the next agent using the handoff router if available
82
- handoff_data = HandOffRequest()
103
+ # Determine the next agent using routing component if available
104
+ next_agent_name = None
83
105
 
84
- if agent.handoff_router:
106
+ if agent.router:
85
107
  logger.info(
86
- f"Using handoff router: {agent.handoff_router.__class__.__name__}",
108
+ f"Using router: {agent.router.__class__.__name__}",
87
109
  agent=agent.name,
88
110
  )
89
111
  try:
90
- # Route to the next agent
91
- handoff_data = await agent.handoff_router.route(
112
+ # Route to the next agent using new routing component
113
+ next_agent_name = await agent.router.determine_next_step(
92
114
  agent, result, context
93
115
  )
94
116
 
95
- if callable(handoff_data):
96
- logger.debug(
97
- "Executing handoff function", agent=agent.name
98
- )
99
- try:
100
- handoff_data = handoff_data(context, result)
101
- if isinstance(
102
- handoff_data.next_agent, FlockAgent
103
- ):
104
- handoff_data.next_agent = (
105
- handoff_data.next_agent.name
106
- )
107
- except Exception as e:
108
- logger.error(
109
- "Handoff function error {} {}",
110
- agent=agent.name,
111
- error=str(e),
112
- )
113
- iter_span.record_exception(e)
114
- return {"error": f"Handoff function error: {e}"}
115
- elif isinstance(handoff_data.next_agent, FlockAgent):
116
- handoff_data.next_agent = (
117
- handoff_data.next_agent.name
118
- )
117
+ # Set next_agent on the agent instance
118
+ agent.next_agent = next_agent_name
119
119
 
120
- if not handoff_data.next_agent:
121
- logger.info(
122
- "Router found no suitable next agent",
123
- agent=agent.name,
124
- )
125
- context.record(
126
- agent.name,
127
- result,
128
- timestamp=datetime.now().isoformat(),
129
- hand_off=None,
130
- called_from=previous_agent_name,
131
- )
132
- logger.info("Completing chain", agent=agent.name)
133
- iter_span.add_event("chain completed")
134
- return result
135
120
  except Exception as e:
136
121
  logger.error(
137
- "Router error {} {}",
138
- agent.name,
139
- str(e),
122
+ f"Router error: {e}",
123
+ agent=agent.name,
124
+ error=str(e),
140
125
  )
141
126
  iter_span.record_exception(e)
142
127
  return {"error": f"Router error: {e}"}
143
128
  else:
144
- # No router, so no handoff
129
+ # Check if next_agent was set directly by user
130
+ next_agent_name = agent.next_agent
131
+ if callable(next_agent_name):
132
+ try:
133
+ next_agent_name = next_agent_name(context, result)
134
+ except Exception as e:
135
+ logger.error(f"next_agent callable error: {e}")
136
+ return {"error": f"next_agent callable error: {e}"}
137
+
138
+ if not next_agent_name:
145
139
  logger.info(
146
- "No handoff router defined, completing chain",
140
+ "No next agent found, completing chain",
147
141
  agent=agent.name,
148
142
  )
149
143
  context.record(
@@ -161,53 +155,34 @@ async def run_agent(context: FlockContext) -> dict:
161
155
  agent.name,
162
156
  result,
163
157
  timestamp=datetime.now().isoformat(),
164
- hand_off=handoff_data.model_dump(),
158
+ hand_off={"next_agent": next_agent_name} if next_agent_name else None,
165
159
  called_from=previous_agent_name,
166
160
  )
161
+ # Remember the current agent's details for the next iteration.
167
162
  previous_agent_name = agent.name
168
163
  previous_agent_output = agent.output
169
- if handoff_data.override_context:
170
- context.update(handoff_data.override_context)
164
+ previous_agent_handoff_strategy = agent.config.handoff_strategy
165
+ previous_agent_handoff_map = agent.config.handoff_map
171
166
 
172
- # Prepare the next agent.
167
+ # Activate the next agent.
173
168
  try:
174
- agent = registry.get_agent(handoff_data.next_agent)
175
- if handoff_data.output_to_input_merge_strategy == "add":
176
- agent.input = previous_agent_output + ", " + agent.input
177
-
178
- if handoff_data.add_input_fields:
179
- for field in handoff_data.add_input_fields:
180
- agent.input = field + ", " + agent.input
181
-
182
- if handoff_data.add_output_fields:
183
- for field in handoff_data.add_output_fields:
184
- agent.output = field + ", " + agent.output
185
-
186
- if handoff_data.add_description:
187
- if agent.description:
188
- agent.description = (
189
- agent.description
190
- + "\n"
191
- + handoff_data.add_description
192
- )
193
- else:
194
- agent.description = handoff_data.add_description
195
-
196
- agent.resolve_callables(context=context)
169
+ agent = registry.get_agent(next_agent_name)
197
170
  if not agent:
198
171
  logger.error(
199
172
  "Next agent not found",
200
- agent=handoff_data.next_agent,
173
+ agent=next_agent_name,
201
174
  )
202
175
  iter_span.record_exception(
203
176
  Exception(
204
- f"Next agent '{handoff_data.next_agent}' not found"
177
+ f"Next agent '{next_agent_name}' not found"
205
178
  )
206
179
  )
207
180
  return {
208
- "error": f"Next agent '{handoff_data.next_agent}' not found."
181
+ "error": f"Next agent '{next_agent_name}' not found."
209
182
  }
210
183
 
184
+ agent.resolve_callables(context=context)
185
+
211
186
  context.set_variable(FLOCK_CURRENT_AGENT, agent.name)
212
187
 
213
188
  logger.info("Handing off to next agent", next=agent.name)