flock-core 0.5.11__py3-none-any.whl → 0.5.21__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 +1 -1
- flock/agent/__init__.py +30 -0
- flock/agent/builder_helpers.py +192 -0
- flock/agent/builder_validator.py +169 -0
- flock/agent/component_lifecycle.py +325 -0
- flock/agent/context_resolver.py +141 -0
- flock/agent/mcp_integration.py +212 -0
- flock/agent/output_processor.py +304 -0
- flock/api/__init__.py +20 -0
- flock/{api_models.py → api/models.py} +0 -2
- flock/{service.py → api/service.py} +3 -3
- flock/cli.py +2 -2
- flock/components/__init__.py +41 -0
- flock/components/agent/__init__.py +22 -0
- flock/{components.py → components/agent/base.py} +4 -3
- flock/{utility/output_utility_component.py → components/agent/output_utility.py} +12 -7
- flock/components/orchestrator/__init__.py +22 -0
- flock/{orchestrator_component.py → components/orchestrator/base.py} +5 -293
- flock/components/orchestrator/circuit_breaker.py +95 -0
- flock/components/orchestrator/collection.py +143 -0
- flock/components/orchestrator/deduplication.py +78 -0
- flock/core/__init__.py +30 -0
- flock/core/agent.py +953 -0
- flock/{artifacts.py → core/artifacts.py} +1 -1
- flock/{context_provider.py → core/context_provider.py} +3 -3
- flock/core/orchestrator.py +1102 -0
- flock/{store.py → core/store.py} +99 -454
- flock/{subscription.py → core/subscription.py} +1 -1
- flock/dashboard/collector.py +5 -5
- flock/dashboard/events.py +1 -1
- flock/dashboard/graph_builder.py +7 -7
- flock/dashboard/routes/__init__.py +21 -0
- flock/dashboard/routes/control.py +327 -0
- flock/dashboard/routes/helpers.py +340 -0
- flock/dashboard/routes/themes.py +76 -0
- flock/dashboard/routes/traces.py +521 -0
- flock/dashboard/routes/websocket.py +108 -0
- flock/dashboard/service.py +43 -1316
- flock/engines/dspy/__init__.py +20 -0
- flock/engines/dspy/artifact_materializer.py +216 -0
- flock/engines/dspy/signature_builder.py +474 -0
- flock/engines/dspy/streaming_executor.py +812 -0
- flock/engines/dspy_engine.py +45 -1330
- flock/engines/examples/simple_batch_engine.py +2 -2
- flock/engines/streaming/__init__.py +3 -0
- flock/engines/streaming/sinks.py +489 -0
- flock/examples.py +7 -7
- flock/logging/logging.py +1 -16
- flock/models/__init__.py +10 -0
- flock/orchestrator/__init__.py +45 -0
- flock/{artifact_collector.py → orchestrator/artifact_collector.py} +3 -3
- flock/orchestrator/artifact_manager.py +168 -0
- flock/{batch_accumulator.py → orchestrator/batch_accumulator.py} +2 -2
- flock/orchestrator/component_runner.py +389 -0
- flock/orchestrator/context_builder.py +167 -0
- flock/{correlation_engine.py → orchestrator/correlation_engine.py} +2 -2
- flock/orchestrator/event_emitter.py +167 -0
- flock/orchestrator/initialization.py +184 -0
- flock/orchestrator/lifecycle_manager.py +226 -0
- flock/orchestrator/mcp_manager.py +202 -0
- flock/orchestrator/scheduler.py +189 -0
- flock/orchestrator/server_manager.py +234 -0
- flock/orchestrator/tracing.py +147 -0
- flock/storage/__init__.py +10 -0
- flock/storage/artifact_aggregator.py +158 -0
- flock/storage/in_memory/__init__.py +6 -0
- flock/storage/in_memory/artifact_filter.py +114 -0
- flock/storage/in_memory/history_aggregator.py +115 -0
- flock/storage/sqlite/__init__.py +10 -0
- flock/storage/sqlite/agent_history_queries.py +154 -0
- flock/storage/sqlite/consumption_loader.py +100 -0
- flock/storage/sqlite/query_builder.py +112 -0
- flock/storage/sqlite/query_params_builder.py +91 -0
- flock/storage/sqlite/schema_manager.py +168 -0
- flock/storage/sqlite/summary_queries.py +194 -0
- flock/utils/__init__.py +14 -0
- flock/utils/async_utils.py +67 -0
- flock/{runtime.py → utils/runtime.py} +3 -3
- flock/utils/time_utils.py +53 -0
- flock/utils/type_resolution.py +38 -0
- flock/{utilities.py → utils/utilities.py} +2 -2
- flock/utils/validation.py +57 -0
- flock/utils/visibility.py +79 -0
- flock/utils/visibility_utils.py +134 -0
- {flock_core-0.5.11.dist-info → flock_core-0.5.21.dist-info}/METADATA +19 -5
- {flock_core-0.5.11.dist-info → flock_core-0.5.21.dist-info}/RECORD +92 -34
- flock/agent.py +0 -1578
- flock/orchestrator.py +0 -1983
- /flock/{visibility.py → core/visibility.py} +0 -0
- /flock/{system_artifacts.py → models/system_artifacts.py} +0 -0
- /flock/{helper → utils}/cli_helper.py +0 -0
- {flock_core-0.5.11.dist-info → flock_core-0.5.21.dist-info}/WHEEL +0 -0
- {flock_core-0.5.11.dist-info → flock_core-0.5.21.dist-info}/entry_points.txt +0 -0
- {flock_core-0.5.11.dist-info → flock_core-0.5.21.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"""Execution context building with security boundary enforcement.
|
|
2
|
+
|
|
3
|
+
Phase 5A: Extracted from orchestrator.py to eliminate code duplication.
|
|
4
|
+
|
|
5
|
+
This module implements the security boundary pattern for context creation,
|
|
6
|
+
consolidating duplicated code from direct_invoke(), invoke(), and _run_agent_task().
|
|
7
|
+
|
|
8
|
+
SECURITY CRITICAL: This module enforces the Phase 8 context provider pattern
|
|
9
|
+
that prevents identity spoofing and READ capability bypass.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import logging
|
|
15
|
+
from typing import TYPE_CHECKING, Any
|
|
16
|
+
from uuid import UUID, uuid4
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
from flock.core import Agent
|
|
21
|
+
from flock.core.artifacts import Artifact
|
|
22
|
+
from flock.core.store import BlackboardStore
|
|
23
|
+
from flock.utils.runtime import Context
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class ContextBuilder:
|
|
27
|
+
"""Builds execution contexts with security boundary enforcement.
|
|
28
|
+
|
|
29
|
+
This module implements the security boundary pattern:
|
|
30
|
+
1. Resolve provider (agent > global > default)
|
|
31
|
+
2. Wrap with BoundContextProvider (prevent identity spoofing)
|
|
32
|
+
3. Evaluate context artifacts (orchestrator controls READ)
|
|
33
|
+
4. Create Context with data-only (no capabilities)
|
|
34
|
+
|
|
35
|
+
Phase 5A: Extracted to eliminate duplication across 3 methods and
|
|
36
|
+
reduce _run_agent_task complexity from C(11) to B or A.
|
|
37
|
+
|
|
38
|
+
SECURITY NOTICE: Changes to this module affect the security boundary
|
|
39
|
+
between agents and the blackboard. Review carefully.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
def __init__(
|
|
43
|
+
self,
|
|
44
|
+
*,
|
|
45
|
+
store: BlackboardStore,
|
|
46
|
+
default_context_provider: Any | None = None,
|
|
47
|
+
):
|
|
48
|
+
"""Initialize ContextBuilder with blackboard store and default provider.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
store: BlackboardStore instance for context provider queries
|
|
52
|
+
default_context_provider: Global context provider (Phase 3 security fix).
|
|
53
|
+
If None, agents use DefaultContextProvider. Can be overridden per-agent.
|
|
54
|
+
"""
|
|
55
|
+
self._store = store
|
|
56
|
+
self._default_context_provider = default_context_provider
|
|
57
|
+
self._logger = logging.getLogger(__name__)
|
|
58
|
+
|
|
59
|
+
async def build_execution_context(
|
|
60
|
+
self,
|
|
61
|
+
*,
|
|
62
|
+
agent: Agent,
|
|
63
|
+
artifacts: list[Artifact],
|
|
64
|
+
correlation_id: UUID | None = None,
|
|
65
|
+
is_batch: bool = False,
|
|
66
|
+
) -> Context:
|
|
67
|
+
"""Build Context with pre-filtered artifacts (Phase 8 security fix).
|
|
68
|
+
|
|
69
|
+
Implements the security boundary pattern:
|
|
70
|
+
1. Resolve provider (agent > global > default)
|
|
71
|
+
2. Wrap with BoundContextProvider (prevent identity spoofing)
|
|
72
|
+
3. Evaluate context artifacts (orchestrator controls READ)
|
|
73
|
+
4. Create Context with data-only (no capabilities)
|
|
74
|
+
|
|
75
|
+
SECURITY NOTICE: This method enforces the security boundary between
|
|
76
|
+
agents and the blackboard. Agents receive pre-filtered context data
|
|
77
|
+
and cannot bypass visibility controls.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
agent: Agent instance being executed
|
|
81
|
+
artifacts: Input artifacts that triggered execution
|
|
82
|
+
correlation_id: Optional correlation ID for grouping related work
|
|
83
|
+
is_batch: Whether this is a batch execution (affects context metadata)
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
Context with pre-filtered artifacts and agent identity
|
|
87
|
+
|
|
88
|
+
Examples:
|
|
89
|
+
>>> # Direct invocation
|
|
90
|
+
>>> context = await builder.build_execution_context(
|
|
91
|
+
... agent=pizza_agent,
|
|
92
|
+
... artifacts=[input_artifact],
|
|
93
|
+
... correlation_id=uuid4(),
|
|
94
|
+
... is_batch=False,
|
|
95
|
+
... )
|
|
96
|
+
>>> outputs = await agent.execute(context, artifacts)
|
|
97
|
+
|
|
98
|
+
>>> # Batch execution
|
|
99
|
+
>>> context = await builder.build_execution_context(
|
|
100
|
+
... agent=batch_agent,
|
|
101
|
+
... artifacts=batch_artifacts,
|
|
102
|
+
... correlation_id=batch_correlation,
|
|
103
|
+
... is_batch=True,
|
|
104
|
+
... )
|
|
105
|
+
"""
|
|
106
|
+
# Phase 8: Evaluate context BEFORE creating Context (security fix)
|
|
107
|
+
# Provider resolution: per-agent > global > DefaultContextProvider
|
|
108
|
+
from flock.core.context_provider import (
|
|
109
|
+
BoundContextProvider,
|
|
110
|
+
ContextRequest,
|
|
111
|
+
DefaultContextProvider,
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
# Resolve correlation ID
|
|
115
|
+
resolved_correlation_id = correlation_id or (
|
|
116
|
+
artifacts[0].correlation_id
|
|
117
|
+
if artifacts and artifacts[0].correlation_id
|
|
118
|
+
else uuid4()
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
# Step 1: Resolve provider (agent > global > default)
|
|
122
|
+
inner_provider = (
|
|
123
|
+
getattr(agent, "context_provider", None)
|
|
124
|
+
or self._default_context_provider
|
|
125
|
+
or DefaultContextProvider()
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
# Step 2: SECURITY FIX - Wrap provider with BoundContextProvider
|
|
129
|
+
# This prevents identity spoofing by binding the provider to the agent's identity
|
|
130
|
+
provider = BoundContextProvider(inner_provider, agent.identity)
|
|
131
|
+
|
|
132
|
+
# Step 3: Evaluate context using provider (orchestrator controls READ capability)
|
|
133
|
+
# Engines will receive pre-filtered artifacts via ctx.artifacts
|
|
134
|
+
request = ContextRequest(
|
|
135
|
+
agent=agent,
|
|
136
|
+
correlation_id=resolved_correlation_id,
|
|
137
|
+
store=self._store,
|
|
138
|
+
agent_identity=agent.identity,
|
|
139
|
+
exclude_ids={a.id for a in artifacts}, # Exclude input artifacts
|
|
140
|
+
)
|
|
141
|
+
context_artifacts = await provider(request)
|
|
142
|
+
|
|
143
|
+
# Step 4: Create Context with pre-filtered data (no capabilities!)
|
|
144
|
+
# SECURITY: Context is now just data - engines can't query anything
|
|
145
|
+
from flock.utils.runtime import Context
|
|
146
|
+
|
|
147
|
+
ctx = Context(
|
|
148
|
+
artifacts=context_artifacts, # Pre-filtered conversation context
|
|
149
|
+
agent_identity=agent.identity,
|
|
150
|
+
task_id=str(uuid4()),
|
|
151
|
+
correlation_id=resolved_correlation_id,
|
|
152
|
+
is_batch=is_batch,
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
# Log context creation for debugging
|
|
156
|
+
self._logger.debug(
|
|
157
|
+
f"Context built: agent={agent.name}, "
|
|
158
|
+
f"correlation_id={resolved_correlation_id}, "
|
|
159
|
+
f"is_batch={is_batch}, "
|
|
160
|
+
f"context_artifacts={len(context_artifacts)}, "
|
|
161
|
+
f"input_artifacts={len(artifacts)}"
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
return ctx
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
__all__ = ["ContextBuilder"]
|
|
@@ -16,8 +16,8 @@ from typing import TYPE_CHECKING, Any
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
if TYPE_CHECKING:
|
|
19
|
-
from flock.artifacts import Artifact
|
|
20
|
-
from flock.subscription import JoinSpec, Subscription
|
|
19
|
+
from flock.core.artifacts import Artifact
|
|
20
|
+
from flock.core.subscription import JoinSpec, Subscription
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
class CorrelationGroup:
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"""Event emission for real-time dashboard updates.
|
|
2
|
+
|
|
3
|
+
Phase 5A: Extracted from orchestrator.py to reduce coupling to dashboard.
|
|
4
|
+
|
|
5
|
+
This module handles WebSocket event emission for dashboard visualization of
|
|
6
|
+
batch and correlation logic operations. Separating this code reduces the
|
|
7
|
+
orchestrator's dependency on dashboard-specific components.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from typing import TYPE_CHECKING, Any
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from flock.core.artifacts import Artifact
|
|
17
|
+
from flock.core.subscription import Subscription
|
|
18
|
+
from flock.orchestrator.batch_accumulator import BatchEngine
|
|
19
|
+
from flock.orchestrator.correlation_engine import CorrelationEngine
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class EventEmitter:
|
|
23
|
+
"""Manages WebSocket event emission for dashboard updates.
|
|
24
|
+
|
|
25
|
+
This module is responsible for broadcasting real-time events about
|
|
26
|
+
batch accumulation and correlation group status to connected dashboard
|
|
27
|
+
clients via WebSocket.
|
|
28
|
+
|
|
29
|
+
Phase 5A: Extracted to reduce orchestrator coupling to dashboard.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(self, websocket_manager: Any | None = None):
|
|
33
|
+
"""Initialize EventEmitter with WebSocket manager.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
websocket_manager: WebSocket manager instance for broadcasting events.
|
|
37
|
+
If None, event emission is disabled (dashboard not active).
|
|
38
|
+
"""
|
|
39
|
+
self._websocket_manager = websocket_manager
|
|
40
|
+
|
|
41
|
+
def set_websocket_manager(self, websocket_manager: Any | None) -> None:
|
|
42
|
+
"""Update the WebSocket manager (called when dashboard is enabled).
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
websocket_manager: WebSocket manager instance for broadcasting
|
|
46
|
+
"""
|
|
47
|
+
self._websocket_manager = websocket_manager
|
|
48
|
+
|
|
49
|
+
async def emit_correlation_updated(
|
|
50
|
+
self,
|
|
51
|
+
*,
|
|
52
|
+
correlation_engine: CorrelationEngine,
|
|
53
|
+
agent_name: str,
|
|
54
|
+
subscription_index: int,
|
|
55
|
+
artifact: Artifact,
|
|
56
|
+
) -> None:
|
|
57
|
+
"""Emit CorrelationGroupUpdatedEvent for real-time dashboard updates.
|
|
58
|
+
|
|
59
|
+
Called when an artifact is added to a correlation group that is not yet complete.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
correlation_engine: CorrelationEngine instance with current state
|
|
63
|
+
agent_name: Name of the agent with the JoinSpec subscription
|
|
64
|
+
subscription_index: Index of the subscription in the agent's subscriptions list
|
|
65
|
+
artifact: The artifact that triggered this update
|
|
66
|
+
"""
|
|
67
|
+
# Only emit if dashboard is enabled
|
|
68
|
+
if self._websocket_manager is None:
|
|
69
|
+
return
|
|
70
|
+
|
|
71
|
+
# Import _get_correlation_groups helper from dashboard service
|
|
72
|
+
from flock.dashboard.routes.helpers import _get_correlation_groups
|
|
73
|
+
|
|
74
|
+
# Get current correlation groups state from engine
|
|
75
|
+
groups = _get_correlation_groups(
|
|
76
|
+
correlation_engine, agent_name, subscription_index
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
if not groups:
|
|
80
|
+
return # No groups to report (shouldn't happen, but defensive)
|
|
81
|
+
|
|
82
|
+
# Find the group that was just updated (match by last updated time or artifact ID)
|
|
83
|
+
# For now, we'll emit an event for the FIRST group that's still waiting
|
|
84
|
+
# In practice, the artifact we just added should be in one of these groups
|
|
85
|
+
for group_state in groups:
|
|
86
|
+
if not group_state["is_complete"]:
|
|
87
|
+
# Import CorrelationGroupUpdatedEvent
|
|
88
|
+
from flock.dashboard.events import CorrelationGroupUpdatedEvent
|
|
89
|
+
|
|
90
|
+
# Build and emit event
|
|
91
|
+
event = CorrelationGroupUpdatedEvent(
|
|
92
|
+
agent_name=agent_name,
|
|
93
|
+
subscription_index=subscription_index,
|
|
94
|
+
correlation_key=group_state["correlation_key"],
|
|
95
|
+
collected_types=group_state["collected_types"],
|
|
96
|
+
required_types=group_state["required_types"],
|
|
97
|
+
waiting_for=group_state["waiting_for"],
|
|
98
|
+
elapsed_seconds=group_state["elapsed_seconds"],
|
|
99
|
+
expires_in_seconds=group_state["expires_in_seconds"],
|
|
100
|
+
expires_in_artifacts=group_state["expires_in_artifacts"],
|
|
101
|
+
artifact_id=str(artifact.id),
|
|
102
|
+
artifact_type=artifact.type,
|
|
103
|
+
is_complete=group_state["is_complete"],
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
# Broadcast via WebSocket
|
|
107
|
+
await self._websocket_manager.broadcast(event)
|
|
108
|
+
break # Only emit one event per artifact addition
|
|
109
|
+
|
|
110
|
+
async def emit_batch_item_added(
|
|
111
|
+
self,
|
|
112
|
+
*,
|
|
113
|
+
batch_engine: BatchEngine,
|
|
114
|
+
agent_name: str,
|
|
115
|
+
subscription_index: int,
|
|
116
|
+
subscription: Subscription,
|
|
117
|
+
artifact: Artifact,
|
|
118
|
+
) -> None:
|
|
119
|
+
"""Emit BatchItemAddedEvent for real-time dashboard updates.
|
|
120
|
+
|
|
121
|
+
Called when an artifact is added to a batch that hasn't reached flush threshold.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
batch_engine: BatchEngine instance with current state
|
|
125
|
+
agent_name: Name of the agent with the BatchSpec subscription
|
|
126
|
+
subscription_index: Index of the subscription in the agent's subscriptions list
|
|
127
|
+
subscription: The subscription with BatchSpec configuration
|
|
128
|
+
artifact: The artifact that triggered this update
|
|
129
|
+
"""
|
|
130
|
+
# Only emit if dashboard is enabled
|
|
131
|
+
if self._websocket_manager is None:
|
|
132
|
+
return
|
|
133
|
+
|
|
134
|
+
# Import _get_batch_state helper from dashboard service
|
|
135
|
+
from flock.dashboard.routes.helpers import _get_batch_state
|
|
136
|
+
|
|
137
|
+
# Get current batch state from engine
|
|
138
|
+
batch_state = _get_batch_state(
|
|
139
|
+
batch_engine, agent_name, subscription_index, subscription.batch
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
if not batch_state:
|
|
143
|
+
return # No batch to report (shouldn't happen, but defensive)
|
|
144
|
+
|
|
145
|
+
# Import BatchItemAddedEvent
|
|
146
|
+
from flock.dashboard.events import BatchItemAddedEvent
|
|
147
|
+
|
|
148
|
+
# Build and emit event
|
|
149
|
+
event = BatchItemAddedEvent(
|
|
150
|
+
agent_name=agent_name,
|
|
151
|
+
subscription_index=subscription_index,
|
|
152
|
+
items_collected=batch_state["items_collected"],
|
|
153
|
+
items_target=batch_state.get("items_target"),
|
|
154
|
+
items_remaining=batch_state.get("items_remaining"),
|
|
155
|
+
elapsed_seconds=batch_state["elapsed_seconds"],
|
|
156
|
+
timeout_seconds=batch_state.get("timeout_seconds"),
|
|
157
|
+
timeout_remaining_seconds=batch_state.get("timeout_remaining_seconds"),
|
|
158
|
+
will_flush=batch_state["will_flush"],
|
|
159
|
+
artifact_id=str(artifact.id),
|
|
160
|
+
artifact_type=artifact.type,
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
# Broadcast via WebSocket
|
|
164
|
+
await self._websocket_manager.broadcast(event)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
__all__ = ["EventEmitter"]
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
"""Orchestrator initialization helper.
|
|
2
|
+
|
|
3
|
+
Handles component setup and state initialization.
|
|
4
|
+
Extracted from orchestrator.py to reduce __init__ complexity.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import asyncio
|
|
10
|
+
import logging
|
|
11
|
+
import os
|
|
12
|
+
from typing import TYPE_CHECKING, Any
|
|
13
|
+
|
|
14
|
+
from flock.core.store import InMemoryBlackboardStore
|
|
15
|
+
from flock.orchestrator.artifact_collector import ArtifactCollector
|
|
16
|
+
from flock.orchestrator.batch_accumulator import BatchEngine
|
|
17
|
+
from flock.orchestrator.component_runner import ComponentRunner
|
|
18
|
+
from flock.orchestrator.context_builder import ContextBuilder
|
|
19
|
+
from flock.orchestrator.correlation_engine import CorrelationEngine
|
|
20
|
+
from flock.orchestrator.event_emitter import EventEmitter
|
|
21
|
+
from flock.orchestrator.lifecycle_manager import LifecycleManager
|
|
22
|
+
from flock.orchestrator.mcp_manager import MCPManager
|
|
23
|
+
from flock.orchestrator.tracing import TracingManager
|
|
24
|
+
from flock.utils.cli_helper import init_console
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
if TYPE_CHECKING:
|
|
28
|
+
from flock.components.orchestrator import OrchestratorComponent
|
|
29
|
+
from flock.core.store import BlackboardStore
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class OrchestratorInitializer:
|
|
33
|
+
"""Handles orchestrator component initialization.
|
|
34
|
+
|
|
35
|
+
Centralizes the complex setup logic from Flock.__init__ into
|
|
36
|
+
a focused helper class.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
@staticmethod
|
|
40
|
+
def initialize_components(
|
|
41
|
+
store: BlackboardStore | None,
|
|
42
|
+
context_provider: Any,
|
|
43
|
+
max_agent_iterations: int,
|
|
44
|
+
logger: logging.Logger,
|
|
45
|
+
model: str | None,
|
|
46
|
+
) -> dict[str, Any]:
|
|
47
|
+
"""Initialize all orchestrator components and state.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
store: Blackboard storage backend (or None for default)
|
|
51
|
+
context_provider: Global context provider for agents
|
|
52
|
+
max_agent_iterations: Circuit breaker limit
|
|
53
|
+
logger: Logger instance
|
|
54
|
+
model: Default LLM model
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
Dictionary of initialized components and state
|
|
58
|
+
|
|
59
|
+
Examples:
|
|
60
|
+
>>> logger = logging.getLogger(__name__)
|
|
61
|
+
>>> components = OrchestratorInitializer.initialize_components(
|
|
62
|
+
... store=None,
|
|
63
|
+
... context_provider=None,
|
|
64
|
+
... max_agent_iterations=1000,
|
|
65
|
+
... logger=logger,
|
|
66
|
+
... model="openai/gpt-4.1",
|
|
67
|
+
... )
|
|
68
|
+
>>> orchestrator.store = components["store"]
|
|
69
|
+
>>> orchestrator._scheduler = components["scheduler"]
|
|
70
|
+
"""
|
|
71
|
+
# Initialize console (with error handling for encoding issues)
|
|
72
|
+
try:
|
|
73
|
+
init_console(clear_screen=True, show_banner=True, model=model)
|
|
74
|
+
except (UnicodeEncodeError, UnicodeDecodeError):
|
|
75
|
+
# Skip banner on Windows consoles with encoding issues (e.g., tests, CI)
|
|
76
|
+
pass
|
|
77
|
+
|
|
78
|
+
# Basic state
|
|
79
|
+
resolved_store = store or InMemoryBlackboardStore()
|
|
80
|
+
agents: dict[str, Any] = {}
|
|
81
|
+
lock = asyncio.Lock()
|
|
82
|
+
metrics: dict[str, float] = {"artifacts_published": 0, "agent_runs": 0}
|
|
83
|
+
agent_iteration_count: dict[str, int] = {}
|
|
84
|
+
|
|
85
|
+
# Engines
|
|
86
|
+
artifact_collector = ArtifactCollector()
|
|
87
|
+
correlation_engine = CorrelationEngine()
|
|
88
|
+
batch_engine = BatchEngine()
|
|
89
|
+
|
|
90
|
+
# Phase 5A modules
|
|
91
|
+
context_builder = ContextBuilder(
|
|
92
|
+
store=resolved_store,
|
|
93
|
+
default_context_provider=context_provider,
|
|
94
|
+
)
|
|
95
|
+
event_emitter = EventEmitter(websocket_manager=None)
|
|
96
|
+
lifecycle_manager = LifecycleManager(
|
|
97
|
+
correlation_engine=correlation_engine,
|
|
98
|
+
batch_engine=batch_engine,
|
|
99
|
+
cleanup_interval=0.1,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
# Phase 3 modules
|
|
103
|
+
mcp_manager_instance = MCPManager()
|
|
104
|
+
tracing_manager = TracingManager()
|
|
105
|
+
|
|
106
|
+
# Auto-workflow tracing feature flag
|
|
107
|
+
auto_workflow_enabled = os.getenv(
|
|
108
|
+
"FLOCK_AUTO_WORKFLOW_TRACE", "false"
|
|
109
|
+
).lower() in {
|
|
110
|
+
"true",
|
|
111
|
+
"1",
|
|
112
|
+
"yes",
|
|
113
|
+
"on",
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
# Basic state
|
|
118
|
+
"store": resolved_store,
|
|
119
|
+
"agents": agents,
|
|
120
|
+
"lock": lock,
|
|
121
|
+
"metrics": metrics,
|
|
122
|
+
"agent_iteration_count": agent_iteration_count,
|
|
123
|
+
# Engines
|
|
124
|
+
"artifact_collector": artifact_collector,
|
|
125
|
+
"correlation_engine": correlation_engine,
|
|
126
|
+
"batch_engine": batch_engine,
|
|
127
|
+
# Phase 5A modules
|
|
128
|
+
"context_builder": context_builder,
|
|
129
|
+
"event_emitter": event_emitter,
|
|
130
|
+
"lifecycle_manager": lifecycle_manager,
|
|
131
|
+
# Phase 3 modules
|
|
132
|
+
"mcp_manager_instance": mcp_manager_instance,
|
|
133
|
+
"tracing_manager": tracing_manager,
|
|
134
|
+
# Feature flags
|
|
135
|
+
"auto_workflow_enabled": auto_workflow_enabled,
|
|
136
|
+
# Placeholders
|
|
137
|
+
"websocket_manager": None,
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
@staticmethod
|
|
141
|
+
def initialize_components_and_runner(
|
|
142
|
+
components_list: list[OrchestratorComponent],
|
|
143
|
+
max_agent_iterations: int,
|
|
144
|
+
logger: logging.Logger,
|
|
145
|
+
) -> dict[str, Any]:
|
|
146
|
+
"""Initialize built-in components and create ComponentRunner.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
components_list: List to populate with components
|
|
150
|
+
max_agent_iterations: Circuit breaker limit
|
|
151
|
+
logger: Logger instance
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
Dictionary with component_runner and updated components list
|
|
155
|
+
|
|
156
|
+
Examples:
|
|
157
|
+
>>> components = []
|
|
158
|
+
>>> result = OrchestratorInitializer.initialize_components_and_runner(
|
|
159
|
+
... components, max_agent_iterations=1000, logger=logger
|
|
160
|
+
... )
|
|
161
|
+
>>> component_runner = result["component_runner"]
|
|
162
|
+
"""
|
|
163
|
+
from flock.components.orchestrator import (
|
|
164
|
+
BuiltinCollectionComponent,
|
|
165
|
+
CircuitBreakerComponent,
|
|
166
|
+
DeduplicationComponent,
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
# Add built-in components
|
|
170
|
+
components_list.append(
|
|
171
|
+
CircuitBreakerComponent(max_iterations=max_agent_iterations)
|
|
172
|
+
)
|
|
173
|
+
components_list.append(DeduplicationComponent())
|
|
174
|
+
components_list.append(BuiltinCollectionComponent())
|
|
175
|
+
|
|
176
|
+
# Sort by priority
|
|
177
|
+
components_list.sort(key=lambda c: c.priority)
|
|
178
|
+
|
|
179
|
+
# Create ComponentRunner
|
|
180
|
+
component_runner = ComponentRunner(components_list, logger)
|
|
181
|
+
|
|
182
|
+
return {
|
|
183
|
+
"component_runner": component_runner,
|
|
184
|
+
}
|