flock-core 0.5.0b62__py3-none-any.whl → 0.5.0b65__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/agent.py CHANGED
@@ -5,7 +5,7 @@ from __future__ import annotations
5
5
  import asyncio
6
6
  import os
7
7
  from dataclasses import dataclass
8
- from typing import TYPE_CHECKING, Any
8
+ from typing import TYPE_CHECKING, Any, TypedDict
9
9
 
10
10
  from pydantic import BaseModel
11
11
 
@@ -27,6 +27,38 @@ if TYPE_CHECKING: # pragma: no cover - type hints only
27
27
  from flock.orchestrator import Flock
28
28
 
29
29
 
30
+ class MCPServerConfig(TypedDict, total=False):
31
+ """Configuration for MCP server assignment to an agent.
32
+
33
+ All fields are optional. If omitted, no restrictions apply.
34
+
35
+ Attributes:
36
+ roots: Filesystem paths this server can access.
37
+ Empty list or omitted = no mount restrictions.
38
+ tool_whitelist: Tool names the agent can use from this server.
39
+ Empty list or omitted = all tools available.
40
+
41
+ Examples:
42
+ >>> # No restrictions
43
+ >>> config: MCPServerConfig = {}
44
+
45
+ >>> # Mount restrictions only
46
+ >>> config: MCPServerConfig = {"roots": ["/workspace/data"]}
47
+
48
+ >>> # Tool whitelist only
49
+ >>> config: MCPServerConfig = {"tool_whitelist": ["read_file", "write_file"]}
50
+
51
+ >>> # Both restrictions
52
+ >>> config: MCPServerConfig = {
53
+ ... "roots": ["/workspace/data"],
54
+ ... "tool_whitelist": ["read_file"]
55
+ ... }
56
+ """
57
+
58
+ roots: list[str]
59
+ tool_whitelist: list[str]
60
+
61
+
30
62
  @dataclass
31
63
  class AgentOutput:
32
64
  spec: ArtifactSpec
@@ -67,7 +99,7 @@ class Agent(metaclass=AutoTracedMeta):
67
99
  self.engines: list[EngineComponent] = []
68
100
  self.best_of_n: int = 1
69
101
  self.best_of_score: Callable[[EvalResult], float] | None = None
70
- self.max_concurrency: int = 1
102
+ self.max_concurrency: int = 2
71
103
  self._semaphore = asyncio.Semaphore(self.max_concurrency)
72
104
  self.calls_func: Callable[..., Any] | None = None
73
105
  self.tools: set[Callable[..., Any]] = set()
@@ -77,6 +109,9 @@ class Agent(metaclass=AutoTracedMeta):
77
109
  self.prevent_self_trigger: bool = True # T065: Prevent infinite feedback loops
78
110
  # MCP integration
79
111
  self.mcp_server_names: set[str] = set()
112
+ self.mcp_mount_points: list[str] = [] # Deprecated: Use mcp_server_mounts instead
113
+ self.mcp_server_mounts: dict[str, list[str]] = {} # Server-specific mount points
114
+ self.tool_whitelist: list[str] | None = None
80
115
 
81
116
  @property
82
117
  def identity(self) -> AgentIdentity:
@@ -137,15 +172,30 @@ class Agent(metaclass=AutoTracedMeta):
137
172
  # Get the MCP manager from orchestrator
138
173
  manager = self._orchestrator.get_mcp_manager()
139
174
 
140
- # Import tool wrapper
141
-
142
175
  # Fetch tools from all assigned servers
143
176
  tools_dict = await manager.get_tools_for_agent(
144
177
  agent_id=self.name,
145
178
  run_id=ctx.task_id,
146
179
  server_names=self.mcp_server_names,
180
+ server_mounts=self.mcp_server_mounts, # Pass server-specific mounts
147
181
  )
148
182
 
183
+ # Whitelisting logic
184
+ tool_whitelist = self.tool_whitelist
185
+ if (
186
+ tool_whitelist is not None
187
+ and isinstance(tool_whitelist, list)
188
+ and len(tool_whitelist) > 0
189
+ ):
190
+ filtered_tools: dict[str, Any] = {}
191
+ for tool_key, tool_entry in tools_dict.items():
192
+ if isinstance(tool_entry, dict):
193
+ original_name = tool_entry.get("original_name", None)
194
+ if original_name is not None and original_name in tool_whitelist:
195
+ filtered_tools[tool_key] = tool_entry
196
+
197
+ tools_dict = filtered_tools
198
+
149
199
  # Convert to DSPy tool callables
150
200
  dspy_tools = []
151
201
  for namespaced_name, tool_info in tools_dict.items():
@@ -630,30 +680,103 @@ class AgentBuilder:
630
680
  self._agent.tools.update(funcs)
631
681
  return self
632
682
 
633
- def with_mcps(self, server_names: Iterable[str]) -> AgentBuilder:
634
- """Assign MCP servers to this agent.
635
-
636
- Architecture Decision: AD001 - Two-Level Architecture
637
- Agents reference servers registered at orchestrator level.
638
-
639
- Args:
640
- server_names: Names of MCP servers this agent should use
641
-
642
- Returns:
643
- self for method chaining
644
-
645
- Raises:
646
- ValueError: If any server name is not registered with orchestrator
647
-
648
- Example:
649
- >>> agent = (
650
- ... orchestrator.agent("file_agent")
651
- ... .with_mcps(["filesystem", "github"])
652
- ... .build()
653
- ... )
683
+ def with_mcps(
684
+ self,
685
+ servers: (
686
+ Iterable[str]
687
+ | dict[str, MCPServerConfig | list[str]] # Support both new and old format
688
+ | list[str | dict[str, MCPServerConfig | list[str]]]
689
+ ),
690
+ ) -> AgentBuilder:
691
+ """Assign MCP servers to this agent with optional server-specific mount points.
692
+
693
+ Architecture Decision: AD001 - Two-Level Architecture
694
+ Agents reference servers registered at orchestrator level.
695
+
696
+ Args:
697
+ servers: One of:
698
+ - List of server names (strings) - no specific mounts
699
+ - Dict mapping server names to MCPServerConfig or list[str] (backward compatible)
700
+ - Mixed list of strings and dicts for flexibility
701
+
702
+ Returns:
703
+ self for method chaining
704
+
705
+ Raises:
706
+ ValueError: If any server name is not registered with orchestrator
707
+
708
+ Examples:
709
+ >>> # Simple: no mount restrictions
710
+ >>> agent.with_mcps(["filesystem", "github"])
711
+
712
+ >>> # New format: Server-specific config with roots and tool whitelist
713
+ >>> agent.with_mcps({
714
+ ... "filesystem": {"roots": ["/workspace/dir/data"], "tool_whitelist": ["read_file"]},
715
+ ... "github": {} # No restrictions for github
716
+ ... })
717
+
718
+ >>> # Old format: Direct list (backward compatible)
719
+ >>> agent.with_mcps({
720
+ ... "filesystem": ["/workspace/dir/data"], # Old format still works
721
+ ... })
722
+
723
+ >>> # Mixed: backward compatible
724
+ >>> agent.with_mcps([
725
+ ... "github", # No mounts
726
+ ... {"filesystem": {"roots": ["mount1", "mount2"] } }
727
+ ```
728
+ ... ])
654
729
  """
655
- # Convert to set for efficient lookup
656
- server_set = set(server_names)
730
+ # Parse input into server_names and mounts
731
+ server_set: set[str] = set()
732
+ server_mounts: dict[str, list[str]] = {}
733
+ whitelist = None
734
+
735
+ if isinstance(servers, dict):
736
+ # Dict format: supports both old and new formats
737
+ # Old: {"server": ["/path1", "/path2"]}
738
+ # New: {"server": {"roots": ["/path1"], "tool_whitelist": ["tool1"]}}
739
+ for server_name, server_config in servers.items():
740
+ server_set.add(server_name)
741
+
742
+ # Check if it's the old format (direct list) or new format (MCPServerConfig dict)
743
+ if isinstance(server_config, list):
744
+ # Old format: direct list of paths (backward compatibility)
745
+ if len(server_config) > 0:
746
+ server_mounts[server_name] = list(server_config)
747
+ elif isinstance(server_config, dict):
748
+ # New format: MCPServerConfig with optional roots and tool_whitelist
749
+ mounts = server_config.get("roots", None)
750
+ if mounts is not None and isinstance(mounts, list) and len(mounts) > 0:
751
+ server_mounts[server_name] = list(mounts)
752
+
753
+ config_whitelist = server_config.get("tool_whitelist", None)
754
+ if (
755
+ config_whitelist is not None
756
+ and isinstance(config_whitelist, list)
757
+ and len(config_whitelist) > 0
758
+ ):
759
+ whitelist = config_whitelist
760
+ elif isinstance(servers, list):
761
+ # List format: can be mixed
762
+ for item in servers:
763
+ if isinstance(item, str):
764
+ # Simple server name
765
+ server_set.add(item)
766
+ elif isinstance(item, dict):
767
+ # Dict with mounts
768
+ for server_name, mounts in item.items():
769
+ server_set.add(server_name)
770
+ if mounts:
771
+ server_mounts[server_name] = list(mounts)
772
+ else:
773
+ raise TypeError(
774
+ f"Invalid server specification: {item}. "
775
+ f"Expected string or dict, got {type(item).__name__}"
776
+ )
777
+ else:
778
+ # Assume it's an iterable of strings (backward compatibility)
779
+ server_set = set(servers)
657
780
 
658
781
  # Validate all servers exist in orchestrator
659
782
  registered_servers = set(self._orchestrator._mcp_configs.keys())
@@ -669,6 +792,61 @@ class AgentBuilder:
669
792
 
670
793
  # Store in agent
671
794
  self._agent.mcp_server_names = server_set
795
+ self._agent.mcp_server_mounts = server_mounts
796
+ self._agent.tool_whitelist = whitelist
797
+
798
+ return self
799
+
800
+ def mount(self, paths: str | list[str], *, validate: bool = False) -> AgentBuilder:
801
+ """Mount agent in specific directories for MCP root access.
802
+
803
+ .. deprecated:: 0.2.0
804
+ Use `.with_mcps({"server_name": ["/path"]})` instead for server-specific mounts.
805
+ This method applies mounts globally to all MCP servers.
806
+
807
+ This sets the filesystem roots that MCP servers will operate under for this agent.
808
+ Paths are cumulative across multiple calls.
809
+
810
+ Args:
811
+ paths: Single path or list of paths to mount
812
+ validate: If True, validate that paths exist (default: False)
813
+
814
+ Returns:
815
+ AgentBuilder for method chaining
816
+
817
+ Example:
818
+ >>> # Old way (deprecated)
819
+ >>> agent.with_mcps(["filesystem"]).mount("/workspace/src")
820
+ >>>
821
+ >>> # New way (recommended)
822
+ >>> agent.with_mcps({"filesystem": ["/workspace/src"]})
823
+ """
824
+ import warnings
825
+
826
+ warnings.warn(
827
+ "Agent.mount() is deprecated. Use .with_mcps({'server': ['/path']}) "
828
+ "for server-specific mounts instead.",
829
+ DeprecationWarning,
830
+ stacklevel=2,
831
+ )
832
+
833
+ if isinstance(paths, str):
834
+ paths = [paths]
835
+ if validate:
836
+ from pathlib import Path
837
+
838
+ for path in paths:
839
+ if not Path(path).exists():
840
+ raise ValueError(f"Mount path does not exist: {path}")
841
+
842
+ # Add to agent's mount points (cumulative) - for backward compatibility
843
+ self._agent.mcp_mount_points.extend(paths)
844
+
845
+ # Also add to all configured servers for backward compatibility
846
+ for server_name in self._agent.mcp_server_names:
847
+ if server_name not in self._agent.mcp_server_mounts:
848
+ self._agent.mcp_server_mounts[server_name] = []
849
+ self._agent.mcp_server_mounts[server_name].extend(paths)
672
850
 
673
851
  return self
674
852
 
@@ -124,15 +124,26 @@ class WebSocketManager:
124
124
  # Broadcast to all clients concurrently
125
125
  # Use return_exceptions=True to handle client failures gracefully
126
126
  # Use send_text() for FastAPI WebSocket (send JSON string as text)
127
+ # CRITICAL: Add timeout to prevent deadlock when client send buffer is full
127
128
  clients_list = list(self.clients) # Copy to avoid modification during iteration
128
- send_tasks = [client.send_text(message) for client in clients_list]
129
+
130
+ send_tasks = [
131
+ asyncio.wait_for(client.send_text(message), timeout=0.5) # 500ms timeout
132
+ for client in clients_list
133
+ ]
129
134
  results = await asyncio.gather(*send_tasks, return_exceptions=True)
130
135
 
131
136
  # Remove clients that failed to receive the message
132
137
  failed_clients = []
133
138
  for client, result in zip(clients_list, results, strict=False):
134
139
  if isinstance(result, Exception):
135
- logger.warning(f"Failed to send to client: {result}")
140
+ # Check if it's a timeout (backpressure) or other error
141
+ if isinstance(result, asyncio.TimeoutError):
142
+ logger.warning(
143
+ "Client send timeout (backpressure) - client is slow or disconnected, removing client"
144
+ )
145
+ else:
146
+ logger.warning(f"Failed to send to client: {result}")
136
147
  failed_clients.append(client)
137
148
 
138
149
  # Clean up failed clients
@@ -2,6 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import asyncio
5
6
  import json
6
7
  import os
7
8
  from collections import OrderedDict, defaultdict
@@ -562,6 +563,9 @@ class DSPyEngine(EngineComponent):
562
563
  stream_buffers[status_field] = []
563
564
  stream_sequence = 0 # Monotonic sequence for ordering
564
565
 
566
+ # Track background WebSocket broadcast tasks to prevent garbage collection
567
+ ws_broadcast_tasks: set[asyncio.Task] = set()
568
+
565
569
  formatter = theme_dict = styles = agent_label = None
566
570
  live_cm = nullcontext()
567
571
  overflow_mode = self.stream_vertical_overflow
@@ -607,7 +611,7 @@ class DSPyEngine(EngineComponent):
607
611
  stream_buffers[status_field].append(str(token) + "\n")
608
612
  display_data["status"] = "".join(stream_buffers[status_field])
609
613
 
610
- # Emit to WebSocket
614
+ # Emit to WebSocket (non-blocking to prevent deadlock)
611
615
  if ws_manager and token:
612
616
  try:
613
617
  event = StreamingOutputEvent(
@@ -621,10 +625,15 @@ class DSPyEngine(EngineComponent):
621
625
  sequence=stream_sequence,
622
626
  is_final=False,
623
627
  )
624
- await ws_manager.broadcast(event)
628
+ # Use create_task to avoid blocking the streaming loop
629
+ task = asyncio.create_task(ws_manager.broadcast(event))
630
+ ws_broadcast_tasks.add(task)
631
+ task.add_done_callback(ws_broadcast_tasks.discard)
625
632
  stream_sequence += 1
626
633
  except Exception as e:
627
634
  logger.warning(f"Failed to emit streaming event: {e}")
635
+ else:
636
+ logger.exception("NO WS_MANAGER PRESENT!!!!")
628
637
 
629
638
  if formatter is not None:
630
639
  _refresh_panel()
@@ -643,7 +652,7 @@ class DSPyEngine(EngineComponent):
643
652
  stream_buffers[buffer_key]
644
653
  )
645
654
 
646
- # Emit to WebSocket
655
+ # Emit to WebSocket (non-blocking to prevent deadlock)
647
656
  if ws_manager:
648
657
  logger.info(
649
658
  f"[STREAMING] Emitting StreamResponse token='{token}', sequence={stream_sequence}"
@@ -660,7 +669,10 @@ class DSPyEngine(EngineComponent):
660
669
  sequence=stream_sequence,
661
670
  is_final=False,
662
671
  )
663
- await ws_manager.broadcast(event)
672
+ # Use create_task to avoid blocking the streaming loop
673
+ task = asyncio.create_task(ws_manager.broadcast(event))
674
+ ws_broadcast_tasks.add(task)
675
+ task.add_done_callback(ws_broadcast_tasks.discard)
664
676
  stream_sequence += 1
665
677
  except Exception as e:
666
678
  logger.warning(f"Failed to emit streaming event: {e}")
@@ -690,7 +702,7 @@ class DSPyEngine(EngineComponent):
690
702
  stream_buffers[status_field].append(str(token))
691
703
  display_data["status"] = "".join(stream_buffers[status_field])
692
704
 
693
- # Emit to WebSocket
705
+ # Emit to WebSocket (non-blocking to prevent deadlock)
694
706
  if ws_manager and token:
695
707
  try:
696
708
  event = StreamingOutputEvent(
@@ -704,7 +716,10 @@ class DSPyEngine(EngineComponent):
704
716
  sequence=stream_sequence,
705
717
  is_final=False,
706
718
  )
707
- await ws_manager.broadcast(event)
719
+ # Use create_task to avoid blocking the streaming loop
720
+ task = asyncio.create_task(ws_manager.broadcast(event))
721
+ ws_broadcast_tasks.add(task)
722
+ task.add_done_callback(ws_broadcast_tasks.discard)
708
723
  stream_sequence += 1
709
724
  except Exception as e:
710
725
  logger.warning(f"Failed to emit streaming event: {e}")
@@ -716,7 +731,7 @@ class DSPyEngine(EngineComponent):
716
731
  if isinstance(value, dspy_mod.Prediction):
717
732
  final_result = value
718
733
 
719
- # Emit final streaming event
734
+ # Emit final streaming event (non-blocking to prevent deadlock)
720
735
  if ws_manager:
721
736
  try:
722
737
  event = StreamingOutputEvent(
@@ -730,7 +745,10 @@ class DSPyEngine(EngineComponent):
730
745
  sequence=stream_sequence,
731
746
  is_final=True, # Mark as final
732
747
  )
733
- await ws_manager.broadcast(event)
748
+ # Use create_task to avoid blocking the streaming loop
749
+ task = asyncio.create_task(ws_manager.broadcast(event))
750
+ ws_broadcast_tasks.add(task)
751
+ task.add_done_callback(ws_broadcast_tasks.discard)
734
752
  event = StreamingOutputEvent(
735
753
  correlation_id=str(ctx.correlation_id)
736
754
  if ctx and ctx.correlation_id
@@ -742,7 +760,10 @@ class DSPyEngine(EngineComponent):
742
760
  sequence=stream_sequence,
743
761
  is_final=True, # Mark as final
744
762
  )
745
- await ws_manager.broadcast(event)
763
+ # Use create_task to avoid blocking the streaming loop
764
+ task = asyncio.create_task(ws_manager.broadcast(event))
765
+ ws_broadcast_tasks.add(task)
766
+ task.add_done_callback(ws_broadcast_tasks.discard)
746
767
  except Exception as e:
747
768
  logger.warning(f"Failed to emit final streaming event: {e}")
748
769
 
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "flock-ui",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "flock-ui",
9
- "version": "0.1.3",
9
+ "version": "0.1.4",
10
10
  "license": "ISC",
11
11
  "dependencies": {
12
12
  "@types/dagre": "^0.7.53",
@@ -147,7 +147,6 @@
147
147
  "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==",
148
148
  "dev": true,
149
149
  "license": "MIT",
150
- "peer": true,
151
150
  "dependencies": {
152
151
  "@babel/code-frame": "^7.27.1",
153
152
  "@babel/generator": "^7.28.3",
@@ -356,7 +355,6 @@
356
355
  "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz",
357
356
  "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==",
358
357
  "license": "MIT",
359
- "peer": true,
360
358
  "engines": {
361
359
  "node": ">=6.9.0"
362
360
  }
@@ -507,7 +505,6 @@
507
505
  }
508
506
  ],
509
507
  "license": "MIT",
510
- "peer": true,
511
508
  "engines": {
512
509
  "node": ">=18"
513
510
  },
@@ -554,7 +551,6 @@
554
551
  }
555
552
  ],
556
553
  "license": "MIT",
557
- "peer": true,
558
554
  "engines": {
559
555
  "node": ">=18"
560
556
  }
@@ -1507,7 +1503,8 @@
1507
1503
  "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
1508
1504
  "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==",
1509
1505
  "dev": true,
1510
- "license": "MIT"
1506
+ "license": "MIT",
1507
+ "peer": true
1511
1508
  },
1512
1509
  "node_modules/@types/babel__core": {
1513
1510
  "version": "7.20.5",
@@ -1638,7 +1635,6 @@
1638
1635
  "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.0.tgz",
1639
1636
  "integrity": "sha512-1LOH8xovvsKsCBq1wnT4ntDUdCJKmnEakhsuoUSy6ExlHCkGP2hqnatagYTgFk6oeL0VU31u7SNjunPN+GchtA==",
1640
1637
  "license": "MIT",
1641
- "peer": true,
1642
1638
  "dependencies": {
1643
1639
  "csstype": "^3.0.2"
1644
1640
  }
@@ -1649,7 +1645,6 @@
1649
1645
  "integrity": "sha512-brtBs0MnE9SMx7px208g39lRmC5uHZs96caOJfTjFcYSLHNamvaSMfJNagChVNkup2SdtOxKX1FDBkRSJe1ZAg==",
1650
1646
  "dev": true,
1651
1647
  "license": "MIT",
1652
- "peer": true,
1653
1648
  "peerDependencies": {
1654
1649
  "@types/react": "^19.2.0"
1655
1650
  }
@@ -1845,7 +1840,6 @@
1845
1840
  "integrity": "sha512-hGISOaP18plkzbWEcP/QvtRW1xDXF2+96HbEX6byqQhAUbiS5oH6/9JwW+QsQCIYON2bI6QZBF+2PvOmrRZ9wA==",
1846
1841
  "dev": true,
1847
1842
  "license": "MIT",
1848
- "peer": true,
1849
1843
  "dependencies": {
1850
1844
  "@vitest/utils": "3.2.4",
1851
1845
  "fflate": "^0.8.2",
@@ -1963,6 +1957,7 @@
1963
1957
  "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
1964
1958
  "dev": true,
1965
1959
  "license": "MIT",
1960
+ "peer": true,
1966
1961
  "engines": {
1967
1962
  "node": ">=10"
1968
1963
  },
@@ -2066,7 +2061,6 @@
2066
2061
  }
2067
2062
  ],
2068
2063
  "license": "MIT",
2069
- "peer": true,
2070
2064
  "dependencies": {
2071
2065
  "baseline-browser-mapping": "^2.8.9",
2072
2066
  "caniuse-lite": "^1.0.30001746",
@@ -2295,7 +2289,6 @@
2295
2289
  "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
2296
2290
  "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
2297
2291
  "license": "ISC",
2298
- "peer": true,
2299
2292
  "engines": {
2300
2293
  "node": ">=12"
2301
2294
  }
@@ -2418,7 +2411,8 @@
2418
2411
  "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
2419
2412
  "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==",
2420
2413
  "dev": true,
2421
- "license": "MIT"
2414
+ "license": "MIT",
2415
+ "peer": true
2422
2416
  },
2423
2417
  "node_modules/eastasianwidth": {
2424
2418
  "version": "0.2.0",
@@ -2840,7 +2834,6 @@
2840
2834
  "integrity": "sha512-lIHeR1qlIRrIN5VMccd8tI2Sgw6ieYXSVktcSHaNe3Z5nE/tcPQYQWOq00wxMvYOsz+73eAkNenVvmPC6bba9A==",
2841
2835
  "dev": true,
2842
2836
  "license": "MIT",
2843
- "peer": true,
2844
2837
  "dependencies": {
2845
2838
  "@asamuzakjp/dom-selector": "^6.5.4",
2846
2839
  "cssstyle": "^5.3.0",
@@ -2942,6 +2935,7 @@
2942
2935
  "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
2943
2936
  "dev": true,
2944
2937
  "license": "MIT",
2938
+ "peer": true,
2945
2939
  "bin": {
2946
2940
  "lz-string": "bin/bin.js"
2947
2941
  }
@@ -3186,7 +3180,6 @@
3186
3180
  "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
3187
3181
  "dev": true,
3188
3182
  "license": "MIT",
3189
- "peer": true,
3190
3183
  "engines": {
3191
3184
  "node": ">=12"
3192
3185
  },
@@ -3214,7 +3207,6 @@
3214
3207
  }
3215
3208
  ],
3216
3209
  "license": "MIT",
3217
- "peer": true,
3218
3210
  "dependencies": {
3219
3211
  "nanoid": "^3.3.11",
3220
3212
  "picocolors": "^1.1.1",
@@ -3230,6 +3222,7 @@
3230
3222
  "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
3231
3223
  "dev": true,
3232
3224
  "license": "MIT",
3225
+ "peer": true,
3233
3226
  "dependencies": {
3234
3227
  "ansi-regex": "^5.0.1",
3235
3228
  "ansi-styles": "^5.0.0",
@@ -3281,7 +3274,6 @@
3281
3274
  "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz",
3282
3275
  "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==",
3283
3276
  "license": "MIT",
3284
- "peer": true,
3285
3277
  "engines": {
3286
3278
  "node": ">=0.10.0"
3287
3279
  }
@@ -3291,7 +3283,6 @@
3291
3283
  "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz",
3292
3284
  "integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==",
3293
3285
  "license": "MIT",
3294
- "peer": true,
3295
3286
  "dependencies": {
3296
3287
  "scheduler": "^0.27.0"
3297
3288
  },
@@ -3318,7 +3309,8 @@
3318
3309
  "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
3319
3310
  "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
3320
3311
  "dev": true,
3321
- "license": "MIT"
3312
+ "license": "MIT",
3313
+ "peer": true
3322
3314
  },
3323
3315
  "node_modules/react-refresh": {
3324
3316
  "version": "0.17.0",
@@ -3907,7 +3899,6 @@
3907
3899
  "integrity": "sha512-4nVGliEpxmhCL8DslSAUdxlB6+SMrhB0a1v5ijlh1xB1nEPuy1mxaHxysVucLHuWryAxLWg6a5ei+U4TLn/rFg==",
3908
3900
  "dev": true,
3909
3901
  "license": "MIT",
3910
- "peer": true,
3911
3902
  "dependencies": {
3912
3903
  "esbuild": "^0.25.0",
3913
3904
  "fdir": "^6.5.0",
@@ -4006,7 +3997,6 @@
4006
3997
  "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==",
4007
3998
  "dev": true,
4008
3999
  "license": "MIT",
4009
- "peer": true,
4010
4000
  "dependencies": {
4011
4001
  "@types/chai": "^5.2.2",
4012
4002
  "@vitest/expect": "3.2.4",
flock/mcp/client.py CHANGED
@@ -153,6 +153,9 @@ class FlockMCPClient(BaseModel, ABC):
153
153
  class _SessionProxy:
154
154
  def __init__(self, client: Any):
155
155
  self._client = client
156
+ # Check if roots are specified in the config
157
+ if not self.current_roots and self.config.connection_config.mount_points:
158
+ self.current_roots = self.config.connection_config.mount_points
156
159
 
157
160
  def __getattr__(self, name: str):
158
161
  # return an async function that auto-reconnects, then calls through.
@@ -366,11 +369,20 @@ class FlockMCPClient(BaseModel, ABC):
366
369
  return result
367
370
 
368
371
  async def _get_tools_internal() -> list[FlockMCPTool]:
369
- # TODO: Crash
370
372
  response: ListToolsResult = await self.session.list_tools()
371
373
  flock_tools = []
374
+ tool_whitelist = self.config.feature_config.tool_whitelist
372
375
 
373
376
  for tool in response.tools:
377
+ # Skip tools that are not whitelisted
378
+ # IF a whitelist is present
379
+ if (
380
+ tool_whitelist is not None
381
+ and isinstance(tool_whitelist, list)
382
+ and len(tool_whitelist) > 0
383
+ and tool.name not in tool_whitelist
384
+ ):
385
+ continue
374
386
  converted_tool = FlockMCPTool.from_mcp_tool(
375
387
  tool,
376
388
  agent_id=agent_id,
@@ -422,6 +434,14 @@ class FlockMCPClient(BaseModel, ABC):
422
434
  async with self.lock:
423
435
  return self.current_roots
424
436
 
437
+ def _get_roots_no_lock(self) -> list[MCPRoot] | None:
438
+ """Get the currently set roots without acquiring a lock.
439
+
440
+ WARNING: Only use this internally when you're sure there's no race condition.
441
+ This is primarily for use during initialization when the lock is already held.
442
+ """
443
+ return self.current_roots
444
+
425
445
  async def set_roots(self, new_roots: list[MCPRoot]) -> None:
426
446
  """Set the current roots of the client.
427
447
 
@@ -599,6 +619,10 @@ class FlockMCPClient(BaseModel, ABC):
599
619
  # 2) if we already know our current roots, notify the server
600
620
  # so that it will follow up with a ListRootsRequest
601
621
  if self.current_roots and self.config.feature_config.roots_enabled:
622
+ logger.debug(
623
+ f"Notifying server '{self.config.name}' of {len(self.current_roots)} root(s): "
624
+ f"{[r.uri for r in self.current_roots]}"
625
+ )
602
626
  await self.client_session.send_roots_list_changed()
603
627
 
604
628
  # 3) Tell the server, what logging level we would like to use
flock/mcp/config.py CHANGED
@@ -271,7 +271,7 @@ class FlockMCPFeatureConfiguration(BaseModel):
271
271
  default=None,
272
272
  description="Whitelist of tool names that are enabled for this MCP server. "
273
273
  "If provided, only tools with names in this list will be available "
274
- "from this server. Used in conjunction with allow_all_tools setting. "
274
+ "from this server."
275
275
  "Note: Agent-level tool filtering is generally preferred over "
276
276
  "server-level filtering for better granular control.",
277
277
  )
@@ -313,15 +313,6 @@ class FlockMCPConfiguration(BaseModel):
313
313
 
314
314
  name: str = Field(..., description="Name of the server the client connects to.")
315
315
 
316
- allow_all_tools: bool = Field(
317
- default=True,
318
- description="Whether to allow usage of all tools from this MCP server. "
319
- "When True (default), all tools are available unless restricted "
320
- "by tool_whitelist. When False, tool access is controlled entirely "
321
- "by tool_whitelist (if provided). Setting to False with no whitelist "
322
- "will block all tools from this server.",
323
- )
324
-
325
316
  connection_config: FlockMCPConnectionConfiguration = Field(
326
317
  ..., description="MCP Connection Configuration for a client."
327
318
  )
flock/mcp/manager.py CHANGED
@@ -68,7 +68,9 @@ class FlockMCPClientManager:
68
68
  self._pool: dict[tuple[str, str], dict[str, FlockMCPClient]] = {}
69
69
  self._lock = asyncio.Lock()
70
70
 
71
- async def get_client(self, server_name: str, agent_id: str, run_id: str) -> FlockMCPClient:
71
+ async def get_client(
72
+ self, server_name: str, agent_id: str, run_id: str, mount_points: list[str] | None = None
73
+ ) -> FlockMCPClient:
72
74
  """Get or create an MCP client for the given context.
73
75
 
74
76
  Architecture Decision: AD005 - Lazy Connection Establishment
@@ -105,6 +107,24 @@ class FlockMCPClientManager:
105
107
  f"(agent={agent_id}, run={run_id})"
106
108
  )
107
109
  config = self._configs[server_name]
110
+ # MCP-ROOTS: Override mount points if provided
111
+ if mount_points:
112
+ from flock.mcp.types import MCPRoot
113
+
114
+ # Create MCPRoot objects from paths
115
+ roots = [
116
+ MCPRoot(uri=f"file://{path}", name=path.split("/")[-1])
117
+ for path in mount_points
118
+ ]
119
+ logger.info(
120
+ f"Setting {len(roots)} mount point(s) for server '{server_name}' "
121
+ f"(agent={agent_id}, run={run_id}): {[r.uri for r in roots]}"
122
+ )
123
+ # Clone config with new mount points
124
+ from copy import deepcopy
125
+
126
+ config = deepcopy(config)
127
+ config.connection_config.mount_points = roots
108
128
 
109
129
  # Instantiate the correct concrete client class based on transport type
110
130
  # Lazy import to avoid requiring all dependencies
@@ -145,7 +165,11 @@ class FlockMCPClientManager:
145
165
  return self._pool[key][server_name]
146
166
 
147
167
  async def get_tools_for_agent(
148
- self, agent_id: str, run_id: str, server_names: set[str]
168
+ self,
169
+ agent_id: str,
170
+ run_id: str,
171
+ server_names: set[str],
172
+ server_mounts: dict[str, list[str]] | None = None,
149
173
  ) -> dict[str, Any]:
150
174
  """Get all tools from specified servers for an agent.
151
175
 
@@ -156,6 +180,7 @@ class FlockMCPClientManager:
156
180
  agent_id: Agent requesting tools
157
181
  run_id: Current run identifier
158
182
  server_names: Set of MCP server names to fetch tools from
183
+ server_mounts: Optional dict mapping server names to mount points
159
184
 
160
185
  Returns:
161
186
  Dictionary mapping namespaced tool names to tool definitions
@@ -166,10 +191,16 @@ class FlockMCPClientManager:
166
191
  other servers rather than failing the entire operation.
167
192
  """
168
193
  tools = {}
194
+ server_mounts = server_mounts or {}
169
195
 
170
196
  for server_name in server_names:
171
197
  try:
172
- client = await self.get_client(server_name, agent_id, run_id)
198
+ # Get mount points specific to this server
199
+ mount_points = server_mounts.get(server_name)
200
+
201
+ client = await self.get_client(
202
+ server_name, agent_id, run_id, mount_points=mount_points
203
+ )
173
204
  server_tools = await client.get_tools(agent_id, run_id)
174
205
 
175
206
  # Apply namespacing: AD003
@@ -64,7 +64,10 @@ async def default_list_roots_callback(
64
64
  ) -> ListRootsResult | ErrorData:
65
65
  """Default List Roots Callback."""
66
66
  if associated_client.config.feature_config.roots_enabled:
67
- current_roots = await associated_client.get_roots()
67
+ # Use lock-free version to avoid deadlock during initialization
68
+ # when the lock is already held by _connect()
69
+ current_roots = associated_client._get_roots_no_lock()
70
+ logger.debug(f"Server requested list/roots. Sending: {current_roots}")
68
71
  return ListRootsResult(roots=current_roots)
69
72
  return ErrorData(code=INVALID_REQUEST, message="List roots not supported.")
70
73
 
flock/orchestrator.py CHANGED
@@ -7,7 +7,7 @@ import os
7
7
  from asyncio import Task
8
8
  from collections.abc import Iterable, Mapping, Sequence
9
9
  from contextlib import asynccontextmanager
10
- from typing import TYPE_CHECKING, Any
10
+ from typing import TYPE_CHECKING, Any, AsyncGenerator
11
11
  from uuid import uuid4
12
12
 
13
13
  from opentelemetry import trace
@@ -193,8 +193,8 @@ class Flock(metaclass=AutoTracedMeta):
193
193
  enable_prompts_feature: bool = True,
194
194
  enable_sampling_feature: bool = True,
195
195
  enable_roots_feature: bool = True,
196
+ mount_points: list[str] | None = None,
196
197
  tool_whitelist: list[str] | None = None,
197
- allow_all_tools: bool = True,
198
198
  read_timeout_seconds: float = 300,
199
199
  max_retries: int = 3,
200
200
  **kwargs,
@@ -212,7 +212,6 @@ class Flock(metaclass=AutoTracedMeta):
212
212
  enable_sampling_feature: Enable LLM sampling requests
213
213
  enable_roots_feature: Enable filesystem roots
214
214
  tool_whitelist: Optional list of tool names to allow
215
- allow_all_tools: If True, allow all tools (subject to whitelist)
216
215
  read_timeout_seconds: Timeout for server communications
217
216
  max_retries: Connection retry attempts
218
217
 
@@ -244,12 +243,43 @@ class Flock(metaclass=AutoTracedMeta):
244
243
  else:
245
244
  transport_type = "custom"
246
245
 
246
+ mcp_roots = None
247
+ if mount_points:
248
+ from pathlib import Path as PathLib
249
+
250
+ from flock.mcp.types import MCPRoot
251
+
252
+ mcp_roots = []
253
+ for path in mount_points:
254
+ # Normalize the path
255
+ if path.startswith("file://"):
256
+ # Already a file URI
257
+ uri = path
258
+ # Extract path from URI for name
259
+ path_str = path.replace("file://", "")
260
+ # the test:// path-prefix is used by testing servers such as the mcp-everything server.
261
+ elif path.startswith("test://"):
262
+ # Already a test URI
263
+ uri = path
264
+ # Extract path from URI for name
265
+ path_str = path.replace("test://", "")
266
+ else:
267
+ # Convert to absolute path and create URI
268
+ abs_path = PathLib(path).resolve()
269
+ uri = f"file://{abs_path}"
270
+ path_str = str(abs_path)
271
+
272
+ # Extract a meaningful name (last component of path)
273
+ name = PathLib(path_str).name or path_str.rstrip("/").split("/")[-1] or "root"
274
+ mcp_roots.append(MCPRoot(uri=uri, name=name))
275
+
247
276
  # Build configuration
248
277
  connection_config = FlockMCPConnectionConfiguration(
249
278
  max_retries=max_retries,
250
279
  connection_parameters=connection_params,
251
280
  transport_type=transport_type,
252
281
  read_timeout_seconds=read_timeout_seconds,
282
+ mount_points=mcp_roots,
253
283
  )
254
284
 
255
285
  feature_config = FlockMCPFeatureConfiguration(
@@ -262,7 +292,6 @@ class Flock(metaclass=AutoTracedMeta):
262
292
 
263
293
  mcp_config = FlockMCPConfiguration(
264
294
  name=name,
265
- allow_all_tools=allow_all_tools,
266
295
  connection_config=connection_config,
267
296
  feature_config=feature_config,
268
297
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flock-core
3
- Version: 0.5.0b62
3
+ Version: 0.5.0b65
4
4
  Summary: Add your description here
5
5
  Author-email: Andre Ratzenberger <andre.ratzenberger@whiteduck.de>
6
6
  License-File: LICENSE
@@ -9,6 +9,7 @@ Requires-Dist: devtools>=0.12.2
9
9
  Requires-Dist: dspy==3.0.0
10
10
  Requires-Dist: duckdb>=1.1.0
11
11
  Requires-Dist: fastapi>=0.117.1
12
+ Requires-Dist: hanging-threads>=2.0.7
12
13
  Requires-Dist: httpx>=0.28.1
13
14
  Requires-Dist: litellm==1.75.3
14
15
  Requires-Dist: loguru>=0.7.3
@@ -178,7 +179,7 @@ agent.consumes(BugReport).publishes(BugDiagnosis)
178
179
  ```
179
180
 
180
181
  <p align="center">
181
- <img alt="Flock Banner" src="docs/img/bug_diagnosis.png" width="1000">
182
+ <img alt="Flock Banner" src="docs/assets/images/bug_diagnosis.png" width="1000">
182
183
  </p>
183
184
 
184
185
  **Why this matters:**
@@ -437,7 +438,7 @@ await flock.serve(dashboard=True)
437
438
  The dashboard provides comprehensive real-time visibility into your agent system with professional UI/UX:
438
439
 
439
440
  <p align="center">
440
- <img alt="Flock Agent View" src="docs/img/flock_ui_agent_view.png" width="1000">
441
+ <img alt="Flock Agent View" src="docs/assets/images/flock_ui_agent_view.png" width="1000">
441
442
  <i>Agent View: See agent communication patterns and message flows in real-time</i>
442
443
  </p>
443
444
 
@@ -448,7 +449,7 @@ The dashboard provides comprehensive real-time visibility into your agent system
448
449
  - **Blackboard View** - Messages as nodes with data transformations as edges
449
450
 
450
451
  <p align="center">
451
- <img alt="Flock Blackboard View" src="docs/img/flock_ui_blackboard_view.png" width="1000">
452
+ <img alt="Flock Blackboard View" src="docs/assets/images/flock_ui_blackboard_view.png" width="1000">
452
453
  <i>Blackboard View: Track data lineage and transformations across the system</i>
453
454
  </p>
454
455
 
@@ -488,7 +489,7 @@ The dashboard provides comprehensive real-time visibility into your agent system
488
489
  The dashboard includes a **Jaeger-style trace viewer** with 7 powerful visualization modes:
489
490
 
490
491
  <p align="center">
491
- <img alt="Trace Viewer" src="docs/img/trace_1.png" width="1000">
492
+ <img alt="Trace Viewer" src="docs/assets/images/trace_1.png" width="1000">
492
493
  <i>Trace Viewer: Timeline view showing span hierarchies and execution flow</i>
493
494
  </p>
494
495
 
@@ -511,7 +512,7 @@ The dashboard includes a **Jaeger-style trace viewer** with 7 powerful visualiza
511
512
  - **CSV Export** - Download query results for offline analysis
512
513
 
513
514
  <p align="center">
514
- <img alt="Trace Viewer" src="docs/img/trace_2.png" width="1000">
515
+ <img alt="Trace Viewer" src="docs/assets/images/trace_2.png" width="1000">
515
516
  <i>Trace Viewer: Dependency Analysis</i>
516
517
  </p>
517
518
 
@@ -565,7 +566,7 @@ AI: [queries DuckDB]
565
566
  **Why DuckDB?** 10-100x faster than SQLite for analytical queries. Zero configuration. AI agents can debug your AI agents.
566
567
 
567
568
  <p align="center">
568
- <img alt="Trace Viewer" src="docs/img/trace_3.png" width="1000">
569
+ <img alt="Trace Viewer" src="docs/assets/images/trace_3.png" width="1000">
569
570
  <i>Trace Viewer: DuckDB Query</i>
570
571
  </p>
571
572
 
@@ -677,83 +678,18 @@ All planned for v1.0
677
678
 
678
679
  ## Roadmap to 1.0
679
680
 
680
- We're not building a toy framework. We're building enterprise infrastructure for AI agents.
681
-
682
- **See [ROADMAP.md](ROADMAP.md) for the complete roadmap with detailed code examples.**
683
-
684
- ### Flock 1.0 - Q4 2025 Release
685
-
686
- **We're confident we can deliver all enterprise features by Q4 2025:**
687
-
688
- **🏢 Enterprise Persistence**
689
- - Redis and PostgreSQL backends for durable blackboard state
690
- - Agent crashes? State persists, agents resume automatically
691
- - Multi-region deployments with shared blackboard
692
- - SQL queries on artifact history for analytics and compliance
693
-
694
- **🔄 Advanced Error Handling**
695
- - Exponential backoff with jitter for transient failures
696
- - Dead letter queues for poison messages
697
- - Per-agent circuit breakers with auto-recovery
698
- - Full observability of all failure modes
699
-
700
- **🤝 Aggregation Patterns**
701
- - Map-reduce pattern for parallel processing → aggregation
702
- - Voting/consensus for multi-agent decision making
703
- - Best-result selection with custom scoring functions
704
-
705
- **📨 Kafka Event Backbone**
706
- - Event replay for debugging production issues in development
707
- - Time-travel debugging with checkpoint restoration
708
- - Immutable audit logs for regulatory compliance
709
- - Backfill new agents with historical data
710
-
711
- **☸️ Kubernetes-Native Deployment**
712
- - Helm charts for production deployments
713
- - Horizontal auto-scaling based on blackboard queue depth
714
- - Zero-downtime deployments with health checks
715
- - Production-grade readiness probes
716
-
717
- **🔐 OAuth/RBAC**
718
- - OAuth2/OIDC authentication for multi-tenant SaaS
719
- - API key authentication for programmatic access
720
- - Role-based access control with agent-level permissions
721
- - Complete audit trails for compliance (SOC2, HIPAA)
722
-
723
- **👤 Human-in-the-Loop**
724
- - Approval patterns for high-value transactions
725
- - Dashboard integration for pending approvals
726
- - Slack/email notifications with audit trails
727
- - Training mode with review-before-automation
728
-
729
- **🔀 Fan-Out/Fan-In Patterns**
730
- - Dynamic work distribution based on runtime data
731
- - Result collection and aggregation
732
- - Map-reduce over LLM operations
733
- - Sharding for horizontal scale
734
-
735
- **⏰ Time-Based Scheduling**
736
- - Cron-like triggers for periodic workflows
737
- - Sliding window patterns for real-time analytics
738
- - Hybrid event+time based triggers
739
- - SLA monitoring and data freshness checks
740
-
741
- ### Release Criteria for v1.0
742
-
743
- **v1.0 will ship when all of these are complete:**
744
- 1. ✅ Production persistence (Redis + Postgres backends stable)
745
- 2. ✅ Advanced error handling (retry, circuit breakers, DLQ working)
746
- 3. ✅ Aggregation patterns (map-reduce, voting, consensus implemented)
747
- 4. ✅ Kafka event backbone (replay and time-travel debugging)
748
- 5. ✅ Kubernetes native (Helm chart with auto-scaling)
749
- 6. ✅ Authentication (OAuth/OIDC + API key auth)
750
- 7. ✅ Human-in-the-loop (approval patterns implemented)
751
- 8. ✅ Fan-out/fan-in (distributed processing patterns)
752
- 9. ✅ Time-based scheduling (cron + sliding windows)
753
- 10. ✅ 85%+ test coverage (1000+ tests passing)
754
- 11. ✅ Production validation (deployed at 3+ companies)
755
-
756
- **Target Date:** Q4 2025
681
+ We're building enterprise infrastructure for AI agents and tracking the work publicly. Check [ROADMAP.md](ROADMAP.md) for deep dives and status updates.
682
+
683
+ ### 0.5.0 Beta (In Flight)
684
+ - **Core data & governance:** [#271](https://github.com/whiteducksoftware/flock/issues/271), [#274](https://github.com/whiteducksoftware/flock/issues/274), [#273](https://github.com/whiteducksoftware/flock/issues/273), [#281](https://github.com/whiteducksoftware/flock/issues/281)
685
+ - **Execution patterns & scheduling:** [#282](https://github.com/whiteducksoftware/flock/issues/282), [#283](https://github.com/whiteducksoftware/flock/issues/283)
686
+ - **REST access & integrations:** [#286](https://github.com/whiteducksoftware/flock/issues/286), [#287](https://github.com/whiteducksoftware/flock/issues/287), [#288](https://github.com/whiteducksoftware/flock/issues/288), [#289](https://github.com/whiteducksoftware/flock/issues/289), [#290](https://github.com/whiteducksoftware/flock/issues/290), [#291](https://github.com/whiteducksoftware/flock/issues/291), [#292](https://github.com/whiteducksoftware/flock/issues/292), [#293](https://github.com/whiteducksoftware/flock/issues/293)
687
+ - **Docs & onboarding:** [#270](https://github.com/whiteducksoftware/flock/issues/270), [#269](https://github.com/whiteducksoftware/flock/issues/269)
688
+
689
+ ### 1.0 Release Goals (Target Q4 2025)
690
+ - **Reliability & operations:** [#277](https://github.com/whiteducksoftware/flock/issues/277), [#278](https://github.com/whiteducksoftware/flock/issues/278), [#279](https://github.com/whiteducksoftware/flock/issues/279), [#294](https://github.com/whiteducksoftware/flock/issues/294)
691
+ - **Platform validation & quality:** [#275](https://github.com/whiteducksoftware/flock/issues/275), [#276](https://github.com/whiteducksoftware/flock/issues/276), [#284](https://github.com/whiteducksoftware/flock/issues/284), [#285](https://github.com/whiteducksoftware/flock/issues/285)
692
+ - **Security & access:** [#280](https://github.com/whiteducksoftware/flock/issues/280)
757
693
 
758
694
  ---
759
695
 
@@ -1,10 +1,10 @@
1
1
  flock/__init__.py,sha256=fvp4ltfaAGmYliShuTY_XVIpOUN6bMXbWiBnwb1NBoM,310
2
- flock/agent.py,sha256=gEELgq0Ab8TnaNLkkjvmR28kYZnLNO1yGhsu0CzfToE,31973
2
+ flock/agent.py,sha256=LHc2COiV_w0eKmGaq9IsTDaNg_gc6iTSKmGPk1FKbt0,39731
3
3
  flock/artifacts.py,sha256=Xnizu0V4Jbwd1yV_FhXjUZG8Em-5GYsLt7sMX0V2OKI,2222
4
4
  flock/cli.py,sha256=Ay_Z4KDd5wnnF1fLr0NqjMF7kRm_9X_1RXyv5GETDAY,2126
5
5
  flock/components.py,sha256=17vhNMHKc3VUruEbSdb9YNKcDziIe0coS9jpfWBmX4o,6259
6
6
  flock/examples.py,sha256=61xkD48yCW-aDrUXIrqrvxLtV8Vn4DrraQrHrrEUnWA,3530
7
- flock/orchestrator.py,sha256=fF7wteH6T_yAPpJDG7kSIkUe5ggJAZ9BIpP6Flz-ozA,33747
7
+ flock/orchestrator.py,sha256=QUFOrgOdMr_TofJnudXmlM6eYYafinrCcFUpKiV2gK4,34963
8
8
  flock/registry.py,sha256=s0-H-TMtOsDZiZQCc7T1tYiWQg3OZHn5T--jaI_INIc,4786
9
9
  flock/runtime.py,sha256=UG-38u578h628mSddBmyZn2VIzFQ0wlHCpCALFiScqA,8518
10
10
  flock/service.py,sha256=cfvcsqZw9xOsEggncZ08zmiDxFiZpxsBYDSji5OIQQ8,5256
@@ -18,12 +18,12 @@ flock/dashboard/collector.py,sha256=dF8uddDMpOSdxGkhDSAvRNNaABo-TfOceipf1SQmLSU,
18
18
  flock/dashboard/events.py,sha256=ujdmRJK-GQubrv43qfQ73dnrTj7g39VzBkWfmskJ0j8,5234
19
19
  flock/dashboard/launcher.py,sha256=zXWVpyLNxCIu6fJ2L2j2sJ4oDWTvkxhT4FWz7K6eooM,8122
20
20
  flock/dashboard/service.py,sha256=30_uUxDhvX1jfy02G4muNuzbcMH5TujY_G5aXWNQa8Q,32104
21
- flock/dashboard/websocket.py,sha256=RdJ7fhjNYJR8WHJ19wWdf9GEQtuKE14NmUpqm-QsLnA,9013
21
+ flock/dashboard/websocket.py,sha256=6piBJ92nSAeTgGFDUgAaQ8CthqThwRUC6aMUBB_2O6E,9508
22
22
  flock/engines/__init__.py,sha256=waNyObJ8PKCLFZL3WUFynxSK-V47m559P3Px-vl_OSc,124
23
- flock/engines/dspy_engine.py,sha256=Q2gPYLW_f8f-JuBYOMtjtoCZH8Fc447zWF8cHpJl4ew,34538
23
+ flock/engines/dspy_engine.py,sha256=mEhOKPsTnKN2BpnBv_cntqxH7hhg0Fv_QQuQneyDHPk,36176
24
24
  flock/frontend/README.md,sha256=OFdOItV8FGifmUDb694rV2xLC0vl1HlR5KBEtYv5AB0,25054
25
25
  flock/frontend/index.html,sha256=BFg1VR_YVAJ_MGN16xa7sT6wTGwtFYUhfJhGuKv89VM,312
26
- flock/frontend/package-lock.json,sha256=F-KmPhq6IbHXzxtmHL0S7dt6DGs4fWrOkrfKeXaSx6U,150998
26
+ flock/frontend/package-lock.json,sha256=PKQaw0nU0PRUWiJ3OIQyS9v-vphokMbUkxoPdgdkiZo,150798
27
27
  flock/frontend/package.json,sha256=8cpupeJo0xlJgHiEHpBYCU-nHFiiNGJ73NfZLr5-FSg,1258
28
28
  flock/frontend/tsconfig.json,sha256=B9p9jXohg_jrCZAq5_yIHvznpeXHiHQkwUZrVE2oMRA,705
29
29
  flock/frontend/tsconfig.node.json,sha256=u5_YWSqeNkZBRBIZ8Q2E2q6bospcyF23mO-taRO7glc,233
@@ -152,9 +152,9 @@ flock/logging/telemetry_exporter/duckdb_exporter.py,sha256=MoFFsAb-twZF9ZAB0s8TO
152
152
  flock/logging/telemetry_exporter/file_exporter.py,sha256=uTgQZSKfbLjzypirBbiVnccaKBDkiRzhFvUdV26pKEY,3126
153
153
  flock/logging/telemetry_exporter/sqlite_exporter.py,sha256=754G8s4X-JHHpCdoNydJnPX4NZijZk9URutfPWFIT7s,3479
154
154
  flock/mcp/__init__.py,sha256=QuANhTz09E2fFhtp2yJ716WxXKwEpCMTGqHj54VRs2Q,2512
155
- flock/mcp/client.py,sha256=QcxSdj3iUsWSsOR0l2IpZdr_dyvaqslmyuSk_LZEamQ,24610
156
- flock/mcp/config.py,sha256=QJcP_r8uE_1mdxMIUop7ca-CtJuEZwUXgUqCjR_a2UU,15326
157
- flock/mcp/manager.py,sha256=wD48yi9oD7Y4ZBCi2HmdJ8TFNO__ODsj8KuRa4UTMxM,9658
155
+ flock/mcp/client.py,sha256=FRkuWNohFhzAzv-8aLz8Nd3CxkCf_SgHpfbPGTjRbMg,25790
156
+ flock/mcp/config.py,sha256=EgCFYTkV97fy8PlmnFVkB8FLCCSIni5vF3mQB0Yy-3c,14850
157
+ flock/mcp/manager.py,sha256=X54Ph5CtcIIZNuWndSH2sZijVjVBpPCNHS4M-B1bViU,10942
158
158
  flock/mcp/tool.py,sha256=cn59dFM1wD0_hl0GjcHxtVYEmPClqskYVMc9AVMB9RY,5397
159
159
  flock/mcp/servers/sse/__init__.py,sha256=qyfDdkZkgscHOkFREvsvoNtzA-CCnXPNma5w9uaIUCo,51
160
160
  flock/mcp/servers/sse/flock_sse_server.py,sha256=dbkRAcn11V9bBEoPlCMuuzuErTjHigqsnvGYJL4v2ts,3685
@@ -165,7 +165,7 @@ flock/mcp/servers/streamable_http/flock_streamable_http_server.py,sha256=qPAypNi
165
165
  flock/mcp/servers/websockets/__init__.py,sha256=KeNgNQRdeCQ9xgpaHB1I0-HyYeBhkifAuZPTIA8eDqM,47
166
166
  flock/mcp/servers/websockets/flock_websocket_server.py,sha256=UywSLftw3stbg_b-3Ygn_tNMo5Btm6I3bmSebcprfjk,3118
167
167
  flock/mcp/types/__init__.py,sha256=iwO7MXRjA-WhIyHSCI9fR6KF_UhzjqXJWT13f_8w4HM,1274
168
- flock/mcp/types/callbacks.py,sha256=9Ex-giTw1D6CNWIUq988SmWYoEqcDPGcJlZmS3fwjNQ,2399
168
+ flock/mcp/types/callbacks.py,sha256=8S-ZhWTnR7dzUtxTc9nFcKCDbNyhnE3Bsm1QpA_ArEg,2607
169
169
  flock/mcp/types/factories.py,sha256=B7h8apMDT2nR6POOj7aWWn39UbELt6nxHluPhRmemGw,3424
170
170
  flock/mcp/types/handlers.py,sha256=6ukkSMv1VZSfk2QDUiJnm8xifHnQvWZsxWXqN21BYSg,7804
171
171
  flock/mcp/types/types.py,sha256=ZbzbVihABFnfmZz2X-CCN7hQDzaSY0T-en43PFbFwQQ,11469
@@ -508,8 +508,8 @@ flock/themes/zenburned.toml,sha256=UEmquBbcAO3Zj652XKUwCsNoC2iQSlIh-q5c6DH-7Kc,1
508
508
  flock/themes/zenwritten-dark.toml,sha256=-dgaUfg1iCr5Dv4UEeHv_cN4GrPUCWAiHSxWK20X1kI,1663
509
509
  flock/themes/zenwritten-light.toml,sha256=G1iEheCPfBNsMTGaVpEVpDzYBHA_T-MV27rolUYolmE,1666
510
510
  flock/utility/output_utility_component.py,sha256=yVHhlIIIoYKziI5UyT_zvQb4G-NsxCTgLwA1wXXTTj4,9047
511
- flock_core-0.5.0b62.dist-info/METADATA,sha256=cmZyLdyb1GOdu0XhlR5pE6fKdsfWwcMSWpsDG_TH2DU,35901
512
- flock_core-0.5.0b62.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
513
- flock_core-0.5.0b62.dist-info/entry_points.txt,sha256=UQdPmtHd97gSA_IdLt9MOd-1rrf_WO-qsQeIiHWVrp4,42
514
- flock_core-0.5.0b62.dist-info/licenses/LICENSE,sha256=U3IZuTbC0yLj7huwJdldLBipSOHF4cPf6cUOodFiaBE,1072
515
- flock_core-0.5.0b62.dist-info/RECORD,,
511
+ flock_core-0.5.0b65.dist-info/METADATA,sha256=foWf00WbMuVzmlasEjY3lQxyrju5nmdGQO2iFa5FHf0,34987
512
+ flock_core-0.5.0b65.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
513
+ flock_core-0.5.0b65.dist-info/entry_points.txt,sha256=UQdPmtHd97gSA_IdLt9MOd-1rrf_WO-qsQeIiHWVrp4,42
514
+ flock_core-0.5.0b65.dist-info/licenses/LICENSE,sha256=U3IZuTbC0yLj7huwJdldLBipSOHF4cPf6cUOodFiaBE,1072
515
+ flock_core-0.5.0b65.dist-info/RECORD,,