flock-core 0.5.9__py3-none-any.whl → 0.5.10__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 (52) hide show
  1. flock/agent.py +149 -62
  2. flock/api/themes.py +6 -2
  3. flock/artifact_collector.py +6 -3
  4. flock/batch_accumulator.py +3 -1
  5. flock/cli.py +3 -1
  6. flock/components.py +45 -56
  7. flock/context_provider.py +531 -0
  8. flock/correlation_engine.py +8 -4
  9. flock/dashboard/collector.py +48 -29
  10. flock/dashboard/events.py +10 -4
  11. flock/dashboard/launcher.py +3 -1
  12. flock/dashboard/models/graph.py +9 -3
  13. flock/dashboard/service.py +143 -72
  14. flock/dashboard/websocket.py +17 -4
  15. flock/engines/dspy_engine.py +174 -98
  16. flock/engines/examples/simple_batch_engine.py +9 -3
  17. flock/examples.py +6 -2
  18. flock/frontend/src/services/indexeddb.test.ts +4 -4
  19. flock/frontend/src/services/indexeddb.ts +1 -1
  20. flock/helper/cli_helper.py +14 -1
  21. flock/logging/auto_trace.py +6 -1
  22. flock/logging/formatters/enum_builder.py +3 -1
  23. flock/logging/formatters/theme_builder.py +32 -17
  24. flock/logging/formatters/themed_formatter.py +38 -22
  25. flock/logging/logging.py +21 -7
  26. flock/logging/telemetry.py +9 -3
  27. flock/logging/telemetry_exporter/duckdb_exporter.py +27 -25
  28. flock/logging/trace_and_logged.py +14 -5
  29. flock/mcp/__init__.py +3 -6
  30. flock/mcp/client.py +49 -19
  31. flock/mcp/config.py +12 -6
  32. flock/mcp/manager.py +6 -2
  33. flock/mcp/servers/sse/flock_sse_server.py +9 -3
  34. flock/mcp/servers/streamable_http/flock_streamable_http_server.py +6 -2
  35. flock/mcp/tool.py +18 -6
  36. flock/mcp/types/handlers.py +3 -1
  37. flock/mcp/types/types.py +9 -3
  38. flock/orchestrator.py +204 -50
  39. flock/orchestrator_component.py +15 -5
  40. flock/patches/dspy_streaming_patch.py +12 -4
  41. flock/registry.py +9 -3
  42. flock/runtime.py +69 -18
  43. flock/service.py +19 -6
  44. flock/store.py +29 -10
  45. flock/subscription.py +6 -4
  46. flock/utilities.py +41 -13
  47. flock/utility/output_utility_component.py +31 -11
  48. {flock_core-0.5.9.dist-info → flock_core-0.5.10.dist-info}/METADATA +132 -2
  49. {flock_core-0.5.9.dist-info → flock_core-0.5.10.dist-info}/RECORD +52 -51
  50. {flock_core-0.5.9.dist-info → flock_core-0.5.10.dist-info}/WHEEL +0 -0
  51. {flock_core-0.5.9.dist-info → flock_core-0.5.10.dist-info}/entry_points.txt +0 -0
  52. {flock_core-0.5.9.dist-info → flock_core-0.5.10.dist-info}/licenses/LICENSE +0 -0
flock/mcp/client.py CHANGED
@@ -76,7 +76,9 @@ class FlockMCPClient(BaseModel, ABC):
76
76
  """
77
77
 
78
78
  # --- Properties ---
79
- config: FlockMCPConfiguration = Field(..., description="The config for this client instance.")
79
+ config: FlockMCPConfiguration = Field(
80
+ ..., description="The config for this client instance."
81
+ )
80
82
 
81
83
  tool_cache: TTLCache | None = Field(
82
84
  default=None,
@@ -174,14 +176,18 @@ class FlockMCPClient(BaseModel, ABC):
174
176
  await client._ensure_connected()
175
177
  try:
176
178
  # delegate the real session
177
- return await getattr(client.client_session, name)(*args, **kwargs)
179
+ return await getattr(client.client_session, name)(
180
+ *args, **kwargs
181
+ )
178
182
  except McpError as e:
179
183
  # only retry on a transport timeout
180
184
  if e.error.code == httpx.codes.REQUEST_TIMEOUT:
181
185
  kind = "timeout"
182
186
  else:
183
187
  # application-level MCP error -> give up immediately
184
- logger.exception(f"MCP error in session.{name}: {e.error}")
188
+ logger.exception(
189
+ f"MCP error in session.{name}: {e.error}"
190
+ )
185
191
  return None
186
192
  except (BrokenPipeError, ClosedResourceError) as e:
187
193
  kind = type(e).__name__
@@ -301,21 +307,27 @@ class FlockMCPClient(BaseModel, ABC):
301
307
 
302
308
  if not self.message_handler:
303
309
  if not self.config.callback_config.message_handler:
304
- self.message_handler = default_flock_mcp_message_handler_callback_factory(
305
- associated_client=self,
306
- logger=logger,
310
+ self.message_handler = (
311
+ default_flock_mcp_message_handler_callback_factory(
312
+ associated_client=self,
313
+ logger=logger,
314
+ )
307
315
  )
308
316
  else:
309
317
  self.message_handler = self.config.callback_config.message_handler
310
318
 
311
319
  if not self.list_roots_callback:
312
320
  if not self.config.callback_config.list_roots_callback:
313
- self.list_roots_callback = default_flock_mcp_list_roots_callback_factory(
314
- associated_client=self,
315
- logger=logger,
321
+ self.list_roots_callback = (
322
+ default_flock_mcp_list_roots_callback_factory(
323
+ associated_client=self,
324
+ logger=logger,
325
+ )
316
326
  )
317
327
  else:
318
- self.list_roots_callback = self.config.callback_config.list_roots_callback
328
+ self.list_roots_callback = (
329
+ self.config.callback_config.list_roots_callback
330
+ )
319
331
 
320
332
  if not self.sampling_callback:
321
333
  if not self.config.callback_config.sampling_callback:
@@ -407,7 +419,9 @@ class FlockMCPClient(BaseModel, ABC):
407
419
  if cache_key in self.tool_result_cache:
408
420
  return self.tool_result_cache[cache_key]
409
421
 
410
- async def _call_tool_internal(name: str, arguments: dict[str, Any]) -> CallToolResult:
422
+ async def _call_tool_internal(
423
+ name: str, arguments: dict[str, Any]
424
+ ) -> CallToolResult:
411
425
  logger.debug(f"Calling tool '{name}' with arguments {arguments}")
412
426
  return await self.session.call_tool(
413
427
  name=name,
@@ -465,19 +479,27 @@ class FlockMCPClient(BaseModel, ABC):
465
479
 
466
480
  async def invalidate_resource_list_cache(self) -> None:
467
481
  """Invalidate the entries in the resource list cache."""
468
- logger.debug(f"Invalidating resource_list_cache for server '{self.config.name}'")
482
+ logger.debug(
483
+ f"Invalidating resource_list_cache for server '{self.config.name}'"
484
+ )
469
485
  async with self.lock:
470
486
  if self.resource_list_cache:
471
487
  self.resource_list_cache.clear()
472
- logger.debug(f"Invalidated resource_list_cache for server '{self.config.name}'")
488
+ logger.debug(
489
+ f"Invalidated resource_list_cache for server '{self.config.name}'"
490
+ )
473
491
 
474
492
  async def invalidate_resource_contents_cache(self) -> None:
475
493
  """Invalidate the entries in the resource contents cache."""
476
- logger.debug(f"Invalidating resource_contents_cache for server '{self.config.name}'.")
494
+ logger.debug(
495
+ f"Invalidating resource_contents_cache for server '{self.config.name}'."
496
+ )
477
497
  async with self.lock:
478
498
  if self.resource_contents_cache:
479
499
  self.resource_contents_cache.clear()
480
- logger.debug(f"Invalidated resource_contents_cache for server '{self.config.name}'")
500
+ logger.debug(
501
+ f"Invalidated resource_contents_cache for server '{self.config.name}'"
502
+ )
481
503
 
482
504
  async def invalidate_resource_contents_cache_entry(self, key: str) -> None:
483
505
  """Invalidate a single entry in the resource contents cache."""
@@ -533,7 +555,9 @@ class FlockMCPClient(BaseModel, ABC):
533
555
  server_params = self.config.connection_config.connection_parameters
534
556
 
535
557
  # Single Hook
536
- transport_ctx = await self.create_transport(server_params, self.additional_params)
558
+ transport_ctx = await self.create_transport(
559
+ server_params, self.additional_params
560
+ )
537
561
  safe_transport = self._safe_transport_ctx(transport_ctx)
538
562
  result = await stack.enter_async_context(safe_transport)
539
563
 
@@ -547,15 +571,21 @@ class FlockMCPClient(BaseModel, ABC):
547
571
  # new type
548
572
  read, write, _get_session_id_callback = result
549
573
  else:
550
- raise RuntimeError(f"create_transport returned unexpected tuple of {result}")
574
+ raise RuntimeError(
575
+ f"create_transport returned unexpected tuple of {result}"
576
+ )
551
577
 
552
578
  if read is None or write is None:
553
- raise RuntimeError("create_transport did not create any read or write streams.")
579
+ raise RuntimeError(
580
+ "create_transport did not create any read or write streams."
581
+ )
554
582
 
555
583
  read_timeout = self.config.connection_config.read_timeout_seconds
556
584
 
557
585
  if self.additional_params and "read_timeout_seconds" in self.additional_params:
558
- read_timeout = self.additional_params.get("read_timeout_seconds", read_timeout)
586
+ read_timeout = self.additional_params.get(
587
+ "read_timeout_seconds", read_timeout
588
+ )
559
589
 
560
590
  timeout_seconds = (
561
591
  read_timeout
flock/mcp/config.py CHANGED
@@ -159,15 +159,17 @@ class FlockMCPConnectionConfiguration(BaseModel):
159
159
  ..., description="Connection parameters for the server."
160
160
  )
161
161
 
162
- transport_type: Literal["stdio", "websockets", "sse", "streamable_http", "custom"] = Field(
163
- ..., description="Type of transport to use."
164
- )
162
+ transport_type: Literal[
163
+ "stdio", "websockets", "sse", "streamable_http", "custom"
164
+ ] = Field(..., description="Type of transport to use.")
165
165
 
166
166
  mount_points: list[MCPRoot] | None = Field(
167
167
  default=None, description="Initial Mountpoints to operate under."
168
168
  )
169
169
 
170
- read_timeout_seconds: float | int = Field(default=60 * 5, description="Read Timeout.")
170
+ read_timeout_seconds: float | int = Field(
171
+ default=60 * 5, description="Read Timeout."
172
+ )
171
173
 
172
174
  server_logging_level: LoggingLevel = Field(
173
175
  default="error",
@@ -187,7 +189,9 @@ class FlockMCPConnectionConfiguration(BaseModel):
187
189
  mode="json",
188
190
  )
189
191
 
190
- data["connection_parameters"] = self.connection_parameters.to_dict(path_type=path_type)
192
+ data["connection_parameters"] = self.connection_parameters.to_dict(
193
+ path_type=path_type
194
+ )
191
195
 
192
196
  return data
193
197
 
@@ -235,7 +239,9 @@ class FlockMCPConnectionConfiguration(BaseModel):
235
239
  )
236
240
  case _:
237
241
  # handle custom server params
238
- connection_params_obj = ServerParameters(**dict(connection_params.items()))
242
+ connection_params_obj = ServerParameters(
243
+ **dict(connection_params.items())
244
+ )
239
245
 
240
246
  if connection_params_obj:
241
247
  data["connection_parameters"] = connection_params_obj
flock/mcp/manager.py CHANGED
@@ -39,7 +39,7 @@ class FlockMCPClientManager:
39
39
  "filesystem": FlockMCPConfiguration(
40
40
  name="filesystem",
41
41
  connection_config=connection_config,
42
- feature_config=feature_config
42
+ feature_config=feature_config,
43
43
  )
44
44
  }
45
45
  manager = FlockMCPClientManager(configs)
@@ -69,7 +69,11 @@ class FlockMCPClientManager:
69
69
  self._lock = asyncio.Lock()
70
70
 
71
71
  async def get_client(
72
- self, server_name: str, agent_id: str, run_id: str, mount_points: list[str] | None = None
72
+ self,
73
+ server_name: str,
74
+ agent_id: str,
75
+ run_id: str,
76
+ mount_points: list[str] | None = None,
73
77
  ) -> FlockMCPClient:
74
78
  """Get or create an MCP client for the given context.
75
79
 
@@ -32,7 +32,9 @@ class FlockSSEConnectionConfig(FlockMCPConnectionConfiguration):
32
32
 
33
33
  # Only thing we need to override here is the concrete transport_type
34
34
  # and connection_parameters fields.
35
- transport_type: Literal["sse"] = Field(default="sse", description="Use the sse transport type.")
35
+ transport_type: Literal["sse"] = Field(
36
+ default="sse", description="Use the sse transport type."
37
+ )
36
38
 
37
39
  connection_parameters: SseServerParameters = Field(
38
40
  ..., description="SSE Server Connection Parameters."
@@ -73,11 +75,15 @@ class FlockSSEClient(FlockMCPClient):
73
75
  override_headers = bool(additional_params.get("override_headers", False))
74
76
  if "headers" in additional_params:
75
77
  if override_headers:
76
- param_copy.headers = additional_params.get("headers", params.headers)
78
+ param_copy.headers = additional_params.get(
79
+ "headers", params.headers
80
+ )
77
81
  else:
78
82
  param_copy.headers.update(additional_params.get("headers", {}))
79
83
  if "read_timeout_seconds" in additional_params:
80
- param_copy.timeout = additional_params.get("read_timeout_seconds", params.timeout)
84
+ param_copy.timeout = additional_params.get(
85
+ "read_timeout_seconds", params.timeout
86
+ )
81
87
 
82
88
  if "sse_read_timeout" in additional_params:
83
89
  param_copy.sse_read_timeout = additional_params.get(
@@ -84,7 +84,9 @@ class FlockStreamableHttpClient(FlockMCPClient):
84
84
 
85
85
  if "headers" in additional_params:
86
86
  if override_headers:
87
- param_copy.headers = additional_params.get("headers", params.headers)
87
+ param_copy.headers = additional_params.get(
88
+ "headers", params.headers
89
+ )
88
90
  else:
89
91
  param_copy.headers.update(additional_params.get("headers", {}))
90
92
  if "auth" in additional_params and isinstance(
@@ -93,7 +95,9 @@ class FlockStreamableHttpClient(FlockMCPClient):
93
95
  param_copy.auth = additional_params.get("auth", param_copy.auth)
94
96
 
95
97
  if "read_timeout_seconds" in additional_params:
96
- param_copy.timeout = additional_params.get("read_timeout_seconds", params.timeout)
98
+ param_copy.timeout = additional_params.get(
99
+ "read_timeout_seconds", params.timeout
100
+ )
97
101
 
98
102
  if "sse_read_timeout" in additional_params:
99
103
  param_copy.sse_read_timeout = additional_params.get(
flock/mcp/tool.py CHANGED
@@ -32,11 +32,17 @@ class FlockMCPTool(BaseModel):
32
32
 
33
33
  name: str = Field(..., description="Name of the tool")
34
34
 
35
- agent_id: str = Field(..., description="Associated agent_id. Used for internal tracking.")
35
+ agent_id: str = Field(
36
+ ..., description="Associated agent_id. Used for internal tracking."
37
+ )
36
38
 
37
- run_id: str = Field(..., description="Associated run_id. Used for internal tracking.")
39
+ run_id: str = Field(
40
+ ..., description="Associated run_id. Used for internal tracking."
41
+ )
38
42
 
39
- description: str | None = Field(..., description="A human-readable description of the tool")
43
+ description: str | None = Field(
44
+ ..., description="A human-readable description of the tool"
45
+ )
40
46
 
41
47
  input_schema: dict[str, Any] = Field(
42
48
  ...,
@@ -76,11 +82,15 @@ class FlockMCPTool(BaseModel):
76
82
  try:
77
83
  return convert_input_schema_to_tool_args(input_schema)
78
84
  except Exception as e: # pragma: no cover - defensive
79
- logger.exception("Failed to convert MCP tool schema to DSPy tool args: %s", e)
85
+ logger.exception(
86
+ "Failed to convert MCP tool schema to DSPy tool args: %s", e
87
+ )
80
88
  # Fallback to empty definitions to avoid breaking execution
81
89
  return {}, {}, {}
82
90
 
83
- def _convert_mcp_tool_result(self, call_tool_result: CallToolResult) -> str | list[Any]:
91
+ def _convert_mcp_tool_result(
92
+ self, call_tool_result: CallToolResult
93
+ ) -> str | list[Any]:
84
94
  text_contents: list[TextContent] = []
85
95
  non_text_contents = []
86
96
 
@@ -107,7 +117,9 @@ class FlockMCPTool(BaseModel):
107
117
 
108
118
  def as_dspy_tool(self, server: Any) -> DSPyTool:
109
119
  """Wrap this tool as a DSPyTool for downstream."""
110
- args, arg_type, args_desc = self._convert_input_schema_to_tool_args(self.input_schema)
120
+ args, arg_type, args_desc = self._convert_input_schema_to_tool_args(
121
+ self.input_schema
122
+ )
111
123
 
112
124
  async def func(*args, **kwargs):
113
125
  with tracer.start_as_current_span(f"tool.{self.name}.call") as span:
@@ -161,7 +161,9 @@ async def handle_logging_message(
161
161
  metadata: dict[str, Any] = params.meta or {} # type: ignore[assignment]
162
162
 
163
163
  str_level = "DEBUG"
164
- prefix = f"Message from Remote MCP Logger '{logger_name}' for server '{server_name}': "
164
+ prefix = (
165
+ f"Message from Remote MCP Logger '{logger_name}' for server '{server_name}': "
166
+ )
165
167
 
166
168
  match level:
167
169
  case "info":
flock/mcp/types/types.py CHANGED
@@ -128,7 +128,9 @@ class ServerParameters(BaseModel):
128
128
  class StdioServerParameters(_MCPStdioServerParameters, ServerParameters):
129
129
  """Base Type for Stdio Server parameters."""
130
130
 
131
- transport_type: Literal["stdio"] = Field(default="stdio", description="Use stdio params.")
131
+ transport_type: Literal["stdio"] = Field(
132
+ default="stdio", description="Use stdio params."
133
+ )
132
134
 
133
135
  env: dict[str, str] | None = Field(
134
136
  default_factory=get_default_env,
@@ -169,7 +171,9 @@ class StreamableHttpServerParameters(ServerParameters):
169
171
  description="How long the client will wait before disconnecting from the server.",
170
172
  )
171
173
 
172
- terminate_on_close: bool = Field(default=True, description="Terminate connection on close")
174
+ terminate_on_close: bool = Field(
175
+ default=True, description="Terminate connection on close"
176
+ )
173
177
 
174
178
  auth: httpx.Auth | None = Field(default=None, description="Httpx Auth Scheme")
175
179
 
@@ -241,7 +245,9 @@ class StreamableHttpServerParameters(ServerParameters):
241
245
  class SseServerParameters(ServerParameters):
242
246
  """Base Type for SSE Server params."""
243
247
 
244
- transport_type: Literal["sse"] = Field(default="sse", description="Use sse server params.")
248
+ transport_type: Literal["sse"] = Field(
249
+ default="sse", description="Use sse server params."
250
+ )
245
251
 
246
252
  url: str | AnyUrl = Field(..., description="The url the server listens at.")
247
253