flock-core 0.5.4__py3-none-any.whl → 0.5.5__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/agent.py +20 -1
- flock/components.py +32 -0
- flock/dashboard/static_v2/assets/index-DFRnI_mt.js +1 -1
- flock/dashboard/static_v2/index.html +3 -3
- flock/engines/dspy_engine.py +41 -3
- flock/engines/examples/__init__.py +6 -0
- flock/engines/examples/simple_batch_engine.py +61 -0
- flock/frontend/README.md +4 -4
- flock/frontend/docs/DESIGN_SYSTEM.md +1 -1
- flock/frontend/package-lock.json +2 -2
- flock/frontend/package.json +1 -1
- flock/frontend/src/components/settings/SettingsPanel.css +1 -1
- flock/frontend/src/components/settings/ThemeSelector.tsx +2 -2
- flock/frontend/src/services/indexeddb.ts +1 -1
- flock/frontend/src/styles/variables.css +1 -1
- flock/orchestrator.py +141 -12
- flock/runtime.py +3 -0
- {flock_core-0.5.4.dist-info → flock_core-0.5.5.dist-info}/METADATA +3 -1
- {flock_core-0.5.4.dist-info → flock_core-0.5.5.dist-info}/RECORD +22 -20
- {flock_core-0.5.4.dist-info → flock_core-0.5.5.dist-info}/WHEEL +0 -0
- {flock_core-0.5.4.dist-info → flock_core-0.5.5.dist-info}/entry_points.txt +0 -0
- {flock_core-0.5.4.dist-info → flock_core-0.5.5.dist-info}/licenses/LICENSE +0 -0
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>🦆🐓 Flock 🐤🐧</title>
|
|
7
|
-
<script type="module" crossorigin src="/assets/index-DFRnI_mt.js"></script>
|
|
8
|
-
<link rel="stylesheet" crossorigin href="/assets/index-fPLNdmp1.css">
|
|
7
|
+
<script type="module" crossorigin src="/assets/index-DFRnI_mt.js"></script>
|
|
8
|
+
<link rel="stylesheet" crossorigin href="/assets/index-fPLNdmp1.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
|
11
|
-
<div id="root"></div>
|
|
11
|
+
<div id="root"></div>
|
|
12
12
|
</body>
|
|
13
13
|
</html>
|
flock/engines/dspy_engine.py
CHANGED
|
@@ -153,6 +153,19 @@ class DSPyEngine(EngineComponent):
|
|
|
153
153
|
)
|
|
154
154
|
|
|
155
155
|
async def evaluate(self, agent, ctx, inputs: EvalInputs) -> EvalResult: # type: ignore[override]
|
|
156
|
+
return await self._evaluate_internal(agent, ctx, inputs, batched=False)
|
|
157
|
+
|
|
158
|
+
async def evaluate_batch(self, agent, ctx, inputs: EvalInputs) -> EvalResult: # type: ignore[override]
|
|
159
|
+
return await self._evaluate_internal(agent, ctx, inputs, batched=True)
|
|
160
|
+
|
|
161
|
+
async def _evaluate_internal(
|
|
162
|
+
self,
|
|
163
|
+
agent,
|
|
164
|
+
ctx,
|
|
165
|
+
inputs: EvalInputs,
|
|
166
|
+
*,
|
|
167
|
+
batched: bool,
|
|
168
|
+
) -> EvalResult:
|
|
156
169
|
if not inputs.artifacts:
|
|
157
170
|
return EvalResult(artifacts=[], state=dict(inputs.state))
|
|
158
171
|
|
|
@@ -169,7 +182,13 @@ class DSPyEngine(EngineComponent):
|
|
|
169
182
|
|
|
170
183
|
primary_artifact = self._select_primary_artifact(inputs.artifacts)
|
|
171
184
|
input_model = self._resolve_input_model(primary_artifact)
|
|
172
|
-
|
|
185
|
+
if batched:
|
|
186
|
+
validated_input = [
|
|
187
|
+
self._validate_input_payload(input_model, artifact.payload)
|
|
188
|
+
for artifact in inputs.artifacts
|
|
189
|
+
]
|
|
190
|
+
else:
|
|
191
|
+
validated_input = self._validate_input_payload(input_model, primary_artifact.payload)
|
|
173
192
|
output_model = self._resolve_output_model(agent)
|
|
174
193
|
|
|
175
194
|
# Fetch conversation context from blackboard
|
|
@@ -183,6 +202,7 @@ class DSPyEngine(EngineComponent):
|
|
|
183
202
|
input_schema=input_model,
|
|
184
203
|
output_schema=output_model,
|
|
185
204
|
has_context=has_context,
|
|
205
|
+
batched=batched,
|
|
186
206
|
)
|
|
187
207
|
|
|
188
208
|
sys_desc = self._system_description(self.instructions or agent.description)
|
|
@@ -193,7 +213,11 @@ class DSPyEngine(EngineComponent):
|
|
|
193
213
|
pre_generated_artifact_id = uuid4()
|
|
194
214
|
|
|
195
215
|
# Build execution payload with context
|
|
196
|
-
if
|
|
216
|
+
if batched:
|
|
217
|
+
execution_payload = {"input": validated_input}
|
|
218
|
+
if has_context:
|
|
219
|
+
execution_payload["context"] = context_history
|
|
220
|
+
elif has_context:
|
|
197
221
|
execution_payload = {
|
|
198
222
|
"input": validated_input,
|
|
199
223
|
"context": context_history,
|
|
@@ -383,6 +407,7 @@ class DSPyEngine(EngineComponent):
|
|
|
383
407
|
input_schema: type[BaseModel] | None,
|
|
384
408
|
output_schema: type[BaseModel] | None,
|
|
385
409
|
has_context: bool = False,
|
|
410
|
+
batched: bool = False,
|
|
386
411
|
) -> Any:
|
|
387
412
|
"""Prepare DSPy signature, optionally including context field."""
|
|
388
413
|
fields = {
|
|
@@ -398,7 +423,15 @@ class DSPyEngine(EngineComponent):
|
|
|
398
423
|
),
|
|
399
424
|
)
|
|
400
425
|
|
|
401
|
-
|
|
426
|
+
if batched:
|
|
427
|
+
if input_schema is not None:
|
|
428
|
+
input_type = list[input_schema]
|
|
429
|
+
else:
|
|
430
|
+
input_type = list[dict[str, Any]]
|
|
431
|
+
else:
|
|
432
|
+
input_type = input_schema or dict
|
|
433
|
+
|
|
434
|
+
fields["input"] = (input_type, dspy_mod.InputField())
|
|
402
435
|
fields["output"] = (output_schema or dict, dspy_mod.OutputField())
|
|
403
436
|
|
|
404
437
|
signature = dspy_mod.Signature(fields)
|
|
@@ -406,6 +439,11 @@ class DSPyEngine(EngineComponent):
|
|
|
406
439
|
instruction = description or "Produce a valid output that matches the 'output' schema."
|
|
407
440
|
if has_context:
|
|
408
441
|
instruction += " Consider the conversation context provided to inform your response."
|
|
442
|
+
if batched:
|
|
443
|
+
instruction += (
|
|
444
|
+
" The 'input' field will contain a list of items representing the batch; "
|
|
445
|
+
"process the entire collection coherently."
|
|
446
|
+
)
|
|
409
447
|
instruction += " Return only JSON."
|
|
410
448
|
|
|
411
449
|
return signature.with_instructions(instruction)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""Reference batch-aware engine used in tutorials and tests."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
|
|
7
|
+
from flock.components import EngineComponent
|
|
8
|
+
from flock.registry import flock_type
|
|
9
|
+
from flock.runtime import EvalInputs, EvalResult
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@flock_type(name="BatchItem")
|
|
13
|
+
class BatchItem(BaseModel):
|
|
14
|
+
"""Input payload used by reference tests and tutorials."""
|
|
15
|
+
|
|
16
|
+
value: int = Field(description="Numeric value contributed by the artifact")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@flock_type(name="BatchSummary")
|
|
20
|
+
class BatchSummary(BaseModel):
|
|
21
|
+
"""Output payload describing the batch that was processed."""
|
|
22
|
+
|
|
23
|
+
batch_size: int = Field(description="Number of items included in this evaluation")
|
|
24
|
+
values: list[int] = Field(description="Original values processed", default_factory=list)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class SimpleBatchEngine(EngineComponent):
|
|
28
|
+
"""Example engine that processes items individually or in batches.
|
|
29
|
+
|
|
30
|
+
- ``evaluate`` is used when the agent is invoked directly without BatchSpec.
|
|
31
|
+
- ``evaluate_batch`` is triggered when BatchSpec flushes accumulated artifacts.
|
|
32
|
+
|
|
33
|
+
The engine simply annotates each item with the current batch size so tests can
|
|
34
|
+
verify that all artifacts were processed together.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
async def evaluate(self, agent, ctx, inputs: EvalInputs) -> EvalResult:
|
|
38
|
+
item = inputs.first_as(BatchItem)
|
|
39
|
+
if item is None:
|
|
40
|
+
return EvalResult.empty()
|
|
41
|
+
|
|
42
|
+
annotated = BatchSummary(batch_size=1, values=[item.value])
|
|
43
|
+
state = dict(inputs.state)
|
|
44
|
+
state.setdefault("batch_size", annotated.batch_size)
|
|
45
|
+
state.setdefault("processed_values", list(annotated.values))
|
|
46
|
+
|
|
47
|
+
return EvalResult.from_object(annotated, agent=agent, state=state)
|
|
48
|
+
|
|
49
|
+
async def evaluate_batch(self, agent, ctx, inputs: EvalInputs) -> EvalResult:
|
|
50
|
+
items = inputs.all_as(BatchItem)
|
|
51
|
+
if not items:
|
|
52
|
+
return EvalResult.empty()
|
|
53
|
+
|
|
54
|
+
batch_size = len(items)
|
|
55
|
+
summary = BatchSummary(batch_size=batch_size, values=[item.value for item in items])
|
|
56
|
+
|
|
57
|
+
state = dict(inputs.state)
|
|
58
|
+
state["batch_size"] = summary.batch_size
|
|
59
|
+
state["processed_values"] = list(summary.values)
|
|
60
|
+
|
|
61
|
+
return EvalResult.from_object(summary, agent=agent, state=state)
|
flock/frontend/README.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
# Flock
|
|
1
|
+
# Flock Dashboard
|
|
2
2
|
|
|
3
|
-
A real-time visualization dashboard for monitoring and controlling Flock
|
|
3
|
+
A real-time visualization dashboard for monitoring and controlling Flock agent orchestration systems. Built with modern web technologies to provide an intuitive, high-performance interface for observing multi-agent workflows.
|
|
4
4
|
|
|
5
5
|
## Overview
|
|
6
6
|
|
|
7
|
-
The Flock
|
|
7
|
+
The Flock Dashboard provides real-time visibility into your agent orchestration system through an interactive graph-based interface. Watch agents activate, messages flow, and data transform in real-time as your multi-agent system operates.
|
|
8
8
|
|
|
9
9
|
The dashboard offers two complementary visualization modes:
|
|
10
10
|
- **Agent View**: Shows agents as nodes with message flows as edges - perfect for understanding agent communication patterns
|
|
@@ -149,7 +149,7 @@ Launch the module via the context menu (or `Add Module → Historical Blackboard
|
|
|
149
149
|
|
|
150
150
|
- **Node.js**: Version 18 or higher
|
|
151
151
|
- **Package Manager**: npm (included with Node.js) or yarn
|
|
152
|
-
- **Flock
|
|
152
|
+
- **Flock Backend**: Running orchestrator instance (typically on port 8344)
|
|
153
153
|
|
|
154
154
|
### Installation
|
|
155
155
|
|
flock/frontend/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flock-ui",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "flock-ui",
|
|
9
|
-
"version": "0.1.
|
|
9
|
+
"version": "0.1.8",
|
|
10
10
|
"license": "ISC",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@types/dagre": "^0.7.53",
|
flock/frontend/package.json
CHANGED
|
@@ -150,7 +150,7 @@ const ThemeSelector: React.FC = () => {
|
|
|
150
150
|
justifyContent: 'space-between',
|
|
151
151
|
}}>
|
|
152
152
|
<span style={{ color: 'var(--color-text-primary)' }}>
|
|
153
|
-
{currentTheme === 'default' ? 'Default (Flock
|
|
153
|
+
{currentTheme === 'default' ? 'Default (Flock)' : currentTheme}
|
|
154
154
|
</span>
|
|
155
155
|
{currentTheme !== 'default' && (
|
|
156
156
|
<button
|
|
@@ -191,7 +191,7 @@ const ThemeSelector: React.FC = () => {
|
|
|
191
191
|
transition: 'var(--transition-all)',
|
|
192
192
|
}}
|
|
193
193
|
>
|
|
194
|
-
Default (Flock
|
|
194
|
+
Default (Flock)
|
|
195
195
|
</button>
|
|
196
196
|
</div>
|
|
197
197
|
</div>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* IndexedDB Persistence Service for Flock
|
|
2
|
+
* IndexedDB Persistence Service for Flock Dashboard
|
|
3
3
|
*
|
|
4
4
|
* Provides persistent storage for dashboard data with LRU eviction strategy.
|
|
5
5
|
* Implements separate layout storage for Agent View and Blackboard View.
|
flock/orchestrator.py
CHANGED
|
@@ -8,7 +8,7 @@ import os
|
|
|
8
8
|
from asyncio import Task
|
|
9
9
|
from collections.abc import AsyncGenerator, Iterable, Mapping, Sequence
|
|
10
10
|
from contextlib import asynccontextmanager
|
|
11
|
-
from datetime import datetime, timezone
|
|
11
|
+
from datetime import datetime, timedelta, timezone
|
|
12
12
|
from pathlib import Path
|
|
13
13
|
from typing import TYPE_CHECKING, Any
|
|
14
14
|
from uuid import uuid4
|
|
@@ -135,8 +135,14 @@ class Flock(metaclass=AutoTracedMeta):
|
|
|
135
135
|
self._artifact_collector = ArtifactCollector()
|
|
136
136
|
# JoinSpec logic: Correlation engine for correlated AND gates
|
|
137
137
|
self._correlation_engine = CorrelationEngine()
|
|
138
|
+
# Background task for checking correlation expiry (time-based JoinSpec)
|
|
139
|
+
self._correlation_cleanup_task: Task[Any] | None = None
|
|
140
|
+
self._correlation_cleanup_interval: float = 0.1 # Check every 100ms
|
|
138
141
|
# BatchSpec logic: Batch accumulator for size/timeout batching
|
|
139
142
|
self._batch_engine = BatchEngine()
|
|
143
|
+
# Background task for checking batch timeouts
|
|
144
|
+
self._batch_timeout_task: Task[Any] | None = None
|
|
145
|
+
self._batch_timeout_interval: float = 0.1 # Check every 100ms
|
|
140
146
|
# Phase 1.2: WebSocket manager for real-time dashboard events (set by serve())
|
|
141
147
|
self._websocket_manager: Any = None
|
|
142
148
|
# Unified tracing support
|
|
@@ -473,6 +479,33 @@ class Flock(metaclass=AutoTracedMeta):
|
|
|
473
479
|
await asyncio.sleep(0.01)
|
|
474
480
|
pending = {task for task in self._tasks if not task.done()}
|
|
475
481
|
self._tasks = pending
|
|
482
|
+
|
|
483
|
+
# Determine whether any deferred work (timeouts/cleanup) is still pending.
|
|
484
|
+
pending_batches = any(
|
|
485
|
+
accumulator.artifacts for accumulator in self._batch_engine.batches.values()
|
|
486
|
+
)
|
|
487
|
+
pending_correlations = any(
|
|
488
|
+
groups and any(group.waiting_artifacts for group in groups.values())
|
|
489
|
+
for groups in self._correlation_engine.correlation_groups.values()
|
|
490
|
+
)
|
|
491
|
+
|
|
492
|
+
# Ensure watchdog loops remain active while pending work exists.
|
|
493
|
+
if pending_batches and (
|
|
494
|
+
self._batch_timeout_task is None or self._batch_timeout_task.done()
|
|
495
|
+
):
|
|
496
|
+
self._batch_timeout_task = asyncio.create_task(self._batch_timeout_checker_loop())
|
|
497
|
+
|
|
498
|
+
if pending_correlations and (
|
|
499
|
+
self._correlation_cleanup_task is None or self._correlation_cleanup_task.done()
|
|
500
|
+
):
|
|
501
|
+
self._correlation_cleanup_task = asyncio.create_task(self._correlation_cleanup_loop())
|
|
502
|
+
|
|
503
|
+
# If deferred work is still outstanding, consider the orchestrator quiescent for
|
|
504
|
+
# now but leave watchdog tasks running to finish the job.
|
|
505
|
+
if pending_batches or pending_correlations:
|
|
506
|
+
self._agent_iteration_count.clear()
|
|
507
|
+
return
|
|
508
|
+
|
|
476
509
|
# T068: Reset circuit breaker counters when idle
|
|
477
510
|
self._agent_iteration_count.clear()
|
|
478
511
|
|
|
@@ -548,6 +581,22 @@ class Flock(metaclass=AutoTracedMeta):
|
|
|
548
581
|
|
|
549
582
|
async def shutdown(self) -> None:
|
|
550
583
|
"""Shutdown orchestrator and clean up resources."""
|
|
584
|
+
# Cancel correlation cleanup task if running
|
|
585
|
+
if self._correlation_cleanup_task and not self._correlation_cleanup_task.done():
|
|
586
|
+
self._correlation_cleanup_task.cancel()
|
|
587
|
+
try:
|
|
588
|
+
await self._correlation_cleanup_task
|
|
589
|
+
except asyncio.CancelledError:
|
|
590
|
+
pass
|
|
591
|
+
|
|
592
|
+
# Cancel batch timeout checker if running
|
|
593
|
+
if self._batch_timeout_task and not self._batch_timeout_task.done():
|
|
594
|
+
self._batch_timeout_task.cancel()
|
|
595
|
+
try:
|
|
596
|
+
await self._batch_timeout_task
|
|
597
|
+
except asyncio.CancelledError:
|
|
598
|
+
pass
|
|
599
|
+
|
|
551
600
|
if self._mcp_manager is not None:
|
|
552
601
|
await self._mcp_manager.cleanup_all()
|
|
553
602
|
self._mcp_manager = None
|
|
@@ -909,6 +958,15 @@ class Flock(metaclass=AutoTracedMeta):
|
|
|
909
958
|
subscription_index=subscription_index,
|
|
910
959
|
)
|
|
911
960
|
|
|
961
|
+
# Start correlation cleanup task if time-based window and not running
|
|
962
|
+
if (
|
|
963
|
+
isinstance(subscription.join.within, timedelta)
|
|
964
|
+
and self._correlation_cleanup_task is None
|
|
965
|
+
):
|
|
966
|
+
self._correlation_cleanup_task = asyncio.create_task(
|
|
967
|
+
self._correlation_cleanup_loop()
|
|
968
|
+
)
|
|
969
|
+
|
|
912
970
|
if completed_group is None:
|
|
913
971
|
# Still waiting for correlation to complete
|
|
914
972
|
# Phase 1.2: Emit real-time correlation update event
|
|
@@ -948,6 +1006,12 @@ class Flock(metaclass=AutoTracedMeta):
|
|
|
948
1006
|
subscription=subscription,
|
|
949
1007
|
subscription_index=subscription_index,
|
|
950
1008
|
)
|
|
1009
|
+
|
|
1010
|
+
# Start timeout checker if this batch has a timeout and checker not running
|
|
1011
|
+
if subscription.batch.timeout and self._batch_timeout_task is None:
|
|
1012
|
+
self._batch_timeout_task = asyncio.create_task(
|
|
1013
|
+
self._batch_timeout_checker_loop()
|
|
1014
|
+
)
|
|
951
1015
|
else:
|
|
952
1016
|
# Single type subscription: Add each artifact individually
|
|
953
1017
|
should_flush = False
|
|
@@ -958,6 +1022,12 @@ class Flock(metaclass=AutoTracedMeta):
|
|
|
958
1022
|
subscription_index=subscription_index,
|
|
959
1023
|
)
|
|
960
1024
|
|
|
1025
|
+
# Start timeout checker if this batch has a timeout and checker not running
|
|
1026
|
+
if subscription.batch.timeout and self._batch_timeout_task is None:
|
|
1027
|
+
self._batch_timeout_task = asyncio.create_task(
|
|
1028
|
+
self._batch_timeout_checker_loop()
|
|
1029
|
+
)
|
|
1030
|
+
|
|
961
1031
|
if should_flush:
|
|
962
1032
|
# Size threshold reached! Flush batch now
|
|
963
1033
|
break
|
|
@@ -994,10 +1064,14 @@ class Flock(metaclass=AutoTracedMeta):
|
|
|
994
1064
|
self._mark_processed(collected_artifact, agent)
|
|
995
1065
|
|
|
996
1066
|
# Schedule agent with ALL artifacts (batched, correlated, or AND gate complete)
|
|
997
|
-
|
|
1067
|
+
# NEW: Mark as batch execution if flushed from BatchSpec
|
|
1068
|
+
is_batch_execution = subscription.batch is not None
|
|
1069
|
+
self._schedule_task(agent, artifacts, is_batch=is_batch_execution)
|
|
998
1070
|
|
|
999
|
-
def _schedule_task(
|
|
1000
|
-
|
|
1071
|
+
def _schedule_task(
|
|
1072
|
+
self, agent: Agent, artifacts: list[Artifact], is_batch: bool = False
|
|
1073
|
+
) -> None:
|
|
1074
|
+
task = asyncio.create_task(self._run_agent_task(agent, artifacts, is_batch=is_batch))
|
|
1001
1075
|
self._tasks.add(task)
|
|
1002
1076
|
task.add_done_callback(self._tasks.discard)
|
|
1003
1077
|
|
|
@@ -1012,14 +1086,17 @@ class Flock(metaclass=AutoTracedMeta):
|
|
|
1012
1086
|
key = (str(artifact.id), agent.name)
|
|
1013
1087
|
return key in self._processed
|
|
1014
1088
|
|
|
1015
|
-
async def _run_agent_task(
|
|
1089
|
+
async def _run_agent_task(
|
|
1090
|
+
self, agent: Agent, artifacts: list[Artifact], is_batch: bool = False
|
|
1091
|
+
) -> None:
|
|
1016
1092
|
correlation_id = artifacts[0].correlation_id if artifacts else uuid4()
|
|
1017
1093
|
|
|
1018
1094
|
ctx = Context(
|
|
1019
1095
|
board=BoardHandle(self),
|
|
1020
1096
|
orchestrator=self,
|
|
1021
1097
|
task_id=str(uuid4()),
|
|
1022
|
-
correlation_id=correlation_id,
|
|
1098
|
+
correlation_id=correlation_id,
|
|
1099
|
+
is_batch=is_batch, # NEW!
|
|
1023
1100
|
)
|
|
1024
1101
|
self._record_agent_run(agent)
|
|
1025
1102
|
await agent.execute(ctx, artifacts)
|
|
@@ -1154,11 +1231,63 @@ class Flock(metaclass=AutoTracedMeta):
|
|
|
1154
1231
|
|
|
1155
1232
|
# Batch Helpers --------------------------------------------------------
|
|
1156
1233
|
|
|
1234
|
+
async def _correlation_cleanup_loop(self) -> None:
|
|
1235
|
+
"""Background task that periodically cleans up expired correlation groups.
|
|
1236
|
+
|
|
1237
|
+
Runs continuously until all correlation groups are cleared or orchestrator shuts down.
|
|
1238
|
+
Checks every 100ms for time-based expired correlations and discards them.
|
|
1239
|
+
"""
|
|
1240
|
+
try:
|
|
1241
|
+
while True:
|
|
1242
|
+
await asyncio.sleep(self._correlation_cleanup_interval)
|
|
1243
|
+
self._cleanup_expired_correlations()
|
|
1244
|
+
|
|
1245
|
+
# Stop if no correlation groups remain
|
|
1246
|
+
if not self._correlation_engine.correlation_groups:
|
|
1247
|
+
self._correlation_cleanup_task = None
|
|
1248
|
+
break
|
|
1249
|
+
except asyncio.CancelledError:
|
|
1250
|
+
# Clean shutdown
|
|
1251
|
+
self._correlation_cleanup_task = None
|
|
1252
|
+
raise
|
|
1253
|
+
|
|
1254
|
+
def _cleanup_expired_correlations(self) -> None:
|
|
1255
|
+
"""Clean up all expired correlation groups across all subscriptions.
|
|
1256
|
+
|
|
1257
|
+
Called periodically by background task to enforce time-based correlation windows.
|
|
1258
|
+
Discards incomplete correlations that have exceeded their time window.
|
|
1259
|
+
"""
|
|
1260
|
+
# Get all active subscription keys
|
|
1261
|
+
for agent_name, subscription_index in list(
|
|
1262
|
+
self._correlation_engine.correlation_groups.keys()
|
|
1263
|
+
):
|
|
1264
|
+
self._correlation_engine.cleanup_expired(agent_name, subscription_index)
|
|
1265
|
+
|
|
1266
|
+
async def _batch_timeout_checker_loop(self) -> None:
|
|
1267
|
+
"""Background task that periodically checks for batch timeouts.
|
|
1268
|
+
|
|
1269
|
+
Runs continuously until all batches are cleared or orchestrator shuts down.
|
|
1270
|
+
Checks every 100ms for expired batches and flushes them.
|
|
1271
|
+
"""
|
|
1272
|
+
try:
|
|
1273
|
+
while True:
|
|
1274
|
+
await asyncio.sleep(self._batch_timeout_interval)
|
|
1275
|
+
await self._check_batch_timeouts()
|
|
1276
|
+
|
|
1277
|
+
# Stop if no batches remain
|
|
1278
|
+
if not self._batch_engine.batches:
|
|
1279
|
+
self._batch_timeout_task = None
|
|
1280
|
+
break
|
|
1281
|
+
except asyncio.CancelledError:
|
|
1282
|
+
# Clean shutdown
|
|
1283
|
+
self._batch_timeout_task = None
|
|
1284
|
+
raise
|
|
1285
|
+
|
|
1157
1286
|
async def _check_batch_timeouts(self) -> None:
|
|
1158
1287
|
"""Check all batches for timeout expiry and flush expired batches.
|
|
1159
1288
|
|
|
1160
|
-
This method is called periodically
|
|
1161
|
-
timeout-based batching.
|
|
1289
|
+
This method is called periodically by the background timeout checker
|
|
1290
|
+
or manually (in tests) to enforce timeout-based batching.
|
|
1162
1291
|
"""
|
|
1163
1292
|
expired_batches = self._batch_engine.check_timeouts()
|
|
1164
1293
|
|
|
@@ -1174,8 +1303,8 @@ class Flock(metaclass=AutoTracedMeta):
|
|
|
1174
1303
|
if agent is None:
|
|
1175
1304
|
continue
|
|
1176
1305
|
|
|
1177
|
-
# Schedule agent with batched artifacts
|
|
1178
|
-
self._schedule_task(agent, artifacts)
|
|
1306
|
+
# Schedule agent with batched artifacts (timeout flush)
|
|
1307
|
+
self._schedule_task(agent, artifacts, is_batch=True)
|
|
1179
1308
|
|
|
1180
1309
|
async def _flush_all_batches(self) -> None:
|
|
1181
1310
|
"""Flush all partial batches (for shutdown - ensures zero data loss)."""
|
|
@@ -1187,8 +1316,8 @@ class Flock(metaclass=AutoTracedMeta):
|
|
|
1187
1316
|
if agent is None:
|
|
1188
1317
|
continue
|
|
1189
1318
|
|
|
1190
|
-
# Schedule agent with partial batch
|
|
1191
|
-
self._schedule_task(agent, artifacts)
|
|
1319
|
+
# Schedule agent with partial batch (shutdown flush)
|
|
1320
|
+
self._schedule_task(agent, artifacts, is_batch=True)
|
|
1192
1321
|
|
|
1193
1322
|
# Wait for all scheduled tasks to complete
|
|
1194
1323
|
await self.run_until_idle()
|
flock/runtime.py
CHANGED
|
@@ -250,6 +250,9 @@ class Context(BaseModel):
|
|
|
250
250
|
correlation_id: UUID | None = None # NEW!
|
|
251
251
|
task_id: str
|
|
252
252
|
state: dict[str, Any] = Field(default_factory=dict)
|
|
253
|
+
is_batch: bool = Field(
|
|
254
|
+
default=False, description="True if this execution is processing a BatchSpec accumulation"
|
|
255
|
+
)
|
|
253
256
|
|
|
254
257
|
def get_variable(self, key: str, default: Any = None) -> Any:
|
|
255
258
|
return self.state.get(key, default)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: flock-core
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.5
|
|
4
4
|
Summary: Flock: A declrative framework for building and orchestrating AI agents.
|
|
5
5
|
Author-email: Andre Ratzenberger <andre.ratzenberger@whiteduck.de>
|
|
6
6
|
License: MIT
|
|
@@ -57,6 +57,8 @@ Flock is a production-focused framework for orchestrating AI agents through **de
|
|
|
57
57
|
**Quick links:**
|
|
58
58
|
- **[Getting Started](https://whiteducksoftware.github.io/flock/getting-started/installation/)** - Installation and first steps
|
|
59
59
|
- **[Tutorials](https://whiteducksoftware.github.io/flock/tutorials/)** - Step-by-step learning path
|
|
60
|
+
- [Custom Engines: Emoji Vibes & Batch Brews](https://whiteducksoftware.github.io/flock/tutorials/custom-engines/)
|
|
61
|
+
- [Custom Agent Components: Foreshadow & Hype](https://whiteducksoftware.github.io/flock/tutorials/custom-agent-components/)
|
|
60
62
|
- **[User Guides](https://whiteducksoftware.github.io/flock/guides/)** - In-depth feature documentation
|
|
61
63
|
- **[API Reference](https://whiteducksoftware.github.io/flock/reference/api/)** - Complete API documentation
|
|
62
64
|
- **[Roadmap](https://whiteducksoftware.github.io/flock/about/roadmap/)** - What's coming in v1.0
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
flock/__init__.py,sha256=fvp4ltfaAGmYliShuTY_XVIpOUN6bMXbWiBnwb1NBoM,310
|
|
2
|
-
flock/agent.py,sha256=
|
|
2
|
+
flock/agent.py,sha256=zlhrY8jWE6LK8hmOHaQWbfnX7_T_6Eg6Qb7oviujJiY,42715
|
|
3
3
|
flock/artifact_collector.py,sha256=8rsg5NzmXeXKNT07TqX1_Z6aAGud2dXKzvS0jhjX3gQ,6372
|
|
4
4
|
flock/artifacts.py,sha256=3vQQ1J7QxTzeQBUGaNLiyojlmBv1NfdhFC98-qj8fpU,2541
|
|
5
5
|
flock/batch_accumulator.py,sha256=YcZSpdYMhm8wAGqKEF49NJ_Jl2HZX78NrJYqfyHqTuE,8003
|
|
6
6
|
flock/cli.py,sha256=lPtKxEXnGtyuTh0gyG3ixEIFS4Ty6Y0xsPd6SpUTD3U,4526
|
|
7
|
-
flock/components.py,sha256=
|
|
7
|
+
flock/components.py,sha256=7y2UWaIU0wBl44xO0SkU1m04ixbj4gd_FP9wFpsuZhY,7609
|
|
8
8
|
flock/correlation_engine.py,sha256=iGB6-G5F-B-pw-clLx4-pZEiUrK_Q2l3kOn4jtEO_kw,8103
|
|
9
9
|
flock/examples.py,sha256=eQb8k6EYBbUhauFuSN_0EIIu5KW0mTqJU0HM4-p14sc,3632
|
|
10
|
-
flock/orchestrator.py,sha256=
|
|
10
|
+
flock/orchestrator.py,sha256=YJ8M7HxCUjq4hZ9wj3XKbOfLPVq-_zeTPclhI0YBaA0,54530
|
|
11
11
|
flock/registry.py,sha256=s0-H-TMtOsDZiZQCc7T1tYiWQg3OZHn5T--jaI_INIc,4786
|
|
12
|
-
flock/runtime.py,sha256=
|
|
12
|
+
flock/runtime.py,sha256=uEq_zpCd--zqVAGU95uQt9QXqp2036iWo2lRr1JGlM8,8651
|
|
13
13
|
flock/service.py,sha256=JDdjjPTPH6NFezAr8x6svtqxIGXA7-AyHS11GF57g9Q,11041
|
|
14
14
|
flock/store.py,sha256=H6z1_y5uDp_4UnHWqrxNksyoSGlzeVTgLY3Sv-guSTU,45793
|
|
15
15
|
flock/subscription.py,sha256=0fqjGVAr-3u1azSsXJ-xVjnUgSSYVO2a0Gd_zln2tZA,5422
|
|
@@ -25,20 +25,22 @@ flock/dashboard/service.py,sha256=_jyHQ_hC9sFgj8mwtuW2r8labZ3NsBJ-1FYcKB0qq9E,52
|
|
|
25
25
|
flock/dashboard/websocket.py,sha256=_DCZApJPXc8OQnxFDFS9TA9ozq7kM73QByRj-_a8n-8,9508
|
|
26
26
|
flock/dashboard/models/__init__.py,sha256=T4Yz8IXMm7lBqa2HLDSv7WJBtaKcdZIlTrz6GHNFZxs,68
|
|
27
27
|
flock/dashboard/models/graph.py,sha256=oQAGAVMV32tim8xscUxZ9nFOci0RvryjdPf1sJgiGxQ,5402
|
|
28
|
-
flock/dashboard/static_v2/index.html,sha256=
|
|
29
|
-
flock/dashboard/static_v2/assets/index-DFRnI_mt.js,sha256=
|
|
28
|
+
flock/dashboard/static_v2/index.html,sha256=0KNfWwmpr0JmVNbalnsaQta0y3XlVhOQ_HSts2LuU3A,423
|
|
29
|
+
flock/dashboard/static_v2/assets/index-DFRnI_mt.js,sha256=qeezaWLISr_EKNBBh8v0jzspd2KRcz8jW1r0p7z0Ht0,764860
|
|
30
30
|
flock/dashboard/static_v2/assets/index-fPLNdmp1.css,sha256=skpvfkkrlw7WbmBh7HN-rUKAtKP-gpuLUH4klUgFHT4,74529
|
|
31
31
|
flock/engines/__init__.py,sha256=waNyObJ8PKCLFZL3WUFynxSK-V47m559P3Px-vl_OSc,124
|
|
32
|
-
flock/engines/dspy_engine.py,sha256=
|
|
33
|
-
flock/
|
|
32
|
+
flock/engines/dspy_engine.py,sha256=Q2stZi6jMjjNlhXvzmW3m-Y8AdVo1QuNunbnxEZl6rA,52232
|
|
33
|
+
flock/engines/examples/__init__.py,sha256=cDAjF8_NPL7nhpwa_xxgnPYRAXZWppKE2HkxmL8LGAI,126
|
|
34
|
+
flock/engines/examples/simple_batch_engine.py,sha256=RZVuFJHr3j_4dJZc2AXUcjKZJdct0A7UKOSwWgVEKk4,2235
|
|
35
|
+
flock/frontend/README.md,sha256=0dEzu4UxacGfSstz9_R0KSeFWJ1vNRi0p-KLIay_TfU,26212
|
|
34
36
|
flock/frontend/index.html,sha256=BFg1VR_YVAJ_MGN16xa7sT6wTGwtFYUhfJhGuKv89VM,312
|
|
35
|
-
flock/frontend/package-lock.json,sha256=
|
|
36
|
-
flock/frontend/package.json,sha256=
|
|
37
|
+
flock/frontend/package-lock.json,sha256=vNOaISeq3EfEDDEsFYPWFaMsqypDgQIE0P_u6tzE0g4,150798
|
|
38
|
+
flock/frontend/package.json,sha256=SFwHeNZ0mS9p4_enSXZNMtuXxSM5cl52dzEr-MuRZ90,1253
|
|
37
39
|
flock/frontend/tsconfig.json,sha256=B9p9jXohg_jrCZAq5_yIHvznpeXHiHQkwUZrVE2oMRA,705
|
|
38
40
|
flock/frontend/tsconfig.node.json,sha256=u5_YWSqeNkZBRBIZ8Q2E2q6bospcyF23mO-taRO7glc,233
|
|
39
41
|
flock/frontend/vite.config.ts,sha256=M76uTsyn7fvHI4NhaGyizOGmml0fJvuhSljOQm_UvGs,566
|
|
40
42
|
flock/frontend/vitest.config.ts,sha256=xSWyGrBv2Cy_5eeZA68NCO5AXS6q8WKZXXzqu2JnXPY,244
|
|
41
|
-
flock/frontend/docs/DESIGN_SYSTEM.md,sha256=
|
|
43
|
+
flock/frontend/docs/DESIGN_SYSTEM.md,sha256=a7hbH7kQCOrOQu3CMTYthTZTrFCQUIRcqtXaYHMkPbo,48498
|
|
42
44
|
flock/frontend/src/App.tsx,sha256=3vwTT_WJP7OUb0LJDVMXfkb5hD-uZZmiGBK2wl1QgeM,5233
|
|
43
45
|
flock/frontend/src/main.tsx,sha256=sfWsPgNn5AyDH4LJJLTz2c5OwOPl0o4oi-FArpqc-W4,354
|
|
44
46
|
flock/frontend/src/vite-env.d.ts,sha256=tDjMtvUVN9uIgSCHe__Jhp0-nZiIV21pkcQuyOjaryw,344
|
|
@@ -112,9 +114,9 @@ flock/frontend/src/components/settings/AdvancedSettings.tsx,sha256=Ngw5jAIRn3ocX
|
|
|
112
114
|
flock/frontend/src/components/settings/AppearanceSettings.tsx,sha256=UHH4BcJ9-F-nRJEJf2rzmX0W-0hvmeKo6AYNF3lZPWE,6872
|
|
113
115
|
flock/frontend/src/components/settings/GraphSettings.tsx,sha256=qX1F4hnsxGcCbn3oHP7nsPPMLyETHG3UL55kJ_3tA-s,3815
|
|
114
116
|
flock/frontend/src/components/settings/MultiSelect.tsx,sha256=aFjk8pt18UFHIPh-MXxEepjh-haNYhxEwzMiewSKFbU,7005
|
|
115
|
-
flock/frontend/src/components/settings/SettingsPanel.css,sha256=
|
|
117
|
+
flock/frontend/src/components/settings/SettingsPanel.css,sha256=SUUfMHGXVxX8GDDQcb0f6v7ZG983tXa9pC6Xrwctep8,7062
|
|
116
118
|
flock/frontend/src/components/settings/SettingsPanel.tsx,sha256=62GXPCQzYdbk17XYVOx_vCeWlVyFZSxp3w0JTkp3z60,4392
|
|
117
|
-
flock/frontend/src/components/settings/ThemeSelector.tsx,sha256=
|
|
119
|
+
flock/frontend/src/components/settings/ThemeSelector.tsx,sha256=FqyRtWhN8vm6cxfPI1pxFCCuCFDb4YXrwzdufwrXFqM,10538
|
|
118
120
|
flock/frontend/src/components/settings/TracingSettings.tsx,sha256=zHZrUuVQzUANclHfvTndm50zHYX1y-LecFnrloh2Hkc,14224
|
|
119
121
|
flock/frontend/src/hooks/useKeyboardShortcuts.ts,sha256=Dsf64nPtJdW2kV-a-bBpaq1VXZkXyxpioBnI1WIGduM,5019
|
|
120
122
|
flock/frontend/src/hooks/useModulePersistence.test.ts,sha256=kqYlFSzqYK_0jGxeRTeXORZ2KTDo3MdByJ2t2ivbg20,14323
|
|
@@ -125,7 +127,7 @@ flock/frontend/src/services/api.ts,sha256=kNHBHvbSk50M3pUtwDMNYwco5Cd4AbQbGGHkpR
|
|
|
125
127
|
flock/frontend/src/services/graphService.test.ts,sha256=wftS4QX-Bw6hogTWXVYaV3F7z6AN94d7iI8jrMVwwkg,11493
|
|
126
128
|
flock/frontend/src/services/graphService.ts,sha256=MaFdj8S2l7UOkQBkACDv29BuroMy1vJEhliVnsYkmwM,1964
|
|
127
129
|
flock/frontend/src/services/indexeddb.test.ts,sha256=lY1JzyKRd4Kaw135JlO7njy5rpaAsRRXLM8SwYxDhAc,27452
|
|
128
|
-
flock/frontend/src/services/indexeddb.ts,sha256=
|
|
130
|
+
flock/frontend/src/services/indexeddb.ts,sha256=2sXodUZAp3jSiS-3a7Bwvkjdjp_Tc47bk5eR_1f9qqw,28703
|
|
129
131
|
flock/frontend/src/services/layout.test.ts,sha256=-KwUxWCum_Rsyc5NIpk99UB3prfAkMO5ksJULhjOiwA,16174
|
|
130
132
|
flock/frontend/src/services/layout.ts,sha256=5WzlOv7OBlQXiUxrv4l1JwaAfHbUK1C99JOT0fQCNRY,9503
|
|
131
133
|
flock/frontend/src/services/themeApplicator.ts,sha256=utRFw-45e1IEOrI6lHkB_E_-5kc2kFKbN-veAUdXiOM,5802
|
|
@@ -144,7 +146,7 @@ flock/frontend/src/store/uiStore.ts,sha256=H5aH8YfaxOTfm3zCrT9A1QdC2dnHJWPUUg8uV
|
|
|
144
146
|
flock/frontend/src/store/wsStore.ts,sha256=vKaGd6H7UONN7A-8tCTOgdH8X2LxrZmVbZ7hRnfGQ2c,936
|
|
145
147
|
flock/frontend/src/styles/index.css,sha256=HVMvVEMvlFk8_vqxBdt8CzkleXkV-oSUcQUzom588PM,231
|
|
146
148
|
flock/frontend/src/styles/scrollbar.css,sha256=OHH2nryMANNvuJBloG_t1BPY_BtKMSb36Df4q2X47Rk,1207
|
|
147
|
-
flock/frontend/src/styles/variables.css,sha256=
|
|
149
|
+
flock/frontend/src/styles/variables.css,sha256=18UzMGPn_GOKWa2eXe1u0lJLI4yRLM7LjONASATwo6Q,13884
|
|
148
150
|
flock/frontend/src/test/setup.ts,sha256=pG1mhRrywFboBf3VdL9ew62xGBxDxeQfChxZLjOK_mQ,36
|
|
149
151
|
flock/frontend/src/types/filters.ts,sha256=zPa4AvL9xudvB6RZsqJCllMoLTnvPuBhqU6vLeWO89Y,1041
|
|
150
152
|
flock/frontend/src/types/graph.ts,sha256=mz62owu0OXO-xbzCPX2_arRRv0_Kp79tj5Ek-TqdHhI,4615
|
|
@@ -527,8 +529,8 @@ flock/themes/zenburned.toml,sha256=UEmquBbcAO3Zj652XKUwCsNoC2iQSlIh-q5c6DH-7Kc,1
|
|
|
527
529
|
flock/themes/zenwritten-dark.toml,sha256=-dgaUfg1iCr5Dv4UEeHv_cN4GrPUCWAiHSxWK20X1kI,1663
|
|
528
530
|
flock/themes/zenwritten-light.toml,sha256=G1iEheCPfBNsMTGaVpEVpDzYBHA_T-MV27rolUYolmE,1666
|
|
529
531
|
flock/utility/output_utility_component.py,sha256=yVHhlIIIoYKziI5UyT_zvQb4G-NsxCTgLwA1wXXTTj4,9047
|
|
530
|
-
flock_core-0.5.
|
|
531
|
-
flock_core-0.5.
|
|
532
|
-
flock_core-0.5.
|
|
533
|
-
flock_core-0.5.
|
|
534
|
-
flock_core-0.5.
|
|
532
|
+
flock_core-0.5.5.dist-info/METADATA,sha256=t-87LUTaHtfmkBW4U37oAhenOGcoq3WITQfRpuvrgsc,39584
|
|
533
|
+
flock_core-0.5.5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
534
|
+
flock_core-0.5.5.dist-info/entry_points.txt,sha256=UQdPmtHd97gSA_IdLt9MOd-1rrf_WO-qsQeIiHWVrp4,42
|
|
535
|
+
flock_core-0.5.5.dist-info/licenses/LICENSE,sha256=U3IZuTbC0yLj7huwJdldLBipSOHF4cPf6cUOodFiaBE,1072
|
|
536
|
+
flock_core-0.5.5.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|