flock-core 0.5.10__py3-none-any.whl → 0.5.20__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 (91) hide show
  1. flock/__init__.py +1 -1
  2. flock/agent/__init__.py +30 -0
  3. flock/agent/builder_helpers.py +192 -0
  4. flock/agent/builder_validator.py +169 -0
  5. flock/agent/component_lifecycle.py +325 -0
  6. flock/agent/context_resolver.py +141 -0
  7. flock/agent/mcp_integration.py +212 -0
  8. flock/agent/output_processor.py +304 -0
  9. flock/api/__init__.py +20 -0
  10. flock/api/models.py +283 -0
  11. flock/{service.py → api/service.py} +121 -63
  12. flock/cli.py +2 -2
  13. flock/components/__init__.py +41 -0
  14. flock/components/agent/__init__.py +22 -0
  15. flock/{components.py → components/agent/base.py} +4 -3
  16. flock/{utility/output_utility_component.py → components/agent/output_utility.py} +12 -7
  17. flock/components/orchestrator/__init__.py +22 -0
  18. flock/{orchestrator_component.py → components/orchestrator/base.py} +5 -293
  19. flock/components/orchestrator/circuit_breaker.py +95 -0
  20. flock/components/orchestrator/collection.py +143 -0
  21. flock/components/orchestrator/deduplication.py +78 -0
  22. flock/core/__init__.py +30 -0
  23. flock/core/agent.py +953 -0
  24. flock/{artifacts.py → core/artifacts.py} +1 -1
  25. flock/{context_provider.py → core/context_provider.py} +3 -3
  26. flock/core/orchestrator.py +1102 -0
  27. flock/{store.py → core/store.py} +99 -454
  28. flock/{subscription.py → core/subscription.py} +1 -1
  29. flock/dashboard/collector.py +5 -5
  30. flock/dashboard/graph_builder.py +7 -7
  31. flock/dashboard/routes/__init__.py +21 -0
  32. flock/dashboard/routes/control.py +327 -0
  33. flock/dashboard/routes/helpers.py +340 -0
  34. flock/dashboard/routes/themes.py +76 -0
  35. flock/dashboard/routes/traces.py +521 -0
  36. flock/dashboard/routes/websocket.py +108 -0
  37. flock/dashboard/service.py +44 -1294
  38. flock/engines/dspy/__init__.py +20 -0
  39. flock/engines/dspy/artifact_materializer.py +216 -0
  40. flock/engines/dspy/signature_builder.py +474 -0
  41. flock/engines/dspy/streaming_executor.py +858 -0
  42. flock/engines/dspy_engine.py +45 -1330
  43. flock/engines/examples/simple_batch_engine.py +2 -2
  44. flock/examples.py +7 -7
  45. flock/logging/logging.py +1 -16
  46. flock/models/__init__.py +10 -0
  47. flock/models/system_artifacts.py +33 -0
  48. flock/orchestrator/__init__.py +45 -0
  49. flock/{artifact_collector.py → orchestrator/artifact_collector.py} +3 -3
  50. flock/orchestrator/artifact_manager.py +168 -0
  51. flock/{batch_accumulator.py → orchestrator/batch_accumulator.py} +2 -2
  52. flock/orchestrator/component_runner.py +389 -0
  53. flock/orchestrator/context_builder.py +167 -0
  54. flock/{correlation_engine.py → orchestrator/correlation_engine.py} +2 -2
  55. flock/orchestrator/event_emitter.py +167 -0
  56. flock/orchestrator/initialization.py +184 -0
  57. flock/orchestrator/lifecycle_manager.py +226 -0
  58. flock/orchestrator/mcp_manager.py +202 -0
  59. flock/orchestrator/scheduler.py +189 -0
  60. flock/orchestrator/server_manager.py +234 -0
  61. flock/orchestrator/tracing.py +147 -0
  62. flock/storage/__init__.py +10 -0
  63. flock/storage/artifact_aggregator.py +158 -0
  64. flock/storage/in_memory/__init__.py +6 -0
  65. flock/storage/in_memory/artifact_filter.py +114 -0
  66. flock/storage/in_memory/history_aggregator.py +115 -0
  67. flock/storage/sqlite/__init__.py +10 -0
  68. flock/storage/sqlite/agent_history_queries.py +154 -0
  69. flock/storage/sqlite/consumption_loader.py +100 -0
  70. flock/storage/sqlite/query_builder.py +112 -0
  71. flock/storage/sqlite/query_params_builder.py +91 -0
  72. flock/storage/sqlite/schema_manager.py +168 -0
  73. flock/storage/sqlite/summary_queries.py +194 -0
  74. flock/utils/__init__.py +14 -0
  75. flock/utils/async_utils.py +67 -0
  76. flock/{runtime.py → utils/runtime.py} +3 -3
  77. flock/utils/time_utils.py +53 -0
  78. flock/utils/type_resolution.py +38 -0
  79. flock/{utilities.py → utils/utilities.py} +2 -2
  80. flock/utils/validation.py +57 -0
  81. flock/utils/visibility.py +79 -0
  82. flock/utils/visibility_utils.py +134 -0
  83. {flock_core-0.5.10.dist-info → flock_core-0.5.20.dist-info}/METADATA +69 -61
  84. {flock_core-0.5.10.dist-info → flock_core-0.5.20.dist-info}/RECORD +89 -31
  85. flock/agent.py +0 -1578
  86. flock/orchestrator.py +0 -1746
  87. /flock/{visibility.py → core/visibility.py} +0 -0
  88. /flock/{helper → utils}/cli_helper.py +0 -0
  89. {flock_core-0.5.10.dist-info → flock_core-0.5.20.dist-info}/WHEEL +0 -0
  90. {flock_core-0.5.10.dist-info → flock_core-0.5.20.dist-info}/entry_points.txt +0 -0
  91. {flock_core-0.5.10.dist-info → flock_core-0.5.20.dist-info}/licenses/LICENSE +0 -0
flock/__init__.py CHANGED
@@ -3,7 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  from flock.cli import main
6
- from flock.orchestrator import Flock, start_orchestrator
6
+ from flock.core import Flock, start_orchestrator
7
7
  from flock.registry import flock_tool, flock_type
8
8
 
9
9
 
@@ -0,0 +1,30 @@
1
+ """Agent implementation modules.
2
+
3
+ This package contains internal implementation details for the Agent class.
4
+
5
+ Phase 5B Additions:
6
+ - BuilderHelpers: PublishBuilder, RunHandle, Pipeline helper classes
7
+ - BuilderValidator: Validation and normalization logic for AgentBuilder
8
+ """
9
+
10
+ from flock.agent.builder_helpers import Pipeline, PublishBuilder, RunHandle
11
+ from flock.agent.builder_validator import BuilderValidator
12
+ from flock.agent.component_lifecycle import ComponentLifecycle
13
+ from flock.agent.context_resolver import ContextResolver
14
+ from flock.agent.mcp_integration import MCPIntegration
15
+ from flock.agent.output_processor import OutputProcessor
16
+ from flock.core.visibility import AgentIdentity
17
+
18
+
19
+ __all__ = [
20
+ "AgentIdentity",
21
+ "BuilderHelpers",
22
+ "BuilderValidator",
23
+ "ComponentLifecycle",
24
+ "ContextResolver",
25
+ "MCPIntegration",
26
+ "OutputProcessor",
27
+ "Pipeline",
28
+ "PublishBuilder",
29
+ "RunHandle",
30
+ ]
@@ -0,0 +1,192 @@
1
+ """Helper classes for AgentBuilder fluent API.
2
+
3
+ Phase 5B: Extracted from agent.py to reduce file size and improve modularity.
4
+
5
+ This module contains three helper classes that support the fluent builder pattern:
6
+ - PublishBuilder: Enables .only_for() and .visibility() configuration sugar
7
+ - RunHandle: Represents chained agent execution (agent.run().then().execute())
8
+ - Pipeline: Represents sequential agent pipeline execution
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from collections.abc import Sequence
14
+ from typing import TYPE_CHECKING, Any
15
+
16
+ from pydantic import BaseModel
17
+
18
+ from flock.core.visibility import Visibility, only_for
19
+
20
+
21
+ if TYPE_CHECKING:
22
+ from flock.core import Agent
23
+ from flock.core.artifacts import Artifact
24
+
25
+
26
+ class PublishBuilder:
27
+ """Helper returned by `.publishes(...)` to support `.only_for` sugar.
28
+
29
+ This class enables method chaining after .publishes() calls, allowing
30
+ conditional visibility configuration and delegation back to AgentBuilder.
31
+
32
+ Examples:
33
+ >>> agent.publishes(Report).only_for("manager", "analyst")
34
+ >>> agent.publishes(Alert).visibility(PrivateVisibility())
35
+ """
36
+
37
+ def __init__(self, parent: Any, outputs: Sequence[Any]) -> None:
38
+ """Initialize PublishBuilder.
39
+
40
+ Args:
41
+ parent: AgentBuilder instance to return to for chaining
42
+ outputs: List of AgentOutput objects to configure
43
+ """
44
+ self._parent = parent
45
+ self._outputs = list(outputs)
46
+
47
+ def only_for(self, *agent_names: str) -> Any:
48
+ """Set visibility to allow only specific agents.
49
+
50
+ Convenience method that creates PrivateVisibility with allowlist.
51
+
52
+ Args:
53
+ *agent_names: Names of agents that can see these outputs
54
+
55
+ Returns:
56
+ Parent AgentBuilder for continued method chaining
57
+
58
+ Example:
59
+ >>> agent.publishes(Report).only_for("manager", "analyst")
60
+ """
61
+ visibility = only_for(*agent_names)
62
+ for output in self._outputs:
63
+ output.default_visibility = visibility
64
+ return self._parent
65
+
66
+ def visibility(self, value: Visibility) -> Any:
67
+ """Set explicit visibility for published outputs.
68
+
69
+ Args:
70
+ value: Visibility instance (PublicVisibility, PrivateVisibility, etc.)
71
+
72
+ Returns:
73
+ Parent AgentBuilder for continued method chaining
74
+
75
+ Example:
76
+ >>> agent.publishes(Report).visibility(TenantVisibility())
77
+ """
78
+ for output in self._outputs:
79
+ output.default_visibility = value
80
+ return self._parent
81
+
82
+ def __getattr__(self, item):
83
+ """Delegate unknown attributes to parent AgentBuilder.
84
+
85
+ This enables seamless chaining like:
86
+ >>> agent.publishes(Report).only_for("alice").consumes(Task)
87
+ """
88
+ return getattr(self._parent, item)
89
+
90
+
91
+ class RunHandle:
92
+ """Represents a chained run starting from a given agent.
93
+
94
+ Enables fluent API for sequential agent execution:
95
+ >>> await agent1.run(input).then(agent2).then(agent3).execute()
96
+
97
+ The chain executes agents in sequence, passing outputs from one to the next.
98
+ """
99
+
100
+ def __init__(self, agent: Agent, inputs: list[BaseModel]) -> None:
101
+ """Initialize RunHandle.
102
+
103
+ Args:
104
+ agent: First agent in the chain
105
+ inputs: Initial inputs to process
106
+ """
107
+ self.agent = agent
108
+ self.inputs = inputs
109
+ self._chain: list[Agent] = [agent]
110
+
111
+ def then(self, builder: Any) -> RunHandle:
112
+ """Add another agent to the execution chain.
113
+
114
+ Args:
115
+ builder: AgentBuilder whose agent to add to chain
116
+
117
+ Returns:
118
+ self for continued chaining
119
+
120
+ Example:
121
+ >>> await agent1.run(task).then(agent2).then(agent3).execute()
122
+ """
123
+ self._chain.append(builder.agent)
124
+ return self
125
+
126
+ async def execute(self) -> list[Artifact]:
127
+ """Execute the agent chain sequentially.
128
+
129
+ Runs each agent in order, passing outputs from one as inputs to the next.
130
+
131
+ Returns:
132
+ Final list of artifacts from the last agent in the chain
133
+
134
+ Example:
135
+ >>> results = await agent1.run(input).then(agent2).execute()
136
+ """
137
+ orchestrator = self.agent._orchestrator
138
+ artifacts = await orchestrator.direct_invoke(self.agent, self.inputs)
139
+ for agent in self._chain[1:]:
140
+ artifacts = await orchestrator.direct_invoke(agent, artifacts)
141
+ return artifacts
142
+
143
+
144
+ class Pipeline:
145
+ """Pipeline of agents executed in sequence.
146
+
147
+ Alternative to RunHandle for building multi-agent pipelines:
148
+ >>> pipeline = Pipeline([agent1, agent2, agent3])
149
+ >>> results = await pipeline.execute()
150
+ """
151
+
152
+ def __init__(self, builders: Sequence[Any]) -> None:
153
+ """Initialize Pipeline.
154
+
155
+ Args:
156
+ builders: Sequence of AgentBuilder instances
157
+ """
158
+ self.builders = list(builders)
159
+
160
+ def then(self, builder: Any) -> Pipeline:
161
+ """Add another agent to the pipeline.
162
+
163
+ Args:
164
+ builder: AgentBuilder to add
165
+
166
+ Returns:
167
+ self for continued chaining
168
+
169
+ Example:
170
+ >>> pipeline = Pipeline([agent1]).then(agent2).then(agent3)
171
+ """
172
+ self.builders.append(builder)
173
+ return self
174
+
175
+ async def execute(self) -> list[Artifact]:
176
+ """Execute all agents in the pipeline sequentially.
177
+
178
+ Returns:
179
+ Final list of artifacts from the last agent
180
+
181
+ Example:
182
+ >>> results = await Pipeline([agent1, agent2, agent3]).execute()
183
+ """
184
+ orchestrator = self.builders[0].agent._orchestrator
185
+ artifacts: list[Artifact] = []
186
+ for builder in self.builders:
187
+ inputs = artifacts if artifacts else []
188
+ artifacts = await orchestrator.direct_invoke(builder.agent, inputs)
189
+ return artifacts
190
+
191
+
192
+ __all__ = ["Pipeline", "PublishBuilder", "RunHandle"]
@@ -0,0 +1,169 @@
1
+ """Validation and normalization logic for AgentBuilder fluent API.
2
+
3
+ Phase 5B: Extracted from agent.py to reduce file size and improve modularity.
4
+
5
+ This module contains validation methods that warn about common configuration issues
6
+ and normalization methods that convert dict-based specs to proper dataclass instances.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from datetime import timedelta
12
+ from typing import TYPE_CHECKING
13
+
14
+ from flock.core.subscription import BatchSpec, JoinSpec
15
+
16
+
17
+ if TYPE_CHECKING:
18
+ from flock.core import Agent
19
+
20
+
21
+ class BuilderValidator:
22
+ """Validation and normalization logic for AgentBuilder configuration.
23
+
24
+ This class provides static methods for:
25
+ - Warning about feedback loop risks (self-trigger detection)
26
+ - Warning about excessive best_of or max_concurrency values
27
+ - Normalizing dict-based JoinSpec and BatchSpec to proper instances
28
+ """
29
+
30
+ @staticmethod
31
+ def validate_self_trigger_risk(agent: Agent) -> None:
32
+ """T074: Warn if agent consumes and publishes same type (feedback loop risk).
33
+
34
+ Detects when an agent subscribes to and publishes the same artifact type,
35
+ which could create infinite feedback loops if not configured carefully.
36
+
37
+ Args:
38
+ agent: Agent instance to validate
39
+ """
40
+ from flock.logging.logging import get_logger
41
+
42
+ logger = get_logger(__name__)
43
+
44
+ # Get types agent consumes
45
+ consuming_types = set()
46
+ for sub in agent.subscriptions:
47
+ consuming_types.update(sub.type_names)
48
+
49
+ # Get types agent publishes
50
+ publishing_types = {
51
+ output.spec.type_name
52
+ for group in agent.output_groups
53
+ for output in group.outputs
54
+ }
55
+
56
+ # Check for overlap
57
+ overlap = consuming_types.intersection(publishing_types)
58
+ if overlap and agent.prevent_self_trigger:
59
+ logger.warning(
60
+ f"Agent '{agent.name}' consumes and publishes {overlap}. "
61
+ f"Feedback loop risk detected. Agent has prevent_self_trigger=True (safe), "
62
+ f"but consider adding filtering: .consumes(Type, where=lambda x: ...) "
63
+ f"or use .prevent_self_trigger(False) for intentional feedback."
64
+ )
65
+
66
+ @staticmethod
67
+ def validate_best_of(agent_name: str, n: int) -> None:
68
+ """T074: Warn if best_of value is excessively high.
69
+
70
+ High best_of values (>100) dramatically increase cost and latency
71
+ by running the same evaluation multiple times.
72
+
73
+ Args:
74
+ agent_name: Name of agent being validated
75
+ n: best_of value to validate
76
+ """
77
+ from flock.logging.logging import get_logger
78
+
79
+ logger = get_logger(__name__)
80
+
81
+ if n > 100:
82
+ logger.warning(
83
+ f"Agent '{agent_name}' has best_of({n}) which is very high. "
84
+ f"Typical values are 3-10. High values increase cost and latency. "
85
+ f"Consider reducing unless you have specific requirements."
86
+ )
87
+
88
+ @staticmethod
89
+ def validate_concurrency(agent_name: str, n: int) -> None:
90
+ """T074: Warn if max_concurrency is excessively high.
91
+
92
+ Excessive concurrency (>1000) may overwhelm resources and cause
93
+ rate limiting, memory issues, or performance degradation.
94
+
95
+ Args:
96
+ agent_name: Name of agent being validated
97
+ n: max_concurrency value to validate
98
+ """
99
+ from flock.logging.logging import get_logger
100
+
101
+ logger = get_logger(__name__)
102
+
103
+ if n > 1000:
104
+ logger.warning(
105
+ f"Agent '{agent_name}' has max_concurrency({n}) which is very high. "
106
+ f"Typical values are 1-50. Excessive concurrency may cause resource issues. "
107
+ f"Consider reducing unless you have specific infrastructure."
108
+ )
109
+
110
+ @staticmethod
111
+ def normalize_join(value: dict | JoinSpec | None) -> JoinSpec | None:
112
+ """Normalize dict-based JoinSpec to proper JoinSpec instance.
113
+
114
+ Converts dict syntax to JoinSpec dataclass for backward compatibility:
115
+ >>> normalize_join({"by": "user_id", "within": 60.0})
116
+ JoinSpec(by="user_id", within=timedelta(seconds=60.0))
117
+
118
+ Args:
119
+ value: Either a JoinSpec instance, a dict with join config, or None
120
+
121
+ Returns:
122
+ JoinSpec instance or None
123
+ """
124
+ if value is None or isinstance(value, JoinSpec):
125
+ return value
126
+
127
+ # Phase 2: New JoinSpec API with 'by' and 'within' (time OR count)
128
+ within_value = value.get("within")
129
+ if isinstance(within_value, (int, float)):
130
+ # Count window or seconds as float - keep as is
131
+ within = (
132
+ int(within_value)
133
+ if isinstance(within_value, int)
134
+ else timedelta(seconds=within_value)
135
+ )
136
+ else:
137
+ # Default to 1 minute time window
138
+ within = timedelta(minutes=1)
139
+
140
+ return JoinSpec(
141
+ by=value["by"], # Required
142
+ within=within,
143
+ )
144
+
145
+ @staticmethod
146
+ def normalize_batch(value: dict | BatchSpec | None) -> BatchSpec | None:
147
+ """Normalize dict-based BatchSpec to proper BatchSpec instance.
148
+
149
+ Converts dict syntax to BatchSpec dataclass for backward compatibility:
150
+ >>> normalize_batch({"size": 10, "within": 5.0})
151
+ BatchSpec(size=10, within=5.0, by=None)
152
+
153
+ Args:
154
+ value: Either a BatchSpec instance, a dict with batch config, or None
155
+
156
+ Returns:
157
+ BatchSpec instance or None
158
+ """
159
+ if value is None or isinstance(value, BatchSpec):
160
+ return value
161
+
162
+ return BatchSpec(
163
+ size=int(value.get("size", 1)),
164
+ within=float(value.get("within", 0.0)),
165
+ by=value.get("by"),
166
+ )
167
+
168
+
169
+ __all__ = ["BuilderValidator"]