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,168 @@
|
|
|
1
|
+
"""Artifact publishing and persistence."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Iterable
|
|
6
|
+
from typing import TYPE_CHECKING, Any
|
|
7
|
+
from uuid import uuid4
|
|
8
|
+
|
|
9
|
+
from pydantic import BaseModel
|
|
10
|
+
|
|
11
|
+
from flock.core.artifacts import Artifact
|
|
12
|
+
from flock.core.visibility import PublicVisibility, Visibility
|
|
13
|
+
from flock.registry import type_registry
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from flock.core import Flock
|
|
18
|
+
from flock.core.store import BlackboardStore
|
|
19
|
+
from flock.orchestrator import AgentScheduler
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class ArtifactManager:
|
|
23
|
+
"""Manages artifact publishing and persistence.
|
|
24
|
+
|
|
25
|
+
Responsibilities:
|
|
26
|
+
- Normalize different input types (BaseModel, dict, Artifact)
|
|
27
|
+
- Persist artifacts to store
|
|
28
|
+
- Trigger scheduling after publish
|
|
29
|
+
- Handle batch publishing
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(
|
|
33
|
+
self, orchestrator: Flock, store: BlackboardStore, scheduler: AgentScheduler
|
|
34
|
+
):
|
|
35
|
+
"""Initialize artifact manager.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
orchestrator: Flock orchestrator instance
|
|
39
|
+
store: Blackboard store for persistence
|
|
40
|
+
scheduler: Scheduler for triggering agent execution
|
|
41
|
+
"""
|
|
42
|
+
self._orchestrator = orchestrator
|
|
43
|
+
self._store = store
|
|
44
|
+
self._scheduler = scheduler
|
|
45
|
+
self._logger = orchestrator._logger
|
|
46
|
+
|
|
47
|
+
async def publish(
|
|
48
|
+
self,
|
|
49
|
+
obj: BaseModel | dict | Artifact,
|
|
50
|
+
*,
|
|
51
|
+
visibility: Visibility | None = None,
|
|
52
|
+
correlation_id: str | None = None,
|
|
53
|
+
partition_key: str | None = None,
|
|
54
|
+
tags: set[str] | None = None,
|
|
55
|
+
is_dashboard: bool = False,
|
|
56
|
+
) -> Artifact:
|
|
57
|
+
"""Publish an artifact to the blackboard (event-driven).
|
|
58
|
+
|
|
59
|
+
All agents with matching subscriptions will be triggered according to
|
|
60
|
+
their filters (type, predicates, visibility, etc).
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
obj: Object to publish (BaseModel instance, dict, or Artifact)
|
|
64
|
+
visibility: Access control (defaults to PublicVisibility)
|
|
65
|
+
correlation_id: Optional correlation ID for request tracing
|
|
66
|
+
partition_key: Optional partition key for sharding
|
|
67
|
+
tags: Optional tags for channel-based routing
|
|
68
|
+
is_dashboard: Internal flag for dashboard events
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
The published Artifact
|
|
72
|
+
|
|
73
|
+
Examples:
|
|
74
|
+
>>> # Publish a model instance (recommended)
|
|
75
|
+
>>> task = Task(name="Deploy", priority=5)
|
|
76
|
+
>>> await artifact_manager.publish(task)
|
|
77
|
+
|
|
78
|
+
>>> # Publish with custom visibility
|
|
79
|
+
>>> await artifact_manager.publish(
|
|
80
|
+
... task, visibility=PrivateVisibility(agents={"admin"})
|
|
81
|
+
... )
|
|
82
|
+
"""
|
|
83
|
+
# Handle different input types
|
|
84
|
+
if isinstance(obj, Artifact):
|
|
85
|
+
# Already an artifact - publish as-is
|
|
86
|
+
artifact = obj
|
|
87
|
+
elif isinstance(obj, BaseModel):
|
|
88
|
+
# BaseModel instance - get type from registry
|
|
89
|
+
type_name = type_registry.name_for(type(obj))
|
|
90
|
+
artifact = Artifact(
|
|
91
|
+
type=type_name,
|
|
92
|
+
payload=obj.model_dump(),
|
|
93
|
+
produced_by="external",
|
|
94
|
+
visibility=visibility or PublicVisibility(),
|
|
95
|
+
correlation_id=correlation_id or uuid4(),
|
|
96
|
+
partition_key=partition_key,
|
|
97
|
+
tags=tags or set(),
|
|
98
|
+
)
|
|
99
|
+
elif isinstance(obj, dict):
|
|
100
|
+
# Dict must have 'type' key
|
|
101
|
+
if "type" not in obj:
|
|
102
|
+
raise ValueError(
|
|
103
|
+
"Dict input must contain 'type' key. "
|
|
104
|
+
"Example: {'type': 'Task', 'name': 'foo', 'priority': 5}"
|
|
105
|
+
)
|
|
106
|
+
# Support both {'type': 'X', 'payload': {...}} and {'type': 'X', ...}
|
|
107
|
+
type_name = obj["type"]
|
|
108
|
+
if "payload" in obj:
|
|
109
|
+
payload = obj["payload"]
|
|
110
|
+
else:
|
|
111
|
+
payload = {k: v for k, v in obj.items() if k != "type"}
|
|
112
|
+
|
|
113
|
+
artifact = Artifact(
|
|
114
|
+
type=type_name,
|
|
115
|
+
payload=payload,
|
|
116
|
+
produced_by="external",
|
|
117
|
+
visibility=visibility or PublicVisibility(),
|
|
118
|
+
correlation_id=correlation_id,
|
|
119
|
+
partition_key=partition_key,
|
|
120
|
+
tags=tags or set(),
|
|
121
|
+
)
|
|
122
|
+
else:
|
|
123
|
+
raise TypeError(
|
|
124
|
+
f"Cannot publish object of type {type(obj).__name__}. "
|
|
125
|
+
"Expected BaseModel, dict, or Artifact."
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
# Persist and schedule matching agents
|
|
129
|
+
await self.persist_and_schedule(artifact)
|
|
130
|
+
return artifact
|
|
131
|
+
|
|
132
|
+
async def publish_many(
|
|
133
|
+
self, objects: Iterable[BaseModel | dict | Artifact], **kwargs: Any
|
|
134
|
+
) -> list[Artifact]:
|
|
135
|
+
"""Publish multiple artifacts at once (event-driven).
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
objects: Iterable of objects to publish
|
|
139
|
+
**kwargs: Passed to each publish() call (visibility, tags, etc)
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
List of published Artifacts
|
|
143
|
+
|
|
144
|
+
Example:
|
|
145
|
+
>>> tasks = [
|
|
146
|
+
... Task(name="Deploy", priority=5),
|
|
147
|
+
... Task(name="Test", priority=3),
|
|
148
|
+
... ]
|
|
149
|
+
>>> await artifact_manager.publish_many(tasks, tags={"sprint-3"})
|
|
150
|
+
"""
|
|
151
|
+
artifacts = []
|
|
152
|
+
for obj in objects:
|
|
153
|
+
artifact = await self.publish(obj, **kwargs)
|
|
154
|
+
artifacts.append(artifact)
|
|
155
|
+
return artifacts
|
|
156
|
+
|
|
157
|
+
async def persist_and_schedule(self, artifact: Artifact) -> None:
|
|
158
|
+
"""Persist artifact to store and trigger scheduling.
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
artifact: Artifact to publish
|
|
162
|
+
"""
|
|
163
|
+
await self._store.publish(artifact)
|
|
164
|
+
self._orchestrator.metrics["artifacts_published"] += 1
|
|
165
|
+
await self._scheduler.schedule_artifact(artifact)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
__all__ = ["ArtifactManager"]
|
|
@@ -16,8 +16,8 @@ from typing import TYPE_CHECKING
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
if TYPE_CHECKING:
|
|
19
|
-
from flock.artifacts import Artifact
|
|
20
|
-
from flock.subscription import BatchSpec, Subscription
|
|
19
|
+
from flock.core.artifacts import Artifact
|
|
20
|
+
from flock.core.subscription import BatchSpec, Subscription
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
class BatchAccumulator:
|
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
"""Component lifecycle hook execution for orchestrator.
|
|
2
|
+
|
|
3
|
+
This module manages the execution of OrchestratorComponent hooks in priority order.
|
|
4
|
+
Components can modify artifacts, control scheduling decisions, and handle collection logic.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
from asyncio import Task
|
|
11
|
+
from typing import TYPE_CHECKING, Any
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from flock.components.orchestrator import OrchestratorComponent
|
|
16
|
+
from flock.core import Agent
|
|
17
|
+
from flock.core.artifacts import Artifact
|
|
18
|
+
from flock.core.subscription import Subscription
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ComponentRunner:
|
|
22
|
+
"""Executes orchestrator component hooks in priority order.
|
|
23
|
+
|
|
24
|
+
This class manages the component lifecycle including initialization,
|
|
25
|
+
artifact processing, scheduling decisions, and shutdown. All hooks
|
|
26
|
+
execute in priority order (lower priority number = earlier execution).
|
|
27
|
+
|
|
28
|
+
Attributes:
|
|
29
|
+
_components: List of orchestrator components (sorted by priority)
|
|
30
|
+
_logger: Logger instance for component execution tracking
|
|
31
|
+
_initialized: Flag to prevent multiple initializations
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(
|
|
35
|
+
self,
|
|
36
|
+
components: list[OrchestratorComponent],
|
|
37
|
+
logger: logging.Logger | None = None,
|
|
38
|
+
) -> None:
|
|
39
|
+
"""Initialize the component runner.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
components: List of orchestrator components (should be pre-sorted by priority)
|
|
43
|
+
logger: Logger instance (defaults to module logger if not provided)
|
|
44
|
+
"""
|
|
45
|
+
self._components = components
|
|
46
|
+
self._logger = logger or logging.getLogger(__name__)
|
|
47
|
+
self._initialized = False
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def components(self) -> list[OrchestratorComponent]:
|
|
51
|
+
"""Get the list of registered components."""
|
|
52
|
+
return self._components
|
|
53
|
+
|
|
54
|
+
@property
|
|
55
|
+
def is_initialized(self) -> bool:
|
|
56
|
+
"""Check if components have been initialized."""
|
|
57
|
+
return self._initialized
|
|
58
|
+
|
|
59
|
+
async def run_initialize(self, orchestrator: Any) -> None:
|
|
60
|
+
"""Initialize all components in priority order (called once).
|
|
61
|
+
|
|
62
|
+
Executes on_initialize hook for each component. Sets _initialized
|
|
63
|
+
flag to prevent multiple initializations.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
orchestrator: The Flock orchestrator instance
|
|
67
|
+
"""
|
|
68
|
+
if self._initialized:
|
|
69
|
+
return
|
|
70
|
+
|
|
71
|
+
self._logger.info(
|
|
72
|
+
f"Initializing {len(self._components)} orchestrator components"
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
for component in self._components:
|
|
76
|
+
comp_name = component.name or component.__class__.__name__
|
|
77
|
+
self._logger.debug(
|
|
78
|
+
f"Initializing component: name={comp_name}, priority={component.priority}"
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
try:
|
|
82
|
+
await component.on_initialize(orchestrator)
|
|
83
|
+
except Exception as e:
|
|
84
|
+
self._logger.exception(
|
|
85
|
+
f"Component initialization failed: name={comp_name}, error={e!s}"
|
|
86
|
+
)
|
|
87
|
+
raise
|
|
88
|
+
|
|
89
|
+
self._initialized = True
|
|
90
|
+
self._logger.info(f"All components initialized: count={len(self._components)}")
|
|
91
|
+
|
|
92
|
+
async def run_artifact_published(
|
|
93
|
+
self, orchestrator: Any, artifact: Artifact
|
|
94
|
+
) -> Artifact | None:
|
|
95
|
+
"""Run on_artifact_published hooks (returns modified artifact or None to block).
|
|
96
|
+
|
|
97
|
+
Components execute in priority order, each receiving the artifact from the
|
|
98
|
+
previous component (chaining). If any component returns None, the artifact
|
|
99
|
+
is blocked and scheduling stops.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
orchestrator: The Flock orchestrator instance
|
|
103
|
+
artifact: The artifact being published
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
Modified artifact or None if blocked by a component
|
|
107
|
+
"""
|
|
108
|
+
current_artifact = artifact
|
|
109
|
+
|
|
110
|
+
for component in self._components:
|
|
111
|
+
comp_name = component.name or component.__class__.__name__
|
|
112
|
+
self._logger.debug(
|
|
113
|
+
f"Running on_artifact_published: component={comp_name}, "
|
|
114
|
+
f"artifact_type={current_artifact.type}, artifact_id={current_artifact.id}"
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
try:
|
|
118
|
+
result = await component.on_artifact_published(
|
|
119
|
+
orchestrator, current_artifact
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
if result is None:
|
|
123
|
+
self._logger.info(
|
|
124
|
+
f"Artifact blocked by component: component={comp_name}, "
|
|
125
|
+
f"artifact_type={current_artifact.type}, artifact_id={current_artifact.id}"
|
|
126
|
+
)
|
|
127
|
+
return None
|
|
128
|
+
|
|
129
|
+
current_artifact = result
|
|
130
|
+
except Exception as e:
|
|
131
|
+
self._logger.exception(
|
|
132
|
+
f"Component hook failed: component={comp_name}, "
|
|
133
|
+
f"hook=on_artifact_published, error={e!s}"
|
|
134
|
+
)
|
|
135
|
+
raise
|
|
136
|
+
|
|
137
|
+
return current_artifact
|
|
138
|
+
|
|
139
|
+
async def run_before_schedule(
|
|
140
|
+
self,
|
|
141
|
+
orchestrator: Any,
|
|
142
|
+
artifact: Artifact,
|
|
143
|
+
agent: Agent,
|
|
144
|
+
subscription: Subscription,
|
|
145
|
+
) -> Any:
|
|
146
|
+
"""Run on_before_schedule hooks (returns CONTINUE, SKIP, or DEFER).
|
|
147
|
+
|
|
148
|
+
Components execute in priority order. First component to return SKIP or
|
|
149
|
+
DEFER stops execution and returns that decision.
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
orchestrator: The Flock orchestrator instance
|
|
153
|
+
artifact: The artifact being scheduled
|
|
154
|
+
agent: The target agent
|
|
155
|
+
subscription: The subscription being evaluated
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
ScheduleDecision (CONTINUE, SKIP, or DEFER)
|
|
159
|
+
"""
|
|
160
|
+
from flock.components.orchestrator import ScheduleDecision
|
|
161
|
+
|
|
162
|
+
for component in self._components:
|
|
163
|
+
comp_name = component.name or component.__class__.__name__
|
|
164
|
+
|
|
165
|
+
self._logger.debug(
|
|
166
|
+
f"Running on_before_schedule: component={comp_name}, "
|
|
167
|
+
f"agent={agent.name}, artifact_type={artifact.type}"
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
try:
|
|
171
|
+
decision = await component.on_before_schedule(
|
|
172
|
+
orchestrator, artifact, agent, subscription
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
if decision == ScheduleDecision.SKIP:
|
|
176
|
+
self._logger.info(
|
|
177
|
+
f"Scheduling skipped by component: component={comp_name}, "
|
|
178
|
+
f"agent={agent.name}, artifact_type={artifact.type}, decision=SKIP"
|
|
179
|
+
)
|
|
180
|
+
return ScheduleDecision.SKIP
|
|
181
|
+
|
|
182
|
+
if decision == ScheduleDecision.DEFER:
|
|
183
|
+
self._logger.debug(
|
|
184
|
+
f"Scheduling deferred by component: component={comp_name}, "
|
|
185
|
+
f"agent={agent.name}, decision=DEFER"
|
|
186
|
+
)
|
|
187
|
+
return ScheduleDecision.DEFER
|
|
188
|
+
|
|
189
|
+
except Exception as e:
|
|
190
|
+
self._logger.exception(
|
|
191
|
+
f"Component hook failed: component={comp_name}, "
|
|
192
|
+
f"hook=on_before_schedule, error={e!s}"
|
|
193
|
+
)
|
|
194
|
+
raise
|
|
195
|
+
|
|
196
|
+
return ScheduleDecision.CONTINUE
|
|
197
|
+
|
|
198
|
+
async def run_collect_artifacts(
|
|
199
|
+
self,
|
|
200
|
+
orchestrator: Any,
|
|
201
|
+
artifact: Artifact,
|
|
202
|
+
agent: Agent,
|
|
203
|
+
subscription: Subscription,
|
|
204
|
+
) -> Any:
|
|
205
|
+
"""Run on_collect_artifacts hooks (returns first non-None result).
|
|
206
|
+
|
|
207
|
+
Components execute in priority order. First component to return non-None
|
|
208
|
+
wins (short-circuit). If all return None, default is immediate scheduling.
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
orchestrator: The Flock orchestrator instance
|
|
212
|
+
artifact: The artifact being collected
|
|
213
|
+
agent: The target agent
|
|
214
|
+
subscription: The subscription being evaluated
|
|
215
|
+
|
|
216
|
+
Returns:
|
|
217
|
+
CollectionResult (complete=True/False, artifacts=[...])
|
|
218
|
+
"""
|
|
219
|
+
from flock.components.orchestrator import CollectionResult
|
|
220
|
+
|
|
221
|
+
for component in self._components:
|
|
222
|
+
comp_name = component.name or component.__class__.__name__
|
|
223
|
+
|
|
224
|
+
self._logger.debug(
|
|
225
|
+
f"Running on_collect_artifacts: component={comp_name}, "
|
|
226
|
+
f"agent={agent.name}, artifact_type={artifact.type}"
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
try:
|
|
230
|
+
result = await component.on_collect_artifacts(
|
|
231
|
+
orchestrator, artifact, agent, subscription
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
if result is not None:
|
|
235
|
+
self._logger.debug(
|
|
236
|
+
f"Collection handled by component: component={comp_name}, "
|
|
237
|
+
f"complete={result.complete}, artifact_count={len(result.artifacts)}"
|
|
238
|
+
)
|
|
239
|
+
return result
|
|
240
|
+
except Exception as e:
|
|
241
|
+
self._logger.exception(
|
|
242
|
+
f"Component hook failed: component={comp_name}, "
|
|
243
|
+
f"hook=on_collect_artifacts, error={e!s}"
|
|
244
|
+
)
|
|
245
|
+
raise
|
|
246
|
+
|
|
247
|
+
# Default: immediate scheduling with single artifact
|
|
248
|
+
self._logger.debug(
|
|
249
|
+
f"No component handled collection, using default: "
|
|
250
|
+
f"agent={agent.name}, artifact_type={artifact.type}"
|
|
251
|
+
)
|
|
252
|
+
return CollectionResult.immediate([artifact])
|
|
253
|
+
|
|
254
|
+
async def run_before_agent_schedule(
|
|
255
|
+
self, orchestrator: Any, agent: Agent, artifacts: list[Artifact]
|
|
256
|
+
) -> list[Artifact] | None:
|
|
257
|
+
"""Run on_before_agent_schedule hooks (returns modified artifacts or None to block).
|
|
258
|
+
|
|
259
|
+
Components execute in priority order, each receiving artifacts from the
|
|
260
|
+
previous component (chaining). If any component returns None, scheduling
|
|
261
|
+
is blocked.
|
|
262
|
+
|
|
263
|
+
Args:
|
|
264
|
+
orchestrator: The Flock orchestrator instance
|
|
265
|
+
agent: The target agent
|
|
266
|
+
artifacts: List of artifacts to schedule
|
|
267
|
+
|
|
268
|
+
Returns:
|
|
269
|
+
Modified artifacts list or None if scheduling blocked
|
|
270
|
+
"""
|
|
271
|
+
current_artifacts = artifacts
|
|
272
|
+
|
|
273
|
+
for component in self._components:
|
|
274
|
+
comp_name = component.name or component.__class__.__name__
|
|
275
|
+
|
|
276
|
+
self._logger.debug(
|
|
277
|
+
f"Running on_before_agent_schedule: component={comp_name}, "
|
|
278
|
+
f"agent={agent.name}, artifact_count={len(current_artifacts)}"
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
try:
|
|
282
|
+
result = await component.on_before_agent_schedule(
|
|
283
|
+
orchestrator, agent, current_artifacts
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
if result is None:
|
|
287
|
+
self._logger.info(
|
|
288
|
+
f"Agent scheduling blocked by component: component={comp_name}, "
|
|
289
|
+
f"agent={agent.name}"
|
|
290
|
+
)
|
|
291
|
+
return None
|
|
292
|
+
|
|
293
|
+
current_artifacts = result
|
|
294
|
+
except Exception as e:
|
|
295
|
+
self._logger.exception(
|
|
296
|
+
f"Component hook failed: component={comp_name}, "
|
|
297
|
+
f"hook=on_before_agent_schedule, error={e!s}"
|
|
298
|
+
)
|
|
299
|
+
raise
|
|
300
|
+
|
|
301
|
+
return current_artifacts
|
|
302
|
+
|
|
303
|
+
async def run_agent_scheduled(
|
|
304
|
+
self,
|
|
305
|
+
orchestrator: Any,
|
|
306
|
+
agent: Agent,
|
|
307
|
+
artifacts: list[Artifact],
|
|
308
|
+
task: Task[Any],
|
|
309
|
+
) -> None:
|
|
310
|
+
"""Run on_agent_scheduled hooks (notification only, non-blocking).
|
|
311
|
+
|
|
312
|
+
Components execute in priority order. Exceptions are logged but don't
|
|
313
|
+
prevent other components from executing or block scheduling.
|
|
314
|
+
|
|
315
|
+
Args:
|
|
316
|
+
orchestrator: The Flock orchestrator instance
|
|
317
|
+
agent: The scheduled agent
|
|
318
|
+
artifacts: List of artifacts for the agent
|
|
319
|
+
task: The asyncio task for agent execution
|
|
320
|
+
"""
|
|
321
|
+
for component in self._components:
|
|
322
|
+
comp_name = component.name or component.__class__.__name__
|
|
323
|
+
|
|
324
|
+
self._logger.debug(
|
|
325
|
+
f"Running on_agent_scheduled: component={comp_name}, "
|
|
326
|
+
f"agent={agent.name}, artifact_count={len(artifacts)}"
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
try:
|
|
330
|
+
await component.on_agent_scheduled(orchestrator, agent, artifacts, task)
|
|
331
|
+
except Exception as e:
|
|
332
|
+
self._logger.warning(
|
|
333
|
+
f"Component notification hook failed (non-critical): "
|
|
334
|
+
f"component={comp_name}, hook=on_agent_scheduled, error={e!s}"
|
|
335
|
+
)
|
|
336
|
+
# Don't propagate - this is a notification hook
|
|
337
|
+
|
|
338
|
+
async def run_idle(self, orchestrator: Any) -> None:
|
|
339
|
+
"""Run on_orchestrator_idle hooks when orchestrator becomes idle.
|
|
340
|
+
|
|
341
|
+
Components execute in priority order. Exceptions are logged but don't
|
|
342
|
+
prevent other components from executing.
|
|
343
|
+
|
|
344
|
+
Args:
|
|
345
|
+
orchestrator: The Flock orchestrator instance
|
|
346
|
+
"""
|
|
347
|
+
self._logger.debug(
|
|
348
|
+
f"Running on_orchestrator_idle hooks: component_count={len(self._components)}"
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
for component in self._components:
|
|
352
|
+
comp_name = component.name or component.__class__.__name__
|
|
353
|
+
|
|
354
|
+
try:
|
|
355
|
+
await component.on_orchestrator_idle(orchestrator)
|
|
356
|
+
except Exception as e:
|
|
357
|
+
self._logger.warning(
|
|
358
|
+
f"Component idle hook failed (non-critical): "
|
|
359
|
+
f"component={comp_name}, hook=on_orchestrator_idle, error={e!s}"
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
async def run_shutdown(self, orchestrator: Any) -> None:
|
|
363
|
+
"""Run on_shutdown hooks when orchestrator shuts down.
|
|
364
|
+
|
|
365
|
+
Components execute in priority order. Exceptions are logged but don't
|
|
366
|
+
prevent shutdown of other components (best-effort cleanup).
|
|
367
|
+
|
|
368
|
+
Args:
|
|
369
|
+
orchestrator: The Flock orchestrator instance
|
|
370
|
+
"""
|
|
371
|
+
self._logger.info(
|
|
372
|
+
f"Shutting down {len(self._components)} orchestrator components"
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
for component in self._components:
|
|
376
|
+
comp_name = component.name or component.__class__.__name__
|
|
377
|
+
self._logger.debug(f"Shutting down component: name={comp_name}")
|
|
378
|
+
|
|
379
|
+
try:
|
|
380
|
+
await component.on_shutdown(orchestrator)
|
|
381
|
+
except Exception as e:
|
|
382
|
+
self._logger.exception(
|
|
383
|
+
f"Component shutdown failed: component={comp_name}, "
|
|
384
|
+
f"hook=on_shutdown, error={e!s}"
|
|
385
|
+
)
|
|
386
|
+
# Continue shutting down other components
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
__all__ = ["ComponentRunner"]
|