flock-core 0.5.0b28__py3-none-any.whl → 0.5.0b51__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 (469) 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/frontend/README.md +678 -0
  17. flock/frontend/docs/DESIGN_SYSTEM.md +1980 -0
  18. flock/frontend/index.html +12 -0
  19. flock/frontend/package-lock.json +4347 -0
  20. flock/frontend/package.json +48 -0
  21. flock/frontend/src/App.tsx +79 -0
  22. flock/frontend/src/__tests__/e2e/critical-scenarios.test.tsx +587 -0
  23. flock/frontend/src/__tests__/integration/filtering-e2e.test.tsx +387 -0
  24. flock/frontend/src/__tests__/integration/graph-rendering.test.tsx +640 -0
  25. flock/frontend/src/__tests__/integration/indexeddb-persistence.test.tsx +699 -0
  26. flock/frontend/src/components/common/BuildInfo.tsx +39 -0
  27. flock/frontend/src/components/common/EmptyState.module.css +115 -0
  28. flock/frontend/src/components/common/EmptyState.tsx +128 -0
  29. flock/frontend/src/components/common/ErrorBoundary.module.css +169 -0
  30. flock/frontend/src/components/common/ErrorBoundary.tsx +118 -0
  31. flock/frontend/src/components/common/KeyboardShortcutsDialog.css +251 -0
  32. flock/frontend/src/components/common/KeyboardShortcutsDialog.tsx +151 -0
  33. flock/frontend/src/components/common/LoadingSpinner.module.css +97 -0
  34. flock/frontend/src/components/common/LoadingSpinner.tsx +29 -0
  35. flock/frontend/src/components/controls/PublishControl.css +547 -0
  36. flock/frontend/src/components/controls/PublishControl.test.tsx +543 -0
  37. flock/frontend/src/components/controls/PublishControl.tsx +432 -0
  38. flock/frontend/src/components/details/DetailWindowContainer.tsx +62 -0
  39. flock/frontend/src/components/details/LiveOutputTab.test.tsx +792 -0
  40. flock/frontend/src/components/details/LiveOutputTab.tsx +220 -0
  41. flock/frontend/src/components/details/MessageHistoryTab.tsx +299 -0
  42. flock/frontend/src/components/details/NodeDetailWindow.test.tsx +501 -0
  43. flock/frontend/src/components/details/NodeDetailWindow.tsx +218 -0
  44. flock/frontend/src/components/details/RunStatusTab.tsx +307 -0
  45. flock/frontend/src/components/details/tabs.test.tsx +1015 -0
  46. flock/frontend/src/components/filters/CorrelationIDFilter.module.css +102 -0
  47. flock/frontend/src/components/filters/CorrelationIDFilter.test.tsx +197 -0
  48. flock/frontend/src/components/filters/CorrelationIDFilter.tsx +121 -0
  49. flock/frontend/src/components/filters/FilterBar.module.css +29 -0
  50. flock/frontend/src/components/filters/FilterBar.test.tsx +133 -0
  51. flock/frontend/src/components/filters/FilterBar.tsx +33 -0
  52. flock/frontend/src/components/filters/FilterPills.module.css +79 -0
  53. flock/frontend/src/components/filters/FilterPills.test.tsx +173 -0
  54. flock/frontend/src/components/filters/FilterPills.tsx +67 -0
  55. flock/frontend/src/components/filters/TimeRangeFilter.module.css +91 -0
  56. flock/frontend/src/components/filters/TimeRangeFilter.test.tsx +154 -0
  57. flock/frontend/src/components/filters/TimeRangeFilter.tsx +105 -0
  58. flock/frontend/src/components/graph/AgentNode.test.tsx +75 -0
  59. flock/frontend/src/components/graph/AgentNode.tsx +322 -0
  60. flock/frontend/src/components/graph/GraphCanvas.tsx +406 -0
  61. flock/frontend/src/components/graph/MessageFlowEdge.tsx +128 -0
  62. flock/frontend/src/components/graph/MessageNode.test.tsx +62 -0
  63. flock/frontend/src/components/graph/MessageNode.tsx +116 -0
  64. flock/frontend/src/components/graph/MiniMap.tsx +47 -0
  65. flock/frontend/src/components/graph/TransformEdge.tsx +123 -0
  66. flock/frontend/src/components/layout/DashboardLayout.css +407 -0
  67. flock/frontend/src/components/layout/DashboardLayout.tsx +300 -0
  68. flock/frontend/src/components/layout/Header.module.css +88 -0
  69. flock/frontend/src/components/layout/Header.tsx +52 -0
  70. flock/frontend/src/components/modules/EventLogModule.test.tsx +401 -0
  71. flock/frontend/src/components/modules/EventLogModule.tsx +396 -0
  72. flock/frontend/src/components/modules/EventLogModuleWrapper.tsx +17 -0
  73. flock/frontend/src/components/modules/ModuleRegistry.test.ts +333 -0
  74. flock/frontend/src/components/modules/ModuleRegistry.ts +85 -0
  75. flock/frontend/src/components/modules/ModuleWindow.tsx +155 -0
  76. flock/frontend/src/components/modules/registerModules.ts +20 -0
  77. flock/frontend/src/components/settings/AdvancedSettings.tsx +175 -0
  78. flock/frontend/src/components/settings/AppearanceSettings.tsx +185 -0
  79. flock/frontend/src/components/settings/GraphSettings.tsx +110 -0
  80. flock/frontend/src/components/settings/SettingsPanel.css +327 -0
  81. flock/frontend/src/components/settings/SettingsPanel.tsx +131 -0
  82. flock/frontend/src/components/settings/ThemeSelector.tsx +298 -0
  83. flock/frontend/src/hooks/useKeyboardShortcuts.ts +148 -0
  84. flock/frontend/src/hooks/useModulePersistence.test.ts +442 -0
  85. flock/frontend/src/hooks/useModulePersistence.ts +154 -0
  86. flock/frontend/src/hooks/useModules.ts +139 -0
  87. flock/frontend/src/hooks/usePersistence.ts +139 -0
  88. flock/frontend/src/main.tsx +13 -0
  89. flock/frontend/src/services/api.ts +213 -0
  90. flock/frontend/src/services/indexeddb.test.ts +793 -0
  91. flock/frontend/src/services/indexeddb.ts +794 -0
  92. flock/frontend/src/services/layout.test.ts +437 -0
  93. flock/frontend/src/services/layout.ts +146 -0
  94. flock/frontend/src/services/themeApplicator.ts +140 -0
  95. flock/frontend/src/services/themeService.ts +77 -0
  96. flock/frontend/src/services/websocket.test.ts +595 -0
  97. flock/frontend/src/services/websocket.ts +685 -0
  98. flock/frontend/src/store/filterStore.test.ts +242 -0
  99. flock/frontend/src/store/filterStore.ts +103 -0
  100. flock/frontend/src/store/graphStore.test.ts +186 -0
  101. flock/frontend/src/store/graphStore.ts +414 -0
  102. flock/frontend/src/store/moduleStore.test.ts +253 -0
  103. flock/frontend/src/store/moduleStore.ts +57 -0
  104. flock/frontend/src/store/settingsStore.ts +188 -0
  105. flock/frontend/src/store/streamStore.ts +68 -0
  106. flock/frontend/src/store/uiStore.test.ts +54 -0
  107. flock/frontend/src/store/uiStore.ts +110 -0
  108. flock/frontend/src/store/wsStore.ts +34 -0
  109. flock/frontend/src/styles/index.css +15 -0
  110. flock/frontend/src/styles/scrollbar.css +47 -0
  111. flock/frontend/src/styles/variables.css +488 -0
  112. flock/frontend/src/test/setup.ts +1 -0
  113. flock/frontend/src/types/filters.ts +14 -0
  114. flock/frontend/src/types/graph.ts +55 -0
  115. flock/frontend/src/types/modules.ts +7 -0
  116. flock/frontend/src/types/theme.ts +55 -0
  117. flock/frontend/src/utils/mockData.ts +85 -0
  118. flock/frontend/src/utils/performance.ts +16 -0
  119. flock/frontend/src/utils/transforms.test.ts +860 -0
  120. flock/frontend/src/utils/transforms.ts +323 -0
  121. flock/frontend/src/vite-env.d.ts +17 -0
  122. flock/frontend/tsconfig.json +27 -0
  123. flock/frontend/tsconfig.node.json +11 -0
  124. flock/frontend/vite.config.ts +25 -0
  125. flock/frontend/vitest.config.ts +11 -0
  126. flock/{core/util → helper}/cli_helper.py +4 -3
  127. flock/{core/logging → logging}/__init__.py +2 -3
  128. flock/{core/logging → logging}/formatters/enum_builder.py +3 -4
  129. flock/{core/logging → logging}/formatters/theme_builder.py +19 -44
  130. flock/{core/logging → logging}/formatters/themed_formatter.py +69 -115
  131. flock/{core/logging → logging}/logging.py +77 -61
  132. flock/{core/logging → logging}/telemetry.py +20 -26
  133. flock/{core/logging → logging}/telemetry_exporter/base_exporter.py +2 -2
  134. flock/{core/logging → logging}/telemetry_exporter/file_exporter.py +6 -9
  135. flock/{core/logging → logging}/telemetry_exporter/sqlite_exporter.py +2 -3
  136. flock/{core/logging → logging}/trace_and_logged.py +20 -24
  137. flock/mcp/__init__.py +91 -0
  138. flock/{core/mcp/mcp_client.py → mcp/client.py} +103 -154
  139. flock/{core/mcp/mcp_config.py → mcp/config.py} +62 -117
  140. flock/mcp/manager.py +255 -0
  141. flock/mcp/servers/sse/__init__.py +1 -1
  142. flock/mcp/servers/sse/flock_sse_server.py +11 -53
  143. flock/mcp/servers/stdio/__init__.py +1 -1
  144. flock/mcp/servers/stdio/flock_stdio_server.py +8 -48
  145. flock/mcp/servers/streamable_http/flock_streamable_http_server.py +17 -62
  146. flock/mcp/servers/websockets/flock_websocket_server.py +7 -40
  147. flock/{core/mcp/flock_mcp_tool.py → mcp/tool.py} +16 -26
  148. flock/mcp/types/__init__.py +42 -0
  149. flock/{core/mcp → mcp}/types/callbacks.py +9 -15
  150. flock/{core/mcp → mcp}/types/factories.py +7 -6
  151. flock/{core/mcp → mcp}/types/handlers.py +13 -18
  152. flock/{core/mcp → mcp}/types/types.py +70 -74
  153. flock/{core/mcp → mcp}/util/helpers.py +1 -1
  154. flock/orchestrator.py +645 -0
  155. flock/registry.py +148 -0
  156. flock/runtime.py +262 -0
  157. flock/service.py +140 -0
  158. flock/store.py +69 -0
  159. flock/subscription.py +111 -0
  160. flock/themes/andromeda.toml +1 -1
  161. flock/themes/apple-system-colors.toml +1 -1
  162. flock/themes/arcoiris.toml +1 -1
  163. flock/themes/atomonelight.toml +1 -1
  164. flock/themes/ayu copy.toml +1 -1
  165. flock/themes/ayu-light.toml +1 -1
  166. flock/themes/belafonte-day.toml +1 -1
  167. flock/themes/belafonte-night.toml +1 -1
  168. flock/themes/blulocodark.toml +1 -1
  169. flock/themes/breeze.toml +1 -1
  170. flock/themes/broadcast.toml +1 -1
  171. flock/themes/brogrammer.toml +1 -1
  172. flock/themes/builtin-dark.toml +1 -1
  173. flock/themes/builtin-pastel-dark.toml +1 -1
  174. flock/themes/catppuccin-latte.toml +1 -1
  175. flock/themes/catppuccin-macchiato.toml +1 -1
  176. flock/themes/catppuccin-mocha.toml +1 -1
  177. flock/themes/cga.toml +1 -1
  178. flock/themes/chalk.toml +1 -1
  179. flock/themes/ciapre.toml +1 -1
  180. flock/themes/coffee-theme.toml +1 -1
  181. flock/themes/cyberpunkscarletprotocol.toml +1 -1
  182. flock/themes/dark+.toml +1 -1
  183. flock/themes/darkermatrix.toml +1 -1
  184. flock/themes/darkside.toml +1 -1
  185. flock/themes/desert.toml +1 -1
  186. flock/themes/django.toml +1 -1
  187. flock/themes/djangosmooth.toml +1 -1
  188. flock/themes/doomone.toml +1 -1
  189. flock/themes/dotgov.toml +1 -1
  190. flock/themes/dracula+.toml +1 -1
  191. flock/themes/duckbones.toml +1 -1
  192. flock/themes/encom.toml +1 -1
  193. flock/themes/espresso.toml +1 -1
  194. flock/themes/everblush.toml +1 -1
  195. flock/themes/fairyfloss.toml +1 -1
  196. flock/themes/fideloper.toml +1 -1
  197. flock/themes/fishtank.toml +1 -1
  198. flock/themes/flexoki-light.toml +1 -1
  199. flock/themes/floraverse.toml +1 -1
  200. flock/themes/framer.toml +1 -1
  201. flock/themes/galizur.toml +1 -1
  202. flock/themes/github.toml +1 -1
  203. flock/themes/grass.toml +1 -1
  204. flock/themes/grey-green.toml +1 -1
  205. flock/themes/gruvboxlight.toml +1 -1
  206. flock/themes/guezwhoz.toml +1 -1
  207. flock/themes/harper.toml +1 -1
  208. flock/themes/hax0r-blue.toml +1 -1
  209. flock/themes/hopscotch.256.toml +1 -1
  210. flock/themes/ic-green-ppl.toml +1 -1
  211. flock/themes/iceberg-dark.toml +1 -1
  212. flock/themes/japanesque.toml +1 -1
  213. flock/themes/jubi.toml +1 -1
  214. flock/themes/kibble.toml +1 -1
  215. flock/themes/kolorit.toml +1 -1
  216. flock/themes/kurokula.toml +1 -1
  217. flock/themes/materialdesigncolors.toml +1 -1
  218. flock/themes/matrix.toml +1 -1
  219. flock/themes/mellifluous.toml +1 -1
  220. flock/themes/midnight-in-mojave.toml +1 -1
  221. flock/themes/monokai-remastered.toml +1 -1
  222. flock/themes/monokai-soda.toml +1 -1
  223. flock/themes/neon.toml +1 -1
  224. flock/themes/neopolitan.toml +1 -1
  225. flock/themes/nord-light.toml +1 -1
  226. flock/themes/ocean.toml +1 -1
  227. flock/themes/onehalfdark.toml +1 -1
  228. flock/themes/onehalflight.toml +1 -1
  229. flock/themes/palenighthc.toml +1 -1
  230. flock/themes/paulmillr.toml +1 -1
  231. flock/themes/pencildark.toml +1 -1
  232. flock/themes/pnevma.toml +1 -1
  233. flock/themes/purple-rain.toml +1 -1
  234. flock/themes/purplepeter.toml +1 -1
  235. flock/themes/raycast-dark.toml +1 -1
  236. flock/themes/red-sands.toml +1 -1
  237. flock/themes/relaxed.toml +1 -1
  238. flock/themes/retro.toml +1 -1
  239. flock/themes/rose-pine.toml +1 -1
  240. flock/themes/royal.toml +1 -1
  241. flock/themes/ryuuko.toml +1 -1
  242. flock/themes/sakura.toml +1 -1
  243. flock/themes/scarlet-protocol.toml +1 -1
  244. flock/themes/seoulbones-dark.toml +1 -1
  245. flock/themes/shades-of-purple.toml +1 -1
  246. flock/themes/smyck.toml +1 -1
  247. flock/themes/softserver.toml +1 -1
  248. flock/themes/solarized-darcula.toml +1 -1
  249. flock/themes/square.toml +1 -1
  250. flock/themes/sugarplum.toml +1 -1
  251. flock/themes/thayer-bright.toml +1 -1
  252. flock/themes/tokyonight.toml +1 -1
  253. flock/themes/tomorrow.toml +1 -1
  254. flock/themes/ubuntu.toml +1 -1
  255. flock/themes/ultradark.toml +1 -1
  256. flock/themes/ultraviolent.toml +1 -1
  257. flock/themes/unikitty.toml +1 -1
  258. flock/themes/urple.toml +1 -1
  259. flock/themes/vesper.toml +1 -1
  260. flock/themes/vimbones.toml +1 -1
  261. flock/themes/wildcherry.toml +1 -1
  262. flock/themes/wilmersdorf.toml +1 -1
  263. flock/themes/wryan.toml +1 -1
  264. flock/themes/xcodedarkhc.toml +1 -1
  265. flock/themes/xcodelight.toml +1 -1
  266. flock/themes/zenbones-light.toml +1 -1
  267. flock/themes/zenwritten-dark.toml +1 -1
  268. flock/utilities.py +301 -0
  269. flock/{components/utility → utility}/output_utility_component.py +68 -53
  270. flock/visibility.py +107 -0
  271. flock_core-0.5.0b51.dist-info/METADATA +747 -0
  272. flock_core-0.5.0b51.dist-info/RECORD +508 -0
  273. flock_core-0.5.0b51.dist-info/entry_points.txt +2 -0
  274. {flock_core-0.5.0b28.dist-info → flock_core-0.5.0b51.dist-info}/licenses/LICENSE +1 -1
  275. flock/adapter/__init__.py +0 -14
  276. flock/adapter/azure_adapter.py +0 -68
  277. flock/adapter/chroma_adapter.py +0 -73
  278. flock/adapter/faiss_adapter.py +0 -97
  279. flock/adapter/pinecone_adapter.py +0 -51
  280. flock/adapter/vector_base.py +0 -47
  281. flock/cli/assets/release_notes.md +0 -140
  282. flock/cli/config.py +0 -8
  283. flock/cli/constants.py +0 -36
  284. flock/cli/create_agent.py +0 -1
  285. flock/cli/create_flock.py +0 -280
  286. flock/cli/execute_flock.py +0 -620
  287. flock/cli/load_agent.py +0 -1
  288. flock/cli/load_examples.py +0 -1
  289. flock/cli/load_flock.py +0 -192
  290. flock/cli/load_release_notes.py +0 -20
  291. flock/cli/loaded_flock_cli.py +0 -254
  292. flock/cli/manage_agents.py +0 -459
  293. flock/cli/registry_management.py +0 -889
  294. flock/cli/runner.py +0 -41
  295. flock/cli/settings.py +0 -857
  296. flock/cli/utils.py +0 -135
  297. flock/cli/view_results.py +0 -29
  298. flock/cli/yaml_editor.py +0 -396
  299. flock/components/__init__.py +0 -30
  300. flock/components/evaluation/__init__.py +0 -9
  301. flock/components/evaluation/declarative_evaluation_component.py +0 -606
  302. flock/components/routing/__init__.py +0 -15
  303. flock/components/routing/conditional_routing_component.py +0 -494
  304. flock/components/routing/default_routing_component.py +0 -103
  305. flock/components/routing/llm_routing_component.py +0 -206
  306. flock/components/utility/__init__.py +0 -22
  307. flock/components/utility/example_utility_component.py +0 -250
  308. flock/components/utility/feedback_utility_component.py +0 -206
  309. flock/components/utility/memory_utility_component.py +0 -550
  310. flock/components/utility/metrics_utility_component.py +0 -700
  311. flock/config.py +0 -61
  312. flock/core/__init__.py +0 -110
  313. flock/core/agent/__init__.py +0 -16
  314. flock/core/agent/default_agent.py +0 -216
  315. flock/core/agent/flock_agent_components.py +0 -104
  316. flock/core/agent/flock_agent_execution.py +0 -101
  317. flock/core/agent/flock_agent_integration.py +0 -260
  318. flock/core/agent/flock_agent_lifecycle.py +0 -186
  319. flock/core/agent/flock_agent_serialization.py +0 -381
  320. flock/core/api/__init__.py +0 -10
  321. flock/core/api/custom_endpoint.py +0 -45
  322. flock/core/api/endpoints.py +0 -254
  323. flock/core/api/main.py +0 -162
  324. flock/core/api/models.py +0 -97
  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/component/__init__.py +0 -15
  329. flock/core/component/agent_component_base.py +0 -309
  330. flock/core/component/evaluation_component.py +0 -62
  331. flock/core/component/routing_component.py +0 -74
  332. flock/core/component/utility_component.py +0 -69
  333. flock/core/config/flock_agent_config.py +0 -58
  334. flock/core/config/scheduled_agent_config.py +0 -40
  335. flock/core/context/context.py +0 -213
  336. flock/core/context/context_manager.py +0 -37
  337. flock/core/context/context_vars.py +0 -10
  338. flock/core/evaluation/utils.py +0 -396
  339. flock/core/execution/batch_executor.py +0 -369
  340. flock/core/execution/evaluation_executor.py +0 -438
  341. flock/core/execution/local_executor.py +0 -31
  342. flock/core/execution/opik_executor.py +0 -103
  343. flock/core/execution/temporal_executor.py +0 -164
  344. flock/core/flock.py +0 -634
  345. flock/core/flock_agent.py +0 -336
  346. flock/core/flock_factory.py +0 -613
  347. flock/core/flock_scheduler.py +0 -166
  348. flock/core/flock_server_manager.py +0 -136
  349. flock/core/interpreter/python_interpreter.py +0 -689
  350. flock/core/mcp/__init__.py +0 -1
  351. flock/core/mcp/flock_mcp_server.py +0 -680
  352. flock/core/mcp/mcp_client_manager.py +0 -201
  353. flock/core/mcp/types/__init__.py +0 -1
  354. flock/core/mixin/dspy_integration.py +0 -403
  355. flock/core/mixin/prompt_parser.py +0 -125
  356. flock/core/orchestration/__init__.py +0 -15
  357. flock/core/orchestration/flock_batch_processor.py +0 -94
  358. flock/core/orchestration/flock_evaluator.py +0 -113
  359. flock/core/orchestration/flock_execution.py +0 -295
  360. flock/core/orchestration/flock_initialization.py +0 -149
  361. flock/core/orchestration/flock_server_manager.py +0 -67
  362. flock/core/orchestration/flock_web_server.py +0 -117
  363. flock/core/registry/__init__.py +0 -45
  364. flock/core/registry/agent_registry.py +0 -69
  365. flock/core/registry/callable_registry.py +0 -139
  366. flock/core/registry/component_discovery.py +0 -142
  367. flock/core/registry/component_registry.py +0 -64
  368. flock/core/registry/config_mapping.py +0 -64
  369. flock/core/registry/decorators.py +0 -137
  370. flock/core/registry/registry_hub.py +0 -205
  371. flock/core/registry/server_registry.py +0 -57
  372. flock/core/registry/type_registry.py +0 -86
  373. flock/core/serialization/__init__.py +0 -13
  374. flock/core/serialization/callable_registry.py +0 -52
  375. flock/core/serialization/flock_serializer.py +0 -832
  376. flock/core/serialization/json_encoder.py +0 -41
  377. flock/core/serialization/secure_serializer.py +0 -175
  378. flock/core/serialization/serializable.py +0 -342
  379. flock/core/serialization/serialization_utils.py +0 -412
  380. flock/core/util/file_path_utils.py +0 -223
  381. flock/core/util/hydrator.py +0 -309
  382. flock/core/util/input_resolver.py +0 -164
  383. flock/core/util/loader.py +0 -59
  384. flock/core/util/splitter.py +0 -219
  385. flock/di.py +0 -27
  386. flock/platform/docker_tools.py +0 -49
  387. flock/platform/jaeger_install.py +0 -86
  388. flock/webapp/__init__.py +0 -1
  389. flock/webapp/app/__init__.py +0 -0
  390. flock/webapp/app/api/__init__.py +0 -0
  391. flock/webapp/app/api/agent_management.py +0 -241
  392. flock/webapp/app/api/execution.py +0 -709
  393. flock/webapp/app/api/flock_management.py +0 -129
  394. flock/webapp/app/api/registry_viewer.py +0 -30
  395. flock/webapp/app/chat.py +0 -665
  396. flock/webapp/app/config.py +0 -104
  397. flock/webapp/app/dependencies.py +0 -117
  398. flock/webapp/app/main.py +0 -1070
  399. flock/webapp/app/middleware.py +0 -113
  400. flock/webapp/app/models_ui.py +0 -7
  401. flock/webapp/app/services/__init__.py +0 -0
  402. flock/webapp/app/services/feedback_file_service.py +0 -363
  403. flock/webapp/app/services/flock_service.py +0 -337
  404. flock/webapp/app/services/sharing_models.py +0 -81
  405. flock/webapp/app/services/sharing_store.py +0 -762
  406. flock/webapp/app/templates/theme_mapper.html +0 -326
  407. flock/webapp/app/theme_mapper.py +0 -812
  408. flock/webapp/app/utils.py +0 -85
  409. flock/webapp/run.py +0 -215
  410. flock/webapp/static/css/chat.css +0 -301
  411. flock/webapp/static/css/components.css +0 -167
  412. flock/webapp/static/css/header.css +0 -39
  413. flock/webapp/static/css/layout.css +0 -46
  414. flock/webapp/static/css/sidebar.css +0 -127
  415. flock/webapp/static/css/two-pane.css +0 -48
  416. flock/webapp/templates/base.html +0 -200
  417. flock/webapp/templates/chat.html +0 -152
  418. flock/webapp/templates/chat_settings.html +0 -19
  419. flock/webapp/templates/flock_editor.html +0 -16
  420. flock/webapp/templates/index.html +0 -12
  421. flock/webapp/templates/partials/_agent_detail_form.html +0 -93
  422. flock/webapp/templates/partials/_agent_list.html +0 -18
  423. flock/webapp/templates/partials/_agent_manager_view.html +0 -51
  424. flock/webapp/templates/partials/_agent_tools_checklist.html +0 -14
  425. flock/webapp/templates/partials/_chat_container.html +0 -15
  426. flock/webapp/templates/partials/_chat_messages.html +0 -57
  427. flock/webapp/templates/partials/_chat_settings_form.html +0 -85
  428. flock/webapp/templates/partials/_create_flock_form.html +0 -50
  429. flock/webapp/templates/partials/_dashboard_flock_detail.html +0 -17
  430. flock/webapp/templates/partials/_dashboard_flock_file_list.html +0 -16
  431. flock/webapp/templates/partials/_dashboard_flock_properties_preview.html +0 -28
  432. flock/webapp/templates/partials/_dashboard_upload_flock_form.html +0 -16
  433. flock/webapp/templates/partials/_dynamic_input_form_content.html +0 -22
  434. flock/webapp/templates/partials/_env_vars_table.html +0 -23
  435. flock/webapp/templates/partials/_execution_form.html +0 -118
  436. flock/webapp/templates/partials/_execution_view_container.html +0 -28
  437. flock/webapp/templates/partials/_flock_file_list.html +0 -23
  438. flock/webapp/templates/partials/_flock_properties_form.html +0 -52
  439. flock/webapp/templates/partials/_flock_upload_form.html +0 -16
  440. flock/webapp/templates/partials/_header_flock_status.html +0 -5
  441. flock/webapp/templates/partials/_load_manager_view.html +0 -49
  442. flock/webapp/templates/partials/_registry_table.html +0 -25
  443. flock/webapp/templates/partials/_registry_viewer_content.html +0 -70
  444. flock/webapp/templates/partials/_results_display.html +0 -78
  445. flock/webapp/templates/partials/_settings_env_content.html +0 -9
  446. flock/webapp/templates/partials/_settings_theme_content.html +0 -14
  447. flock/webapp/templates/partials/_settings_view.html +0 -36
  448. flock/webapp/templates/partials/_share_chat_link_snippet.html +0 -11
  449. flock/webapp/templates/partials/_share_link_snippet.html +0 -35
  450. flock/webapp/templates/partials/_sidebar.html +0 -74
  451. flock/webapp/templates/partials/_streaming_results_container.html +0 -195
  452. flock/webapp/templates/partials/_structured_data_view.html +0 -40
  453. flock/webapp/templates/partials/_theme_preview.html +0 -36
  454. flock/webapp/templates/registry_viewer.html +0 -84
  455. flock/webapp/templates/shared_run_page.html +0 -140
  456. flock/workflow/__init__.py +0 -0
  457. flock/workflow/activities.py +0 -196
  458. flock/workflow/agent_activities.py +0 -24
  459. flock/workflow/agent_execution_activity.py +0 -202
  460. flock/workflow/flock_workflow.py +0 -214
  461. flock/workflow/temporal_config.py +0 -96
  462. flock/workflow/temporal_setup.py +0 -68
  463. flock_core-0.5.0b28.dist-info/METADATA +0 -274
  464. flock_core-0.5.0b28.dist-info/RECORD +0 -561
  465. flock_core-0.5.0b28.dist-info/entry_points.txt +0 -2
  466. /flock/{core/logging → logging}/formatters/themes.py +0 -0
  467. /flock/{core/logging → logging}/span_middleware/baggage_span_processor.py +0 -0
  468. /flock/{core/mcp → mcp}/util/__init__.py +0 -0
  469. {flock_core-0.5.0b28.dist-info → flock_core-0.5.0b51.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"]