flock-core 0.5.0b28__py3-none-any.whl → 0.5.56b0__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 (359) 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.56b0.dist-info/METADATA +747 -0
  162. flock_core-0.5.56b0.dist-info/RECORD +398 -0
  163. flock_core-0.5.56b0.dist-info/entry_points.txt +2 -0
  164. {flock_core-0.5.0b28.dist-info → flock_core-0.5.56b0.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 -22
  197. flock/components/utility/example_utility_component.py +0 -250
  198. flock/components/utility/feedback_utility_component.py +0 -206
  199. flock/components/utility/memory_utility_component.py +0 -550
  200. flock/components/utility/metrics_utility_component.py +0 -700
  201. flock/config.py +0 -61
  202. flock/core/__init__.py +0 -110
  203. flock/core/agent/__init__.py +0 -16
  204. flock/core/agent/default_agent.py +0 -216
  205. flock/core/agent/flock_agent_components.py +0 -104
  206. flock/core/agent/flock_agent_execution.py +0 -101
  207. flock/core/agent/flock_agent_integration.py +0 -260
  208. flock/core/agent/flock_agent_lifecycle.py +0 -186
  209. flock/core/agent/flock_agent_serialization.py +0 -381
  210. flock/core/api/__init__.py +0 -10
  211. flock/core/api/custom_endpoint.py +0 -45
  212. flock/core/api/endpoints.py +0 -254
  213. flock/core/api/main.py +0 -162
  214. flock/core/api/models.py +0 -97
  215. flock/core/api/run_store.py +0 -224
  216. flock/core/api/runner.py +0 -44
  217. flock/core/api/service.py +0 -214
  218. flock/core/component/__init__.py +0 -15
  219. flock/core/component/agent_component_base.py +0 -309
  220. flock/core/component/evaluation_component.py +0 -62
  221. flock/core/component/routing_component.py +0 -74
  222. flock/core/component/utility_component.py +0 -69
  223. flock/core/config/flock_agent_config.py +0 -58
  224. flock/core/config/scheduled_agent_config.py +0 -40
  225. flock/core/context/context.py +0 -213
  226. flock/core/context/context_manager.py +0 -37
  227. flock/core/context/context_vars.py +0 -10
  228. flock/core/evaluation/utils.py +0 -396
  229. flock/core/execution/batch_executor.py +0 -369
  230. flock/core/execution/evaluation_executor.py +0 -438
  231. flock/core/execution/local_executor.py +0 -31
  232. flock/core/execution/opik_executor.py +0 -103
  233. flock/core/execution/temporal_executor.py +0 -164
  234. flock/core/flock.py +0 -634
  235. flock/core/flock_agent.py +0 -336
  236. flock/core/flock_factory.py +0 -613
  237. flock/core/flock_scheduler.py +0 -166
  238. flock/core/flock_server_manager.py +0 -136
  239. flock/core/interpreter/python_interpreter.py +0 -689
  240. flock/core/mcp/__init__.py +0 -1
  241. flock/core/mcp/flock_mcp_server.py +0 -680
  242. flock/core/mcp/mcp_client_manager.py +0 -201
  243. flock/core/mcp/types/__init__.py +0 -1
  244. flock/core/mixin/dspy_integration.py +0 -403
  245. flock/core/mixin/prompt_parser.py +0 -125
  246. flock/core/orchestration/__init__.py +0 -15
  247. flock/core/orchestration/flock_batch_processor.py +0 -94
  248. flock/core/orchestration/flock_evaluator.py +0 -113
  249. flock/core/orchestration/flock_execution.py +0 -295
  250. flock/core/orchestration/flock_initialization.py +0 -149
  251. flock/core/orchestration/flock_server_manager.py +0 -67
  252. flock/core/orchestration/flock_web_server.py +0 -117
  253. flock/core/registry/__init__.py +0 -45
  254. flock/core/registry/agent_registry.py +0 -69
  255. flock/core/registry/callable_registry.py +0 -139
  256. flock/core/registry/component_discovery.py +0 -142
  257. flock/core/registry/component_registry.py +0 -64
  258. flock/core/registry/config_mapping.py +0 -64
  259. flock/core/registry/decorators.py +0 -137
  260. flock/core/registry/registry_hub.py +0 -205
  261. flock/core/registry/server_registry.py +0 -57
  262. flock/core/registry/type_registry.py +0 -86
  263. flock/core/serialization/__init__.py +0 -13
  264. flock/core/serialization/callable_registry.py +0 -52
  265. flock/core/serialization/flock_serializer.py +0 -832
  266. flock/core/serialization/json_encoder.py +0 -41
  267. flock/core/serialization/secure_serializer.py +0 -175
  268. flock/core/serialization/serializable.py +0 -342
  269. flock/core/serialization/serialization_utils.py +0 -412
  270. flock/core/util/file_path_utils.py +0 -223
  271. flock/core/util/hydrator.py +0 -309
  272. flock/core/util/input_resolver.py +0 -164
  273. flock/core/util/loader.py +0 -59
  274. flock/core/util/splitter.py +0 -219
  275. flock/di.py +0 -27
  276. flock/platform/docker_tools.py +0 -49
  277. flock/platform/jaeger_install.py +0 -86
  278. flock/webapp/__init__.py +0 -1
  279. flock/webapp/app/__init__.py +0 -0
  280. flock/webapp/app/api/__init__.py +0 -0
  281. flock/webapp/app/api/agent_management.py +0 -241
  282. flock/webapp/app/api/execution.py +0 -709
  283. flock/webapp/app/api/flock_management.py +0 -129
  284. flock/webapp/app/api/registry_viewer.py +0 -30
  285. flock/webapp/app/chat.py +0 -665
  286. flock/webapp/app/config.py +0 -104
  287. flock/webapp/app/dependencies.py +0 -117
  288. flock/webapp/app/main.py +0 -1070
  289. flock/webapp/app/middleware.py +0 -113
  290. flock/webapp/app/models_ui.py +0 -7
  291. flock/webapp/app/services/__init__.py +0 -0
  292. flock/webapp/app/services/feedback_file_service.py +0 -363
  293. flock/webapp/app/services/flock_service.py +0 -337
  294. flock/webapp/app/services/sharing_models.py +0 -81
  295. flock/webapp/app/services/sharing_store.py +0 -762
  296. flock/webapp/app/templates/theme_mapper.html +0 -326
  297. flock/webapp/app/theme_mapper.py +0 -812
  298. flock/webapp/app/utils.py +0 -85
  299. flock/webapp/run.py +0 -215
  300. flock/webapp/static/css/chat.css +0 -301
  301. flock/webapp/static/css/components.css +0 -167
  302. flock/webapp/static/css/header.css +0 -39
  303. flock/webapp/static/css/layout.css +0 -46
  304. flock/webapp/static/css/sidebar.css +0 -127
  305. flock/webapp/static/css/two-pane.css +0 -48
  306. flock/webapp/templates/base.html +0 -200
  307. flock/webapp/templates/chat.html +0 -152
  308. flock/webapp/templates/chat_settings.html +0 -19
  309. flock/webapp/templates/flock_editor.html +0 -16
  310. flock/webapp/templates/index.html +0 -12
  311. flock/webapp/templates/partials/_agent_detail_form.html +0 -93
  312. flock/webapp/templates/partials/_agent_list.html +0 -18
  313. flock/webapp/templates/partials/_agent_manager_view.html +0 -51
  314. flock/webapp/templates/partials/_agent_tools_checklist.html +0 -14
  315. flock/webapp/templates/partials/_chat_container.html +0 -15
  316. flock/webapp/templates/partials/_chat_messages.html +0 -57
  317. flock/webapp/templates/partials/_chat_settings_form.html +0 -85
  318. flock/webapp/templates/partials/_create_flock_form.html +0 -50
  319. flock/webapp/templates/partials/_dashboard_flock_detail.html +0 -17
  320. flock/webapp/templates/partials/_dashboard_flock_file_list.html +0 -16
  321. flock/webapp/templates/partials/_dashboard_flock_properties_preview.html +0 -28
  322. flock/webapp/templates/partials/_dashboard_upload_flock_form.html +0 -16
  323. flock/webapp/templates/partials/_dynamic_input_form_content.html +0 -22
  324. flock/webapp/templates/partials/_env_vars_table.html +0 -23
  325. flock/webapp/templates/partials/_execution_form.html +0 -118
  326. flock/webapp/templates/partials/_execution_view_container.html +0 -28
  327. flock/webapp/templates/partials/_flock_file_list.html +0 -23
  328. flock/webapp/templates/partials/_flock_properties_form.html +0 -52
  329. flock/webapp/templates/partials/_flock_upload_form.html +0 -16
  330. flock/webapp/templates/partials/_header_flock_status.html +0 -5
  331. flock/webapp/templates/partials/_load_manager_view.html +0 -49
  332. flock/webapp/templates/partials/_registry_table.html +0 -25
  333. flock/webapp/templates/partials/_registry_viewer_content.html +0 -70
  334. flock/webapp/templates/partials/_results_display.html +0 -78
  335. flock/webapp/templates/partials/_settings_env_content.html +0 -9
  336. flock/webapp/templates/partials/_settings_theme_content.html +0 -14
  337. flock/webapp/templates/partials/_settings_view.html +0 -36
  338. flock/webapp/templates/partials/_share_chat_link_snippet.html +0 -11
  339. flock/webapp/templates/partials/_share_link_snippet.html +0 -35
  340. flock/webapp/templates/partials/_sidebar.html +0 -74
  341. flock/webapp/templates/partials/_streaming_results_container.html +0 -195
  342. flock/webapp/templates/partials/_structured_data_view.html +0 -40
  343. flock/webapp/templates/partials/_theme_preview.html +0 -36
  344. flock/webapp/templates/registry_viewer.html +0 -84
  345. flock/webapp/templates/shared_run_page.html +0 -140
  346. flock/workflow/__init__.py +0 -0
  347. flock/workflow/activities.py +0 -196
  348. flock/workflow/agent_activities.py +0 -24
  349. flock/workflow/agent_execution_activity.py +0 -202
  350. flock/workflow/flock_workflow.py +0 -214
  351. flock/workflow/temporal_config.py +0 -96
  352. flock/workflow/temporal_setup.py +0 -68
  353. flock_core-0.5.0b28.dist-info/METADATA +0 -274
  354. flock_core-0.5.0b28.dist-info/RECORD +0 -561
  355. flock_core-0.5.0b28.dist-info/entry_points.txt +0 -2
  356. /flock/{core/logging → logging}/formatters/themes.py +0 -0
  357. /flock/{core/logging → logging}/span_middleware/baggage_span_processor.py +0 -0
  358. /flock/{core/mcp → mcp}/util/__init__.py +0 -0
  359. {flock_core-0.5.0b28.dist-info → flock_core-0.5.56b0.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"]