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.

Files changed (94) hide show
  1. flock/__init__.py +1 -1
  2. flock/agent/__init__.py +30 -0
  3. flock/agent/builder_helpers.py +192 -0
  4. flock/agent/builder_validator.py +169 -0
  5. flock/agent/component_lifecycle.py +325 -0
  6. flock/agent/context_resolver.py +141 -0
  7. flock/agent/mcp_integration.py +212 -0
  8. flock/agent/output_processor.py +304 -0
  9. flock/api/__init__.py +20 -0
  10. flock/{api_models.py → api/models.py} +0 -2
  11. flock/{service.py → api/service.py} +3 -3
  12. flock/cli.py +2 -2
  13. flock/components/__init__.py +41 -0
  14. flock/components/agent/__init__.py +22 -0
  15. flock/{components.py → components/agent/base.py} +4 -3
  16. flock/{utility/output_utility_component.py → components/agent/output_utility.py} +12 -7
  17. flock/components/orchestrator/__init__.py +22 -0
  18. flock/{orchestrator_component.py → components/orchestrator/base.py} +5 -293
  19. flock/components/orchestrator/circuit_breaker.py +95 -0
  20. flock/components/orchestrator/collection.py +143 -0
  21. flock/components/orchestrator/deduplication.py +78 -0
  22. flock/core/__init__.py +30 -0
  23. flock/core/agent.py +953 -0
  24. flock/{artifacts.py → core/artifacts.py} +1 -1
  25. flock/{context_provider.py → core/context_provider.py} +3 -3
  26. flock/core/orchestrator.py +1102 -0
  27. flock/{store.py → core/store.py} +99 -454
  28. flock/{subscription.py → core/subscription.py} +1 -1
  29. flock/dashboard/collector.py +5 -5
  30. flock/dashboard/events.py +1 -1
  31. flock/dashboard/graph_builder.py +7 -7
  32. flock/dashboard/routes/__init__.py +21 -0
  33. flock/dashboard/routes/control.py +327 -0
  34. flock/dashboard/routes/helpers.py +340 -0
  35. flock/dashboard/routes/themes.py +76 -0
  36. flock/dashboard/routes/traces.py +521 -0
  37. flock/dashboard/routes/websocket.py +108 -0
  38. flock/dashboard/service.py +43 -1316
  39. flock/engines/dspy/__init__.py +20 -0
  40. flock/engines/dspy/artifact_materializer.py +216 -0
  41. flock/engines/dspy/signature_builder.py +474 -0
  42. flock/engines/dspy/streaming_executor.py +812 -0
  43. flock/engines/dspy_engine.py +45 -1330
  44. flock/engines/examples/simple_batch_engine.py +2 -2
  45. flock/engines/streaming/__init__.py +3 -0
  46. flock/engines/streaming/sinks.py +489 -0
  47. flock/examples.py +7 -7
  48. flock/logging/logging.py +1 -16
  49. flock/models/__init__.py +10 -0
  50. flock/orchestrator/__init__.py +45 -0
  51. flock/{artifact_collector.py → orchestrator/artifact_collector.py} +3 -3
  52. flock/orchestrator/artifact_manager.py +168 -0
  53. flock/{batch_accumulator.py → orchestrator/batch_accumulator.py} +2 -2
  54. flock/orchestrator/component_runner.py +389 -0
  55. flock/orchestrator/context_builder.py +167 -0
  56. flock/{correlation_engine.py → orchestrator/correlation_engine.py} +2 -2
  57. flock/orchestrator/event_emitter.py +167 -0
  58. flock/orchestrator/initialization.py +184 -0
  59. flock/orchestrator/lifecycle_manager.py +226 -0
  60. flock/orchestrator/mcp_manager.py +202 -0
  61. flock/orchestrator/scheduler.py +189 -0
  62. flock/orchestrator/server_manager.py +234 -0
  63. flock/orchestrator/tracing.py +147 -0
  64. flock/storage/__init__.py +10 -0
  65. flock/storage/artifact_aggregator.py +158 -0
  66. flock/storage/in_memory/__init__.py +6 -0
  67. flock/storage/in_memory/artifact_filter.py +114 -0
  68. flock/storage/in_memory/history_aggregator.py +115 -0
  69. flock/storage/sqlite/__init__.py +10 -0
  70. flock/storage/sqlite/agent_history_queries.py +154 -0
  71. flock/storage/sqlite/consumption_loader.py +100 -0
  72. flock/storage/sqlite/query_builder.py +112 -0
  73. flock/storage/sqlite/query_params_builder.py +91 -0
  74. flock/storage/sqlite/schema_manager.py +168 -0
  75. flock/storage/sqlite/summary_queries.py +194 -0
  76. flock/utils/__init__.py +14 -0
  77. flock/utils/async_utils.py +67 -0
  78. flock/{runtime.py → utils/runtime.py} +3 -3
  79. flock/utils/time_utils.py +53 -0
  80. flock/utils/type_resolution.py +38 -0
  81. flock/{utilities.py → utils/utilities.py} +2 -2
  82. flock/utils/validation.py +57 -0
  83. flock/utils/visibility.py +79 -0
  84. flock/utils/visibility_utils.py +134 -0
  85. {flock_core-0.5.11.dist-info → flock_core-0.5.21.dist-info}/METADATA +19 -5
  86. {flock_core-0.5.11.dist-info → flock_core-0.5.21.dist-info}/RECORD +92 -34
  87. flock/agent.py +0 -1578
  88. flock/orchestrator.py +0 -1983
  89. /flock/{visibility.py → core/visibility.py} +0 -0
  90. /flock/{system_artifacts.py → models/system_artifacts.py} +0 -0
  91. /flock/{helper → utils}/cli_helper.py +0 -0
  92. {flock_core-0.5.11.dist-info → flock_core-0.5.21.dist-info}/WHEEL +0 -0
  93. {flock_core-0.5.11.dist-info → flock_core-0.5.21.dist-info}/entry_points.txt +0 -0
  94. {flock_core-0.5.11.dist-info → flock_core-0.5.21.dist-info}/licenses/LICENSE +0 -0
@@ -13,7 +13,7 @@ from flock.registry import type_registry
13
13
 
14
14
 
15
15
  if TYPE_CHECKING:
16
- from flock.artifacts import Artifact
16
+ from flock.core.artifacts import Artifact
17
17
 
18
18
 
19
19
  Predicate = Callable[[BaseModel], bool]
@@ -16,7 +16,8 @@ from typing import TYPE_CHECKING, Any, Optional
16
16
 
17
17
  from pydantic import PrivateAttr
18
18
 
19
- from flock.components import AgentComponent
19
+ from flock.components.agent import AgentComponent
20
+ from flock.core.store import AgentSnapshotRecord, BlackboardStore
20
21
  from flock.dashboard.events import (
21
22
  AgentActivatedEvent,
22
23
  AgentCompletedEvent,
@@ -27,15 +28,14 @@ from flock.dashboard.events import (
27
28
  )
28
29
  from flock.dashboard.models.graph import GraphRun, GraphState
29
30
  from flock.logging.logging import get_logger
30
- from flock.runtime import Context
31
- from flock.store import AgentSnapshotRecord, BlackboardStore
31
+ from flock.utils.runtime import Context
32
32
 
33
33
 
34
34
  logger = get_logger("dashboard.collector")
35
35
 
36
36
  if TYPE_CHECKING: # pragma: no cover - type hints only
37
- from flock.agent import Agent
38
- from flock.artifacts import Artifact
37
+ from flock.core import Agent
38
+ from flock.core.artifacts import Artifact
39
39
  from flock.dashboard.websocket import WebSocketManager
40
40
 
41
41
 
flock/dashboard/events.py CHANGED
@@ -21,7 +21,7 @@ class SubscriptionInfo(BaseModel):
21
21
  class VisibilitySpec(BaseModel):
22
22
  """Visibility specification for artifacts.
23
23
 
24
- Matches visibility types from flock.visibility module.
24
+ Matches visibility types from flock.core.visibility module.
25
25
  """
26
26
 
27
27
  kind: str # "Public" | "Private" | "Labelled" | "Tenant" | "After"
@@ -23,13 +23,13 @@ from flock.dashboard.models.graph import (
23
23
  GraphTimeRangePreset,
24
24
  )
25
25
  from flock.logging.auto_trace import AutoTracedMeta
26
- from flock.orchestrator import Flock
27
- from flock.store import (
26
+ from flock.core import Flock
27
+ from flock.core.store import (
28
28
  Artifact,
29
29
  BlackboardStore,
30
30
  FilterConfig,
31
31
  )
32
- from flock.store import (
32
+ from flock.core.store import (
33
33
  ArtifactEnvelope as StoreArtifactEnvelope,
34
34
  )
35
35
 
@@ -447,7 +447,7 @@ class GraphAssembler(metaclass=AutoTracedMeta):
447
447
  start_counter: int,
448
448
  ) -> list[GraphEdge]:
449
449
  """Build pending edges for JoinSpec correlation groups."""
450
- from flock.dashboard.service import _get_correlation_groups
450
+ from flock.dashboard.routes.helpers import _get_correlation_groups
451
451
 
452
452
  edges: list[GraphEdge] = []
453
453
  correlation_groups = _get_correlation_groups(
@@ -522,7 +522,7 @@ class GraphAssembler(metaclass=AutoTracedMeta):
522
522
  start_counter: int,
523
523
  ) -> list[GraphEdge]:
524
524
  """Build pending edges for BatchSpec accumulation."""
525
- from flock.dashboard.service import _get_batch_state
525
+ from flock.dashboard.routes.helpers import _get_batch_state
526
526
 
527
527
  edges: list[GraphEdge] = []
528
528
  batch_state = _get_batch_state(
@@ -795,7 +795,7 @@ class GraphAssembler(metaclass=AutoTracedMeta):
795
795
  }
796
796
 
797
797
  # Phase 1.2.1: Get waiting state from CorrelationEngine
798
- from flock.dashboard.service import _get_correlation_groups
798
+ from flock.dashboard.routes.helpers import _get_correlation_groups
799
799
  correlation_groups = _get_correlation_groups(
800
800
  self._orchestrator._correlation_engine, agent.name, idx
801
801
  )
@@ -825,7 +825,7 @@ class GraphAssembler(metaclass=AutoTracedMeta):
825
825
  config["batch"]["timeout_seconds"] = int(batch_spec.timeout.total_seconds())
826
826
 
827
827
  # Phase 1.2.1: Get waiting state from BatchEngine
828
- from flock.dashboard.service import _get_batch_state
828
+ from flock.dashboard.routes.helpers import _get_batch_state
829
829
  batch_state = _get_batch_state(self._orchestrator._batch_engine, agent.name, idx, batch_spec)
830
830
  if batch_state:
831
831
  if "waiting_state" not in config:
@@ -0,0 +1,21 @@
1
+ """Dashboard route modules.
2
+
3
+ Organized route handlers extracted from service.py for better modularity:
4
+ - control.py: Control API endpoints (publish, invoke, agents, etc.)
5
+ - traces.py: Trace-related endpoints (OpenTelemetry, history, etc.)
6
+ - themes.py: Theme management endpoints
7
+ - websocket.py: WebSocket and real-time dashboard endpoints
8
+ """
9
+
10
+ from flock.dashboard.routes.control import register_control_routes
11
+ from flock.dashboard.routes.themes import register_theme_routes
12
+ from flock.dashboard.routes.traces import register_trace_routes
13
+ from flock.dashboard.routes.websocket import register_websocket_routes
14
+
15
+
16
+ __all__ = [
17
+ "register_control_routes",
18
+ "register_theme_routes",
19
+ "register_trace_routes",
20
+ "register_websocket_routes",
21
+ ]
@@ -0,0 +1,327 @@
1
+ """Control API routes for dashboard operations."""
2
+
3
+ from typing import Any
4
+ from uuid import uuid4
5
+
6
+ from fastapi import FastAPI, HTTPException
7
+ from pydantic import ValidationError
8
+
9
+ from flock.core import Flock
10
+ from flock.dashboard.collector import DashboardEventCollector
11
+ from flock.dashboard.events import MessagePublishedEvent, VisibilitySpec
12
+ from flock.dashboard.websocket import WebSocketManager
13
+ from flock.logging.logging import get_logger
14
+ from flock.registry import type_registry
15
+
16
+
17
+ logger = get_logger("dashboard.routes.control")
18
+
19
+
20
+ def register_control_routes(
21
+ app: FastAPI,
22
+ orchestrator: Flock,
23
+ websocket_manager: WebSocketManager,
24
+ event_collector: DashboardEventCollector,
25
+ ) -> None:
26
+ """Register control API endpoints for dashboard operations.
27
+
28
+ Args:
29
+ app: FastAPI application instance
30
+ orchestrator: Flock orchestrator instance
31
+ websocket_manager: WebSocket manager for real-time updates
32
+ event_collector: Dashboard event collector
33
+ """
34
+
35
+ @app.get("/api/artifact-types")
36
+ async def get_artifact_types() -> dict[str, Any]:
37
+ """Get all registered artifact types with their schemas.
38
+
39
+ Returns:
40
+ {
41
+ "artifact_types": [
42
+ {
43
+ "name": "TypeName",
44
+ "schema": {...}
45
+ },
46
+ ...
47
+ ]
48
+ }
49
+ """
50
+ artifact_types = []
51
+
52
+ for type_name in type_registry._by_name:
53
+ try:
54
+ model_class = type_registry.resolve(type_name)
55
+ # Get Pydantic schema
56
+ schema = model_class.model_json_schema()
57
+ artifact_types.append({"name": type_name, "schema": schema})
58
+ except Exception as e:
59
+ logger.warning(f"Could not get schema for {type_name}: {e}")
60
+
61
+ return {"artifact_types": artifact_types}
62
+
63
+ @app.get("/api/agents")
64
+ async def get_agents() -> dict[str, Any]:
65
+ """Get all registered agents with logic operations state.
66
+
67
+ Phase 1.2 Enhancement: Now includes logic_operations configuration
68
+ and waiting state for agents using JoinSpec or BatchSpec.
69
+
70
+ Returns:
71
+ {
72
+ "agents": [
73
+ {
74
+ "name": "agent_name",
75
+ "description": "...",
76
+ "status": "ready" | "waiting" | "active",
77
+ "subscriptions": ["TypeA", "TypeB"],
78
+ "output_types": ["TypeC", "TypeD"],
79
+ "logic_operations": [ # NEW: Phase 1.2
80
+ {
81
+ "subscription_index": 0,
82
+ "subscription_types": ["TypeA", "TypeB"],
83
+ "join": {...}, # JoinSpec config
84
+ "batch": {...}, # BatchSpec config
85
+ "waiting_state": {...} # Current state
86
+ }
87
+ ]
88
+ },
89
+ ...
90
+ ]
91
+ }
92
+ """
93
+ from flock.dashboard.routes.helpers import (
94
+ _build_logic_config,
95
+ _compute_agent_status,
96
+ )
97
+
98
+ agents = []
99
+
100
+ for agent in orchestrator.agents:
101
+ # Extract consumed types from agent subscriptions
102
+ consumed_types = []
103
+ for sub in agent.subscriptions:
104
+ consumed_types.extend(sub.type_names)
105
+
106
+ # Extract produced types from agent outputs
107
+ produced_types = [output.spec.type_name for output in agent.outputs]
108
+
109
+ # NEW Phase 1.2: Logic operations configuration
110
+ logic_operations = []
111
+ for idx, subscription in enumerate(agent.subscriptions):
112
+ logic_config = _build_logic_config(
113
+ agent, subscription, idx, orchestrator
114
+ )
115
+ if logic_config: # Only include if has join/batch
116
+ logic_operations.append(logic_config)
117
+
118
+ agent_data = {
119
+ "name": agent.name,
120
+ "description": agent.description or "",
121
+ "status": _compute_agent_status(
122
+ agent, orchestrator
123
+ ), # NEW: Dynamic status
124
+ "subscriptions": consumed_types,
125
+ "output_types": produced_types,
126
+ }
127
+
128
+ if logic_operations:
129
+ agent_data["logic_operations"] = logic_operations
130
+
131
+ agents.append(agent_data)
132
+
133
+ return {"agents": agents}
134
+
135
+ @app.get("/api/version")
136
+ async def get_version() -> dict[str, str]:
137
+ """Get version information for the backend and dashboard.
138
+
139
+ Returns:
140
+ {
141
+ "backend_version": "0.1.18",
142
+ "package_name": "flock-flow"
143
+ }
144
+ """
145
+ from importlib.metadata import PackageNotFoundError, version
146
+
147
+ try:
148
+ backend_version = version("flock-flow")
149
+ except PackageNotFoundError:
150
+ # Fallback version if package not installed
151
+ backend_version = "0.2.0-dev"
152
+
153
+ return {"backend_version": backend_version, "package_name": "flock-flow"}
154
+
155
+ @app.post("/api/control/publish")
156
+ async def publish_artifact(body: dict[str, Any]) -> dict[str, str]:
157
+ """Publish artifact with correlation tracking.
158
+
159
+ Request body:
160
+ {
161
+ "artifact_type": "TypeName",
162
+ "content": {"field": "value", ...}
163
+ }
164
+
165
+ Returns:
166
+ {
167
+ "correlation_id": "<uuid>",
168
+ "published_at": "<iso-timestamp>"
169
+ }
170
+ """
171
+ # Validate required fields
172
+ artifact_type = body.get("artifact_type")
173
+ content = body.get("content")
174
+
175
+ if not artifact_type:
176
+ raise HTTPException(status_code=400, detail="artifact_type is required")
177
+ if content is None:
178
+ raise HTTPException(status_code=400, detail="content is required")
179
+
180
+ try:
181
+ # Resolve type from registry
182
+ model_class = type_registry.resolve(artifact_type)
183
+
184
+ # Validate content against Pydantic schema
185
+ try:
186
+ instance = model_class(**content)
187
+ except ValidationError as e:
188
+ raise HTTPException(status_code=422, detail=f"Validation error: {e!s}")
189
+
190
+ # Generate correlation ID
191
+ correlation_id = str(uuid4())
192
+
193
+ # Publish to orchestrator
194
+ artifact = await orchestrator.publish(
195
+ instance, correlation_id=correlation_id, is_dashboard=True
196
+ )
197
+
198
+ # Phase 11 Fix: Emit message_published event for dashboard visibility
199
+ # This enables virtual "orchestrator" agent to appear in both Agent View and Blackboard View
200
+ event = MessagePublishedEvent(
201
+ correlation_id=str(artifact.correlation_id),
202
+ artifact_id=str(artifact.id),
203
+ artifact_type=artifact.type,
204
+ produced_by=artifact.produced_by, # Will be "orchestrator" or similar for non-agent publishers
205
+ payload=artifact.payload,
206
+ visibility=VisibilitySpec(
207
+ kind="Public"
208
+ ), # Dashboard-published artifacts are public by default
209
+ tags=list(artifact.tags) if artifact.tags else [],
210
+ partition_key=artifact.partition_key,
211
+ version=artifact.version,
212
+ consumers=[], # Will be populated by subscription matching in frontend
213
+ )
214
+ await websocket_manager.broadcast(event)
215
+
216
+ return {
217
+ "correlation_id": str(artifact.correlation_id),
218
+ "published_at": artifact.created_at.isoformat(),
219
+ }
220
+
221
+ except KeyError:
222
+ raise HTTPException(
223
+ status_code=422, detail=f"Unknown artifact type: {artifact_type}"
224
+ )
225
+ except Exception as e:
226
+ logger.exception(f"Error publishing artifact: {e}")
227
+ raise HTTPException(status_code=500, detail=str(e))
228
+
229
+ @app.post("/api/control/invoke")
230
+ async def invoke_agent(body: dict[str, Any]) -> dict[str, Any]:
231
+ """Directly invoke a specific agent.
232
+
233
+ Request body:
234
+ {
235
+ "agent_name": "agent_name",
236
+ "input": {"type": "TypeName", "field": "value", ...}
237
+ }
238
+
239
+ Returns:
240
+ {
241
+ "invocation_id": "<uuid>",
242
+ "result": "success"
243
+ }
244
+ """
245
+ # Validate required fields
246
+ agent_name = body.get("agent_name")
247
+ input_data = body.get("input")
248
+
249
+ if not agent_name:
250
+ raise HTTPException(status_code=400, detail="agent_name is required")
251
+ if input_data is None:
252
+ raise HTTPException(status_code=400, detail="input is required")
253
+
254
+ try:
255
+ # Get agent from orchestrator
256
+ agent = orchestrator.get_agent(agent_name)
257
+ except KeyError:
258
+ raise HTTPException(
259
+ status_code=404, detail=f"Agent not found: {agent_name}"
260
+ )
261
+
262
+ try:
263
+ # Parse input type and create instance
264
+ input_type = input_data.get("type")
265
+ if not input_type:
266
+ raise HTTPException(status_code=400, detail="input.type is required")
267
+
268
+ # Resolve type from registry
269
+ model_class = type_registry.resolve(input_type)
270
+
271
+ # Create payload by removing 'type' key
272
+ payload = {k: v for k, v in input_data.items() if k != "type"}
273
+
274
+ # Validate and create instance
275
+ try:
276
+ instance = model_class(**payload)
277
+ except ValidationError as e:
278
+ raise HTTPException(status_code=422, detail=f"Validation error: {e!s}")
279
+
280
+ # Invoke agent
281
+ outputs = await orchestrator.invoke(agent, instance)
282
+
283
+ # Generate invocation ID from first output or create new UUID
284
+ invocation_id = str(outputs[0].id) if outputs else str(uuid4())
285
+
286
+ # Extract correlation_id from first output (for filter automation)
287
+ correlation_id = (
288
+ str(outputs[0].correlation_id)
289
+ if outputs and outputs[0].correlation_id
290
+ else None
291
+ )
292
+
293
+ return {
294
+ "invocation_id": invocation_id,
295
+ "correlation_id": correlation_id,
296
+ "result": "success",
297
+ }
298
+
299
+ except HTTPException:
300
+ raise
301
+ except KeyError:
302
+ raise HTTPException(status_code=422, detail=f"Unknown type: {input_type}")
303
+ except Exception as e:
304
+ logger.exception(f"Error invoking agent: {e}")
305
+ raise HTTPException(status_code=500, detail=str(e))
306
+
307
+ @app.post("/api/control/pause")
308
+ async def pause_orchestrator() -> dict[str, Any]:
309
+ """Pause orchestrator (placeholder).
310
+
311
+ Returns:
312
+ 501 Not Implemented
313
+ """
314
+ raise HTTPException(
315
+ status_code=501, detail="Pause functionality coming in Phase 12"
316
+ )
317
+
318
+ @app.post("/api/control/resume")
319
+ async def resume_orchestrator() -> dict[str, Any]:
320
+ """Resume orchestrator (placeholder).
321
+
322
+ Returns:
323
+ 501 Not Implemented
324
+ """
325
+ raise HTTPException(
326
+ status_code=501, detail="Resume functionality coming in Phase 12"
327
+ )