flock-core 0.5.0b27__py3-none-any.whl → 0.5.0b50__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 (357) 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.0b50.dist-info/METADATA +747 -0
  162. flock_core-0.5.0b50.dist-info/RECORD +398 -0
  163. flock_core-0.5.0b50.dist-info/entry_points.txt +2 -0
  164. {flock_core-0.5.0b27.dist-info → flock_core-0.5.0b50.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 -15
  197. flock/components/utility/memory_utility_component.py +0 -550
  198. flock/components/utility/metrics_utility_component.py +0 -700
  199. flock/config.py +0 -61
  200. flock/core/__init__.py +0 -110
  201. flock/core/agent/__init__.py +0 -16
  202. flock/core/agent/default_agent.py +0 -180
  203. flock/core/agent/flock_agent_components.py +0 -104
  204. flock/core/agent/flock_agent_execution.py +0 -101
  205. flock/core/agent/flock_agent_integration.py +0 -260
  206. flock/core/agent/flock_agent_lifecycle.py +0 -186
  207. flock/core/agent/flock_agent_serialization.py +0 -381
  208. flock/core/api/__init__.py +0 -10
  209. flock/core/api/custom_endpoint.py +0 -45
  210. flock/core/api/endpoints.py +0 -254
  211. flock/core/api/main.py +0 -162
  212. flock/core/api/models.py +0 -97
  213. flock/core/api/run_store.py +0 -224
  214. flock/core/api/runner.py +0 -44
  215. flock/core/api/service.py +0 -214
  216. flock/core/component/__init__.py +0 -15
  217. flock/core/component/agent_component_base.py +0 -309
  218. flock/core/component/evaluation_component.py +0 -62
  219. flock/core/component/routing_component.py +0 -74
  220. flock/core/component/utility_component.py +0 -69
  221. flock/core/config/flock_agent_config.py +0 -58
  222. flock/core/config/scheduled_agent_config.py +0 -40
  223. flock/core/context/context.py +0 -213
  224. flock/core/context/context_manager.py +0 -37
  225. flock/core/context/context_vars.py +0 -10
  226. flock/core/evaluation/utils.py +0 -396
  227. flock/core/execution/batch_executor.py +0 -369
  228. flock/core/execution/evaluation_executor.py +0 -438
  229. flock/core/execution/local_executor.py +0 -31
  230. flock/core/execution/opik_executor.py +0 -103
  231. flock/core/execution/temporal_executor.py +0 -164
  232. flock/core/flock.py +0 -634
  233. flock/core/flock_agent.py +0 -336
  234. flock/core/flock_factory.py +0 -551
  235. flock/core/flock_scheduler.py +0 -166
  236. flock/core/flock_server_manager.py +0 -136
  237. flock/core/interpreter/python_interpreter.py +0 -689
  238. flock/core/mcp/__init__.py +0 -1
  239. flock/core/mcp/flock_mcp_server.py +0 -680
  240. flock/core/mcp/mcp_client_manager.py +0 -201
  241. flock/core/mcp/types/__init__.py +0 -1
  242. flock/core/mixin/dspy_integration.py +0 -403
  243. flock/core/mixin/prompt_parser.py +0 -125
  244. flock/core/orchestration/__init__.py +0 -15
  245. flock/core/orchestration/flock_batch_processor.py +0 -94
  246. flock/core/orchestration/flock_evaluator.py +0 -113
  247. flock/core/orchestration/flock_execution.py +0 -295
  248. flock/core/orchestration/flock_initialization.py +0 -149
  249. flock/core/orchestration/flock_server_manager.py +0 -67
  250. flock/core/orchestration/flock_web_server.py +0 -117
  251. flock/core/registry/__init__.py +0 -45
  252. flock/core/registry/agent_registry.py +0 -69
  253. flock/core/registry/callable_registry.py +0 -139
  254. flock/core/registry/component_discovery.py +0 -142
  255. flock/core/registry/component_registry.py +0 -64
  256. flock/core/registry/config_mapping.py +0 -64
  257. flock/core/registry/decorators.py +0 -137
  258. flock/core/registry/registry_hub.py +0 -205
  259. flock/core/registry/server_registry.py +0 -57
  260. flock/core/registry/type_registry.py +0 -86
  261. flock/core/serialization/__init__.py +0 -13
  262. flock/core/serialization/callable_registry.py +0 -52
  263. flock/core/serialization/flock_serializer.py +0 -832
  264. flock/core/serialization/json_encoder.py +0 -41
  265. flock/core/serialization/secure_serializer.py +0 -175
  266. flock/core/serialization/serializable.py +0 -342
  267. flock/core/serialization/serialization_utils.py +0 -412
  268. flock/core/util/file_path_utils.py +0 -223
  269. flock/core/util/hydrator.py +0 -309
  270. flock/core/util/input_resolver.py +0 -164
  271. flock/core/util/loader.py +0 -59
  272. flock/core/util/splitter.py +0 -219
  273. flock/di.py +0 -27
  274. flock/platform/docker_tools.py +0 -49
  275. flock/platform/jaeger_install.py +0 -86
  276. flock/webapp/__init__.py +0 -1
  277. flock/webapp/app/__init__.py +0 -0
  278. flock/webapp/app/api/__init__.py +0 -0
  279. flock/webapp/app/api/agent_management.py +0 -241
  280. flock/webapp/app/api/execution.py +0 -709
  281. flock/webapp/app/api/flock_management.py +0 -129
  282. flock/webapp/app/api/registry_viewer.py +0 -30
  283. flock/webapp/app/chat.py +0 -665
  284. flock/webapp/app/config.py +0 -104
  285. flock/webapp/app/dependencies.py +0 -117
  286. flock/webapp/app/main.py +0 -1070
  287. flock/webapp/app/middleware.py +0 -113
  288. flock/webapp/app/models_ui.py +0 -7
  289. flock/webapp/app/services/__init__.py +0 -0
  290. flock/webapp/app/services/feedback_file_service.py +0 -363
  291. flock/webapp/app/services/flock_service.py +0 -337
  292. flock/webapp/app/services/sharing_models.py +0 -81
  293. flock/webapp/app/services/sharing_store.py +0 -598
  294. flock/webapp/app/templates/theme_mapper.html +0 -326
  295. flock/webapp/app/theme_mapper.py +0 -812
  296. flock/webapp/app/utils.py +0 -85
  297. flock/webapp/run.py +0 -215
  298. flock/webapp/static/css/chat.css +0 -301
  299. flock/webapp/static/css/components.css +0 -167
  300. flock/webapp/static/css/header.css +0 -39
  301. flock/webapp/static/css/layout.css +0 -46
  302. flock/webapp/static/css/sidebar.css +0 -127
  303. flock/webapp/static/css/two-pane.css +0 -48
  304. flock/webapp/templates/base.html +0 -200
  305. flock/webapp/templates/chat.html +0 -152
  306. flock/webapp/templates/chat_settings.html +0 -19
  307. flock/webapp/templates/flock_editor.html +0 -16
  308. flock/webapp/templates/index.html +0 -12
  309. flock/webapp/templates/partials/_agent_detail_form.html +0 -93
  310. flock/webapp/templates/partials/_agent_list.html +0 -18
  311. flock/webapp/templates/partials/_agent_manager_view.html +0 -51
  312. flock/webapp/templates/partials/_agent_tools_checklist.html +0 -14
  313. flock/webapp/templates/partials/_chat_container.html +0 -15
  314. flock/webapp/templates/partials/_chat_messages.html +0 -57
  315. flock/webapp/templates/partials/_chat_settings_form.html +0 -85
  316. flock/webapp/templates/partials/_create_flock_form.html +0 -50
  317. flock/webapp/templates/partials/_dashboard_flock_detail.html +0 -17
  318. flock/webapp/templates/partials/_dashboard_flock_file_list.html +0 -16
  319. flock/webapp/templates/partials/_dashboard_flock_properties_preview.html +0 -28
  320. flock/webapp/templates/partials/_dashboard_upload_flock_form.html +0 -16
  321. flock/webapp/templates/partials/_dynamic_input_form_content.html +0 -22
  322. flock/webapp/templates/partials/_env_vars_table.html +0 -23
  323. flock/webapp/templates/partials/_execution_form.html +0 -118
  324. flock/webapp/templates/partials/_execution_view_container.html +0 -28
  325. flock/webapp/templates/partials/_flock_file_list.html +0 -23
  326. flock/webapp/templates/partials/_flock_properties_form.html +0 -52
  327. flock/webapp/templates/partials/_flock_upload_form.html +0 -16
  328. flock/webapp/templates/partials/_header_flock_status.html +0 -5
  329. flock/webapp/templates/partials/_load_manager_view.html +0 -49
  330. flock/webapp/templates/partials/_registry_table.html +0 -25
  331. flock/webapp/templates/partials/_registry_viewer_content.html +0 -70
  332. flock/webapp/templates/partials/_results_display.html +0 -78
  333. flock/webapp/templates/partials/_settings_env_content.html +0 -9
  334. flock/webapp/templates/partials/_settings_theme_content.html +0 -14
  335. flock/webapp/templates/partials/_settings_view.html +0 -36
  336. flock/webapp/templates/partials/_share_chat_link_snippet.html +0 -11
  337. flock/webapp/templates/partials/_share_link_snippet.html +0 -35
  338. flock/webapp/templates/partials/_sidebar.html +0 -74
  339. flock/webapp/templates/partials/_streaming_results_container.html +0 -195
  340. flock/webapp/templates/partials/_structured_data_view.html +0 -40
  341. flock/webapp/templates/partials/_theme_preview.html +0 -36
  342. flock/webapp/templates/registry_viewer.html +0 -84
  343. flock/webapp/templates/shared_run_page.html +0 -140
  344. flock/workflow/__init__.py +0 -0
  345. flock/workflow/activities.py +0 -196
  346. flock/workflow/agent_activities.py +0 -24
  347. flock/workflow/agent_execution_activity.py +0 -202
  348. flock/workflow/flock_workflow.py +0 -214
  349. flock/workflow/temporal_config.py +0 -96
  350. flock/workflow/temporal_setup.py +0 -68
  351. flock_core-0.5.0b27.dist-info/METADATA +0 -274
  352. flock_core-0.5.0b27.dist-info/RECORD +0 -559
  353. flock_core-0.5.0b27.dist-info/entry_points.txt +0 -2
  354. /flock/{core/logging → logging}/formatters/themes.py +0 -0
  355. /flock/{core/logging → logging}/span_middleware/baggage_span_processor.py +0 -0
  356. /flock/{core/mcp → mcp}/util/__init__.py +0 -0
  357. {flock_core-0.5.0b27.dist-info → flock_core-0.5.0b50.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
+ ]