flock-core 0.5.0b28__py3-none-any.whl → 0.5.0b51__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of flock-core might be problematic. Click here for more details.
- flock/__init__.py +12 -217
- flock/agent.py +678 -0
- flock/api/themes.py +71 -0
- flock/artifacts.py +79 -0
- flock/cli.py +75 -0
- flock/components.py +173 -0
- flock/dashboard/__init__.py +28 -0
- flock/dashboard/collector.py +283 -0
- flock/dashboard/events.py +182 -0
- flock/dashboard/launcher.py +230 -0
- flock/dashboard/service.py +537 -0
- flock/dashboard/websocket.py +235 -0
- flock/engines/__init__.py +6 -0
- flock/engines/dspy_engine.py +856 -0
- flock/examples.py +128 -0
- flock/frontend/README.md +678 -0
- flock/frontend/docs/DESIGN_SYSTEM.md +1980 -0
- flock/frontend/index.html +12 -0
- flock/frontend/package-lock.json +4347 -0
- flock/frontend/package.json +48 -0
- flock/frontend/src/App.tsx +79 -0
- flock/frontend/src/__tests__/e2e/critical-scenarios.test.tsx +587 -0
- flock/frontend/src/__tests__/integration/filtering-e2e.test.tsx +387 -0
- flock/frontend/src/__tests__/integration/graph-rendering.test.tsx +640 -0
- flock/frontend/src/__tests__/integration/indexeddb-persistence.test.tsx +699 -0
- flock/frontend/src/components/common/BuildInfo.tsx +39 -0
- flock/frontend/src/components/common/EmptyState.module.css +115 -0
- flock/frontend/src/components/common/EmptyState.tsx +128 -0
- flock/frontend/src/components/common/ErrorBoundary.module.css +169 -0
- flock/frontend/src/components/common/ErrorBoundary.tsx +118 -0
- flock/frontend/src/components/common/KeyboardShortcutsDialog.css +251 -0
- flock/frontend/src/components/common/KeyboardShortcutsDialog.tsx +151 -0
- flock/frontend/src/components/common/LoadingSpinner.module.css +97 -0
- flock/frontend/src/components/common/LoadingSpinner.tsx +29 -0
- flock/frontend/src/components/controls/PublishControl.css +547 -0
- flock/frontend/src/components/controls/PublishControl.test.tsx +543 -0
- flock/frontend/src/components/controls/PublishControl.tsx +432 -0
- flock/frontend/src/components/details/DetailWindowContainer.tsx +62 -0
- flock/frontend/src/components/details/LiveOutputTab.test.tsx +792 -0
- flock/frontend/src/components/details/LiveOutputTab.tsx +220 -0
- flock/frontend/src/components/details/MessageHistoryTab.tsx +299 -0
- flock/frontend/src/components/details/NodeDetailWindow.test.tsx +501 -0
- flock/frontend/src/components/details/NodeDetailWindow.tsx +218 -0
- flock/frontend/src/components/details/RunStatusTab.tsx +307 -0
- flock/frontend/src/components/details/tabs.test.tsx +1015 -0
- flock/frontend/src/components/filters/CorrelationIDFilter.module.css +102 -0
- flock/frontend/src/components/filters/CorrelationIDFilter.test.tsx +197 -0
- flock/frontend/src/components/filters/CorrelationIDFilter.tsx +121 -0
- flock/frontend/src/components/filters/FilterBar.module.css +29 -0
- flock/frontend/src/components/filters/FilterBar.test.tsx +133 -0
- flock/frontend/src/components/filters/FilterBar.tsx +33 -0
- flock/frontend/src/components/filters/FilterPills.module.css +79 -0
- flock/frontend/src/components/filters/FilterPills.test.tsx +173 -0
- flock/frontend/src/components/filters/FilterPills.tsx +67 -0
- flock/frontend/src/components/filters/TimeRangeFilter.module.css +91 -0
- flock/frontend/src/components/filters/TimeRangeFilter.test.tsx +154 -0
- flock/frontend/src/components/filters/TimeRangeFilter.tsx +105 -0
- flock/frontend/src/components/graph/AgentNode.test.tsx +75 -0
- flock/frontend/src/components/graph/AgentNode.tsx +322 -0
- flock/frontend/src/components/graph/GraphCanvas.tsx +406 -0
- flock/frontend/src/components/graph/MessageFlowEdge.tsx +128 -0
- flock/frontend/src/components/graph/MessageNode.test.tsx +62 -0
- flock/frontend/src/components/graph/MessageNode.tsx +116 -0
- flock/frontend/src/components/graph/MiniMap.tsx +47 -0
- flock/frontend/src/components/graph/TransformEdge.tsx +123 -0
- flock/frontend/src/components/layout/DashboardLayout.css +407 -0
- flock/frontend/src/components/layout/DashboardLayout.tsx +300 -0
- flock/frontend/src/components/layout/Header.module.css +88 -0
- flock/frontend/src/components/layout/Header.tsx +52 -0
- flock/frontend/src/components/modules/EventLogModule.test.tsx +401 -0
- flock/frontend/src/components/modules/EventLogModule.tsx +396 -0
- flock/frontend/src/components/modules/EventLogModuleWrapper.tsx +17 -0
- flock/frontend/src/components/modules/ModuleRegistry.test.ts +333 -0
- flock/frontend/src/components/modules/ModuleRegistry.ts +85 -0
- flock/frontend/src/components/modules/ModuleWindow.tsx +155 -0
- flock/frontend/src/components/modules/registerModules.ts +20 -0
- flock/frontend/src/components/settings/AdvancedSettings.tsx +175 -0
- flock/frontend/src/components/settings/AppearanceSettings.tsx +185 -0
- flock/frontend/src/components/settings/GraphSettings.tsx +110 -0
- flock/frontend/src/components/settings/SettingsPanel.css +327 -0
- flock/frontend/src/components/settings/SettingsPanel.tsx +131 -0
- flock/frontend/src/components/settings/ThemeSelector.tsx +298 -0
- flock/frontend/src/hooks/useKeyboardShortcuts.ts +148 -0
- flock/frontend/src/hooks/useModulePersistence.test.ts +442 -0
- flock/frontend/src/hooks/useModulePersistence.ts +154 -0
- flock/frontend/src/hooks/useModules.ts +139 -0
- flock/frontend/src/hooks/usePersistence.ts +139 -0
- flock/frontend/src/main.tsx +13 -0
- flock/frontend/src/services/api.ts +213 -0
- flock/frontend/src/services/indexeddb.test.ts +793 -0
- flock/frontend/src/services/indexeddb.ts +794 -0
- flock/frontend/src/services/layout.test.ts +437 -0
- flock/frontend/src/services/layout.ts +146 -0
- flock/frontend/src/services/themeApplicator.ts +140 -0
- flock/frontend/src/services/themeService.ts +77 -0
- flock/frontend/src/services/websocket.test.ts +595 -0
- flock/frontend/src/services/websocket.ts +685 -0
- flock/frontend/src/store/filterStore.test.ts +242 -0
- flock/frontend/src/store/filterStore.ts +103 -0
- flock/frontend/src/store/graphStore.test.ts +186 -0
- flock/frontend/src/store/graphStore.ts +414 -0
- flock/frontend/src/store/moduleStore.test.ts +253 -0
- flock/frontend/src/store/moduleStore.ts +57 -0
- flock/frontend/src/store/settingsStore.ts +188 -0
- flock/frontend/src/store/streamStore.ts +68 -0
- flock/frontend/src/store/uiStore.test.ts +54 -0
- flock/frontend/src/store/uiStore.ts +110 -0
- flock/frontend/src/store/wsStore.ts +34 -0
- flock/frontend/src/styles/index.css +15 -0
- flock/frontend/src/styles/scrollbar.css +47 -0
- flock/frontend/src/styles/variables.css +488 -0
- flock/frontend/src/test/setup.ts +1 -0
- flock/frontend/src/types/filters.ts +14 -0
- flock/frontend/src/types/graph.ts +55 -0
- flock/frontend/src/types/modules.ts +7 -0
- flock/frontend/src/types/theme.ts +55 -0
- flock/frontend/src/utils/mockData.ts +85 -0
- flock/frontend/src/utils/performance.ts +16 -0
- flock/frontend/src/utils/transforms.test.ts +860 -0
- flock/frontend/src/utils/transforms.ts +323 -0
- flock/frontend/src/vite-env.d.ts +17 -0
- flock/frontend/tsconfig.json +27 -0
- flock/frontend/tsconfig.node.json +11 -0
- flock/frontend/vite.config.ts +25 -0
- flock/frontend/vitest.config.ts +11 -0
- flock/{core/util → helper}/cli_helper.py +4 -3
- flock/{core/logging → logging}/__init__.py +2 -3
- flock/{core/logging → logging}/formatters/enum_builder.py +3 -4
- flock/{core/logging → logging}/formatters/theme_builder.py +19 -44
- flock/{core/logging → logging}/formatters/themed_formatter.py +69 -115
- flock/{core/logging → logging}/logging.py +77 -61
- flock/{core/logging → logging}/telemetry.py +20 -26
- flock/{core/logging → logging}/telemetry_exporter/base_exporter.py +2 -2
- flock/{core/logging → logging}/telemetry_exporter/file_exporter.py +6 -9
- flock/{core/logging → logging}/telemetry_exporter/sqlite_exporter.py +2 -3
- flock/{core/logging → logging}/trace_and_logged.py +20 -24
- flock/mcp/__init__.py +91 -0
- flock/{core/mcp/mcp_client.py → mcp/client.py} +103 -154
- flock/{core/mcp/mcp_config.py → mcp/config.py} +62 -117
- flock/mcp/manager.py +255 -0
- flock/mcp/servers/sse/__init__.py +1 -1
- flock/mcp/servers/sse/flock_sse_server.py +11 -53
- flock/mcp/servers/stdio/__init__.py +1 -1
- flock/mcp/servers/stdio/flock_stdio_server.py +8 -48
- flock/mcp/servers/streamable_http/flock_streamable_http_server.py +17 -62
- flock/mcp/servers/websockets/flock_websocket_server.py +7 -40
- flock/{core/mcp/flock_mcp_tool.py → mcp/tool.py} +16 -26
- flock/mcp/types/__init__.py +42 -0
- flock/{core/mcp → mcp}/types/callbacks.py +9 -15
- flock/{core/mcp → mcp}/types/factories.py +7 -6
- flock/{core/mcp → mcp}/types/handlers.py +13 -18
- flock/{core/mcp → mcp}/types/types.py +70 -74
- flock/{core/mcp → mcp}/util/helpers.py +1 -1
- flock/orchestrator.py +645 -0
- flock/registry.py +148 -0
- flock/runtime.py +262 -0
- flock/service.py +140 -0
- flock/store.py +69 -0
- flock/subscription.py +111 -0
- flock/themes/andromeda.toml +1 -1
- flock/themes/apple-system-colors.toml +1 -1
- flock/themes/arcoiris.toml +1 -1
- flock/themes/atomonelight.toml +1 -1
- flock/themes/ayu copy.toml +1 -1
- flock/themes/ayu-light.toml +1 -1
- flock/themes/belafonte-day.toml +1 -1
- flock/themes/belafonte-night.toml +1 -1
- flock/themes/blulocodark.toml +1 -1
- flock/themes/breeze.toml +1 -1
- flock/themes/broadcast.toml +1 -1
- flock/themes/brogrammer.toml +1 -1
- flock/themes/builtin-dark.toml +1 -1
- flock/themes/builtin-pastel-dark.toml +1 -1
- flock/themes/catppuccin-latte.toml +1 -1
- flock/themes/catppuccin-macchiato.toml +1 -1
- flock/themes/catppuccin-mocha.toml +1 -1
- flock/themes/cga.toml +1 -1
- flock/themes/chalk.toml +1 -1
- flock/themes/ciapre.toml +1 -1
- flock/themes/coffee-theme.toml +1 -1
- flock/themes/cyberpunkscarletprotocol.toml +1 -1
- flock/themes/dark+.toml +1 -1
- flock/themes/darkermatrix.toml +1 -1
- flock/themes/darkside.toml +1 -1
- flock/themes/desert.toml +1 -1
- flock/themes/django.toml +1 -1
- flock/themes/djangosmooth.toml +1 -1
- flock/themes/doomone.toml +1 -1
- flock/themes/dotgov.toml +1 -1
- flock/themes/dracula+.toml +1 -1
- flock/themes/duckbones.toml +1 -1
- flock/themes/encom.toml +1 -1
- flock/themes/espresso.toml +1 -1
- flock/themes/everblush.toml +1 -1
- flock/themes/fairyfloss.toml +1 -1
- flock/themes/fideloper.toml +1 -1
- flock/themes/fishtank.toml +1 -1
- flock/themes/flexoki-light.toml +1 -1
- flock/themes/floraverse.toml +1 -1
- flock/themes/framer.toml +1 -1
- flock/themes/galizur.toml +1 -1
- flock/themes/github.toml +1 -1
- flock/themes/grass.toml +1 -1
- flock/themes/grey-green.toml +1 -1
- flock/themes/gruvboxlight.toml +1 -1
- flock/themes/guezwhoz.toml +1 -1
- flock/themes/harper.toml +1 -1
- flock/themes/hax0r-blue.toml +1 -1
- flock/themes/hopscotch.256.toml +1 -1
- flock/themes/ic-green-ppl.toml +1 -1
- flock/themes/iceberg-dark.toml +1 -1
- flock/themes/japanesque.toml +1 -1
- flock/themes/jubi.toml +1 -1
- flock/themes/kibble.toml +1 -1
- flock/themes/kolorit.toml +1 -1
- flock/themes/kurokula.toml +1 -1
- flock/themes/materialdesigncolors.toml +1 -1
- flock/themes/matrix.toml +1 -1
- flock/themes/mellifluous.toml +1 -1
- flock/themes/midnight-in-mojave.toml +1 -1
- flock/themes/monokai-remastered.toml +1 -1
- flock/themes/monokai-soda.toml +1 -1
- flock/themes/neon.toml +1 -1
- flock/themes/neopolitan.toml +1 -1
- flock/themes/nord-light.toml +1 -1
- flock/themes/ocean.toml +1 -1
- flock/themes/onehalfdark.toml +1 -1
- flock/themes/onehalflight.toml +1 -1
- flock/themes/palenighthc.toml +1 -1
- flock/themes/paulmillr.toml +1 -1
- flock/themes/pencildark.toml +1 -1
- flock/themes/pnevma.toml +1 -1
- flock/themes/purple-rain.toml +1 -1
- flock/themes/purplepeter.toml +1 -1
- flock/themes/raycast-dark.toml +1 -1
- flock/themes/red-sands.toml +1 -1
- flock/themes/relaxed.toml +1 -1
- flock/themes/retro.toml +1 -1
- flock/themes/rose-pine.toml +1 -1
- flock/themes/royal.toml +1 -1
- flock/themes/ryuuko.toml +1 -1
- flock/themes/sakura.toml +1 -1
- flock/themes/scarlet-protocol.toml +1 -1
- flock/themes/seoulbones-dark.toml +1 -1
- flock/themes/shades-of-purple.toml +1 -1
- flock/themes/smyck.toml +1 -1
- flock/themes/softserver.toml +1 -1
- flock/themes/solarized-darcula.toml +1 -1
- flock/themes/square.toml +1 -1
- flock/themes/sugarplum.toml +1 -1
- flock/themes/thayer-bright.toml +1 -1
- flock/themes/tokyonight.toml +1 -1
- flock/themes/tomorrow.toml +1 -1
- flock/themes/ubuntu.toml +1 -1
- flock/themes/ultradark.toml +1 -1
- flock/themes/ultraviolent.toml +1 -1
- flock/themes/unikitty.toml +1 -1
- flock/themes/urple.toml +1 -1
- flock/themes/vesper.toml +1 -1
- flock/themes/vimbones.toml +1 -1
- flock/themes/wildcherry.toml +1 -1
- flock/themes/wilmersdorf.toml +1 -1
- flock/themes/wryan.toml +1 -1
- flock/themes/xcodedarkhc.toml +1 -1
- flock/themes/xcodelight.toml +1 -1
- flock/themes/zenbones-light.toml +1 -1
- flock/themes/zenwritten-dark.toml +1 -1
- flock/utilities.py +301 -0
- flock/{components/utility → utility}/output_utility_component.py +68 -53
- flock/visibility.py +107 -0
- flock_core-0.5.0b51.dist-info/METADATA +747 -0
- flock_core-0.5.0b51.dist-info/RECORD +508 -0
- flock_core-0.5.0b51.dist-info/entry_points.txt +2 -0
- {flock_core-0.5.0b28.dist-info → flock_core-0.5.0b51.dist-info}/licenses/LICENSE +1 -1
- flock/adapter/__init__.py +0 -14
- flock/adapter/azure_adapter.py +0 -68
- flock/adapter/chroma_adapter.py +0 -73
- flock/adapter/faiss_adapter.py +0 -97
- flock/adapter/pinecone_adapter.py +0 -51
- flock/adapter/vector_base.py +0 -47
- flock/cli/assets/release_notes.md +0 -140
- flock/cli/config.py +0 -8
- flock/cli/constants.py +0 -36
- flock/cli/create_agent.py +0 -1
- flock/cli/create_flock.py +0 -280
- flock/cli/execute_flock.py +0 -620
- flock/cli/load_agent.py +0 -1
- flock/cli/load_examples.py +0 -1
- flock/cli/load_flock.py +0 -192
- flock/cli/load_release_notes.py +0 -20
- flock/cli/loaded_flock_cli.py +0 -254
- flock/cli/manage_agents.py +0 -459
- flock/cli/registry_management.py +0 -889
- flock/cli/runner.py +0 -41
- flock/cli/settings.py +0 -857
- flock/cli/utils.py +0 -135
- flock/cli/view_results.py +0 -29
- flock/cli/yaml_editor.py +0 -396
- flock/components/__init__.py +0 -30
- flock/components/evaluation/__init__.py +0 -9
- flock/components/evaluation/declarative_evaluation_component.py +0 -606
- flock/components/routing/__init__.py +0 -15
- flock/components/routing/conditional_routing_component.py +0 -494
- flock/components/routing/default_routing_component.py +0 -103
- flock/components/routing/llm_routing_component.py +0 -206
- flock/components/utility/__init__.py +0 -22
- flock/components/utility/example_utility_component.py +0 -250
- flock/components/utility/feedback_utility_component.py +0 -206
- flock/components/utility/memory_utility_component.py +0 -550
- flock/components/utility/metrics_utility_component.py +0 -700
- flock/config.py +0 -61
- flock/core/__init__.py +0 -110
- flock/core/agent/__init__.py +0 -16
- flock/core/agent/default_agent.py +0 -216
- flock/core/agent/flock_agent_components.py +0 -104
- flock/core/agent/flock_agent_execution.py +0 -101
- flock/core/agent/flock_agent_integration.py +0 -260
- flock/core/agent/flock_agent_lifecycle.py +0 -186
- flock/core/agent/flock_agent_serialization.py +0 -381
- flock/core/api/__init__.py +0 -10
- flock/core/api/custom_endpoint.py +0 -45
- flock/core/api/endpoints.py +0 -254
- flock/core/api/main.py +0 -162
- flock/core/api/models.py +0 -97
- flock/core/api/run_store.py +0 -224
- flock/core/api/runner.py +0 -44
- flock/core/api/service.py +0 -214
- flock/core/component/__init__.py +0 -15
- flock/core/component/agent_component_base.py +0 -309
- flock/core/component/evaluation_component.py +0 -62
- flock/core/component/routing_component.py +0 -74
- flock/core/component/utility_component.py +0 -69
- flock/core/config/flock_agent_config.py +0 -58
- flock/core/config/scheduled_agent_config.py +0 -40
- flock/core/context/context.py +0 -213
- flock/core/context/context_manager.py +0 -37
- flock/core/context/context_vars.py +0 -10
- flock/core/evaluation/utils.py +0 -396
- flock/core/execution/batch_executor.py +0 -369
- flock/core/execution/evaluation_executor.py +0 -438
- flock/core/execution/local_executor.py +0 -31
- flock/core/execution/opik_executor.py +0 -103
- flock/core/execution/temporal_executor.py +0 -164
- flock/core/flock.py +0 -634
- flock/core/flock_agent.py +0 -336
- flock/core/flock_factory.py +0 -613
- flock/core/flock_scheduler.py +0 -166
- flock/core/flock_server_manager.py +0 -136
- flock/core/interpreter/python_interpreter.py +0 -689
- flock/core/mcp/__init__.py +0 -1
- flock/core/mcp/flock_mcp_server.py +0 -680
- flock/core/mcp/mcp_client_manager.py +0 -201
- flock/core/mcp/types/__init__.py +0 -1
- flock/core/mixin/dspy_integration.py +0 -403
- flock/core/mixin/prompt_parser.py +0 -125
- flock/core/orchestration/__init__.py +0 -15
- flock/core/orchestration/flock_batch_processor.py +0 -94
- flock/core/orchestration/flock_evaluator.py +0 -113
- flock/core/orchestration/flock_execution.py +0 -295
- flock/core/orchestration/flock_initialization.py +0 -149
- flock/core/orchestration/flock_server_manager.py +0 -67
- flock/core/orchestration/flock_web_server.py +0 -117
- flock/core/registry/__init__.py +0 -45
- flock/core/registry/agent_registry.py +0 -69
- flock/core/registry/callable_registry.py +0 -139
- flock/core/registry/component_discovery.py +0 -142
- flock/core/registry/component_registry.py +0 -64
- flock/core/registry/config_mapping.py +0 -64
- flock/core/registry/decorators.py +0 -137
- flock/core/registry/registry_hub.py +0 -205
- flock/core/registry/server_registry.py +0 -57
- flock/core/registry/type_registry.py +0 -86
- flock/core/serialization/__init__.py +0 -13
- flock/core/serialization/callable_registry.py +0 -52
- flock/core/serialization/flock_serializer.py +0 -832
- flock/core/serialization/json_encoder.py +0 -41
- flock/core/serialization/secure_serializer.py +0 -175
- flock/core/serialization/serializable.py +0 -342
- flock/core/serialization/serialization_utils.py +0 -412
- flock/core/util/file_path_utils.py +0 -223
- flock/core/util/hydrator.py +0 -309
- flock/core/util/input_resolver.py +0 -164
- flock/core/util/loader.py +0 -59
- flock/core/util/splitter.py +0 -219
- flock/di.py +0 -27
- flock/platform/docker_tools.py +0 -49
- flock/platform/jaeger_install.py +0 -86
- flock/webapp/__init__.py +0 -1
- flock/webapp/app/__init__.py +0 -0
- flock/webapp/app/api/__init__.py +0 -0
- flock/webapp/app/api/agent_management.py +0 -241
- flock/webapp/app/api/execution.py +0 -709
- flock/webapp/app/api/flock_management.py +0 -129
- flock/webapp/app/api/registry_viewer.py +0 -30
- flock/webapp/app/chat.py +0 -665
- flock/webapp/app/config.py +0 -104
- flock/webapp/app/dependencies.py +0 -117
- flock/webapp/app/main.py +0 -1070
- flock/webapp/app/middleware.py +0 -113
- flock/webapp/app/models_ui.py +0 -7
- flock/webapp/app/services/__init__.py +0 -0
- flock/webapp/app/services/feedback_file_service.py +0 -363
- flock/webapp/app/services/flock_service.py +0 -337
- flock/webapp/app/services/sharing_models.py +0 -81
- flock/webapp/app/services/sharing_store.py +0 -762
- flock/webapp/app/templates/theme_mapper.html +0 -326
- flock/webapp/app/theme_mapper.py +0 -812
- flock/webapp/app/utils.py +0 -85
- flock/webapp/run.py +0 -215
- flock/webapp/static/css/chat.css +0 -301
- flock/webapp/static/css/components.css +0 -167
- flock/webapp/static/css/header.css +0 -39
- flock/webapp/static/css/layout.css +0 -46
- flock/webapp/static/css/sidebar.css +0 -127
- flock/webapp/static/css/two-pane.css +0 -48
- flock/webapp/templates/base.html +0 -200
- flock/webapp/templates/chat.html +0 -152
- flock/webapp/templates/chat_settings.html +0 -19
- flock/webapp/templates/flock_editor.html +0 -16
- flock/webapp/templates/index.html +0 -12
- flock/webapp/templates/partials/_agent_detail_form.html +0 -93
- flock/webapp/templates/partials/_agent_list.html +0 -18
- flock/webapp/templates/partials/_agent_manager_view.html +0 -51
- flock/webapp/templates/partials/_agent_tools_checklist.html +0 -14
- flock/webapp/templates/partials/_chat_container.html +0 -15
- flock/webapp/templates/partials/_chat_messages.html +0 -57
- flock/webapp/templates/partials/_chat_settings_form.html +0 -85
- flock/webapp/templates/partials/_create_flock_form.html +0 -50
- flock/webapp/templates/partials/_dashboard_flock_detail.html +0 -17
- flock/webapp/templates/partials/_dashboard_flock_file_list.html +0 -16
- flock/webapp/templates/partials/_dashboard_flock_properties_preview.html +0 -28
- flock/webapp/templates/partials/_dashboard_upload_flock_form.html +0 -16
- flock/webapp/templates/partials/_dynamic_input_form_content.html +0 -22
- flock/webapp/templates/partials/_env_vars_table.html +0 -23
- flock/webapp/templates/partials/_execution_form.html +0 -118
- flock/webapp/templates/partials/_execution_view_container.html +0 -28
- flock/webapp/templates/partials/_flock_file_list.html +0 -23
- flock/webapp/templates/partials/_flock_properties_form.html +0 -52
- flock/webapp/templates/partials/_flock_upload_form.html +0 -16
- flock/webapp/templates/partials/_header_flock_status.html +0 -5
- flock/webapp/templates/partials/_load_manager_view.html +0 -49
- flock/webapp/templates/partials/_registry_table.html +0 -25
- flock/webapp/templates/partials/_registry_viewer_content.html +0 -70
- flock/webapp/templates/partials/_results_display.html +0 -78
- flock/webapp/templates/partials/_settings_env_content.html +0 -9
- flock/webapp/templates/partials/_settings_theme_content.html +0 -14
- flock/webapp/templates/partials/_settings_view.html +0 -36
- flock/webapp/templates/partials/_share_chat_link_snippet.html +0 -11
- flock/webapp/templates/partials/_share_link_snippet.html +0 -35
- flock/webapp/templates/partials/_sidebar.html +0 -74
- flock/webapp/templates/partials/_streaming_results_container.html +0 -195
- flock/webapp/templates/partials/_structured_data_view.html +0 -40
- flock/webapp/templates/partials/_theme_preview.html +0 -36
- flock/webapp/templates/registry_viewer.html +0 -84
- flock/webapp/templates/shared_run_page.html +0 -140
- flock/workflow/__init__.py +0 -0
- flock/workflow/activities.py +0 -196
- flock/workflow/agent_activities.py +0 -24
- flock/workflow/agent_execution_activity.py +0 -202
- flock/workflow/flock_workflow.py +0 -214
- flock/workflow/temporal_config.py +0 -96
- flock/workflow/temporal_setup.py +0 -68
- flock_core-0.5.0b28.dist-info/METADATA +0 -274
- flock_core-0.5.0b28.dist-info/RECORD +0 -561
- flock_core-0.5.0b28.dist-info/entry_points.txt +0 -2
- /flock/{core/logging → logging}/formatters/themes.py +0 -0
- /flock/{core/logging → logging}/span_middleware/baggage_span_processor.py +0 -0
- /flock/{core/mcp → mcp}/util/__init__.py +0 -0
- {flock_core-0.5.0b28.dist-info → flock_core-0.5.0b51.dist-info}/WHEEL +0 -0
flock/agent.py
ADDED
|
@@ -0,0 +1,678 @@
|
|
|
1
|
+
"""Agent definitions and fluent builder APIs."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import os
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from typing import TYPE_CHECKING, Any
|
|
9
|
+
|
|
10
|
+
from pydantic import BaseModel
|
|
11
|
+
|
|
12
|
+
from flock.artifacts import Artifact, ArtifactSpec
|
|
13
|
+
from flock.logging.logging import get_logger
|
|
14
|
+
from flock.registry import function_registry, type_registry
|
|
15
|
+
from flock.runtime import Context, EvalInputs, EvalResult
|
|
16
|
+
from flock.subscription import BatchSpec, JoinSpec, Subscription, TextPredicate
|
|
17
|
+
from flock.visibility import AgentIdentity, Visibility, ensure_visibility, only_for
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
logger = get_logger(__name__)
|
|
21
|
+
|
|
22
|
+
if TYPE_CHECKING: # pragma: no cover - type hints only
|
|
23
|
+
from collections.abc import Callable, Iterable, Sequence
|
|
24
|
+
|
|
25
|
+
from flock.components import AgentComponent, EngineComponent
|
|
26
|
+
from flock.orchestrator import Flock
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass
|
|
30
|
+
class AgentOutput:
|
|
31
|
+
spec: ArtifactSpec
|
|
32
|
+
default_visibility: Visibility
|
|
33
|
+
|
|
34
|
+
def apply(
|
|
35
|
+
self,
|
|
36
|
+
data: dict[str, Any],
|
|
37
|
+
*,
|
|
38
|
+
produced_by: str,
|
|
39
|
+
metadata: dict[str, Any] | None = None,
|
|
40
|
+
) -> Artifact:
|
|
41
|
+
metadata = metadata or {}
|
|
42
|
+
return self.spec.build(
|
|
43
|
+
produced_by=produced_by,
|
|
44
|
+
data=data,
|
|
45
|
+
visibility=metadata.get("visibility", self.default_visibility),
|
|
46
|
+
correlation_id=metadata.get("correlation_id"),
|
|
47
|
+
partition_key=metadata.get("partition_key"),
|
|
48
|
+
tags=metadata.get("tags"),
|
|
49
|
+
version=metadata.get("version", 1),
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class Agent:
|
|
54
|
+
"""Executable agent constructed via `AgentBuilder`."""
|
|
55
|
+
|
|
56
|
+
def __init__(self, name: str, *, orchestrator: Flock) -> None:
|
|
57
|
+
self.name = name
|
|
58
|
+
self.description: str | None = None
|
|
59
|
+
self._orchestrator = orchestrator
|
|
60
|
+
self.subscriptions: list[Subscription] = []
|
|
61
|
+
self.outputs: list[AgentOutput] = []
|
|
62
|
+
self.utilities: list[AgentComponent] = []
|
|
63
|
+
self.engines: list[EngineComponent] = []
|
|
64
|
+
self.best_of_n: int = 1
|
|
65
|
+
self.best_of_score: Callable[[EvalResult], float] | None = None
|
|
66
|
+
self.max_concurrency: int = 1
|
|
67
|
+
self._semaphore = asyncio.Semaphore(self.max_concurrency)
|
|
68
|
+
self.calls_func: Callable[..., Any] | None = None
|
|
69
|
+
self.tools: set[Callable[..., Any]] = set()
|
|
70
|
+
self.labels: set[str] = set()
|
|
71
|
+
self.tenant_id: str | None = None
|
|
72
|
+
self.model: str | None = None
|
|
73
|
+
self.prevent_self_trigger: bool = True # T065: Prevent infinite feedback loops
|
|
74
|
+
# MCP integration
|
|
75
|
+
self.mcp_server_names: set[str] = set()
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def identity(self) -> AgentIdentity:
|
|
79
|
+
return AgentIdentity(name=self.name, labels=self.labels, tenant_id=self.tenant_id)
|
|
80
|
+
|
|
81
|
+
def set_max_concurrency(self, value: int) -> None:
|
|
82
|
+
self.max_concurrency = max(1, value)
|
|
83
|
+
self._semaphore = asyncio.Semaphore(self.max_concurrency)
|
|
84
|
+
|
|
85
|
+
async def run_direct(self, *inputs: BaseModel) -> list[Artifact]:
|
|
86
|
+
return await self._orchestrator.direct_invoke(self, list(inputs))
|
|
87
|
+
|
|
88
|
+
async def execute(self, ctx: Context, artifacts: list[Artifact]) -> list[Artifact]:
|
|
89
|
+
async with self._semaphore:
|
|
90
|
+
try:
|
|
91
|
+
self._resolve_engines()
|
|
92
|
+
self._resolve_utilities()
|
|
93
|
+
await self._run_initialize(ctx)
|
|
94
|
+
processed_inputs = await self._run_pre_consume(ctx, artifacts)
|
|
95
|
+
eval_inputs = EvalInputs(artifacts=processed_inputs, state=dict(ctx.state))
|
|
96
|
+
eval_inputs = await self._run_pre_evaluate(ctx, eval_inputs)
|
|
97
|
+
result = await self._run_engines(ctx, eval_inputs)
|
|
98
|
+
result = await self._run_post_evaluate(ctx, eval_inputs, result)
|
|
99
|
+
outputs = await self._make_outputs(ctx, result)
|
|
100
|
+
await self._run_post_publish(ctx, outputs)
|
|
101
|
+
if self.calls_func:
|
|
102
|
+
await self._invoke_call(ctx, outputs or processed_inputs)
|
|
103
|
+
return outputs
|
|
104
|
+
except Exception as exc:
|
|
105
|
+
await self._run_error(ctx, exc)
|
|
106
|
+
raise
|
|
107
|
+
finally:
|
|
108
|
+
await self._run_terminate(ctx)
|
|
109
|
+
|
|
110
|
+
async def _get_mcp_tools(self, ctx: Context) -> list[Callable]:
|
|
111
|
+
"""Lazy-load MCP tools from assigned servers.
|
|
112
|
+
|
|
113
|
+
Architecture Decision: AD001 - Two-Level Architecture
|
|
114
|
+
Agents fetch tools from servers registered at orchestrator level.
|
|
115
|
+
|
|
116
|
+
Architecture Decision: AD003 - Tool Namespacing
|
|
117
|
+
All tools are namespaced as {server}__{tool}.
|
|
118
|
+
|
|
119
|
+
Architecture Decision: AD007 - Graceful Degradation
|
|
120
|
+
If MCP loading fails, returns empty list so agent continues with native tools.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
ctx: Current execution context with agent_id and run_id
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
List of DSPy-compatible tool callables
|
|
127
|
+
"""
|
|
128
|
+
if not self.mcp_server_names:
|
|
129
|
+
# No MCP servers assigned to this agent
|
|
130
|
+
return []
|
|
131
|
+
|
|
132
|
+
try:
|
|
133
|
+
# Get the MCP manager from orchestrator
|
|
134
|
+
manager = self._orchestrator.get_mcp_manager()
|
|
135
|
+
|
|
136
|
+
# Import tool wrapper
|
|
137
|
+
|
|
138
|
+
# Fetch tools from all assigned servers
|
|
139
|
+
tools_dict = await manager.get_tools_for_agent(
|
|
140
|
+
agent_id=self.name,
|
|
141
|
+
run_id=ctx.task_id,
|
|
142
|
+
server_names=self.mcp_server_names,
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
# Convert to DSPy tool callables
|
|
146
|
+
dspy_tools = []
|
|
147
|
+
for namespaced_name, tool_info in tools_dict.items():
|
|
148
|
+
tool_info["server_name"]
|
|
149
|
+
flock_tool = tool_info["tool"] # Already a FlockMCPTool
|
|
150
|
+
client = tool_info["client"]
|
|
151
|
+
|
|
152
|
+
# Convert to DSPy tool
|
|
153
|
+
dspy_tool = flock_tool.as_dspy_tool(server=client)
|
|
154
|
+
|
|
155
|
+
# Update name to include namespace
|
|
156
|
+
dspy_tool.name = namespaced_name
|
|
157
|
+
|
|
158
|
+
dspy_tools.append(dspy_tool)
|
|
159
|
+
|
|
160
|
+
return dspy_tools
|
|
161
|
+
|
|
162
|
+
except Exception as e:
|
|
163
|
+
# Architecture Decision: AD007 - Graceful Degradation
|
|
164
|
+
# Agent continues with native tools only
|
|
165
|
+
logger.error(f"Failed to load MCP tools for agent {self.name}: {e}", exc_info=True)
|
|
166
|
+
return []
|
|
167
|
+
|
|
168
|
+
async def _run_initialize(self, ctx: Context) -> None:
|
|
169
|
+
for component in self.utilities:
|
|
170
|
+
await component.on_initialize(self, ctx)
|
|
171
|
+
for engine in self.engines:
|
|
172
|
+
await engine.on_initialize(self, ctx)
|
|
173
|
+
|
|
174
|
+
async def _run_pre_consume(self, ctx: Context, inputs: list[Artifact]) -> list[Artifact]:
|
|
175
|
+
current = inputs
|
|
176
|
+
for component in self.utilities:
|
|
177
|
+
current = await component.on_pre_consume(self, ctx, current)
|
|
178
|
+
return current
|
|
179
|
+
|
|
180
|
+
async def _run_pre_evaluate(self, ctx: Context, inputs: EvalInputs) -> EvalInputs:
|
|
181
|
+
current = inputs
|
|
182
|
+
for component in self.utilities:
|
|
183
|
+
current = await component.on_pre_evaluate(self, ctx, current)
|
|
184
|
+
return current
|
|
185
|
+
|
|
186
|
+
async def _run_engines(self, ctx: Context, inputs: EvalInputs) -> EvalResult:
|
|
187
|
+
engines = self._resolve_engines()
|
|
188
|
+
if not engines:
|
|
189
|
+
return EvalResult(artifacts=inputs.artifacts, state=inputs.state)
|
|
190
|
+
|
|
191
|
+
async def run_chain() -> EvalResult:
|
|
192
|
+
current_inputs = inputs
|
|
193
|
+
accumulated_logs: list[str] = []
|
|
194
|
+
accumulated_metrics: dict[str, float] = {}
|
|
195
|
+
for engine in engines:
|
|
196
|
+
current_inputs = await engine.on_pre_evaluate(self, ctx, current_inputs)
|
|
197
|
+
result = await engine.evaluate(self, ctx, current_inputs)
|
|
198
|
+
|
|
199
|
+
# AUTO-WRAP: If engine returns BaseModel instead of EvalResult, wrap it
|
|
200
|
+
from flock.runtime import EvalResult as ER
|
|
201
|
+
|
|
202
|
+
if isinstance(result, BaseModel) and not isinstance(result, ER):
|
|
203
|
+
result = ER.from_object(result, agent=self)
|
|
204
|
+
|
|
205
|
+
result = await engine.on_post_evaluate(self, ctx, current_inputs, result)
|
|
206
|
+
accumulated_logs.extend(result.logs)
|
|
207
|
+
accumulated_metrics.update(result.metrics)
|
|
208
|
+
merged_state = dict(current_inputs.state)
|
|
209
|
+
merged_state.update(result.state)
|
|
210
|
+
current_inputs = EvalInputs(
|
|
211
|
+
artifacts=result.artifacts or current_inputs.artifacts,
|
|
212
|
+
state=merged_state,
|
|
213
|
+
)
|
|
214
|
+
return EvalResult(
|
|
215
|
+
artifacts=current_inputs.artifacts,
|
|
216
|
+
state=current_inputs.state,
|
|
217
|
+
metrics=accumulated_metrics,
|
|
218
|
+
logs=accumulated_logs,
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
if self.best_of_n <= 1:
|
|
222
|
+
return await run_chain()
|
|
223
|
+
|
|
224
|
+
async with asyncio.TaskGroup() as tg: # Python 3.12
|
|
225
|
+
tasks: list[asyncio.Task[EvalResult]] = []
|
|
226
|
+
for _ in range(self.best_of_n):
|
|
227
|
+
tasks.append(tg.create_task(run_chain()))
|
|
228
|
+
results = [task.result() for task in tasks]
|
|
229
|
+
if not results:
|
|
230
|
+
return EvalResult(artifacts=[], state={})
|
|
231
|
+
if self.best_of_score is None:
|
|
232
|
+
return results[0]
|
|
233
|
+
return max(results, key=self.best_of_score)
|
|
234
|
+
|
|
235
|
+
async def _run_post_evaluate(
|
|
236
|
+
self, ctx: Context, inputs: EvalInputs, result: EvalResult
|
|
237
|
+
) -> EvalResult:
|
|
238
|
+
current = result
|
|
239
|
+
for component in self.utilities:
|
|
240
|
+
current = await component.on_post_evaluate(self, ctx, inputs, current)
|
|
241
|
+
return current
|
|
242
|
+
|
|
243
|
+
async def _make_outputs(self, ctx: Context, result: EvalResult) -> list[Artifact]:
|
|
244
|
+
if not self.outputs:
|
|
245
|
+
# Utility agents may not publish anything
|
|
246
|
+
return list(result.artifacts)
|
|
247
|
+
|
|
248
|
+
produced: list[Artifact] = []
|
|
249
|
+
for output_decl in self.outputs:
|
|
250
|
+
payload = self._select_payload(output_decl, result)
|
|
251
|
+
if payload is None:
|
|
252
|
+
continue
|
|
253
|
+
metadata = {
|
|
254
|
+
"correlation_id": ctx.correlation_id # Need to add this to Context!
|
|
255
|
+
}
|
|
256
|
+
artifact = output_decl.apply(payload, produced_by=self.name, metadata=metadata)
|
|
257
|
+
produced.append(artifact)
|
|
258
|
+
await ctx.board.publish(artifact)
|
|
259
|
+
return produced
|
|
260
|
+
|
|
261
|
+
async def _run_post_publish(self, ctx: Context, artifacts: Sequence[Artifact]) -> None:
|
|
262
|
+
for artifact in artifacts:
|
|
263
|
+
for component in self.utilities:
|
|
264
|
+
await component.on_post_publish(self, ctx, artifact)
|
|
265
|
+
|
|
266
|
+
async def _invoke_call(self, ctx: Context, artifacts: Sequence[Artifact]) -> None:
|
|
267
|
+
func = self.calls_func
|
|
268
|
+
if func is None:
|
|
269
|
+
return
|
|
270
|
+
if not artifacts:
|
|
271
|
+
return
|
|
272
|
+
first = artifacts[0]
|
|
273
|
+
model_cls = type_registry.resolve(first.type)
|
|
274
|
+
payload = model_cls(**first.payload)
|
|
275
|
+
maybe_coro = func(payload)
|
|
276
|
+
if asyncio.iscoroutine(maybe_coro): # pragma: no cover - optional async support
|
|
277
|
+
await maybe_coro
|
|
278
|
+
|
|
279
|
+
async def _run_error(self, ctx: Context, error: Exception) -> None:
|
|
280
|
+
for component in self.utilities:
|
|
281
|
+
await component.on_error(self, ctx, error)
|
|
282
|
+
for engine in self.engines:
|
|
283
|
+
await engine.on_error(self, ctx, error)
|
|
284
|
+
|
|
285
|
+
async def _run_terminate(self, ctx: Context) -> None:
|
|
286
|
+
for component in self.utilities:
|
|
287
|
+
await component.on_terminate(self, ctx)
|
|
288
|
+
for engine in self.engines:
|
|
289
|
+
await engine.on_terminate(self, ctx)
|
|
290
|
+
|
|
291
|
+
def _resolve_engines(self) -> list[EngineComponent]:
|
|
292
|
+
if self.engines:
|
|
293
|
+
return self.engines
|
|
294
|
+
try:
|
|
295
|
+
from flock.engines import DSPyEngine
|
|
296
|
+
except Exception: # pragma: no cover - optional dependency issues
|
|
297
|
+
return []
|
|
298
|
+
|
|
299
|
+
default_engine = DSPyEngine(
|
|
300
|
+
model=self._orchestrator.model or os.getenv("DEFAULT_MODEL", "openai/gpt-4o-mini"),
|
|
301
|
+
instructions=self.description,
|
|
302
|
+
)
|
|
303
|
+
self.engines = [default_engine]
|
|
304
|
+
return self.engines
|
|
305
|
+
|
|
306
|
+
def _resolve_utilities(self) -> list[AgentComponent]:
|
|
307
|
+
if self.utilities:
|
|
308
|
+
return self.utilities
|
|
309
|
+
try:
|
|
310
|
+
from flock.utility.output_utility_component import (
|
|
311
|
+
OutputUtilityComponent,
|
|
312
|
+
)
|
|
313
|
+
except Exception: # pragma: no cover - optional dependency issues
|
|
314
|
+
return []
|
|
315
|
+
|
|
316
|
+
default_component = OutputUtilityComponent()
|
|
317
|
+
self.utilities = [default_component]
|
|
318
|
+
return self.utilities
|
|
319
|
+
|
|
320
|
+
def _select_payload(
|
|
321
|
+
self, output_decl: AgentOutput, result: EvalResult
|
|
322
|
+
) -> dict[str, Any] | None:
|
|
323
|
+
from flock.registry import type_registry
|
|
324
|
+
|
|
325
|
+
if not result.artifacts:
|
|
326
|
+
return None
|
|
327
|
+
|
|
328
|
+
# Normalize the expected type name to canonical form
|
|
329
|
+
expected_canonical = type_registry.resolve_name(output_decl.spec.type_name)
|
|
330
|
+
|
|
331
|
+
for artifact in result.artifacts:
|
|
332
|
+
# Normalize artifact type name to canonical form for comparison
|
|
333
|
+
try:
|
|
334
|
+
artifact_canonical = type_registry.resolve_name(artifact.type)
|
|
335
|
+
if artifact_canonical == expected_canonical:
|
|
336
|
+
return artifact.payload
|
|
337
|
+
except Exception:
|
|
338
|
+
# If normalization fails, fall back to direct comparison
|
|
339
|
+
if artifact.type == output_decl.spec.type_name:
|
|
340
|
+
return artifact.payload
|
|
341
|
+
|
|
342
|
+
# Fallback to state entries keyed by type name
|
|
343
|
+
maybe_data = result.state.get(output_decl.spec.type_name)
|
|
344
|
+
if isinstance(maybe_data, dict):
|
|
345
|
+
return maybe_data
|
|
346
|
+
return None
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
class AgentBuilder:
|
|
350
|
+
"""Fluent builder that also acts as the runtime agent handle."""
|
|
351
|
+
|
|
352
|
+
def __init__(self, orchestrator: Flock, name: str) -> None:
|
|
353
|
+
self._orchestrator = orchestrator
|
|
354
|
+
self._agent = Agent(name, orchestrator=orchestrator)
|
|
355
|
+
self._agent.model = orchestrator.model
|
|
356
|
+
orchestrator.register_agent(self._agent)
|
|
357
|
+
|
|
358
|
+
# Fluent configuration -------------------------------------------------
|
|
359
|
+
|
|
360
|
+
def description(self, text: str) -> AgentBuilder:
|
|
361
|
+
self._agent.description = text
|
|
362
|
+
return self
|
|
363
|
+
|
|
364
|
+
def consumes(
|
|
365
|
+
self,
|
|
366
|
+
*types: type[BaseModel],
|
|
367
|
+
where: Callable[[BaseModel], bool] | Sequence[Callable[[BaseModel], bool]] | None = None,
|
|
368
|
+
text: str | None = None,
|
|
369
|
+
min_p: float = 0.0,
|
|
370
|
+
from_agents: Iterable[str] | None = None,
|
|
371
|
+
channels: Iterable[str] | None = None,
|
|
372
|
+
join: dict | JoinSpec | None = None,
|
|
373
|
+
batch: dict | BatchSpec | None = None,
|
|
374
|
+
delivery: str = "exclusive",
|
|
375
|
+
mode: str = "both",
|
|
376
|
+
priority: int = 0,
|
|
377
|
+
) -> AgentBuilder:
|
|
378
|
+
predicates: Sequence[Callable[[BaseModel], bool]] | None
|
|
379
|
+
if where is None:
|
|
380
|
+
predicates = None
|
|
381
|
+
elif callable(where):
|
|
382
|
+
predicates = [where]
|
|
383
|
+
else:
|
|
384
|
+
predicates = list(where)
|
|
385
|
+
|
|
386
|
+
join_spec = self._normalize_join(join)
|
|
387
|
+
batch_spec = self._normalize_batch(batch)
|
|
388
|
+
text_predicates = [TextPredicate(text=text, min_p=min_p)] if text else []
|
|
389
|
+
subscription = Subscription(
|
|
390
|
+
agent_name=self._agent.name,
|
|
391
|
+
types=types,
|
|
392
|
+
where=predicates,
|
|
393
|
+
text_predicates=text_predicates,
|
|
394
|
+
from_agents=from_agents,
|
|
395
|
+
channels=channels,
|
|
396
|
+
join=join_spec,
|
|
397
|
+
batch=batch_spec,
|
|
398
|
+
delivery=delivery,
|
|
399
|
+
mode=mode,
|
|
400
|
+
priority=priority,
|
|
401
|
+
)
|
|
402
|
+
self._agent.subscriptions.append(subscription)
|
|
403
|
+
return self
|
|
404
|
+
|
|
405
|
+
def publishes(
|
|
406
|
+
self, *types: type[BaseModel], visibility: Visibility | None = None
|
|
407
|
+
) -> PublishBuilder:
|
|
408
|
+
outputs = []
|
|
409
|
+
for model in types:
|
|
410
|
+
spec = ArtifactSpec.from_model(model)
|
|
411
|
+
output = AgentOutput(spec=spec, default_visibility=ensure_visibility(visibility))
|
|
412
|
+
self._agent.outputs.append(output)
|
|
413
|
+
outputs.append(output)
|
|
414
|
+
# T074: Validate configuration after adding outputs
|
|
415
|
+
self._validate_self_trigger_risk()
|
|
416
|
+
return PublishBuilder(self, outputs)
|
|
417
|
+
|
|
418
|
+
def with_utilities(self, *components: AgentComponent) -> AgentBuilder:
|
|
419
|
+
self._agent.utilities.extend(components)
|
|
420
|
+
return self
|
|
421
|
+
|
|
422
|
+
def with_engines(self, *engines: EngineComponent) -> AgentBuilder:
|
|
423
|
+
self._agent.engines.extend(engines)
|
|
424
|
+
return self
|
|
425
|
+
|
|
426
|
+
def best_of(self, n: int, score: Callable[[EvalResult], float]) -> AgentBuilder:
|
|
427
|
+
self._agent.best_of_n = max(1, n)
|
|
428
|
+
self._agent.best_of_score = score
|
|
429
|
+
# T074: Validate best_of value
|
|
430
|
+
self._validate_best_of(n)
|
|
431
|
+
return self
|
|
432
|
+
|
|
433
|
+
def max_concurrency(self, n: int) -> AgentBuilder:
|
|
434
|
+
self._agent.set_max_concurrency(n)
|
|
435
|
+
# T074: Validate concurrency value
|
|
436
|
+
self._validate_concurrency(n)
|
|
437
|
+
return self
|
|
438
|
+
|
|
439
|
+
def calls(self, func: Callable[..., Any]) -> AgentBuilder:
|
|
440
|
+
function_registry.register(func)
|
|
441
|
+
self._agent.calls_func = func
|
|
442
|
+
return self
|
|
443
|
+
|
|
444
|
+
def with_tools(self, funcs: Iterable[Callable[..., Any]]) -> AgentBuilder:
|
|
445
|
+
self._agent.tools.update(funcs)
|
|
446
|
+
return self
|
|
447
|
+
|
|
448
|
+
def with_mcps(self, server_names: Iterable[str]) -> AgentBuilder:
|
|
449
|
+
"""Assign MCP servers to this agent.
|
|
450
|
+
|
|
451
|
+
Architecture Decision: AD001 - Two-Level Architecture
|
|
452
|
+
Agents reference servers registered at orchestrator level.
|
|
453
|
+
|
|
454
|
+
Args:
|
|
455
|
+
server_names: Names of MCP servers this agent should use
|
|
456
|
+
|
|
457
|
+
Returns:
|
|
458
|
+
self for method chaining
|
|
459
|
+
|
|
460
|
+
Raises:
|
|
461
|
+
ValueError: If any server name is not registered with orchestrator
|
|
462
|
+
|
|
463
|
+
Example:
|
|
464
|
+
>>> agent = (
|
|
465
|
+
... orchestrator.agent("file_agent")
|
|
466
|
+
... .with_mcps(["filesystem", "github"])
|
|
467
|
+
... .build()
|
|
468
|
+
... )
|
|
469
|
+
"""
|
|
470
|
+
# Convert to set for efficient lookup
|
|
471
|
+
server_set = set(server_names)
|
|
472
|
+
|
|
473
|
+
# Validate all servers exist in orchestrator
|
|
474
|
+
registered_servers = set(self._orchestrator._mcp_configs.keys())
|
|
475
|
+
invalid_servers = server_set - registered_servers
|
|
476
|
+
|
|
477
|
+
if invalid_servers:
|
|
478
|
+
available = list(registered_servers) if registered_servers else ["none"]
|
|
479
|
+
raise ValueError(
|
|
480
|
+
f"MCP servers not registered: {invalid_servers}. "
|
|
481
|
+
f"Available servers: {available}. "
|
|
482
|
+
f"Register servers using orchestrator.add_mcp() first."
|
|
483
|
+
)
|
|
484
|
+
|
|
485
|
+
# Store in agent
|
|
486
|
+
self._agent.mcp_server_names = server_set
|
|
487
|
+
|
|
488
|
+
return self
|
|
489
|
+
|
|
490
|
+
def labels(self, *labels: str) -> AgentBuilder:
|
|
491
|
+
self._agent.labels.update(labels)
|
|
492
|
+
return self
|
|
493
|
+
|
|
494
|
+
def tenant(self, tenant_id: str) -> AgentBuilder:
|
|
495
|
+
self._agent.tenant_id = tenant_id
|
|
496
|
+
return self
|
|
497
|
+
|
|
498
|
+
def prevent_self_trigger(self, enabled: bool = True) -> AgentBuilder:
|
|
499
|
+
"""Prevent agent from being triggered by its own outputs.
|
|
500
|
+
|
|
501
|
+
When enabled (default), the orchestrator will skip scheduling this agent
|
|
502
|
+
for artifacts it produced itself. This prevents infinite feedback loops
|
|
503
|
+
when an agent consumes and publishes the same type.
|
|
504
|
+
|
|
505
|
+
Args:
|
|
506
|
+
enabled: True to prevent self-triggering (safe default),
|
|
507
|
+
False to allow feedback loops (advanced use case)
|
|
508
|
+
|
|
509
|
+
Returns:
|
|
510
|
+
AgentBuilder for method chaining
|
|
511
|
+
|
|
512
|
+
Example:
|
|
513
|
+
# Safe by default (recommended)
|
|
514
|
+
agent.consumes(Document).publishes(Document)
|
|
515
|
+
# Won't trigger on own outputs ✅
|
|
516
|
+
|
|
517
|
+
# Explicit feedback loop (use with caution!)
|
|
518
|
+
agent.consumes(Data, where=lambda d: d.depth < 10)
|
|
519
|
+
.publishes(Data)
|
|
520
|
+
.prevent_self_trigger(False) # Acknowledge risk
|
|
521
|
+
"""
|
|
522
|
+
self._agent.prevent_self_trigger = enabled
|
|
523
|
+
return self
|
|
524
|
+
|
|
525
|
+
# Runtime helpers ------------------------------------------------------
|
|
526
|
+
|
|
527
|
+
def run(self, *inputs: BaseModel) -> RunHandle:
|
|
528
|
+
return RunHandle(self._agent, list(inputs))
|
|
529
|
+
|
|
530
|
+
def then(self, other: AgentBuilder) -> Pipeline:
|
|
531
|
+
return Pipeline([self, other])
|
|
532
|
+
|
|
533
|
+
# Validation -----------------------------------------------------------
|
|
534
|
+
|
|
535
|
+
def _validate_self_trigger_risk(self) -> None:
|
|
536
|
+
"""T074: Warn if agent consumes and publishes same type (feedback loop risk)."""
|
|
537
|
+
from flock.logging.logging import get_logger
|
|
538
|
+
|
|
539
|
+
logger = get_logger(__name__)
|
|
540
|
+
|
|
541
|
+
# Get types agent consumes
|
|
542
|
+
consuming_types = set()
|
|
543
|
+
for sub in self._agent.subscriptions:
|
|
544
|
+
consuming_types.update(sub.type_names)
|
|
545
|
+
|
|
546
|
+
# Get types agent publishes
|
|
547
|
+
publishing_types = {output.spec.type_name for output in self._agent.outputs}
|
|
548
|
+
|
|
549
|
+
# Check for overlap
|
|
550
|
+
overlap = consuming_types.intersection(publishing_types)
|
|
551
|
+
if overlap and self._agent.prevent_self_trigger:
|
|
552
|
+
logger.warning(
|
|
553
|
+
f"Agent '{self._agent.name}' consumes and publishes {overlap}. "
|
|
554
|
+
f"Feedback loop risk detected. Agent has prevent_self_trigger=True (safe), "
|
|
555
|
+
f"but consider adding filtering: .consumes(Type, where=lambda x: ...) "
|
|
556
|
+
f"or use .prevent_self_trigger(False) for intentional feedback."
|
|
557
|
+
)
|
|
558
|
+
|
|
559
|
+
def _validate_best_of(self, n: int) -> None:
|
|
560
|
+
"""T074: Warn if best_of value is excessively high."""
|
|
561
|
+
from flock.logging.logging import get_logger
|
|
562
|
+
|
|
563
|
+
logger = get_logger(__name__)
|
|
564
|
+
|
|
565
|
+
if n > 100:
|
|
566
|
+
logger.warning(
|
|
567
|
+
f"Agent '{self._agent.name}' has best_of({n}) which is very high. "
|
|
568
|
+
f"Typical values are 3-10. High values increase cost and latency. "
|
|
569
|
+
f"Consider reducing unless you have specific requirements."
|
|
570
|
+
)
|
|
571
|
+
|
|
572
|
+
def _validate_concurrency(self, n: int) -> None:
|
|
573
|
+
"""T074: Warn if max_concurrency is excessively high."""
|
|
574
|
+
from flock.logging.logging import get_logger
|
|
575
|
+
|
|
576
|
+
logger = get_logger(__name__)
|
|
577
|
+
|
|
578
|
+
if n > 1000:
|
|
579
|
+
logger.warning(
|
|
580
|
+
f"Agent '{self._agent.name}' has max_concurrency({n}) which is very high. "
|
|
581
|
+
f"Typical values are 1-50. Excessive concurrency may cause resource issues. "
|
|
582
|
+
f"Consider reducing unless you have specific infrastructure."
|
|
583
|
+
)
|
|
584
|
+
|
|
585
|
+
# Utility --------------------------------------------------------------
|
|
586
|
+
|
|
587
|
+
def _normalize_join(self, value: dict | JoinSpec | None) -> JoinSpec | None:
|
|
588
|
+
if value is None or isinstance(value, JoinSpec):
|
|
589
|
+
return value
|
|
590
|
+
return JoinSpec(
|
|
591
|
+
kind=value.get("kind", "all_of"),
|
|
592
|
+
window=float(value.get("window", 0.0)),
|
|
593
|
+
by=value.get("by"),
|
|
594
|
+
)
|
|
595
|
+
|
|
596
|
+
def _normalize_batch(self, value: dict | BatchSpec | None) -> BatchSpec | None:
|
|
597
|
+
if value is None or isinstance(value, BatchSpec):
|
|
598
|
+
return value
|
|
599
|
+
return BatchSpec(
|
|
600
|
+
size=int(value.get("size", 1)),
|
|
601
|
+
within=float(value.get("within", 0.0)),
|
|
602
|
+
by=value.get("by"),
|
|
603
|
+
)
|
|
604
|
+
|
|
605
|
+
# Properties -----------------------------------------------------------
|
|
606
|
+
|
|
607
|
+
@property
|
|
608
|
+
def name(self) -> str:
|
|
609
|
+
return self._agent.name
|
|
610
|
+
|
|
611
|
+
@property
|
|
612
|
+
def agent(self) -> Agent:
|
|
613
|
+
return self._agent
|
|
614
|
+
|
|
615
|
+
|
|
616
|
+
class PublishBuilder:
|
|
617
|
+
"""Helper returned by `.publishes(...)` to support `.only_for` sugar."""
|
|
618
|
+
|
|
619
|
+
def __init__(self, parent: AgentBuilder, outputs: Sequence[AgentOutput]) -> None:
|
|
620
|
+
self._parent = parent
|
|
621
|
+
self._outputs = list(outputs)
|
|
622
|
+
|
|
623
|
+
def only_for(self, *agent_names: str) -> AgentBuilder:
|
|
624
|
+
visibility = only_for(*agent_names)
|
|
625
|
+
for output in self._outputs:
|
|
626
|
+
output.default_visibility = visibility
|
|
627
|
+
return self._parent
|
|
628
|
+
|
|
629
|
+
def visibility(self, value: Visibility) -> AgentBuilder:
|
|
630
|
+
for output in self._outputs:
|
|
631
|
+
output.default_visibility = value
|
|
632
|
+
return self._parent
|
|
633
|
+
|
|
634
|
+
def __getattr__(self, item):
|
|
635
|
+
return getattr(self._parent, item)
|
|
636
|
+
|
|
637
|
+
|
|
638
|
+
class RunHandle:
|
|
639
|
+
"""Represents a chained run starting from a given agent."""
|
|
640
|
+
|
|
641
|
+
def __init__(self, agent: Agent, inputs: list[BaseModel]) -> None:
|
|
642
|
+
self.agent = agent
|
|
643
|
+
self.inputs = inputs
|
|
644
|
+
self._chain: list[Agent] = [agent]
|
|
645
|
+
|
|
646
|
+
def then(self, builder: AgentBuilder) -> RunHandle:
|
|
647
|
+
self._chain.append(builder.agent)
|
|
648
|
+
return self
|
|
649
|
+
|
|
650
|
+
async def execute(self) -> list[Artifact]:
|
|
651
|
+
orchestrator = self.agent._orchestrator
|
|
652
|
+
artifacts = await orchestrator.direct_invoke(self.agent, self.inputs)
|
|
653
|
+
for agent in self._chain[1:]:
|
|
654
|
+
artifacts = await orchestrator.direct_invoke(agent, artifacts)
|
|
655
|
+
return artifacts
|
|
656
|
+
|
|
657
|
+
|
|
658
|
+
class Pipeline:
|
|
659
|
+
def __init__(self, builders: Sequence[AgentBuilder]) -> None:
|
|
660
|
+
self.builders = list(builders)
|
|
661
|
+
|
|
662
|
+
def then(self, builder: AgentBuilder) -> Pipeline:
|
|
663
|
+
self.builders.append(builder)
|
|
664
|
+
return self
|
|
665
|
+
|
|
666
|
+
async def execute(self) -> list[Artifact]:
|
|
667
|
+
orchestrator = self.builders[0].agent._orchestrator
|
|
668
|
+
artifacts: list[Artifact] = []
|
|
669
|
+
for builder in self.builders:
|
|
670
|
+
inputs = artifacts if artifacts else []
|
|
671
|
+
artifacts = await orchestrator.direct_invoke(builder.agent, inputs)
|
|
672
|
+
return artifacts
|
|
673
|
+
|
|
674
|
+
|
|
675
|
+
__all__ = [
|
|
676
|
+
"Agent",
|
|
677
|
+
"AgentBuilder",
|
|
678
|
+
]
|