flock-core 0.4.543__py3-none-any.whl → 0.5.0__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 (501) hide show
  1. flock/__init__.py +12 -217
  2. flock/agent.py +1079 -0
  3. flock/api/themes.py +71 -0
  4. flock/artifacts.py +86 -0
  5. flock/cli.py +147 -0
  6. flock/components.py +189 -0
  7. flock/dashboard/__init__.py +30 -0
  8. flock/dashboard/collector.py +559 -0
  9. flock/dashboard/events.py +188 -0
  10. flock/dashboard/graph_builder.py +563 -0
  11. flock/dashboard/launcher.py +235 -0
  12. flock/dashboard/models/graph.py +156 -0
  13. flock/dashboard/service.py +991 -0
  14. flock/dashboard/static_v2/assets/index-DFRnI_mt.js +111 -0
  15. flock/dashboard/static_v2/assets/index-fPLNdmp1.css +1 -0
  16. flock/dashboard/static_v2/index.html +13 -0
  17. flock/dashboard/websocket.py +246 -0
  18. flock/engines/__init__.py +6 -0
  19. flock/engines/dspy_engine.py +932 -0
  20. flock/examples.py +131 -0
  21. flock/frontend/README.md +778 -0
  22. flock/frontend/docs/DESIGN_SYSTEM.md +1980 -0
  23. flock/frontend/index.html +12 -0
  24. flock/frontend/package-lock.json +4337 -0
  25. flock/frontend/package.json +48 -0
  26. flock/frontend/src/App.tsx +139 -0
  27. flock/frontend/src/__tests__/integration/graph-snapshot.test.tsx +647 -0
  28. flock/frontend/src/__tests__/integration/indexeddb-persistence.test.tsx +699 -0
  29. flock/frontend/src/components/common/BuildInfo.tsx +39 -0
  30. flock/frontend/src/components/common/EmptyState.module.css +115 -0
  31. flock/frontend/src/components/common/EmptyState.tsx +128 -0
  32. flock/frontend/src/components/common/ErrorBoundary.module.css +169 -0
  33. flock/frontend/src/components/common/ErrorBoundary.tsx +118 -0
  34. flock/frontend/src/components/common/KeyboardShortcutsDialog.css +251 -0
  35. flock/frontend/src/components/common/KeyboardShortcutsDialog.tsx +151 -0
  36. flock/frontend/src/components/common/LoadingSpinner.module.css +97 -0
  37. flock/frontend/src/components/common/LoadingSpinner.tsx +29 -0
  38. flock/frontend/src/components/controls/PublishControl.css +547 -0
  39. flock/frontend/src/components/controls/PublishControl.test.tsx +543 -0
  40. flock/frontend/src/components/controls/PublishControl.tsx +432 -0
  41. flock/frontend/src/components/details/DetailWindowContainer.tsx +58 -0
  42. flock/frontend/src/components/details/LiveOutputTab.test.tsx +792 -0
  43. flock/frontend/src/components/details/LiveOutputTab.tsx +220 -0
  44. flock/frontend/src/components/details/MessageDetailWindow.tsx +439 -0
  45. flock/frontend/src/components/details/MessageHistoryTab.tsx +374 -0
  46. flock/frontend/src/components/details/NodeDetailWindow.test.tsx +501 -0
  47. flock/frontend/src/components/details/NodeDetailWindow.tsx +218 -0
  48. flock/frontend/src/components/details/RunStatusTab.tsx +348 -0
  49. flock/frontend/src/components/details/tabs.test.tsx +1015 -0
  50. flock/frontend/src/components/filters/ArtifactTypeFilter.tsx +21 -0
  51. flock/frontend/src/components/filters/CorrelationIDFilter.module.css +102 -0
  52. flock/frontend/src/components/filters/CorrelationIDFilter.test.tsx +197 -0
  53. flock/frontend/src/components/filters/CorrelationIDFilter.tsx +121 -0
  54. flock/frontend/src/components/filters/FilterFlyout.module.css +104 -0
  55. flock/frontend/src/components/filters/FilterFlyout.tsx +80 -0
  56. flock/frontend/src/components/filters/FilterPills.module.css +220 -0
  57. flock/frontend/src/components/filters/FilterPills.test.tsx +189 -0
  58. flock/frontend/src/components/filters/FilterPills.tsx +143 -0
  59. flock/frontend/src/components/filters/ProducerFilter.tsx +21 -0
  60. flock/frontend/src/components/filters/SavedFiltersControl.module.css +60 -0
  61. flock/frontend/src/components/filters/SavedFiltersControl.test.tsx +158 -0
  62. flock/frontend/src/components/filters/SavedFiltersControl.tsx +159 -0
  63. flock/frontend/src/components/filters/TagFilter.tsx +21 -0
  64. flock/frontend/src/components/filters/TimeRangeFilter.module.css +115 -0
  65. flock/frontend/src/components/filters/TimeRangeFilter.test.tsx +154 -0
  66. flock/frontend/src/components/filters/TimeRangeFilter.tsx +110 -0
  67. flock/frontend/src/components/filters/VisibilityFilter.tsx +21 -0
  68. flock/frontend/src/components/graph/AgentNode.test.tsx +77 -0
  69. flock/frontend/src/components/graph/AgentNode.tsx +324 -0
  70. flock/frontend/src/components/graph/GraphCanvas.tsx +613 -0
  71. flock/frontend/src/components/graph/MessageFlowEdge.tsx +128 -0
  72. flock/frontend/src/components/graph/MessageNode.test.tsx +64 -0
  73. flock/frontend/src/components/graph/MessageNode.tsx +129 -0
  74. flock/frontend/src/components/graph/MiniMap.tsx +47 -0
  75. flock/frontend/src/components/graph/TransformEdge.tsx +123 -0
  76. flock/frontend/src/components/layout/DashboardLayout.css +420 -0
  77. flock/frontend/src/components/layout/DashboardLayout.tsx +287 -0
  78. flock/frontend/src/components/layout/Header.module.css +88 -0
  79. flock/frontend/src/components/layout/Header.tsx +52 -0
  80. flock/frontend/src/components/modules/HistoricalArtifactsModule.module.css +288 -0
  81. flock/frontend/src/components/modules/HistoricalArtifactsModule.tsx +450 -0
  82. flock/frontend/src/components/modules/HistoricalArtifactsModuleWrapper.tsx +13 -0
  83. flock/frontend/src/components/modules/JsonAttributeRenderer.tsx +140 -0
  84. flock/frontend/src/components/modules/ModuleRegistry.test.ts +333 -0
  85. flock/frontend/src/components/modules/ModuleRegistry.ts +93 -0
  86. flock/frontend/src/components/modules/ModuleWindow.tsx +223 -0
  87. flock/frontend/src/components/modules/TraceModuleJaeger.tsx +1971 -0
  88. flock/frontend/src/components/modules/TraceModuleJaegerWrapper.tsx +13 -0
  89. flock/frontend/src/components/modules/registerModules.ts +29 -0
  90. flock/frontend/src/components/settings/AdvancedSettings.tsx +175 -0
  91. flock/frontend/src/components/settings/AppearanceSettings.tsx +185 -0
  92. flock/frontend/src/components/settings/GraphSettings.tsx +110 -0
  93. flock/frontend/src/components/settings/MultiSelect.tsx +235 -0
  94. flock/frontend/src/components/settings/SettingsPanel.css +327 -0
  95. flock/frontend/src/components/settings/SettingsPanel.tsx +131 -0
  96. flock/frontend/src/components/settings/ThemeSelector.tsx +298 -0
  97. flock/frontend/src/components/settings/TracingSettings.tsx +404 -0
  98. flock/frontend/src/hooks/useKeyboardShortcuts.ts +148 -0
  99. flock/frontend/src/hooks/useModulePersistence.test.ts +442 -0
  100. flock/frontend/src/hooks/useModulePersistence.ts +154 -0
  101. flock/frontend/src/hooks/useModules.ts +157 -0
  102. flock/frontend/src/hooks/usePersistence.ts +141 -0
  103. flock/frontend/src/main.tsx +13 -0
  104. flock/frontend/src/services/api.ts +337 -0
  105. flock/frontend/src/services/graphService.test.ts +330 -0
  106. flock/frontend/src/services/graphService.ts +75 -0
  107. flock/frontend/src/services/indexeddb.test.ts +793 -0
  108. flock/frontend/src/services/indexeddb.ts +848 -0
  109. flock/frontend/src/services/layout.test.ts +437 -0
  110. flock/frontend/src/services/layout.ts +357 -0
  111. flock/frontend/src/services/themeApplicator.ts +140 -0
  112. flock/frontend/src/services/themeService.ts +77 -0
  113. flock/frontend/src/services/websocket.ts +650 -0
  114. flock/frontend/src/store/filterStore.test.ts +250 -0
  115. flock/frontend/src/store/filterStore.ts +272 -0
  116. flock/frontend/src/store/graphStore.test.ts +570 -0
  117. flock/frontend/src/store/graphStore.ts +462 -0
  118. flock/frontend/src/store/moduleStore.test.ts +253 -0
  119. flock/frontend/src/store/moduleStore.ts +75 -0
  120. flock/frontend/src/store/settingsStore.ts +188 -0
  121. flock/frontend/src/store/streamStore.ts +68 -0
  122. flock/frontend/src/store/uiStore.test.ts +54 -0
  123. flock/frontend/src/store/uiStore.ts +122 -0
  124. flock/frontend/src/store/wsStore.ts +34 -0
  125. flock/frontend/src/styles/index.css +15 -0
  126. flock/frontend/src/styles/scrollbar.css +47 -0
  127. flock/frontend/src/styles/variables.css +488 -0
  128. flock/frontend/src/test/setup.ts +1 -0
  129. flock/frontend/src/types/filters.ts +47 -0
  130. flock/frontend/src/types/graph.ts +95 -0
  131. flock/frontend/src/types/modules.ts +10 -0
  132. flock/frontend/src/types/theme.ts +55 -0
  133. flock/frontend/src/utils/artifacts.ts +24 -0
  134. flock/frontend/src/utils/mockData.ts +98 -0
  135. flock/frontend/src/utils/performance.ts +16 -0
  136. flock/frontend/src/vite-env.d.ts +17 -0
  137. flock/frontend/tsconfig.json +27 -0
  138. flock/frontend/tsconfig.node.json +11 -0
  139. flock/frontend/vite.config.ts +25 -0
  140. flock/frontend/vitest.config.ts +11 -0
  141. flock/{core/util → helper}/cli_helper.py +9 -5
  142. flock/{core/logging → logging}/__init__.py +2 -3
  143. flock/logging/auto_trace.py +159 -0
  144. flock/{core/logging → logging}/formatters/enum_builder.py +3 -4
  145. flock/{core/logging → logging}/formatters/theme_builder.py +19 -44
  146. flock/{core/logging → logging}/formatters/themed_formatter.py +69 -107
  147. flock/{core/logging → logging}/logging.py +78 -61
  148. flock/{core/logging → logging}/telemetry.py +66 -26
  149. flock/{core/logging → logging}/telemetry_exporter/base_exporter.py +2 -2
  150. flock/logging/telemetry_exporter/duckdb_exporter.py +216 -0
  151. flock/{core/logging → logging}/telemetry_exporter/file_exporter.py +13 -10
  152. flock/{core/logging → logging}/telemetry_exporter/sqlite_exporter.py +2 -3
  153. flock/logging/trace_and_logged.py +304 -0
  154. flock/mcp/__init__.py +91 -0
  155. flock/{core/mcp/mcp_client.py → mcp/client.py} +131 -158
  156. flock/{core/mcp/mcp_config.py → mcp/config.py} +86 -132
  157. flock/mcp/manager.py +286 -0
  158. flock/mcp/servers/sse/__init__.py +1 -1
  159. flock/mcp/servers/sse/flock_sse_server.py +16 -58
  160. flock/mcp/servers/stdio/__init__.py +1 -1
  161. flock/mcp/servers/stdio/flock_stdio_server.py +13 -53
  162. flock/mcp/servers/streamable_http/flock_streamable_http_server.py +22 -67
  163. flock/mcp/servers/websockets/flock_websocket_server.py +12 -45
  164. flock/{core/mcp/flock_mcp_tool_base.py → mcp/tool.py} +24 -78
  165. flock/mcp/types/__init__.py +42 -0
  166. flock/{core/mcp → mcp}/types/callbacks.py +12 -15
  167. flock/{core/mcp → mcp}/types/factories.py +7 -6
  168. flock/{core/mcp → mcp}/types/handlers.py +13 -18
  169. flock/{core/mcp → mcp}/types/types.py +70 -74
  170. flock/{core/mcp → mcp}/util/helpers.py +3 -3
  171. flock/orchestrator.py +970 -0
  172. flock/registry.py +148 -0
  173. flock/runtime.py +262 -0
  174. flock/service.py +277 -0
  175. flock/store.py +1214 -0
  176. flock/subscription.py +111 -0
  177. flock/themes/andromeda.toml +1 -1
  178. flock/themes/apple-system-colors.toml +1 -1
  179. flock/themes/arcoiris.toml +1 -1
  180. flock/themes/atomonelight.toml +1 -1
  181. flock/themes/ayu copy.toml +1 -1
  182. flock/themes/ayu-light.toml +1 -1
  183. flock/themes/belafonte-day.toml +1 -1
  184. flock/themes/belafonte-night.toml +1 -1
  185. flock/themes/blulocodark.toml +1 -1
  186. flock/themes/breeze.toml +1 -1
  187. flock/themes/broadcast.toml +1 -1
  188. flock/themes/brogrammer.toml +1 -1
  189. flock/themes/builtin-dark.toml +1 -1
  190. flock/themes/builtin-pastel-dark.toml +1 -1
  191. flock/themes/catppuccin-latte.toml +1 -1
  192. flock/themes/catppuccin-macchiato.toml +1 -1
  193. flock/themes/catppuccin-mocha.toml +1 -1
  194. flock/themes/cga.toml +1 -1
  195. flock/themes/chalk.toml +1 -1
  196. flock/themes/ciapre.toml +1 -1
  197. flock/themes/coffee-theme.toml +1 -1
  198. flock/themes/cyberpunkscarletprotocol.toml +1 -1
  199. flock/themes/dark+.toml +1 -1
  200. flock/themes/darkermatrix.toml +1 -1
  201. flock/themes/darkmatrix.toml +2 -2
  202. flock/themes/darkside.toml +1 -1
  203. flock/themes/deep.toml +2 -2
  204. flock/themes/desert.toml +1 -1
  205. flock/themes/django.toml +1 -1
  206. flock/themes/djangosmooth.toml +1 -1
  207. flock/themes/doomone.toml +1 -1
  208. flock/themes/dotgov.toml +1 -1
  209. flock/themes/dracula+.toml +1 -1
  210. flock/themes/duckbones.toml +1 -1
  211. flock/themes/encom.toml +1 -1
  212. flock/themes/espresso.toml +1 -1
  213. flock/themes/everblush.toml +1 -1
  214. flock/themes/fairyfloss.toml +1 -1
  215. flock/themes/fideloper.toml +1 -1
  216. flock/themes/fishtank.toml +1 -1
  217. flock/themes/flexoki-light.toml +1 -1
  218. flock/themes/floraverse.toml +1 -1
  219. flock/themes/framer.toml +1 -1
  220. flock/themes/galizur.toml +1 -1
  221. flock/themes/github.toml +1 -1
  222. flock/themes/grass.toml +1 -1
  223. flock/themes/grey-green.toml +1 -1
  224. flock/themes/gruvboxlight.toml +1 -1
  225. flock/themes/guezwhoz.toml +1 -1
  226. flock/themes/harper.toml +1 -1
  227. flock/themes/hax0r-blue.toml +1 -1
  228. flock/themes/hopscotch.256.toml +1 -1
  229. flock/themes/ic-green-ppl.toml +1 -1
  230. flock/themes/iceberg-dark.toml +1 -1
  231. flock/themes/japanesque.toml +1 -1
  232. flock/themes/jubi.toml +1 -1
  233. flock/themes/kibble.toml +1 -1
  234. flock/themes/kolorit.toml +1 -1
  235. flock/themes/kurokula.toml +1 -1
  236. flock/themes/materialdesigncolors.toml +1 -1
  237. flock/themes/matrix.toml +1 -1
  238. flock/themes/mellifluous.toml +1 -1
  239. flock/themes/midnight-in-mojave.toml +1 -1
  240. flock/themes/monokai-remastered.toml +1 -1
  241. flock/themes/monokai-soda.toml +1 -1
  242. flock/themes/neon.toml +1 -1
  243. flock/themes/neopolitan.toml +5 -5
  244. flock/themes/nord-light.toml +1 -1
  245. flock/themes/ocean.toml +1 -1
  246. flock/themes/onehalfdark.toml +1 -1
  247. flock/themes/onehalflight.toml +1 -1
  248. flock/themes/palenighthc.toml +1 -1
  249. flock/themes/paulmillr.toml +1 -1
  250. flock/themes/pencildark.toml +1 -1
  251. flock/themes/pnevma.toml +1 -1
  252. flock/themes/purple-rain.toml +1 -1
  253. flock/themes/purplepeter.toml +1 -1
  254. flock/themes/raycast-dark.toml +1 -1
  255. flock/themes/red-sands.toml +1 -1
  256. flock/themes/relaxed.toml +1 -1
  257. flock/themes/retro.toml +1 -1
  258. flock/themes/rose-pine.toml +1 -1
  259. flock/themes/royal.toml +1 -1
  260. flock/themes/ryuuko.toml +1 -1
  261. flock/themes/sakura.toml +1 -1
  262. flock/themes/scarlet-protocol.toml +1 -1
  263. flock/themes/seoulbones-dark.toml +1 -1
  264. flock/themes/shades-of-purple.toml +1 -1
  265. flock/themes/smyck.toml +1 -1
  266. flock/themes/softserver.toml +1 -1
  267. flock/themes/solarized-darcula.toml +1 -1
  268. flock/themes/square.toml +1 -1
  269. flock/themes/sugarplum.toml +1 -1
  270. flock/themes/thayer-bright.toml +1 -1
  271. flock/themes/tokyonight.toml +1 -1
  272. flock/themes/tomorrow.toml +1 -1
  273. flock/themes/ubuntu.toml +1 -1
  274. flock/themes/ultradark.toml +1 -1
  275. flock/themes/ultraviolent.toml +1 -1
  276. flock/themes/unikitty.toml +1 -1
  277. flock/themes/urple.toml +1 -1
  278. flock/themes/vesper.toml +1 -1
  279. flock/themes/vimbones.toml +1 -1
  280. flock/themes/wildcherry.toml +1 -1
  281. flock/themes/wilmersdorf.toml +1 -1
  282. flock/themes/wryan.toml +1 -1
  283. flock/themes/xcodedarkhc.toml +1 -1
  284. flock/themes/xcodelight.toml +1 -1
  285. flock/themes/zenbones-light.toml +1 -1
  286. flock/themes/zenwritten-dark.toml +1 -1
  287. flock/utilities.py +301 -0
  288. flock/utility/output_utility_component.py +226 -0
  289. flock/visibility.py +107 -0
  290. flock_core-0.5.0.dist-info/METADATA +964 -0
  291. flock_core-0.5.0.dist-info/RECORD +525 -0
  292. flock_core-0.5.0.dist-info/entry_points.txt +2 -0
  293. {flock_core-0.4.543.dist-info → flock_core-0.5.0.dist-info}/licenses/LICENSE +1 -1
  294. flock/adapter/__init__.py +0 -14
  295. flock/adapter/azure_adapter.py +0 -68
  296. flock/adapter/chroma_adapter.py +0 -73
  297. flock/adapter/faiss_adapter.py +0 -97
  298. flock/adapter/pinecone_adapter.py +0 -51
  299. flock/adapter/vector_base.py +0 -47
  300. flock/cli/assets/release_notes.md +0 -140
  301. flock/cli/config.py +0 -8
  302. flock/cli/constants.py +0 -36
  303. flock/cli/create_agent.py +0 -1
  304. flock/cli/create_flock.py +0 -280
  305. flock/cli/execute_flock.py +0 -620
  306. flock/cli/load_agent.py +0 -1
  307. flock/cli/load_examples.py +0 -1
  308. flock/cli/load_flock.py +0 -192
  309. flock/cli/load_release_notes.py +0 -20
  310. flock/cli/loaded_flock_cli.py +0 -254
  311. flock/cli/manage_agents.py +0 -459
  312. flock/cli/registry_management.py +0 -889
  313. flock/cli/runner.py +0 -41
  314. flock/cli/settings.py +0 -857
  315. flock/cli/utils.py +0 -135
  316. flock/cli/view_results.py +0 -29
  317. flock/cli/yaml_editor.py +0 -396
  318. flock/config.py +0 -56
  319. flock/core/__init__.py +0 -44
  320. flock/core/api/__init__.py +0 -10
  321. flock/core/api/custom_endpoint.py +0 -45
  322. flock/core/api/endpoints.py +0 -262
  323. flock/core/api/main.py +0 -162
  324. flock/core/api/models.py +0 -101
  325. flock/core/api/run_store.py +0 -224
  326. flock/core/api/runner.py +0 -44
  327. flock/core/api/service.py +0 -214
  328. flock/core/config/flock_agent_config.py +0 -11
  329. flock/core/config/scheduled_agent_config.py +0 -40
  330. flock/core/context/context.py +0 -214
  331. flock/core/context/context_manager.py +0 -40
  332. flock/core/context/context_vars.py +0 -11
  333. flock/core/evaluation/utils.py +0 -395
  334. flock/core/execution/batch_executor.py +0 -369
  335. flock/core/execution/evaluation_executor.py +0 -438
  336. flock/core/execution/local_executor.py +0 -31
  337. flock/core/execution/opik_executor.py +0 -103
  338. flock/core/execution/temporal_executor.py +0 -166
  339. flock/core/flock.py +0 -1003
  340. flock/core/flock_agent.py +0 -1258
  341. flock/core/flock_evaluator.py +0 -60
  342. flock/core/flock_factory.py +0 -513
  343. flock/core/flock_module.py +0 -207
  344. flock/core/flock_registry.py +0 -702
  345. flock/core/flock_router.py +0 -83
  346. flock/core/flock_scheduler.py +0 -166
  347. flock/core/flock_server_manager.py +0 -136
  348. flock/core/interpreter/python_interpreter.py +0 -689
  349. flock/core/logging/live_capture.py +0 -137
  350. flock/core/logging/trace_and_logged.py +0 -59
  351. flock/core/mcp/__init__.py +0 -1
  352. flock/core/mcp/flock_mcp_server.py +0 -640
  353. flock/core/mcp/mcp_client_manager.py +0 -201
  354. flock/core/mcp/types/__init__.py +0 -1
  355. flock/core/mixin/dspy_integration.py +0 -445
  356. flock/core/mixin/prompt_parser.py +0 -125
  357. flock/core/serialization/__init__.py +0 -13
  358. flock/core/serialization/callable_registry.py +0 -52
  359. flock/core/serialization/flock_serializer.py +0 -854
  360. flock/core/serialization/json_encoder.py +0 -41
  361. flock/core/serialization/secure_serializer.py +0 -175
  362. flock/core/serialization/serializable.py +0 -342
  363. flock/core/serialization/serialization_utils.py +0 -409
  364. flock/core/util/file_path_utils.py +0 -223
  365. flock/core/util/hydrator.py +0 -309
  366. flock/core/util/input_resolver.py +0 -141
  367. flock/core/util/loader.py +0 -59
  368. flock/core/util/splitter.py +0 -219
  369. flock/di.py +0 -41
  370. flock/evaluators/__init__.py +0 -1
  371. flock/evaluators/declarative/__init__.py +0 -1
  372. flock/evaluators/declarative/declarative_evaluator.py +0 -217
  373. flock/evaluators/memory/memory_evaluator.py +0 -90
  374. flock/evaluators/test/test_case_evaluator.py +0 -38
  375. flock/evaluators/zep/zep_evaluator.py +0 -59
  376. flock/modules/__init__.py +0 -1
  377. flock/modules/assertion/__init__.py +0 -1
  378. flock/modules/assertion/assertion_module.py +0 -286
  379. flock/modules/callback/__init__.py +0 -1
  380. flock/modules/callback/callback_module.py +0 -91
  381. flock/modules/enterprise_memory/README.md +0 -99
  382. flock/modules/enterprise_memory/enterprise_memory_module.py +0 -526
  383. flock/modules/mem0/__init__.py +0 -1
  384. flock/modules/mem0/mem0_module.py +0 -126
  385. flock/modules/mem0_async/__init__.py +0 -1
  386. flock/modules/mem0_async/async_mem0_module.py +0 -126
  387. flock/modules/memory/__init__.py +0 -1
  388. flock/modules/memory/memory_module.py +0 -429
  389. flock/modules/memory/memory_parser.py +0 -125
  390. flock/modules/memory/memory_storage.py +0 -736
  391. flock/modules/output/__init__.py +0 -1
  392. flock/modules/output/output_module.py +0 -196
  393. flock/modules/performance/__init__.py +0 -1
  394. flock/modules/performance/metrics_module.py +0 -678
  395. flock/modules/zep/__init__.py +0 -1
  396. flock/modules/zep/zep_module.py +0 -192
  397. flock/platform/docker_tools.py +0 -49
  398. flock/platform/jaeger_install.py +0 -86
  399. flock/routers/__init__.py +0 -1
  400. flock/routers/agent/__init__.py +0 -1
  401. flock/routers/agent/agent_router.py +0 -236
  402. flock/routers/agent/handoff_agent.py +0 -58
  403. flock/routers/conditional/conditional_router.py +0 -486
  404. flock/routers/default/__init__.py +0 -1
  405. flock/routers/default/default_router.py +0 -80
  406. flock/routers/feedback/feedback_router.py +0 -114
  407. flock/routers/list_generator/list_generator_router.py +0 -166
  408. flock/routers/llm/__init__.py +0 -1
  409. flock/routers/llm/llm_router.py +0 -365
  410. flock/tools/__init__.py +0 -0
  411. flock/tools/azure_tools.py +0 -781
  412. flock/tools/code_tools.py +0 -167
  413. flock/tools/file_tools.py +0 -149
  414. flock/tools/github_tools.py +0 -157
  415. flock/tools/markdown_tools.py +0 -205
  416. flock/tools/system_tools.py +0 -9
  417. flock/tools/text_tools.py +0 -810
  418. flock/tools/web_tools.py +0 -92
  419. flock/tools/zendesk_tools.py +0 -501
  420. flock/webapp/__init__.py +0 -1
  421. flock/webapp/app/__init__.py +0 -0
  422. flock/webapp/app/api/__init__.py +0 -0
  423. flock/webapp/app/api/agent_management.py +0 -237
  424. flock/webapp/app/api/execution.py +0 -503
  425. flock/webapp/app/api/flock_management.py +0 -125
  426. flock/webapp/app/api/registry_viewer.py +0 -29
  427. flock/webapp/app/chat.py +0 -662
  428. flock/webapp/app/config.py +0 -104
  429. flock/webapp/app/dependencies.py +0 -117
  430. flock/webapp/app/main.py +0 -1086
  431. flock/webapp/app/middleware.py +0 -113
  432. flock/webapp/app/models_ui.py +0 -7
  433. flock/webapp/app/services/__init__.py +0 -0
  434. flock/webapp/app/services/feedback_file_service.py +0 -363
  435. flock/webapp/app/services/flock_service.py +0 -345
  436. flock/webapp/app/services/sharing_models.py +0 -81
  437. flock/webapp/app/services/sharing_store.py +0 -597
  438. flock/webapp/app/templates/theme_mapper.html +0 -326
  439. flock/webapp/app/theme_mapper.py +0 -811
  440. flock/webapp/app/utils.py +0 -85
  441. flock/webapp/run.py +0 -219
  442. flock/webapp/static/css/chat.css +0 -301
  443. flock/webapp/static/css/components.css +0 -167
  444. flock/webapp/static/css/header.css +0 -39
  445. flock/webapp/static/css/layout.css +0 -281
  446. flock/webapp/static/css/sidebar.css +0 -127
  447. flock/webapp/static/css/two-pane.css +0 -48
  448. flock/webapp/templates/base.html +0 -389
  449. flock/webapp/templates/chat.html +0 -152
  450. flock/webapp/templates/chat_settings.html +0 -19
  451. flock/webapp/templates/flock_editor.html +0 -16
  452. flock/webapp/templates/index.html +0 -12
  453. flock/webapp/templates/partials/_agent_detail_form.html +0 -93
  454. flock/webapp/templates/partials/_agent_list.html +0 -18
  455. flock/webapp/templates/partials/_agent_manager_view.html +0 -51
  456. flock/webapp/templates/partials/_agent_tools_checklist.html +0 -14
  457. flock/webapp/templates/partials/_chat_container.html +0 -15
  458. flock/webapp/templates/partials/_chat_messages.html +0 -57
  459. flock/webapp/templates/partials/_chat_settings_form.html +0 -85
  460. flock/webapp/templates/partials/_create_flock_form.html +0 -50
  461. flock/webapp/templates/partials/_dashboard_flock_detail.html +0 -17
  462. flock/webapp/templates/partials/_dashboard_flock_file_list.html +0 -16
  463. flock/webapp/templates/partials/_dashboard_flock_properties_preview.html +0 -28
  464. flock/webapp/templates/partials/_dashboard_upload_flock_form.html +0 -16
  465. flock/webapp/templates/partials/_dynamic_input_form_content.html +0 -22
  466. flock/webapp/templates/partials/_env_vars_table.html +0 -23
  467. flock/webapp/templates/partials/_execution_form.html +0 -127
  468. flock/webapp/templates/partials/_execution_view_container.html +0 -28
  469. flock/webapp/templates/partials/_flock_file_list.html +0 -23
  470. flock/webapp/templates/partials/_flock_properties_form.html +0 -52
  471. flock/webapp/templates/partials/_flock_upload_form.html +0 -16
  472. flock/webapp/templates/partials/_header_flock_status.html +0 -5
  473. flock/webapp/templates/partials/_live_logs.html +0 -13
  474. flock/webapp/templates/partials/_load_manager_view.html +0 -49
  475. flock/webapp/templates/partials/_registry_table.html +0 -25
  476. flock/webapp/templates/partials/_registry_viewer_content.html +0 -70
  477. flock/webapp/templates/partials/_results_display.html +0 -78
  478. flock/webapp/templates/partials/_settings_env_content.html +0 -9
  479. flock/webapp/templates/partials/_settings_theme_content.html +0 -14
  480. flock/webapp/templates/partials/_settings_view.html +0 -36
  481. flock/webapp/templates/partials/_share_chat_link_snippet.html +0 -11
  482. flock/webapp/templates/partials/_share_link_snippet.html +0 -35
  483. flock/webapp/templates/partials/_sidebar.html +0 -74
  484. flock/webapp/templates/partials/_structured_data_view.html +0 -40
  485. flock/webapp/templates/partials/_theme_preview.html +0 -36
  486. flock/webapp/templates/registry_viewer.html +0 -84
  487. flock/webapp/templates/shared_run_page.html +0 -140
  488. flock/workflow/__init__.py +0 -0
  489. flock/workflow/activities.py +0 -237
  490. flock/workflow/agent_activities.py +0 -24
  491. flock/workflow/agent_execution_activity.py +0 -240
  492. flock/workflow/flock_workflow.py +0 -225
  493. flock/workflow/temporal_config.py +0 -96
  494. flock/workflow/temporal_setup.py +0 -60
  495. flock_core-0.4.543.dist-info/METADATA +0 -676
  496. flock_core-0.4.543.dist-info/RECORD +0 -572
  497. flock_core-0.4.543.dist-info/entry_points.txt +0 -2
  498. /flock/{core/logging → logging}/formatters/themes.py +0 -0
  499. /flock/{core/logging → logging}/span_middleware/baggage_span_processor.py +0 -0
  500. /flock/{core/mcp → mcp}/util/__init__.py +0 -0
  501. {flock_core-0.4.543.dist-info → flock_core-0.5.0.dist-info}/WHEEL +0 -0
flock/orchestrator.py ADDED
@@ -0,0 +1,970 @@
1
+ """Blackboard orchestrator and scheduling runtime."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import logging
7
+ import os
8
+ from asyncio import Task
9
+ from collections.abc import AsyncGenerator, Iterable, Mapping, Sequence
10
+ from contextlib import asynccontextmanager
11
+ from datetime import datetime, timezone
12
+ from pathlib import Path
13
+ from typing import TYPE_CHECKING, Any
14
+ from uuid import uuid4
15
+
16
+ from opentelemetry import trace
17
+ from opentelemetry.trace import Status, StatusCode
18
+ from pydantic import BaseModel
19
+
20
+ from flock.agent import Agent, AgentBuilder
21
+ from flock.artifacts import Artifact
22
+ from flock.helper.cli_helper import init_console
23
+ from flock.logging.auto_trace import AutoTracedMeta
24
+ from flock.mcp import (
25
+ FlockMCPClientManager,
26
+ FlockMCPConfiguration,
27
+ FlockMCPConnectionConfiguration,
28
+ FlockMCPFeatureConfiguration,
29
+ ServerParameters,
30
+ )
31
+ from flock.registry import type_registry
32
+ from flock.runtime import Context
33
+ from flock.store import BlackboardStore, ConsumptionRecord, InMemoryBlackboardStore
34
+ from flock.visibility import AgentIdentity, PublicVisibility, Visibility
35
+
36
+
37
+ if TYPE_CHECKING:
38
+ import builtins
39
+
40
+
41
+ class BoardHandle:
42
+ """Handle exposed to components for publishing and inspection."""
43
+
44
+ def __init__(self, orchestrator: Flock) -> None:
45
+ self._orchestrator = orchestrator
46
+
47
+ async def publish(self, artifact: Artifact) -> None:
48
+ await self._orchestrator._persist_and_schedule(artifact)
49
+
50
+ async def get(self, artifact_id) -> Artifact | None:
51
+ return await self._orchestrator.store.get(artifact_id)
52
+
53
+ async def list(self) -> builtins.list[Artifact]:
54
+ return await self._orchestrator.store.list()
55
+
56
+
57
+ class Flock(metaclass=AutoTracedMeta):
58
+ """Main orchestrator for blackboard-based agent coordination.
59
+
60
+ All public methods are automatically traced via OpenTelemetry.
61
+ """
62
+
63
+ def _patch_litellm_proxy_imports(self) -> None:
64
+ """Stub litellm proxy_server to avoid optional proxy deps when not used.
65
+
66
+ Some litellm versions import `litellm.proxy.proxy_server` during standard logging
67
+ to read `general_settings`, which pulls in optional dependencies like `apscheduler`.
68
+ We provide a stub so imports succeed but cold storage remains disabled.
69
+ """
70
+ try:
71
+ import sys
72
+ import types
73
+
74
+ if "litellm.proxy.proxy_server" not in sys.modules:
75
+ stub = types.ModuleType("litellm.proxy.proxy_server")
76
+ # Minimal surface that cold_storage_handler accesses
77
+ stub.general_settings = {}
78
+ sys.modules["litellm.proxy.proxy_server"] = stub
79
+ except Exception: # nosec B110 - Safe to ignore; worst case litellm will log a warning
80
+ # logger.debug(f"Failed to stub litellm proxy_server: {e}")
81
+ pass
82
+
83
+ def __init__(
84
+ self,
85
+ model: str | None = None,
86
+ *,
87
+ store: BlackboardStore | None = None,
88
+ max_agent_iterations: int = 1000,
89
+ ) -> None:
90
+ """Initialize the Flock orchestrator for blackboard-based agent coordination.
91
+
92
+ Args:
93
+ model: Default LLM model for agents (e.g., "openai/gpt-4.1").
94
+ Can be overridden per-agent. If None, uses DEFAULT_MODEL env var.
95
+ store: Custom blackboard storage backend. Defaults to InMemoryBlackboardStore.
96
+ max_agent_iterations: Circuit breaker limit to prevent runaway agent loops.
97
+ Defaults to 1000 iterations per agent before reset.
98
+
99
+ Examples:
100
+ >>> # Basic initialization with default model
101
+ >>> flock = Flock("openai/gpt-4.1")
102
+
103
+ >>> # Custom storage backend
104
+ >>> flock = Flock(
105
+ ... "openai/gpt-4o",
106
+ ... store=CustomBlackboardStore()
107
+ ... )
108
+
109
+ >>> # Circuit breaker configuration
110
+ >>> flock = Flock(
111
+ ... "openai/gpt-4.1",
112
+ ... max_agent_iterations=500
113
+ ... )
114
+ """
115
+ self._patch_litellm_proxy_imports()
116
+ self._logger = logging.getLogger(__name__)
117
+ self.model = model
118
+ self.store: BlackboardStore = store or InMemoryBlackboardStore()
119
+ self._agents: dict[str, Agent] = {}
120
+ self._tasks: set[Task[Any]] = set()
121
+ self._processed: set[tuple[str, str]] = set()
122
+ self._lock = asyncio.Lock()
123
+ self.metrics: dict[str, float] = {"artifacts_published": 0, "agent_runs": 0}
124
+ # MCP integration
125
+ self._mcp_configs: dict[str, FlockMCPConfiguration] = {}
126
+ self._mcp_manager: FlockMCPClientManager | None = None
127
+ # T068: Circuit breaker for runaway agents
128
+ self.max_agent_iterations: int = max_agent_iterations
129
+ self._agent_iteration_count: dict[str, int] = {}
130
+ self.is_dashboard: bool = False
131
+ # Unified tracing support
132
+ self._workflow_span = None
133
+ self._auto_workflow_enabled = os.getenv("FLOCK_AUTO_WORKFLOW_TRACE", "false").lower() in {
134
+ "true",
135
+ "1",
136
+ "yes",
137
+ "on",
138
+ }
139
+ if not model:
140
+ self.model = os.getenv("DEFAULT_MODEL")
141
+
142
+ # Agent management -----------------------------------------------------
143
+
144
+ def agent(self, name: str) -> AgentBuilder:
145
+ """Create a new agent using the fluent builder API.
146
+
147
+ Args:
148
+ name: Unique identifier for the agent. Used for visibility controls and metrics.
149
+
150
+ Returns:
151
+ AgentBuilder for fluent configuration
152
+
153
+ Raises:
154
+ ValueError: If an agent with this name already exists
155
+
156
+ Examples:
157
+ >>> # Basic agent
158
+ >>> pizza_agent = (
159
+ ... flock.agent("pizza_master")
160
+ ... .description("Creates delicious pizza recipes")
161
+ ... .consumes(DreamPizza)
162
+ ... .publishes(Pizza)
163
+ ... )
164
+
165
+ >>> # Advanced agent with filtering
166
+ >>> critic = (
167
+ ... flock.agent("critic")
168
+ ... .consumes(Movie, where=lambda m: m.rating >= 8)
169
+ ... .publishes(Review)
170
+ ... .with_utilities(RateLimiter(max_calls=10))
171
+ ... )
172
+ """
173
+ if name in self._agents:
174
+ raise ValueError(f"Agent '{name}' already registered.")
175
+ return AgentBuilder(self, name)
176
+
177
+ def register_agent(self, agent: Agent) -> None:
178
+ if agent.name in self._agents:
179
+ raise ValueError(f"Agent '{agent.name}' already registered.")
180
+ self._agents[agent.name] = agent
181
+
182
+ def get_agent(self, name: str) -> Agent:
183
+ return self._agents[name]
184
+
185
+ @property
186
+ def agents(self) -> list[Agent]:
187
+ return list(self._agents.values())
188
+
189
+ # MCP management -------------------------------------------------------
190
+
191
+ def add_mcp(
192
+ self,
193
+ name: str,
194
+ connection_params: ServerParameters,
195
+ *,
196
+ enable_tools_feature: bool = True,
197
+ enable_prompts_feature: bool = True,
198
+ enable_sampling_feature: bool = True,
199
+ enable_roots_feature: bool = True,
200
+ mount_points: list[str] | None = None,
201
+ tool_whitelist: list[str] | None = None,
202
+ read_timeout_seconds: float = 300,
203
+ max_retries: int = 3,
204
+ **kwargs,
205
+ ) -> Flock:
206
+ """Register an MCP server for use by agents.
207
+
208
+ Architecture Decision: AD001 - Two-Level Architecture
209
+ MCP servers are registered at orchestrator level and assigned to agents.
210
+
211
+ Args:
212
+ name: Unique identifier for this MCP server
213
+ connection_params: Server connection parameters
214
+ enable_tools_feature: Enable tool execution
215
+ enable_prompts_feature: Enable prompt templates
216
+ enable_sampling_feature: Enable LLM sampling requests
217
+ enable_roots_feature: Enable filesystem roots
218
+ tool_whitelist: Optional list of tool names to allow
219
+ read_timeout_seconds: Timeout for server communications
220
+ max_retries: Connection retry attempts
221
+
222
+ Returns:
223
+ self for method chaining
224
+
225
+ Raises:
226
+ ValueError: If server name already registered
227
+ """
228
+ if name in self._mcp_configs:
229
+ raise ValueError(f"MCP server '{name}' is already registered.")
230
+
231
+ # Detect transport type
232
+ from flock.mcp.types import (
233
+ SseServerParameters,
234
+ StdioServerParameters,
235
+ StreamableHttpServerParameters,
236
+ WebsocketServerParameters,
237
+ )
238
+
239
+ if isinstance(connection_params, StdioServerParameters):
240
+ transport_type = "stdio"
241
+ elif isinstance(connection_params, WebsocketServerParameters):
242
+ transport_type = "websockets"
243
+ elif isinstance(connection_params, SseServerParameters):
244
+ transport_type = "sse"
245
+ elif isinstance(connection_params, StreamableHttpServerParameters):
246
+ transport_type = "streamable_http"
247
+ else:
248
+ transport_type = "custom"
249
+
250
+ mcp_roots = None
251
+ if mount_points:
252
+ from pathlib import Path as PathLib
253
+
254
+ from flock.mcp.types import MCPRoot
255
+
256
+ mcp_roots = []
257
+ for path in mount_points:
258
+ # Normalize the path
259
+ if path.startswith("file://"):
260
+ # Already a file URI
261
+ uri = path
262
+ # Extract path from URI for name
263
+ path_str = path.replace("file://", "")
264
+ # the test:// path-prefix is used by testing servers such as the mcp-everything server.
265
+ elif path.startswith("test://"):
266
+ # Already a test URI
267
+ uri = path
268
+ # Extract path from URI for name
269
+ path_str = path.replace("test://", "")
270
+ else:
271
+ # Convert to absolute path and create URI
272
+ abs_path = PathLib(path).resolve()
273
+ uri = f"file://{abs_path}"
274
+ path_str = str(abs_path)
275
+
276
+ # Extract a meaningful name (last component of path)
277
+ name = PathLib(path_str).name or path_str.rstrip("/").split("/")[-1] or "root"
278
+ mcp_roots.append(MCPRoot(uri=uri, name=name))
279
+
280
+ # Build configuration
281
+ connection_config = FlockMCPConnectionConfiguration(
282
+ max_retries=max_retries,
283
+ connection_parameters=connection_params,
284
+ transport_type=transport_type,
285
+ read_timeout_seconds=read_timeout_seconds,
286
+ mount_points=mcp_roots,
287
+ )
288
+
289
+ feature_config = FlockMCPFeatureConfiguration(
290
+ tools_enabled=enable_tools_feature,
291
+ prompts_enabled=enable_prompts_feature,
292
+ sampling_enabled=enable_sampling_feature,
293
+ roots_enabled=enable_roots_feature,
294
+ tool_whitelist=tool_whitelist,
295
+ )
296
+
297
+ mcp_config = FlockMCPConfiguration(
298
+ name=name,
299
+ connection_config=connection_config,
300
+ feature_config=feature_config,
301
+ )
302
+
303
+ self._mcp_configs[name] = mcp_config
304
+ return self
305
+
306
+ def get_mcp_manager(self) -> FlockMCPClientManager:
307
+ """Get or create the MCP client manager.
308
+
309
+ Architecture Decision: AD005 - Lazy Connection Establishment
310
+ """
311
+ if not self._mcp_configs:
312
+ raise RuntimeError("No MCP servers registered. Call add_mcp() first.")
313
+
314
+ if self._mcp_manager is None:
315
+ self._mcp_manager = FlockMCPClientManager(self._mcp_configs)
316
+
317
+ return self._mcp_manager
318
+
319
+ # Unified Tracing ------------------------------------------------------
320
+
321
+ @asynccontextmanager
322
+ async def traced_run(self, name: str = "workflow") -> AsyncGenerator[Any, None]:
323
+ """Context manager for wrapping an entire execution in a single unified trace.
324
+
325
+ This creates a parent span that encompasses all operations (publish, run_until_idle, etc.)
326
+ within the context, ensuring they all belong to the same trace_id for better observability.
327
+
328
+ Args:
329
+ name: Name for the workflow trace (default: "workflow")
330
+
331
+ Yields:
332
+ The workflow span for optional manual attribute setting
333
+
334
+ Examples:
335
+ # Explicit workflow tracing (recommended)
336
+ async with flock.traced_run("pizza_workflow"):
337
+ await flock.publish(pizza_idea)
338
+ await flock.run_until_idle()
339
+ # All operations now share the same trace_id!
340
+
341
+ # Custom attributes
342
+ async with flock.traced_run("data_pipeline") as span:
343
+ span.set_attribute("pipeline.version", "2.0")
344
+ await flock.publish(data)
345
+ await flock.run_until_idle()
346
+ """
347
+ tracer = trace.get_tracer(__name__)
348
+ with tracer.start_as_current_span(name) as span:
349
+ # Set workflow-level attributes
350
+ span.set_attribute("flock.workflow", True)
351
+ span.set_attribute("workflow.name", name)
352
+ span.set_attribute("workflow.flock_id", str(id(self)))
353
+
354
+ # Store span for nested operations to use
355
+ prev_workflow_span = self._workflow_span
356
+ self._workflow_span = span
357
+
358
+ try:
359
+ yield span
360
+ span.set_status(Status(StatusCode.OK))
361
+ except Exception as e:
362
+ span.set_status(Status(StatusCode.ERROR, str(e)))
363
+ span.record_exception(e)
364
+ raise
365
+ finally:
366
+ # Restore previous workflow span
367
+ self._workflow_span = prev_workflow_span
368
+
369
+ @staticmethod
370
+ def clear_traces(db_path: str = ".flock/traces.duckdb") -> dict[str, Any]:
371
+ """Clear all traces from the DuckDB database.
372
+
373
+ Useful for resetting debug sessions or cleaning up test data.
374
+
375
+ Args:
376
+ db_path: Path to the DuckDB database file (default: ".flock/traces.duckdb")
377
+
378
+ Returns:
379
+ Dictionary with operation results:
380
+ - deleted_count: Number of spans deleted
381
+ - success: Whether operation succeeded
382
+ - error: Error message if failed
383
+
384
+ Examples:
385
+ # Clear all traces
386
+ result = Flock.clear_traces()
387
+ print(f"Deleted {result['deleted_count']} spans")
388
+
389
+ # Custom database path
390
+ result = Flock.clear_traces(".flock/custom_traces.duckdb")
391
+
392
+ # Check if operation succeeded
393
+ if result['success']:
394
+ print("Traces cleared successfully!")
395
+ else:
396
+ print(f"Error: {result['error']}")
397
+ """
398
+ try:
399
+ from pathlib import Path
400
+
401
+ import duckdb
402
+
403
+ db_file = Path(db_path)
404
+ if not db_file.exists():
405
+ return {
406
+ "success": False,
407
+ "deleted_count": 0,
408
+ "error": f"Database file not found: {db_path}",
409
+ }
410
+
411
+ # Connect and clear
412
+ conn = duckdb.connect(str(db_file))
413
+ try:
414
+ # Get count before deletion
415
+ count_result = conn.execute("SELECT COUNT(*) FROM spans").fetchone()
416
+ deleted_count = count_result[0] if count_result else 0
417
+
418
+ # Delete all spans
419
+ conn.execute("DELETE FROM spans")
420
+
421
+ # Vacuum to reclaim space
422
+ conn.execute("VACUUM")
423
+
424
+ return {"success": True, "deleted_count": deleted_count, "error": None}
425
+
426
+ finally:
427
+ conn.close()
428
+
429
+ except Exception as e:
430
+ return {"success": False, "deleted_count": 0, "error": str(e)}
431
+
432
+ # Runtime --------------------------------------------------------------
433
+
434
+ async def run_until_idle(self) -> None:
435
+ """Wait for all scheduled agent tasks to complete.
436
+
437
+ This method blocks until the blackboard reaches a stable state where no
438
+ agents are queued for execution. Essential for batch processing and ensuring
439
+ all agent cascades complete before continuing.
440
+
441
+ Note:
442
+ Automatically resets circuit breaker counters and shuts down MCP connections
443
+ when idle. Used with publish() for event-driven workflows.
444
+
445
+ Examples:
446
+ >>> # Event-driven workflow (recommended)
447
+ >>> await flock.publish(task1)
448
+ >>> await flock.publish(task2)
449
+ >>> await flock.run_until_idle() # Wait for all cascades
450
+ >>> # All agents have finished processing
451
+
452
+ >>> # Parallel batch processing
453
+ >>> await flock.publish_many([task1, task2, task3])
454
+ >>> await flock.run_until_idle() # All tasks processed in parallel
455
+
456
+ See Also:
457
+ - publish(): Event-driven artifact publishing
458
+ - publish_many(): Batch publishing for parallel execution
459
+ - invoke(): Direct agent invocation without cascade
460
+ """
461
+ while self._tasks:
462
+ await asyncio.sleep(0.01)
463
+ pending = {task for task in self._tasks if not task.done()}
464
+ self._tasks = pending
465
+ # T068: Reset circuit breaker counters when idle
466
+ self._agent_iteration_count.clear()
467
+
468
+ # Automatically shutdown MCP connections when idle
469
+ await self.shutdown()
470
+
471
+ async def direct_invoke(
472
+ self, agent: Agent, inputs: Sequence[BaseModel | Mapping[str, Any] | Artifact]
473
+ ) -> list[Artifact]:
474
+ artifacts = [self._normalize_input(value, produced_by="__direct__") for value in inputs]
475
+ for artifact in artifacts:
476
+ self._mark_processed(artifact, agent)
477
+ await self._persist_and_schedule(artifact)
478
+ ctx = Context(board=BoardHandle(self), orchestrator=self, task_id=str(uuid4()))
479
+ self._record_agent_run(agent)
480
+ return await agent.execute(ctx, artifacts)
481
+
482
+ async def arun(self, agent_builder: AgentBuilder, *inputs: BaseModel) -> list[Artifact]:
483
+ """Execute an agent with inputs and wait for all cascades to complete (async).
484
+
485
+ Convenience method that combines direct agent invocation with run_until_idle().
486
+ Useful for testing and synchronous request-response patterns.
487
+
488
+ Args:
489
+ agent_builder: Agent to execute (from flock.agent())
490
+ *inputs: Input objects (BaseModel instances)
491
+
492
+ Returns:
493
+ Artifacts produced by the agent and any triggered cascades
494
+
495
+ Examples:
496
+ >>> # Test a single agent
497
+ >>> flock = Flock("openai/gpt-4.1")
498
+ >>> pizza_agent = flock.agent("pizza").consumes(Idea).publishes(Pizza)
499
+ >>> results = await flock.arun(pizza_agent, Idea(topic="Margherita"))
500
+
501
+ >>> # Multiple inputs
502
+ >>> results = await flock.arun(
503
+ ... task_agent,
504
+ ... Task(name="deploy"),
505
+ ... Task(name="test")
506
+ ... )
507
+
508
+ Note:
509
+ For event-driven workflows, prefer publish() + run_until_idle() for better
510
+ control over execution timing and parallel processing.
511
+ """
512
+ artifacts = await self.direct_invoke(agent_builder.agent, list(inputs))
513
+ await self.run_until_idle()
514
+ return artifacts
515
+
516
+ def run(self, agent_builder: AgentBuilder, *inputs: BaseModel) -> list[Artifact]:
517
+ """Synchronous wrapper for arun() - executes agent and waits for completion.
518
+
519
+ Args:
520
+ agent_builder: Agent to execute (from flock.agent())
521
+ *inputs: Input objects (BaseModel instances)
522
+
523
+ Returns:
524
+ Artifacts produced by the agent and any triggered cascades
525
+
526
+ Examples:
527
+ >>> # Synchronous execution (blocks until complete)
528
+ >>> flock = Flock("openai/gpt-4o-mini")
529
+ >>> agent = flock.agent("analyzer").consumes(Data).publishes(Report)
530
+ >>> results = flock.run(agent, Data(value=42))
531
+
532
+ Warning:
533
+ Cannot be called from within an async context. Use arun() instead
534
+ if already in an async function.
535
+ """
536
+ return asyncio.run(self.arun(agent_builder, *inputs))
537
+
538
+ async def shutdown(self) -> None:
539
+ """Shutdown orchestrator and clean up resources."""
540
+ if self._mcp_manager is not None:
541
+ await self._mcp_manager.cleanup_all()
542
+ self._mcp_manager = None
543
+
544
+ def cli(self) -> Flock:
545
+ # Placeholder for CLI wiring (rich UI in Step 3)
546
+ return self
547
+
548
+ async def serve(
549
+ self,
550
+ *,
551
+ dashboard: bool = False,
552
+ dashboard_v2: bool = False,
553
+ host: str = "127.0.0.1",
554
+ port: int = 8344,
555
+ ) -> None:
556
+ """Start HTTP service for the orchestrator (blocking).
557
+
558
+ Args:
559
+ dashboard: Enable real-time dashboard with WebSocket support (default: False)
560
+ dashboard_v2: Launch the new dashboard v2 frontend (implies dashboard=True)
561
+ host: Host to bind to (default: "127.0.0.1")
562
+ port: Port to bind to (default: 8344)
563
+
564
+ Examples:
565
+ # Basic HTTP API (no dashboard) - runs until interrupted
566
+ await orchestrator.serve()
567
+
568
+ # With dashboard (WebSocket + browser launch) - runs until interrupted
569
+ await orchestrator.serve(dashboard=True)
570
+ """
571
+ if dashboard_v2:
572
+ dashboard = True
573
+
574
+ if not dashboard:
575
+ # Standard service without dashboard
576
+ from flock.service import BlackboardHTTPService
577
+
578
+ service = BlackboardHTTPService(self)
579
+ await service.run_async(host=host, port=port)
580
+ return
581
+
582
+ # Dashboard mode: integrate event collection and WebSocket
583
+ from flock.dashboard.collector import DashboardEventCollector
584
+ from flock.dashboard.launcher import DashboardLauncher
585
+ from flock.dashboard.service import DashboardHTTPService
586
+ from flock.dashboard.websocket import WebSocketManager
587
+
588
+ # Create dashboard components
589
+ websocket_manager = WebSocketManager()
590
+ event_collector = DashboardEventCollector(store=self.store)
591
+ event_collector.set_websocket_manager(websocket_manager)
592
+ await event_collector.load_persistent_snapshots()
593
+
594
+ # Store collector reference for agents added later
595
+ self._dashboard_collector = event_collector
596
+
597
+ # Inject event collector into all existing agents
598
+ for agent in self._agents.values():
599
+ # Insert at beginning of utilities list (highest priority)
600
+ agent.utilities.insert(0, event_collector)
601
+
602
+ # Start dashboard launcher (npm process + browser)
603
+ launcher_kwargs: dict[str, Any] = {"port": port}
604
+ if dashboard_v2:
605
+ dashboard_pkg_dir = Path(__file__).parent / "dashboard"
606
+ launcher_kwargs["frontend_dir"] = dashboard_pkg_dir.parent / "frontend_v2"
607
+ launcher_kwargs["static_dir"] = dashboard_pkg_dir / "static_v2"
608
+
609
+ launcher = DashboardLauncher(**launcher_kwargs)
610
+ launcher.start()
611
+
612
+ # Create dashboard HTTP service
613
+ service = DashboardHTTPService(
614
+ orchestrator=self,
615
+ websocket_manager=websocket_manager,
616
+ event_collector=event_collector,
617
+ use_v2=dashboard_v2,
618
+ )
619
+
620
+ # Store launcher for cleanup
621
+ self._dashboard_launcher = launcher
622
+
623
+ # Run service (blocking call)
624
+ try:
625
+ await service.run_async(host=host, port=port)
626
+ finally:
627
+ # Cleanup on exit
628
+ launcher.stop()
629
+
630
+ # Scheduling -----------------------------------------------------------
631
+
632
+ async def publish(
633
+ self,
634
+ obj: BaseModel | dict | Artifact,
635
+ *,
636
+ visibility: Visibility | None = None,
637
+ correlation_id: str | None = None,
638
+ partition_key: str | None = None,
639
+ tags: set[str] | None = None,
640
+ is_dashboard: bool = False,
641
+ ) -> Artifact:
642
+ """Publish an artifact to the blackboard (event-driven).
643
+
644
+ All agents with matching subscriptions will be triggered according to
645
+ their filters (type, predicates, visibility, etc).
646
+
647
+ Args:
648
+ obj: Object to publish (BaseModel instance, dict, or Artifact)
649
+ visibility: Access control (defaults to PublicVisibility)
650
+ correlation_id: Optional correlation ID for request tracing
651
+ partition_key: Optional partition key for sharding
652
+ tags: Optional tags for channel-based routing
653
+
654
+ Returns:
655
+ The published Artifact
656
+
657
+ Examples:
658
+ >>> # Publish a model instance (recommended)
659
+ >>> task = Task(name="Deploy", priority=5)
660
+ >>> await orchestrator.publish(task)
661
+
662
+ >>> # Publish with custom visibility
663
+ >>> await orchestrator.publish(
664
+ ... task,
665
+ ... visibility=PrivateVisibility(agents={"admin"})
666
+ ... )
667
+
668
+ >>> # Publish with tags for channel routing
669
+ >>> await orchestrator.publish(task, tags={"urgent", "backend"})
670
+ """
671
+ init_console(clear_screen=True, show_banner=True, model=self.model)
672
+ self.is_dashboard = is_dashboard
673
+ # Handle different input types
674
+ if isinstance(obj, Artifact):
675
+ # Already an artifact - publish as-is
676
+ artifact = obj
677
+ elif isinstance(obj, BaseModel):
678
+ # BaseModel instance - get type from registry
679
+ type_name = type_registry.name_for(type(obj))
680
+ artifact = Artifact(
681
+ type=type_name,
682
+ payload=obj.model_dump(),
683
+ produced_by="external",
684
+ visibility=visibility or PublicVisibility(),
685
+ correlation_id=correlation_id or uuid4(),
686
+ partition_key=partition_key,
687
+ tags=tags or set(),
688
+ )
689
+ elif isinstance(obj, dict):
690
+ # Dict must have 'type' key
691
+ if "type" not in obj:
692
+ raise ValueError(
693
+ "Dict input must contain 'type' key. "
694
+ "Example: {'type': 'Task', 'name': 'foo', 'priority': 5}"
695
+ )
696
+ # Support both {'type': 'X', 'payload': {...}} and {'type': 'X', ...}
697
+ type_name = obj["type"]
698
+ if "payload" in obj:
699
+ payload = obj["payload"]
700
+ else:
701
+ payload = {k: v for k, v in obj.items() if k != "type"}
702
+
703
+ artifact = Artifact(
704
+ type=type_name,
705
+ payload=payload,
706
+ produced_by="external",
707
+ visibility=visibility or PublicVisibility(),
708
+ correlation_id=correlation_id,
709
+ partition_key=partition_key,
710
+ tags=tags or set(),
711
+ )
712
+ else:
713
+ raise TypeError(
714
+ f"Cannot publish object of type {type(obj).__name__}. "
715
+ "Expected BaseModel, dict, or Artifact."
716
+ )
717
+
718
+ # Persist and schedule matching agents
719
+ await self._persist_and_schedule(artifact)
720
+ return artifact
721
+
722
+ async def publish_many(
723
+ self, objects: Iterable[BaseModel | dict | Artifact], **kwargs: Any
724
+ ) -> list[Artifact]:
725
+ """Publish multiple artifacts at once (event-driven).
726
+
727
+ Args:
728
+ objects: Iterable of objects to publish
729
+ **kwargs: Passed to each publish() call (visibility, tags, etc)
730
+
731
+ Returns:
732
+ List of published Artifacts
733
+
734
+ Example:
735
+ >>> tasks = [
736
+ ... Task(name="Deploy", priority=5),
737
+ ... Task(name="Test", priority=3),
738
+ ... Task(name="Document", priority=1),
739
+ ... ]
740
+ >>> await orchestrator.publish_many(tasks, tags={"sprint-3"})
741
+ """
742
+ artifacts = []
743
+ for obj in objects:
744
+ artifact = await self.publish(obj, **kwargs)
745
+ artifacts.append(artifact)
746
+ return artifacts
747
+
748
+ # -----------------------------------------------------------------------------
749
+ # NEW DIRECT INVOCATION API - Explicit Control
750
+ # -----------------------------------------------------------------------------
751
+
752
+ async def invoke(
753
+ self,
754
+ agent: Agent | AgentBuilder,
755
+ obj: BaseModel,
756
+ *,
757
+ publish_outputs: bool = True,
758
+ timeout: float | None = None,
759
+ ) -> list[Artifact]:
760
+ """Directly invoke a specific agent (bypasses subscription matching).
761
+
762
+ This executes the agent immediately without checking subscriptions or
763
+ predicates. Useful for testing or synchronous request-response patterns.
764
+
765
+ Args:
766
+ agent: Agent or AgentBuilder to invoke
767
+ obj: Input object (BaseModel instance)
768
+ publish_outputs: If True, publish outputs to blackboard for cascade
769
+ timeout: Optional timeout in seconds
770
+
771
+ Returns:
772
+ Artifacts produced by the agent
773
+
774
+ Warning:
775
+ This bypasses subscription filters and predicates. For event-driven
776
+ coordination, use publish() instead.
777
+
778
+ Examples:
779
+ >>> # Testing: Execute agent without triggering others
780
+ >>> results = await orchestrator.invoke(
781
+ ... agent,
782
+ ... Task(name="test", priority=5),
783
+ ... publish_outputs=False
784
+ ... )
785
+
786
+ >>> # HTTP endpoint: Execute specific agent, allow cascade
787
+ >>> results = await orchestrator.invoke(
788
+ ... movie_agent,
789
+ ... Idea(topic="AI", genre="comedy"),
790
+ ... publish_outputs=True
791
+ ... )
792
+ >>> await orchestrator.run_until_idle()
793
+ """
794
+ from asyncio import wait_for
795
+ from uuid import uuid4
796
+
797
+ # Get Agent instance
798
+ agent_obj = agent.agent if isinstance(agent, AgentBuilder) else agent
799
+
800
+ # Create artifact (don't publish to blackboard yet)
801
+ type_name = type_registry.name_for(type(obj))
802
+ artifact = Artifact(
803
+ type=type_name,
804
+ payload=obj.model_dump(),
805
+ produced_by="__direct__",
806
+ visibility=PublicVisibility(),
807
+ )
808
+
809
+ # Execute agent directly
810
+ ctx = Context(board=BoardHandle(self), orchestrator=self, task_id=str(uuid4()))
811
+ self._record_agent_run(agent_obj)
812
+
813
+ # Execute with optional timeout
814
+ if timeout:
815
+ execution = agent_obj.execute(ctx, [artifact])
816
+ outputs = await wait_for(execution, timeout=timeout)
817
+ else:
818
+ outputs = await agent_obj.execute(ctx, [artifact])
819
+
820
+ # Optionally publish outputs to blackboard
821
+ if publish_outputs:
822
+ for output in outputs:
823
+ await self._persist_and_schedule(output)
824
+
825
+ return outputs
826
+
827
+ # Keep publish_external as deprecated alias
828
+ async def publish_external(
829
+ self,
830
+ type_name: str,
831
+ payload: dict[str, Any],
832
+ *,
833
+ visibility: Visibility | None = None,
834
+ correlation_id: str | None = None,
835
+ partition_key: str | None = None,
836
+ tags: set[str] | None = None,
837
+ ) -> Artifact:
838
+ """Deprecated: Use publish() instead.
839
+
840
+ This method will be removed in v2.0.
841
+ """
842
+ import warnings
843
+
844
+ warnings.warn(
845
+ "publish_external() is deprecated. Use publish(obj) instead.",
846
+ DeprecationWarning,
847
+ stacklevel=2,
848
+ )
849
+ return await self.publish(
850
+ {"type": type_name, "payload": payload},
851
+ visibility=visibility,
852
+ correlation_id=correlation_id,
853
+ partition_key=partition_key,
854
+ tags=tags,
855
+ )
856
+
857
+ async def _persist_and_schedule(self, artifact: Artifact) -> None:
858
+ await self.store.publish(artifact)
859
+ self.metrics["artifacts_published"] += 1
860
+ await self._schedule_artifact(artifact)
861
+
862
+ async def _schedule_artifact(self, artifact: Artifact) -> None:
863
+ for agent in self.agents:
864
+ identity = agent.identity
865
+ for subscription in agent.subscriptions:
866
+ if not subscription.accepts_events():
867
+ continue
868
+ # T066: Check prevent_self_trigger
869
+ if agent.prevent_self_trigger and artifact.produced_by == agent.name:
870
+ continue # Skip - agent produced this artifact (prevents feedback loops)
871
+ # T068: Circuit breaker - check iteration limit
872
+ iteration_count = self._agent_iteration_count.get(agent.name, 0)
873
+ if iteration_count >= self.max_agent_iterations:
874
+ # Agent hit iteration limit - possible infinite loop
875
+ continue
876
+ if not self._check_visibility(artifact, identity):
877
+ continue
878
+ if not subscription.matches(artifact):
879
+ continue
880
+ if self._seen_before(artifact, agent):
881
+ continue
882
+ # T068: Increment iteration counter
883
+ self._agent_iteration_count[agent.name] = iteration_count + 1
884
+ self._mark_processed(artifact, agent)
885
+ self._schedule_task(agent, [artifact])
886
+
887
+ def _schedule_task(self, agent: Agent, artifacts: list[Artifact]) -> None:
888
+ task = asyncio.create_task(self._run_agent_task(agent, artifacts))
889
+ self._tasks.add(task)
890
+ task.add_done_callback(self._tasks.discard)
891
+
892
+ def _record_agent_run(self, agent: Agent) -> None:
893
+ self.metrics["agent_runs"] += 1
894
+
895
+ def _mark_processed(self, artifact: Artifact, agent: Agent) -> None:
896
+ key = (str(artifact.id), agent.name)
897
+ self._processed.add(key)
898
+
899
+ def _seen_before(self, artifact: Artifact, agent: Agent) -> bool:
900
+ key = (str(artifact.id), agent.name)
901
+ return key in self._processed
902
+
903
+ async def _run_agent_task(self, agent: Agent, artifacts: list[Artifact]) -> None:
904
+ correlation_id = artifacts[0].correlation_id if artifacts else uuid4()
905
+
906
+ ctx = Context(
907
+ board=BoardHandle(self),
908
+ orchestrator=self,
909
+ task_id=str(uuid4()),
910
+ correlation_id=correlation_id, # NEW!
911
+ )
912
+ self._record_agent_run(agent)
913
+ await agent.execute(ctx, artifacts)
914
+
915
+ if artifacts:
916
+ try:
917
+ timestamp = datetime.now(timezone.utc)
918
+ records = [
919
+ ConsumptionRecord(
920
+ artifact_id=artifact.id,
921
+ consumer=agent.name,
922
+ run_id=ctx.task_id,
923
+ correlation_id=str(correlation_id) if correlation_id else None,
924
+ consumed_at=timestamp,
925
+ )
926
+ for artifact in artifacts
927
+ ]
928
+ await self.store.record_consumptions(records)
929
+ except NotImplementedError:
930
+ pass
931
+ except Exception as exc: # pragma: no cover - defensive logging
932
+ self._logger.exception("Failed to record artifact consumption: %s", exc)
933
+
934
+ # Helpers --------------------------------------------------------------
935
+
936
+ def _normalize_input(
937
+ self, value: BaseModel | Mapping[str, Any] | Artifact, *, produced_by: str
938
+ ) -> Artifact:
939
+ if isinstance(value, Artifact):
940
+ return value
941
+ if isinstance(value, BaseModel):
942
+ model_cls = type(value)
943
+ type_name = type_registry.register(model_cls)
944
+ payload = value.model_dump()
945
+ elif isinstance(value, Mapping):
946
+ if "type" not in value:
947
+ raise ValueError("Mapping input must contain 'type'.")
948
+ type_name = value["type"]
949
+ payload = value.get("payload", {})
950
+ else: # pragma: no cover - defensive
951
+ raise TypeError("Unsupported input for direct invoke.")
952
+ return Artifact(type=type_name, payload=payload, produced_by=produced_by)
953
+
954
+ def _check_visibility(self, artifact: Artifact, identity: AgentIdentity) -> bool:
955
+ try:
956
+ return artifact.visibility.allows(identity)
957
+ except AttributeError: # pragma: no cover - fallback for dict vis
958
+ return True
959
+
960
+
961
+ @asynccontextmanager
962
+ async def start_orchestrator(orchestrator: Flock): # pragma: no cover - CLI helper
963
+ try:
964
+ yield orchestrator
965
+ await orchestrator.run_until_idle()
966
+ finally:
967
+ pass
968
+
969
+
970
+ __all__ = ["Flock", "start_orchestrator"]