flock-core 0.5.0b28__py3-none-any.whl → 0.5.56b0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of flock-core might be problematic. Click here for more details.
- 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/{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.56b0.dist-info/METADATA +747 -0
- flock_core-0.5.56b0.dist-info/RECORD +398 -0
- flock_core-0.5.56b0.dist-info/entry_points.txt +2 -0
- {flock_core-0.5.0b28.dist-info → flock_core-0.5.56b0.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.56b0.dist-info}/WHEEL +0 -0
flock/core/flock_scheduler.py
DELETED
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
# src/flock/core/scheduler.py (new file or inside flock.py)
|
|
2
|
-
import asyncio
|
|
3
|
-
import traceback
|
|
4
|
-
from datetime import datetime, timedelta, timezone
|
|
5
|
-
|
|
6
|
-
from croniter import croniter # pip install croniter
|
|
7
|
-
|
|
8
|
-
from flock.core.flock import Flock
|
|
9
|
-
from flock.core.flock_agent import FlockAgent # For type hinting
|
|
10
|
-
from flock.core.logging.logging import get_logger
|
|
11
|
-
|
|
12
|
-
logger = get_logger("flock.scheduler")
|
|
13
|
-
|
|
14
|
-
class FlockScheduler:
|
|
15
|
-
def __init__(self, flock_instance: Flock):
|
|
16
|
-
self.flock = flock_instance
|
|
17
|
-
self._scheduled_tasks: list[tuple[FlockAgent, croniter | timedelta, datetime | None]] = []
|
|
18
|
-
self._stop_event = asyncio.Event()
|
|
19
|
-
self._is_running = False
|
|
20
|
-
|
|
21
|
-
def _parse_schedule_expression(self, expression: str) -> croniter | timedelta:
|
|
22
|
-
"""Parses a schedule expression.
|
|
23
|
-
Supports cron syntax and simple intervals like 'every Xm', 'every Xh', 'every Xd'.
|
|
24
|
-
"""
|
|
25
|
-
expression = expression.strip().lower()
|
|
26
|
-
if expression.startswith("every "):
|
|
27
|
-
try:
|
|
28
|
-
parts = expression.split(" ")
|
|
29
|
-
value = int(parts[1][:-1])
|
|
30
|
-
unit = parts[1][-1]
|
|
31
|
-
if unit == 's': return timedelta(seconds=value)
|
|
32
|
-
if unit == 'm': return timedelta(minutes=value)
|
|
33
|
-
elif unit == 'h': return timedelta(hours=value)
|
|
34
|
-
elif unit == 'd': return timedelta(days=value)
|
|
35
|
-
else: raise ValueError(f"Invalid time unit: {unit}")
|
|
36
|
-
except Exception as e:
|
|
37
|
-
logger.error(f"Invalid interval expression '{expression}': {e}")
|
|
38
|
-
raise ValueError(f"Invalid interval expression: {expression}") from e
|
|
39
|
-
else:
|
|
40
|
-
# Assume cron expression
|
|
41
|
-
if not croniter.is_valid(expression):
|
|
42
|
-
raise ValueError(f"Invalid cron expression: {expression}")
|
|
43
|
-
return croniter(expression, datetime.now(timezone.utc))
|
|
44
|
-
|
|
45
|
-
def add_agent(self, agent: FlockAgent):
|
|
46
|
-
"""Adds an agent to the scheduler if it has a schedule expression."""
|
|
47
|
-
# Assuming schedule_expression is stored in agent._flock_config or a dedicated field
|
|
48
|
-
schedule_expression = None
|
|
49
|
-
if hasattr(agent, '_flock_config') and isinstance(agent._flock_config, dict):
|
|
50
|
-
schedule_expression = agent._flock_config.get('schedule_expression')
|
|
51
|
-
elif hasattr(agent, 'schedule_expression'): # If directly on agent
|
|
52
|
-
schedule_expression = agent.schedule_expression
|
|
53
|
-
elif hasattr(agent, 'config') and hasattr(agent.config, 'schedule_expression'): # If on agent.config
|
|
54
|
-
schedule_expression = agent.config.schedule_expression
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
if not schedule_expression:
|
|
58
|
-
logger.warning(f"Agent '{agent.name}' has no schedule_expression. Skipping.")
|
|
59
|
-
return
|
|
60
|
-
|
|
61
|
-
try:
|
|
62
|
-
parsed_schedule = self._parse_schedule_expression(schedule_expression)
|
|
63
|
-
self._scheduled_tasks.append((agent, parsed_schedule, None)) # agent, schedule, last_run_utc
|
|
64
|
-
logger.info(f"Scheduled agent '{agent.name}' with expression: {schedule_expression}")
|
|
65
|
-
except ValueError as e:
|
|
66
|
-
logger.error(f"Could not schedule agent '{agent.name}': {e}")
|
|
67
|
-
|
|
68
|
-
def _load_scheduled_agents_from_flock(self):
|
|
69
|
-
"""Scans the Flock instance for agents with scheduling configuration."""
|
|
70
|
-
self._scheduled_tasks = [] # Clear existing before loading
|
|
71
|
-
for agent_name, agent_instance in self.flock.agents.items():
|
|
72
|
-
self.add_agent(agent_instance)
|
|
73
|
-
logger.info(f"Loaded {len(self._scheduled_tasks)} scheduled agents from Flock '{self.flock.name}'.")
|
|
74
|
-
|
|
75
|
-
async def _run_agent_task(self, agent: FlockAgent, trigger_time: datetime):
|
|
76
|
-
logger.info(f"Triggering scheduled agent '{agent.name}' at {trigger_time.isoformat()}")
|
|
77
|
-
try:
|
|
78
|
-
# Input for a scheduled agent could include the trigger time
|
|
79
|
-
await self.flock.run_async(agent=agent.name, input={"trigger_time": trigger_time})
|
|
80
|
-
logger.info(f"Scheduled agent '{agent.name}' finished successfully.")
|
|
81
|
-
except Exception as e:
|
|
82
|
-
logger.error(f"Error running scheduled agent '{agent.name}': {e}\n{traceback.format_exc()}")
|
|
83
|
-
|
|
84
|
-
async def _scheduler_loop(self):
|
|
85
|
-
self._is_running = True
|
|
86
|
-
logger.info("FlockScheduler loop started.")
|
|
87
|
-
while not self._stop_event.is_set():
|
|
88
|
-
now_utc = datetime.now(timezone.utc)
|
|
89
|
-
tasks_to_run_this_cycle = []
|
|
90
|
-
|
|
91
|
-
for i, (agent, schedule, last_run_utc) in enumerate(self._scheduled_tasks):
|
|
92
|
-
should_run = False
|
|
93
|
-
next_run_utc = None
|
|
94
|
-
|
|
95
|
-
if isinstance(schedule, croniter):
|
|
96
|
-
# For cron, get the next scheduled time AFTER the last run (or now if never run)
|
|
97
|
-
base_time = last_run_utc if last_run_utc else now_utc
|
|
98
|
-
# Croniter's get_next gives the next time *after* the base_time.
|
|
99
|
-
# If last_run_utc is None (first run), we check if the *current* now_utc
|
|
100
|
-
# is past the first scheduled time.
|
|
101
|
-
# A simpler check: is `now_utc` >= `schedule.get_next(datetime, base_time=last_run_utc if last_run_utc else now_utc - timedelta(seconds=1))` ?
|
|
102
|
-
# Let's refine croniter check to be more precise.
|
|
103
|
-
# If we are past the *next* scheduled time since *last check*
|
|
104
|
-
if last_run_utc is None: # First run check
|
|
105
|
-
next_run_utc = schedule.get_next(datetime, start_time=now_utc - timedelta(seconds=1)) # Check if first run is due
|
|
106
|
-
if next_run_utc <= now_utc:
|
|
107
|
-
should_run = True
|
|
108
|
-
else:
|
|
109
|
-
next_run_utc = schedule.get_next(datetime, start_time=last_run_utc)
|
|
110
|
-
if next_run_utc <= now_utc:
|
|
111
|
-
should_run = True
|
|
112
|
-
|
|
113
|
-
elif isinstance(schedule, timedelta): # Simple interval
|
|
114
|
-
if last_run_utc is None or (now_utc - last_run_utc >= schedule):
|
|
115
|
-
should_run = True
|
|
116
|
-
next_run_utc = now_utc # Or now_utc + schedule for next interval start
|
|
117
|
-
else:
|
|
118
|
-
next_run_utc = last_run_utc + schedule
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
if should_run:
|
|
122
|
-
tasks_to_run_this_cycle.append(self._run_agent_task(agent, now_utc))
|
|
123
|
-
# Update last_run_utc for this agent *before* awaiting its execution
|
|
124
|
-
# For cron, advance the iterator to the *current* scheduled time that triggered it.
|
|
125
|
-
if isinstance(schedule, croniter):
|
|
126
|
-
current_cron_trigger = schedule.get_current(datetime) # This is the time it *should* have run
|
|
127
|
-
self._scheduled_tasks[i] = (agent, schedule, current_cron_trigger)
|
|
128
|
-
|
|
129
|
-
elif isinstance(schedule, timedelta):
|
|
130
|
-
self._scheduled_tasks[i] = (agent, schedule, now_utc) # Mark as run now
|
|
131
|
-
|
|
132
|
-
if tasks_to_run_this_cycle:
|
|
133
|
-
await asyncio.gather(*tasks_to_run_this_cycle, return_exceptions=True)
|
|
134
|
-
|
|
135
|
-
try:
|
|
136
|
-
# Sleep for a short interval, e.g., 10 seconds, or until stop_event is set
|
|
137
|
-
await asyncio.wait_for(self._stop_event.wait(), timeout=10.0)
|
|
138
|
-
except asyncio.TimeoutError:
|
|
139
|
-
pass # Timeout is normal, means continue loop
|
|
140
|
-
self._is_running = False
|
|
141
|
-
logger.info("FlockScheduler loop stopped.")
|
|
142
|
-
|
|
143
|
-
async def start(self) -> asyncio.Task | None: # Modified to return Task or None
|
|
144
|
-
if self._is_running:
|
|
145
|
-
logger.warning("Scheduler is already running.")
|
|
146
|
-
return None # Or return the existing task if you store it
|
|
147
|
-
|
|
148
|
-
self._load_scheduled_agents_from_flock()
|
|
149
|
-
if not self._scheduled_tasks:
|
|
150
|
-
logger.info("No scheduled agents found. Scheduler will not start a loop task.")
|
|
151
|
-
return None # Return None if no tasks to schedule
|
|
152
|
-
|
|
153
|
-
self._stop_event.clear()
|
|
154
|
-
loop_task = asyncio.create_task(self._scheduler_loop())
|
|
155
|
-
# Store the task if you need to reference it, e.g., for forced cancellation beyond _stop_event
|
|
156
|
-
# self._loop_task = loop_task
|
|
157
|
-
return loop_task # Return the created task
|
|
158
|
-
|
|
159
|
-
async def stop(self):
|
|
160
|
-
if not self._is_running and not self._stop_event.is_set(): # Check if stop already called
|
|
161
|
-
logger.info("Scheduler is not running or already signaled to stop.")
|
|
162
|
-
return
|
|
163
|
-
logger.info("Stopping FlockScheduler...")
|
|
164
|
-
self._stop_event.set()
|
|
165
|
-
# If you stored self._loop_task, you can await it here or in the lifespan manager
|
|
166
|
-
# await self._loop_task # (This might block if loop doesn't exit quickly)
|
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
"""Manages Server-Lifecycles within the larger lifecycle of Flock."""
|
|
2
|
-
|
|
3
|
-
import asyncio
|
|
4
|
-
from contextlib import AsyncExitStack
|
|
5
|
-
|
|
6
|
-
from anyio import Lock
|
|
7
|
-
from pydantic import BaseModel, ConfigDict, Field
|
|
8
|
-
|
|
9
|
-
from flock.core.mcp.flock_mcp_server import FlockMCPServer
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class FlockServerManager(BaseModel):
|
|
13
|
-
"""Async-context-manager to start/stop a set of Flock MCP servers."""
|
|
14
|
-
|
|
15
|
-
servers: list[FlockMCPServer] | None = Field(
|
|
16
|
-
..., exclude=True, description="The servers to manage."
|
|
17
|
-
)
|
|
18
|
-
|
|
19
|
-
stack: AsyncExitStack | None = Field(
|
|
20
|
-
default=None,
|
|
21
|
-
exclude=True,
|
|
22
|
-
description="Central exit stack for managing the execution context of the servers.",
|
|
23
|
-
)
|
|
24
|
-
|
|
25
|
-
lock: Lock | None = Field(
|
|
26
|
-
default=None, exclude=True, description="Global lock for mutex access."
|
|
27
|
-
)
|
|
28
|
-
|
|
29
|
-
model_config = ConfigDict(
|
|
30
|
-
arbitrary_types_allowed=True,
|
|
31
|
-
)
|
|
32
|
-
|
|
33
|
-
def __init__(
|
|
34
|
-
self,
|
|
35
|
-
servers: list[FlockMCPServer] | None = None,
|
|
36
|
-
stack: AsyncExitStack | None = None,
|
|
37
|
-
lock: asyncio.Lock | None = None,
|
|
38
|
-
) -> None:
|
|
39
|
-
"""Initialize the FlockServerManager with optional server, stack, and lock references."""
|
|
40
|
-
super().__init__(
|
|
41
|
-
servers=servers,
|
|
42
|
-
stack=stack,
|
|
43
|
-
lock=lock,
|
|
44
|
-
)
|
|
45
|
-
|
|
46
|
-
def add_server_sync(self, server: FlockMCPServer) -> None:
|
|
47
|
-
"""Add a server to be managed by the ServerManager.
|
|
48
|
-
|
|
49
|
-
Note:
|
|
50
|
-
IT IS CRUCIAL THAT THIS METHOD IS NOT CALLED
|
|
51
|
-
WHEN THE SERVER MANAGER HAS ALREADY BEEN INTIALIZED
|
|
52
|
-
(with server_manager as manager: ...)
|
|
53
|
-
OTHERWISE EXECUTION WILL BREAK DOWN.
|
|
54
|
-
"""
|
|
55
|
-
if self.servers is None:
|
|
56
|
-
self.servers = []
|
|
57
|
-
|
|
58
|
-
self.servers.append(server)
|
|
59
|
-
|
|
60
|
-
def remove_server_sync(self, server: FlockMCPServer) -> None:
|
|
61
|
-
"""Remove a server from the list of managed servers.
|
|
62
|
-
|
|
63
|
-
Note:
|
|
64
|
-
IT IS CRUCIAL THAT THIS METHOD IS NOT CALLED
|
|
65
|
-
WHEN THE SERVER MANAGER HAS ALREADY BEEN INITIALIZED
|
|
66
|
-
(with server_manager as manager: ...)
|
|
67
|
-
OTHERWISE EXECUTION WILL BREAK DOWN.
|
|
68
|
-
"""
|
|
69
|
-
if self.servers and server in self.servers:
|
|
70
|
-
self.servers.remove(server)
|
|
71
|
-
|
|
72
|
-
# -- For future use: Allow adding and removal of servers during runtime ---
|
|
73
|
-
async def add_server_during_runtime(
|
|
74
|
-
self, server: FlockMCPServer
|
|
75
|
-
) -> None:
|
|
76
|
-
"""Add a server to the manager and, if already running, start it immediately."""
|
|
77
|
-
if self.lock is None:
|
|
78
|
-
self.lock = asyncio.Lock()
|
|
79
|
-
|
|
80
|
-
async with self.lock:
|
|
81
|
-
if self.servers is None:
|
|
82
|
-
self.servers = []
|
|
83
|
-
|
|
84
|
-
self.servers.append(server)
|
|
85
|
-
|
|
86
|
-
# If we are already running in async-with, enter the context now
|
|
87
|
-
if self.stack is not None:
|
|
88
|
-
await self.stack.enter_async_context(server)
|
|
89
|
-
|
|
90
|
-
async def remove_server_during_runtime(
|
|
91
|
-
self, server: FlockMCPServer
|
|
92
|
-
) -> None:
|
|
93
|
-
"""Tear down and remove a server from the manager at runtime."""
|
|
94
|
-
if self.lock is None:
|
|
95
|
-
self.lock = asyncio.Lock()
|
|
96
|
-
|
|
97
|
-
retrieved_server: FlockMCPServer | None = None
|
|
98
|
-
|
|
99
|
-
async with self.lock:
|
|
100
|
-
if not self.servers or server not in self.servers:
|
|
101
|
-
return # Skip as to not impede application flow
|
|
102
|
-
else:
|
|
103
|
-
try:
|
|
104
|
-
self.servers.remove(server)
|
|
105
|
-
retrieved_server = server
|
|
106
|
-
except ValueError:
|
|
107
|
-
# The server is not present (a little paranoid at this point, but still...)
|
|
108
|
-
return
|
|
109
|
-
|
|
110
|
-
# tell the server to shut down.
|
|
111
|
-
if retrieved_server:
|
|
112
|
-
# trigger the server's own exit hook (this closes its connection_manager, sessions, tools....)
|
|
113
|
-
await retrieved_server.__aexit__(None, None, None)
|
|
114
|
-
|
|
115
|
-
async def __aenter__(self) -> "FlockServerManager":
|
|
116
|
-
"""Enter the asynchronous context for the server manager."""
|
|
117
|
-
if not self.stack:
|
|
118
|
-
self.stack = AsyncExitStack()
|
|
119
|
-
|
|
120
|
-
if not self.servers:
|
|
121
|
-
self.servers = []
|
|
122
|
-
|
|
123
|
-
if not self.lock:
|
|
124
|
-
self.lock = asyncio.Lock()
|
|
125
|
-
|
|
126
|
-
for srv in self.servers:
|
|
127
|
-
await self.stack.enter_async_context(srv)
|
|
128
|
-
|
|
129
|
-
return self
|
|
130
|
-
|
|
131
|
-
async def __aexit__(self, exc_type, exc, tb) -> None:
|
|
132
|
-
"""Exit the asynchronous context for the server manager."""
|
|
133
|
-
# Unwind the servers in LIFO order
|
|
134
|
-
if self.stack is not None:
|
|
135
|
-
await self.stack.aclose()
|
|
136
|
-
self.stack = None
|