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/agent.py ADDED
@@ -0,0 +1,678 @@
1
+ """Agent definitions and fluent builder APIs."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import os
7
+ from dataclasses import dataclass
8
+ from typing import TYPE_CHECKING, Any
9
+
10
+ from pydantic import BaseModel
11
+
12
+ from flock.artifacts import Artifact, ArtifactSpec
13
+ from flock.logging.logging import get_logger
14
+ from flock.registry import function_registry, type_registry
15
+ from flock.runtime import Context, EvalInputs, EvalResult
16
+ from flock.subscription import BatchSpec, JoinSpec, Subscription, TextPredicate
17
+ from flock.visibility import AgentIdentity, Visibility, ensure_visibility, only_for
18
+
19
+
20
+ logger = get_logger(__name__)
21
+
22
+ if TYPE_CHECKING: # pragma: no cover - type hints only
23
+ from collections.abc import Callable, Iterable, Sequence
24
+
25
+ from flock.components import AgentComponent, EngineComponent
26
+ from flock.orchestrator import Flock
27
+
28
+
29
+ @dataclass
30
+ class AgentOutput:
31
+ spec: ArtifactSpec
32
+ default_visibility: Visibility
33
+
34
+ def apply(
35
+ self,
36
+ data: dict[str, Any],
37
+ *,
38
+ produced_by: str,
39
+ metadata: dict[str, Any] | None = None,
40
+ ) -> Artifact:
41
+ metadata = metadata or {}
42
+ return self.spec.build(
43
+ produced_by=produced_by,
44
+ data=data,
45
+ visibility=metadata.get("visibility", self.default_visibility),
46
+ correlation_id=metadata.get("correlation_id"),
47
+ partition_key=metadata.get("partition_key"),
48
+ tags=metadata.get("tags"),
49
+ version=metadata.get("version", 1),
50
+ )
51
+
52
+
53
+ class Agent:
54
+ """Executable agent constructed via `AgentBuilder`."""
55
+
56
+ def __init__(self, name: str, *, orchestrator: Flock) -> None:
57
+ self.name = name
58
+ self.description: str | None = None
59
+ self._orchestrator = orchestrator
60
+ self.subscriptions: list[Subscription] = []
61
+ self.outputs: list[AgentOutput] = []
62
+ self.utilities: list[AgentComponent] = []
63
+ self.engines: list[EngineComponent] = []
64
+ self.best_of_n: int = 1
65
+ self.best_of_score: Callable[[EvalResult], float] | None = None
66
+ self.max_concurrency: int = 1
67
+ self._semaphore = asyncio.Semaphore(self.max_concurrency)
68
+ self.calls_func: Callable[..., Any] | None = None
69
+ self.tools: set[Callable[..., Any]] = set()
70
+ self.labels: set[str] = set()
71
+ self.tenant_id: str | None = None
72
+ self.model: str | None = None
73
+ self.prevent_self_trigger: bool = True # T065: Prevent infinite feedback loops
74
+ # MCP integration
75
+ self.mcp_server_names: set[str] = set()
76
+
77
+ @property
78
+ def identity(self) -> AgentIdentity:
79
+ return AgentIdentity(name=self.name, labels=self.labels, tenant_id=self.tenant_id)
80
+
81
+ def set_max_concurrency(self, value: int) -> None:
82
+ self.max_concurrency = max(1, value)
83
+ self._semaphore = asyncio.Semaphore(self.max_concurrency)
84
+
85
+ async def run_direct(self, *inputs: BaseModel) -> list[Artifact]:
86
+ return await self._orchestrator.direct_invoke(self, list(inputs))
87
+
88
+ async def execute(self, ctx: Context, artifacts: list[Artifact]) -> list[Artifact]:
89
+ async with self._semaphore:
90
+ try:
91
+ self._resolve_engines()
92
+ self._resolve_utilities()
93
+ await self._run_initialize(ctx)
94
+ processed_inputs = await self._run_pre_consume(ctx, artifacts)
95
+ eval_inputs = EvalInputs(artifacts=processed_inputs, state=dict(ctx.state))
96
+ eval_inputs = await self._run_pre_evaluate(ctx, eval_inputs)
97
+ result = await self._run_engines(ctx, eval_inputs)
98
+ result = await self._run_post_evaluate(ctx, eval_inputs, result)
99
+ outputs = await self._make_outputs(ctx, result)
100
+ await self._run_post_publish(ctx, outputs)
101
+ if self.calls_func:
102
+ await self._invoke_call(ctx, outputs or processed_inputs)
103
+ return outputs
104
+ except Exception as exc:
105
+ await self._run_error(ctx, exc)
106
+ raise
107
+ finally:
108
+ await self._run_terminate(ctx)
109
+
110
+ async def _get_mcp_tools(self, ctx: Context) -> list[Callable]:
111
+ """Lazy-load MCP tools from assigned servers.
112
+
113
+ Architecture Decision: AD001 - Two-Level Architecture
114
+ Agents fetch tools from servers registered at orchestrator level.
115
+
116
+ Architecture Decision: AD003 - Tool Namespacing
117
+ All tools are namespaced as {server}__{tool}.
118
+
119
+ Architecture Decision: AD007 - Graceful Degradation
120
+ If MCP loading fails, returns empty list so agent continues with native tools.
121
+
122
+ Args:
123
+ ctx: Current execution context with agent_id and run_id
124
+
125
+ Returns:
126
+ List of DSPy-compatible tool callables
127
+ """
128
+ if not self.mcp_server_names:
129
+ # No MCP servers assigned to this agent
130
+ return []
131
+
132
+ try:
133
+ # Get the MCP manager from orchestrator
134
+ manager = self._orchestrator.get_mcp_manager()
135
+
136
+ # Import tool wrapper
137
+
138
+ # Fetch tools from all assigned servers
139
+ tools_dict = await manager.get_tools_for_agent(
140
+ agent_id=self.name,
141
+ run_id=ctx.task_id,
142
+ server_names=self.mcp_server_names,
143
+ )
144
+
145
+ # Convert to DSPy tool callables
146
+ dspy_tools = []
147
+ for namespaced_name, tool_info in tools_dict.items():
148
+ tool_info["server_name"]
149
+ flock_tool = tool_info["tool"] # Already a FlockMCPTool
150
+ client = tool_info["client"]
151
+
152
+ # Convert to DSPy tool
153
+ dspy_tool = flock_tool.as_dspy_tool(server=client)
154
+
155
+ # Update name to include namespace
156
+ dspy_tool.name = namespaced_name
157
+
158
+ dspy_tools.append(dspy_tool)
159
+
160
+ return dspy_tools
161
+
162
+ except Exception as e:
163
+ # Architecture Decision: AD007 - Graceful Degradation
164
+ # Agent continues with native tools only
165
+ logger.error(f"Failed to load MCP tools for agent {self.name}: {e}", exc_info=True)
166
+ return []
167
+
168
+ async def _run_initialize(self, ctx: Context) -> None:
169
+ for component in self.utilities:
170
+ await component.on_initialize(self, ctx)
171
+ for engine in self.engines:
172
+ await engine.on_initialize(self, ctx)
173
+
174
+ async def _run_pre_consume(self, ctx: Context, inputs: list[Artifact]) -> list[Artifact]:
175
+ current = inputs
176
+ for component in self.utilities:
177
+ current = await component.on_pre_consume(self, ctx, current)
178
+ return current
179
+
180
+ async def _run_pre_evaluate(self, ctx: Context, inputs: EvalInputs) -> EvalInputs:
181
+ current = inputs
182
+ for component in self.utilities:
183
+ current = await component.on_pre_evaluate(self, ctx, current)
184
+ return current
185
+
186
+ async def _run_engines(self, ctx: Context, inputs: EvalInputs) -> EvalResult:
187
+ engines = self._resolve_engines()
188
+ if not engines:
189
+ return EvalResult(artifacts=inputs.artifacts, state=inputs.state)
190
+
191
+ async def run_chain() -> EvalResult:
192
+ current_inputs = inputs
193
+ accumulated_logs: list[str] = []
194
+ accumulated_metrics: dict[str, float] = {}
195
+ for engine in engines:
196
+ current_inputs = await engine.on_pre_evaluate(self, ctx, current_inputs)
197
+ result = await engine.evaluate(self, ctx, current_inputs)
198
+
199
+ # AUTO-WRAP: If engine returns BaseModel instead of EvalResult, wrap it
200
+ from flock.runtime import EvalResult as ER
201
+
202
+ if isinstance(result, BaseModel) and not isinstance(result, ER):
203
+ result = ER.from_object(result, agent=self)
204
+
205
+ result = await engine.on_post_evaluate(self, ctx, current_inputs, result)
206
+ accumulated_logs.extend(result.logs)
207
+ accumulated_metrics.update(result.metrics)
208
+ merged_state = dict(current_inputs.state)
209
+ merged_state.update(result.state)
210
+ current_inputs = EvalInputs(
211
+ artifacts=result.artifacts or current_inputs.artifacts,
212
+ state=merged_state,
213
+ )
214
+ return EvalResult(
215
+ artifacts=current_inputs.artifacts,
216
+ state=current_inputs.state,
217
+ metrics=accumulated_metrics,
218
+ logs=accumulated_logs,
219
+ )
220
+
221
+ if self.best_of_n <= 1:
222
+ return await run_chain()
223
+
224
+ async with asyncio.TaskGroup() as tg: # Python 3.12
225
+ tasks: list[asyncio.Task[EvalResult]] = []
226
+ for _ in range(self.best_of_n):
227
+ tasks.append(tg.create_task(run_chain()))
228
+ results = [task.result() for task in tasks]
229
+ if not results:
230
+ return EvalResult(artifacts=[], state={})
231
+ if self.best_of_score is None:
232
+ return results[0]
233
+ return max(results, key=self.best_of_score)
234
+
235
+ async def _run_post_evaluate(
236
+ self, ctx: Context, inputs: EvalInputs, result: EvalResult
237
+ ) -> EvalResult:
238
+ current = result
239
+ for component in self.utilities:
240
+ current = await component.on_post_evaluate(self, ctx, inputs, current)
241
+ return current
242
+
243
+ async def _make_outputs(self, ctx: Context, result: EvalResult) -> list[Artifact]:
244
+ if not self.outputs:
245
+ # Utility agents may not publish anything
246
+ return list(result.artifacts)
247
+
248
+ produced: list[Artifact] = []
249
+ for output_decl in self.outputs:
250
+ payload = self._select_payload(output_decl, result)
251
+ if payload is None:
252
+ continue
253
+ metadata = {
254
+ "correlation_id": ctx.correlation_id # Need to add this to Context!
255
+ }
256
+ artifact = output_decl.apply(payload, produced_by=self.name, metadata=metadata)
257
+ produced.append(artifact)
258
+ await ctx.board.publish(artifact)
259
+ return produced
260
+
261
+ async def _run_post_publish(self, ctx: Context, artifacts: Sequence[Artifact]) -> None:
262
+ for artifact in artifacts:
263
+ for component in self.utilities:
264
+ await component.on_post_publish(self, ctx, artifact)
265
+
266
+ async def _invoke_call(self, ctx: Context, artifacts: Sequence[Artifact]) -> None:
267
+ func = self.calls_func
268
+ if func is None:
269
+ return
270
+ if not artifacts:
271
+ return
272
+ first = artifacts[0]
273
+ model_cls = type_registry.resolve(first.type)
274
+ payload = model_cls(**first.payload)
275
+ maybe_coro = func(payload)
276
+ if asyncio.iscoroutine(maybe_coro): # pragma: no cover - optional async support
277
+ await maybe_coro
278
+
279
+ async def _run_error(self, ctx: Context, error: Exception) -> None:
280
+ for component in self.utilities:
281
+ await component.on_error(self, ctx, error)
282
+ for engine in self.engines:
283
+ await engine.on_error(self, ctx, error)
284
+
285
+ async def _run_terminate(self, ctx: Context) -> None:
286
+ for component in self.utilities:
287
+ await component.on_terminate(self, ctx)
288
+ for engine in self.engines:
289
+ await engine.on_terminate(self, ctx)
290
+
291
+ def _resolve_engines(self) -> list[EngineComponent]:
292
+ if self.engines:
293
+ return self.engines
294
+ try:
295
+ from flock.engines import DSPyEngine
296
+ except Exception: # pragma: no cover - optional dependency issues
297
+ return []
298
+
299
+ default_engine = DSPyEngine(
300
+ model=self._orchestrator.model or os.getenv("DEFAULT_MODEL", "openai/gpt-4o-mini"),
301
+ instructions=self.description,
302
+ )
303
+ self.engines = [default_engine]
304
+ return self.engines
305
+
306
+ def _resolve_utilities(self) -> list[AgentComponent]:
307
+ if self.utilities:
308
+ return self.utilities
309
+ try:
310
+ from flock.utility.output_utility_component import (
311
+ OutputUtilityComponent,
312
+ )
313
+ except Exception: # pragma: no cover - optional dependency issues
314
+ return []
315
+
316
+ default_component = OutputUtilityComponent()
317
+ self.utilities = [default_component]
318
+ return self.utilities
319
+
320
+ def _select_payload(
321
+ self, output_decl: AgentOutput, result: EvalResult
322
+ ) -> dict[str, Any] | None:
323
+ from flock.registry import type_registry
324
+
325
+ if not result.artifacts:
326
+ return None
327
+
328
+ # Normalize the expected type name to canonical form
329
+ expected_canonical = type_registry.resolve_name(output_decl.spec.type_name)
330
+
331
+ for artifact in result.artifacts:
332
+ # Normalize artifact type name to canonical form for comparison
333
+ try:
334
+ artifact_canonical = type_registry.resolve_name(artifact.type)
335
+ if artifact_canonical == expected_canonical:
336
+ return artifact.payload
337
+ except Exception:
338
+ # If normalization fails, fall back to direct comparison
339
+ if artifact.type == output_decl.spec.type_name:
340
+ return artifact.payload
341
+
342
+ # Fallback to state entries keyed by type name
343
+ maybe_data = result.state.get(output_decl.spec.type_name)
344
+ if isinstance(maybe_data, dict):
345
+ return maybe_data
346
+ return None
347
+
348
+
349
+ class AgentBuilder:
350
+ """Fluent builder that also acts as the runtime agent handle."""
351
+
352
+ def __init__(self, orchestrator: Flock, name: str) -> None:
353
+ self._orchestrator = orchestrator
354
+ self._agent = Agent(name, orchestrator=orchestrator)
355
+ self._agent.model = orchestrator.model
356
+ orchestrator.register_agent(self._agent)
357
+
358
+ # Fluent configuration -------------------------------------------------
359
+
360
+ def description(self, text: str) -> AgentBuilder:
361
+ self._agent.description = text
362
+ return self
363
+
364
+ def consumes(
365
+ self,
366
+ *types: type[BaseModel],
367
+ where: Callable[[BaseModel], bool] | Sequence[Callable[[BaseModel], bool]] | None = None,
368
+ text: str | None = None,
369
+ min_p: float = 0.0,
370
+ from_agents: Iterable[str] | None = None,
371
+ channels: Iterable[str] | None = None,
372
+ join: dict | JoinSpec | None = None,
373
+ batch: dict | BatchSpec | None = None,
374
+ delivery: str = "exclusive",
375
+ mode: str = "both",
376
+ priority: int = 0,
377
+ ) -> AgentBuilder:
378
+ predicates: Sequence[Callable[[BaseModel], bool]] | None
379
+ if where is None:
380
+ predicates = None
381
+ elif callable(where):
382
+ predicates = [where]
383
+ else:
384
+ predicates = list(where)
385
+
386
+ join_spec = self._normalize_join(join)
387
+ batch_spec = self._normalize_batch(batch)
388
+ text_predicates = [TextPredicate(text=text, min_p=min_p)] if text else []
389
+ subscription = Subscription(
390
+ agent_name=self._agent.name,
391
+ types=types,
392
+ where=predicates,
393
+ text_predicates=text_predicates,
394
+ from_agents=from_agents,
395
+ channels=channels,
396
+ join=join_spec,
397
+ batch=batch_spec,
398
+ delivery=delivery,
399
+ mode=mode,
400
+ priority=priority,
401
+ )
402
+ self._agent.subscriptions.append(subscription)
403
+ return self
404
+
405
+ def publishes(
406
+ self, *types: type[BaseModel], visibility: Visibility | None = None
407
+ ) -> PublishBuilder:
408
+ outputs = []
409
+ for model in types:
410
+ spec = ArtifactSpec.from_model(model)
411
+ output = AgentOutput(spec=spec, default_visibility=ensure_visibility(visibility))
412
+ self._agent.outputs.append(output)
413
+ outputs.append(output)
414
+ # T074: Validate configuration after adding outputs
415
+ self._validate_self_trigger_risk()
416
+ return PublishBuilder(self, outputs)
417
+
418
+ def with_utilities(self, *components: AgentComponent) -> AgentBuilder:
419
+ self._agent.utilities.extend(components)
420
+ return self
421
+
422
+ def with_engines(self, *engines: EngineComponent) -> AgentBuilder:
423
+ self._agent.engines.extend(engines)
424
+ return self
425
+
426
+ def best_of(self, n: int, score: Callable[[EvalResult], float]) -> AgentBuilder:
427
+ self._agent.best_of_n = max(1, n)
428
+ self._agent.best_of_score = score
429
+ # T074: Validate best_of value
430
+ self._validate_best_of(n)
431
+ return self
432
+
433
+ def max_concurrency(self, n: int) -> AgentBuilder:
434
+ self._agent.set_max_concurrency(n)
435
+ # T074: Validate concurrency value
436
+ self._validate_concurrency(n)
437
+ return self
438
+
439
+ def calls(self, func: Callable[..., Any]) -> AgentBuilder:
440
+ function_registry.register(func)
441
+ self._agent.calls_func = func
442
+ return self
443
+
444
+ def with_tools(self, funcs: Iterable[Callable[..., Any]]) -> AgentBuilder:
445
+ self._agent.tools.update(funcs)
446
+ return self
447
+
448
+ def with_mcps(self, server_names: Iterable[str]) -> AgentBuilder:
449
+ """Assign MCP servers to this agent.
450
+
451
+ Architecture Decision: AD001 - Two-Level Architecture
452
+ Agents reference servers registered at orchestrator level.
453
+
454
+ Args:
455
+ server_names: Names of MCP servers this agent should use
456
+
457
+ Returns:
458
+ self for method chaining
459
+
460
+ Raises:
461
+ ValueError: If any server name is not registered with orchestrator
462
+
463
+ Example:
464
+ >>> agent = (
465
+ ... orchestrator.agent("file_agent")
466
+ ... .with_mcps(["filesystem", "github"])
467
+ ... .build()
468
+ ... )
469
+ """
470
+ # Convert to set for efficient lookup
471
+ server_set = set(server_names)
472
+
473
+ # Validate all servers exist in orchestrator
474
+ registered_servers = set(self._orchestrator._mcp_configs.keys())
475
+ invalid_servers = server_set - registered_servers
476
+
477
+ if invalid_servers:
478
+ available = list(registered_servers) if registered_servers else ["none"]
479
+ raise ValueError(
480
+ f"MCP servers not registered: {invalid_servers}. "
481
+ f"Available servers: {available}. "
482
+ f"Register servers using orchestrator.add_mcp() first."
483
+ )
484
+
485
+ # Store in agent
486
+ self._agent.mcp_server_names = server_set
487
+
488
+ return self
489
+
490
+ def labels(self, *labels: str) -> AgentBuilder:
491
+ self._agent.labels.update(labels)
492
+ return self
493
+
494
+ def tenant(self, tenant_id: str) -> AgentBuilder:
495
+ self._agent.tenant_id = tenant_id
496
+ return self
497
+
498
+ def prevent_self_trigger(self, enabled: bool = True) -> AgentBuilder:
499
+ """Prevent agent from being triggered by its own outputs.
500
+
501
+ When enabled (default), the orchestrator will skip scheduling this agent
502
+ for artifacts it produced itself. This prevents infinite feedback loops
503
+ when an agent consumes and publishes the same type.
504
+
505
+ Args:
506
+ enabled: True to prevent self-triggering (safe default),
507
+ False to allow feedback loops (advanced use case)
508
+
509
+ Returns:
510
+ AgentBuilder for method chaining
511
+
512
+ Example:
513
+ # Safe by default (recommended)
514
+ agent.consumes(Document).publishes(Document)
515
+ # Won't trigger on own outputs ✅
516
+
517
+ # Explicit feedback loop (use with caution!)
518
+ agent.consumes(Data, where=lambda d: d.depth < 10)
519
+ .publishes(Data)
520
+ .prevent_self_trigger(False) # Acknowledge risk
521
+ """
522
+ self._agent.prevent_self_trigger = enabled
523
+ return self
524
+
525
+ # Runtime helpers ------------------------------------------------------
526
+
527
+ def run(self, *inputs: BaseModel) -> RunHandle:
528
+ return RunHandle(self._agent, list(inputs))
529
+
530
+ def then(self, other: AgentBuilder) -> Pipeline:
531
+ return Pipeline([self, other])
532
+
533
+ # Validation -----------------------------------------------------------
534
+
535
+ def _validate_self_trigger_risk(self) -> None:
536
+ """T074: Warn if agent consumes and publishes same type (feedback loop risk)."""
537
+ from flock.logging.logging import get_logger
538
+
539
+ logger = get_logger(__name__)
540
+
541
+ # Get types agent consumes
542
+ consuming_types = set()
543
+ for sub in self._agent.subscriptions:
544
+ consuming_types.update(sub.type_names)
545
+
546
+ # Get types agent publishes
547
+ publishing_types = {output.spec.type_name for output in self._agent.outputs}
548
+
549
+ # Check for overlap
550
+ overlap = consuming_types.intersection(publishing_types)
551
+ if overlap and self._agent.prevent_self_trigger:
552
+ logger.warning(
553
+ f"Agent '{self._agent.name}' consumes and publishes {overlap}. "
554
+ f"Feedback loop risk detected. Agent has prevent_self_trigger=True (safe), "
555
+ f"but consider adding filtering: .consumes(Type, where=lambda x: ...) "
556
+ f"or use .prevent_self_trigger(False) for intentional feedback."
557
+ )
558
+
559
+ def _validate_best_of(self, n: int) -> None:
560
+ """T074: Warn if best_of value is excessively high."""
561
+ from flock.logging.logging import get_logger
562
+
563
+ logger = get_logger(__name__)
564
+
565
+ if n > 100:
566
+ logger.warning(
567
+ f"Agent '{self._agent.name}' has best_of({n}) which is very high. "
568
+ f"Typical values are 3-10. High values increase cost and latency. "
569
+ f"Consider reducing unless you have specific requirements."
570
+ )
571
+
572
+ def _validate_concurrency(self, n: int) -> None:
573
+ """T074: Warn if max_concurrency is excessively high."""
574
+ from flock.logging.logging import get_logger
575
+
576
+ logger = get_logger(__name__)
577
+
578
+ if n > 1000:
579
+ logger.warning(
580
+ f"Agent '{self._agent.name}' has max_concurrency({n}) which is very high. "
581
+ f"Typical values are 1-50. Excessive concurrency may cause resource issues. "
582
+ f"Consider reducing unless you have specific infrastructure."
583
+ )
584
+
585
+ # Utility --------------------------------------------------------------
586
+
587
+ def _normalize_join(self, value: dict | JoinSpec | None) -> JoinSpec | None:
588
+ if value is None or isinstance(value, JoinSpec):
589
+ return value
590
+ return JoinSpec(
591
+ kind=value.get("kind", "all_of"),
592
+ window=float(value.get("window", 0.0)),
593
+ by=value.get("by"),
594
+ )
595
+
596
+ def _normalize_batch(self, value: dict | BatchSpec | None) -> BatchSpec | None:
597
+ if value is None or isinstance(value, BatchSpec):
598
+ return value
599
+ return BatchSpec(
600
+ size=int(value.get("size", 1)),
601
+ within=float(value.get("within", 0.0)),
602
+ by=value.get("by"),
603
+ )
604
+
605
+ # Properties -----------------------------------------------------------
606
+
607
+ @property
608
+ def name(self) -> str:
609
+ return self._agent.name
610
+
611
+ @property
612
+ def agent(self) -> Agent:
613
+ return self._agent
614
+
615
+
616
+ class PublishBuilder:
617
+ """Helper returned by `.publishes(...)` to support `.only_for` sugar."""
618
+
619
+ def __init__(self, parent: AgentBuilder, outputs: Sequence[AgentOutput]) -> None:
620
+ self._parent = parent
621
+ self._outputs = list(outputs)
622
+
623
+ def only_for(self, *agent_names: str) -> AgentBuilder:
624
+ visibility = only_for(*agent_names)
625
+ for output in self._outputs:
626
+ output.default_visibility = visibility
627
+ return self._parent
628
+
629
+ def visibility(self, value: Visibility) -> AgentBuilder:
630
+ for output in self._outputs:
631
+ output.default_visibility = value
632
+ return self._parent
633
+
634
+ def __getattr__(self, item):
635
+ return getattr(self._parent, item)
636
+
637
+
638
+ class RunHandle:
639
+ """Represents a chained run starting from a given agent."""
640
+
641
+ def __init__(self, agent: Agent, inputs: list[BaseModel]) -> None:
642
+ self.agent = agent
643
+ self.inputs = inputs
644
+ self._chain: list[Agent] = [agent]
645
+
646
+ def then(self, builder: AgentBuilder) -> RunHandle:
647
+ self._chain.append(builder.agent)
648
+ return self
649
+
650
+ async def execute(self) -> list[Artifact]:
651
+ orchestrator = self.agent._orchestrator
652
+ artifacts = await orchestrator.direct_invoke(self.agent, self.inputs)
653
+ for agent in self._chain[1:]:
654
+ artifacts = await orchestrator.direct_invoke(agent, artifacts)
655
+ return artifacts
656
+
657
+
658
+ class Pipeline:
659
+ def __init__(self, builders: Sequence[AgentBuilder]) -> None:
660
+ self.builders = list(builders)
661
+
662
+ def then(self, builder: AgentBuilder) -> Pipeline:
663
+ self.builders.append(builder)
664
+ return self
665
+
666
+ async def execute(self) -> list[Artifact]:
667
+ orchestrator = self.builders[0].agent._orchestrator
668
+ artifacts: list[Artifact] = []
669
+ for builder in self.builders:
670
+ inputs = artifacts if artifacts else []
671
+ artifacts = await orchestrator.direct_invoke(builder.agent, inputs)
672
+ return artifacts
673
+
674
+
675
+ __all__ = [
676
+ "Agent",
677
+ "AgentBuilder",
678
+ ]