flock-core 0.4.0b25__py3-none-any.whl → 0.4.0b27__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.

flock/__init__.py CHANGED
@@ -1,35 +1,31 @@
1
1
  """Flock package initialization."""
2
2
 
3
- from rich.panel import Panel
4
-
5
- from flock.cli.config import init_config_file, load_config_file
6
- from flock.cli.constants import (
7
- CLI_CFG_FILE,
8
- CLI_EXIT,
9
- CLI_NOTES,
10
- CLI_REGISTRY_MANAGEMENT,
11
- CLI_THEME_BUILDER,
12
- )
13
- from flock.cli.load_release_notes import load_release_notes
14
- from flock.cli.settings import settings_editor
15
- from flock.core.logging.formatters.theme_builder import theme_builder
16
-
17
3
 
18
4
  def main():
19
5
  """Main function."""
20
6
  import questionary
21
7
  from rich.console import Console
8
+ from rich.panel import Panel
22
9
 
10
+ from flock.cli.config import init_config_file, load_config_file
23
11
  from flock.cli.constants import (
12
+ CLI_CFG_FILE,
24
13
  CLI_CREATE_AGENT,
25
14
  CLI_CREATE_FLOCK,
15
+ CLI_EXIT,
26
16
  CLI_LOAD_AGENT,
27
17
  CLI_LOAD_EXAMPLE,
28
18
  CLI_LOAD_FLOCK,
19
+ CLI_NOTES,
20
+ CLI_REGISTRY_MANAGEMENT,
29
21
  CLI_SETTINGS,
30
22
  CLI_START_WEB_SERVER,
23
+ CLI_THEME_BUILDER,
31
24
  )
32
25
  from flock.cli.load_flock import load_flock
26
+ from flock.cli.load_release_notes import load_release_notes
27
+ from flock.cli.settings import settings_editor
28
+ from flock.core.logging.formatters.theme_builder import theme_builder
33
29
  from flock.core.util.cli_helper import init_console
34
30
 
35
31
  console = Console()
@@ -1,14 +1,12 @@
1
1
  from pathlib import Path
2
2
 
3
- from flock.core.util.cli_helper import display_hummingbird
4
-
5
3
 
6
4
  def load_release_notes():
7
5
  """Load release notes."""
8
6
  from rich.console import Console
9
7
  from rich.markdown import Markdown
10
8
 
11
- from flock.core.util.cli_helper import init_console
9
+ from flock.core.util.cli_helper import display_hummingbird, init_console
12
10
 
13
11
  console = Console()
14
12
  file_path = Path(__file__).parent / "assets" / "release_notes.md"
@@ -19,5 +17,4 @@ def load_release_notes():
19
17
  with open(file_path) as file:
20
18
  release_notes = file.read()
21
19
 
22
-
23
20
  console.print(Markdown(release_notes))
@@ -20,7 +20,7 @@ class AgentRunRecord(BaseModel):
20
20
  data: dict[str, Any] = Field(default_factory=dict)
21
21
  timestamp: str = Field(default="")
22
22
  hand_off: dict | None = Field(default_factory=dict)
23
- called_from: str = Field(default="")
23
+ called_from: str | None = Field(default=None)
24
24
 
25
25
 
26
26
  class AgentDefinition(BaseModel):
@@ -132,6 +132,15 @@ class FlockContext(Serializable, BaseModel):
132
132
  def get_agent_definition(self, agent_name: str) -> AgentDefinition | None:
133
133
  return self.agent_definitions.get(agent_name)
134
134
 
135
+ def get_last_agent_name(self) -> str | None:
136
+ """Returns the name of the agent from the most recent history record."""
137
+ if not self.history:
138
+ return None
139
+ last_record = self.history[-1]
140
+ # The 'called_from' field in the *next* record is the previous agent.
141
+ # However, to get the name of the *last executed agent*, we look at the 'agent' field.
142
+ return last_record.agent
143
+
135
144
  def add_agent_definition(
136
145
  self, agent_type: type, agent_name: str, agent_data: Any
137
146
  ) -> None:
@@ -1,49 +1,166 @@
1
1
  # src/your_package/core/execution/temporal_executor.py
2
2
 
3
+ import asyncio # Import asyncio
4
+ from typing import TYPE_CHECKING, Any
5
+
6
+ from temporalio.worker import Worker # Import Worker
7
+
8
+ if TYPE_CHECKING:
9
+ from flock.core.flock import Flock # Import Flock for type hinting
10
+
3
11
  from flock.core.context.context import FlockContext
4
12
  from flock.core.context.context_vars import FLOCK_RUN_ID
5
13
  from flock.core.logging.logging import get_logger
6
- from flock.workflow.activities import (
7
- run_agent, # Activity function used in Temporal
14
+ from flock.workflow.agent_execution_activity import (
15
+ determine_next_agent,
16
+ execute_single_agent,
17
+ )
18
+ from flock.workflow.temporal_config import (
19
+ TemporalRetryPolicyConfig,
20
+ TemporalWorkflowConfig,
8
21
  )
9
22
  from flock.workflow.temporal_setup import create_temporal_client, setup_worker
10
- from flock.workflow.workflow import FlockWorkflow # Your workflow class
11
23
 
12
24
  logger = get_logger("flock")
13
25
 
14
26
 
15
27
  async def run_temporal_workflow(
28
+ flock_instance: "Flock", # Accept Flock instance
16
29
  context: FlockContext,
17
30
  box_result: bool = True,
31
+ memo: dict[str, Any] | None = None, # Add memo argument
18
32
  ) -> dict:
19
33
  """Execute the agent workflow via Temporal for robust, distributed processing.
20
34
 
21
35
  Args:
36
+ flock_instance: The Flock instance.
22
37
  context: The FlockContext instance with state and history.
23
38
  box_result: If True, wraps the result in a Box for nicer display.
39
+ memo: Optional dictionary of metadata to attach to the Temporal workflow.
24
40
 
25
41
  Returns:
26
42
  A dictionary containing the workflow result.
27
43
  """
28
- logger.info("Setting up Temporal workflow")
29
- await setup_worker(workflow=FlockWorkflow, activity=run_agent)
30
- logger.debug("Creating Temporal client")
31
- flock_client = await create_temporal_client()
32
- workflow_id = context.get_variable(FLOCK_RUN_ID)
33
- logger.info("Executing Temporal workflow", workflow_id=workflow_id)
34
- result = await flock_client.execute_workflow(
35
- FlockWorkflow.run,
36
- context.to_dict(),
37
- id=workflow_id,
38
- task_queue="flock-queue",
39
- )
40
-
41
- agent_name = context.get_variable("FLOCK_CURRENT_AGENT")
42
- logger.debug("Formatting Temporal result", agent=agent_name)
43
-
44
- if box_result:
45
- from box import Box
46
-
47
- logger.debug("Boxing Temporal result")
48
- return Box(result)
49
- return result
44
+ try:
45
+ from flock.workflow.flock_workflow import (
46
+ FlockWorkflow, # Your workflow class
47
+ )
48
+
49
+ # Get workflow config from Flock instance or use defaults
50
+ wf_config = flock_instance.temporal_config or TemporalWorkflowConfig()
51
+
52
+ logger.debug("Creating Temporal client")
53
+ flock_client = await create_temporal_client()
54
+
55
+ # Determine if we need to manage an in-process worker
56
+ start_worker_locally = flock_instance.temporal_start_in_process_worker
57
+
58
+ # Setup worker instance
59
+ worker: Worker | None = None
60
+ worker_task: asyncio.Task | None = None
61
+
62
+ if start_worker_locally:
63
+ logger.info(
64
+ f"Setting up temporary in-process worker for task queue '{wf_config.task_queue}'"
65
+ )
66
+ worker = await setup_worker(
67
+ flock_client, # Pass the client
68
+ wf_config.task_queue, # Pass the task queue
69
+ FlockWorkflow,
70
+ [execute_single_agent, determine_next_agent],
71
+ )
72
+
73
+ # Run the worker in the background
74
+ worker_task = asyncio.create_task(worker.run())
75
+ logger.info("Temporal worker started in background.")
76
+
77
+ # Allow worker time to start polling (heuristic for local testing)
78
+ await asyncio.sleep(2)
79
+ else:
80
+ logger.info(
81
+ "Skipping in-process worker startup. Assuming dedicated workers are running."
82
+ )
83
+
84
+ try:
85
+ workflow_id = context.get_variable(FLOCK_RUN_ID)
86
+ logger.info(
87
+ "Executing Temporal workflow",
88
+ workflow_id=workflow_id,
89
+ task_queue=wf_config.task_queue,
90
+ )
91
+
92
+ # Prepare the single workflow argument dictionary
93
+ workflow_args_dict = {
94
+ "context_dict": context.model_dump(mode="json"),
95
+ "default_retry_config_dict": (
96
+ wf_config.default_activity_retry_policy.model_dump(
97
+ mode="json"
98
+ )
99
+ if wf_config.default_activity_retry_policy
100
+ else TemporalRetryPolicyConfig().model_dump(mode="json")
101
+ ),
102
+ }
103
+
104
+ # Start the workflow using start_workflow
105
+ handle = await flock_client.start_workflow(
106
+ FlockWorkflow.run,
107
+ # Pass the single dictionary as the only element in the args list
108
+ args=[workflow_args_dict],
109
+ id=workflow_id,
110
+ task_queue=wf_config.task_queue,
111
+ # Corrected timeout argument names
112
+ execution_timeout=wf_config.workflow_execution_timeout,
113
+ run_timeout=wf_config.workflow_run_timeout,
114
+ memo=memo or {}, # Pass memo if provided
115
+ )
116
+
117
+ logger.info(
118
+ "Workflow started, awaiting result...", workflow_id=handle.id
119
+ )
120
+ # Await the result from the handle
121
+ result = await handle.result()
122
+ logger.info("Workflow result received.")
123
+
124
+ agent_name = context.get_variable("FLOCK_CURRENT_AGENT")
125
+ logger.debug("Formatting Temporal result", agent=agent_name)
126
+
127
+ if box_result:
128
+ from box import Box
129
+
130
+ logger.debug("Boxing Temporal result")
131
+ return Box(result)
132
+ return result
133
+ except Exception as e:
134
+ logger.error(
135
+ "Error during Temporal workflow execution or result retrieval",
136
+ error=e,
137
+ )
138
+ raise e # Re-raise the exception after logging
139
+ finally:
140
+ # Ensure worker is shut down regardless of success or failure
141
+ if (
142
+ start_worker_locally
143
+ and worker
144
+ and worker_task
145
+ and not worker_task.done()
146
+ ):
147
+ logger.info("Shutting down temporal worker...")
148
+ await worker.shutdown() # Await the shutdown coroutine
149
+ try:
150
+ await asyncio.wait_for(
151
+ worker_task, timeout=10.0
152
+ ) # Wait for task to finish
153
+ logger.info("Temporal worker shut down gracefully.")
154
+ except asyncio.TimeoutError:
155
+ logger.warning(
156
+ "Temporal worker shutdown timed out. Cancelling task."
157
+ )
158
+ worker_task.cancel()
159
+ except Exception as shutdown_err:
160
+ logger.error(
161
+ f"Error during worker shutdown: {shutdown_err}",
162
+ exc_info=True,
163
+ )
164
+ except Exception as e:
165
+ logger.error("Error executing Temporal workflow", error=e)
166
+ raise e
flock/core/flock.py CHANGED
@@ -17,7 +17,14 @@ from typing import (
17
17
 
18
18
  # Third-party imports
19
19
  from box import Box
20
- from datasets import Dataset
20
+ from temporalio import workflow
21
+
22
+ with workflow.unsafe.imports_passed_through():
23
+ from datasets import Dataset
24
+
25
+ from flock.core.execution.local_executor import (
26
+ run_local_workflow,
27
+ )
21
28
  from opentelemetry import trace
22
29
  from opentelemetry.baggage import get_baggage, set_baggage
23
30
  from pandas import DataFrame
@@ -27,12 +34,12 @@ from pydantic import BaseModel, Field
27
34
  from flock.config import DEFAULT_MODEL, TELEMETRY
28
35
  from flock.core.context.context import FlockContext
29
36
  from flock.core.context.context_manager import initialize_context
30
- from flock.core.execution.local_executor import run_local_workflow
31
37
  from flock.core.execution.temporal_executor import run_temporal_workflow
32
38
  from flock.core.flock_evaluator import FlockEvaluator
33
39
  from flock.core.logging.logging import LOGGERS, get_logger, get_module_loggers
34
40
  from flock.core.serialization.serializable import Serializable
35
41
  from flock.core.util.cli_helper import init_console
42
+ from flock.workflow.temporal_config import TemporalWorkflowConfig
36
43
 
37
44
  # Import FlockAgent using TYPE_CHECKING to avoid circular import at runtime
38
45
  if TYPE_CHECKING:
@@ -59,9 +66,9 @@ FlockRegistry = get_registry() # Get the registry instance
59
66
  # Define TypeVar for generic class methods like from_dict
60
67
  T = TypeVar("T", bound="Flock")
61
68
 
62
- from rich.traceback import install
69
+ # from rich.traceback import install
63
70
 
64
- install(show_locals=True)
71
+ # install(show_locals=True)
65
72
 
66
73
 
67
74
  class Flock(BaseModel, Serializable):
@@ -96,6 +103,16 @@ class Flock(BaseModel, Serializable):
96
103
  default=True,
97
104
  description="If True, show the Flock banner on console interactions.",
98
105
  )
106
+ # --- Temporal Configuration (Optional) ---
107
+ temporal_config: TemporalWorkflowConfig | None = Field(
108
+ default=None,
109
+ description="Optional Temporal settings specific to the workflow execution for this Flock.",
110
+ )
111
+ # --- Temporal Dev/Test Setting ---
112
+ temporal_start_in_process_worker: bool = Field(
113
+ default=True,
114
+ description="If True (default) and enable_temporal=True, start a temporary in-process worker for development/testing convenience. Set to False when using dedicated workers.",
115
+ )
99
116
  # Internal agent storage - not part of the Pydantic model for direct serialization
100
117
  _agents: dict[str, FlockAgent]
101
118
  _start_agent_name: str | None = None # For potential pre-configuration
@@ -116,6 +133,8 @@ class Flock(BaseModel, Serializable):
116
133
  enable_temporal: bool = False,
117
134
  enable_logging: bool | list[str] = False,
118
135
  agents: list[FlockAgent] | None = None,
136
+ temporal_config: TemporalWorkflowConfig | None = None,
137
+ temporal_start_in_process_worker: bool = True,
119
138
  **kwargs,
120
139
  ):
121
140
  """Initialize the Flock orchestrator."""
@@ -130,6 +149,8 @@ class Flock(BaseModel, Serializable):
130
149
  enable_temporal=enable_temporal,
131
150
  enable_logging=enable_logging,
132
151
  show_flock_banner=show_flock_banner,
152
+ temporal_config=temporal_config,
153
+ temporal_start_in_process_worker=temporal_start_in_process_worker,
133
154
  **kwargs,
134
155
  )
135
156
 
@@ -305,6 +326,7 @@ class Flock(BaseModel, Serializable):
305
326
  run_id: str = "",
306
327
  box_result: bool = True,
307
328
  agents: list[FlockAgent] | None = None,
329
+ memo: dict[str, Any] | None = None,
308
330
  ) -> Box | dict:
309
331
  """Entry point for running an agent system asynchronously."""
310
332
  # Import here to allow forward reference resolution
@@ -342,7 +364,17 @@ class Flock(BaseModel, Serializable):
342
364
 
343
365
  # Check if start_agent is in agents
344
366
  if start_agent_name not in self._agents:
345
- raise ValueError(f"Start agent '{start_agent_name}' not found.")
367
+ # Try loading from registry if not found locally yet
368
+ reg_agent = FlockRegistry.get_agent(start_agent_name)
369
+ if reg_agent:
370
+ self.add_agent(reg_agent)
371
+ logger.info(
372
+ f"Loaded start agent '{start_agent_name}' from registry."
373
+ )
374
+ else:
375
+ raise ValueError(
376
+ f"Start agent '{start_agent_name}' not found locally or in registry."
377
+ )
346
378
 
347
379
  run_input = input if input is not None else self._start_input
348
380
  effective_run_id = run_id or f"flockrun_{uuid.uuid4().hex[:8]}"
@@ -388,6 +420,15 @@ class Flock(BaseModel, Serializable):
388
420
  agent_data=agent_dict_repr, # Pass the serialized dict
389
421
  )
390
422
 
423
+ # Add temporal config to context if enabled
424
+ if self.enable_temporal and self.temporal_config:
425
+ # Store the workflow config dict for the executor/workflow to use
426
+ # Using a specific key to avoid potential clashes in state
427
+ run_context.set_variable(
428
+ "flock.temporal_workflow_config",
429
+ self.temporal_config.model_dump(mode="json"),
430
+ )
431
+
391
432
  logger.info(
392
433
  "Starting agent execution",
393
434
  agent=start_agent_name,
@@ -400,8 +441,15 @@ class Flock(BaseModel, Serializable):
400
441
  run_context, box_result=False
401
442
  )
402
443
  else:
444
+ # Pass the Flock instance itself to the executor
445
+ # so it can access the temporal_config directly if needed
446
+ # This avoids putting potentially large/complex config objects
447
+ # directly into the context state that gets passed around.
403
448
  result = await run_temporal_workflow(
404
- run_context, box_result=False
449
+ self, # Pass the Flock instance
450
+ run_context,
451
+ box_result=False,
452
+ memo=memo,
405
453
  )
406
454
 
407
455
  span.set_attribute("result.type", str(type(result)))
@@ -662,6 +710,7 @@ class Flock(BaseModel, Serializable):
662
710
  # Import locally to prevent circular imports at module level if structure is complex
663
711
  from flock.core.serialization.flock_serializer import FlockSerializer
664
712
 
713
+ # Assuming FlockSerializer handles the nested temporal_config serialization
665
714
  return FlockSerializer.serialize(self, path_type=path_type)
666
715
 
667
716
  @classmethod
@@ -670,6 +719,7 @@ class Flock(BaseModel, Serializable):
670
719
  # Import locally
671
720
  from flock.core.serialization.flock_serializer import FlockSerializer
672
721
 
722
+ # Assuming FlockSerializer handles the nested temporal_config deserialization
673
723
  return FlockSerializer.deserialize(cls, data)
674
724
 
675
725
  # --- Static Method Loader (Delegates to loader module) ---