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/utilities.py ADDED
@@ -0,0 +1,301 @@
1
+ from __future__ import annotations
2
+
3
+
4
+ """Built-in utility components for metrics and logging."""
5
+
6
+ import asyncio
7
+ import contextlib
8
+ import json
9
+ import sys
10
+ import time
11
+ from collections.abc import Iterable, Mapping, MutableMapping, Sequence
12
+ from typing import TYPE_CHECKING, Any
13
+
14
+ from rich.console import Console
15
+ from rich.json import JSON
16
+ from rich.live import Live
17
+ from rich.panel import Panel
18
+ from rich.pretty import Pretty
19
+ from rich.table import Table
20
+ from rich.text import Text
21
+
22
+ from flock.components import AgentComponent
23
+
24
+
25
+ if TYPE_CHECKING:
26
+ from flock.runtime import Context, EvalInputs, EvalResult
27
+
28
+
29
+ class MetricsUtility(AgentComponent):
30
+ """Records simple runtime metrics per agent execution."""
31
+
32
+ name: str | None = "metrics"
33
+
34
+ async def on_pre_evaluate(self, agent, ctx: Context, inputs: EvalInputs) -> EvalInputs:
35
+ ctx.state.setdefault("metrics", {})[f"{agent.name}:start"] = time.perf_counter()
36
+ return inputs
37
+
38
+ async def on_post_evaluate(
39
+ self, agent, ctx: Context, inputs: EvalInputs, result: EvalResult
40
+ ) -> EvalResult:
41
+ metrics = ctx.state.setdefault("metrics", {})
42
+ start = metrics.get(f"{agent.name}:start")
43
+ if start:
44
+ metrics[f"{agent.name}:duration_ms"] = (time.perf_counter() - start) * 1000
45
+ result.metrics.update({k: v for k, v in metrics.items() if k.endswith("duration_ms")})
46
+ return result
47
+
48
+
49
+ class LoggingUtility(AgentComponent):
50
+ """Rich-powered logging with optional streaming previews."""
51
+
52
+ name: str | None = "logs"
53
+
54
+ def __init__(
55
+ self,
56
+ console: Console | None = None,
57
+ *,
58
+ highlight_json: bool = True,
59
+ stream_tokens: bool = True,
60
+ ) -> None:
61
+ super().__init__()
62
+ if console is None:
63
+ console = Console(
64
+ file=sys.stdout,
65
+ force_terminal=True,
66
+ highlight=False,
67
+ log_time=True,
68
+ log_path=False,
69
+ )
70
+ self._console = console
71
+ self._highlight_json = highlight_json
72
+ self._stream_tokens = stream_tokens
73
+ self._stream_context: dict[str, tuple[asyncio.Queue, asyncio.Task]] = {}
74
+
75
+ async def on_initialize(self, agent, ctx: Context) -> None:
76
+ self._console.log(f"[{agent.name}] start task={ctx.task_id}")
77
+ await super().on_initialize(agent, ctx)
78
+
79
+ async def on_pre_consume(self, agent, ctx: Context, inputs: list[Any]):
80
+ summary = ", ".join(self._summarize_artifact(art) for art in inputs) or "<none>"
81
+ self._console.log(f"[{agent.name}] consume n={len(inputs)} artifacts -> {summary}")
82
+ self._render_artifacts(agent.name, inputs, role="input")
83
+ return await super().on_pre_consume(agent, ctx, inputs)
84
+
85
+ async def on_pre_evaluate(self, agent, ctx: Context, inputs: EvalInputs) -> EvalInputs:
86
+ if self._stream_tokens:
87
+ self._maybe_start_stream(agent, ctx)
88
+ return await super().on_pre_evaluate(agent, ctx, inputs)
89
+
90
+ async def on_post_evaluate(
91
+ self, agent, ctx: Context, inputs: EvalInputs, result: EvalResult
92
+ ) -> EvalResult:
93
+ self._render_metrics(agent.name, result.metrics)
94
+ self._render_artifacts(agent.name, result.artifacts or inputs.artifacts, role="output")
95
+ if result.logs:
96
+ self._render_logs(agent.name, result.logs)
97
+ awaited = await super().on_post_evaluate(agent, ctx, inputs, result)
98
+ if self._stream_tokens:
99
+ await self._finalize_stream(agent, ctx)
100
+ return awaited
101
+
102
+ async def on_post_publish(self, agent, ctx: Context, artifact):
103
+ visibility = getattr(artifact.visibility, "kind", "Public")
104
+ subtitle = f"visibility={visibility}"
105
+ panel = self._build_artifact_panel(artifact, role="published", subtitle=subtitle)
106
+ self._console.print(panel)
107
+ await super().on_post_publish(agent, ctx, artifact)
108
+
109
+ async def on_error(self, agent, ctx: Context, error: Exception) -> None:
110
+ self._console.log(f"[{agent.name}] error {error!r}", style="bold red")
111
+ if self._stream_tokens:
112
+ await self._abort_stream(agent, ctx)
113
+ await super().on_error(agent, ctx, error)
114
+
115
+ async def on_terminate(self, agent, ctx: Context) -> None:
116
+ if self._stream_tokens:
117
+ await self._abort_stream(agent, ctx)
118
+ self._console.log(f"[{agent.name}] end task={ctx.task_id}")
119
+ await super().on_terminate(agent, ctx)
120
+
121
+ # ------------------------------------------------------------------
122
+ # Rendering helpers
123
+
124
+ def _render_artifacts(self, agent_name: str, artifacts: Sequence[Any], *, role: str) -> None:
125
+ for artifact in artifacts:
126
+ panel = self._build_artifact_panel(artifact, role=role)
127
+ self._console.print(panel)
128
+
129
+ def _build_artifact_panel(
130
+ self, artifact: Any, *, role: str, subtitle: str | None = None
131
+ ) -> Panel:
132
+ title = f"{role} • {self._summarize_artifact(artifact)}"
133
+ if subtitle is None:
134
+ produced_by = getattr(artifact, "produced_by", None)
135
+ visibility = getattr(artifact, "visibility", None)
136
+ visibility_name = getattr(visibility, "kind", None)
137
+ pieces = []
138
+ if produced_by:
139
+ pieces.append(f"from={produced_by}")
140
+ if visibility_name:
141
+ pieces.append(f"visibility={visibility_name}")
142
+ subtitle = " | ".join(pieces)
143
+
144
+ payload = getattr(artifact, "payload", None)
145
+ renderable = self._render_payload(payload)
146
+ return Panel(renderable, title=title, subtitle=subtitle, border_style="cyan")
147
+
148
+ def _render_payload(self, payload: Any):
149
+ if payload is None:
150
+ return Pretty(payload)
151
+ if isinstance(payload, Mapping):
152
+ if self._highlight_json:
153
+ try:
154
+ return JSON.from_data(payload, indent=2, sort_keys=True)
155
+ except Exception: # nosec B110 - pragma: no cover - serialization guard
156
+ pass
157
+ return Pretty(dict(payload))
158
+ if isinstance(payload, (list, tuple, set)):
159
+ return Pretty(payload)
160
+ if hasattr(payload, "model_dump"):
161
+ model_dict = payload.model_dump()
162
+ return JSON.from_data(model_dict, indent=2, sort_keys=True)
163
+ return Pretty(payload)
164
+
165
+ def _render_metrics(self, agent_name: str, metrics: Mapping[str, Any]) -> None:
166
+ if not metrics:
167
+ return
168
+ table = Table(title=f"{agent_name} metrics", box=None, show_header=False)
169
+ for key, value in metrics.items():
170
+ display = f"{value:.2f}" if isinstance(value, (int, float)) else str(value)
171
+ table.add_row(key, display)
172
+ self._console.print(table)
173
+
174
+ def _render_logs(self, agent_name: str, logs: Iterable[str]) -> None:
175
+ if not logs:
176
+ return
177
+ textual: list[str] = []
178
+ json_sections: list[JSON] = []
179
+ for line in logs:
180
+ if line.startswith("dspy.output="):
181
+ _, _, payload = line.partition("=")
182
+ try:
183
+ json_sections.append(
184
+ JSON.from_data(json.loads(payload), indent=2, sort_keys=True)
185
+ )
186
+ continue
187
+ except json.JSONDecodeError:
188
+ textual.append(line)
189
+ else:
190
+ textual.append(line)
191
+ for payload in json_sections:
192
+ panel = Panel(payload, title=f"{agent_name} ▸ dspy.output", border_style="green")
193
+ self._console.print(panel)
194
+ if textual:
195
+ body = Text("\n".join(textual) + "\n")
196
+ panel = Panel(body, title=f"{agent_name} logs", border_style="magenta")
197
+ self._console.print(panel)
198
+
199
+ def _summarize_artifact(self, artifact: Any) -> str:
200
+ try:
201
+ art_id = getattr(artifact, "id", None)
202
+ prefix = str(art_id)[:8] if art_id else "?"
203
+ art_type = getattr(artifact, "type", type(artifact).__name__)
204
+ return f"{art_type}@{prefix}"
205
+ except Exception: # pragma: no cover - defensive
206
+ return repr(artifact)
207
+
208
+ # ------------------------------------------------------------------
209
+ # Streaming support
210
+
211
+ def _maybe_start_stream(self, agent, ctx: Context) -> None:
212
+ stream_key = self._stream_key(agent, ctx)
213
+ if stream_key in self._stream_context:
214
+ return
215
+ queue: asyncio.Queue = asyncio.Queue()
216
+ self._attach_stream_queue(ctx.state, queue)
217
+ task = asyncio.create_task(self._consume_stream(agent.name, stream_key, queue))
218
+ self._stream_context[stream_key] = (queue, task)
219
+
220
+ async def _finalize_stream(self, agent, ctx: Context) -> None:
221
+ stream_key = self._stream_key(agent, ctx)
222
+ record = self._stream_context.pop(stream_key, None)
223
+ self._detach_stream_queue(ctx.state)
224
+ if not record:
225
+ return
226
+ queue, task = record
227
+ if not task.done():
228
+ await queue.put({"kind": "end"})
229
+ try:
230
+ await asyncio.wait_for(task, timeout=2.0)
231
+ except asyncio.TimeoutError: # pragma: no cover - defensive cancel
232
+ task.cancel()
233
+
234
+ async def _abort_stream(self, agent, ctx: Context) -> None:
235
+ stream_key = self._stream_key(agent, ctx)
236
+ record = self._stream_context.pop(stream_key, None)
237
+ self._detach_stream_queue(ctx.state)
238
+ if not record:
239
+ return
240
+ queue, task = record
241
+ if not task.done():
242
+ await queue.put({"kind": "end", "error": "aborted"})
243
+ task.cancel()
244
+ with contextlib.suppress(asyncio.CancelledError):
245
+ await task
246
+
247
+ async def _consume_stream(self, agent_name: str, stream_key: str, queue: asyncio.Queue) -> None:
248
+ body = Text()
249
+ live: Live | None = None
250
+ try:
251
+ while True:
252
+ event = await queue.get()
253
+ if event is None or event.get("kind") == "end":
254
+ break
255
+ kind = event.get("kind")
256
+ if live is None:
257
+ live_panel = Panel(body, title=f"{agent_name} ▸ streaming", border_style="cyan")
258
+ live = Live(
259
+ live_panel,
260
+ console=self._console,
261
+ refresh_per_second=12,
262
+ transient=True,
263
+ )
264
+ live.__enter__()
265
+ if kind == "chunk":
266
+ chunk = event.get("chunk") or ""
267
+ body.append(chunk)
268
+ elif kind == "status":
269
+ message = event.get("message") or ""
270
+ stage = event.get("stage")
271
+ line = f"[{stage}] {message}" if stage else message
272
+ body.append(f"\n{line}\n", style="dim")
273
+ elif kind == "error":
274
+ message = event.get("message") or ""
275
+ body.append(f"\n⚠ {message}\n", style="bold red")
276
+ if live is not None:
277
+ live.update(Panel(body, title=f"{agent_name} ▸ streaming", border_style="cyan"))
278
+ finally:
279
+ if live is not None:
280
+ live.__exit__(None, None, None)
281
+ if body.plain:
282
+ self._console.print(
283
+ Panel(body, title=f"{agent_name} ▸ stream transcript", border_style="cyan")
284
+ )
285
+
286
+ def _stream_key(self, agent, ctx: Context) -> str:
287
+ return f"{ctx.task_id}:{agent.name}"
288
+
289
+ def _attach_stream_queue(self, state: MutableMapping[str, Any], queue: asyncio.Queue) -> None:
290
+ state.setdefault("_logging", {})["stream_queue"] = queue
291
+
292
+ def _detach_stream_queue(self, state: MutableMapping[str, Any]) -> None:
293
+ try:
294
+ logging_state = state.get("_logging")
295
+ if isinstance(logging_state, MutableMapping):
296
+ logging_state.pop("stream_queue", None)
297
+ except Exception: # nosec B110 - pragma: no cover - defensive
298
+ pass
299
+
300
+
301
+ __all__ = ["LoggingUtility", "MetricsUtility"]
@@ -6,19 +6,18 @@ from typing import TYPE_CHECKING, Any
6
6
 
7
7
  from pydantic import Field
8
8
 
9
- from flock.core.component.agent_component_base import AgentComponentConfig
10
- from flock.core.component.utility_component import UtilityComponent
11
- from flock.core.context.context import FlockContext
12
- from flock.core.context.context_vars import FLOCK_BATCH_SILENT_MODE
13
- from flock.core.logging.formatters.themed_formatter import (
9
+ from flock.components import AgentComponent, AgentComponentConfig
10
+ from flock.logging.formatters.themed_formatter import (
14
11
  ThemedAgentResultFormatter,
15
12
  )
16
- from flock.core.logging.formatters.themes import OutputTheme
17
- from flock.core.logging.logging import get_logger
18
- from flock.core.registry import flock_component
13
+ from flock.logging.formatters.themes import OutputTheme
14
+ from flock.logging.logging import get_logger
15
+ from flock.runtime import Context, EvalInputs, EvalResult
16
+
17
+
18
+ if TYPE_CHECKING: # pragma: no cover - type checking only
19
+ from flock.agent import Agent
19
20
 
20
- if TYPE_CHECKING:
21
- from flock.core.flock_agent import FlockAgent
22
21
 
23
22
  logger = get_logger("components.utility.output")
24
23
 
@@ -27,14 +26,10 @@ class OutputUtilityConfig(AgentComponentConfig):
27
26
  """Configuration for output formatting and display."""
28
27
 
29
28
  theme: OutputTheme = Field(
30
- default=OutputTheme.afterglow, description="Theme for output formatting"
31
- )
32
- render_table: bool = Field(
33
- default=False, description="Whether to render output as a table"
34
- )
35
- max_length: int = Field(
36
- default=1000, description="Maximum length for displayed output"
29
+ default=OutputTheme.catppuccin_mocha, description="Theme for output formatting"
37
30
  )
31
+ render_table: bool = Field(default=True, description="Whether to render output as a table")
32
+ max_length: int = Field(default=1000, description="Maximum length for displayed output")
38
33
  truncate_long_values: bool = Field(
39
34
  default=True, description="Whether to truncate long values in display"
40
35
  )
@@ -59,8 +54,7 @@ class OutputUtilityConfig(AgentComponentConfig):
59
54
  )
60
55
 
61
56
 
62
- @flock_component(config_class=OutputUtilityConfig)
63
- class OutputUtilityComponent(UtilityComponent):
57
+ class OutputUtilityComponent(AgentComponent):
64
58
  """Utility component that handles output formatting and display."""
65
59
 
66
60
  config: OutputUtilityConfig = Field(
@@ -88,12 +82,11 @@ class OutputUtilityComponent(UtilityComponent):
88
82
  # Default formatting based on type
89
83
  if isinstance(value, dict):
90
84
  return self._format_dict(value)
91
- elif isinstance(value, list):
85
+ if isinstance(value, list):
92
86
  return self._format_list(value)
93
- elif isinstance(value, str) and self.config.format_code_blocks:
87
+ if isinstance(value, str) and self.config.format_code_blocks:
94
88
  return self._format_potential_code(value)
95
- else:
96
- return str(value)
89
+ return str(value)
97
90
 
98
91
  def _format_dict(self, d: dict[str, Any], indent: int = 0) -> str:
99
92
  """Format a dictionary with proper indentation."""
@@ -124,6 +117,7 @@ class OutputUtilityComponent(UtilityComponent):
124
117
 
125
118
  def _format_potential_code(self, text: str) -> str:
126
119
  """Apply syntax highlighting to potential code blocks."""
120
+
127
121
  # Simple pattern matching for code blocks
128
122
  def replace_code_block(match):
129
123
  language = match.group(1) or "text"
@@ -131,57 +125,74 @@ class OutputUtilityComponent(UtilityComponent):
131
125
  return f"[CODE:{language}]\n{code}\n[/CODE]"
132
126
 
133
127
  # Replace markdown-style code blocks
134
- text = re.sub(
135
- r"```(\w+)?\n(.*?)\n```", replace_code_block, text, flags=re.DOTALL
136
- )
137
- return text
128
+ return re.sub(r"```(\w+)?\n(.*?)\n```", replace_code_block, text, flags=re.DOTALL)
138
129
 
139
130
  async def on_post_evaluate(
140
- self,
141
- agent: "FlockAgent",
142
- inputs: dict[str, Any],
143
- context: FlockContext | None = None,
144
- result: dict[str, Any] | None = None,
131
+ self, agent: "Agent", ctx: Context, inputs: EvalInputs, result: EvalResult
145
132
  ) -> dict[str, Any]:
146
133
  """Format and display the output."""
147
134
  logger.debug("Formatting and displaying output")
148
135
 
149
136
  streaming_live_handled = False
150
- if context:
151
- streaming_live_handled = bool(
152
- context.get_variable("_flock_stream_live_active", False)
153
- )
137
+ output_queued = False
138
+ streamed_artifact_id = None
139
+
140
+ if ctx:
141
+ streaming_live_handled = bool(ctx.get_variable("_flock_stream_live_active", False))
142
+ output_queued = bool(ctx.get_variable("_flock_output_queued", False))
143
+ streamed_artifact_id = ctx.get_variable("_flock_streamed_artifact_id")
144
+
154
145
  if streaming_live_handled:
155
- context.state.pop("_flock_stream_live_active", None)
146
+ ctx.state.pop("_flock_stream_live_active", None)
156
147
 
157
- # Determine if output should be suppressed
158
- is_silent = self.config.no_output or (
159
- context and context.get_variable(FLOCK_BATCH_SILENT_MODE, False)
160
- )
148
+ if output_queued:
149
+ ctx.state.pop("_flock_output_queued", None)
150
+
151
+ if streamed_artifact_id:
152
+ ctx.state.pop("_flock_streamed_artifact_id", None)
161
153
 
154
+ # If streaming was handled, we need to update the final display with the real artifact ID
155
+ if streaming_live_handled and streamed_artifact_id:
156
+ logger.debug(
157
+ f"Updating streamed display with final artifact ID: {streamed_artifact_id}"
158
+ )
159
+ # The streaming display already showed everything, we just need to update the ID
160
+ # This is handled by a final refresh in the streaming code
161
+ return result
162
+
163
+ # Skip output if streaming already handled it (and no ID to update)
162
164
  if streaming_live_handled:
163
165
  logger.debug("Skipping static table because streaming rendered live output.")
164
166
  return result
165
167
 
166
- if is_silent:
167
- logger.debug("Output suppressed (config or batch silent mode).")
168
- return result # Skip console output
168
+ # If output was queued due to concurrent stream, wait and then display
169
+ if output_queued:
170
+ # Wait for active streams to complete
171
+ orchestrator = getattr(ctx, "orchestrator", None)
172
+ if orchestrator:
173
+ import asyncio
174
+
175
+ # Wait until no streams are active
176
+ max_wait = 30 # seconds
177
+ waited = 0
178
+ while getattr(orchestrator, "_active_streams", 0) > 0 and waited < max_wait:
179
+ await asyncio.sleep(0.1)
180
+ waited += 0.1
181
+ logger.debug(
182
+ f"Queued output displayed after waiting {waited:.1f}s for streams to complete."
183
+ )
169
184
 
170
185
  logger.debug("Formatting and displaying output to console.")
171
186
 
172
- if self.config.print_context and context:
187
+ if self.config.print_context and ctx:
173
188
  # Add context snapshot if requested (be careful with large contexts)
174
189
  try:
175
190
  # Create a copy or select relevant parts to avoid modifying original result dict directly
176
191
  display_result = result.copy()
177
- display_result["context_snapshot"] = (
178
- context.to_dict()
179
- ) # Potential performance hit
192
+ display_result["context_snapshot"] = ctx.to_dict() # Potential performance hit
180
193
  except Exception:
181
194
  display_result = result.copy()
182
- display_result["context_snapshot"] = (
183
- "[Error serializing context]"
184
- )
195
+ display_result["context_snapshot"] = "[Error serializing context]"
185
196
  result_to_display = display_result
186
197
  else:
187
198
  result_to_display = result
@@ -192,8 +203,12 @@ class OutputUtilityComponent(UtilityComponent):
192
203
  max_length=self.config.max_length,
193
204
  render_table=self.config.render_table,
194
205
  )
195
- model = agent.model if agent.model else context.get_variable("model")
196
- self._formatter.display_result(result_to_display, agent.name + " - " + model)
206
+ model = agent.model if agent.model else ctx.get_variable("model")
207
+ # Handle None model gracefully
208
+ model_display = model if model is not None else "default"
209
+ self._formatter.display_result(
210
+ result_to_display.artifacts, agent.name + " - " + model_display
211
+ )
197
212
 
198
213
  return result # Return the original, unmodified result
199
214
 
flock/visibility.py ADDED
@@ -0,0 +1,107 @@
1
+ from __future__ import annotations
2
+
3
+
4
+ """Artifact visibility policies."""
5
+
6
+ from datetime import datetime, timedelta, timezone
7
+ from typing import TYPE_CHECKING, Literal
8
+
9
+ from pydantic import BaseModel, Field, PrivateAttr
10
+
11
+
12
+ if TYPE_CHECKING:
13
+ from collections.abc import Iterable
14
+
15
+
16
+ class AgentIdentity(BaseModel):
17
+ """Minimal identity information about an agent for visibility checks."""
18
+
19
+ name: str
20
+ labels: set[str] = Field(default_factory=set)
21
+ tenant_id: str | None = None
22
+
23
+
24
+ class Visibility(BaseModel):
25
+ """Base visibility contract."""
26
+
27
+ kind: Literal["Public", "Private", "Labelled", "Tenant", "After"]
28
+
29
+ def allows(self, agent: AgentIdentity, *, now: datetime | None = None) -> bool:
30
+ raise NotImplementedError
31
+
32
+
33
+ class PublicVisibility(Visibility):
34
+ kind: Literal["Public"] = "Public"
35
+
36
+ def allows(
37
+ self, agent: AgentIdentity, *, now: datetime | None = None
38
+ ) -> bool: # pragma: no cover - trivial
39
+ return True
40
+
41
+
42
+ class PrivateVisibility(Visibility):
43
+ kind: Literal["Private"] = "Private"
44
+ agents: set[str] = Field(default_factory=set)
45
+
46
+ def allows(self, agent: AgentIdentity, *, now: datetime | None = None) -> bool:
47
+ return agent.name in self.agents
48
+
49
+
50
+ class LabelledVisibility(Visibility):
51
+ kind: Literal["Labelled"] = "Labelled"
52
+ required_labels: set[str] = Field(default_factory=set)
53
+
54
+ def allows(self, agent: AgentIdentity, *, now: datetime | None = None) -> bool:
55
+ return self.required_labels.issubset(agent.labels)
56
+
57
+
58
+ class TenantVisibility(Visibility):
59
+ kind: Literal["Tenant"] = "Tenant"
60
+ tenant_id: str | None = None
61
+
62
+ def allows(self, agent: AgentIdentity, *, now: datetime | None = None) -> bool:
63
+ if self.tenant_id is None:
64
+ return True
65
+ return agent.tenant_id == self.tenant_id
66
+
67
+
68
+ class AfterVisibility(Visibility):
69
+ kind: Literal["After"] = "After"
70
+ ttl: timedelta = Field(default=timedelta())
71
+ then: Visibility | None = None
72
+ _created_at: datetime = PrivateAttr(default_factory=lambda: datetime.now(timezone.utc))
73
+
74
+ def allows(self, agent: AgentIdentity, *, now: datetime | None = None) -> bool:
75
+ now = now or datetime.now(timezone.utc)
76
+ if now - self._created_at >= self.ttl:
77
+ if self.then:
78
+ return self.then.allows(agent, now=now)
79
+ return True
80
+ return False
81
+
82
+
83
+ def ensure_visibility(value: Visibility | None) -> Visibility:
84
+ if value is None:
85
+ return PublicVisibility()
86
+ return value
87
+
88
+
89
+ def only_for(*agent_names: str) -> PrivateVisibility:
90
+ return PrivateVisibility(agents=set(agent_names))
91
+
92
+
93
+ def agents_from_names(names: Iterable[str]) -> set[str]: # pragma: no cover - helper
94
+ return set(names)
95
+
96
+
97
+ __all__ = [
98
+ "AfterVisibility",
99
+ "AgentIdentity",
100
+ "LabelledVisibility",
101
+ "PrivateVisibility",
102
+ "PublicVisibility",
103
+ "TenantVisibility",
104
+ "Visibility",
105
+ "ensure_visibility",
106
+ "only_for",
107
+ ]