flock-core 0.5.0b28__py3-none-any.whl → 0.5.56b0__py3-none-any.whl

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

Potentially problematic release.


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

Files changed (359) hide show
  1. flock/__init__.py +12 -217
  2. flock/agent.py +678 -0
  3. flock/api/themes.py +71 -0
  4. flock/artifacts.py +79 -0
  5. flock/cli.py +75 -0
  6. flock/components.py +173 -0
  7. flock/dashboard/__init__.py +28 -0
  8. flock/dashboard/collector.py +283 -0
  9. flock/dashboard/events.py +182 -0
  10. flock/dashboard/launcher.py +230 -0
  11. flock/dashboard/service.py +537 -0
  12. flock/dashboard/websocket.py +235 -0
  13. flock/engines/__init__.py +6 -0
  14. flock/engines/dspy_engine.py +856 -0
  15. flock/examples.py +128 -0
  16. flock/{core/util → helper}/cli_helper.py +4 -3
  17. flock/{core/logging → logging}/__init__.py +2 -3
  18. flock/{core/logging → logging}/formatters/enum_builder.py +3 -4
  19. flock/{core/logging → logging}/formatters/theme_builder.py +19 -44
  20. flock/{core/logging → logging}/formatters/themed_formatter.py +69 -115
  21. flock/{core/logging → logging}/logging.py +77 -61
  22. flock/{core/logging → logging}/telemetry.py +20 -26
  23. flock/{core/logging → logging}/telemetry_exporter/base_exporter.py +2 -2
  24. flock/{core/logging → logging}/telemetry_exporter/file_exporter.py +6 -9
  25. flock/{core/logging → logging}/telemetry_exporter/sqlite_exporter.py +2 -3
  26. flock/{core/logging → logging}/trace_and_logged.py +20 -24
  27. flock/mcp/__init__.py +91 -0
  28. flock/{core/mcp/mcp_client.py → mcp/client.py} +103 -154
  29. flock/{core/mcp/mcp_config.py → mcp/config.py} +62 -117
  30. flock/mcp/manager.py +255 -0
  31. flock/mcp/servers/sse/__init__.py +1 -1
  32. flock/mcp/servers/sse/flock_sse_server.py +11 -53
  33. flock/mcp/servers/stdio/__init__.py +1 -1
  34. flock/mcp/servers/stdio/flock_stdio_server.py +8 -48
  35. flock/mcp/servers/streamable_http/flock_streamable_http_server.py +17 -62
  36. flock/mcp/servers/websockets/flock_websocket_server.py +7 -40
  37. flock/{core/mcp/flock_mcp_tool.py → mcp/tool.py} +16 -26
  38. flock/mcp/types/__init__.py +42 -0
  39. flock/{core/mcp → mcp}/types/callbacks.py +9 -15
  40. flock/{core/mcp → mcp}/types/factories.py +7 -6
  41. flock/{core/mcp → mcp}/types/handlers.py +13 -18
  42. flock/{core/mcp → mcp}/types/types.py +70 -74
  43. flock/{core/mcp → mcp}/util/helpers.py +1 -1
  44. flock/orchestrator.py +645 -0
  45. flock/registry.py +148 -0
  46. flock/runtime.py +262 -0
  47. flock/service.py +140 -0
  48. flock/store.py +69 -0
  49. flock/subscription.py +111 -0
  50. flock/themes/andromeda.toml +1 -1
  51. flock/themes/apple-system-colors.toml +1 -1
  52. flock/themes/arcoiris.toml +1 -1
  53. flock/themes/atomonelight.toml +1 -1
  54. flock/themes/ayu copy.toml +1 -1
  55. flock/themes/ayu-light.toml +1 -1
  56. flock/themes/belafonte-day.toml +1 -1
  57. flock/themes/belafonte-night.toml +1 -1
  58. flock/themes/blulocodark.toml +1 -1
  59. flock/themes/breeze.toml +1 -1
  60. flock/themes/broadcast.toml +1 -1
  61. flock/themes/brogrammer.toml +1 -1
  62. flock/themes/builtin-dark.toml +1 -1
  63. flock/themes/builtin-pastel-dark.toml +1 -1
  64. flock/themes/catppuccin-latte.toml +1 -1
  65. flock/themes/catppuccin-macchiato.toml +1 -1
  66. flock/themes/catppuccin-mocha.toml +1 -1
  67. flock/themes/cga.toml +1 -1
  68. flock/themes/chalk.toml +1 -1
  69. flock/themes/ciapre.toml +1 -1
  70. flock/themes/coffee-theme.toml +1 -1
  71. flock/themes/cyberpunkscarletprotocol.toml +1 -1
  72. flock/themes/dark+.toml +1 -1
  73. flock/themes/darkermatrix.toml +1 -1
  74. flock/themes/darkside.toml +1 -1
  75. flock/themes/desert.toml +1 -1
  76. flock/themes/django.toml +1 -1
  77. flock/themes/djangosmooth.toml +1 -1
  78. flock/themes/doomone.toml +1 -1
  79. flock/themes/dotgov.toml +1 -1
  80. flock/themes/dracula+.toml +1 -1
  81. flock/themes/duckbones.toml +1 -1
  82. flock/themes/encom.toml +1 -1
  83. flock/themes/espresso.toml +1 -1
  84. flock/themes/everblush.toml +1 -1
  85. flock/themes/fairyfloss.toml +1 -1
  86. flock/themes/fideloper.toml +1 -1
  87. flock/themes/fishtank.toml +1 -1
  88. flock/themes/flexoki-light.toml +1 -1
  89. flock/themes/floraverse.toml +1 -1
  90. flock/themes/framer.toml +1 -1
  91. flock/themes/galizur.toml +1 -1
  92. flock/themes/github.toml +1 -1
  93. flock/themes/grass.toml +1 -1
  94. flock/themes/grey-green.toml +1 -1
  95. flock/themes/gruvboxlight.toml +1 -1
  96. flock/themes/guezwhoz.toml +1 -1
  97. flock/themes/harper.toml +1 -1
  98. flock/themes/hax0r-blue.toml +1 -1
  99. flock/themes/hopscotch.256.toml +1 -1
  100. flock/themes/ic-green-ppl.toml +1 -1
  101. flock/themes/iceberg-dark.toml +1 -1
  102. flock/themes/japanesque.toml +1 -1
  103. flock/themes/jubi.toml +1 -1
  104. flock/themes/kibble.toml +1 -1
  105. flock/themes/kolorit.toml +1 -1
  106. flock/themes/kurokula.toml +1 -1
  107. flock/themes/materialdesigncolors.toml +1 -1
  108. flock/themes/matrix.toml +1 -1
  109. flock/themes/mellifluous.toml +1 -1
  110. flock/themes/midnight-in-mojave.toml +1 -1
  111. flock/themes/monokai-remastered.toml +1 -1
  112. flock/themes/monokai-soda.toml +1 -1
  113. flock/themes/neon.toml +1 -1
  114. flock/themes/neopolitan.toml +1 -1
  115. flock/themes/nord-light.toml +1 -1
  116. flock/themes/ocean.toml +1 -1
  117. flock/themes/onehalfdark.toml +1 -1
  118. flock/themes/onehalflight.toml +1 -1
  119. flock/themes/palenighthc.toml +1 -1
  120. flock/themes/paulmillr.toml +1 -1
  121. flock/themes/pencildark.toml +1 -1
  122. flock/themes/pnevma.toml +1 -1
  123. flock/themes/purple-rain.toml +1 -1
  124. flock/themes/purplepeter.toml +1 -1
  125. flock/themes/raycast-dark.toml +1 -1
  126. flock/themes/red-sands.toml +1 -1
  127. flock/themes/relaxed.toml +1 -1
  128. flock/themes/retro.toml +1 -1
  129. flock/themes/rose-pine.toml +1 -1
  130. flock/themes/royal.toml +1 -1
  131. flock/themes/ryuuko.toml +1 -1
  132. flock/themes/sakura.toml +1 -1
  133. flock/themes/scarlet-protocol.toml +1 -1
  134. flock/themes/seoulbones-dark.toml +1 -1
  135. flock/themes/shades-of-purple.toml +1 -1
  136. flock/themes/smyck.toml +1 -1
  137. flock/themes/softserver.toml +1 -1
  138. flock/themes/solarized-darcula.toml +1 -1
  139. flock/themes/square.toml +1 -1
  140. flock/themes/sugarplum.toml +1 -1
  141. flock/themes/thayer-bright.toml +1 -1
  142. flock/themes/tokyonight.toml +1 -1
  143. flock/themes/tomorrow.toml +1 -1
  144. flock/themes/ubuntu.toml +1 -1
  145. flock/themes/ultradark.toml +1 -1
  146. flock/themes/ultraviolent.toml +1 -1
  147. flock/themes/unikitty.toml +1 -1
  148. flock/themes/urple.toml +1 -1
  149. flock/themes/vesper.toml +1 -1
  150. flock/themes/vimbones.toml +1 -1
  151. flock/themes/wildcherry.toml +1 -1
  152. flock/themes/wilmersdorf.toml +1 -1
  153. flock/themes/wryan.toml +1 -1
  154. flock/themes/xcodedarkhc.toml +1 -1
  155. flock/themes/xcodelight.toml +1 -1
  156. flock/themes/zenbones-light.toml +1 -1
  157. flock/themes/zenwritten-dark.toml +1 -1
  158. flock/utilities.py +301 -0
  159. flock/{components/utility → utility}/output_utility_component.py +68 -53
  160. flock/visibility.py +107 -0
  161. flock_core-0.5.56b0.dist-info/METADATA +747 -0
  162. flock_core-0.5.56b0.dist-info/RECORD +398 -0
  163. flock_core-0.5.56b0.dist-info/entry_points.txt +2 -0
  164. {flock_core-0.5.0b28.dist-info → flock_core-0.5.56b0.dist-info}/licenses/LICENSE +1 -1
  165. flock/adapter/__init__.py +0 -14
  166. flock/adapter/azure_adapter.py +0 -68
  167. flock/adapter/chroma_adapter.py +0 -73
  168. flock/adapter/faiss_adapter.py +0 -97
  169. flock/adapter/pinecone_adapter.py +0 -51
  170. flock/adapter/vector_base.py +0 -47
  171. flock/cli/assets/release_notes.md +0 -140
  172. flock/cli/config.py +0 -8
  173. flock/cli/constants.py +0 -36
  174. flock/cli/create_agent.py +0 -1
  175. flock/cli/create_flock.py +0 -280
  176. flock/cli/execute_flock.py +0 -620
  177. flock/cli/load_agent.py +0 -1
  178. flock/cli/load_examples.py +0 -1
  179. flock/cli/load_flock.py +0 -192
  180. flock/cli/load_release_notes.py +0 -20
  181. flock/cli/loaded_flock_cli.py +0 -254
  182. flock/cli/manage_agents.py +0 -459
  183. flock/cli/registry_management.py +0 -889
  184. flock/cli/runner.py +0 -41
  185. flock/cli/settings.py +0 -857
  186. flock/cli/utils.py +0 -135
  187. flock/cli/view_results.py +0 -29
  188. flock/cli/yaml_editor.py +0 -396
  189. flock/components/__init__.py +0 -30
  190. flock/components/evaluation/__init__.py +0 -9
  191. flock/components/evaluation/declarative_evaluation_component.py +0 -606
  192. flock/components/routing/__init__.py +0 -15
  193. flock/components/routing/conditional_routing_component.py +0 -494
  194. flock/components/routing/default_routing_component.py +0 -103
  195. flock/components/routing/llm_routing_component.py +0 -206
  196. flock/components/utility/__init__.py +0 -22
  197. flock/components/utility/example_utility_component.py +0 -250
  198. flock/components/utility/feedback_utility_component.py +0 -206
  199. flock/components/utility/memory_utility_component.py +0 -550
  200. flock/components/utility/metrics_utility_component.py +0 -700
  201. flock/config.py +0 -61
  202. flock/core/__init__.py +0 -110
  203. flock/core/agent/__init__.py +0 -16
  204. flock/core/agent/default_agent.py +0 -216
  205. flock/core/agent/flock_agent_components.py +0 -104
  206. flock/core/agent/flock_agent_execution.py +0 -101
  207. flock/core/agent/flock_agent_integration.py +0 -260
  208. flock/core/agent/flock_agent_lifecycle.py +0 -186
  209. flock/core/agent/flock_agent_serialization.py +0 -381
  210. flock/core/api/__init__.py +0 -10
  211. flock/core/api/custom_endpoint.py +0 -45
  212. flock/core/api/endpoints.py +0 -254
  213. flock/core/api/main.py +0 -162
  214. flock/core/api/models.py +0 -97
  215. flock/core/api/run_store.py +0 -224
  216. flock/core/api/runner.py +0 -44
  217. flock/core/api/service.py +0 -214
  218. flock/core/component/__init__.py +0 -15
  219. flock/core/component/agent_component_base.py +0 -309
  220. flock/core/component/evaluation_component.py +0 -62
  221. flock/core/component/routing_component.py +0 -74
  222. flock/core/component/utility_component.py +0 -69
  223. flock/core/config/flock_agent_config.py +0 -58
  224. flock/core/config/scheduled_agent_config.py +0 -40
  225. flock/core/context/context.py +0 -213
  226. flock/core/context/context_manager.py +0 -37
  227. flock/core/context/context_vars.py +0 -10
  228. flock/core/evaluation/utils.py +0 -396
  229. flock/core/execution/batch_executor.py +0 -369
  230. flock/core/execution/evaluation_executor.py +0 -438
  231. flock/core/execution/local_executor.py +0 -31
  232. flock/core/execution/opik_executor.py +0 -103
  233. flock/core/execution/temporal_executor.py +0 -164
  234. flock/core/flock.py +0 -634
  235. flock/core/flock_agent.py +0 -336
  236. flock/core/flock_factory.py +0 -613
  237. flock/core/flock_scheduler.py +0 -166
  238. flock/core/flock_server_manager.py +0 -136
  239. flock/core/interpreter/python_interpreter.py +0 -689
  240. flock/core/mcp/__init__.py +0 -1
  241. flock/core/mcp/flock_mcp_server.py +0 -680
  242. flock/core/mcp/mcp_client_manager.py +0 -201
  243. flock/core/mcp/types/__init__.py +0 -1
  244. flock/core/mixin/dspy_integration.py +0 -403
  245. flock/core/mixin/prompt_parser.py +0 -125
  246. flock/core/orchestration/__init__.py +0 -15
  247. flock/core/orchestration/flock_batch_processor.py +0 -94
  248. flock/core/orchestration/flock_evaluator.py +0 -113
  249. flock/core/orchestration/flock_execution.py +0 -295
  250. flock/core/orchestration/flock_initialization.py +0 -149
  251. flock/core/orchestration/flock_server_manager.py +0 -67
  252. flock/core/orchestration/flock_web_server.py +0 -117
  253. flock/core/registry/__init__.py +0 -45
  254. flock/core/registry/agent_registry.py +0 -69
  255. flock/core/registry/callable_registry.py +0 -139
  256. flock/core/registry/component_discovery.py +0 -142
  257. flock/core/registry/component_registry.py +0 -64
  258. flock/core/registry/config_mapping.py +0 -64
  259. flock/core/registry/decorators.py +0 -137
  260. flock/core/registry/registry_hub.py +0 -205
  261. flock/core/registry/server_registry.py +0 -57
  262. flock/core/registry/type_registry.py +0 -86
  263. flock/core/serialization/__init__.py +0 -13
  264. flock/core/serialization/callable_registry.py +0 -52
  265. flock/core/serialization/flock_serializer.py +0 -832
  266. flock/core/serialization/json_encoder.py +0 -41
  267. flock/core/serialization/secure_serializer.py +0 -175
  268. flock/core/serialization/serializable.py +0 -342
  269. flock/core/serialization/serialization_utils.py +0 -412
  270. flock/core/util/file_path_utils.py +0 -223
  271. flock/core/util/hydrator.py +0 -309
  272. flock/core/util/input_resolver.py +0 -164
  273. flock/core/util/loader.py +0 -59
  274. flock/core/util/splitter.py +0 -219
  275. flock/di.py +0 -27
  276. flock/platform/docker_tools.py +0 -49
  277. flock/platform/jaeger_install.py +0 -86
  278. flock/webapp/__init__.py +0 -1
  279. flock/webapp/app/__init__.py +0 -0
  280. flock/webapp/app/api/__init__.py +0 -0
  281. flock/webapp/app/api/agent_management.py +0 -241
  282. flock/webapp/app/api/execution.py +0 -709
  283. flock/webapp/app/api/flock_management.py +0 -129
  284. flock/webapp/app/api/registry_viewer.py +0 -30
  285. flock/webapp/app/chat.py +0 -665
  286. flock/webapp/app/config.py +0 -104
  287. flock/webapp/app/dependencies.py +0 -117
  288. flock/webapp/app/main.py +0 -1070
  289. flock/webapp/app/middleware.py +0 -113
  290. flock/webapp/app/models_ui.py +0 -7
  291. flock/webapp/app/services/__init__.py +0 -0
  292. flock/webapp/app/services/feedback_file_service.py +0 -363
  293. flock/webapp/app/services/flock_service.py +0 -337
  294. flock/webapp/app/services/sharing_models.py +0 -81
  295. flock/webapp/app/services/sharing_store.py +0 -762
  296. flock/webapp/app/templates/theme_mapper.html +0 -326
  297. flock/webapp/app/theme_mapper.py +0 -812
  298. flock/webapp/app/utils.py +0 -85
  299. flock/webapp/run.py +0 -215
  300. flock/webapp/static/css/chat.css +0 -301
  301. flock/webapp/static/css/components.css +0 -167
  302. flock/webapp/static/css/header.css +0 -39
  303. flock/webapp/static/css/layout.css +0 -46
  304. flock/webapp/static/css/sidebar.css +0 -127
  305. flock/webapp/static/css/two-pane.css +0 -48
  306. flock/webapp/templates/base.html +0 -200
  307. flock/webapp/templates/chat.html +0 -152
  308. flock/webapp/templates/chat_settings.html +0 -19
  309. flock/webapp/templates/flock_editor.html +0 -16
  310. flock/webapp/templates/index.html +0 -12
  311. flock/webapp/templates/partials/_agent_detail_form.html +0 -93
  312. flock/webapp/templates/partials/_agent_list.html +0 -18
  313. flock/webapp/templates/partials/_agent_manager_view.html +0 -51
  314. flock/webapp/templates/partials/_agent_tools_checklist.html +0 -14
  315. flock/webapp/templates/partials/_chat_container.html +0 -15
  316. flock/webapp/templates/partials/_chat_messages.html +0 -57
  317. flock/webapp/templates/partials/_chat_settings_form.html +0 -85
  318. flock/webapp/templates/partials/_create_flock_form.html +0 -50
  319. flock/webapp/templates/partials/_dashboard_flock_detail.html +0 -17
  320. flock/webapp/templates/partials/_dashboard_flock_file_list.html +0 -16
  321. flock/webapp/templates/partials/_dashboard_flock_properties_preview.html +0 -28
  322. flock/webapp/templates/partials/_dashboard_upload_flock_form.html +0 -16
  323. flock/webapp/templates/partials/_dynamic_input_form_content.html +0 -22
  324. flock/webapp/templates/partials/_env_vars_table.html +0 -23
  325. flock/webapp/templates/partials/_execution_form.html +0 -118
  326. flock/webapp/templates/partials/_execution_view_container.html +0 -28
  327. flock/webapp/templates/partials/_flock_file_list.html +0 -23
  328. flock/webapp/templates/partials/_flock_properties_form.html +0 -52
  329. flock/webapp/templates/partials/_flock_upload_form.html +0 -16
  330. flock/webapp/templates/partials/_header_flock_status.html +0 -5
  331. flock/webapp/templates/partials/_load_manager_view.html +0 -49
  332. flock/webapp/templates/partials/_registry_table.html +0 -25
  333. flock/webapp/templates/partials/_registry_viewer_content.html +0 -70
  334. flock/webapp/templates/partials/_results_display.html +0 -78
  335. flock/webapp/templates/partials/_settings_env_content.html +0 -9
  336. flock/webapp/templates/partials/_settings_theme_content.html +0 -14
  337. flock/webapp/templates/partials/_settings_view.html +0 -36
  338. flock/webapp/templates/partials/_share_chat_link_snippet.html +0 -11
  339. flock/webapp/templates/partials/_share_link_snippet.html +0 -35
  340. flock/webapp/templates/partials/_sidebar.html +0 -74
  341. flock/webapp/templates/partials/_streaming_results_container.html +0 -195
  342. flock/webapp/templates/partials/_structured_data_view.html +0 -40
  343. flock/webapp/templates/partials/_theme_preview.html +0 -36
  344. flock/webapp/templates/registry_viewer.html +0 -84
  345. flock/webapp/templates/shared_run_page.html +0 -140
  346. flock/workflow/__init__.py +0 -0
  347. flock/workflow/activities.py +0 -196
  348. flock/workflow/agent_activities.py +0 -24
  349. flock/workflow/agent_execution_activity.py +0 -202
  350. flock/workflow/flock_workflow.py +0 -214
  351. flock/workflow/temporal_config.py +0 -96
  352. flock/workflow/temporal_setup.py +0 -68
  353. flock_core-0.5.0b28.dist-info/METADATA +0 -274
  354. flock_core-0.5.0b28.dist-info/RECORD +0 -561
  355. flock_core-0.5.0b28.dist-info/entry_points.txt +0 -2
  356. /flock/{core/logging → logging}/formatters/themes.py +0 -0
  357. /flock/{core/logging → logging}/span_middleware/baggage_span_processor.py +0 -0
  358. /flock/{core/mcp → mcp}/util/__init__.py +0 -0
  359. {flock_core-0.5.0b28.dist-info → flock_core-0.5.56b0.dist-info}/WHEEL +0 -0
flock/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
+ ]