flock-core 0.1.1__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 (48) hide show
  1. flock/__init__.py +4 -0
  2. flock/agents/__init__.py +3 -0
  3. flock/agents/batch_agent.py +175 -0
  4. flock/agents/declarative_agent.py +166 -0
  5. flock/agents/loop_agent.py +178 -0
  6. flock/agents/trigger_agent.py +191 -0
  7. flock/agents/user_agent.py +230 -0
  8. flock/app/components/__init__.py +14 -0
  9. flock/app/components/charts/agent_workflow.py +14 -0
  10. flock/app/components/charts/core_architecture.py +14 -0
  11. flock/app/components/charts/tool_system.py +14 -0
  12. flock/app/components/history_grid.py +168 -0
  13. flock/app/components/history_grid_alt.py +189 -0
  14. flock/app/components/sidebar.py +19 -0
  15. flock/app/components/theme.py +9 -0
  16. flock/app/components/util.py +18 -0
  17. flock/app/hive_app.py +118 -0
  18. flock/app/html/d3.html +179 -0
  19. flock/app/modules/__init__.py +12 -0
  20. flock/app/modules/about.py +17 -0
  21. flock/app/modules/agent_detail.py +70 -0
  22. flock/app/modules/agent_list.py +59 -0
  23. flock/app/modules/playground.py +322 -0
  24. flock/app/modules/settings.py +96 -0
  25. flock/core/__init__.py +7 -0
  26. flock/core/agent.py +150 -0
  27. flock/core/agent_registry.py +162 -0
  28. flock/core/config/declarative_agent_config.py +0 -0
  29. flock/core/context.py +279 -0
  30. flock/core/context_vars.py +6 -0
  31. flock/core/flock.py +208 -0
  32. flock/core/handoff/handoff_base.py +12 -0
  33. flock/core/logging/__init__.py +18 -0
  34. flock/core/logging/error_handler.py +84 -0
  35. flock/core/logging/formatters.py +122 -0
  36. flock/core/logging/handlers.py +117 -0
  37. flock/core/logging/logger.py +107 -0
  38. flock/core/serializable.py +206 -0
  39. flock/core/tools/basic_tools.py +98 -0
  40. flock/workflow/activities.py +115 -0
  41. flock/workflow/agent_activities.py +26 -0
  42. flock/workflow/temporal_setup.py +37 -0
  43. flock/workflow/workflow.py +53 -0
  44. flock_core-0.1.1.dist-info/METADATA +449 -0
  45. flock_core-0.1.1.dist-info/RECORD +48 -0
  46. flock_core-0.1.1.dist-info/WHEEL +4 -0
  47. flock_core-0.1.1.dist-info/entry_points.txt +2 -0
  48. flock_core-0.1.1.dist-info/licenses/LICENSE +21 -0
flock/core/flock.py ADDED
@@ -0,0 +1,208 @@
1
+ # src/your_package/core/manager.py
2
+ import contextlib
3
+ import os
4
+ import uuid
5
+ from typing import TypeVar
6
+
7
+ from flock.core.agent_registry import Registry
8
+ from flock.core.context import FlockContext
9
+ from flock.core.context_vars import FLOCK_CURRENT_AGENT, FLOCK_INITIAL_INPUT, FLOCK_LOCAL_DEBUG, FLOCK_RUN_ID
10
+ from flock.core.logging import flock_logger, performance_handler
11
+ from flock.core.logging.formatters import AgentResultFormatter
12
+ from flock.core.logging.logger import FlockLogger, LogLevel
13
+ from flock.workflow.activities import run_agent
14
+ from flock.workflow.temporal_setup import create_temporal_client, setup_worker
15
+ from flock.workflow.workflow import FlockWorkflow
16
+
17
+ from .agent import Agent
18
+
19
+ T = TypeVar("T", bound=Agent)
20
+
21
+
22
+ class Flock:
23
+ """Manages creation and execution of agents.
24
+ This is a high-level class that the user interacts with directly.
25
+ """
26
+
27
+ def __init__(
28
+ self,
29
+ model: str = "openai/gpt-4o",
30
+ log_level: int = LogLevel.MINIMAL,
31
+ enable_performance_tracking: bool = False,
32
+ local_debug: bool = False,
33
+ ):
34
+ """Initialize the Flock.
35
+
36
+ Args:
37
+ model: The default model to use for agents.
38
+ log_level: The logging verbosity level:
39
+ - LogLevel.NONE: No logging except errors
40
+ - LogLevel.MINIMAL: Only agent outputs (default)
41
+ - LogLevel.BASIC: Agent outputs + basic workflow info
42
+ - LogLevel.VERBOSE: Everything including debug info
43
+ log_format: The logging format to use:
44
+ - LogFormat.SIMPLE: Simple message format (default)
45
+ - LogFormat.STRUCTURED: Table-like structured format
46
+ enable_performance_tracking: Whether to track and display performance metrics.
47
+ local_debug: Whether to run in local debug mode (default: False).
48
+ """
49
+ self.agents: dict[str, Agent] = {}
50
+ self.registry = Registry()
51
+ self.context = FlockContext()
52
+ self.model = model
53
+ self.enable_performance_tracking = enable_performance_tracking
54
+ self.local_debug = local_debug
55
+
56
+ # Configure performance tracking
57
+ if enable_performance_tracking:
58
+ performance_handler.enable()
59
+ else:
60
+ performance_handler.disable()
61
+
62
+ # Set LOCAL_DEBUG environment variable for console detection
63
+ if local_debug:
64
+ os.environ["LOCAL_DEBUG"] = "1"
65
+ elif "LOCAL_DEBUG" in os.environ:
66
+ del os.environ["LOCAL_DEBUG"]
67
+
68
+ # Configure logging
69
+ global flock_logger
70
+ flock_logger = FlockLogger(level=log_level)
71
+ flock_logger.info("Initialized Flock", model=model, local_debug=local_debug)
72
+
73
+ def add_agent(self, agent: T) -> T:
74
+ if not agent.model:
75
+ agent.model = self.model
76
+
77
+ if agent.name in self.agents:
78
+ flock_logger.debug(f"Agent {agent.name} already exists, returning existing instance")
79
+ return self.agents[agent.name]
80
+
81
+ self.agents[agent.name] = agent
82
+ self.registry.register_agent(agent)
83
+ self.context.add_agent_definition(type(agent), agent.name, agent.to_dict())
84
+ if hasattr(agent, "tools") and agent.tools:
85
+ for tool in agent.tools:
86
+ self.registry.register_tool(tool.__name__, tool)
87
+ flock_logger.debug(f"Registered tool: {tool.__name__}")
88
+
89
+ flock_logger.info(f"Added agent: {agent.name}", model=agent.model)
90
+ return agent
91
+
92
+ def add_tool(self, tool_name: str, tool: callable):
93
+ self.registry.register_tool(tool_name, tool)
94
+ flock_logger.debug(f"Registered tool: {tool_name}")
95
+
96
+ async def _run_local_debug_workflow(self, box_result: bool = True):
97
+ if self.enable_performance_tracking:
98
+ perf_context = performance_handler.track_time("local_workflow")
99
+ else:
100
+ perf_context = contextlib.nullcontext()
101
+
102
+ with perf_context:
103
+ flock_logger.info("Running workflow in local debug mode")
104
+ result = await run_agent(self.context)
105
+
106
+ # Format and display the result (always shown regardless of log level)
107
+ agent_name = self.context.get_variable(FLOCK_CURRENT_AGENT)
108
+ AgentResultFormatter.print_result(result, agent_name)
109
+
110
+ if self.enable_performance_tracking:
111
+ performance_handler.display_timings()
112
+
113
+ if box_result:
114
+ from box import Box
115
+
116
+ flock_logger.debug("Boxing result")
117
+ return Box(result)
118
+ return result
119
+
120
+ async def _run_temporal_workflow(self, box_result: bool = True):
121
+ if self.enable_performance_tracking:
122
+ perf_context = performance_handler.track_time("temporal_workflow")
123
+ else:
124
+ perf_context = contextlib.nullcontext()
125
+
126
+ with perf_context:
127
+ flock_logger.info("Connecting to Temporal and starting worker...")
128
+
129
+ await setup_worker(workflow=FlockWorkflow, activity=run_agent)
130
+
131
+ flock_logger.info("Starting workflow execution")
132
+
133
+ flock_client = await create_temporal_client()
134
+ result = await flock_client.execute_workflow(
135
+ FlockWorkflow.run,
136
+ self.context.to_dict(),
137
+ id=self.context.get_variable(FLOCK_RUN_ID),
138
+ task_queue="flock-queue",
139
+ )
140
+
141
+ # Format and display the result (always shown regardless of log level)
142
+ agent_name = self.context.get_variable(FLOCK_CURRENT_AGENT)
143
+ flock_logger.result(result, agent_name)
144
+
145
+ if self.enable_performance_tracking:
146
+ performance_handler.display_timings()
147
+
148
+ if box_result:
149
+ from box import Box
150
+
151
+ flock_logger.debug("Boxing result")
152
+ return Box(result)
153
+ return result
154
+
155
+ async def run_async(
156
+ self,
157
+ start_agent: Agent | str,
158
+ input: str,
159
+ context: FlockContext = None,
160
+ run_id: str = "",
161
+ box_result: bool = True,
162
+ ) -> dict:
163
+ """Entry point for running an agent system."""
164
+ try:
165
+ if self.enable_performance_tracking:
166
+ perf_context = performance_handler.track_time("run_setup")
167
+ else:
168
+ perf_context = contextlib.nullcontext()
169
+
170
+ with perf_context:
171
+ if isinstance(start_agent, str):
172
+ flock_logger.debug(f"Looking up agent by name: {start_agent}")
173
+ start_agent = self.registry.get_agent(start_agent)
174
+ if not start_agent:
175
+ raise ValueError(f"Agent '{start_agent}' not found in registry")
176
+
177
+ if context:
178
+ flock_logger.debug("Using provided context")
179
+ self.context = context
180
+
181
+ flock_logger.info("Setting up run context", agent=start_agent.name, input=input)
182
+ self.context.set_variable(FLOCK_CURRENT_AGENT, start_agent.name)
183
+ self.context.set_variable(FLOCK_INITIAL_INPUT, input)
184
+ self.context.set_variable(FLOCK_LOCAL_DEBUG, self.local_debug)
185
+
186
+ if not run_id:
187
+ run_id = start_agent.name + "_" + uuid.uuid4().hex[:4]
188
+ flock_logger.debug(f"Generated run ID: {run_id}")
189
+
190
+ self.context.set_variable(FLOCK_RUN_ID, run_id)
191
+
192
+ if self.enable_performance_tracking:
193
+ performance_handler.display_timings()
194
+
195
+ # we can run the flow locally for debugging purposes
196
+ if self.local_debug:
197
+ return await self._run_local_debug_workflow(box_result)
198
+ else:
199
+ return await self._run_temporal_workflow(box_result)
200
+
201
+ except Exception as e:
202
+ flock_logger.error(
203
+ "Run failed",
204
+ error=str(e),
205
+ agent=getattr(start_agent, "name", str(start_agent)),
206
+ input=input,
207
+ )
208
+ raise
@@ -0,0 +1,12 @@
1
+ from typing import Any
2
+
3
+ from flock.core.agent import Agent
4
+ from flock.core.context import FlockContext
5
+
6
+
7
+ class HandoffBase:
8
+ """Base class for handoff implementations."""
9
+
10
+ next_agent: str | Agent
11
+ input: dict[str, Any]
12
+ context: FlockContext
@@ -0,0 +1,18 @@
1
+ """Flock logging system with Rich integration and Temporal compatibility."""
2
+
3
+ from flock.core.logging.error_handler import error_handler
4
+ from flock.core.logging.formatters import PerformanceFormatter, StructuredFormatter
5
+ from flock.core.logging.handlers import live_update_handler, performance_handler
6
+ from flock.core.logging.logger import flock_logger
7
+
8
+ # Install the Rich error handler by default
9
+ error_handler.install()
10
+
11
+ __all__ = [
12
+ "PerformanceFormatter",
13
+ "StructuredFormatter",
14
+ "error_handler",
15
+ "flock_logger",
16
+ "live_update_handler",
17
+ "performance_handler",
18
+ ]
@@ -0,0 +1,84 @@
1
+ import sys
2
+ from types import TracebackType
3
+
4
+ from temporalio import workflow
5
+
6
+ with workflow.unsafe.imports_passed_through():
7
+ from rich.console import Console
8
+ from rich.panel import Panel
9
+ from rich.syntax import Syntax
10
+ from rich.traceback import Traceback
11
+
12
+ from flock.core.logging.logger import flock_logger
13
+
14
+
15
+ class ErrorHandler:
16
+ """Handles error formatting and display using Rich."""
17
+
18
+ def __init__(self, console: Console | None = None):
19
+ self.console = console or Console()
20
+
21
+ def format_exception(
22
+ self,
23
+ exc_type: type[BaseException],
24
+ exc_value: BaseException,
25
+ exc_tb: TracebackType,
26
+ *,
27
+ show_locals: bool = True,
28
+ ) -> Panel:
29
+ """Format an exception with Rich styling."""
30
+ # Create a Rich traceback
31
+ rich_tb = Traceback.from_exception(
32
+ exc_type,
33
+ exc_value,
34
+ exc_tb,
35
+ show_locals=show_locals,
36
+ )
37
+
38
+ # If there's source code available, syntax highlight it
39
+ if exc_tb and exc_tb.tb_frame.f_code.co_filename != "<string>":
40
+ try:
41
+ with open(exc_tb.tb_frame.f_code.co_filename) as f:
42
+ source = f.read()
43
+ syntax = Syntax(
44
+ source,
45
+ "python",
46
+ line_numbers=True,
47
+ highlight_lines={exc_tb.tb_lineno},
48
+ )
49
+ except:
50
+ syntax = None
51
+ else:
52
+ syntax = None
53
+
54
+ # Create a panel with the traceback
55
+ return Panel(
56
+ rich_tb,
57
+ title=f"[red]{exc_type.__name__}[/]: {exc_value!s}",
58
+ border_style="red",
59
+ padding=(1, 2),
60
+ )
61
+
62
+ def handle_exception(
63
+ self,
64
+ exc_type: type[BaseException],
65
+ exc_value: BaseException,
66
+ exc_tb: TracebackType,
67
+ ) -> None:
68
+ """Handle an exception by formatting and displaying it."""
69
+ panel = self.format_exception(exc_type, exc_value, exc_tb)
70
+ self.console.print(panel)
71
+ # Also log the error through our logger
72
+ flock_logger.error(
73
+ f"Exception occurred: {exc_type.__name__}: {exc_value!s}",
74
+ error_type=exc_type.__name__,
75
+ error_details=str(exc_value),
76
+ )
77
+
78
+ def install(self) -> None:
79
+ """Install this error handler as the default exception handler."""
80
+ sys.excepthook = self.handle_exception
81
+
82
+
83
+ # Global error handler instance
84
+ error_handler = ErrorHandler()
@@ -0,0 +1,122 @@
1
+ from typing import Any
2
+
3
+ from devtools import pprint
4
+ from temporalio import workflow
5
+
6
+ with workflow.unsafe.imports_passed_through():
7
+ from rich.console import Console
8
+ from rich.panel import Panel
9
+ from rich.progress import BarColumn, Progress, SpinnerColumn, TextColumn, TimeElapsedColumn
10
+ from rich.table import Table
11
+
12
+
13
+ class StructuredFormatter:
14
+ """Formats structured data for Rich output."""
15
+
16
+ @staticmethod
17
+ def create_status_table(data: dict[str, Any]) -> Table:
18
+ """Create a Rich table for displaying status information."""
19
+ table = Table(show_header=True, header_style="bold magenta")
20
+ table.add_column("Key")
21
+ table.add_column("Value")
22
+
23
+ for key, value in data.items():
24
+ table.add_row(str(key), str(value))
25
+
26
+ return table
27
+
28
+ @staticmethod
29
+ def create_progress_bar(description: str = "Progress") -> Progress:
30
+ """Create a Rich progress bar with time tracking."""
31
+ return Progress(
32
+ SpinnerColumn(),
33
+ TextColumn("[progress.description]{task.description}"),
34
+ BarColumn(),
35
+ TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
36
+ TimeElapsedColumn(),
37
+ )
38
+
39
+ @staticmethod
40
+ def create_workflow_panel(
41
+ workflow_id: str, status: str, details: dict[str, Any], title: str = "Workflow Status"
42
+ ) -> Panel:
43
+ """Create a Rich panel for workflow information."""
44
+ table = StructuredFormatter.create_status_table({"Workflow ID": workflow_id, "Status": status, **details})
45
+ return Panel(table, title=title, border_style="blue")
46
+
47
+ @staticmethod
48
+ def create_activity_panel(
49
+ activity_id: str, name: str, status: str, details: dict[str, Any], title: str = "Activity Status"
50
+ ) -> Panel:
51
+ """Create a Rich panel for activity information."""
52
+ table = StructuredFormatter.create_status_table(
53
+ {"Activity ID": activity_id, "Name": name, "Status": status, **details}
54
+ )
55
+ return Panel(table, title=title, border_style="magenta")
56
+
57
+
58
+ class PerformanceFormatter:
59
+ """Formats performance metrics for Rich output."""
60
+
61
+ @staticmethod
62
+ def create_timing_tree(timings: dict[str, float]) -> str:
63
+ """Create a tree-like structure showing execution timings."""
64
+ if not timings:
65
+ return "No timings recorded"
66
+
67
+ result = "Performance Metrics:\n"
68
+ for operation, duration in timings.items():
69
+ result += f"├── {operation}: {duration:.3f}s\n"
70
+ return result[:-1] # Remove trailing newline
71
+
72
+ @staticmethod
73
+ def create_performance_panel(metrics: dict[str, Any], title: str = "Performance Metrics") -> Panel:
74
+ """Create a panel showing performance metrics."""
75
+ table = StructuredFormatter.create_status_table(metrics)
76
+ return Panel(table, title=title, border_style="cyan")
77
+
78
+
79
+ class AgentResultFormatter:
80
+ """Formats agent results in a beautiful Rich table."""
81
+
82
+ @staticmethod
83
+ def format_result(result: dict[str, Any], agent_name: str) -> Panel:
84
+ """Format an agent's result as a Rich panel containing a table."""
85
+ # Create a table with a nice header
86
+ table = Table(
87
+ show_header=True,
88
+ header_style="bold green",
89
+ title=f"Agent Results: {agent_name}",
90
+ title_style="bold blue",
91
+ border_style="bright_blue",
92
+ )
93
+ table.add_column("Output", style="cyan")
94
+ table.add_column("Value", style="green")
95
+
96
+ # Add each result to the table
97
+ for key, value in result.items():
98
+ # Format multi-line values (like blog headers) nicely
99
+ if isinstance(value, (list, tuple)) or (isinstance(value, str) and "\n" in value):
100
+ formatted_value = "\n".join(value) if isinstance(value, (list, tuple)) else value
101
+ # Add some padding for multi-line values
102
+ table.add_row(key, f"\n{formatted_value}\n")
103
+ else:
104
+ table.add_row(key, str(value))
105
+
106
+ pprint(result)
107
+
108
+ # Wrap the table in a panel for a nice border
109
+ return Panel(
110
+ table,
111
+ title="🎯 Agent Output",
112
+ title_align="left",
113
+ border_style="blue",
114
+ padding=(1, 2),
115
+ )
116
+
117
+ @staticmethod
118
+ def print_result(result: dict[str, Any], agent_name: str, console: Console | None = None) -> None:
119
+ """Print an agent's result using Rich formatting."""
120
+ console = console or Console()
121
+ panel = AgentResultFormatter.format_result(result, agent_name)
122
+ console.print(panel)
@@ -0,0 +1,117 @@
1
+ import time
2
+
3
+ from temporalio import workflow
4
+
5
+ with workflow.unsafe.imports_passed_through():
6
+ from rich.console import Console
7
+ from rich.live import Live
8
+
9
+ from collections.abc import Callable, Generator
10
+ from contextlib import contextmanager
11
+ from functools import wraps
12
+ from typing import Any, TypeVar
13
+
14
+ from flock.core.logging.formatters import PerformanceFormatter, StructuredFormatter
15
+
16
+ T = TypeVar("T")
17
+
18
+
19
+ class PerformanceHandler:
20
+ """Handles performance tracking and reporting."""
21
+
22
+ def __init__(self, console: Console | None = None):
23
+ self.console = console or Console()
24
+ self.timings: dict[str, float] = {}
25
+ self.enabled = False
26
+
27
+ def enable(self):
28
+ """Enable performance tracking."""
29
+ self.enabled = True
30
+
31
+ def disable(self):
32
+ """Disable performance tracking."""
33
+ self.enabled = False
34
+
35
+ def _get_time(self) -> float:
36
+ """Get current time in a workflow-safe way."""
37
+ try:
38
+ # Try to get workflow time first
39
+ return workflow.now().timestamp()
40
+ except workflow._NotInWorkflowEventLoopError:
41
+ # Fall back to system time if not in workflow
42
+ return time.time()
43
+
44
+ @contextmanager
45
+ def track_time(self, operation_name: str) -> Generator[None, None, None]:
46
+ """Context manager for tracking operation execution time."""
47
+ if not self.enabled:
48
+ yield
49
+ return
50
+
51
+ start_time = self._get_time()
52
+ try:
53
+ yield
54
+ finally:
55
+ end_time = self._get_time()
56
+ duration = end_time - start_time
57
+ self.timings[operation_name] = duration
58
+
59
+ def track_operation(self, operation_name: str) -> Callable[[Callable[..., T]], Callable[..., T]]:
60
+ """Decorator for tracking function execution time."""
61
+
62
+ def decorator(func: Callable[..., T]) -> Callable[..., T]:
63
+ @wraps(func)
64
+ def wrapper(*args: Any, **kwargs: Any) -> T:
65
+ with self.track_time(operation_name):
66
+ return func(*args, **kwargs)
67
+
68
+ return wrapper
69
+
70
+ return decorator
71
+
72
+ def display_timings(self) -> None:
73
+ """Display all tracked timings in a tree format."""
74
+ if self.timings:
75
+ tree = PerformanceFormatter.create_timing_tree(self.timings)
76
+ self.console.print(tree)
77
+
78
+ def clear_timings(self) -> None:
79
+ """Clear all tracked timings."""
80
+ self.timings.clear()
81
+
82
+
83
+ class LiveUpdateHandler:
84
+ """Handles live updates for long-running operations."""
85
+
86
+ def __init__(self, console: Console | None = None):
87
+ self.console = console or Console()
88
+
89
+ @contextmanager
90
+ def progress_tracker(self, description: str = "Progress") -> Generator[Any, None, None]:
91
+ """Context manager for tracking progress with a live display."""
92
+ progress = StructuredFormatter.create_progress_bar(description)
93
+ with Live(progress, console=self.console, refresh_per_second=4) as live:
94
+ task = progress.add_task(description, total=100)
95
+ try:
96
+ yield lambda p: progress.update(task, completed=p)
97
+ finally:
98
+ progress.update(task, completed=100)
99
+
100
+ def update_workflow_status(
101
+ self, workflow_id: str, status: str, details: dict[str, Any], refresh_per_second: int = 1
102
+ ) -> Live:
103
+ """Create a live updating workflow status panel."""
104
+ panel = StructuredFormatter.create_workflow_panel(workflow_id, status, details)
105
+ return Live(panel, console=self.console, refresh_per_second=refresh_per_second)
106
+
107
+ def update_activity_status(
108
+ self, activity_id: str, name: str, status: str, details: dict[str, Any], refresh_per_second: int = 1
109
+ ) -> Live:
110
+ """Create a live updating activity status panel."""
111
+ panel = StructuredFormatter.create_activity_panel(activity_id, name, status, details)
112
+ return Live(panel, console=self.console, refresh_per_second=refresh_per_second)
113
+
114
+
115
+ # Global instances for convenience
116
+ performance_handler = PerformanceHandler()
117
+ live_update_handler = LiveUpdateHandler()
@@ -0,0 +1,107 @@
1
+ from typing import Any
2
+
3
+ from temporalio import workflow
4
+
5
+ from flock.core.logging.formatters import AgentResultFormatter
6
+
7
+ with workflow.unsafe.imports_passed_through():
8
+ from rich.console import Console
9
+ from rich.theme import Theme
10
+
11
+ # Custom theme for different log levels
12
+ THEME = Theme({
13
+ "info": "cyan",
14
+ "warning": "yellow",
15
+ "error": "red",
16
+ "debug": "grey50",
17
+ "success": "green",
18
+ "workflow": "blue",
19
+ "activity": "magenta",
20
+ })
21
+
22
+
23
+ class LogLevel:
24
+ NONE = 0 # No logging except errors
25
+ MINIMAL = 1 # Only agent outputs
26
+ BASIC = 2 # Agent outputs + basic workflow info
27
+ VERBOSE = 3 # Everything including debug info
28
+
29
+
30
+ class FlockLogger:
31
+ """Custom logger for Flock that integrates with Rich and respects Temporal constraints."""
32
+
33
+ def __init__(self, name: str = "flock", console: Console | None = None, level: int = LogLevel.MINIMAL):
34
+ self.name = name
35
+ self.console = console or Console(theme=THEME)
36
+ self._workflow_id: str | None = None
37
+ self._activity_id: str | None = None
38
+ self.level = level
39
+
40
+ def set_level(self, level: int) -> None:
41
+ """Set the logging level."""
42
+ self.level = level
43
+
44
+ def set_context(self, workflow_id: str | None = None, activity_id: str | None = None) -> None:
45
+ """Set the current workflow and activity context."""
46
+ self._workflow_id = workflow_id
47
+ self._activity_id = activity_id
48
+
49
+ def _format_message(self, level: str, message: str, **kwargs: Any) -> str:
50
+ """Format log message with context but without timestamps (for Temporal compatibility)."""
51
+ context_parts = []
52
+ if self._workflow_id:
53
+ context_parts.append(f"[workflow]workflow={self._workflow_id}[/]")
54
+ if self._activity_id:
55
+ context_parts.append(f"[activity]activity={self._activity_id}[/]")
56
+
57
+ # Add any additional context from kwargs
58
+ for key, value in kwargs.items():
59
+ if key not in ("workflow_id", "activity_id"):
60
+ context_parts.append(f"{key}={value}")
61
+
62
+ context_str = " ".join(context_parts)
63
+ return f"[{level}]{level.upper()}[/] {message} {context_str if context_str else ''}"
64
+
65
+ def info(self, message: str, **kwargs: Any) -> None:
66
+ """Log an info message."""
67
+ if self.level >= LogLevel.BASIC:
68
+ self.console.print(self._format_message("info", message, **kwargs))
69
+
70
+ def warning(self, message: str, **kwargs: Any) -> None:
71
+ """Log a warning message."""
72
+ if self.level >= LogLevel.BASIC:
73
+ self.console.print(self._format_message("warning", message, **kwargs))
74
+
75
+ def error(self, message: str, **kwargs: Any) -> None:
76
+ """Log an error message."""
77
+ # Errors are always logged regardless of level
78
+ self.console.print(self._format_message("error", message, **kwargs))
79
+
80
+ def debug(self, message: str, **kwargs: Any) -> None:
81
+ """Log a debug message."""
82
+ if self.level >= LogLevel.VERBOSE:
83
+ self.console.print(self._format_message("debug", message, **kwargs))
84
+
85
+ def success(self, message: str, **kwargs: Any) -> None:
86
+ """Log a success message."""
87
+ if self.level >= LogLevel.BASIC:
88
+ self.console.print(self._format_message("success", message, **kwargs))
89
+
90
+ def workflow_event(self, message: str, **kwargs: Any) -> None:
91
+ """Log a workflow-specific event."""
92
+ if self.level >= LogLevel.BASIC:
93
+ self.console.print(self._format_message("workflow", message, **kwargs))
94
+
95
+ def activity_event(self, message: str, **kwargs: Any) -> None:
96
+ """Log an activity-specific event."""
97
+ if self.level >= LogLevel.BASIC:
98
+ self.console.print(self._format_message("activity", message, **kwargs))
99
+
100
+ def result(self, message: dict, agent_name, **kwargs: Any) -> None:
101
+ """Log a result message."""
102
+ if self.level >= LogLevel.MINIMAL:
103
+ AgentResultFormatter.print_result(message, agent_name, self.console)
104
+
105
+
106
+ # Global logger instance with minimal logging by default
107
+ flock_logger = FlockLogger(level=LogLevel.MINIMAL)