flock-core 0.3.6__py3-none-any.whl → 0.3.8__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.

@@ -1,4 +1,3 @@
1
-
2
1
  # Flock v0.3 - Hummingbird
3
2
 
4
3
  We're excited to announce Flock v0.3, codenamed **"Hummingbird"**! This release brings a fundamental redesign of Flock's core architecture, introducing **unprecedented modularity and flexibility** to AI agent development.
@@ -13,17 +12,17 @@ Like a hummingbird, modules are small and nimble code packages. Put enough of th
13
12
 
14
13
  ### Other notable additions:
15
14
  - **CLI Interface** – Flock now has a command-line interface
15
+ - **REST API Server** – Expose your agents via HTTP endpoints
16
16
  - **Color-coded logging** – Better debugging experience
17
17
  - **New examples**
18
18
  - ...and much more!
19
19
 
20
20
  ---
21
21
 
22
- ## Core Changes
22
+ ## Core Changes
23
23
 
24
- ### New Module System
25
- - **Complete redesign** of the module architecture
26
- - Simple yet powerful lifecycle hooks: `initialize`, `pre_evaluate`, `post_evaluate`, `terminate`
24
+ ### New Module System
25
+ - **Pluggable modules system á la FastAPI**
27
26
  - **Easy-to-implement** module interface
28
27
  - **Configuration system** for clean parameter management
29
28
 
@@ -32,15 +31,44 @@ Like a hummingbird, modules are small and nimble code packages. Put enough of th
32
31
  - Built-in support for multiple evaluation strategies:
33
32
  - **Declarative Evaluator** – The default way Flock is designed
34
33
  - **Natural Language Evaluator** – Use "classic" prompting
34
+ - **Zep Evaluator** – Add or query data
35
35
  - **Easily extendable** with custom evaluation approaches
36
36
 
37
- ### FlockFactory
37
+ ### New Router System
38
+ - **Pluggable router system** for dynamic agent chaining
39
+ - Built-in support for multiple routing strategies:
40
+ - **Default Router** – Uses the agent's hand_off property
41
+ - **LLM Router** – Uses an LLM to determine the next agent
42
+ - **Agent Router** – Uses a dedicated agent to make routing decisions
43
+ - **Easily extendable** with custom routing approaches
44
+
45
+ ### REST API Server
46
+ - **FastAPI-based** HTTP server for exposing agents
47
+ - **Synchronous and asynchronous** execution modes
48
+ - **Run status tracking** with unique run IDs
49
+ - **Agent discovery** endpoint to list available agents
50
+ - **Simple integration** with existing Flock instances
51
+
52
+ ### Auto-Handoff Feature
53
+ - **Dynamic agent chaining** without explicit handoff definitions
54
+ - **LLM-powered routing** to determine the best next agent
55
+ - **Emergent behavior** in multi-agent systems
56
+ - **Simple to use** with the "auto_handoff" string value
57
+
58
+ ### New high end examples like the Repository Analyzer
59
+ - **Automatic documentation generation** for any codebase
60
+ - **Rule-based version** using custom evaluators
61
+ - **LLM-based version** for more flexible and powerful analysis
62
+ - **Comprehensive documentation** including overview, architecture, components, and more
63
+
64
+ ### FlockFactory
38
65
  - Provides **pre-configured agents**, so you don't have to manage modules and evaluators manually!
39
66
 
40
67
  ### Built-in Modules
41
68
  - **Memory Module** – Persistent agent memory
42
69
  - **Output Module** – Advanced output formatting and storage
43
70
  - **Metrics Module** – Detailed performance tracking
71
+ - **Zep Module** – Uses Zep for Knowledge Graphs
44
72
 
45
73
  ---
46
74
 
@@ -99,7 +127,7 @@ See? **Basically nothing changed!** Just more modular and flexible.
99
127
 
100
128
  ---
101
129
 
102
- ## 🛠 Installation
130
+ ## Installation
103
131
 
104
132
  ```bash
105
133
  pip install flock-core>=0.3.0
@@ -107,5 +135,5 @@ pip install flock-core>=0.3.0
107
135
 
108
136
  ---
109
137
 
110
- **Full documentation**: [docs.flock.ai](https://docs.flock.ai)
111
- **GitHub**: [github.com/flock-ai](https://github.com/flock-ai)
138
+ **Full documentation**: [https://whiteducksoftware.github.io/flock](https://whiteducksoftware.github.io/flock)
139
+ **GitHub**: [https://github.com/whiteducksoftware/flock](https://github.com/whiteducksoftware/flock)
flock/core/flock_agent.py CHANGED
@@ -5,15 +5,15 @@ import json
5
5
  import os
6
6
  from abc import ABC
7
7
  from collections.abc import Callable
8
- from typing import Any, TypeVar, Union
8
+ from typing import Any, TypeVar
9
9
 
10
10
  import cloudpickle
11
11
  from opentelemetry import trace
12
12
  from pydantic import BaseModel, Field
13
13
 
14
- from flock.core.context.context import FlockContext
15
14
  from flock.core.flock_evaluator import FlockEvaluator
16
15
  from flock.core.flock_module import FlockModule
16
+ from flock.core.flock_router import FlockRouter
17
17
  from flock.core.logging.logging import get_logger
18
18
 
19
19
  logger = get_logger("agent")
@@ -23,21 +23,6 @@ tracer = trace.get_tracer(__name__)
23
23
  T = TypeVar("T", bound="FlockAgent")
24
24
 
25
25
 
26
- class HandOff(BaseModel):
27
- """Base class for handoff returns."""
28
-
29
- next_agent: Union[str, "FlockAgent"] = Field(
30
- default="", description="Next agent to invoke"
31
- )
32
- input: dict[str, Any] = Field(
33
- default_factory=dict,
34
- description="Input data for the next agent",
35
- )
36
- context: FlockContext = Field(
37
- default=None, descrio="Override context parameters"
38
- )
39
-
40
-
41
26
  class FlockAgent(BaseModel, ABC):
42
27
  name: str = Field(..., description="Unique identifier for the agent.")
43
28
  model: str | None = Field(
@@ -72,12 +57,9 @@ class FlockAgent(BaseModel, ABC):
72
57
  description="Set to True to enable caching of the agent's results.",
73
58
  )
74
59
 
75
- hand_off: str | HandOff | Callable[..., HandOff] | None = Field(
76
- None,
77
- description=(
78
- "Specifies the next agent in the workflow or a callable that determines the handoff. "
79
- "This allows chaining of agents."
80
- ),
60
+ handoff_router: FlockRouter | None = Field(
61
+ default=None,
62
+ description="Router to use for determining the next agent in the workflow.",
81
63
  )
82
64
 
83
65
  evaluator: FlockEvaluator = Field(
@@ -3,14 +3,17 @@
3
3
  from collections.abc import Callable
4
4
  from typing import Any
5
5
 
6
- from flock.core.flock_agent import FlockAgent, HandOff
6
+ from flock.core.flock_agent import FlockAgent
7
7
  from flock.core.logging.formatters.themes import OutputTheme
8
8
  from flock.evaluators.declarative.declarative_evaluator import (
9
9
  DeclarativeEvaluator,
10
10
  DeclarativeEvaluatorConfig,
11
11
  )
12
- from flock.modules.performance.metrics_module import MetricsModule, MetricsModuleConfig
13
12
  from flock.modules.output.output_module import OutputModule, OutputModuleConfig
13
+ from flock.modules.performance.metrics_module import (
14
+ MetricsModule,
15
+ MetricsModuleConfig,
16
+ )
14
17
 
15
18
 
16
19
  class FlockFactory:
@@ -24,7 +27,6 @@ class FlockFactory:
24
27
  input: str | Callable[..., str] | None = None,
25
28
  output: str | Callable[..., str] | None = None,
26
29
  tools: list[Callable[..., Any] | Any] | None = None,
27
- hand_off: str | HandOff | Callable[..., HandOff] | None = None,
28
30
  use_cache: bool = True,
29
31
  enable_rich_tables: bool = False,
30
32
  output_theme: OutputTheme = OutputTheme.abernathy,
@@ -53,7 +55,6 @@ class FlockFactory:
53
55
  input=input,
54
56
  output=output,
55
57
  tools=tools,
56
- hand_off=hand_off,
57
58
  model=model,
58
59
  description=description,
59
60
  evaluator=evaluator,
@@ -65,7 +66,9 @@ class FlockFactory:
65
66
  )
66
67
  output_module = OutputModule("output", config=output_config)
67
68
 
68
- metrics_config = MetricsModuleConfig(latency_threshold_ms=alert_latency_threshold_ms)
69
+ metrics_config = MetricsModuleConfig(
70
+ latency_threshold_ms=alert_latency_threshold_ms
71
+ )
69
72
  metrics_module = MetricsModule("metrics", config=metrics_config)
70
73
 
71
74
  agent.add_module(output_module)
@@ -43,7 +43,9 @@ class FlockModule(BaseModel, ABC):
43
43
  2. Using FlockModuleConfig.with_fields() to create a config class
44
44
  """
45
45
 
46
- name: str = Field(..., description="Unique identifier for the module")
46
+ name: str = Field(
47
+ default="", description="Unique identifier for the module"
48
+ )
47
49
  config: FlockModuleConfig = Field(
48
50
  default_factory=FlockModuleConfig, description="Module configuration"
49
51
  )
@@ -0,0 +1,70 @@
1
+ """Base router class for the Flock framework."""
2
+
3
+ from abc import ABC, abstractmethod
4
+ from typing import Any, Literal
5
+
6
+ from pydantic import BaseModel, Field
7
+
8
+ from flock.core.context.context import FlockContext
9
+
10
+
11
+ class HandOffRequest(BaseModel):
12
+ """Base class for handoff returns."""
13
+
14
+ next_agent: str = Field(default="", description="Next agent to invoke")
15
+ # match = use the output fields of the current agent that also exists as input field of the next agent
16
+ # add = add the output of the current agent to the input of the next agent
17
+ hand_off_mode: Literal["match", "add"] = Field(default="match")
18
+ override_next_agent: Any | None = Field(
19
+ default=None,
20
+ description="Override the next agent to hand off to",
21
+ )
22
+ override_context: FlockContext | None = Field(
23
+ default=None, descrio="Override context parameters"
24
+ )
25
+
26
+
27
+ class FlockRouterConfig(BaseModel):
28
+ """Configuration for a router.
29
+
30
+ This class defines the configuration parameters for a router.
31
+ Subclasses can extend this to add additional parameters.
32
+ """
33
+
34
+ enabled: bool = Field(
35
+ default=True, description="Whether the router is enabled"
36
+ )
37
+ agents: list[str] | None = Field(
38
+ default=None,
39
+ description="List of agents to choose from",
40
+ )
41
+
42
+
43
+ class FlockRouter(BaseModel, ABC):
44
+ """Base class for all routers.
45
+
46
+ A router is responsible for determining the next agent in a workflow
47
+ based on the current agent's output.
48
+ """
49
+
50
+ name: str = Field(..., description="Name of the router")
51
+ config: FlockRouterConfig = Field(default_factory=FlockRouterConfig)
52
+
53
+ @abstractmethod
54
+ async def route(
55
+ self,
56
+ current_agent: Any,
57
+ result: dict[str, Any],
58
+ context: FlockContext,
59
+ ) -> HandOffRequest:
60
+ """Determine the next agent to hand off to based on the current agent's output.
61
+
62
+ Args:
63
+ current_agent: The agent that just completed execution
64
+ result: The output from the current agent
65
+ context: The global execution context
66
+
67
+ Returns:
68
+ A HandOff object containing the next agent and input data
69
+ """
70
+ pass
@@ -40,6 +40,8 @@ class ZepEvaluator(FlockEvaluator, DSPyIntegrationMixin, PromptParserMixin):
40
40
  zep_api_key=self.config.zep_api_key,
41
41
  zep_url=self.config.zep_url,
42
42
  min_fact_rating=self.config.min_fact_rating,
43
+ enable_read=True,
44
+ enable_write=True,
43
45
  ),
44
46
  )
45
47
  client = zep.get_client()
@@ -0,0 +1 @@
1
+ """Routers for the Flock framework."""
@@ -0,0 +1 @@
1
+ """Agent-based router implementation for the Flock framework."""
@@ -0,0 +1,234 @@
1
+ """Agent-based router implementation for the Flock framework."""
2
+
3
+ from typing import Any
4
+
5
+ from flock.core.context.context import FlockContext
6
+ from flock.core.flock_agent import FlockAgent
7
+ from flock.core.flock_router import (
8
+ FlockRouter,
9
+ FlockRouterConfig,
10
+ HandOffRequest,
11
+ )
12
+ from flock.core.logging.formatters.themes import OutputTheme
13
+ from flock.core.logging.logging import get_logger
14
+ from flock.evaluators.declarative.declarative_evaluator import (
15
+ DeclarativeEvaluator,
16
+ DeclarativeEvaluatorConfig,
17
+ )
18
+ from flock.modules.output.output_module import OutputModule, OutputModuleConfig
19
+ from flock.routers.agent.handoff_agent import (
20
+ AgentInfo,
21
+ HandoffAgent,
22
+ )
23
+
24
+ logger = get_logger("agent_router")
25
+
26
+
27
+ class AgentRouterConfig(FlockRouterConfig):
28
+ """Configuration for the agent router.
29
+
30
+ This class extends FlockRouterConfig with parameters specific to the agent router.
31
+ """
32
+
33
+ with_output: bool = False
34
+ confidence_threshold: float = 0.5 # No additional parameters needed for now
35
+
36
+
37
+ class AgentRouter(FlockRouter):
38
+ """Router that uses a FlockAgent to determine the next agent in a workflow.
39
+
40
+ This class is responsible for:
41
+ 1. Creating and managing a HandoffAgent
42
+ 2. Analyzing available agents in the registry
43
+ 3. Using the HandoffAgent to determine the best next agent
44
+ 4. Creating a HandOff object with the selected agent
45
+ """
46
+
47
+ def __init__(
48
+ self,
49
+ name: str = "agent_router",
50
+ config: AgentRouterConfig | None = None,
51
+ ):
52
+ """Initialize the AgentRouter.
53
+
54
+ Args:
55
+ registry: The agent registry containing all available agents
56
+ name: The name of the router
57
+ config: The router configuration
58
+ """
59
+ super().__init__(
60
+ name=name, config=config or AgentRouterConfig(name=name)
61
+ )
62
+
63
+ async def route(
64
+ self,
65
+ current_agent: FlockAgent,
66
+ result: dict[str, Any],
67
+ context: FlockContext,
68
+ ) -> HandOffRequest:
69
+ """Determine the next agent to hand off to based on the current agent's output.
70
+
71
+ Args:
72
+ current_agent: The agent that just completed execution
73
+ result: The output from the current agent
74
+ context: The global execution context
75
+
76
+ Returns:
77
+ A HandOff object containing the next agent and input data
78
+ """
79
+ # Get all available agents from context.agent_definitions
80
+ agent_definitions = context.agent_definitions
81
+ handoff_agent = HandoffAgent(model=current_agent.model)
82
+ handoff_agent.evaluator = DeclarativeEvaluator(
83
+ name="evaluator",
84
+ config=DeclarativeEvaluatorConfig(
85
+ model=current_agent.model,
86
+ use_cache=True,
87
+ max_tokens=1000,
88
+ temperature=0.0,
89
+ ),
90
+ )
91
+ if self.config.with_output:
92
+ handoff_agent.add_module(
93
+ OutputModule(
94
+ name="output",
95
+ config=OutputModuleConfig(
96
+ theme=OutputTheme.abernathy,
97
+ ),
98
+ )
99
+ )
100
+ available_agents = self._get_available_agents(
101
+ agent_definitions, current_agent.name
102
+ )
103
+
104
+ if not available_agents:
105
+ logger.warning("No available agents for agent-based routing")
106
+ return HandOffRequest(
107
+ next_agent="",
108
+ hand_off_mode="add",
109
+ override_next_agent=None,
110
+ override_context=None,
111
+ )
112
+
113
+ # Prepare input for the handoff agent
114
+ handoff_input = {
115
+ "current_agent_name": current_agent.name,
116
+ "current_agent_description": current_agent.description,
117
+ "current_agent_input": current_agent.input,
118
+ "current_agent_output": current_agent.output,
119
+ "current_result": result,
120
+ "available_agents": available_agents,
121
+ }
122
+
123
+ try:
124
+ # Run the handoff agent to determine the next agent
125
+ handoff_result = await handoff_agent.run_async(handoff_input)
126
+
127
+ # Extract the decision
128
+ next_agent_name = handoff_result.get("agent_name")
129
+ confidence = handoff_result.get("confidence")
130
+ reasoning = handoff_result.get("reasoning")
131
+ logger.info(
132
+ f"Agent router selected agent '{next_agent_name}' with confidence {confidence} and reasoning: {reasoning}"
133
+ )
134
+
135
+ if confidence < self.config.confidence_threshold:
136
+ logger.info(
137
+ f"No suitable next agent found (best score: {confidence})"
138
+ )
139
+ return HandOffRequest(
140
+ next_agent="",
141
+ hand_off_mode="add",
142
+ override_next_agent=None,
143
+ override_context=None,
144
+ )
145
+
146
+ next_agent = agent_definitions.get(next_agent_name)
147
+ if not next_agent:
148
+ logger.error(
149
+ f"Selected agent '{next_agent_name}' not found in agent definitions"
150
+ )
151
+ return HandOffRequest(
152
+ next_agent="",
153
+ hand_off_mode="add",
154
+ override_next_agent=None,
155
+ override_context=None,
156
+ )
157
+
158
+ logger.info(
159
+ f"Agent router selected agent '{next_agent_name}' with confidence {confidence}"
160
+ )
161
+ return HandOffRequest(
162
+ next_agent=next_agent_name,
163
+ hand_off_mode="add",
164
+ override_next_agent=None,
165
+ override_context=None,
166
+ )
167
+
168
+ except Exception as e:
169
+ logger.error(f"Error in agent-based routing: {e}")
170
+ return HandOffRequest(
171
+ next_agent="",
172
+ hand_off_mode="add",
173
+ override_next_agent=None,
174
+ override_context=None,
175
+ )
176
+
177
+ def _get_available_agents(
178
+ self, agent_definitions: dict[str, Any], current_agent_name: str
179
+ ) -> list[AgentInfo]:
180
+ """Get all available agents except the current one and the handoff agent.
181
+
182
+ Args:
183
+ agent_definitions: Dictionary of available agents
184
+ current_agent_name: Name of the current agent to exclude
185
+
186
+ Returns:
187
+ List of available agents as AgentInfo objects
188
+ """
189
+ agents = []
190
+ for agent_name in agent_definitions:
191
+ if agent_name != current_agent_name:
192
+ agent = agent_definitions[agent_name]
193
+ agent_info = AgentInfo(
194
+ name=agent_name,
195
+ description=agent.agent_data["description"]
196
+ if agent.agent_data["description"]
197
+ else "",
198
+ input_schema=agent.agent_data["input"],
199
+ output_schema=agent.agent_data["output"],
200
+ )
201
+ agents.append(agent_info)
202
+ return agents
203
+
204
+ def _get_schema_from_agent(
205
+ self, agent: Any, schema_type: str
206
+ ) -> dict[str, Any]:
207
+ """Extract input or output schema from an agent.
208
+
209
+ Args:
210
+ agent: The agent to extract schema from
211
+ schema_type: Either "input" or "output"
212
+
213
+ Returns:
214
+ Dictionary representation of the schema
215
+ """
216
+ schema = {}
217
+ schema_str = agent.agent_data.get(schema_type, "")
218
+
219
+ # Parse the schema string to extract field names, types, and descriptions
220
+ if schema_str:
221
+ fields = schema_str.split(",")
222
+ for field in fields:
223
+ field = field.strip()
224
+ if ":" in field:
225
+ name, rest = field.split(":", 1)
226
+ name = name.strip()
227
+ schema[name] = rest.strip()
228
+ else:
229
+ schema[field] = "Any"
230
+
231
+ return schema
232
+
233
+ # The _create_next_input method is no longer needed since we're using hand_off_mode="add"
234
+ # instead of manually preparing inputs for the next agent
@@ -0,0 +1,58 @@
1
+ """Handoff agent for the agent-based router."""
2
+
3
+ from pydantic import BaseModel
4
+
5
+ from flock.core.flock_agent import FlockAgent
6
+
7
+
8
+ class AgentInfo(BaseModel):
9
+ """Information about an agent for handoff decisions."""
10
+
11
+ name: str
12
+ description: str = ""
13
+ input_schema: str = ""
14
+ output_schema: str = ""
15
+
16
+
17
+ class HandoffDecision(BaseModel):
18
+ """Decision about which agent to hand off to."""
19
+
20
+ agent_name: str
21
+ confidence: float
22
+ reasoning: str
23
+
24
+
25
+ class HandoffAgent(FlockAgent):
26
+ """Agent that decides which agent to hand off to next.
27
+
28
+ This agent analyzes the current agent's output and available agents
29
+ to determine the best next agent in the workflow.
30
+ """
31
+
32
+ def __init__(
33
+ self,
34
+ name: str = "handoff_agent",
35
+ model: str | None = None,
36
+ description: str = "Decides which agent to hand off to next",
37
+ ):
38
+ """Initialize the HandoffAgent.
39
+
40
+ Args:
41
+ name: The name of the agent
42
+ model: The model to use (e.g., 'openai/gpt-4o')
43
+ description: A human-readable description of the agent
44
+ """
45
+ super().__init__(
46
+ name=name,
47
+ model=model,
48
+ description=description,
49
+ input=(
50
+ "current_agent_name: str | Name of the current agent, "
51
+ "current_agent_description: str | Description of the current agent, "
52
+ "current_agent_input: str | Input schema of the current agent, "
53
+ "current_agent_output: str | Output schema of the current agent, "
54
+ "current_result: dict | Output from the current agent, "
55
+ "available_agents: list[AgentInfo] | List of available agents"
56
+ ),
57
+ output="agent_name: str | Name of the agent to hand off to, confidence: float | Confidence in the decision, reasoning: str | Reasoning for the decision",
58
+ )
@@ -0,0 +1 @@
1
+ """Default router implementation for the Flock framework."""
@@ -0,0 +1,76 @@
1
+ """Default router implementation for the Flock framework."""
2
+
3
+ from collections.abc import Callable
4
+ from typing import Any
5
+
6
+ from pydantic import Field
7
+
8
+ from flock.core.context.context import FlockContext
9
+ from flock.core.flock_agent import FlockAgent
10
+ from flock.core.flock_router import (
11
+ FlockRouter,
12
+ FlockRouterConfig,
13
+ HandOffRequest,
14
+ )
15
+ from flock.core.logging.logging import get_logger
16
+
17
+ logger = get_logger("default_router")
18
+
19
+
20
+ class DefaultRouterConfig(FlockRouterConfig):
21
+ """Configuration for the default router."""
22
+
23
+ hand_off: str | HandOffRequest | Callable[..., HandOffRequest] = Field(
24
+ default="", description="Next agent to hand off to"
25
+ )
26
+
27
+
28
+ class DefaultRouter(FlockRouter):
29
+ """Default router implementation.
30
+
31
+ This router simply uses the agent's hand_off property to determine the next agent.
32
+ It does not perform any dynamic routing.
33
+ """
34
+
35
+ name: str = "default_router"
36
+ config: DefaultRouterConfig = Field(
37
+ default_factory=DefaultRouterConfig, description="Output configuration"
38
+ )
39
+
40
+ def __init__(
41
+ self,
42
+ name: str = "default_router",
43
+ config: DefaultRouterConfig | None = None,
44
+ ):
45
+ """Initialize the DefaultRouter.
46
+
47
+ Args:
48
+ name: The name of the router
49
+ config: The router configuration
50
+ """
51
+ super().__init__(
52
+ name=name, config=config or DefaultRouterConfig(name=name)
53
+ )
54
+
55
+ async def route(
56
+ self,
57
+ current_agent: FlockAgent,
58
+ result: dict[str, Any],
59
+ context: FlockContext,
60
+ ) -> HandOffRequest:
61
+ """Determine the next agent to hand off to based on the current agent's output.
62
+
63
+ Args:
64
+ current_agent: The agent that just completed execution
65
+ result: The output from the current agent
66
+ context: The global execution context
67
+
68
+ Returns:
69
+ A HandOff object containing the next agent and input data
70
+ """
71
+ handoff = self.config.hand_off
72
+ if callable(handoff):
73
+ handoff = handoff(context, result)
74
+ if isinstance(handoff, str):
75
+ handoff = HandOffRequest(next_agent=handoff, hand_off_mode="match")
76
+ return handoff
@@ -0,0 +1 @@
1
+ """LLM-based router implementation for the Flock framework."""
@@ -0,0 +1,363 @@
1
+ """LLM-based router implementation for the Flock framework."""
2
+
3
+ import json
4
+ from typing import Any
5
+
6
+ import litellm
7
+
8
+ from flock.core.context.context import FlockContext
9
+ from flock.core.flock_agent import FlockAgent
10
+ from flock.core.flock_router import (
11
+ FlockRouter,
12
+ FlockRouterConfig,
13
+ HandOffRequest,
14
+ )
15
+ from flock.core.logging.logging import get_logger
16
+
17
+ logger = get_logger("llm_router")
18
+
19
+
20
+ class LLMRouterConfig(FlockRouterConfig):
21
+ """Configuration for the LLM router.
22
+
23
+ This class extends FlockRouterConfig with parameters specific to the LLM router.
24
+ """
25
+
26
+ temperature: float = 0.2
27
+ max_tokens: int = 500
28
+ confidence_threshold: float = 0.5
29
+ prompt: str = ""
30
+
31
+
32
+ class LLMRouter(FlockRouter):
33
+ """Router that uses an LLM to determine the next agent in a workflow.
34
+
35
+ This class is responsible for:
36
+ 1. Analyzing available agents in the registry
37
+ 2. Using an LLM to score each agent's suitability as the next step
38
+ 3. Selecting the highest-scoring agent
39
+ 4. Creating a HandOff object with the selected agent
40
+ """
41
+
42
+ def __init__(
43
+ self,
44
+ name: str = "llm_router",
45
+ config: LLMRouterConfig | None = None,
46
+ ):
47
+ """Initialize the LLMRouter.
48
+
49
+ Args:
50
+ registry: The agent registry containing all available agents
51
+ name: The name of the router
52
+ config: The router configuration
53
+ """
54
+ logger.info(f"Initializing LLM Router '{name}'")
55
+ super().__init__(name=name, config=config or LLMRouterConfig(name=name))
56
+ logger.debug(
57
+ "LLM Router configuration",
58
+ temperature=self.config.temperature,
59
+ max_tokens=self.config.max_tokens,
60
+ )
61
+
62
+ async def route(
63
+ self,
64
+ current_agent: FlockAgent,
65
+ result: dict[str, Any],
66
+ context: FlockContext,
67
+ ) -> HandOffRequest:
68
+ """Determine the next agent to hand off to based on the current agent's output.
69
+
70
+ Args:
71
+ current_agent: The agent that just completed execution
72
+ result: The output from the current agent
73
+ context: The global execution context
74
+
75
+ Returns:
76
+ A HandOff object containing the next agent and input data
77
+ """
78
+ logger.info(
79
+ f"Routing from agent '{current_agent.name}'",
80
+ current_agent=current_agent.name,
81
+ )
82
+ logger.debug("Current agent result", result=result)
83
+
84
+ agent_definitions = context.agent_definitions
85
+ # Get all available agents from the registry
86
+ available_agents = self._get_available_agents(
87
+ agent_definitions, current_agent.name
88
+ )
89
+ logger.debug(
90
+ "Available agents for routing",
91
+ count=len(available_agents),
92
+ agents=[a.agent_data["name"] for a in available_agents],
93
+ )
94
+
95
+ if not available_agents:
96
+ logger.warning(
97
+ "No available agents for routing",
98
+ current_agent=current_agent.name,
99
+ )
100
+ return HandOffRequest(
101
+ next_agent="", override_next_agent={}, override_context=None
102
+ )
103
+
104
+ # Use LLM to determine the best next agent
105
+ next_agent_name, score = await self._select_next_agent(
106
+ current_agent, result, available_agents
107
+ )
108
+ logger.info(
109
+ "Agent selection result",
110
+ next_agent=next_agent_name,
111
+ score=score,
112
+ )
113
+
114
+ if not next_agent_name or score < self.config.confidence_threshold:
115
+ logger.warning(
116
+ "No suitable next agent found",
117
+ best_score=score,
118
+ )
119
+ return HandOffRequest(
120
+ next_agent="", override_next_agent={}, override_context=None
121
+ )
122
+
123
+ # Get the next agent from the registry
124
+ next_agent = agent_definitions.get(next_agent_name)
125
+ if not next_agent:
126
+ logger.error(
127
+ "Selected agent not found in registry",
128
+ agent_name=next_agent_name,
129
+ )
130
+ return HandOffRequest(
131
+ next_agent="", override_next_agent={}, override_context=None
132
+ )
133
+
134
+ # Create input for the next agent
135
+
136
+ logger.success(
137
+ f"Successfully routed to agent '{next_agent_name}'",
138
+ score=score,
139
+ from_agent=current_agent.name,
140
+ )
141
+ return HandOffRequest(
142
+ next_agent=next_agent_name,
143
+ hand_off_mode="add",
144
+ override_next_agent=None,
145
+ override_context=None,
146
+ )
147
+
148
+ def _get_available_agents(
149
+ self, agent_definitions: dict[str, Any], current_agent_name: str
150
+ ) -> list[FlockAgent]:
151
+ """Get all available agents except the current one.
152
+
153
+ Args:
154
+ current_agent_name: Name of the current agent to exclude
155
+
156
+ Returns:
157
+ List of available agents
158
+ """
159
+ logger.debug(
160
+ "Getting available agents",
161
+ total_agents=len(agent_definitions),
162
+ current_agent=current_agent_name,
163
+ )
164
+ agents = []
165
+ for agent in agent_definitions:
166
+ if agent != current_agent_name:
167
+ agents.append(agent_definitions.get(agent))
168
+ return agents
169
+
170
+ async def _select_next_agent(
171
+ self,
172
+ current_agent: FlockAgent,
173
+ result: dict[str, Any],
174
+ available_agents: list[FlockAgent],
175
+ ) -> tuple[str, float]:
176
+ """Use an LLM to select the best next agent.
177
+
178
+ Args:
179
+ current_agent: The agent that just completed execution
180
+ result: The output from the current agent
181
+ available_agents: List of available agents to choose from
182
+
183
+ Returns:
184
+ Tuple of (selected_agent_name, confidence_score)
185
+ """
186
+ logger.debug(
187
+ "Selecting next agent",
188
+ current_agent=current_agent.name,
189
+ available_count=len(available_agents),
190
+ )
191
+
192
+ # Prepare the prompt for the LLM
193
+ prompt = self._create_selection_prompt(
194
+ current_agent, result, available_agents
195
+ )
196
+ logger.debug("Generated selection prompt", prompt_length=len(prompt))
197
+
198
+ try:
199
+ logger.info(
200
+ "Calling LLM for agent selection",
201
+ model=current_agent.model,
202
+ temperature=self.config.temperature,
203
+ )
204
+ # Call the LLM to get the next agent
205
+ response = await litellm.acompletion(
206
+ model=current_agent.model,
207
+ messages=[{"role": "user", "content": prompt}],
208
+ temperature=self.config.temperature
209
+ if isinstance(self.config, LLMRouterConfig)
210
+ else 0.2,
211
+ max_tokens=self.config.max_tokens
212
+ if isinstance(self.config, LLMRouterConfig)
213
+ else 500,
214
+ )
215
+
216
+ content = response.choices[0].message.content
217
+ # Parse the response to get the agent name and score
218
+ try:
219
+ # extract the json object from the response
220
+ content = content.split("```json")[1].split("```")[0]
221
+ data = json.loads(content)
222
+ next_agent = data.get("next_agent", "")
223
+ score = float(data.get("score", 0))
224
+ reasoning = data.get("reasoning", "")
225
+ logger.info(
226
+ "Successfully parsed LLM response",
227
+ next_agent=next_agent,
228
+ score=score,
229
+ reasoning=reasoning,
230
+ )
231
+ return next_agent, score
232
+ except (json.JSONDecodeError, ValueError) as e:
233
+ logger.error(
234
+ "Failed to parse LLM response",
235
+ error=str(e),
236
+ raw_response=content,
237
+ )
238
+ logger.debug("Attempting fallback parsing")
239
+
240
+ # Fallback: try to extract the agent name from the text
241
+ for agent in available_agents:
242
+ if agent.agent_data["name"] in content:
243
+ logger.info(
244
+ "Found agent name in response using fallback",
245
+ agent=agent.agent_data["name"],
246
+ )
247
+ return agent.agent_data[
248
+ "name"
249
+ ], 0.6 # Default score for fallback
250
+
251
+ return "", 0.0
252
+
253
+ except Exception as e:
254
+ logger.error(
255
+ "Error calling LLM for agent selection",
256
+ error=str(e),
257
+ current_agent=current_agent.name,
258
+ )
259
+ return "", 0.0
260
+
261
+ def _create_selection_prompt(
262
+ self,
263
+ current_agent: FlockAgent,
264
+ result: dict[str, Any],
265
+ available_agents: list[FlockAgent],
266
+ ) -> str:
267
+ """Create a prompt for the LLM to select the next agent.
268
+
269
+ Args:
270
+ current_agent: The agent that just completed execution
271
+ result: The output from the current agent
272
+ available_agents: List of available agents to choose from
273
+
274
+ Returns:
275
+ Prompt string for the LLM
276
+ """
277
+ # Format the current agent's output
278
+ result_str = json.dumps(result, indent=2)
279
+
280
+ # Format the available agents' information
281
+ agents_info = []
282
+ for agent in available_agents:
283
+ agent_info = {
284
+ "name": agent.agent_data["name"],
285
+ "description": agent.agent_data["description"]
286
+ if agent.agent_data["description"]
287
+ else "",
288
+ "input": agent.agent_data["input"],
289
+ "output": agent.agent_data["output"],
290
+ }
291
+ agents_info.append(agent_info)
292
+
293
+ agents_str = json.dumps(agents_info, indent=2)
294
+
295
+ # Create the prompt
296
+ if self.config.prompt:
297
+ prompt = self.config.prompt
298
+ else:
299
+ prompt = f"""
300
+ You are a workflow router that determines the next agent to execute in a multi-agent system.
301
+
302
+ CURRENT AGENT:
303
+ Name: {current_agent.name}
304
+ Description: {current_agent.description}
305
+ Input: {current_agent.input}
306
+ Output: {current_agent.output}
307
+
308
+ CURRENT AGENT'S OUTPUT:
309
+ {result_str}
310
+
311
+ AVAILABLE AGENTS:
312
+ {agents_str}
313
+
314
+ Based on the current agent's output and the available agents, determine which agent should be executed next.
315
+ Consider the following:
316
+ 1. Which agent's input requirements best match the current agent's output?
317
+ 2. Which agent's purpose and description make it the most logical next step?
318
+ 3. Which agent would provide the most value in continuing the workflow?
319
+
320
+ Respond with a JSON object containing:
321
+ 1. "next_agent": The name of the selected agent
322
+ 2. "score": A confidence score between 0 and 1 indicating how suitable this agent is
323
+ 3. "reasoning": A brief explanation of why this agent was selected
324
+
325
+ If no agent is suitable, set "next_agent" to an empty string and "score" to 0.
326
+
327
+ JSON Response:
328
+ """
329
+ return prompt
330
+
331
+ def _create_next_input(
332
+ self,
333
+ current_agent: FlockAgent,
334
+ result: dict[str, Any],
335
+ next_agent: FlockAgent,
336
+ ) -> dict[str, Any]:
337
+ """Create the input for the next agent, including the previous agent's output.
338
+
339
+ Args:
340
+ current_agent: The agent that just completed execution
341
+ result: The output from the current agent
342
+ next_agent: The next agent to execute
343
+
344
+ Returns:
345
+ Input dictionary for the next agent
346
+ """
347
+ # Start with an empty input
348
+ next_input = {}
349
+
350
+ # Add a special field for the previous agent's output
351
+ next_input["previous_agent_output"] = {
352
+ "agent_name": current_agent.name,
353
+ "result": result,
354
+ }
355
+
356
+ # Try to map the current agent's output to the next agent's input
357
+ # This is a simple implementation that could be enhanced with more sophisticated mapping
358
+ for key in result:
359
+ # If the next agent expects this key, add it directly
360
+ if key in next_agent.input:
361
+ next_input[key] = result[key]
362
+
363
+ return next_input
@@ -7,7 +7,8 @@ from temporalio import activity
7
7
 
8
8
  from flock.core.context.context import FlockContext
9
9
  from flock.core.context.context_vars import FLOCK_CURRENT_AGENT
10
- from flock.core.flock_agent import FlockAgent, HandOff
10
+ from flock.core.flock_agent import FlockAgent
11
+ from flock.core.flock_router import HandOffRequest
11
12
  from flock.core.logging.logging import get_logger
12
13
  from flock.core.registry.agent_registry import Registry
13
14
  from flock.core.util.input_resolver import resolve_inputs
@@ -74,49 +75,83 @@ async def run_agent(context: FlockContext) -> dict:
74
75
  exec_span.record_exception(e)
75
76
  raise
76
77
 
77
- # If there is no handoff, record the result and finish.
78
- if not agent.hand_off:
79
- context.record(
80
- agent.name,
81
- result,
82
- timestamp=datetime.now().isoformat(),
83
- hand_off=None,
84
- called_from=previous_agent_name,
85
- )
78
+ # Determine the next agent using the handoff router if available
79
+ handoff_data = HandOffRequest()
80
+
81
+ if agent.handoff_router:
86
82
  logger.info(
87
- "No handoff defined, completing chain", agent=agent.name
83
+ f"Using handoff router: {agent.handoff_router.__class__.__name__}",
84
+ agent=agent.name,
88
85
  )
89
- iter_span.add_event("chain completed")
90
- return result
91
-
92
- # Determine the next agent.
93
- handoff_data = HandOff()
94
- if callable(agent.hand_off):
95
- logger.debug("Executing handoff function", agent=agent.name)
96
86
  try:
97
- handoff_data = agent.hand_off(context, result)
98
- if isinstance(handoff_data.next_agent, FlockAgent):
87
+ # Route to the next agent
88
+ handoff_data = await agent.handoff_router.route(
89
+ agent, result, context
90
+ )
91
+
92
+ if callable(handoff_data):
93
+ logger.debug(
94
+ "Executing handoff function", agent=agent.name
95
+ )
96
+ try:
97
+ handoff_data = handoff_data(context, result)
98
+ if isinstance(
99
+ handoff_data.next_agent, FlockAgent
100
+ ):
101
+ handoff_data.next_agent = (
102
+ handoff_data.next_agent.name
103
+ )
104
+ except Exception as e:
105
+ logger.error(
106
+ "Handoff function error {} {}",
107
+ agent=agent.name,
108
+ error=str(e),
109
+ )
110
+ iter_span.record_exception(e)
111
+ return {"error": f"Handoff function error: {e}"}
112
+ elif isinstance(handoff_data.next_agent, FlockAgent):
99
113
  handoff_data.next_agent = (
100
114
  handoff_data.next_agent.name
101
115
  )
116
+
117
+ if not handoff_data.next_agent:
118
+ logger.info(
119
+ "Router found no suitable next agent",
120
+ agent=agent.name,
121
+ )
122
+ context.record(
123
+ agent.name,
124
+ result,
125
+ timestamp=datetime.now().isoformat(),
126
+ hand_off=None,
127
+ called_from=previous_agent_name,
128
+ )
129
+ logger.info("Completing chain", agent=agent.name)
130
+ iter_span.add_event("chain completed")
131
+ return result
102
132
  except Exception as e:
103
133
  logger.error(
104
- "Handoff function error",
105
- agent=agent.name,
106
- error=str(e),
134
+ "Router error {} {}",
135
+ agent.name,
136
+ str(e),
107
137
  )
108
138
  iter_span.record_exception(e)
109
- return {"error": f"Handoff function error: {e}"}
110
- elif isinstance(agent.hand_off, str | FlockAgent):
111
- handoff_data.next_agent = (
112
- agent.hand_off
113
- if isinstance(agent.hand_off, str)
114
- else agent.hand_off.name
115
- )
139
+ return {"error": f"Router error: {e}"}
116
140
  else:
117
- logger.error("Unsupported hand_off type", agent=agent.name)
118
- iter_span.add_event("unsupported hand_off type")
119
- return {"error": "Unsupported hand_off type."}
141
+ # No router, so no handoff
142
+ logger.info(
143
+ "No handoff router defined, completing chain",
144
+ agent=agent.name,
145
+ )
146
+ context.record(
147
+ agent.name,
148
+ result,
149
+ timestamp=datetime.now().isoformat(),
150
+ hand_off=None,
151
+ called_from=previous_agent_name,
152
+ )
153
+ iter_span.add_event("chain completed")
154
+ return result
120
155
 
121
156
  # Record the agent run in the context.
122
157
  context.record(
@@ -127,10 +162,15 @@ async def run_agent(context: FlockContext) -> dict:
127
162
  called_from=previous_agent_name,
128
163
  )
129
164
  previous_agent_name = agent.name
165
+ previous_agent_output = agent.output
166
+ if handoff_data.override_context:
167
+ context.update(handoff_data.override_context)
130
168
 
131
169
  # Prepare the next agent.
132
170
  try:
133
171
  agent = registry.get_agent(handoff_data.next_agent)
172
+ if handoff_data.hand_off_mode == "add":
173
+ agent.input = previous_agent_output + ", " + agent.input
134
174
  agent.resolve_callables(context=context)
135
175
  if not agent:
136
176
  logger.error(
@@ -147,6 +187,7 @@ async def run_agent(context: FlockContext) -> dict:
147
187
  }
148
188
 
149
189
  context.set_variable(FLOCK_CURRENT_AGENT, agent.name)
190
+
150
191
  logger.info("Handing off to next agent", next=agent.name)
151
192
  iter_span.set_attribute("next.agent", agent.name)
152
193
  except Exception as e:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flock-core
3
- Version: 0.3.6
3
+ Version: 0.3.8
4
4
  Summary: Declarative LLM Orchestration at Scale
5
5
  Author-email: Andre Ratzenberger <andre.ratzenberger@whiteduck.de>
6
6
  License-File: LICENSE
@@ -8,14 +8,15 @@ flock/cli/load_examples.py,sha256=DkeLUlrb7rGx3nZ04aADU9HXXu5mZTf_DBwT0xhzIv4,7
8
8
  flock/cli/load_flock.py,sha256=3JdECvt5X7uyOG2vZS3-Zk5C5SI_84_QZjcsB3oJmfA,932
9
9
  flock/cli/load_release_notes.py,sha256=qFcgUrMddAE_TP6x1P-6ZywTUjTknfhTDW5LTxtg1yk,599
10
10
  flock/cli/settings.py,sha256=DkeLUlrb7rGx3nZ04aADU9HXXu5mZTf_DBwT0xhzIv4,7
11
- flock/cli/assets/release_notes.md,sha256=K-upUm5vuUuRSSU2FkMdgDfai_YlDk_vTCp0s4s2WO0,3419
11
+ flock/cli/assets/release_notes.md,sha256=-RuE-G9Sn8z1LWEdr9iqjuQN7N1K_JMaCzHYoyLR42U,4793
12
12
  flock/core/__init__.py,sha256=mPlvKc0SxC2qCvSlgYeP_7EyV8ptmdn24NO8mlQoCSo,559
13
13
  flock/core/flock.py,sha256=1LPMblsvT90Na35LXx0w3Us66yIaTzsokL7lF5fsVX8,19228
14
- flock/core/flock_agent.py,sha256=RzKX0GRrRJz16YbQFheMo8TqJPXOZSHWNloTbp35zwI,12229
14
+ flock/core/flock_agent.py,sha256=LjSuAYRFh4UWV8Z4_mguMme2GnMQwteNFyion4Memt8,11705
15
15
  flock/core/flock_api.py,sha256=SKQVKgFCaNCqHtwvIcksnpqG6ajHodVhs3oaKUw-d8c,7192
16
16
  flock/core/flock_evaluator.py,sha256=j7riJj_KsWoBnKmLiGp-U0CRhxDyJbgEdLGN26tfKm8,1588
17
- flock/core/flock_factory.py,sha256=vyDq0eyFT4MyE_n2JyNU7YaFx2ljmjSDmZ07OIsmIOE,2694
18
- flock/core/flock_module.py,sha256=VWFlBiY2RHZLTlGYfcchuT41M3m_JrZcmzw07u7KayM,2581
17
+ flock/core/flock_factory.py,sha256=xTz2VqcLjaHn60b3Oaz8JCJdcIQra0vc5NHqh3Dy44s,2617
18
+ flock/core/flock_module.py,sha256=3DmxOc39gQS-tiJcgUCjMaLr8QDDJR4acV_M76Xcf6I,2602
19
+ flock/core/flock_router.py,sha256=A5GaxcGvtiFlRLHBTW7okh5RDm3BdKam2uXvRHRaj7k,2187
19
20
  flock/core/context/context.py,sha256=AW0qKIAkgZucVroGsulrPVPc4WmWuqWIrVPHf2qaOLI,6380
20
21
  flock/core/context/context_manager.py,sha256=qMySVny_dbTNLh21RHK_YT0mNKIOrqJDZpi9ZVdBsxU,1103
21
22
  flock/core/context/context_vars.py,sha256=0Hn6fM2iNc0_jIIU0B7KX-K2o8qXqtZ5EYtwujETQ7U,272
@@ -46,7 +47,7 @@ flock/core/util/hydrator.py,sha256=6qNwOwCZB7r6y25BZ--0PGofrAlfMaXbDKFQeP5NLts,1
46
47
  flock/core/util/input_resolver.py,sha256=g9vDPdY4OH-G7qjas5ksGEHueokHGFPMoLOvC-ngeLo,5984
47
48
  flock/evaluators/declarative/declarative_evaluator.py,sha256=f8ldgZZp94zC4CoGzBufKvbvtckCGBe9EHTOoAZfZK0,1695
48
49
  flock/evaluators/natural_language/natural_language_evaluator.py,sha256=6nVEeh8_uwv_h-d3FWlA0GbzDzRtdhvxCGKirHtyvOU,2012
49
- flock/evaluators/zep/zep_evaluator.py,sha256=hEHQdgIwGsbC4ci9RvtdA2k7f4M0yznIok4v4XltNwg,1885
50
+ flock/evaluators/zep/zep_evaluator.py,sha256=9NOELl7JAuUcx_FQrxY6b-_vN3MjwDyW7ZppPIGeCFc,1954
50
51
  flock/modules/callback/callback_module.py,sha256=hCCw-HNYjK4aHnUQfvw26ZP1Q_jdlKb9kDh3BHzbCQA,2916
51
52
  flock/modules/memory/memory_module.py,sha256=2grdmvw7FJWZvz0IjgASbDPCfyS1w4gWkRzOWtK7BFM,8214
52
53
  flock/modules/memory/memory_parser.py,sha256=2S7CmVEsm22gD7-MiFj4318FTg8wd_jB-RKMwXI14WM,4369
@@ -56,6 +57,14 @@ flock/modules/performance/metrics_module.py,sha256=K5z5bizIjA4ZEUjBk5ShwTR9ZElR-
56
57
  flock/modules/zep/zep_module.py,sha256=BIJ5K-hg2bLeJmGKoDcVY1rVN7_0yYETiSaVrO-gtMI,5830
57
58
  flock/platform/docker_tools.py,sha256=fpA7-6rJBjPOUBLdQP4ny2QPgJ_042nmqRn5GtKnoYw,1445
58
59
  flock/platform/jaeger_install.py,sha256=MyOMJQx4TQSMYvdUJxfiGSo3YCtsfkbNXcAcQ9bjETA,2898
60
+ flock/routers/__init__.py,sha256=w9uL34Auuo26-q_EGlE8Z9iHsw6S8qutTAH_ZI7pn7M,39
61
+ flock/routers/agent/__init__.py,sha256=0ZOYpR8BMnR5iCGfcUiv99g7aT_g13xvm2Shl-XzybY,65
62
+ flock/routers/agent/agent_router.py,sha256=9s3AwcBqpyhpPXOTqyMSVtS8Bcme1RDdqSUfWIqEBfc,8139
63
+ flock/routers/agent/handoff_agent.py,sha256=p-0XEPXIyv1T3DGAhhXg2SYXmrwEaJ5pnuLgRSvbiZg,1903
64
+ flock/routers/default/__init__.py,sha256=DOatGX_aE2DWvf55a0Tv7qDK05QFD-hL3sm7g58hmLU,61
65
+ flock/routers/default/default_router.py,sha256=D9TCAAeNfzt3Se6QduGO2TmZ6038XlQLV6Y1u5IGI-0,2232
66
+ flock/routers/llm/__init__.py,sha256=OV89ebq8RPWZwCJTS2_P46Q0yKD_03rwq_fBOsETd08,63
67
+ flock/routers/llm/llm_router.py,sha256=3WXUK2TqZENYXSFb7o_WtpONq0SsebaZZpytCRr1daw,12217
59
68
  flock/themes/3024-day.toml,sha256=uOVHqEzSyHx0WlUk3D0lne4RBsNBAPCTy3C58yU7kEY,667
60
69
  flock/themes/3024-night.toml,sha256=qsXUwd6ZYz6J-R129_Ao2TKlvvK60svhZJJjB5c8Tfo,1667
61
70
  flock/themes/aardvark-blue.toml,sha256=5ZgsxP3pWLPN3yJ2Wd9ErCo7fy_VJpIfje4kriDKlqo,1667
@@ -393,12 +402,12 @@ flock/themes/zenburned.toml,sha256=UEmquBbcAO3Zj652XKUwCsNoC2iQSlIh-q5c6DH-7Kc,1
393
402
  flock/themes/zenwritten-dark.toml,sha256=To5l6520_3UqAGiEumpzGWsHhXxqu9ThrMildXKgIO0,1669
394
403
  flock/themes/zenwritten-light.toml,sha256=G1iEheCPfBNsMTGaVpEVpDzYBHA_T-MV27rolUYolmE,1666
395
404
  flock/workflow/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
396
- flock/workflow/activities.py,sha256=2zcYyDoCuYs9oQbnhLjCzBUdEi7d5IEIemKJ7TV_B8w,6932
405
+ flock/workflow/activities.py,sha256=PqUc1jecg-7qq4KQr0Gr2XLNCABZ79fvD1MqazT--YU,8866
397
406
  flock/workflow/agent_activities.py,sha256=NhBZscflEf2IMfSRa_pBM_TRP7uVEF_O0ROvWZ33eDc,963
398
407
  flock/workflow/temporal_setup.py,sha256=VWBgmBgfTBjwM5ruS_dVpA5AVxx6EZ7oFPGw4j3m0l0,1091
399
408
  flock/workflow/workflow.py,sha256=I9MryXW_bqYVTHx-nl2epbTqeRy27CAWHHA7ZZA0nAk,1696
400
- flock_core-0.3.6.dist-info/METADATA,sha256=-TPh-D8HF2et5uEyksY7uwhl9Fgc0b_RRf0PMe1B2hg,20494
401
- flock_core-0.3.6.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
402
- flock_core-0.3.6.dist-info/entry_points.txt,sha256=rWaS5KSpkTmWySURGFZk6PhbJ87TmvcFQDi2uzjlagQ,37
403
- flock_core-0.3.6.dist-info/licenses/LICENSE,sha256=iYEqWy0wjULzM9GAERaybP4LBiPeu7Z1NEliLUdJKSc,1072
404
- flock_core-0.3.6.dist-info/RECORD,,
409
+ flock_core-0.3.8.dist-info/METADATA,sha256=0ImaV46N5uSNlZPP4kF2q-pjfY3nsLLHzDvicvv7_gA,20494
410
+ flock_core-0.3.8.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
411
+ flock_core-0.3.8.dist-info/entry_points.txt,sha256=rWaS5KSpkTmWySURGFZk6PhbJ87TmvcFQDi2uzjlagQ,37
412
+ flock_core-0.3.8.dist-info/licenses/LICENSE,sha256=iYEqWy0wjULzM9GAERaybP4LBiPeu7Z1NEliLUdJKSc,1072
413
+ flock_core-0.3.8.dist-info/RECORD,,