flock-core 0.4.543__py3-none-any.whl → 0.5.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of flock-core might be problematic. Click here for more details.

Files changed (501) hide show
  1. flock/__init__.py +12 -217
  2. flock/agent.py +1079 -0
  3. flock/api/themes.py +71 -0
  4. flock/artifacts.py +86 -0
  5. flock/cli.py +147 -0
  6. flock/components.py +189 -0
  7. flock/dashboard/__init__.py +30 -0
  8. flock/dashboard/collector.py +559 -0
  9. flock/dashboard/events.py +188 -0
  10. flock/dashboard/graph_builder.py +563 -0
  11. flock/dashboard/launcher.py +235 -0
  12. flock/dashboard/models/graph.py +156 -0
  13. flock/dashboard/service.py +991 -0
  14. flock/dashboard/static_v2/assets/index-DFRnI_mt.js +111 -0
  15. flock/dashboard/static_v2/assets/index-fPLNdmp1.css +1 -0
  16. flock/dashboard/static_v2/index.html +13 -0
  17. flock/dashboard/websocket.py +246 -0
  18. flock/engines/__init__.py +6 -0
  19. flock/engines/dspy_engine.py +932 -0
  20. flock/examples.py +131 -0
  21. flock/frontend/README.md +778 -0
  22. flock/frontend/docs/DESIGN_SYSTEM.md +1980 -0
  23. flock/frontend/index.html +12 -0
  24. flock/frontend/package-lock.json +4337 -0
  25. flock/frontend/package.json +48 -0
  26. flock/frontend/src/App.tsx +139 -0
  27. flock/frontend/src/__tests__/integration/graph-snapshot.test.tsx +647 -0
  28. flock/frontend/src/__tests__/integration/indexeddb-persistence.test.tsx +699 -0
  29. flock/frontend/src/components/common/BuildInfo.tsx +39 -0
  30. flock/frontend/src/components/common/EmptyState.module.css +115 -0
  31. flock/frontend/src/components/common/EmptyState.tsx +128 -0
  32. flock/frontend/src/components/common/ErrorBoundary.module.css +169 -0
  33. flock/frontend/src/components/common/ErrorBoundary.tsx +118 -0
  34. flock/frontend/src/components/common/KeyboardShortcutsDialog.css +251 -0
  35. flock/frontend/src/components/common/KeyboardShortcutsDialog.tsx +151 -0
  36. flock/frontend/src/components/common/LoadingSpinner.module.css +97 -0
  37. flock/frontend/src/components/common/LoadingSpinner.tsx +29 -0
  38. flock/frontend/src/components/controls/PublishControl.css +547 -0
  39. flock/frontend/src/components/controls/PublishControl.test.tsx +543 -0
  40. flock/frontend/src/components/controls/PublishControl.tsx +432 -0
  41. flock/frontend/src/components/details/DetailWindowContainer.tsx +58 -0
  42. flock/frontend/src/components/details/LiveOutputTab.test.tsx +792 -0
  43. flock/frontend/src/components/details/LiveOutputTab.tsx +220 -0
  44. flock/frontend/src/components/details/MessageDetailWindow.tsx +439 -0
  45. flock/frontend/src/components/details/MessageHistoryTab.tsx +374 -0
  46. flock/frontend/src/components/details/NodeDetailWindow.test.tsx +501 -0
  47. flock/frontend/src/components/details/NodeDetailWindow.tsx +218 -0
  48. flock/frontend/src/components/details/RunStatusTab.tsx +348 -0
  49. flock/frontend/src/components/details/tabs.test.tsx +1015 -0
  50. flock/frontend/src/components/filters/ArtifactTypeFilter.tsx +21 -0
  51. flock/frontend/src/components/filters/CorrelationIDFilter.module.css +102 -0
  52. flock/frontend/src/components/filters/CorrelationIDFilter.test.tsx +197 -0
  53. flock/frontend/src/components/filters/CorrelationIDFilter.tsx +121 -0
  54. flock/frontend/src/components/filters/FilterFlyout.module.css +104 -0
  55. flock/frontend/src/components/filters/FilterFlyout.tsx +80 -0
  56. flock/frontend/src/components/filters/FilterPills.module.css +220 -0
  57. flock/frontend/src/components/filters/FilterPills.test.tsx +189 -0
  58. flock/frontend/src/components/filters/FilterPills.tsx +143 -0
  59. flock/frontend/src/components/filters/ProducerFilter.tsx +21 -0
  60. flock/frontend/src/components/filters/SavedFiltersControl.module.css +60 -0
  61. flock/frontend/src/components/filters/SavedFiltersControl.test.tsx +158 -0
  62. flock/frontend/src/components/filters/SavedFiltersControl.tsx +159 -0
  63. flock/frontend/src/components/filters/TagFilter.tsx +21 -0
  64. flock/frontend/src/components/filters/TimeRangeFilter.module.css +115 -0
  65. flock/frontend/src/components/filters/TimeRangeFilter.test.tsx +154 -0
  66. flock/frontend/src/components/filters/TimeRangeFilter.tsx +110 -0
  67. flock/frontend/src/components/filters/VisibilityFilter.tsx +21 -0
  68. flock/frontend/src/components/graph/AgentNode.test.tsx +77 -0
  69. flock/frontend/src/components/graph/AgentNode.tsx +324 -0
  70. flock/frontend/src/components/graph/GraphCanvas.tsx +613 -0
  71. flock/frontend/src/components/graph/MessageFlowEdge.tsx +128 -0
  72. flock/frontend/src/components/graph/MessageNode.test.tsx +64 -0
  73. flock/frontend/src/components/graph/MessageNode.tsx +129 -0
  74. flock/frontend/src/components/graph/MiniMap.tsx +47 -0
  75. flock/frontend/src/components/graph/TransformEdge.tsx +123 -0
  76. flock/frontend/src/components/layout/DashboardLayout.css +420 -0
  77. flock/frontend/src/components/layout/DashboardLayout.tsx +287 -0
  78. flock/frontend/src/components/layout/Header.module.css +88 -0
  79. flock/frontend/src/components/layout/Header.tsx +52 -0
  80. flock/frontend/src/components/modules/HistoricalArtifactsModule.module.css +288 -0
  81. flock/frontend/src/components/modules/HistoricalArtifactsModule.tsx +450 -0
  82. flock/frontend/src/components/modules/HistoricalArtifactsModuleWrapper.tsx +13 -0
  83. flock/frontend/src/components/modules/JsonAttributeRenderer.tsx +140 -0
  84. flock/frontend/src/components/modules/ModuleRegistry.test.ts +333 -0
  85. flock/frontend/src/components/modules/ModuleRegistry.ts +93 -0
  86. flock/frontend/src/components/modules/ModuleWindow.tsx +223 -0
  87. flock/frontend/src/components/modules/TraceModuleJaeger.tsx +1971 -0
  88. flock/frontend/src/components/modules/TraceModuleJaegerWrapper.tsx +13 -0
  89. flock/frontend/src/components/modules/registerModules.ts +29 -0
  90. flock/frontend/src/components/settings/AdvancedSettings.tsx +175 -0
  91. flock/frontend/src/components/settings/AppearanceSettings.tsx +185 -0
  92. flock/frontend/src/components/settings/GraphSettings.tsx +110 -0
  93. flock/frontend/src/components/settings/MultiSelect.tsx +235 -0
  94. flock/frontend/src/components/settings/SettingsPanel.css +327 -0
  95. flock/frontend/src/components/settings/SettingsPanel.tsx +131 -0
  96. flock/frontend/src/components/settings/ThemeSelector.tsx +298 -0
  97. flock/frontend/src/components/settings/TracingSettings.tsx +404 -0
  98. flock/frontend/src/hooks/useKeyboardShortcuts.ts +148 -0
  99. flock/frontend/src/hooks/useModulePersistence.test.ts +442 -0
  100. flock/frontend/src/hooks/useModulePersistence.ts +154 -0
  101. flock/frontend/src/hooks/useModules.ts +157 -0
  102. flock/frontend/src/hooks/usePersistence.ts +141 -0
  103. flock/frontend/src/main.tsx +13 -0
  104. flock/frontend/src/services/api.ts +337 -0
  105. flock/frontend/src/services/graphService.test.ts +330 -0
  106. flock/frontend/src/services/graphService.ts +75 -0
  107. flock/frontend/src/services/indexeddb.test.ts +793 -0
  108. flock/frontend/src/services/indexeddb.ts +848 -0
  109. flock/frontend/src/services/layout.test.ts +437 -0
  110. flock/frontend/src/services/layout.ts +357 -0
  111. flock/frontend/src/services/themeApplicator.ts +140 -0
  112. flock/frontend/src/services/themeService.ts +77 -0
  113. flock/frontend/src/services/websocket.ts +650 -0
  114. flock/frontend/src/store/filterStore.test.ts +250 -0
  115. flock/frontend/src/store/filterStore.ts +272 -0
  116. flock/frontend/src/store/graphStore.test.ts +570 -0
  117. flock/frontend/src/store/graphStore.ts +462 -0
  118. flock/frontend/src/store/moduleStore.test.ts +253 -0
  119. flock/frontend/src/store/moduleStore.ts +75 -0
  120. flock/frontend/src/store/settingsStore.ts +188 -0
  121. flock/frontend/src/store/streamStore.ts +68 -0
  122. flock/frontend/src/store/uiStore.test.ts +54 -0
  123. flock/frontend/src/store/uiStore.ts +122 -0
  124. flock/frontend/src/store/wsStore.ts +34 -0
  125. flock/frontend/src/styles/index.css +15 -0
  126. flock/frontend/src/styles/scrollbar.css +47 -0
  127. flock/frontend/src/styles/variables.css +488 -0
  128. flock/frontend/src/test/setup.ts +1 -0
  129. flock/frontend/src/types/filters.ts +47 -0
  130. flock/frontend/src/types/graph.ts +95 -0
  131. flock/frontend/src/types/modules.ts +10 -0
  132. flock/frontend/src/types/theme.ts +55 -0
  133. flock/frontend/src/utils/artifacts.ts +24 -0
  134. flock/frontend/src/utils/mockData.ts +98 -0
  135. flock/frontend/src/utils/performance.ts +16 -0
  136. flock/frontend/src/vite-env.d.ts +17 -0
  137. flock/frontend/tsconfig.json +27 -0
  138. flock/frontend/tsconfig.node.json +11 -0
  139. flock/frontend/vite.config.ts +25 -0
  140. flock/frontend/vitest.config.ts +11 -0
  141. flock/{core/util → helper}/cli_helper.py +9 -5
  142. flock/{core/logging → logging}/__init__.py +2 -3
  143. flock/logging/auto_trace.py +159 -0
  144. flock/{core/logging → logging}/formatters/enum_builder.py +3 -4
  145. flock/{core/logging → logging}/formatters/theme_builder.py +19 -44
  146. flock/{core/logging → logging}/formatters/themed_formatter.py +69 -107
  147. flock/{core/logging → logging}/logging.py +78 -61
  148. flock/{core/logging → logging}/telemetry.py +66 -26
  149. flock/{core/logging → logging}/telemetry_exporter/base_exporter.py +2 -2
  150. flock/logging/telemetry_exporter/duckdb_exporter.py +216 -0
  151. flock/{core/logging → logging}/telemetry_exporter/file_exporter.py +13 -10
  152. flock/{core/logging → logging}/telemetry_exporter/sqlite_exporter.py +2 -3
  153. flock/logging/trace_and_logged.py +304 -0
  154. flock/mcp/__init__.py +91 -0
  155. flock/{core/mcp/mcp_client.py → mcp/client.py} +131 -158
  156. flock/{core/mcp/mcp_config.py → mcp/config.py} +86 -132
  157. flock/mcp/manager.py +286 -0
  158. flock/mcp/servers/sse/__init__.py +1 -1
  159. flock/mcp/servers/sse/flock_sse_server.py +16 -58
  160. flock/mcp/servers/stdio/__init__.py +1 -1
  161. flock/mcp/servers/stdio/flock_stdio_server.py +13 -53
  162. flock/mcp/servers/streamable_http/flock_streamable_http_server.py +22 -67
  163. flock/mcp/servers/websockets/flock_websocket_server.py +12 -45
  164. flock/{core/mcp/flock_mcp_tool_base.py → mcp/tool.py} +24 -78
  165. flock/mcp/types/__init__.py +42 -0
  166. flock/{core/mcp → mcp}/types/callbacks.py +12 -15
  167. flock/{core/mcp → mcp}/types/factories.py +7 -6
  168. flock/{core/mcp → mcp}/types/handlers.py +13 -18
  169. flock/{core/mcp → mcp}/types/types.py +70 -74
  170. flock/{core/mcp → mcp}/util/helpers.py +3 -3
  171. flock/orchestrator.py +970 -0
  172. flock/registry.py +148 -0
  173. flock/runtime.py +262 -0
  174. flock/service.py +277 -0
  175. flock/store.py +1214 -0
  176. flock/subscription.py +111 -0
  177. flock/themes/andromeda.toml +1 -1
  178. flock/themes/apple-system-colors.toml +1 -1
  179. flock/themes/arcoiris.toml +1 -1
  180. flock/themes/atomonelight.toml +1 -1
  181. flock/themes/ayu copy.toml +1 -1
  182. flock/themes/ayu-light.toml +1 -1
  183. flock/themes/belafonte-day.toml +1 -1
  184. flock/themes/belafonte-night.toml +1 -1
  185. flock/themes/blulocodark.toml +1 -1
  186. flock/themes/breeze.toml +1 -1
  187. flock/themes/broadcast.toml +1 -1
  188. flock/themes/brogrammer.toml +1 -1
  189. flock/themes/builtin-dark.toml +1 -1
  190. flock/themes/builtin-pastel-dark.toml +1 -1
  191. flock/themes/catppuccin-latte.toml +1 -1
  192. flock/themes/catppuccin-macchiato.toml +1 -1
  193. flock/themes/catppuccin-mocha.toml +1 -1
  194. flock/themes/cga.toml +1 -1
  195. flock/themes/chalk.toml +1 -1
  196. flock/themes/ciapre.toml +1 -1
  197. flock/themes/coffee-theme.toml +1 -1
  198. flock/themes/cyberpunkscarletprotocol.toml +1 -1
  199. flock/themes/dark+.toml +1 -1
  200. flock/themes/darkermatrix.toml +1 -1
  201. flock/themes/darkmatrix.toml +2 -2
  202. flock/themes/darkside.toml +1 -1
  203. flock/themes/deep.toml +2 -2
  204. flock/themes/desert.toml +1 -1
  205. flock/themes/django.toml +1 -1
  206. flock/themes/djangosmooth.toml +1 -1
  207. flock/themes/doomone.toml +1 -1
  208. flock/themes/dotgov.toml +1 -1
  209. flock/themes/dracula+.toml +1 -1
  210. flock/themes/duckbones.toml +1 -1
  211. flock/themes/encom.toml +1 -1
  212. flock/themes/espresso.toml +1 -1
  213. flock/themes/everblush.toml +1 -1
  214. flock/themes/fairyfloss.toml +1 -1
  215. flock/themes/fideloper.toml +1 -1
  216. flock/themes/fishtank.toml +1 -1
  217. flock/themes/flexoki-light.toml +1 -1
  218. flock/themes/floraverse.toml +1 -1
  219. flock/themes/framer.toml +1 -1
  220. flock/themes/galizur.toml +1 -1
  221. flock/themes/github.toml +1 -1
  222. flock/themes/grass.toml +1 -1
  223. flock/themes/grey-green.toml +1 -1
  224. flock/themes/gruvboxlight.toml +1 -1
  225. flock/themes/guezwhoz.toml +1 -1
  226. flock/themes/harper.toml +1 -1
  227. flock/themes/hax0r-blue.toml +1 -1
  228. flock/themes/hopscotch.256.toml +1 -1
  229. flock/themes/ic-green-ppl.toml +1 -1
  230. flock/themes/iceberg-dark.toml +1 -1
  231. flock/themes/japanesque.toml +1 -1
  232. flock/themes/jubi.toml +1 -1
  233. flock/themes/kibble.toml +1 -1
  234. flock/themes/kolorit.toml +1 -1
  235. flock/themes/kurokula.toml +1 -1
  236. flock/themes/materialdesigncolors.toml +1 -1
  237. flock/themes/matrix.toml +1 -1
  238. flock/themes/mellifluous.toml +1 -1
  239. flock/themes/midnight-in-mojave.toml +1 -1
  240. flock/themes/monokai-remastered.toml +1 -1
  241. flock/themes/monokai-soda.toml +1 -1
  242. flock/themes/neon.toml +1 -1
  243. flock/themes/neopolitan.toml +5 -5
  244. flock/themes/nord-light.toml +1 -1
  245. flock/themes/ocean.toml +1 -1
  246. flock/themes/onehalfdark.toml +1 -1
  247. flock/themes/onehalflight.toml +1 -1
  248. flock/themes/palenighthc.toml +1 -1
  249. flock/themes/paulmillr.toml +1 -1
  250. flock/themes/pencildark.toml +1 -1
  251. flock/themes/pnevma.toml +1 -1
  252. flock/themes/purple-rain.toml +1 -1
  253. flock/themes/purplepeter.toml +1 -1
  254. flock/themes/raycast-dark.toml +1 -1
  255. flock/themes/red-sands.toml +1 -1
  256. flock/themes/relaxed.toml +1 -1
  257. flock/themes/retro.toml +1 -1
  258. flock/themes/rose-pine.toml +1 -1
  259. flock/themes/royal.toml +1 -1
  260. flock/themes/ryuuko.toml +1 -1
  261. flock/themes/sakura.toml +1 -1
  262. flock/themes/scarlet-protocol.toml +1 -1
  263. flock/themes/seoulbones-dark.toml +1 -1
  264. flock/themes/shades-of-purple.toml +1 -1
  265. flock/themes/smyck.toml +1 -1
  266. flock/themes/softserver.toml +1 -1
  267. flock/themes/solarized-darcula.toml +1 -1
  268. flock/themes/square.toml +1 -1
  269. flock/themes/sugarplum.toml +1 -1
  270. flock/themes/thayer-bright.toml +1 -1
  271. flock/themes/tokyonight.toml +1 -1
  272. flock/themes/tomorrow.toml +1 -1
  273. flock/themes/ubuntu.toml +1 -1
  274. flock/themes/ultradark.toml +1 -1
  275. flock/themes/ultraviolent.toml +1 -1
  276. flock/themes/unikitty.toml +1 -1
  277. flock/themes/urple.toml +1 -1
  278. flock/themes/vesper.toml +1 -1
  279. flock/themes/vimbones.toml +1 -1
  280. flock/themes/wildcherry.toml +1 -1
  281. flock/themes/wilmersdorf.toml +1 -1
  282. flock/themes/wryan.toml +1 -1
  283. flock/themes/xcodedarkhc.toml +1 -1
  284. flock/themes/xcodelight.toml +1 -1
  285. flock/themes/zenbones-light.toml +1 -1
  286. flock/themes/zenwritten-dark.toml +1 -1
  287. flock/utilities.py +301 -0
  288. flock/utility/output_utility_component.py +226 -0
  289. flock/visibility.py +107 -0
  290. flock_core-0.5.0.dist-info/METADATA +964 -0
  291. flock_core-0.5.0.dist-info/RECORD +525 -0
  292. flock_core-0.5.0.dist-info/entry_points.txt +2 -0
  293. {flock_core-0.4.543.dist-info → flock_core-0.5.0.dist-info}/licenses/LICENSE +1 -1
  294. flock/adapter/__init__.py +0 -14
  295. flock/adapter/azure_adapter.py +0 -68
  296. flock/adapter/chroma_adapter.py +0 -73
  297. flock/adapter/faiss_adapter.py +0 -97
  298. flock/adapter/pinecone_adapter.py +0 -51
  299. flock/adapter/vector_base.py +0 -47
  300. flock/cli/assets/release_notes.md +0 -140
  301. flock/cli/config.py +0 -8
  302. flock/cli/constants.py +0 -36
  303. flock/cli/create_agent.py +0 -1
  304. flock/cli/create_flock.py +0 -280
  305. flock/cli/execute_flock.py +0 -620
  306. flock/cli/load_agent.py +0 -1
  307. flock/cli/load_examples.py +0 -1
  308. flock/cli/load_flock.py +0 -192
  309. flock/cli/load_release_notes.py +0 -20
  310. flock/cli/loaded_flock_cli.py +0 -254
  311. flock/cli/manage_agents.py +0 -459
  312. flock/cli/registry_management.py +0 -889
  313. flock/cli/runner.py +0 -41
  314. flock/cli/settings.py +0 -857
  315. flock/cli/utils.py +0 -135
  316. flock/cli/view_results.py +0 -29
  317. flock/cli/yaml_editor.py +0 -396
  318. flock/config.py +0 -56
  319. flock/core/__init__.py +0 -44
  320. flock/core/api/__init__.py +0 -10
  321. flock/core/api/custom_endpoint.py +0 -45
  322. flock/core/api/endpoints.py +0 -262
  323. flock/core/api/main.py +0 -162
  324. flock/core/api/models.py +0 -101
  325. flock/core/api/run_store.py +0 -224
  326. flock/core/api/runner.py +0 -44
  327. flock/core/api/service.py +0 -214
  328. flock/core/config/flock_agent_config.py +0 -11
  329. flock/core/config/scheduled_agent_config.py +0 -40
  330. flock/core/context/context.py +0 -214
  331. flock/core/context/context_manager.py +0 -40
  332. flock/core/context/context_vars.py +0 -11
  333. flock/core/evaluation/utils.py +0 -395
  334. flock/core/execution/batch_executor.py +0 -369
  335. flock/core/execution/evaluation_executor.py +0 -438
  336. flock/core/execution/local_executor.py +0 -31
  337. flock/core/execution/opik_executor.py +0 -103
  338. flock/core/execution/temporal_executor.py +0 -166
  339. flock/core/flock.py +0 -1003
  340. flock/core/flock_agent.py +0 -1258
  341. flock/core/flock_evaluator.py +0 -60
  342. flock/core/flock_factory.py +0 -513
  343. flock/core/flock_module.py +0 -207
  344. flock/core/flock_registry.py +0 -702
  345. flock/core/flock_router.py +0 -83
  346. flock/core/flock_scheduler.py +0 -166
  347. flock/core/flock_server_manager.py +0 -136
  348. flock/core/interpreter/python_interpreter.py +0 -689
  349. flock/core/logging/live_capture.py +0 -137
  350. flock/core/logging/trace_and_logged.py +0 -59
  351. flock/core/mcp/__init__.py +0 -1
  352. flock/core/mcp/flock_mcp_server.py +0 -640
  353. flock/core/mcp/mcp_client_manager.py +0 -201
  354. flock/core/mcp/types/__init__.py +0 -1
  355. flock/core/mixin/dspy_integration.py +0 -445
  356. flock/core/mixin/prompt_parser.py +0 -125
  357. flock/core/serialization/__init__.py +0 -13
  358. flock/core/serialization/callable_registry.py +0 -52
  359. flock/core/serialization/flock_serializer.py +0 -854
  360. flock/core/serialization/json_encoder.py +0 -41
  361. flock/core/serialization/secure_serializer.py +0 -175
  362. flock/core/serialization/serializable.py +0 -342
  363. flock/core/serialization/serialization_utils.py +0 -409
  364. flock/core/util/file_path_utils.py +0 -223
  365. flock/core/util/hydrator.py +0 -309
  366. flock/core/util/input_resolver.py +0 -141
  367. flock/core/util/loader.py +0 -59
  368. flock/core/util/splitter.py +0 -219
  369. flock/di.py +0 -41
  370. flock/evaluators/__init__.py +0 -1
  371. flock/evaluators/declarative/__init__.py +0 -1
  372. flock/evaluators/declarative/declarative_evaluator.py +0 -217
  373. flock/evaluators/memory/memory_evaluator.py +0 -90
  374. flock/evaluators/test/test_case_evaluator.py +0 -38
  375. flock/evaluators/zep/zep_evaluator.py +0 -59
  376. flock/modules/__init__.py +0 -1
  377. flock/modules/assertion/__init__.py +0 -1
  378. flock/modules/assertion/assertion_module.py +0 -286
  379. flock/modules/callback/__init__.py +0 -1
  380. flock/modules/callback/callback_module.py +0 -91
  381. flock/modules/enterprise_memory/README.md +0 -99
  382. flock/modules/enterprise_memory/enterprise_memory_module.py +0 -526
  383. flock/modules/mem0/__init__.py +0 -1
  384. flock/modules/mem0/mem0_module.py +0 -126
  385. flock/modules/mem0_async/__init__.py +0 -1
  386. flock/modules/mem0_async/async_mem0_module.py +0 -126
  387. flock/modules/memory/__init__.py +0 -1
  388. flock/modules/memory/memory_module.py +0 -429
  389. flock/modules/memory/memory_parser.py +0 -125
  390. flock/modules/memory/memory_storage.py +0 -736
  391. flock/modules/output/__init__.py +0 -1
  392. flock/modules/output/output_module.py +0 -196
  393. flock/modules/performance/__init__.py +0 -1
  394. flock/modules/performance/metrics_module.py +0 -678
  395. flock/modules/zep/__init__.py +0 -1
  396. flock/modules/zep/zep_module.py +0 -192
  397. flock/platform/docker_tools.py +0 -49
  398. flock/platform/jaeger_install.py +0 -86
  399. flock/routers/__init__.py +0 -1
  400. flock/routers/agent/__init__.py +0 -1
  401. flock/routers/agent/agent_router.py +0 -236
  402. flock/routers/agent/handoff_agent.py +0 -58
  403. flock/routers/conditional/conditional_router.py +0 -486
  404. flock/routers/default/__init__.py +0 -1
  405. flock/routers/default/default_router.py +0 -80
  406. flock/routers/feedback/feedback_router.py +0 -114
  407. flock/routers/list_generator/list_generator_router.py +0 -166
  408. flock/routers/llm/__init__.py +0 -1
  409. flock/routers/llm/llm_router.py +0 -365
  410. flock/tools/__init__.py +0 -0
  411. flock/tools/azure_tools.py +0 -781
  412. flock/tools/code_tools.py +0 -167
  413. flock/tools/file_tools.py +0 -149
  414. flock/tools/github_tools.py +0 -157
  415. flock/tools/markdown_tools.py +0 -205
  416. flock/tools/system_tools.py +0 -9
  417. flock/tools/text_tools.py +0 -810
  418. flock/tools/web_tools.py +0 -92
  419. flock/tools/zendesk_tools.py +0 -501
  420. flock/webapp/__init__.py +0 -1
  421. flock/webapp/app/__init__.py +0 -0
  422. flock/webapp/app/api/__init__.py +0 -0
  423. flock/webapp/app/api/agent_management.py +0 -237
  424. flock/webapp/app/api/execution.py +0 -503
  425. flock/webapp/app/api/flock_management.py +0 -125
  426. flock/webapp/app/api/registry_viewer.py +0 -29
  427. flock/webapp/app/chat.py +0 -662
  428. flock/webapp/app/config.py +0 -104
  429. flock/webapp/app/dependencies.py +0 -117
  430. flock/webapp/app/main.py +0 -1086
  431. flock/webapp/app/middleware.py +0 -113
  432. flock/webapp/app/models_ui.py +0 -7
  433. flock/webapp/app/services/__init__.py +0 -0
  434. flock/webapp/app/services/feedback_file_service.py +0 -363
  435. flock/webapp/app/services/flock_service.py +0 -345
  436. flock/webapp/app/services/sharing_models.py +0 -81
  437. flock/webapp/app/services/sharing_store.py +0 -597
  438. flock/webapp/app/templates/theme_mapper.html +0 -326
  439. flock/webapp/app/theme_mapper.py +0 -811
  440. flock/webapp/app/utils.py +0 -85
  441. flock/webapp/run.py +0 -219
  442. flock/webapp/static/css/chat.css +0 -301
  443. flock/webapp/static/css/components.css +0 -167
  444. flock/webapp/static/css/header.css +0 -39
  445. flock/webapp/static/css/layout.css +0 -281
  446. flock/webapp/static/css/sidebar.css +0 -127
  447. flock/webapp/static/css/two-pane.css +0 -48
  448. flock/webapp/templates/base.html +0 -389
  449. flock/webapp/templates/chat.html +0 -152
  450. flock/webapp/templates/chat_settings.html +0 -19
  451. flock/webapp/templates/flock_editor.html +0 -16
  452. flock/webapp/templates/index.html +0 -12
  453. flock/webapp/templates/partials/_agent_detail_form.html +0 -93
  454. flock/webapp/templates/partials/_agent_list.html +0 -18
  455. flock/webapp/templates/partials/_agent_manager_view.html +0 -51
  456. flock/webapp/templates/partials/_agent_tools_checklist.html +0 -14
  457. flock/webapp/templates/partials/_chat_container.html +0 -15
  458. flock/webapp/templates/partials/_chat_messages.html +0 -57
  459. flock/webapp/templates/partials/_chat_settings_form.html +0 -85
  460. flock/webapp/templates/partials/_create_flock_form.html +0 -50
  461. flock/webapp/templates/partials/_dashboard_flock_detail.html +0 -17
  462. flock/webapp/templates/partials/_dashboard_flock_file_list.html +0 -16
  463. flock/webapp/templates/partials/_dashboard_flock_properties_preview.html +0 -28
  464. flock/webapp/templates/partials/_dashboard_upload_flock_form.html +0 -16
  465. flock/webapp/templates/partials/_dynamic_input_form_content.html +0 -22
  466. flock/webapp/templates/partials/_env_vars_table.html +0 -23
  467. flock/webapp/templates/partials/_execution_form.html +0 -127
  468. flock/webapp/templates/partials/_execution_view_container.html +0 -28
  469. flock/webapp/templates/partials/_flock_file_list.html +0 -23
  470. flock/webapp/templates/partials/_flock_properties_form.html +0 -52
  471. flock/webapp/templates/partials/_flock_upload_form.html +0 -16
  472. flock/webapp/templates/partials/_header_flock_status.html +0 -5
  473. flock/webapp/templates/partials/_live_logs.html +0 -13
  474. flock/webapp/templates/partials/_load_manager_view.html +0 -49
  475. flock/webapp/templates/partials/_registry_table.html +0 -25
  476. flock/webapp/templates/partials/_registry_viewer_content.html +0 -70
  477. flock/webapp/templates/partials/_results_display.html +0 -78
  478. flock/webapp/templates/partials/_settings_env_content.html +0 -9
  479. flock/webapp/templates/partials/_settings_theme_content.html +0 -14
  480. flock/webapp/templates/partials/_settings_view.html +0 -36
  481. flock/webapp/templates/partials/_share_chat_link_snippet.html +0 -11
  482. flock/webapp/templates/partials/_share_link_snippet.html +0 -35
  483. flock/webapp/templates/partials/_sidebar.html +0 -74
  484. flock/webapp/templates/partials/_structured_data_view.html +0 -40
  485. flock/webapp/templates/partials/_theme_preview.html +0 -36
  486. flock/webapp/templates/registry_viewer.html +0 -84
  487. flock/webapp/templates/shared_run_page.html +0 -140
  488. flock/workflow/__init__.py +0 -0
  489. flock/workflow/activities.py +0 -237
  490. flock/workflow/agent_activities.py +0 -24
  491. flock/workflow/agent_execution_activity.py +0 -240
  492. flock/workflow/flock_workflow.py +0 -225
  493. flock/workflow/temporal_config.py +0 -96
  494. flock/workflow/temporal_setup.py +0 -60
  495. flock_core-0.4.543.dist-info/METADATA +0 -676
  496. flock_core-0.4.543.dist-info/RECORD +0 -572
  497. flock_core-0.4.543.dist-info/entry_points.txt +0 -2
  498. /flock/{core/logging → logging}/formatters/themes.py +0 -0
  499. /flock/{core/logging → logging}/span_middleware/baggage_span_processor.py +0 -0
  500. /flock/{core/mcp → mcp}/util/__init__.py +0 -0
  501. {flock_core-0.4.543.dist-info → flock_core-0.5.0.dist-info}/WHEEL +0 -0
flock/agent.py ADDED
@@ -0,0 +1,1079 @@
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, TypedDict
9
+
10
+ from pydantic import BaseModel
11
+
12
+ from flock.artifacts import Artifact, ArtifactSpec
13
+ from flock.logging.auto_trace import AutoTracedMeta
14
+ from flock.logging.logging import get_logger
15
+ from flock.registry import function_registry, type_registry
16
+ from flock.runtime import Context, EvalInputs, EvalResult
17
+ from flock.subscription import BatchSpec, JoinSpec, Subscription, TextPredicate
18
+ from flock.visibility import AgentIdentity, Visibility, ensure_visibility, only_for
19
+
20
+
21
+ logger = get_logger(__name__)
22
+
23
+ if TYPE_CHECKING: # pragma: no cover - type hints only
24
+ from collections.abc import Callable, Iterable, Sequence
25
+
26
+ from flock.components import AgentComponent, EngineComponent
27
+ from flock.orchestrator import Flock
28
+
29
+
30
+ class MCPServerConfig(TypedDict, total=False):
31
+ """Configuration for MCP server assignment to an agent.
32
+
33
+ All fields are optional. If omitted, no restrictions apply.
34
+
35
+ Attributes:
36
+ roots: Filesystem paths this server can access.
37
+ Empty list or omitted = no mount restrictions.
38
+ tool_whitelist: Tool names the agent can use from this server.
39
+ Empty list or omitted = all tools available.
40
+
41
+ Examples:
42
+ >>> # No restrictions
43
+ >>> config: MCPServerConfig = {}
44
+
45
+ >>> # Mount restrictions only
46
+ >>> config: MCPServerConfig = {"roots": ["/workspace/data"]}
47
+
48
+ >>> # Tool whitelist only
49
+ >>> config: MCPServerConfig = {"tool_whitelist": ["read_file", "write_file"]}
50
+
51
+ >>> # Both restrictions
52
+ >>> config: MCPServerConfig = {
53
+ ... "roots": ["/workspace/data"],
54
+ ... "tool_whitelist": ["read_file"]
55
+ ... }
56
+ """
57
+
58
+ roots: list[str]
59
+ tool_whitelist: list[str]
60
+
61
+
62
+ @dataclass
63
+ class AgentOutput:
64
+ spec: ArtifactSpec
65
+ default_visibility: Visibility
66
+
67
+ def apply(
68
+ self,
69
+ data: dict[str, Any],
70
+ *,
71
+ produced_by: str,
72
+ metadata: dict[str, Any] | None = None,
73
+ ) -> Artifact:
74
+ metadata = metadata or {}
75
+ return self.spec.build(
76
+ produced_by=produced_by,
77
+ data=data,
78
+ visibility=metadata.get("visibility", self.default_visibility),
79
+ correlation_id=metadata.get("correlation_id"),
80
+ partition_key=metadata.get("partition_key"),
81
+ tags=metadata.get("tags"),
82
+ version=metadata.get("version", 1),
83
+ artifact_id=metadata.get("artifact_id"), # Phase 6: Preserve engine's ID
84
+ )
85
+
86
+
87
+ class Agent(metaclass=AutoTracedMeta):
88
+ """Executable agent constructed via `AgentBuilder`.
89
+
90
+ All public methods are automatically traced via OpenTelemetry.
91
+ """
92
+
93
+ def __init__(self, name: str, *, orchestrator: Flock) -> None:
94
+ self.name = name
95
+ self.description: str | None = None
96
+ self._orchestrator = orchestrator
97
+ self.subscriptions: list[Subscription] = []
98
+ self.outputs: list[AgentOutput] = []
99
+ self.utilities: list[AgentComponent] = []
100
+ self.engines: list[EngineComponent] = []
101
+ self.best_of_n: int = 1
102
+ self.best_of_score: Callable[[EvalResult], float] | None = None
103
+ self.max_concurrency: int = 2
104
+ self._semaphore = asyncio.Semaphore(self.max_concurrency)
105
+ self.calls_func: Callable[..., Any] | None = None
106
+ self.tools: set[Callable[..., Any]] = set()
107
+ self.labels: set[str] = set()
108
+ self.tenant_id: str | None = None
109
+ self.model: str | None = None
110
+ self.prevent_self_trigger: bool = True # T065: Prevent infinite feedback loops
111
+ # MCP integration
112
+ self.mcp_server_names: set[str] = set()
113
+ self.mcp_mount_points: list[str] = [] # Deprecated: Use mcp_server_mounts instead
114
+ self.mcp_server_mounts: dict[str, list[str]] = {} # Server-specific mount points
115
+ self.tool_whitelist: list[str] | None = None
116
+
117
+ @property
118
+ def identity(self) -> AgentIdentity:
119
+ return AgentIdentity(name=self.name, labels=self.labels, tenant_id=self.tenant_id)
120
+
121
+ def set_max_concurrency(self, value: int) -> None:
122
+ self.max_concurrency = max(1, value)
123
+ self._semaphore = asyncio.Semaphore(self.max_concurrency)
124
+
125
+ async def run_direct(self, *inputs: BaseModel) -> list[Artifact]:
126
+ return await self._orchestrator.direct_invoke(self, list(inputs))
127
+
128
+ async def execute(self, ctx: Context, artifacts: list[Artifact]) -> list[Artifact]:
129
+ async with self._semaphore:
130
+ try:
131
+ self._resolve_engines()
132
+ self._resolve_utilities()
133
+ await self._run_initialize(ctx)
134
+ processed_inputs = await self._run_pre_consume(ctx, artifacts)
135
+ eval_inputs = EvalInputs(artifacts=processed_inputs, state=dict(ctx.state))
136
+ eval_inputs = await self._run_pre_evaluate(ctx, eval_inputs)
137
+ result = await self._run_engines(ctx, eval_inputs)
138
+ result = await self._run_post_evaluate(ctx, eval_inputs, result)
139
+ outputs = await self._make_outputs(ctx, result)
140
+ await self._run_post_publish(ctx, outputs)
141
+ if self.calls_func:
142
+ await self._invoke_call(ctx, outputs or processed_inputs)
143
+ return outputs
144
+ except Exception as exc:
145
+ await self._run_error(ctx, exc)
146
+ raise
147
+ finally:
148
+ await self._run_terminate(ctx)
149
+
150
+ async def _get_mcp_tools(self, ctx: Context) -> list[Callable]:
151
+ """Lazy-load MCP tools from assigned servers.
152
+
153
+ Architecture Decision: AD001 - Two-Level Architecture
154
+ Agents fetch tools from servers registered at orchestrator level.
155
+
156
+ Architecture Decision: AD003 - Tool Namespacing
157
+ All tools are namespaced as {server}__{tool}.
158
+
159
+ Architecture Decision: AD007 - Graceful Degradation
160
+ If MCP loading fails, returns empty list so agent continues with native tools.
161
+
162
+ Args:
163
+ ctx: Current execution context with agent_id and run_id
164
+
165
+ Returns:
166
+ List of DSPy-compatible tool callables
167
+ """
168
+ if not self.mcp_server_names:
169
+ # No MCP servers assigned to this agent
170
+ return []
171
+
172
+ try:
173
+ # Get the MCP manager from orchestrator
174
+ manager = self._orchestrator.get_mcp_manager()
175
+
176
+ # Fetch tools from all assigned servers
177
+ tools_dict = await manager.get_tools_for_agent(
178
+ agent_id=self.name,
179
+ run_id=ctx.task_id,
180
+ server_names=self.mcp_server_names,
181
+ server_mounts=self.mcp_server_mounts, # Pass server-specific mounts
182
+ )
183
+
184
+ # Whitelisting logic
185
+ tool_whitelist = self.tool_whitelist
186
+ if (
187
+ tool_whitelist is not None
188
+ and isinstance(tool_whitelist, list)
189
+ and len(tool_whitelist) > 0
190
+ ):
191
+ filtered_tools: dict[str, Any] = {}
192
+ for tool_key, tool_entry in tools_dict.items():
193
+ if isinstance(tool_entry, dict):
194
+ original_name = tool_entry.get("original_name", None)
195
+ if original_name is not None and original_name in tool_whitelist:
196
+ filtered_tools[tool_key] = tool_entry
197
+
198
+ tools_dict = filtered_tools
199
+
200
+ # Convert to DSPy tool callables
201
+ dspy_tools = []
202
+ for namespaced_name, tool_info in tools_dict.items():
203
+ tool_info["server_name"]
204
+ flock_tool = tool_info["tool"] # Already a FlockMCPTool
205
+ client = tool_info["client"]
206
+
207
+ # Convert to DSPy tool
208
+ dspy_tool = flock_tool.as_dspy_tool(server=client)
209
+
210
+ # Update name to include namespace
211
+ dspy_tool.name = namespaced_name
212
+
213
+ dspy_tools.append(dspy_tool)
214
+
215
+ return dspy_tools
216
+
217
+ except Exception as e:
218
+ # Architecture Decision: AD007 - Graceful Degradation
219
+ # Agent continues with native tools only
220
+ logger.error(f"Failed to load MCP tools for agent {self.name}: {e}", exc_info=True)
221
+ return []
222
+
223
+ async def _run_initialize(self, ctx: Context) -> None:
224
+ for component in self.utilities:
225
+ await component.on_initialize(self, ctx)
226
+ for engine in self.engines:
227
+ await engine.on_initialize(self, ctx)
228
+
229
+ async def _run_pre_consume(self, ctx: Context, inputs: list[Artifact]) -> list[Artifact]:
230
+ current = inputs
231
+ for component in self.utilities:
232
+ current = await component.on_pre_consume(self, ctx, current)
233
+ return current
234
+
235
+ async def _run_pre_evaluate(self, ctx: Context, inputs: EvalInputs) -> EvalInputs:
236
+ current = inputs
237
+ for component in self.utilities:
238
+ current = await component.on_pre_evaluate(self, ctx, current)
239
+ return current
240
+
241
+ async def _run_engines(self, ctx: Context, inputs: EvalInputs) -> EvalResult:
242
+ engines = self._resolve_engines()
243
+ if not engines:
244
+ return EvalResult(artifacts=inputs.artifacts, state=inputs.state)
245
+
246
+ async def run_chain() -> EvalResult:
247
+ current_inputs = inputs
248
+ accumulated_logs: list[str] = []
249
+ accumulated_metrics: dict[str, float] = {}
250
+ for engine in engines:
251
+ current_inputs = await engine.on_pre_evaluate(self, ctx, current_inputs)
252
+ result = await engine.evaluate(self, ctx, current_inputs)
253
+
254
+ # AUTO-WRAP: If engine returns BaseModel instead of EvalResult, wrap it
255
+ from flock.runtime import EvalResult as ER
256
+
257
+ if isinstance(result, BaseModel) and not isinstance(result, ER):
258
+ result = ER.from_object(result, agent=self)
259
+
260
+ result = await engine.on_post_evaluate(self, ctx, current_inputs, result)
261
+ accumulated_logs.extend(result.logs)
262
+ accumulated_metrics.update(result.metrics)
263
+ merged_state = dict(current_inputs.state)
264
+ merged_state.update(result.state)
265
+ current_inputs = EvalInputs(
266
+ artifacts=result.artifacts or current_inputs.artifacts,
267
+ state=merged_state,
268
+ )
269
+ return EvalResult(
270
+ artifacts=current_inputs.artifacts,
271
+ state=current_inputs.state,
272
+ metrics=accumulated_metrics,
273
+ logs=accumulated_logs,
274
+ )
275
+
276
+ if self.best_of_n <= 1:
277
+ return await run_chain()
278
+
279
+ async with asyncio.TaskGroup() as tg: # Python 3.12
280
+ tasks: list[asyncio.Task[EvalResult]] = []
281
+ for _ in range(self.best_of_n):
282
+ tasks.append(tg.create_task(run_chain()))
283
+ results = [task.result() for task in tasks]
284
+ if not results:
285
+ return EvalResult(artifacts=[], state={})
286
+ if self.best_of_score is None:
287
+ return results[0]
288
+ return max(results, key=self.best_of_score)
289
+
290
+ async def _run_post_evaluate(
291
+ self, ctx: Context, inputs: EvalInputs, result: EvalResult
292
+ ) -> EvalResult:
293
+ current = result
294
+ for component in self.utilities:
295
+ current = await component.on_post_evaluate(self, ctx, inputs, current)
296
+ return current
297
+
298
+ async def _make_outputs(self, ctx: Context, result: EvalResult) -> list[Artifact]:
299
+ if not self.outputs:
300
+ # Utility agents may not publish anything
301
+ return list(result.artifacts)
302
+
303
+ produced: list[Artifact] = []
304
+ for output_decl in self.outputs:
305
+ # Phase 6: Find the matching artifact from engine result to preserve its ID
306
+ matching_artifact = self._find_matching_artifact(output_decl, result)
307
+
308
+ payload = self._select_payload(output_decl, result)
309
+ if payload is None:
310
+ continue
311
+ metadata = {
312
+ "correlation_id": ctx.correlation_id,
313
+ }
314
+
315
+ # Phase 6: Preserve artifact ID from engine (for streaming message preview)
316
+ if matching_artifact:
317
+ metadata["artifact_id"] = matching_artifact.id
318
+
319
+ artifact = output_decl.apply(payload, produced_by=self.name, metadata=metadata)
320
+ produced.append(artifact)
321
+ await ctx.board.publish(artifact)
322
+ return produced
323
+
324
+ async def _run_post_publish(self, ctx: Context, artifacts: Sequence[Artifact]) -> None:
325
+ for artifact in artifacts:
326
+ for component in self.utilities:
327
+ await component.on_post_publish(self, ctx, artifact)
328
+
329
+ async def _invoke_call(self, ctx: Context, artifacts: Sequence[Artifact]) -> None:
330
+ func = self.calls_func
331
+ if func is None:
332
+ return
333
+ if not artifacts:
334
+ return
335
+ first = artifacts[0]
336
+ model_cls = type_registry.resolve(first.type)
337
+ payload = model_cls(**first.payload)
338
+ maybe_coro = func(payload)
339
+ if asyncio.iscoroutine(maybe_coro): # pragma: no cover - optional async support
340
+ await maybe_coro
341
+
342
+ async def _run_error(self, ctx: Context, error: Exception) -> None:
343
+ for component in self.utilities:
344
+ await component.on_error(self, ctx, error)
345
+ for engine in self.engines:
346
+ await engine.on_error(self, ctx, error)
347
+
348
+ async def _run_terminate(self, ctx: Context) -> None:
349
+ for component in self.utilities:
350
+ await component.on_terminate(self, ctx)
351
+ for engine in self.engines:
352
+ await engine.on_terminate(self, ctx)
353
+
354
+ def _resolve_engines(self) -> list[EngineComponent]:
355
+ if self.engines:
356
+ return self.engines
357
+ try:
358
+ from flock.engines import DSPyEngine
359
+ except Exception: # pragma: no cover - optional dependency issues
360
+ return []
361
+
362
+ default_engine = DSPyEngine(
363
+ model=self._orchestrator.model or os.getenv("DEFAULT_MODEL", "openai/gpt-4.1"),
364
+ instructions=self.description,
365
+ )
366
+ self.engines = [default_engine]
367
+ return self.engines
368
+
369
+ def _resolve_utilities(self) -> list[AgentComponent]:
370
+ if self.utilities:
371
+ return self.utilities
372
+ try:
373
+ from flock.utility.output_utility_component import (
374
+ OutputUtilityComponent,
375
+ )
376
+ except Exception: # pragma: no cover - optional dependency issues
377
+ return []
378
+
379
+ default_component = OutputUtilityComponent()
380
+ self.utilities = [default_component]
381
+ return self.utilities
382
+
383
+ def _find_matching_artifact(
384
+ self, output_decl: AgentOutput, result: EvalResult
385
+ ) -> Artifact | None:
386
+ """Phase 6: Find artifact from engine result that matches this output declaration.
387
+
388
+ Returns the artifact object (with its ID) so we can preserve it when creating
389
+ the final published artifact. This ensures streaming events use the same ID.
390
+ """
391
+ from flock.registry import type_registry
392
+
393
+ if not result.artifacts:
394
+ return None
395
+
396
+ # Normalize the expected type name to canonical form
397
+ expected_canonical = type_registry.resolve_name(output_decl.spec.type_name)
398
+
399
+ for artifact in result.artifacts:
400
+ # Normalize artifact type name to canonical form for comparison
401
+ try:
402
+ artifact_canonical = type_registry.resolve_name(artifact.type)
403
+ if artifact_canonical == expected_canonical:
404
+ return artifact
405
+ except Exception:
406
+ # If normalization fails, fall back to direct comparison
407
+ if artifact.type == output_decl.spec.type_name:
408
+ return artifact
409
+
410
+ return None
411
+
412
+ def _select_payload(
413
+ self, output_decl: AgentOutput, result: EvalResult
414
+ ) -> dict[str, Any] | None:
415
+ from flock.registry import type_registry
416
+
417
+ if not result.artifacts:
418
+ return None
419
+
420
+ # Normalize the expected type name to canonical form
421
+ expected_canonical = type_registry.resolve_name(output_decl.spec.type_name)
422
+
423
+ for artifact in result.artifacts:
424
+ # Normalize artifact type name to canonical form for comparison
425
+ try:
426
+ artifact_canonical = type_registry.resolve_name(artifact.type)
427
+ if artifact_canonical == expected_canonical:
428
+ return artifact.payload
429
+ except Exception:
430
+ # If normalization fails, fall back to direct comparison
431
+ if artifact.type == output_decl.spec.type_name:
432
+ return artifact.payload
433
+
434
+ # Fallback to state entries keyed by type name
435
+ maybe_data = result.state.get(output_decl.spec.type_name)
436
+ if isinstance(maybe_data, dict):
437
+ return maybe_data
438
+ return None
439
+
440
+
441
+ class AgentBuilder:
442
+ """Fluent builder that also acts as the runtime agent handle."""
443
+
444
+ def __init__(self, orchestrator: Flock, name: str) -> None:
445
+ self._orchestrator = orchestrator
446
+ self._agent = Agent(name, orchestrator=orchestrator)
447
+ self._agent.model = orchestrator.model
448
+ orchestrator.register_agent(self._agent)
449
+
450
+ # Fluent configuration -------------------------------------------------
451
+
452
+ def description(self, text: str) -> AgentBuilder:
453
+ """Set the agent's description for documentation and tracing.
454
+
455
+ Args:
456
+ text: Human-readable description of what the agent does
457
+
458
+ Returns:
459
+ self for method chaining
460
+
461
+ Example:
462
+ >>> agent = (
463
+ ... flock.agent("pizza_chef")
464
+ ... .description("Creates authentic Italian pizza recipes")
465
+ ... .consumes(Idea)
466
+ ... .publishes(Recipe)
467
+ ... )
468
+ """
469
+ self._agent.description = text
470
+ return self
471
+
472
+ def consumes(
473
+ self,
474
+ *types: type[BaseModel],
475
+ where: Callable[[BaseModel], bool] | Sequence[Callable[[BaseModel], bool]] | None = None,
476
+ text: str | None = None,
477
+ min_p: float = 0.0,
478
+ from_agents: Iterable[str] | None = None,
479
+ channels: Iterable[str] | None = None,
480
+ join: dict | JoinSpec | None = None,
481
+ batch: dict | BatchSpec | None = None,
482
+ delivery: str = "exclusive",
483
+ mode: str = "both",
484
+ priority: int = 0,
485
+ ) -> AgentBuilder:
486
+ """Declare which artifact types this agent processes.
487
+
488
+ Sets up subscription rules that determine when the agent executes.
489
+ Supports type-based matching, conditional filters, batching, and joins.
490
+
491
+ Args:
492
+ *types: Artifact types (Pydantic models) to consume
493
+ where: Optional filter predicate(s). Agent only executes if predicate returns True.
494
+ Can be a single callable or sequence of callables (all must pass).
495
+ text: Optional semantic text filter using embedding similarity
496
+ min_p: Minimum probability threshold for text similarity (0.0-1.0)
497
+ from_agents: Only consume artifacts from specific agents
498
+ channels: Only consume artifacts with matching tags
499
+ join: Join specification for coordinating multiple artifact types
500
+ batch: Batch specification for processing multiple artifacts together
501
+ delivery: Delivery mode - "exclusive" (one agent) or "broadcast" (all matching)
502
+ mode: Processing mode - "both", "streaming", or "batch"
503
+ priority: Execution priority (higher = executes first)
504
+
505
+ Returns:
506
+ self for method chaining
507
+
508
+ Examples:
509
+ >>> # Basic type subscription
510
+ >>> agent.consumes(Task)
511
+
512
+ >>> # Multiple types
513
+ >>> agent.consumes(Task, Event, Command)
514
+
515
+ >>> # Conditional consumption (filtering)
516
+ >>> agent.consumes(Review, where=lambda r: r.score >= 8)
517
+
518
+ >>> # Multiple predicates (all must pass)
519
+ >>> agent.consumes(
520
+ ... Order,
521
+ ... where=[
522
+ ... lambda o: o.total > 100,
523
+ ... lambda o: o.status == "pending"
524
+ ... ]
525
+ ... )
526
+
527
+ >>> # Consume from specific agents
528
+ >>> agent.consumes(Report, from_agents=["analyzer", "validator"])
529
+
530
+ >>> # Channel-based routing
531
+ >>> agent.consumes(Alert, channels={"critical", "security"})
532
+
533
+ >>> # Batch processing
534
+ >>> agent.consumes(
535
+ ... Email,
536
+ ... batch={"size": 10, "timeout": 5.0}
537
+ ... )
538
+ """
539
+ predicates: Sequence[Callable[[BaseModel], bool]] | None
540
+ if where is None:
541
+ predicates = None
542
+ elif callable(where):
543
+ predicates = [where]
544
+ else:
545
+ predicates = list(where)
546
+
547
+ join_spec = self._normalize_join(join)
548
+ batch_spec = self._normalize_batch(batch)
549
+ text_predicates = [TextPredicate(text=text, min_p=min_p)] if text else []
550
+ subscription = Subscription(
551
+ agent_name=self._agent.name,
552
+ types=types,
553
+ where=predicates,
554
+ text_predicates=text_predicates,
555
+ from_agents=from_agents,
556
+ channels=channels,
557
+ join=join_spec,
558
+ batch=batch_spec,
559
+ delivery=delivery,
560
+ mode=mode,
561
+ priority=priority,
562
+ )
563
+ self._agent.subscriptions.append(subscription)
564
+ return self
565
+
566
+ def publishes(
567
+ self, *types: type[BaseModel], visibility: Visibility | None = None
568
+ ) -> PublishBuilder:
569
+ """Declare which artifact types this agent produces.
570
+
571
+ Configures the output types and default visibility controls for artifacts
572
+ published by this agent. Can chain with .where() for conditional publishing.
573
+
574
+ Args:
575
+ *types: Artifact types (Pydantic models) to publish
576
+ visibility: Default visibility control for all outputs. Defaults to PublicVisibility.
577
+ Can be overridden per-publish or with .where() chaining.
578
+
579
+ Returns:
580
+ PublishBuilder for conditional publishing configuration
581
+
582
+ Examples:
583
+ >>> # Basic output declaration
584
+ >>> agent.publishes(Report)
585
+
586
+ >>> # Multiple output types
587
+ >>> agent.publishes(Summary, DetailedReport, Alert)
588
+
589
+ >>> # Private outputs (only specific agents can see)
590
+ >>> agent.publishes(
591
+ ... SecretData,
592
+ ... visibility=PrivateVisibility(agents={"admin", "auditor"})
593
+ ... )
594
+
595
+ >>> # Tenant-isolated outputs
596
+ >>> agent.publishes(
597
+ ... Invoice,
598
+ ... visibility=TenantVisibility()
599
+ ... )
600
+
601
+ >>> # Conditional publishing with chaining
602
+ >>> (agent.publishes(Alert)
603
+ ... .where(lambda result: result.severity == "critical"))
604
+
605
+ See Also:
606
+ - PublicVisibility: Default, visible to all agents
607
+ - PrivateVisibility: Allowlist-based access control
608
+ - TenantVisibility: Multi-tenant isolation
609
+ - LabelledVisibility: Role-based access control
610
+ """
611
+ outputs = []
612
+ for model in types:
613
+ spec = ArtifactSpec.from_model(model)
614
+ output = AgentOutput(spec=spec, default_visibility=ensure_visibility(visibility))
615
+ self._agent.outputs.append(output)
616
+ outputs.append(output)
617
+ # T074: Validate configuration after adding outputs
618
+ self._validate_self_trigger_risk()
619
+ return PublishBuilder(self, outputs)
620
+
621
+ def with_utilities(self, *components: AgentComponent) -> AgentBuilder:
622
+ """Add utility components to customize agent lifecycle and behavior.
623
+
624
+ Components are hooks that run at specific points in the agent execution
625
+ lifecycle. Common uses include rate limiting, budgets, metrics, caching,
626
+ and custom preprocessing/postprocessing.
627
+
628
+ Args:
629
+ *components: AgentComponent instances with lifecycle hooks
630
+
631
+ Returns:
632
+ self for method chaining
633
+
634
+ Examples:
635
+ >>> # Rate limiting
636
+ >>> agent.with_utilities(
637
+ ... RateLimiter(max_calls=10, window=60)
638
+ ... )
639
+
640
+ >>> # Budget control
641
+ >>> agent.with_utilities(
642
+ ... TokenBudget(max_tokens=10000)
643
+ ... )
644
+
645
+ >>> # Multiple components (executed in order)
646
+ >>> agent.with_utilities(
647
+ ... RateLimiter(max_calls=5),
648
+ ... MetricsCollector(),
649
+ ... CacheLayer(ttl=3600)
650
+ ... )
651
+
652
+ See Also:
653
+ - AgentComponent: Base class for custom components
654
+ - Lifecycle hooks: on_initialize, on_pre_consume, on_post_publish, etc.
655
+ """
656
+ self._agent.utilities.extend(components)
657
+ return self
658
+
659
+ def with_engines(self, *engines: EngineComponent) -> AgentBuilder:
660
+ """Configure LLM engines for agent evaluation.
661
+
662
+ Engines determine how agents process inputs. Default is DSPy with the
663
+ orchestrator's model. Custom engines enable different LLM backends,
664
+ non-LLM logic, or hybrid approaches.
665
+
666
+ Args:
667
+ *engines: EngineComponent instances for evaluation
668
+
669
+ Returns:
670
+ self for method chaining
671
+
672
+ Examples:
673
+ >>> # DSPy engine with specific model
674
+ >>> agent.with_engines(
675
+ ... DSPyEngine(model="openai/gpt-4o")
676
+ ... )
677
+
678
+ >>> # Custom non-LLM engine
679
+ >>> agent.with_engines(
680
+ ... RuleBasedEngine(rules=my_rules)
681
+ ... )
682
+
683
+ >>> # Hybrid approach (multiple engines)
684
+ >>> agent.with_engines(
685
+ ... DSPyEngine(model="openai/gpt-4o-mini"),
686
+ ... FallbackEngine()
687
+ ... )
688
+
689
+ Note:
690
+ If no engines specified, agent uses DSPy with the orchestrator's default model.
691
+
692
+ See Also:
693
+ - DSPyEngine: Default LLM-based evaluation
694
+ - EngineComponent: Base class for custom engines
695
+ """
696
+ self._agent.engines.extend(engines)
697
+ return self
698
+
699
+ def best_of(self, n: int, score: Callable[[EvalResult], float]) -> AgentBuilder:
700
+ self._agent.best_of_n = max(1, n)
701
+ self._agent.best_of_score = score
702
+ # T074: Validate best_of value
703
+ self._validate_best_of(n)
704
+ return self
705
+
706
+ def max_concurrency(self, n: int) -> AgentBuilder:
707
+ self._agent.set_max_concurrency(n)
708
+ # T074: Validate concurrency value
709
+ self._validate_concurrency(n)
710
+ return self
711
+
712
+ def calls(self, func: Callable[..., Any]) -> AgentBuilder:
713
+ function_registry.register(func)
714
+ self._agent.calls_func = func
715
+ return self
716
+
717
+ def with_tools(self, funcs: Iterable[Callable[..., Any]]) -> AgentBuilder:
718
+ self._agent.tools.update(funcs)
719
+ return self
720
+
721
+ def with_mcps(
722
+ self,
723
+ servers: (
724
+ Iterable[str]
725
+ | dict[str, MCPServerConfig | list[str]] # Support both new and old format
726
+ | list[str | dict[str, MCPServerConfig | list[str]]]
727
+ ),
728
+ ) -> AgentBuilder:
729
+ """Assign MCP servers to this agent with optional server-specific mount points.
730
+
731
+ Architecture Decision: AD001 - Two-Level Architecture
732
+ Agents reference servers registered at orchestrator level.
733
+
734
+ Args:
735
+ servers: One of:
736
+ - List of server names (strings) - no specific mounts
737
+ - Dict mapping server names to MCPServerConfig or list[str] (backward compatible)
738
+ - Mixed list of strings and dicts for flexibility
739
+
740
+ Returns:
741
+ self for method chaining
742
+
743
+ Raises:
744
+ ValueError: If any server name is not registered with orchestrator
745
+
746
+ Examples:
747
+ >>> # Simple: no mount restrictions
748
+ >>> agent.with_mcps(["filesystem", "github"])
749
+
750
+ >>> # New format: Server-specific config with roots and tool whitelist
751
+ >>> agent.with_mcps({
752
+ ... "filesystem": {"roots": ["/workspace/dir/data"], "tool_whitelist": ["read_file"]},
753
+ ... "github": {} # No restrictions for github
754
+ ... })
755
+
756
+ >>> # Old format: Direct list (backward compatible)
757
+ >>> agent.with_mcps({
758
+ ... "filesystem": ["/workspace/dir/data"], # Old format still works
759
+ ... })
760
+
761
+ >>> # Mixed: backward compatible
762
+ >>> agent.with_mcps([
763
+ ... "github", # No mounts
764
+ ... {"filesystem": {"roots": ["mount1", "mount2"] } }
765
+ ```
766
+ ... ])
767
+ """
768
+ # Parse input into server_names and mounts
769
+ server_set: set[str] = set()
770
+ server_mounts: dict[str, list[str]] = {}
771
+ whitelist = None
772
+
773
+ if isinstance(servers, dict):
774
+ # Dict format: supports both old and new formats
775
+ # Old: {"server": ["/path1", "/path2"]}
776
+ # New: {"server": {"roots": ["/path1"], "tool_whitelist": ["tool1"]}}
777
+ for server_name, server_config in servers.items():
778
+ server_set.add(server_name)
779
+
780
+ # Check if it's the old format (direct list) or new format (MCPServerConfig dict)
781
+ if isinstance(server_config, list):
782
+ # Old format: direct list of paths (backward compatibility)
783
+ if len(server_config) > 0:
784
+ server_mounts[server_name] = list(server_config)
785
+ elif isinstance(server_config, dict):
786
+ # New format: MCPServerConfig with optional roots and tool_whitelist
787
+ mounts = server_config.get("roots", None)
788
+ if mounts is not None and isinstance(mounts, list) and len(mounts) > 0:
789
+ server_mounts[server_name] = list(mounts)
790
+
791
+ config_whitelist = server_config.get("tool_whitelist", None)
792
+ if (
793
+ config_whitelist is not None
794
+ and isinstance(config_whitelist, list)
795
+ and len(config_whitelist) > 0
796
+ ):
797
+ whitelist = config_whitelist
798
+ elif isinstance(servers, list):
799
+ # List format: can be mixed
800
+ for item in servers:
801
+ if isinstance(item, str):
802
+ # Simple server name
803
+ server_set.add(item)
804
+ elif isinstance(item, dict):
805
+ # Dict with mounts
806
+ for server_name, mounts in item.items():
807
+ server_set.add(server_name)
808
+ if mounts:
809
+ server_mounts[server_name] = list(mounts)
810
+ else:
811
+ raise TypeError(
812
+ f"Invalid server specification: {item}. "
813
+ f"Expected string or dict, got {type(item).__name__}"
814
+ )
815
+ else:
816
+ # Assume it's an iterable of strings (backward compatibility)
817
+ server_set = set(servers)
818
+
819
+ # Validate all servers exist in orchestrator
820
+ registered_servers = set(self._orchestrator._mcp_configs.keys())
821
+ invalid_servers = server_set - registered_servers
822
+
823
+ if invalid_servers:
824
+ available = list(registered_servers) if registered_servers else ["none"]
825
+ raise ValueError(
826
+ f"MCP servers not registered: {invalid_servers}. "
827
+ f"Available servers: {available}. "
828
+ f"Register servers using orchestrator.add_mcp() first."
829
+ )
830
+
831
+ # Store in agent
832
+ self._agent.mcp_server_names = server_set
833
+ self._agent.mcp_server_mounts = server_mounts
834
+ self._agent.tool_whitelist = whitelist
835
+
836
+ return self
837
+
838
+ def mount(self, paths: str | list[str], *, validate: bool = False) -> AgentBuilder:
839
+ """Mount agent in specific directories for MCP root access.
840
+
841
+ .. deprecated:: 0.2.0
842
+ Use `.with_mcps({"server_name": ["/path"]})` instead for server-specific mounts.
843
+ This method applies mounts globally to all MCP servers.
844
+
845
+ This sets the filesystem roots that MCP servers will operate under for this agent.
846
+ Paths are cumulative across multiple calls.
847
+
848
+ Args:
849
+ paths: Single path or list of paths to mount
850
+ validate: If True, validate that paths exist (default: False)
851
+
852
+ Returns:
853
+ AgentBuilder for method chaining
854
+
855
+ Example:
856
+ >>> # Old way (deprecated)
857
+ >>> agent.with_mcps(["filesystem"]).mount("/workspace/src")
858
+ >>>
859
+ >>> # New way (recommended)
860
+ >>> agent.with_mcps({"filesystem": ["/workspace/src"]})
861
+ """
862
+ import warnings
863
+
864
+ warnings.warn(
865
+ "Agent.mount() is deprecated. Use .with_mcps({'server': ['/path']}) "
866
+ "for server-specific mounts instead.",
867
+ DeprecationWarning,
868
+ stacklevel=2,
869
+ )
870
+
871
+ if isinstance(paths, str):
872
+ paths = [paths]
873
+ if validate:
874
+ from pathlib import Path
875
+
876
+ for path in paths:
877
+ if not Path(path).exists():
878
+ raise ValueError(f"Mount path does not exist: {path}")
879
+
880
+ # Add to agent's mount points (cumulative) - for backward compatibility
881
+ self._agent.mcp_mount_points.extend(paths)
882
+
883
+ # Also add to all configured servers for backward compatibility
884
+ for server_name in self._agent.mcp_server_names:
885
+ if server_name not in self._agent.mcp_server_mounts:
886
+ self._agent.mcp_server_mounts[server_name] = []
887
+ self._agent.mcp_server_mounts[server_name].extend(paths)
888
+
889
+ return self
890
+
891
+ def labels(self, *labels: str) -> AgentBuilder:
892
+ self._agent.labels.update(labels)
893
+ return self
894
+
895
+ def tenant(self, tenant_id: str) -> AgentBuilder:
896
+ self._agent.tenant_id = tenant_id
897
+ return self
898
+
899
+ def prevent_self_trigger(self, enabled: bool = True) -> AgentBuilder:
900
+ """Prevent agent from being triggered by its own outputs.
901
+
902
+ When enabled (default), the orchestrator will skip scheduling this agent
903
+ for artifacts it produced itself. This prevents infinite feedback loops
904
+ when an agent consumes and publishes the same type.
905
+
906
+ Args:
907
+ enabled: True to prevent self-triggering (safe default),
908
+ False to allow feedback loops (advanced use case)
909
+
910
+ Returns:
911
+ AgentBuilder for method chaining
912
+
913
+ Example:
914
+ # Safe by default (recommended)
915
+ agent.consumes(Document).publishes(Document)
916
+ # Won't trigger on own outputs ✅
917
+
918
+ # Explicit feedback loop (use with caution!)
919
+ agent.consumes(Data, where=lambda d: d.depth < 10)
920
+ .publishes(Data)
921
+ .prevent_self_trigger(False) # Acknowledge risk
922
+ """
923
+ self._agent.prevent_self_trigger = enabled
924
+ return self
925
+
926
+ # Runtime helpers ------------------------------------------------------
927
+
928
+ def run(self, *inputs: BaseModel) -> RunHandle:
929
+ return RunHandle(self._agent, list(inputs))
930
+
931
+ def then(self, other: AgentBuilder) -> Pipeline:
932
+ return Pipeline([self, other])
933
+
934
+ # Validation -----------------------------------------------------------
935
+
936
+ def _validate_self_trigger_risk(self) -> None:
937
+ """T074: Warn if agent consumes and publishes same type (feedback loop risk)."""
938
+ from flock.logging.logging import get_logger
939
+
940
+ logger = get_logger(__name__)
941
+
942
+ # Get types agent consumes
943
+ consuming_types = set()
944
+ for sub in self._agent.subscriptions:
945
+ consuming_types.update(sub.type_names)
946
+
947
+ # Get types agent publishes
948
+ publishing_types = {output.spec.type_name for output in self._agent.outputs}
949
+
950
+ # Check for overlap
951
+ overlap = consuming_types.intersection(publishing_types)
952
+ if overlap and self._agent.prevent_self_trigger:
953
+ logger.warning(
954
+ f"Agent '{self._agent.name}' consumes and publishes {overlap}. "
955
+ f"Feedback loop risk detected. Agent has prevent_self_trigger=True (safe), "
956
+ f"but consider adding filtering: .consumes(Type, where=lambda x: ...) "
957
+ f"or use .prevent_self_trigger(False) for intentional feedback."
958
+ )
959
+
960
+ def _validate_best_of(self, n: int) -> None:
961
+ """T074: Warn if best_of value is excessively high."""
962
+ from flock.logging.logging import get_logger
963
+
964
+ logger = get_logger(__name__)
965
+
966
+ if n > 100:
967
+ logger.warning(
968
+ f"Agent '{self._agent.name}' has best_of({n}) which is very high. "
969
+ f"Typical values are 3-10. High values increase cost and latency. "
970
+ f"Consider reducing unless you have specific requirements."
971
+ )
972
+
973
+ def _validate_concurrency(self, n: int) -> None:
974
+ """T074: Warn if max_concurrency is excessively high."""
975
+ from flock.logging.logging import get_logger
976
+
977
+ logger = get_logger(__name__)
978
+
979
+ if n > 1000:
980
+ logger.warning(
981
+ f"Agent '{self._agent.name}' has max_concurrency({n}) which is very high. "
982
+ f"Typical values are 1-50. Excessive concurrency may cause resource issues. "
983
+ f"Consider reducing unless you have specific infrastructure."
984
+ )
985
+
986
+ # Utility --------------------------------------------------------------
987
+
988
+ def _normalize_join(self, value: dict | JoinSpec | None) -> JoinSpec | None:
989
+ if value is None or isinstance(value, JoinSpec):
990
+ return value
991
+ return JoinSpec(
992
+ kind=value.get("kind", "all_of"),
993
+ window=float(value.get("window", 0.0)),
994
+ by=value.get("by"),
995
+ )
996
+
997
+ def _normalize_batch(self, value: dict | BatchSpec | None) -> BatchSpec | None:
998
+ if value is None or isinstance(value, BatchSpec):
999
+ return value
1000
+ return BatchSpec(
1001
+ size=int(value.get("size", 1)),
1002
+ within=float(value.get("within", 0.0)),
1003
+ by=value.get("by"),
1004
+ )
1005
+
1006
+ # Properties -----------------------------------------------------------
1007
+
1008
+ @property
1009
+ def name(self) -> str:
1010
+ return self._agent.name
1011
+
1012
+ @property
1013
+ def agent(self) -> Agent:
1014
+ return self._agent
1015
+
1016
+
1017
+ class PublishBuilder:
1018
+ """Helper returned by `.publishes(...)` to support `.only_for` sugar."""
1019
+
1020
+ def __init__(self, parent: AgentBuilder, outputs: Sequence[AgentOutput]) -> None:
1021
+ self._parent = parent
1022
+ self._outputs = list(outputs)
1023
+
1024
+ def only_for(self, *agent_names: str) -> AgentBuilder:
1025
+ visibility = only_for(*agent_names)
1026
+ for output in self._outputs:
1027
+ output.default_visibility = visibility
1028
+ return self._parent
1029
+
1030
+ def visibility(self, value: Visibility) -> AgentBuilder:
1031
+ for output in self._outputs:
1032
+ output.default_visibility = value
1033
+ return self._parent
1034
+
1035
+ def __getattr__(self, item):
1036
+ return getattr(self._parent, item)
1037
+
1038
+
1039
+ class RunHandle:
1040
+ """Represents a chained run starting from a given agent."""
1041
+
1042
+ def __init__(self, agent: Agent, inputs: list[BaseModel]) -> None:
1043
+ self.agent = agent
1044
+ self.inputs = inputs
1045
+ self._chain: list[Agent] = [agent]
1046
+
1047
+ def then(self, builder: AgentBuilder) -> RunHandle:
1048
+ self._chain.append(builder.agent)
1049
+ return self
1050
+
1051
+ async def execute(self) -> list[Artifact]:
1052
+ orchestrator = self.agent._orchestrator
1053
+ artifacts = await orchestrator.direct_invoke(self.agent, self.inputs)
1054
+ for agent in self._chain[1:]:
1055
+ artifacts = await orchestrator.direct_invoke(agent, artifacts)
1056
+ return artifacts
1057
+
1058
+
1059
+ class Pipeline:
1060
+ def __init__(self, builders: Sequence[AgentBuilder]) -> None:
1061
+ self.builders = list(builders)
1062
+
1063
+ def then(self, builder: AgentBuilder) -> Pipeline:
1064
+ self.builders.append(builder)
1065
+ return self
1066
+
1067
+ async def execute(self) -> list[Artifact]:
1068
+ orchestrator = self.builders[0].agent._orchestrator
1069
+ artifacts: list[Artifact] = []
1070
+ for builder in self.builders:
1071
+ inputs = artifacts if artifacts else []
1072
+ artifacts = await orchestrator.direct_invoke(builder.agent, inputs)
1073
+ return artifacts
1074
+
1075
+
1076
+ __all__ = [
1077
+ "Agent",
1078
+ "AgentBuilder",
1079
+ ]