flock-core 0.4.528__py3-none-any.whl → 0.5.0b0__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 (130) hide show
  1. flock/cli/execute_flock.py +1 -1
  2. flock/cli/manage_agents.py +6 -6
  3. flock/components/__init__.py +30 -0
  4. flock/components/evaluation/__init__.py +9 -0
  5. flock/components/evaluation/declarative_evaluation_component.py +222 -0
  6. flock/components/routing/__init__.py +15 -0
  7. flock/{routers/conditional/conditional_router.py → components/routing/conditional_routing_component.py} +61 -53
  8. flock/components/routing/default_routing_component.py +103 -0
  9. flock/components/routing/llm_routing_component.py +206 -0
  10. flock/components/utility/__init__.py +15 -0
  11. flock/{modules/enterprise_memory/enterprise_memory_module.py → components/utility/memory_utility_component.py} +195 -173
  12. flock/{modules/performance/metrics_module.py → components/utility/metrics_utility_component.py} +110 -95
  13. flock/{modules/output/output_module.py → components/utility/output_utility_component.py} +47 -45
  14. flock/core/__init__.py +26 -18
  15. flock/core/agent/__init__.py +16 -0
  16. flock/core/agent/flock_agent_components.py +104 -0
  17. flock/core/agent/flock_agent_execution.py +101 -0
  18. flock/core/agent/flock_agent_integration.py +206 -0
  19. flock/core/agent/flock_agent_lifecycle.py +177 -0
  20. flock/core/agent/flock_agent_serialization.py +381 -0
  21. flock/core/api/endpoints.py +2 -2
  22. flock/core/api/service.py +2 -2
  23. flock/core/component/__init__.py +15 -0
  24. flock/core/{flock_module.py → component/agent_component_base.py} +136 -34
  25. flock/core/component/evaluation_component.py +56 -0
  26. flock/core/component/routing_component.py +74 -0
  27. flock/core/component/utility_component.py +69 -0
  28. flock/core/config/flock_agent_config.py +49 -2
  29. flock/core/evaluation/utils.py +3 -2
  30. flock/core/execution/batch_executor.py +1 -1
  31. flock/core/execution/evaluation_executor.py +2 -2
  32. flock/core/execution/opik_executor.py +1 -1
  33. flock/core/flock.py +147 -493
  34. flock/core/flock_agent.py +195 -1032
  35. flock/core/flock_factory.py +114 -90
  36. flock/core/flock_scheduler.py +1 -1
  37. flock/core/flock_server_manager.py +8 -8
  38. flock/core/logging/logging.py +1 -0
  39. flock/core/mcp/flock_mcp_server.py +53 -48
  40. flock/core/mcp/{flock_mcp_tool_base.py → flock_mcp_tool.py} +2 -2
  41. flock/core/mcp/mcp_client.py +9 -9
  42. flock/core/mcp/mcp_client_manager.py +9 -9
  43. flock/core/mcp/mcp_config.py +24 -24
  44. flock/core/mixin/dspy_integration.py +5 -5
  45. flock/core/orchestration/__init__.py +18 -0
  46. flock/core/orchestration/flock_batch_processor.py +94 -0
  47. flock/core/orchestration/flock_evaluator.py +113 -0
  48. flock/core/orchestration/flock_execution.py +288 -0
  49. flock/core/orchestration/flock_initialization.py +125 -0
  50. flock/core/orchestration/flock_server_manager.py +67 -0
  51. flock/core/orchestration/flock_web_server.py +117 -0
  52. flock/core/registry/__init__.py +45 -0
  53. flock/core/registry/agent_registry.py +69 -0
  54. flock/core/registry/callable_registry.py +139 -0
  55. flock/core/registry/component_discovery.py +142 -0
  56. flock/core/registry/component_registry.py +64 -0
  57. flock/core/registry/config_mapping.py +64 -0
  58. flock/core/registry/decorators.py +137 -0
  59. flock/core/registry/registry_hub.py +205 -0
  60. flock/core/registry/server_registry.py +57 -0
  61. flock/core/registry/type_registry.py +86 -0
  62. flock/core/serialization/flock_serializer.py +36 -32
  63. flock/core/serialization/serialization_utils.py +28 -25
  64. flock/core/util/hydrator.py +1 -1
  65. flock/core/util/input_resolver.py +29 -2
  66. flock/mcp/servers/sse/flock_sse_server.py +10 -10
  67. flock/mcp/servers/stdio/flock_stdio_server.py +10 -10
  68. flock/mcp/servers/streamable_http/flock_streamable_http_server.py +10 -10
  69. flock/mcp/servers/websockets/flock_websocket_server.py +10 -10
  70. flock/platform/docker_tools.py +3 -3
  71. flock/webapp/app/chat.py +1 -1
  72. flock/webapp/app/main.py +9 -5
  73. flock/webapp/app/services/flock_service.py +1 -1
  74. flock/webapp/app/services/sharing_store.py +1 -0
  75. flock/workflow/activities.py +67 -92
  76. flock/workflow/agent_execution_activity.py +6 -6
  77. flock/workflow/flock_workflow.py +1 -1
  78. flock_core-0.5.0b0.dist-info/METADATA +272 -0
  79. {flock_core-0.4.528.dist-info → flock_core-0.5.0b0.dist-info}/RECORD +82 -95
  80. flock/core/flock_evaluator.py +0 -60
  81. flock/core/flock_registry.py +0 -702
  82. flock/core/flock_router.py +0 -83
  83. flock/evaluators/__init__.py +0 -1
  84. flock/evaluators/declarative/__init__.py +0 -1
  85. flock/evaluators/declarative/declarative_evaluator.py +0 -217
  86. flock/evaluators/memory/memory_evaluator.py +0 -90
  87. flock/evaluators/test/test_case_evaluator.py +0 -38
  88. flock/evaluators/zep/zep_evaluator.py +0 -59
  89. flock/modules/__init__.py +0 -1
  90. flock/modules/assertion/__init__.py +0 -1
  91. flock/modules/assertion/assertion_module.py +0 -286
  92. flock/modules/callback/__init__.py +0 -1
  93. flock/modules/callback/callback_module.py +0 -91
  94. flock/modules/enterprise_memory/README.md +0 -99
  95. flock/modules/mem0/__init__.py +0 -1
  96. flock/modules/mem0/mem0_module.py +0 -126
  97. flock/modules/mem0_async/__init__.py +0 -1
  98. flock/modules/mem0_async/async_mem0_module.py +0 -126
  99. flock/modules/memory/__init__.py +0 -1
  100. flock/modules/memory/memory_module.py +0 -429
  101. flock/modules/memory/memory_parser.py +0 -125
  102. flock/modules/memory/memory_storage.py +0 -736
  103. flock/modules/output/__init__.py +0 -1
  104. flock/modules/performance/__init__.py +0 -1
  105. flock/modules/zep/__init__.py +0 -1
  106. flock/modules/zep/zep_module.py +0 -192
  107. flock/routers/__init__.py +0 -1
  108. flock/routers/agent/__init__.py +0 -1
  109. flock/routers/agent/agent_router.py +0 -236
  110. flock/routers/agent/handoff_agent.py +0 -58
  111. flock/routers/default/__init__.py +0 -1
  112. flock/routers/default/default_router.py +0 -80
  113. flock/routers/feedback/feedback_router.py +0 -114
  114. flock/routers/list_generator/list_generator_router.py +0 -166
  115. flock/routers/llm/__init__.py +0 -1
  116. flock/routers/llm/llm_router.py +0 -365
  117. flock/tools/__init__.py +0 -0
  118. flock/tools/azure_tools.py +0 -781
  119. flock/tools/code_tools.py +0 -167
  120. flock/tools/file_tools.py +0 -149
  121. flock/tools/github_tools.py +0 -157
  122. flock/tools/markdown_tools.py +0 -205
  123. flock/tools/system_tools.py +0 -9
  124. flock/tools/text_tools.py +0 -810
  125. flock/tools/web_tools.py +0 -90
  126. flock/tools/zendesk_tools.py +0 -147
  127. flock_core-0.4.528.dist-info/METADATA +0 -675
  128. {flock_core-0.4.528.dist-info → flock_core-0.5.0b0.dist-info}/WHEEL +0 -0
  129. {flock_core-0.4.528.dist-info → flock_core-0.5.0b0.dist-info}/entry_points.txt +0 -0
  130. {flock_core-0.4.528.dist-info → flock_core-0.5.0b0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,104 @@
1
+ """Component management functionality for FlockAgent."""
2
+
3
+ from typing import TYPE_CHECKING, Any
4
+
5
+ from flock.core.component.evaluation_component import EvaluationComponent
6
+ from flock.core.component.routing_component import RoutingComponent
7
+ from flock.core.component.utility_component import UtilityComponent
8
+ from flock.core.logging.logging import get_logger
9
+
10
+ if TYPE_CHECKING:
11
+ from flock.core.component.agent_component_base import AgentComponent
12
+ from flock.core.flock_agent import FlockAgent
13
+
14
+ logger = get_logger("agent.components")
15
+
16
+
17
+ class FlockAgentComponents:
18
+ """Helper class for managing unified components on FlockAgent."""
19
+
20
+ def __init__(self, agent: "FlockAgent"):
21
+ self.agent = agent
22
+
23
+ def add_component(self, component: "AgentComponent") -> None:
24
+ """Add a unified component to this agent."""
25
+ if not component.name:
26
+ logger.error("Component must have a name to be added.")
27
+ return
28
+
29
+ # Check if component with same name already exists
30
+ existing = self.get_component(component.name)
31
+ if existing:
32
+ logger.warning(f"Overwriting existing component: {component.name}")
33
+ self.agent.components.remove(existing)
34
+
35
+ self.agent.components.append(component)
36
+ logger.debug(f"Added component '{component.name}' to agent '{self.agent.name}'")
37
+
38
+ def remove_component(self, component_name: str) -> None:
39
+ """Remove a component from this agent."""
40
+ component = self.get_component(component_name)
41
+ if component:
42
+ self.agent.components.remove(component)
43
+ logger.debug(f"Removed component '{component_name}' from agent '{self.agent.name}'")
44
+ else:
45
+ logger.warning(f"Component '{component_name}' not found on agent '{self.agent.name}'")
46
+
47
+ def get_component(self, component_name: str) -> "AgentComponent | None":
48
+ """Get a component by name."""
49
+ for component in self.agent.components:
50
+ if component.name == component_name:
51
+ return component
52
+ return None
53
+
54
+ def get_enabled_components(self) -> list["AgentComponent"]:
55
+ """Get a list of currently enabled components attached to this agent."""
56
+ return [c for c in self.agent.components if c.config.enabled]
57
+
58
+ def get_components_by_type(self, component_type: type) -> list["AgentComponent"]:
59
+ """Get all components of a specific type."""
60
+ return [c for c in self.agent.components if isinstance(c, component_type)]
61
+
62
+ def get_evaluation_components(self) -> list[EvaluationComponent]:
63
+ """Get all evaluation components."""
64
+ return self.get_components_by_type(EvaluationComponent)
65
+
66
+ def get_routing_components(self) -> list[RoutingComponent]:
67
+ """Get all routing components."""
68
+ return self.get_components_by_type(RoutingComponent)
69
+
70
+ def get_utility_components(self) -> list[UtilityComponent]:
71
+ """Get all utility components."""
72
+ return self.get_components_by_type(UtilityComponent)
73
+
74
+ def get_primary_evaluator(self) -> EvaluationComponent | None:
75
+ """Get the primary evaluation component (first one found)."""
76
+ evaluators = self.get_evaluation_components()
77
+ return evaluators[0] if evaluators else None
78
+
79
+ def get_primary_router(self) -> RoutingComponent | None:
80
+ """Get the primary routing component (first one found)."""
81
+ routers = self.get_routing_components()
82
+ return routers[0] if routers else None
83
+
84
+ # Legacy compatibility methods (delegate to new unified approach)
85
+ def add_module(self, module: Any) -> None:
86
+ """DEPRECATED: Use add_component() instead."""
87
+ logger.warning("add_module is deprecated - use add_component() instead")
88
+ if hasattr(module, 'name'):
89
+ self.add_component(module)
90
+
91
+ def get_module(self, module_name: str) -> Any | None:
92
+ """DEPRECATED: Use get_component() instead."""
93
+ logger.warning("get_module is deprecated - use get_component() instead")
94
+ return self.get_component(module_name)
95
+
96
+ def get_evaluator(self) -> Any | None:
97
+ """DEPRECATED: Use get_primary_evaluator() instead."""
98
+ logger.warning("get_evaluator is deprecated - use get_primary_evaluator() instead")
99
+ return self.get_primary_evaluator()
100
+
101
+ def get_router(self) -> Any | None:
102
+ """DEPRECATED: Use get_primary_router() instead."""
103
+ logger.warning("get_router is deprecated - use get_primary_router() instead")
104
+ return self.get_primary_router()
@@ -0,0 +1,101 @@
1
+ # src/flock/core/agent/flock_agent_execution.py
2
+ """Execution management functionality for FlockAgent."""
3
+
4
+ import asyncio
5
+ from typing import TYPE_CHECKING, Any
6
+
7
+ from opentelemetry import trace
8
+ from flock.core.logging.logging import get_logger
9
+
10
+ if TYPE_CHECKING:
11
+ from flock.core.flock_agent import FlockAgent
12
+
13
+ logger = get_logger("agent.execution")
14
+ tracer = trace.get_tracer(__name__)
15
+
16
+
17
+ class FlockAgentExecution:
18
+ """Handles execution management for FlockAgent including run, run_async, and run_temporal."""
19
+
20
+ def __init__(self, agent: "FlockAgent"):
21
+ self.agent = agent
22
+
23
+ def run(self, inputs: dict[str, Any]) -> dict[str, Any]:
24
+ """Synchronous wrapper for run_async."""
25
+ try:
26
+ loop = asyncio.get_running_loop()
27
+ except (
28
+ RuntimeError
29
+ ): # 'RuntimeError: There is no current event loop...'
30
+ loop = asyncio.new_event_loop()
31
+ asyncio.set_event_loop(loop)
32
+ return loop.run_until_complete(self.run_async(inputs))
33
+
34
+ async def run_async(self, inputs: dict[str, Any]) -> dict[str, Any]:
35
+ """Asynchronous execution logic with lifecycle hooks."""
36
+ with tracer.start_as_current_span("agent.run") as span:
37
+ span.set_attribute("agent.name", self.agent.name)
38
+ span.set_attribute("inputs", str(inputs))
39
+ try:
40
+ # Initialize lifecycle system if not already present
41
+ if not hasattr(self.agent, '_lifecycle'):
42
+ from flock.core.agent.flock_agent_lifecycle import FlockAgentLifecycle
43
+ self.agent._lifecycle = FlockAgentLifecycle(self.agent)
44
+
45
+ await self.agent._lifecycle.initialize(inputs)
46
+ result = await self.agent._lifecycle.evaluate(inputs)
47
+ await self.agent._lifecycle.terminate(inputs, result)
48
+ span.set_attribute("result", str(result))
49
+ logger.info("Agent run completed", agent=self.agent.name)
50
+ return result
51
+ except Exception as run_error:
52
+ logger.error(
53
+ "Error running agent", agent=self.agent.name, error=str(run_error)
54
+ )
55
+ if "evaluate" not in str(
56
+ run_error
57
+ ): # Simple check, might need refinement
58
+ await self.agent._lifecycle.on_error(run_error, inputs)
59
+ logger.error(
60
+ f"Agent '{self.agent.name}' run failed: {run_error}",
61
+ exc_info=True,
62
+ )
63
+ span.record_exception(run_error)
64
+ raise # Re-raise after handling
65
+
66
+ async def run_temporal(self, inputs: dict[str, Any]) -> dict[str, Any]:
67
+ """Execute agent using Temporal workflow orchestration."""
68
+ with tracer.start_as_current_span("agent.run_temporal") as span:
69
+ span.set_attribute("agent.name", self.agent.name)
70
+ span.set_attribute("inputs", str(inputs))
71
+ try:
72
+ from temporalio.client import Client
73
+
74
+ from flock.workflow.agent_activities import (
75
+ run_flock_agent_activity,
76
+ )
77
+ from flock.workflow.temporal_setup import run_activity
78
+
79
+ client = await Client.connect(
80
+ "localhost:7233", namespace="default"
81
+ )
82
+ agent_data = self.agent._serialization.to_dict()
83
+ inputs_data = inputs
84
+
85
+ result = await run_activity(
86
+ client,
87
+ self.agent.name,
88
+ run_flock_agent_activity,
89
+ {"agent_data": agent_data, "inputs": inputs_data},
90
+ )
91
+ span.set_attribute("result", str(result))
92
+ logger.info("Temporal run successful", agent=self.agent.name)
93
+ return result
94
+ except Exception as temporal_error:
95
+ logger.error(
96
+ "Error in Temporal workflow",
97
+ agent=self.agent.name,
98
+ error=str(temporal_error),
99
+ )
100
+ span.record_exception(temporal_error)
101
+ raise
@@ -0,0 +1,206 @@
1
+ # src/flock/core/agent/flock_agent_integration.py
2
+ """Tool and server integration functionality for FlockAgent."""
3
+
4
+ from collections.abc import Callable
5
+ from functools import wraps
6
+ from inspect import Parameter, signature
7
+ from typing import TYPE_CHECKING, Any, TypeVar, cast
8
+
9
+ from flock.core.context.context import FlockContext
10
+ from flock.core.logging.logging import get_logger
11
+ from flock.core.mcp.flock_mcp_server import FlockMCPServer
12
+
13
+ if TYPE_CHECKING:
14
+ from flock.core.flock_agent import FlockAgent
15
+
16
+ logger = get_logger("agent.integration")
17
+
18
+ R = TypeVar("R", bound=str)
19
+
20
+
21
+ def adapt(prop_name: str, fn: Callable[..., R]) -> Callable[[FlockContext], R]:
22
+ """Coerce *fn* into the canonical ``(ctx: FlockContext) -> str`` form.
23
+
24
+ Acceptable signatures
25
+ ---------------------
26
+ 1. ``() -> str`` (no parameters)
27
+ 2. ``(ctx: FlockContext) -> str`` (exactly one positional parameter)
28
+
29
+ Anything else raises ``TypeError``.
30
+
31
+ The wrapper also enforces at runtime that the result is ``str``.
32
+ """
33
+ if not callable(fn):
34
+ raise TypeError(f"{prop_name} must be a callable, got {type(fn).__name__}")
35
+
36
+ sig = signature(fn)
37
+ params = list(sig.parameters.values())
38
+
39
+ def _validate_result(res: object) -> R:
40
+ if not isinstance(res, str):
41
+ raise TypeError(
42
+ f"{prop_name} callable must return str, got {type(res).__name__}"
43
+ )
44
+ return cast(R, res)
45
+
46
+ # ── Case 1: () -> str ────────────────────────────────────────────────────
47
+ if len(params) == 0:
48
+
49
+ @wraps(fn)
50
+ def _wrapped(ctx: FlockContext) -> R:
51
+ return _validate_result(fn())
52
+
53
+ return _wrapped
54
+
55
+ # ── Case 2: (ctx) -> str ────────────────────────────────────────────────
56
+ if len(params) == 1:
57
+ p: Parameter = params[0]
58
+ valid_kind = p.kind in (
59
+ Parameter.POSITIONAL_ONLY,
60
+ Parameter.POSITIONAL_OR_KEYWORD,
61
+ )
62
+ valid_annotation = p.annotation in (Parameter.empty, FlockContext)
63
+ has_no_default = p.default is Parameter.empty
64
+
65
+ if valid_kind and valid_annotation and has_no_default:
66
+
67
+ @wraps(fn)
68
+ def _wrapped(ctx: FlockContext) -> R:
69
+ return _validate_result(fn(ctx)) # type: ignore[arg-type]
70
+
71
+ return _wrapped
72
+
73
+ # ── Anything else: reject ───────────────────────────────────────────────
74
+ raise TypeError(
75
+ f"{prop_name} callable must be () -> str or (ctx: FlockContext) -> str; "
76
+ f"got signature {sig}"
77
+ )
78
+
79
+ class FlockAgentIntegration:
80
+ """Handles tool and server integration for FlockAgent including MCP servers and callable tools."""
81
+
82
+ def __init__(self, agent: "FlockAgent"):
83
+ self.agent = agent
84
+
85
+ def _resolve(self, raw: str | Callable[..., str], name: str, ctx: FlockContext | None) -> str | None:
86
+ if callable(raw):
87
+ raw = adapt(name, raw)(ctx or FlockContext())
88
+ return raw
89
+
90
+ def resolve_description(self, context: FlockContext | None = None) -> str | None:
91
+ """Resolve the agent's description, handling callable descriptions."""
92
+ return self._resolve(self.agent.description_spec, "description", context)
93
+
94
+ def resolve_input(self, context: FlockContext | None = None) -> str | None:
95
+ """Resolve the agent's input, handling callable inputs."""
96
+ return self._resolve(self.agent.input_spec, "input", context)
97
+
98
+ def resolve_output(self, context: FlockContext | None = None) -> str | None:
99
+ """Resolve the agent's output, handling callable outputs."""
100
+ return self._resolve(self.agent.output_spec, "output", context)
101
+
102
+ def resolve_next_agent(self, context: FlockContext | None = None) -> str | None:
103
+ """Resolve the next agent, handling callable next agents."""
104
+ return self._resolve(self.agent.next_agent_spec, "next_agent", context)
105
+
106
+ async def get_mcp_tools(self) -> list[Any]:
107
+ """Get tools from registered MCP servers."""
108
+ mcp_tools = []
109
+ if self.agent.servers:
110
+ from flock.core.registry import get_registry
111
+
112
+ registry = get_registry() # Get the registry
113
+ for server in self.agent.servers:
114
+ registered_server: FlockMCPServer | None = None
115
+ server_tools = []
116
+ if isinstance(server, FlockMCPServer):
117
+ # check if registered
118
+ server_name = server.config.name
119
+ registered_server = registry.get_server(
120
+ server_name
121
+ )
122
+ else:
123
+ # servers must be registered.
124
+ registered_server = registry.get_server(
125
+ name=server
126
+ )
127
+ if registered_server:
128
+ server_tools = await registered_server.get_tools(
129
+ agent_id=self.agent.agent_id,
130
+ run_id=self.agent.context.run_id,
131
+ )
132
+ else:
133
+ logger.warning(
134
+ f"No Server with name '{server.config.name if isinstance(server, FlockMCPServer) else server}' registered! Skipping."
135
+ )
136
+ mcp_tools = mcp_tools + server_tools
137
+ return mcp_tools
138
+
139
+ async def execute_with_middleware(
140
+ self,
141
+ current_inputs: dict[str, Any],
142
+ registered_tools: list[Any],
143
+ mcp_tools: list[Any]
144
+ ) -> dict[str, Any]:
145
+ """Execute evaluator with optional DI middleware pipeline."""
146
+ container = None
147
+ if self.agent.context is not None:
148
+ container = self.agent.context.get_variable("di.container")
149
+
150
+ # If a MiddlewarePipeline is registered in DI, wrap the evaluator
151
+ result: dict[str, Any] | None = None
152
+
153
+ if container is not None:
154
+ try:
155
+ from wd.di.middleware import (
156
+ MiddlewarePipeline,
157
+ )
158
+
159
+ pipeline: MiddlewarePipeline | None = None
160
+ try:
161
+ pipeline = container.get_service(MiddlewarePipeline)
162
+ except Exception:
163
+ pipeline = None
164
+
165
+ if pipeline is not None:
166
+ # Build execution chain where the evaluator is the terminal handler
167
+
168
+ async def _final_handler():
169
+ return await self.agent.evaluator.evaluate_core(
170
+ self.agent, current_inputs, self.agent.context, registered_tools, mcp_tools
171
+ )
172
+
173
+ idx = 0
174
+
175
+ async def _invoke_next():
176
+ nonlocal idx
177
+
178
+ if idx < len(pipeline._middleware):
179
+ mw = pipeline._middleware[idx]
180
+ idx += 1
181
+ return await mw(self.agent.context, _invoke_next) # type: ignore[arg-type]
182
+ return await _final_handler()
183
+
184
+ # Execute pipeline
185
+ result = await _invoke_next()
186
+ else:
187
+ # No pipeline registered, direct evaluation
188
+ result = await self.agent.evaluator.evaluate_core(
189
+ self.agent, current_inputs, self.agent.context, registered_tools, mcp_tools
190
+ )
191
+ except ImportError:
192
+ # wd.di not installed – fall back
193
+ result = await self.agent.evaluator.evaluate_core(
194
+ self.agent, current_inputs, self.agent.context, registered_tools, mcp_tools
195
+ )
196
+ else:
197
+ # No DI container – standard execution
198
+ result = await self.agent.evaluator.evaluate_core(
199
+ self.agent,
200
+ current_inputs,
201
+ self.agent.context,
202
+ registered_tools,
203
+ mcp_tools,
204
+ )
205
+
206
+ return result
@@ -0,0 +1,177 @@
1
+ # src/flock/core/agent/flock_agent_lifecycle.py
2
+ """Lifecycle management functionality for FlockAgent."""
3
+
4
+ from typing import TYPE_CHECKING, Any
5
+
6
+ from opentelemetry import trace
7
+
8
+ from flock.core.logging.logging import get_logger
9
+
10
+ if TYPE_CHECKING:
11
+ from flock.core.flock_agent import FlockAgent
12
+
13
+ logger = get_logger("agent.lifecycle")
14
+ tracer = trace.get_tracer(__name__)
15
+
16
+
17
+ class FlockAgentLifecycle:
18
+ """Handles lifecycle management for FlockAgent including initialization, evaluation, and termination."""
19
+
20
+ def __init__(self, agent: "FlockAgent"):
21
+ self.agent = agent
22
+
23
+ async def initialize(self, inputs: dict[str, Any]) -> None:
24
+ """Initialize agent and run module initializers."""
25
+ logger.debug(f"Initializing agent '{self.agent.name}'")
26
+ with tracer.start_as_current_span("agent.initialize") as span:
27
+ span.set_attribute("agent.name", self.agent.name)
28
+ span.set_attribute("inputs", str(inputs))
29
+ logger.info(
30
+ f"agent.initialize",
31
+ agent=self.agent.name,
32
+ )
33
+ try:
34
+ for component in self.agent.get_enabled_components():
35
+ await component.on_initialize(self.agent, inputs, self.agent.context)
36
+ except Exception as component_error:
37
+ logger.error(
38
+ "Error during initialize",
39
+ agent=self.agent.name,
40
+ error=str(component_error),
41
+ )
42
+ span.record_exception(component_error)
43
+
44
+ async def terminate(
45
+ self, inputs: dict[str, Any], result: dict[str, Any]
46
+ ) -> None:
47
+ """Terminate agent and run module terminators."""
48
+ logger.debug(f"Terminating agent '{self.agent.name}'")
49
+ with tracer.start_as_current_span("agent.terminate") as span:
50
+ span.set_attribute("agent.name", self.agent.name)
51
+ span.set_attribute("inputs", str(inputs))
52
+ span.set_attribute("result", str(result))
53
+ logger.info(
54
+ f"agent.terminate",
55
+ agent=self.agent.name,
56
+ )
57
+ try:
58
+ current_result = result
59
+ for component in self.agent.get_enabled_components():
60
+ tmp_result = await component.on_terminate(
61
+ self.agent, inputs, self.agent.context, current_result
62
+ )
63
+ # If the component returns a result, use it
64
+ if tmp_result:
65
+ current_result = tmp_result
66
+
67
+ if self.agent.config.write_to_file:
68
+ self.agent._serialization._save_output(self.agent.name, current_result)
69
+
70
+ if self.agent.config.wait_for_input:
71
+ # simple input prompt
72
+ input("Press Enter to continue...")
73
+
74
+ except Exception as component_error:
75
+ logger.error(
76
+ "Error during terminate",
77
+ agent=self.agent.name,
78
+ error=str(component_error),
79
+ )
80
+ span.record_exception(component_error)
81
+
82
+ async def on_error(self, error: Exception, inputs: dict[str, Any]) -> None:
83
+ """Handle errors and run component error handlers."""
84
+ logger.error(f"Error occurred in agent '{self.agent.name}': {error}")
85
+ with tracer.start_as_current_span("agent.on_error") as span:
86
+ span.set_attribute("agent.name", self.agent.name)
87
+ span.set_attribute("inputs", str(inputs))
88
+ try:
89
+ for component in self.agent.get_enabled_components():
90
+ await component.on_error(self.agent, inputs, self.agent.context, error)
91
+ except Exception as component_error:
92
+ logger.error(
93
+ "Error during on_error",
94
+ agent=self.agent.name,
95
+ error=str(component_error),
96
+ )
97
+ span.record_exception(component_error)
98
+
99
+ async def evaluate(self, inputs: dict[str, Any]) -> dict[str, Any]:
100
+ """Core evaluation logic, calling the assigned evaluator and components."""
101
+ if not self.agent.evaluator:
102
+ raise RuntimeError(
103
+ f"Agent '{self.agent.name}' has no evaluator assigned."
104
+ )
105
+ with tracer.start_as_current_span("agent.evaluate") as span:
106
+ span.set_attribute("agent.name", self.agent.name)
107
+ span.set_attribute("inputs", str(inputs))
108
+ logger.info(
109
+ f"agent.evaluate",
110
+ agent=self.agent.name,
111
+ )
112
+
113
+ logger.debug(f"Evaluating agent '{self.agent.name}'")
114
+ current_inputs = inputs
115
+
116
+ # Pre-evaluate hooks
117
+ for component in self.agent.get_enabled_components():
118
+ current_inputs = await component.on_pre_evaluate(
119
+ self.agent, current_inputs, self.agent.context
120
+ )
121
+
122
+ # Actual evaluation
123
+ try:
124
+ # Get tools and MCP tools through integration handler
125
+ registered_tools = []
126
+ if self.agent.tools:
127
+ registered_tools = self.agent.tools
128
+
129
+ # Retrieve available mcp_tools if the evaluator needs them
130
+ mcp_tools = []
131
+ if self.agent.servers:
132
+ mcp_tools = await self.agent._integration.get_mcp_tools()
133
+
134
+ # --------------------------------------------------
135
+ # Use evaluator component's evaluate_core method
136
+ # --------------------------------------------------
137
+ result = await self.agent.evaluator.evaluate_core(
138
+ self.agent, current_inputs, self.agent.context, registered_tools, mcp_tools
139
+ )
140
+
141
+ except Exception as eval_error:
142
+ logger.error(
143
+ "Error during evaluate",
144
+ agent=self.agent.name,
145
+ error=str(eval_error),
146
+ )
147
+ span.record_exception(eval_error)
148
+ await self.on_error(
149
+ eval_error, current_inputs
150
+ ) # Call error hook
151
+ raise # Re-raise the exception
152
+
153
+ # Post-evaluate hooks
154
+ current_result = result
155
+ for component in self.agent.get_enabled_components():
156
+ tmp_result = await component.on_post_evaluate(
157
+ self.agent,
158
+ current_inputs,
159
+ self.agent.context,
160
+ current_result,
161
+ )
162
+ # If the component returns a result, use it
163
+ if tmp_result:
164
+ current_result = tmp_result
165
+
166
+ # Handle routing logic
167
+ router = self.agent.router
168
+ if router:
169
+ try:
170
+ self.agent.next_agent = await router.determine_next_step(
171
+ self.agent, current_result, self.agent.context
172
+ )
173
+ except Exception as e:
174
+ logger.error(f"Error in routing: {e}")
175
+
176
+ logger.debug(f"Evaluation completed for agent '{self.agent.name}'")
177
+ return current_result