flock-core 0.5.0b27__py3-none-any.whl → 0.5.0b50__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 (357) hide show
  1. flock/__init__.py +12 -217
  2. flock/agent.py +678 -0
  3. flock/api/themes.py +71 -0
  4. flock/artifacts.py +79 -0
  5. flock/cli.py +75 -0
  6. flock/components.py +173 -0
  7. flock/dashboard/__init__.py +28 -0
  8. flock/dashboard/collector.py +283 -0
  9. flock/dashboard/events.py +182 -0
  10. flock/dashboard/launcher.py +230 -0
  11. flock/dashboard/service.py +537 -0
  12. flock/dashboard/websocket.py +235 -0
  13. flock/engines/__init__.py +6 -0
  14. flock/engines/dspy_engine.py +856 -0
  15. flock/examples.py +128 -0
  16. flock/{core/util → helper}/cli_helper.py +4 -3
  17. flock/{core/logging → logging}/__init__.py +2 -3
  18. flock/{core/logging → logging}/formatters/enum_builder.py +3 -4
  19. flock/{core/logging → logging}/formatters/theme_builder.py +19 -44
  20. flock/{core/logging → logging}/formatters/themed_formatter.py +69 -115
  21. flock/{core/logging → logging}/logging.py +77 -61
  22. flock/{core/logging → logging}/telemetry.py +20 -26
  23. flock/{core/logging → logging}/telemetry_exporter/base_exporter.py +2 -2
  24. flock/{core/logging → logging}/telemetry_exporter/file_exporter.py +6 -9
  25. flock/{core/logging → logging}/telemetry_exporter/sqlite_exporter.py +2 -3
  26. flock/{core/logging → logging}/trace_and_logged.py +20 -24
  27. flock/mcp/__init__.py +91 -0
  28. flock/{core/mcp/mcp_client.py → mcp/client.py} +103 -154
  29. flock/{core/mcp/mcp_config.py → mcp/config.py} +62 -117
  30. flock/mcp/manager.py +255 -0
  31. flock/mcp/servers/sse/__init__.py +1 -1
  32. flock/mcp/servers/sse/flock_sse_server.py +11 -53
  33. flock/mcp/servers/stdio/__init__.py +1 -1
  34. flock/mcp/servers/stdio/flock_stdio_server.py +8 -48
  35. flock/mcp/servers/streamable_http/flock_streamable_http_server.py +17 -62
  36. flock/mcp/servers/websockets/flock_websocket_server.py +7 -40
  37. flock/{core/mcp/flock_mcp_tool.py → mcp/tool.py} +16 -26
  38. flock/mcp/types/__init__.py +42 -0
  39. flock/{core/mcp → mcp}/types/callbacks.py +9 -15
  40. flock/{core/mcp → mcp}/types/factories.py +7 -6
  41. flock/{core/mcp → mcp}/types/handlers.py +13 -18
  42. flock/{core/mcp → mcp}/types/types.py +70 -74
  43. flock/{core/mcp → mcp}/util/helpers.py +1 -1
  44. flock/orchestrator.py +645 -0
  45. flock/registry.py +148 -0
  46. flock/runtime.py +262 -0
  47. flock/service.py +140 -0
  48. flock/store.py +69 -0
  49. flock/subscription.py +111 -0
  50. flock/themes/andromeda.toml +1 -1
  51. flock/themes/apple-system-colors.toml +1 -1
  52. flock/themes/arcoiris.toml +1 -1
  53. flock/themes/atomonelight.toml +1 -1
  54. flock/themes/ayu copy.toml +1 -1
  55. flock/themes/ayu-light.toml +1 -1
  56. flock/themes/belafonte-day.toml +1 -1
  57. flock/themes/belafonte-night.toml +1 -1
  58. flock/themes/blulocodark.toml +1 -1
  59. flock/themes/breeze.toml +1 -1
  60. flock/themes/broadcast.toml +1 -1
  61. flock/themes/brogrammer.toml +1 -1
  62. flock/themes/builtin-dark.toml +1 -1
  63. flock/themes/builtin-pastel-dark.toml +1 -1
  64. flock/themes/catppuccin-latte.toml +1 -1
  65. flock/themes/catppuccin-macchiato.toml +1 -1
  66. flock/themes/catppuccin-mocha.toml +1 -1
  67. flock/themes/cga.toml +1 -1
  68. flock/themes/chalk.toml +1 -1
  69. flock/themes/ciapre.toml +1 -1
  70. flock/themes/coffee-theme.toml +1 -1
  71. flock/themes/cyberpunkscarletprotocol.toml +1 -1
  72. flock/themes/dark+.toml +1 -1
  73. flock/themes/darkermatrix.toml +1 -1
  74. flock/themes/darkside.toml +1 -1
  75. flock/themes/desert.toml +1 -1
  76. flock/themes/django.toml +1 -1
  77. flock/themes/djangosmooth.toml +1 -1
  78. flock/themes/doomone.toml +1 -1
  79. flock/themes/dotgov.toml +1 -1
  80. flock/themes/dracula+.toml +1 -1
  81. flock/themes/duckbones.toml +1 -1
  82. flock/themes/encom.toml +1 -1
  83. flock/themes/espresso.toml +1 -1
  84. flock/themes/everblush.toml +1 -1
  85. flock/themes/fairyfloss.toml +1 -1
  86. flock/themes/fideloper.toml +1 -1
  87. flock/themes/fishtank.toml +1 -1
  88. flock/themes/flexoki-light.toml +1 -1
  89. flock/themes/floraverse.toml +1 -1
  90. flock/themes/framer.toml +1 -1
  91. flock/themes/galizur.toml +1 -1
  92. flock/themes/github.toml +1 -1
  93. flock/themes/grass.toml +1 -1
  94. flock/themes/grey-green.toml +1 -1
  95. flock/themes/gruvboxlight.toml +1 -1
  96. flock/themes/guezwhoz.toml +1 -1
  97. flock/themes/harper.toml +1 -1
  98. flock/themes/hax0r-blue.toml +1 -1
  99. flock/themes/hopscotch.256.toml +1 -1
  100. flock/themes/ic-green-ppl.toml +1 -1
  101. flock/themes/iceberg-dark.toml +1 -1
  102. flock/themes/japanesque.toml +1 -1
  103. flock/themes/jubi.toml +1 -1
  104. flock/themes/kibble.toml +1 -1
  105. flock/themes/kolorit.toml +1 -1
  106. flock/themes/kurokula.toml +1 -1
  107. flock/themes/materialdesigncolors.toml +1 -1
  108. flock/themes/matrix.toml +1 -1
  109. flock/themes/mellifluous.toml +1 -1
  110. flock/themes/midnight-in-mojave.toml +1 -1
  111. flock/themes/monokai-remastered.toml +1 -1
  112. flock/themes/monokai-soda.toml +1 -1
  113. flock/themes/neon.toml +1 -1
  114. flock/themes/neopolitan.toml +1 -1
  115. flock/themes/nord-light.toml +1 -1
  116. flock/themes/ocean.toml +1 -1
  117. flock/themes/onehalfdark.toml +1 -1
  118. flock/themes/onehalflight.toml +1 -1
  119. flock/themes/palenighthc.toml +1 -1
  120. flock/themes/paulmillr.toml +1 -1
  121. flock/themes/pencildark.toml +1 -1
  122. flock/themes/pnevma.toml +1 -1
  123. flock/themes/purple-rain.toml +1 -1
  124. flock/themes/purplepeter.toml +1 -1
  125. flock/themes/raycast-dark.toml +1 -1
  126. flock/themes/red-sands.toml +1 -1
  127. flock/themes/relaxed.toml +1 -1
  128. flock/themes/retro.toml +1 -1
  129. flock/themes/rose-pine.toml +1 -1
  130. flock/themes/royal.toml +1 -1
  131. flock/themes/ryuuko.toml +1 -1
  132. flock/themes/sakura.toml +1 -1
  133. flock/themes/scarlet-protocol.toml +1 -1
  134. flock/themes/seoulbones-dark.toml +1 -1
  135. flock/themes/shades-of-purple.toml +1 -1
  136. flock/themes/smyck.toml +1 -1
  137. flock/themes/softserver.toml +1 -1
  138. flock/themes/solarized-darcula.toml +1 -1
  139. flock/themes/square.toml +1 -1
  140. flock/themes/sugarplum.toml +1 -1
  141. flock/themes/thayer-bright.toml +1 -1
  142. flock/themes/tokyonight.toml +1 -1
  143. flock/themes/tomorrow.toml +1 -1
  144. flock/themes/ubuntu.toml +1 -1
  145. flock/themes/ultradark.toml +1 -1
  146. flock/themes/ultraviolent.toml +1 -1
  147. flock/themes/unikitty.toml +1 -1
  148. flock/themes/urple.toml +1 -1
  149. flock/themes/vesper.toml +1 -1
  150. flock/themes/vimbones.toml +1 -1
  151. flock/themes/wildcherry.toml +1 -1
  152. flock/themes/wilmersdorf.toml +1 -1
  153. flock/themes/wryan.toml +1 -1
  154. flock/themes/xcodedarkhc.toml +1 -1
  155. flock/themes/xcodelight.toml +1 -1
  156. flock/themes/zenbones-light.toml +1 -1
  157. flock/themes/zenwritten-dark.toml +1 -1
  158. flock/utilities.py +301 -0
  159. flock/{components/utility → utility}/output_utility_component.py +68 -53
  160. flock/visibility.py +107 -0
  161. flock_core-0.5.0b50.dist-info/METADATA +747 -0
  162. flock_core-0.5.0b50.dist-info/RECORD +398 -0
  163. flock_core-0.5.0b50.dist-info/entry_points.txt +2 -0
  164. {flock_core-0.5.0b27.dist-info → flock_core-0.5.0b50.dist-info}/licenses/LICENSE +1 -1
  165. flock/adapter/__init__.py +0 -14
  166. flock/adapter/azure_adapter.py +0 -68
  167. flock/adapter/chroma_adapter.py +0 -73
  168. flock/adapter/faiss_adapter.py +0 -97
  169. flock/adapter/pinecone_adapter.py +0 -51
  170. flock/adapter/vector_base.py +0 -47
  171. flock/cli/assets/release_notes.md +0 -140
  172. flock/cli/config.py +0 -8
  173. flock/cli/constants.py +0 -36
  174. flock/cli/create_agent.py +0 -1
  175. flock/cli/create_flock.py +0 -280
  176. flock/cli/execute_flock.py +0 -620
  177. flock/cli/load_agent.py +0 -1
  178. flock/cli/load_examples.py +0 -1
  179. flock/cli/load_flock.py +0 -192
  180. flock/cli/load_release_notes.py +0 -20
  181. flock/cli/loaded_flock_cli.py +0 -254
  182. flock/cli/manage_agents.py +0 -459
  183. flock/cli/registry_management.py +0 -889
  184. flock/cli/runner.py +0 -41
  185. flock/cli/settings.py +0 -857
  186. flock/cli/utils.py +0 -135
  187. flock/cli/view_results.py +0 -29
  188. flock/cli/yaml_editor.py +0 -396
  189. flock/components/__init__.py +0 -30
  190. flock/components/evaluation/__init__.py +0 -9
  191. flock/components/evaluation/declarative_evaluation_component.py +0 -606
  192. flock/components/routing/__init__.py +0 -15
  193. flock/components/routing/conditional_routing_component.py +0 -494
  194. flock/components/routing/default_routing_component.py +0 -103
  195. flock/components/routing/llm_routing_component.py +0 -206
  196. flock/components/utility/__init__.py +0 -15
  197. flock/components/utility/memory_utility_component.py +0 -550
  198. flock/components/utility/metrics_utility_component.py +0 -700
  199. flock/config.py +0 -61
  200. flock/core/__init__.py +0 -110
  201. flock/core/agent/__init__.py +0 -16
  202. flock/core/agent/default_agent.py +0 -180
  203. flock/core/agent/flock_agent_components.py +0 -104
  204. flock/core/agent/flock_agent_execution.py +0 -101
  205. flock/core/agent/flock_agent_integration.py +0 -260
  206. flock/core/agent/flock_agent_lifecycle.py +0 -186
  207. flock/core/agent/flock_agent_serialization.py +0 -381
  208. flock/core/api/__init__.py +0 -10
  209. flock/core/api/custom_endpoint.py +0 -45
  210. flock/core/api/endpoints.py +0 -254
  211. flock/core/api/main.py +0 -162
  212. flock/core/api/models.py +0 -97
  213. flock/core/api/run_store.py +0 -224
  214. flock/core/api/runner.py +0 -44
  215. flock/core/api/service.py +0 -214
  216. flock/core/component/__init__.py +0 -15
  217. flock/core/component/agent_component_base.py +0 -309
  218. flock/core/component/evaluation_component.py +0 -62
  219. flock/core/component/routing_component.py +0 -74
  220. flock/core/component/utility_component.py +0 -69
  221. flock/core/config/flock_agent_config.py +0 -58
  222. flock/core/config/scheduled_agent_config.py +0 -40
  223. flock/core/context/context.py +0 -213
  224. flock/core/context/context_manager.py +0 -37
  225. flock/core/context/context_vars.py +0 -10
  226. flock/core/evaluation/utils.py +0 -396
  227. flock/core/execution/batch_executor.py +0 -369
  228. flock/core/execution/evaluation_executor.py +0 -438
  229. flock/core/execution/local_executor.py +0 -31
  230. flock/core/execution/opik_executor.py +0 -103
  231. flock/core/execution/temporal_executor.py +0 -164
  232. flock/core/flock.py +0 -634
  233. flock/core/flock_agent.py +0 -336
  234. flock/core/flock_factory.py +0 -551
  235. flock/core/flock_scheduler.py +0 -166
  236. flock/core/flock_server_manager.py +0 -136
  237. flock/core/interpreter/python_interpreter.py +0 -689
  238. flock/core/mcp/__init__.py +0 -1
  239. flock/core/mcp/flock_mcp_server.py +0 -680
  240. flock/core/mcp/mcp_client_manager.py +0 -201
  241. flock/core/mcp/types/__init__.py +0 -1
  242. flock/core/mixin/dspy_integration.py +0 -403
  243. flock/core/mixin/prompt_parser.py +0 -125
  244. flock/core/orchestration/__init__.py +0 -15
  245. flock/core/orchestration/flock_batch_processor.py +0 -94
  246. flock/core/orchestration/flock_evaluator.py +0 -113
  247. flock/core/orchestration/flock_execution.py +0 -295
  248. flock/core/orchestration/flock_initialization.py +0 -149
  249. flock/core/orchestration/flock_server_manager.py +0 -67
  250. flock/core/orchestration/flock_web_server.py +0 -117
  251. flock/core/registry/__init__.py +0 -45
  252. flock/core/registry/agent_registry.py +0 -69
  253. flock/core/registry/callable_registry.py +0 -139
  254. flock/core/registry/component_discovery.py +0 -142
  255. flock/core/registry/component_registry.py +0 -64
  256. flock/core/registry/config_mapping.py +0 -64
  257. flock/core/registry/decorators.py +0 -137
  258. flock/core/registry/registry_hub.py +0 -205
  259. flock/core/registry/server_registry.py +0 -57
  260. flock/core/registry/type_registry.py +0 -86
  261. flock/core/serialization/__init__.py +0 -13
  262. flock/core/serialization/callable_registry.py +0 -52
  263. flock/core/serialization/flock_serializer.py +0 -832
  264. flock/core/serialization/json_encoder.py +0 -41
  265. flock/core/serialization/secure_serializer.py +0 -175
  266. flock/core/serialization/serializable.py +0 -342
  267. flock/core/serialization/serialization_utils.py +0 -412
  268. flock/core/util/file_path_utils.py +0 -223
  269. flock/core/util/hydrator.py +0 -309
  270. flock/core/util/input_resolver.py +0 -164
  271. flock/core/util/loader.py +0 -59
  272. flock/core/util/splitter.py +0 -219
  273. flock/di.py +0 -27
  274. flock/platform/docker_tools.py +0 -49
  275. flock/platform/jaeger_install.py +0 -86
  276. flock/webapp/__init__.py +0 -1
  277. flock/webapp/app/__init__.py +0 -0
  278. flock/webapp/app/api/__init__.py +0 -0
  279. flock/webapp/app/api/agent_management.py +0 -241
  280. flock/webapp/app/api/execution.py +0 -709
  281. flock/webapp/app/api/flock_management.py +0 -129
  282. flock/webapp/app/api/registry_viewer.py +0 -30
  283. flock/webapp/app/chat.py +0 -665
  284. flock/webapp/app/config.py +0 -104
  285. flock/webapp/app/dependencies.py +0 -117
  286. flock/webapp/app/main.py +0 -1070
  287. flock/webapp/app/middleware.py +0 -113
  288. flock/webapp/app/models_ui.py +0 -7
  289. flock/webapp/app/services/__init__.py +0 -0
  290. flock/webapp/app/services/feedback_file_service.py +0 -363
  291. flock/webapp/app/services/flock_service.py +0 -337
  292. flock/webapp/app/services/sharing_models.py +0 -81
  293. flock/webapp/app/services/sharing_store.py +0 -598
  294. flock/webapp/app/templates/theme_mapper.html +0 -326
  295. flock/webapp/app/theme_mapper.py +0 -812
  296. flock/webapp/app/utils.py +0 -85
  297. flock/webapp/run.py +0 -215
  298. flock/webapp/static/css/chat.css +0 -301
  299. flock/webapp/static/css/components.css +0 -167
  300. flock/webapp/static/css/header.css +0 -39
  301. flock/webapp/static/css/layout.css +0 -46
  302. flock/webapp/static/css/sidebar.css +0 -127
  303. flock/webapp/static/css/two-pane.css +0 -48
  304. flock/webapp/templates/base.html +0 -200
  305. flock/webapp/templates/chat.html +0 -152
  306. flock/webapp/templates/chat_settings.html +0 -19
  307. flock/webapp/templates/flock_editor.html +0 -16
  308. flock/webapp/templates/index.html +0 -12
  309. flock/webapp/templates/partials/_agent_detail_form.html +0 -93
  310. flock/webapp/templates/partials/_agent_list.html +0 -18
  311. flock/webapp/templates/partials/_agent_manager_view.html +0 -51
  312. flock/webapp/templates/partials/_agent_tools_checklist.html +0 -14
  313. flock/webapp/templates/partials/_chat_container.html +0 -15
  314. flock/webapp/templates/partials/_chat_messages.html +0 -57
  315. flock/webapp/templates/partials/_chat_settings_form.html +0 -85
  316. flock/webapp/templates/partials/_create_flock_form.html +0 -50
  317. flock/webapp/templates/partials/_dashboard_flock_detail.html +0 -17
  318. flock/webapp/templates/partials/_dashboard_flock_file_list.html +0 -16
  319. flock/webapp/templates/partials/_dashboard_flock_properties_preview.html +0 -28
  320. flock/webapp/templates/partials/_dashboard_upload_flock_form.html +0 -16
  321. flock/webapp/templates/partials/_dynamic_input_form_content.html +0 -22
  322. flock/webapp/templates/partials/_env_vars_table.html +0 -23
  323. flock/webapp/templates/partials/_execution_form.html +0 -118
  324. flock/webapp/templates/partials/_execution_view_container.html +0 -28
  325. flock/webapp/templates/partials/_flock_file_list.html +0 -23
  326. flock/webapp/templates/partials/_flock_properties_form.html +0 -52
  327. flock/webapp/templates/partials/_flock_upload_form.html +0 -16
  328. flock/webapp/templates/partials/_header_flock_status.html +0 -5
  329. flock/webapp/templates/partials/_load_manager_view.html +0 -49
  330. flock/webapp/templates/partials/_registry_table.html +0 -25
  331. flock/webapp/templates/partials/_registry_viewer_content.html +0 -70
  332. flock/webapp/templates/partials/_results_display.html +0 -78
  333. flock/webapp/templates/partials/_settings_env_content.html +0 -9
  334. flock/webapp/templates/partials/_settings_theme_content.html +0 -14
  335. flock/webapp/templates/partials/_settings_view.html +0 -36
  336. flock/webapp/templates/partials/_share_chat_link_snippet.html +0 -11
  337. flock/webapp/templates/partials/_share_link_snippet.html +0 -35
  338. flock/webapp/templates/partials/_sidebar.html +0 -74
  339. flock/webapp/templates/partials/_streaming_results_container.html +0 -195
  340. flock/webapp/templates/partials/_structured_data_view.html +0 -40
  341. flock/webapp/templates/partials/_theme_preview.html +0 -36
  342. flock/webapp/templates/registry_viewer.html +0 -84
  343. flock/webapp/templates/shared_run_page.html +0 -140
  344. flock/workflow/__init__.py +0 -0
  345. flock/workflow/activities.py +0 -196
  346. flock/workflow/agent_activities.py +0 -24
  347. flock/workflow/agent_execution_activity.py +0 -202
  348. flock/workflow/flock_workflow.py +0 -214
  349. flock/workflow/temporal_config.py +0 -96
  350. flock/workflow/temporal_setup.py +0 -68
  351. flock_core-0.5.0b27.dist-info/METADATA +0 -274
  352. flock_core-0.5.0b27.dist-info/RECORD +0 -559
  353. flock_core-0.5.0b27.dist-info/entry_points.txt +0 -2
  354. /flock/{core/logging → logging}/formatters/themes.py +0 -0
  355. /flock/{core/logging → logging}/span_middleware/baggage_span_processor.py +0 -0
  356. /flock/{core/mcp → mcp}/util/__init__.py +0 -0
  357. {flock_core-0.5.0b27.dist-info → flock_core-0.5.0b50.dist-info}/WHEEL +0 -0
flock/orchestrator.py ADDED
@@ -0,0 +1,645 @@
1
+ """Blackboard orchestrator and scheduling runtime."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import os
7
+ from asyncio import Task
8
+ from collections.abc import Iterable, Mapping, Sequence
9
+ from contextlib import asynccontextmanager
10
+ from typing import TYPE_CHECKING, Any
11
+ from uuid import uuid4
12
+
13
+ from pydantic import BaseModel
14
+
15
+ from flock.agent import Agent, AgentBuilder
16
+ from flock.artifacts import Artifact
17
+ from flock.helper.cli_helper import init_console
18
+ from flock.mcp import (
19
+ FlockMCPClientManager,
20
+ FlockMCPConfiguration,
21
+ FlockMCPConnectionConfiguration,
22
+ FlockMCPFeatureConfiguration,
23
+ ServerParameters,
24
+ )
25
+ from flock.registry import type_registry
26
+ from flock.runtime import Context
27
+ from flock.store import BlackboardStore, InMemoryBlackboardStore
28
+ from flock.visibility import AgentIdentity, PublicVisibility, Visibility
29
+
30
+
31
+ if TYPE_CHECKING:
32
+ import builtins
33
+
34
+
35
+ class BoardHandle:
36
+ """Handle exposed to components for publishing and inspection."""
37
+
38
+ def __init__(self, orchestrator: Flock) -> None:
39
+ self._orchestrator = orchestrator
40
+
41
+ async def publish(self, artifact: Artifact) -> None:
42
+ await self._orchestrator._persist_and_schedule(artifact)
43
+
44
+ async def get(self, artifact_id) -> Artifact | None:
45
+ return await self._orchestrator.store.get(artifact_id)
46
+
47
+ async def list(self) -> builtins.list[Artifact]:
48
+ return await self._orchestrator.store.list()
49
+
50
+
51
+ class Flock:
52
+ def _patch_litellm_proxy_imports(self) -> None:
53
+ """Stub litellm proxy_server to avoid optional proxy deps when not used.
54
+
55
+ Some litellm versions import `litellm.proxy.proxy_server` during standard logging
56
+ to read `general_settings`, which pulls in optional dependencies like `apscheduler`.
57
+ We provide a stub so imports succeed but cold storage remains disabled.
58
+ """
59
+ try:
60
+ import sys
61
+ import types
62
+
63
+ if "litellm.proxy.proxy_server" not in sys.modules:
64
+ stub = types.ModuleType("litellm.proxy.proxy_server")
65
+ # Minimal surface that cold_storage_handler accesses
66
+ stub.general_settings = {}
67
+ sys.modules["litellm.proxy.proxy_server"] = stub
68
+ except Exception: # nosec B110 - Safe to ignore; worst case litellm will log a warning
69
+ # logger.debug(f"Failed to stub litellm proxy_server: {e}")
70
+ pass
71
+
72
+ def __init__(
73
+ self,
74
+ model: str | None = None,
75
+ *,
76
+ store: BlackboardStore | None = None,
77
+ max_agent_iterations: int = 1000,
78
+ ) -> None:
79
+ self._patch_litellm_proxy_imports()
80
+ self.model = model
81
+ self.store: BlackboardStore = store or InMemoryBlackboardStore()
82
+ self._agents: dict[str, Agent] = {}
83
+ self._tasks: set[Task[Any]] = set()
84
+ self._processed: set[tuple[str, str]] = set()
85
+ self._lock = asyncio.Lock()
86
+ self.metrics: dict[str, float] = {"artifacts_published": 0, "agent_runs": 0}
87
+ # MCP integration
88
+ self._mcp_configs: dict[str, FlockMCPConfiguration] = {}
89
+ self._mcp_manager: FlockMCPClientManager | None = None
90
+ # T068: Circuit breaker for runaway agents
91
+ self.max_agent_iterations: int = max_agent_iterations
92
+ self._agent_iteration_count: dict[str, int] = {}
93
+ self.is_dashboard: bool = False
94
+ if not model:
95
+ self.model = os.getenv("DEFAULT_MODEL")
96
+
97
+ # Agent management -----------------------------------------------------
98
+
99
+ def agent(self, name: str) -> AgentBuilder:
100
+ if name in self._agents:
101
+ raise ValueError(f"Agent '{name}' already registered.")
102
+ return AgentBuilder(self, name)
103
+
104
+ def register_agent(self, agent: Agent) -> None:
105
+ if agent.name in self._agents:
106
+ raise ValueError(f"Agent '{agent.name}' already registered.")
107
+ self._agents[agent.name] = agent
108
+
109
+ def get_agent(self, name: str) -> Agent:
110
+ return self._agents[name]
111
+
112
+ @property
113
+ def agents(self) -> list[Agent]:
114
+ return list(self._agents.values())
115
+
116
+ # MCP management -------------------------------------------------------
117
+
118
+ def add_mcp(
119
+ self,
120
+ name: str,
121
+ connection_params: ServerParameters,
122
+ *,
123
+ enable_tools_feature: bool = True,
124
+ enable_prompts_feature: bool = True,
125
+ enable_sampling_feature: bool = True,
126
+ enable_roots_feature: bool = True,
127
+ tool_whitelist: list[str] | None = None,
128
+ allow_all_tools: bool = True,
129
+ read_timeout_seconds: float = 300,
130
+ max_retries: int = 3,
131
+ **kwargs,
132
+ ) -> Flock:
133
+ """Register an MCP server for use by agents.
134
+
135
+ Architecture Decision: AD001 - Two-Level Architecture
136
+ MCP servers are registered at orchestrator level and assigned to agents.
137
+
138
+ Args:
139
+ name: Unique identifier for this MCP server
140
+ connection_params: Server connection parameters
141
+ enable_tools_feature: Enable tool execution
142
+ enable_prompts_feature: Enable prompt templates
143
+ enable_sampling_feature: Enable LLM sampling requests
144
+ enable_roots_feature: Enable filesystem roots
145
+ tool_whitelist: Optional list of tool names to allow
146
+ allow_all_tools: If True, allow all tools (subject to whitelist)
147
+ read_timeout_seconds: Timeout for server communications
148
+ max_retries: Connection retry attempts
149
+
150
+ Returns:
151
+ self for method chaining
152
+
153
+ Raises:
154
+ ValueError: If server name already registered
155
+ """
156
+ if name in self._mcp_configs:
157
+ raise ValueError(f"MCP server '{name}' is already registered.")
158
+
159
+ # Detect transport type
160
+ from flock.mcp.types import (
161
+ SseServerParameters,
162
+ StdioServerParameters,
163
+ StreamableHttpServerParameters,
164
+ WebsocketServerParameters,
165
+ )
166
+
167
+ if isinstance(connection_params, StdioServerParameters):
168
+ transport_type = "stdio"
169
+ elif isinstance(connection_params, WebsocketServerParameters):
170
+ transport_type = "websockets"
171
+ elif isinstance(connection_params, SseServerParameters):
172
+ transport_type = "sse"
173
+ elif isinstance(connection_params, StreamableHttpServerParameters):
174
+ transport_type = "streamable_http"
175
+ else:
176
+ transport_type = "custom"
177
+
178
+ # Build configuration
179
+ connection_config = FlockMCPConnectionConfiguration(
180
+ max_retries=max_retries,
181
+ connection_parameters=connection_params,
182
+ transport_type=transport_type,
183
+ read_timeout_seconds=read_timeout_seconds,
184
+ )
185
+
186
+ feature_config = FlockMCPFeatureConfiguration(
187
+ tools_enabled=enable_tools_feature,
188
+ prompts_enabled=enable_prompts_feature,
189
+ sampling_enabled=enable_sampling_feature,
190
+ roots_enabled=enable_roots_feature,
191
+ tool_whitelist=tool_whitelist,
192
+ )
193
+
194
+ mcp_config = FlockMCPConfiguration(
195
+ name=name,
196
+ allow_all_tools=allow_all_tools,
197
+ connection_config=connection_config,
198
+ feature_config=feature_config,
199
+ )
200
+
201
+ self._mcp_configs[name] = mcp_config
202
+ return self
203
+
204
+ def get_mcp_manager(self) -> FlockMCPClientManager:
205
+ """Get or create the MCP client manager.
206
+
207
+ Architecture Decision: AD005 - Lazy Connection Establishment
208
+ """
209
+ if not self._mcp_configs:
210
+ raise RuntimeError("No MCP servers registered. Call add_mcp() first.")
211
+
212
+ if self._mcp_manager is None:
213
+ self._mcp_manager = FlockMCPClientManager(self._mcp_configs)
214
+
215
+ return self._mcp_manager
216
+
217
+ # Runtime --------------------------------------------------------------
218
+
219
+ async def run_until_idle(self) -> None:
220
+ while self._tasks:
221
+ await asyncio.sleep(0.01)
222
+ pending = {task for task in self._tasks if not task.done()}
223
+ self._tasks = pending
224
+ # T068: Reset circuit breaker counters when idle
225
+ self._agent_iteration_count.clear()
226
+
227
+ # Automatically shutdown MCP connections when idle
228
+ await self.shutdown()
229
+
230
+ async def direct_invoke(
231
+ self, agent: Agent, inputs: Sequence[BaseModel | Mapping[str, Any] | Artifact]
232
+ ) -> list[Artifact]:
233
+ artifacts = [self._normalize_input(value, produced_by="__direct__") for value in inputs]
234
+ for artifact in artifacts:
235
+ self._mark_processed(artifact, agent)
236
+ await self._persist_and_schedule(artifact)
237
+ ctx = Context(board=BoardHandle(self), orchestrator=self, task_id=str(uuid4()))
238
+ self._record_agent_run(agent)
239
+ return await agent.execute(ctx, artifacts)
240
+
241
+ async def arun(self, agent_builder: AgentBuilder, *inputs: BaseModel) -> list[Artifact]:
242
+ artifacts = await self.direct_invoke(agent_builder.agent, list(inputs))
243
+ await self.run_until_idle()
244
+ return artifacts
245
+
246
+ def run(self, agent_builder: AgentBuilder, *inputs: BaseModel) -> list[Artifact]:
247
+ return asyncio.run(self.arun(agent_builder, *inputs))
248
+
249
+ async def shutdown(self) -> None:
250
+ """Shutdown orchestrator and clean up resources."""
251
+ if self._mcp_manager is not None:
252
+ await self._mcp_manager.cleanup_all()
253
+ self._mcp_manager = None
254
+
255
+ def cli(self) -> Flock:
256
+ # Placeholder for CLI wiring (rich UI in Step 3)
257
+ return self
258
+
259
+ async def serve(
260
+ self, *, dashboard: bool = False, host: str = "127.0.0.1", port: int = 8000
261
+ ) -> None:
262
+ """Start HTTP service for the orchestrator (blocking).
263
+
264
+ Args:
265
+ dashboard: Enable real-time dashboard with WebSocket support (default: False)
266
+ host: Host to bind to (default: "127.0.0.1")
267
+ port: Port to bind to (default: 8000)
268
+
269
+ Examples:
270
+ # Basic HTTP API (no dashboard) - runs until interrupted
271
+ await orchestrator.serve()
272
+
273
+ # With dashboard (WebSocket + browser launch) - runs until interrupted
274
+ await orchestrator.serve(dashboard=True)
275
+ """
276
+ if not dashboard:
277
+ # Standard service without dashboard
278
+ from flock.service import BlackboardHTTPService
279
+
280
+ service = BlackboardHTTPService(self)
281
+ await service.run_async(host=host, port=port)
282
+ return
283
+
284
+ # Dashboard mode: integrate event collection and WebSocket
285
+ from flock.dashboard.collector import DashboardEventCollector
286
+ from flock.dashboard.launcher import DashboardLauncher
287
+ from flock.dashboard.service import DashboardHTTPService
288
+ from flock.dashboard.websocket import WebSocketManager
289
+
290
+ # Create dashboard components
291
+ websocket_manager = WebSocketManager()
292
+ event_collector = DashboardEventCollector()
293
+ event_collector.set_websocket_manager(websocket_manager)
294
+
295
+ # Store collector reference for agents added later
296
+ self._dashboard_collector = event_collector
297
+
298
+ # Inject event collector into all existing agents
299
+ for agent in self._agents.values():
300
+ # Insert at beginning of utilities list (highest priority)
301
+ agent.utilities.insert(0, event_collector)
302
+
303
+ # Start dashboard launcher (npm process + browser)
304
+ launcher = DashboardLauncher(port=port)
305
+ launcher.start()
306
+
307
+ # Create dashboard HTTP service
308
+ service = DashboardHTTPService(
309
+ orchestrator=self,
310
+ websocket_manager=websocket_manager,
311
+ event_collector=event_collector,
312
+ )
313
+
314
+ # Store launcher for cleanup
315
+ self._dashboard_launcher = launcher
316
+
317
+ # Run service (blocking call)
318
+ try:
319
+ await service.run_async(host=host, port=port)
320
+ finally:
321
+ # Cleanup on exit
322
+ launcher.stop()
323
+
324
+ # Scheduling -----------------------------------------------------------
325
+
326
+ async def publish(
327
+ self,
328
+ obj: BaseModel | dict | Artifact,
329
+ *,
330
+ visibility: Visibility | None = None,
331
+ correlation_id: str | None = None,
332
+ partition_key: str | None = None,
333
+ tags: set[str] | None = None,
334
+ is_dashboard: bool = False,
335
+ ) -> Artifact:
336
+ """Publish an artifact to the blackboard (event-driven).
337
+
338
+ All agents with matching subscriptions will be triggered according to
339
+ their filters (type, predicates, visibility, etc).
340
+
341
+ Args:
342
+ obj: Object to publish (BaseModel instance, dict, or Artifact)
343
+ visibility: Access control (defaults to PublicVisibility)
344
+ correlation_id: Optional correlation ID for request tracing
345
+ partition_key: Optional partition key for sharding
346
+ tags: Optional tags for channel-based routing
347
+
348
+ Returns:
349
+ The published Artifact
350
+
351
+ Examples:
352
+ >>> # Publish a model instance (recommended)
353
+ >>> task = Task(name="Deploy", priority=5)
354
+ >>> await orchestrator.publish(task)
355
+
356
+ >>> # Publish with custom visibility
357
+ >>> await orchestrator.publish(
358
+ ... task,
359
+ ... visibility=PrivateVisibility(agents={"admin"})
360
+ ... )
361
+
362
+ >>> # Publish with tags for channel routing
363
+ >>> await orchestrator.publish(task, tags={"urgent", "backend"})
364
+ """
365
+ init_console(clear_screen=True, show_banner=True, model=self.model)
366
+ self.is_dashboard = is_dashboard
367
+ # Handle different input types
368
+ if isinstance(obj, Artifact):
369
+ # Already an artifact - publish as-is
370
+ artifact = obj
371
+ elif isinstance(obj, BaseModel):
372
+ # BaseModel instance - get type from registry
373
+ type_name = type_registry.name_for(type(obj))
374
+ artifact = Artifact(
375
+ type=type_name,
376
+ payload=obj.model_dump(),
377
+ produced_by="external",
378
+ visibility=visibility or PublicVisibility(),
379
+ correlation_id=correlation_id or uuid4(),
380
+ partition_key=partition_key,
381
+ tags=tags or set(),
382
+ )
383
+ elif isinstance(obj, dict):
384
+ # Dict must have 'type' key
385
+ if "type" not in obj:
386
+ raise ValueError(
387
+ "Dict input must contain 'type' key. "
388
+ "Example: {'type': 'Task', 'name': 'foo', 'priority': 5}"
389
+ )
390
+ # Support both {'type': 'X', 'payload': {...}} and {'type': 'X', ...}
391
+ type_name = obj["type"]
392
+ if "payload" in obj:
393
+ payload = obj["payload"]
394
+ else:
395
+ payload = {k: v for k, v in obj.items() if k != "type"}
396
+
397
+ artifact = Artifact(
398
+ type=type_name,
399
+ payload=payload,
400
+ produced_by="external",
401
+ visibility=visibility or PublicVisibility(),
402
+ correlation_id=correlation_id,
403
+ partition_key=partition_key,
404
+ tags=tags or set(),
405
+ )
406
+ else:
407
+ raise TypeError(
408
+ f"Cannot publish object of type {type(obj).__name__}. "
409
+ "Expected BaseModel, dict, or Artifact."
410
+ )
411
+
412
+ # Persist and schedule matching agents
413
+ await self._persist_and_schedule(artifact)
414
+ return artifact
415
+
416
+ async def publish_many(
417
+ self, objects: Iterable[BaseModel | dict | Artifact], **kwargs
418
+ ) -> list[Artifact]:
419
+ """Publish multiple artifacts at once (event-driven).
420
+
421
+ Args:
422
+ objects: Iterable of objects to publish
423
+ **kwargs: Passed to each publish() call (visibility, tags, etc)
424
+
425
+ Returns:
426
+ List of published Artifacts
427
+
428
+ Example:
429
+ >>> tasks = [
430
+ ... Task(name="Deploy", priority=5),
431
+ ... Task(name="Test", priority=3),
432
+ ... Task(name="Document", priority=1),
433
+ ... ]
434
+ >>> await orchestrator.publish_many(tasks, tags={"sprint-3"})
435
+ """
436
+ artifacts = []
437
+ for obj in objects:
438
+ artifact = await self.publish(obj, **kwargs)
439
+ artifacts.append(artifact)
440
+ return artifacts
441
+
442
+ # -----------------------------------------------------------------------------
443
+ # NEW DIRECT INVOCATION API - Explicit Control
444
+ # -----------------------------------------------------------------------------
445
+
446
+ async def invoke(
447
+ self,
448
+ agent: Agent | AgentBuilder,
449
+ obj: BaseModel,
450
+ *,
451
+ publish_outputs: bool = True,
452
+ timeout: float | None = None,
453
+ ) -> list[Artifact]:
454
+ """Directly invoke a specific agent (bypasses subscription matching).
455
+
456
+ This executes the agent immediately without checking subscriptions or
457
+ predicates. Useful for testing or synchronous request-response patterns.
458
+
459
+ Args:
460
+ agent: Agent or AgentBuilder to invoke
461
+ obj: Input object (BaseModel instance)
462
+ publish_outputs: If True, publish outputs to blackboard for cascade
463
+ timeout: Optional timeout in seconds
464
+
465
+ Returns:
466
+ Artifacts produced by the agent
467
+
468
+ Warning:
469
+ This bypasses subscription filters and predicates. For event-driven
470
+ coordination, use publish() instead.
471
+
472
+ Examples:
473
+ >>> # Testing: Execute agent without triggering others
474
+ >>> results = await orchestrator.invoke(
475
+ ... agent,
476
+ ... Task(name="test", priority=5),
477
+ ... publish_outputs=False
478
+ ... )
479
+
480
+ >>> # HTTP endpoint: Execute specific agent, allow cascade
481
+ >>> results = await orchestrator.invoke(
482
+ ... movie_agent,
483
+ ... Idea(topic="AI", genre="comedy"),
484
+ ... publish_outputs=True
485
+ ... )
486
+ >>> await orchestrator.run_until_idle()
487
+ """
488
+ from asyncio import wait_for
489
+ from uuid import uuid4
490
+
491
+ # Get Agent instance
492
+ agent_obj = agent.agent if isinstance(agent, AgentBuilder) else agent
493
+
494
+ # Create artifact (don't publish to blackboard yet)
495
+ type_name = type_registry.name_for(type(obj))
496
+ artifact = Artifact(
497
+ type=type_name,
498
+ payload=obj.model_dump(),
499
+ produced_by="__direct__",
500
+ visibility=PublicVisibility(),
501
+ )
502
+
503
+ # Execute agent directly
504
+ ctx = Context(board=BoardHandle(self), orchestrator=self, task_id=str(uuid4()))
505
+ self._record_agent_run(agent_obj)
506
+
507
+ # Execute with optional timeout
508
+ if timeout:
509
+ execution = agent_obj.execute(ctx, [artifact])
510
+ outputs = await wait_for(execution, timeout=timeout)
511
+ else:
512
+ outputs = await agent_obj.execute(ctx, [artifact])
513
+
514
+ # Optionally publish outputs to blackboard
515
+ if publish_outputs:
516
+ for output in outputs:
517
+ await self._persist_and_schedule(output)
518
+
519
+ return outputs
520
+
521
+ # Keep publish_external as deprecated alias
522
+ async def publish_external(
523
+ self,
524
+ type_name: str,
525
+ payload: dict[str, Any],
526
+ *,
527
+ visibility: Visibility | None = None,
528
+ correlation_id: str | None = None,
529
+ partition_key: str | None = None,
530
+ tags: set[str] | None = None,
531
+ ) -> Artifact:
532
+ """Deprecated: Use publish() instead.
533
+
534
+ This method will be removed in v2.0.
535
+ """
536
+ import warnings
537
+
538
+ warnings.warn(
539
+ "publish_external() is deprecated. Use publish(obj) instead.",
540
+ DeprecationWarning,
541
+ stacklevel=2,
542
+ )
543
+ return await self.publish(
544
+ {"type": type_name, "payload": payload},
545
+ visibility=visibility,
546
+ correlation_id=correlation_id,
547
+ partition_key=partition_key,
548
+ tags=tags,
549
+ )
550
+
551
+ async def _persist_and_schedule(self, artifact: Artifact) -> None:
552
+ await self.store.publish(artifact)
553
+ self.metrics["artifacts_published"] += 1
554
+ await self._schedule_artifact(artifact)
555
+
556
+ async def _schedule_artifact(self, artifact: Artifact) -> None:
557
+ for agent in self.agents:
558
+ identity = agent.identity
559
+ for subscription in agent.subscriptions:
560
+ if not subscription.accepts_events():
561
+ continue
562
+ # T066: Check prevent_self_trigger
563
+ if agent.prevent_self_trigger and artifact.produced_by == agent.name:
564
+ continue # Skip - agent produced this artifact (prevents feedback loops)
565
+ # T068: Circuit breaker - check iteration limit
566
+ iteration_count = self._agent_iteration_count.get(agent.name, 0)
567
+ if iteration_count >= self.max_agent_iterations:
568
+ # Agent hit iteration limit - possible infinite loop
569
+ continue
570
+ if not self._check_visibility(artifact, identity):
571
+ continue
572
+ if not subscription.matches(artifact):
573
+ continue
574
+ if self._seen_before(artifact, agent):
575
+ continue
576
+ # T068: Increment iteration counter
577
+ self._agent_iteration_count[agent.name] = iteration_count + 1
578
+ self._mark_processed(artifact, agent)
579
+ self._schedule_task(agent, [artifact])
580
+
581
+ def _schedule_task(self, agent: Agent, artifacts: list[Artifact]) -> None:
582
+ task = asyncio.create_task(self._run_agent_task(agent, artifacts))
583
+ self._tasks.add(task)
584
+ task.add_done_callback(self._tasks.discard)
585
+
586
+ def _record_agent_run(self, agent: Agent) -> None:
587
+ self.metrics["agent_runs"] += 1
588
+
589
+ def _mark_processed(self, artifact: Artifact, agent: Agent) -> None:
590
+ key = (str(artifact.id), agent.name)
591
+ self._processed.add(key)
592
+
593
+ def _seen_before(self, artifact: Artifact, agent: Agent) -> bool:
594
+ key = (str(artifact.id), agent.name)
595
+ return key in self._processed
596
+
597
+ async def _run_agent_task(self, agent: Agent, artifacts: list[Artifact]) -> None:
598
+ correlation_id = artifacts[0].correlation_id if artifacts else uuid4()
599
+
600
+ ctx = Context(
601
+ board=BoardHandle(self),
602
+ orchestrator=self,
603
+ task_id=str(uuid4()),
604
+ correlation_id=correlation_id, # NEW!
605
+ )
606
+ self._record_agent_run(agent)
607
+ await agent.execute(ctx, artifacts)
608
+
609
+ # Helpers --------------------------------------------------------------
610
+
611
+ def _normalize_input(
612
+ self, value: BaseModel | Mapping[str, Any] | Artifact, *, produced_by: str
613
+ ) -> Artifact:
614
+ if isinstance(value, Artifact):
615
+ return value
616
+ if isinstance(value, BaseModel):
617
+ model_cls = type(value)
618
+ type_name = type_registry.register(model_cls)
619
+ payload = value.model_dump()
620
+ elif isinstance(value, Mapping):
621
+ if "type" not in value:
622
+ raise ValueError("Mapping input must contain 'type'.")
623
+ type_name = value["type"]
624
+ payload = value.get("payload", {})
625
+ else: # pragma: no cover - defensive
626
+ raise TypeError("Unsupported input for direct invoke.")
627
+ return Artifact(type=type_name, payload=payload, produced_by=produced_by)
628
+
629
+ def _check_visibility(self, artifact: Artifact, identity: AgentIdentity) -> bool:
630
+ try:
631
+ return artifact.visibility.allows(identity)
632
+ except AttributeError: # pragma: no cover - fallback for dict vis
633
+ return True
634
+
635
+
636
+ @asynccontextmanager
637
+ async def start_orchestrator(orchestrator: Flock): # pragma: no cover - CLI helper
638
+ try:
639
+ yield orchestrator
640
+ await orchestrator.run_until_idle()
641
+ finally:
642
+ pass
643
+
644
+
645
+ __all__ = ["Flock", "start_orchestrator"]