flock-core 0.5.0b75__py3-none-any.whl → 0.5.2__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/dashboard/models/__init__.py +1 -0
- flock/engines/dspy_engine.py +267 -12
- flock/orchestrator.py +3 -1
- flock/patches/__init__.py +5 -0
- flock/patches/dspy_streaming_patch.py +82 -0
- {flock_core-0.5.0b75.dist-info → flock_core-0.5.2.dist-info}/METADATA +32 -24
- {flock_core-0.5.0b75.dist-info → flock_core-0.5.2.dist-info}/RECORD +10 -7
- {flock_core-0.5.0b75.dist-info → flock_core-0.5.2.dist-info}/WHEEL +0 -0
- {flock_core-0.5.0b75.dist-info → flock_core-0.5.2.dist-info}/entry_points.txt +0 -0
- {flock_core-0.5.0b75.dist-info → flock_core-0.5.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Dashboard models for graph visualization and data structures."""
|
flock/engines/dspy_engine.py
CHANGED
|
@@ -242,19 +242,51 @@ class DSPyEngine(EngineComponent):
|
|
|
242
242
|
|
|
243
243
|
try:
|
|
244
244
|
if should_stream:
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
signature,
|
|
252
|
-
description=sys_desc,
|
|
253
|
-
payload=execution_payload,
|
|
254
|
-
agent=agent,
|
|
255
|
-
ctx=ctx,
|
|
256
|
-
pre_generated_artifact_id=pre_generated_artifact_id,
|
|
245
|
+
# Choose streaming method based on dashboard mode
|
|
246
|
+
is_dashboard = orchestrator and getattr(orchestrator, "is_dashboard", False)
|
|
247
|
+
|
|
248
|
+
# DEBUG: Log routing decision
|
|
249
|
+
logger.info(
|
|
250
|
+
f"[STREAMING ROUTER] agent={agent.name}, is_dashboard={is_dashboard}, orchestrator={orchestrator is not None}"
|
|
257
251
|
)
|
|
252
|
+
|
|
253
|
+
if is_dashboard:
|
|
254
|
+
# Dashboard mode: WebSocket-only streaming (no Rich overhead)
|
|
255
|
+
# This eliminates the Rich Live context that causes deadlocks with MCP tools
|
|
256
|
+
logger.info(
|
|
257
|
+
f"[STREAMING ROUTER] Routing {agent.name} to WebSocket-only method (dashboard mode)"
|
|
258
|
+
)
|
|
259
|
+
(
|
|
260
|
+
raw_result,
|
|
261
|
+
_stream_final_display_data,
|
|
262
|
+
) = await self._execute_streaming_websocket_only(
|
|
263
|
+
dspy_mod,
|
|
264
|
+
program,
|
|
265
|
+
signature,
|
|
266
|
+
description=sys_desc,
|
|
267
|
+
payload=execution_payload,
|
|
268
|
+
agent=agent,
|
|
269
|
+
ctx=ctx,
|
|
270
|
+
pre_generated_artifact_id=pre_generated_artifact_id,
|
|
271
|
+
)
|
|
272
|
+
else:
|
|
273
|
+
# CLI mode: Rich streaming with terminal display
|
|
274
|
+
logger.info(
|
|
275
|
+
f"[STREAMING ROUTER] Routing {agent.name} to Rich streaming method (CLI mode)"
|
|
276
|
+
)
|
|
277
|
+
(
|
|
278
|
+
raw_result,
|
|
279
|
+
_stream_final_display_data,
|
|
280
|
+
) = await self._execute_streaming(
|
|
281
|
+
dspy_mod,
|
|
282
|
+
program,
|
|
283
|
+
signature,
|
|
284
|
+
description=sys_desc,
|
|
285
|
+
payload=execution_payload,
|
|
286
|
+
agent=agent,
|
|
287
|
+
ctx=ctx,
|
|
288
|
+
pre_generated_artifact_id=pre_generated_artifact_id,
|
|
289
|
+
)
|
|
258
290
|
if not self.no_output and ctx:
|
|
259
291
|
ctx.state["_flock_stream_live_active"] = True
|
|
260
292
|
else:
|
|
@@ -503,6 +535,221 @@ class DSPyEngine(EngineComponent):
|
|
|
503
535
|
# Handle old format: direct payload (backwards compatible)
|
|
504
536
|
return program(description=description, input=payload, context=[])
|
|
505
537
|
|
|
538
|
+
async def _execute_streaming_websocket_only(
|
|
539
|
+
self,
|
|
540
|
+
dspy_mod,
|
|
541
|
+
program,
|
|
542
|
+
signature,
|
|
543
|
+
*,
|
|
544
|
+
description: str,
|
|
545
|
+
payload: dict[str, Any],
|
|
546
|
+
agent: Any,
|
|
547
|
+
ctx: Any = None,
|
|
548
|
+
pre_generated_artifact_id: Any = None,
|
|
549
|
+
) -> tuple[Any, None]:
|
|
550
|
+
"""Execute streaming for WebSocket only (no Rich display).
|
|
551
|
+
|
|
552
|
+
Optimized path for dashboard mode that skips all Rich formatting overhead.
|
|
553
|
+
Used when multiple agents stream in parallel to avoid terminal conflicts
|
|
554
|
+
and deadlocks with MCP tools.
|
|
555
|
+
|
|
556
|
+
This method eliminates the Rich Live context that can cause deadlocks when
|
|
557
|
+
combined with MCP tool execution and parallel agent streaming.
|
|
558
|
+
"""
|
|
559
|
+
logger.info(f"Agent {agent.name}: Starting WebSocket-only streaming (dashboard mode)")
|
|
560
|
+
|
|
561
|
+
# Get WebSocketManager
|
|
562
|
+
ws_manager = None
|
|
563
|
+
if ctx:
|
|
564
|
+
orchestrator = getattr(ctx, "orchestrator", None)
|
|
565
|
+
if orchestrator:
|
|
566
|
+
collector = getattr(orchestrator, "_dashboard_collector", None)
|
|
567
|
+
if collector:
|
|
568
|
+
ws_manager = getattr(collector, "_websocket_manager", None)
|
|
569
|
+
|
|
570
|
+
if not ws_manager:
|
|
571
|
+
logger.warning(
|
|
572
|
+
f"Agent {agent.name}: No WebSocket manager, falling back to standard execution"
|
|
573
|
+
)
|
|
574
|
+
result = await self._execute_standard(
|
|
575
|
+
dspy_mod, program, description=description, payload=payload
|
|
576
|
+
)
|
|
577
|
+
return result, None
|
|
578
|
+
|
|
579
|
+
# Get artifact type name for WebSocket events
|
|
580
|
+
artifact_type_name = "output"
|
|
581
|
+
if hasattr(agent, "outputs") and agent.outputs:
|
|
582
|
+
artifact_type_name = agent.outputs[0].spec.type_name
|
|
583
|
+
|
|
584
|
+
# Prepare stream listeners
|
|
585
|
+
listeners = []
|
|
586
|
+
try:
|
|
587
|
+
streaming_mod = getattr(dspy_mod, "streaming", None)
|
|
588
|
+
if streaming_mod and hasattr(streaming_mod, "StreamListener"):
|
|
589
|
+
for name, field in signature.output_fields.items():
|
|
590
|
+
if field.annotation is str:
|
|
591
|
+
listeners.append(streaming_mod.StreamListener(signature_field_name=name))
|
|
592
|
+
except Exception:
|
|
593
|
+
listeners = []
|
|
594
|
+
|
|
595
|
+
# Create streaming task
|
|
596
|
+
streaming_task = dspy_mod.streamify(
|
|
597
|
+
program,
|
|
598
|
+
is_async_program=True,
|
|
599
|
+
stream_listeners=listeners if listeners else None,
|
|
600
|
+
)
|
|
601
|
+
|
|
602
|
+
# Execute with appropriate payload format
|
|
603
|
+
if isinstance(payload, dict) and "input" in payload:
|
|
604
|
+
stream_generator = streaming_task(
|
|
605
|
+
description=description,
|
|
606
|
+
input=payload["input"],
|
|
607
|
+
context=payload.get("context", []),
|
|
608
|
+
)
|
|
609
|
+
else:
|
|
610
|
+
stream_generator = streaming_task(description=description, input=payload, context=[])
|
|
611
|
+
|
|
612
|
+
# Process stream (WebSocket only, no Rich display)
|
|
613
|
+
final_result = None
|
|
614
|
+
stream_sequence = 0
|
|
615
|
+
|
|
616
|
+
# Track background WebSocket broadcast tasks to prevent garbage collection
|
|
617
|
+
# Using fire-and-forget pattern to avoid blocking DSPy's streaming loop
|
|
618
|
+
ws_broadcast_tasks: set[asyncio.Task] = set()
|
|
619
|
+
|
|
620
|
+
async for value in stream_generator:
|
|
621
|
+
try:
|
|
622
|
+
from dspy.streaming import StatusMessage, StreamResponse
|
|
623
|
+
from litellm import ModelResponseStream
|
|
624
|
+
except Exception:
|
|
625
|
+
StatusMessage = object # type: ignore
|
|
626
|
+
StreamResponse = object # type: ignore
|
|
627
|
+
ModelResponseStream = object # type: ignore
|
|
628
|
+
|
|
629
|
+
if isinstance(value, StatusMessage):
|
|
630
|
+
token = getattr(value, "message", "")
|
|
631
|
+
if token:
|
|
632
|
+
try:
|
|
633
|
+
event = StreamingOutputEvent(
|
|
634
|
+
correlation_id=str(ctx.correlation_id)
|
|
635
|
+
if ctx and ctx.correlation_id
|
|
636
|
+
else "",
|
|
637
|
+
agent_name=agent.name,
|
|
638
|
+
run_id=ctx.task_id if ctx else "",
|
|
639
|
+
output_type="log",
|
|
640
|
+
content=str(token + "\n"),
|
|
641
|
+
sequence=stream_sequence,
|
|
642
|
+
is_final=False,
|
|
643
|
+
artifact_id=str(pre_generated_artifact_id),
|
|
644
|
+
artifact_type=artifact_type_name,
|
|
645
|
+
)
|
|
646
|
+
# Fire-and-forget to avoid blocking DSPy's streaming loop
|
|
647
|
+
task = asyncio.create_task(ws_manager.broadcast(event))
|
|
648
|
+
ws_broadcast_tasks.add(task)
|
|
649
|
+
task.add_done_callback(ws_broadcast_tasks.discard)
|
|
650
|
+
stream_sequence += 1
|
|
651
|
+
except Exception as e:
|
|
652
|
+
logger.warning(f"Failed to emit streaming event: {e}")
|
|
653
|
+
|
|
654
|
+
elif isinstance(value, StreamResponse):
|
|
655
|
+
token = getattr(value, "chunk", None)
|
|
656
|
+
if token:
|
|
657
|
+
try:
|
|
658
|
+
event = StreamingOutputEvent(
|
|
659
|
+
correlation_id=str(ctx.correlation_id)
|
|
660
|
+
if ctx and ctx.correlation_id
|
|
661
|
+
else "",
|
|
662
|
+
agent_name=agent.name,
|
|
663
|
+
run_id=ctx.task_id if ctx else "",
|
|
664
|
+
output_type="llm_token",
|
|
665
|
+
content=str(token),
|
|
666
|
+
sequence=stream_sequence,
|
|
667
|
+
is_final=False,
|
|
668
|
+
artifact_id=str(pre_generated_artifact_id),
|
|
669
|
+
artifact_type=artifact_type_name,
|
|
670
|
+
)
|
|
671
|
+
# Fire-and-forget to avoid blocking DSPy's streaming loop
|
|
672
|
+
task = asyncio.create_task(ws_manager.broadcast(event))
|
|
673
|
+
ws_broadcast_tasks.add(task)
|
|
674
|
+
task.add_done_callback(ws_broadcast_tasks.discard)
|
|
675
|
+
stream_sequence += 1
|
|
676
|
+
except Exception as e:
|
|
677
|
+
logger.warning(f"Failed to emit streaming event: {e}")
|
|
678
|
+
|
|
679
|
+
elif isinstance(value, ModelResponseStream):
|
|
680
|
+
chunk = value
|
|
681
|
+
token = chunk.choices[0].delta.content or ""
|
|
682
|
+
if token:
|
|
683
|
+
try:
|
|
684
|
+
event = StreamingOutputEvent(
|
|
685
|
+
correlation_id=str(ctx.correlation_id)
|
|
686
|
+
if ctx and ctx.correlation_id
|
|
687
|
+
else "",
|
|
688
|
+
agent_name=agent.name,
|
|
689
|
+
run_id=ctx.task_id if ctx else "",
|
|
690
|
+
output_type="llm_token",
|
|
691
|
+
content=str(token),
|
|
692
|
+
sequence=stream_sequence,
|
|
693
|
+
is_final=False,
|
|
694
|
+
artifact_id=str(pre_generated_artifact_id),
|
|
695
|
+
artifact_type=artifact_type_name,
|
|
696
|
+
)
|
|
697
|
+
# Fire-and-forget to avoid blocking DSPy's streaming loop
|
|
698
|
+
task = asyncio.create_task(ws_manager.broadcast(event))
|
|
699
|
+
ws_broadcast_tasks.add(task)
|
|
700
|
+
task.add_done_callback(ws_broadcast_tasks.discard)
|
|
701
|
+
stream_sequence += 1
|
|
702
|
+
except Exception as e:
|
|
703
|
+
logger.warning(f"Failed to emit streaming event: {e}")
|
|
704
|
+
|
|
705
|
+
elif isinstance(value, dspy_mod.Prediction):
|
|
706
|
+
final_result = value
|
|
707
|
+
# Send final events
|
|
708
|
+
try:
|
|
709
|
+
event = StreamingOutputEvent(
|
|
710
|
+
correlation_id=str(ctx.correlation_id)
|
|
711
|
+
if ctx and ctx.correlation_id
|
|
712
|
+
else "",
|
|
713
|
+
agent_name=agent.name,
|
|
714
|
+
run_id=ctx.task_id if ctx else "",
|
|
715
|
+
output_type="log",
|
|
716
|
+
content=f"\nAmount of output tokens: {stream_sequence}",
|
|
717
|
+
sequence=stream_sequence,
|
|
718
|
+
is_final=True,
|
|
719
|
+
artifact_id=str(pre_generated_artifact_id),
|
|
720
|
+
artifact_type=artifact_type_name,
|
|
721
|
+
)
|
|
722
|
+
# Fire-and-forget to avoid blocking DSPy's streaming loop
|
|
723
|
+
task = asyncio.create_task(ws_manager.broadcast(event))
|
|
724
|
+
ws_broadcast_tasks.add(task)
|
|
725
|
+
task.add_done_callback(ws_broadcast_tasks.discard)
|
|
726
|
+
|
|
727
|
+
event = StreamingOutputEvent(
|
|
728
|
+
correlation_id=str(ctx.correlation_id)
|
|
729
|
+
if ctx and ctx.correlation_id
|
|
730
|
+
else "",
|
|
731
|
+
agent_name=agent.name,
|
|
732
|
+
run_id=ctx.task_id if ctx else "",
|
|
733
|
+
output_type="log",
|
|
734
|
+
content="--- End of output ---",
|
|
735
|
+
sequence=stream_sequence + 1,
|
|
736
|
+
is_final=True,
|
|
737
|
+
artifact_id=str(pre_generated_artifact_id),
|
|
738
|
+
artifact_type=artifact_type_name,
|
|
739
|
+
)
|
|
740
|
+
# Fire-and-forget to avoid blocking DSPy's streaming loop
|
|
741
|
+
task = asyncio.create_task(ws_manager.broadcast(event))
|
|
742
|
+
ws_broadcast_tasks.add(task)
|
|
743
|
+
task.add_done_callback(ws_broadcast_tasks.discard)
|
|
744
|
+
except Exception as e:
|
|
745
|
+
logger.warning(f"Failed to emit final streaming event: {e}")
|
|
746
|
+
|
|
747
|
+
if final_result is None:
|
|
748
|
+
raise RuntimeError(f"Agent {agent.name}: Streaming did not yield a final prediction")
|
|
749
|
+
|
|
750
|
+
logger.info(f"Agent {agent.name}: WebSocket streaming completed ({stream_sequence} tokens)")
|
|
751
|
+
return final_result, None
|
|
752
|
+
|
|
506
753
|
async def _execute_streaming(
|
|
507
754
|
self,
|
|
508
755
|
dspy_mod,
|
|
@@ -930,3 +1177,11 @@ __all__ = ["DSPyEngine"]
|
|
|
930
1177
|
|
|
931
1178
|
# Apply the Rich Live patch when this module is imported
|
|
932
1179
|
_apply_live_patch_on_import()
|
|
1180
|
+
|
|
1181
|
+
# Apply the DSPy streaming patch to fix deadlocks with MCP tools
|
|
1182
|
+
try:
|
|
1183
|
+
from flock.patches.dspy_streaming_patch import apply_patch as apply_dspy_streaming_patch
|
|
1184
|
+
|
|
1185
|
+
apply_dspy_streaming_patch()
|
|
1186
|
+
except Exception:
|
|
1187
|
+
pass # Silently ignore if patch fails to apply
|
flock/orchestrator.py
CHANGED
|
@@ -668,8 +668,10 @@ class Flock(metaclass=AutoTracedMeta):
|
|
|
668
668
|
>>> # Publish with tags for channel routing
|
|
669
669
|
>>> await orchestrator.publish(task, tags={"urgent", "backend"})
|
|
670
670
|
"""
|
|
671
|
-
init_console(clear_screen=True, show_banner=True, model=self.model)
|
|
672
671
|
self.is_dashboard = is_dashboard
|
|
672
|
+
# Only show banner in CLI mode, not dashboard mode
|
|
673
|
+
if not self.is_dashboard:
|
|
674
|
+
init_console(clear_screen=True, show_banner=True, model=self.model)
|
|
673
675
|
# Handle different input types
|
|
674
676
|
if isinstance(obj, Artifact):
|
|
675
677
|
# Already an artifact - publish as-is
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Monkey-patch for DSPy's sync_send_to_stream function.
|
|
3
|
+
|
|
4
|
+
The original DSPy implementation blocks the event loop with future.result(),
|
|
5
|
+
causing deadlocks when using MCP tools with dashboard streaming.
|
|
6
|
+
|
|
7
|
+
This patch replaces it with a non-blocking fire-and-forget approach.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import asyncio
|
|
11
|
+
import logging
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def patched_sync_send_to_stream(stream, message):
|
|
17
|
+
"""Non-blocking replacement for DSPy's sync_send_to_stream.
|
|
18
|
+
|
|
19
|
+
Instead of blocking with future.result(), this version:
|
|
20
|
+
1. Schedules the send as a background task (fire-and-forget)
|
|
21
|
+
2. Never blocks the calling thread
|
|
22
|
+
3. Logs errors but doesn't raise them
|
|
23
|
+
|
|
24
|
+
This allows MCP tool callbacks to complete without deadlocking.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
async def _send():
|
|
28
|
+
try:
|
|
29
|
+
await stream.send(message)
|
|
30
|
+
except Exception as e:
|
|
31
|
+
logger.debug(f"DSPy status message send failed (non-critical): {e}")
|
|
32
|
+
|
|
33
|
+
try:
|
|
34
|
+
# Try to get the running event loop
|
|
35
|
+
loop = asyncio.get_running_loop()
|
|
36
|
+
|
|
37
|
+
# Schedule as a background task (fire-and-forget)
|
|
38
|
+
# This won't block - the task runs independently
|
|
39
|
+
loop.create_task(_send())
|
|
40
|
+
|
|
41
|
+
except RuntimeError:
|
|
42
|
+
# No event loop running - this is a sync context
|
|
43
|
+
# We can safely create a new loop and run the task
|
|
44
|
+
try:
|
|
45
|
+
asyncio.run(_send())
|
|
46
|
+
except Exception as e:
|
|
47
|
+
logger.debug(f"DSPy status message send failed in sync context (non-critical): {e}")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def apply_patch():
|
|
51
|
+
"""Apply the monkey-patch to DSPy's streaming module."""
|
|
52
|
+
try:
|
|
53
|
+
import dspy.streaming.messages as dspy_messages
|
|
54
|
+
|
|
55
|
+
# Store original for reference (in case we need to restore)
|
|
56
|
+
if not hasattr(dspy_messages, "_original_sync_send_to_stream"):
|
|
57
|
+
dspy_messages._original_sync_send_to_stream = dspy_messages.sync_send_to_stream
|
|
58
|
+
|
|
59
|
+
# Replace with our non-blocking version
|
|
60
|
+
dspy_messages.sync_send_to_stream = patched_sync_send_to_stream
|
|
61
|
+
|
|
62
|
+
logger.info("Applied DSPy streaming patch - status messages are now non-blocking")
|
|
63
|
+
return True
|
|
64
|
+
|
|
65
|
+
except Exception as e:
|
|
66
|
+
logger.warning(f"Failed to apply DSPy streaming patch: {e}")
|
|
67
|
+
return False
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def restore_original():
|
|
71
|
+
"""Restore the original DSPy function (for testing/debugging)."""
|
|
72
|
+
try:
|
|
73
|
+
import dspy.streaming.messages as dspy_messages
|
|
74
|
+
|
|
75
|
+
if hasattr(dspy_messages, "_original_sync_send_to_stream"):
|
|
76
|
+
dspy_messages.sync_send_to_stream = dspy_messages._original_sync_send_to_stream
|
|
77
|
+
logger.info("Restored original DSPy streaming function")
|
|
78
|
+
return True
|
|
79
|
+
|
|
80
|
+
except Exception as e:
|
|
81
|
+
logger.warning(f"Failed to restore original DSPy function: {e}")
|
|
82
|
+
return False
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: flock-core
|
|
3
|
-
Version: 0.5.
|
|
4
|
-
Summary:
|
|
3
|
+
Version: 0.5.2
|
|
4
|
+
Summary: Flock: A declrative framework for building and orchestrating AI agents.
|
|
5
5
|
Author-email: Andre Ratzenberger <andre.ratzenberger@whiteduck.de>
|
|
6
|
+
License: MIT
|
|
6
7
|
License-File: LICENSE
|
|
7
8
|
Requires-Python: >=3.10
|
|
8
9
|
Requires-Dist: aiosqlite>=0.20.0
|
|
9
10
|
Requires-Dist: devtools>=0.12.2
|
|
10
|
-
Requires-Dist: dspy==3.0.
|
|
11
|
+
Requires-Dist: dspy==3.0.4b1
|
|
11
12
|
Requires-Dist: duckdb>=1.1.0
|
|
12
13
|
Requires-Dist: fastapi>=0.117.1
|
|
13
14
|
Requires-Dist: hanging-threads>=2.0.7
|
|
@@ -31,13 +32,13 @@ Requires-Dist: websockets>=15.0.1
|
|
|
31
32
|
Description-Content-Type: text/markdown
|
|
32
33
|
|
|
33
34
|
<p align="center">
|
|
34
|
-
<img alt="Flock Banner" src="
|
|
35
|
+
<img alt="Flock Banner" src="docs/assets/images/flock.png" width="800">
|
|
35
36
|
</p>
|
|
36
37
|
<p align="center">
|
|
37
|
-
<a href="https://
|
|
38
|
+
<a href="https://whiteducksoftware.github.io/flock/" target="_blank"><img alt="Documentation" src="https://img.shields.io/badge/docs-online-blue?style=for-the-badge&logo=readthedocs"></a>
|
|
38
39
|
<a href="https://pypi.org/project/flock-core/" target="_blank"><img alt="PyPI Version" src="https://img.shields.io/pypi/v/flock-core?style=for-the-badge&logo=pypi&label=pip%20version"></a>
|
|
39
40
|
<img alt="Python Version" src="https://img.shields.io/badge/python-3.10%2B-blue?style=for-the-badge&logo=python">
|
|
40
|
-
<a href="LICENSE" target="_blank"><img alt="License" src="https://img.shields.io/
|
|
41
|
+
<a href="LICENSE" target="_blank"><img alt="License" src="https://img.shields.io/github/license/whiteducksoftware/flock?style=for-the-badge"></a>
|
|
41
42
|
<a href="https://whiteduck.de" target="_blank"><img alt="Built by white duck" src="https://img.shields.io/badge/Built%20by-white%20duck%20GmbH-white?style=for-the-badge&labelColor=black"></a>
|
|
42
43
|
<img alt="Test Coverage" src="https://img.shields.io/badge/coverage-77%25-green?style=for-the-badge">
|
|
43
44
|
<img alt="Tests" src="https://img.shields.io/badge/tests-743%20passing-brightgreen?style=for-the-badge">
|
|
@@ -51,14 +52,14 @@ Description-Content-Type: text/markdown
|
|
|
51
52
|
|
|
52
53
|
Flock is a production-focused framework for orchestrating AI agents through **declarative type contracts** and **blackboard architecture**—proven patterns from distributed systems, decades of experience with microservice architectures, and classical AI—now applied to modern LLMs.
|
|
53
54
|
|
|
54
|
-
**📖 [Read the full documentation →](https://
|
|
55
|
+
**📖 [Read the full documentation →](https://whiteducksoftware.github.io/flock)**
|
|
55
56
|
|
|
56
57
|
**Quick links:**
|
|
57
|
-
- **[Getting Started](https://
|
|
58
|
-
- **[Tutorials](https://
|
|
59
|
-
- **[User Guides](https://
|
|
60
|
-
- **[API Reference](https://
|
|
61
|
-
- **[Roadmap](https://
|
|
58
|
+
- **[Getting Started](https://whiteducksoftware.github.io/flock/getting-started/installation/)** - Installation and first steps
|
|
59
|
+
- **[Tutorials](https://whiteducksoftware.github.io/flock/tutorials/)** - Step-by-step learning path
|
|
60
|
+
- **[User Guides](https://whiteducksoftware.github.io/flock/guides/)** - In-depth feature documentation
|
|
61
|
+
- **[API Reference](https://whiteducksoftware.github.io/flock/reference/api/)** - Complete API documentation
|
|
62
|
+
- **[Roadmap](https://whiteducksoftware.github.io/flock/about/roadmap/)** - What's coming in v1.0
|
|
62
63
|
|
|
63
64
|
---
|
|
64
65
|
|
|
@@ -675,13 +676,15 @@ Flock uses a fundamentally different coordination pattern than most multi-agent
|
|
|
675
676
|
- Type-safe retrieval API (`get_by_type()`)
|
|
676
677
|
|
|
677
678
|
**⚠️ What's missing for large-scale production:**
|
|
678
|
-
- **
|
|
679
|
-
- **Advanced retry logic** - Basic only
|
|
679
|
+
- **Advanced retry logic** - Basic only (exponential backoff planned)
|
|
680
680
|
- **Event replay** - No Kafka integration yet
|
|
681
681
|
- **Kubernetes-native deployment** - No Helm chart yet
|
|
682
682
|
- **OAuth/RBAC** - Dashboard has no auth
|
|
683
683
|
|
|
684
|
-
|
|
684
|
+
**✅ Available today:**
|
|
685
|
+
- **Persistent blackboard** - SQLiteBlackboardStore (see above)
|
|
686
|
+
|
|
687
|
+
All missing features planned for v1.0
|
|
685
688
|
|
|
686
689
|
### Recommended Use Cases Today
|
|
687
690
|
|
|
@@ -873,24 +876,29 @@ pip install flock-core
|
|
|
873
876
|
# Set API key
|
|
874
877
|
export OPENAI_API_KEY="sk-..."
|
|
875
878
|
|
|
876
|
-
# Try the
|
|
879
|
+
# Try the examples
|
|
877
880
|
git clone https://github.com/whiteducksoftware/flock-flow.git
|
|
878
881
|
cd flock-flow
|
|
879
|
-
|
|
882
|
+
|
|
883
|
+
# CLI examples with detailed output
|
|
884
|
+
uv run python examples/01-cli/01_declarative_pizza.py
|
|
885
|
+
|
|
886
|
+
# Dashboard examples with visualization
|
|
887
|
+
uv run python examples/02-dashboard/01_declarative_pizza.py
|
|
880
888
|
```
|
|
881
889
|
|
|
882
890
|
**Learn by doing:**
|
|
883
|
-
- 📚 [
|
|
884
|
-
-
|
|
885
|
-
-
|
|
886
|
-
- 📖 [Documentation](https://
|
|
891
|
+
- 📚 [Examples README](examples/README.md) - 12-step learning path from basics to advanced
|
|
892
|
+
- 🖥️ [CLI Examples](examples/01-cli/) - Detailed console output examples (01-12)
|
|
893
|
+
- 📊 [Dashboard Examples](examples/02-dashboard/) - Interactive visualization examples (01-12)
|
|
894
|
+
- 📖 [Documentation](https://whiteducksoftware.github.io/flock) - Complete online documentation
|
|
887
895
|
- 📘 [AGENTS.md](AGENTS.md) - Development guide
|
|
888
896
|
|
|
889
897
|
---
|
|
890
898
|
|
|
891
899
|
## Contributing
|
|
892
900
|
|
|
893
|
-
We're building Flock in the open. See **[Contributing Guide](https://
|
|
901
|
+
We're building Flock in the open. See **[Contributing Guide](https://whiteducksoftware.github.io/flock/about/contributing/)** for development setup, or check [CONTRIBUTING.md](CONTRIBUTING.md) and [AGENTS.md](AGENTS.md) locally.
|
|
894
902
|
|
|
895
903
|
**We welcome:**
|
|
896
904
|
- Bug reports and feature requests
|
|
@@ -953,12 +961,12 @@ We're calling this 0.5 to signal:
|
|
|
953
961
|
|
|
954
962
|
**"Declarative contracts eliminate prompt hell. Blackboard architecture eliminates graph spaghetti. Proven patterns applied to modern LLMs."**
|
|
955
963
|
|
|
956
|
-
[⭐ Star on GitHub](https://github.com/whiteducksoftware/flock-flow) | [📖 Documentation](https://
|
|
964
|
+
[⭐ Star on GitHub](https://github.com/whiteducksoftware/flock-flow) | [📖 Documentation](https://whiteducksoftware.github.io/flock) | [🚀 Try Examples](examples/) | [💼 Enterprise Support](mailto:support@whiteduck.de)
|
|
957
965
|
|
|
958
966
|
</div>
|
|
959
967
|
|
|
960
968
|
---
|
|
961
969
|
|
|
962
|
-
**Last Updated:** October
|
|
970
|
+
**Last Updated:** October 13, 2025
|
|
963
971
|
**Version:** Flock 0.5.0 (Blackboard Edition)
|
|
964
972
|
**Status:** Production-Ready Core, Enterprise Features Roadmapped
|
|
@@ -4,7 +4,7 @@ flock/artifacts.py,sha256=3vQQ1J7QxTzeQBUGaNLiyojlmBv1NfdhFC98-qj8fpU,2541
|
|
|
4
4
|
flock/cli.py,sha256=lPtKxEXnGtyuTh0gyG3ixEIFS4Ty6Y0xsPd6SpUTD3U,4526
|
|
5
5
|
flock/components.py,sha256=17vhNMHKc3VUruEbSdb9YNKcDziIe0coS9jpfWBmX4o,6259
|
|
6
6
|
flock/examples.py,sha256=eQb8k6EYBbUhauFuSN_0EIIu5KW0mTqJU0HM4-p14sc,3632
|
|
7
|
-
flock/orchestrator.py,sha256=
|
|
7
|
+
flock/orchestrator.py,sha256=f7FD1i2bcpkHEER0w3DEgzcWp1AmmBSbegVODdhYxdY,36661
|
|
8
8
|
flock/registry.py,sha256=s0-H-TMtOsDZiZQCc7T1tYiWQg3OZHn5T--jaI_INIc,4786
|
|
9
9
|
flock/runtime.py,sha256=UG-38u578h628mSddBmyZn2VIzFQ0wlHCpCALFiScqA,8518
|
|
10
10
|
flock/service.py,sha256=JDdjjPTPH6NFezAr8x6svtqxIGXA7-AyHS11GF57g9Q,11041
|
|
@@ -20,12 +20,13 @@ flock/dashboard/graph_builder.py,sha256=u5Hp007Wr_v3aQI_0YzTit9hAGDc-gUiilnj-p11
|
|
|
20
20
|
flock/dashboard/launcher.py,sha256=dCfwaeMLtyIkik3aVSEsbBdABS5ADRlKWYkih7sF5hM,8196
|
|
21
21
|
flock/dashboard/service.py,sha256=PzyJS3T3lSoC1sULdfKqUJyu2UUhtSV5xEG0LPhO4qI,39071
|
|
22
22
|
flock/dashboard/websocket.py,sha256=_DCZApJPXc8OQnxFDFS9TA9ozq7kM73QByRj-_a8n-8,9508
|
|
23
|
+
flock/dashboard/models/__init__.py,sha256=T4Yz8IXMm7lBqa2HLDSv7WJBtaKcdZIlTrz6GHNFZxs,68
|
|
23
24
|
flock/dashboard/models/graph.py,sha256=t159CMNavFYZB-qvSE5HRh9Zi_HOR4RYc8VzIngpx_E,5316
|
|
24
25
|
flock/dashboard/static_v2/index.html,sha256=iWL-fgTY2egom20DMvSOpuYZ6pC5AVvdNZcj-EvPm5w,422
|
|
25
26
|
flock/dashboard/static_v2/assets/index-DFRnI_mt.js,sha256=PVrBXxY3Igbf9dqtsWoCbhogEhibapLAwlMYV5Gv4pY,764870
|
|
26
27
|
flock/dashboard/static_v2/assets/index-fPLNdmp1.css,sha256=skpvfkkrlw7WbmBh7HN-rUKAtKP-gpuLUH4klUgFHT4,74529
|
|
27
28
|
flock/engines/__init__.py,sha256=waNyObJ8PKCLFZL3WUFynxSK-V47m559P3Px-vl_OSc,124
|
|
28
|
-
flock/engines/dspy_engine.py,sha256=
|
|
29
|
+
flock/engines/dspy_engine.py,sha256=o_k9mnWuxGN2jEL8KQmaBCGi6Aw8hQb9AZPsQRLRw3U,50921
|
|
29
30
|
flock/frontend/README.md,sha256=R1gqm524Xa5PZAkfl-IJDEf6VsBJ6ThrpY7SyDawjog,26232
|
|
30
31
|
flock/frontend/index.html,sha256=BFg1VR_YVAJ_MGN16xa7sT6wTGwtFYUhfJhGuKv89VM,312
|
|
31
32
|
flock/frontend/package-lock.json,sha256=1edo2JDle0if_MljnK4Xto7Q7hhGUEBQAvQFqKy9RzQ,150798
|
|
@@ -181,6 +182,8 @@ flock/mcp/types/handlers.py,sha256=6ukkSMv1VZSfk2QDUiJnm8xifHnQvWZsxWXqN21BYSg,7
|
|
|
181
182
|
flock/mcp/types/types.py,sha256=ZbzbVihABFnfmZz2X-CCN7hQDzaSY0T-en43PFbFwQQ,11469
|
|
182
183
|
flock/mcp/util/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
183
184
|
flock/mcp/util/helpers.py,sha256=jO8DqqSb_S4dyvsxv5vlMGRoMx92Su_5-Uv17gQNuLU,740
|
|
185
|
+
flock/patches/__init__.py,sha256=2turkIzq6C_DpIKDwah7aPd-1RFOAGX7dxTkaBd9X4w,193
|
|
186
|
+
flock/patches/dspy_streaming_patch.py,sha256=1vJ7t_PlBBza6MAQPiBhFn2A0k42tdYGs-WpC77BmDA,2762
|
|
184
187
|
flock/themes/3024-day.toml,sha256=uOVHqEzSyHx0WlUk3D0lne4RBsNBAPCTy3C58yU7kEY,667
|
|
185
188
|
flock/themes/3024-night.toml,sha256=qsXUwd6ZYz6J-R129_Ao2TKlvvK60svhZJJjB5c8Tfo,1667
|
|
186
189
|
flock/themes/aardvark-blue.toml,sha256=5ZgsxP3pWLPN3yJ2Wd9ErCo7fy_VJpIfje4kriDKlqo,1667
|
|
@@ -518,8 +521,8 @@ flock/themes/zenburned.toml,sha256=UEmquBbcAO3Zj652XKUwCsNoC2iQSlIh-q5c6DH-7Kc,1
|
|
|
518
521
|
flock/themes/zenwritten-dark.toml,sha256=-dgaUfg1iCr5Dv4UEeHv_cN4GrPUCWAiHSxWK20X1kI,1663
|
|
519
522
|
flock/themes/zenwritten-light.toml,sha256=G1iEheCPfBNsMTGaVpEVpDzYBHA_T-MV27rolUYolmE,1666
|
|
520
523
|
flock/utility/output_utility_component.py,sha256=yVHhlIIIoYKziI5UyT_zvQb4G-NsxCTgLwA1wXXTTj4,9047
|
|
521
|
-
flock_core-0.5.
|
|
522
|
-
flock_core-0.5.
|
|
523
|
-
flock_core-0.5.
|
|
524
|
-
flock_core-0.5.
|
|
525
|
-
flock_core-0.5.
|
|
524
|
+
flock_core-0.5.2.dist-info/METADATA,sha256=tCrSJS6TpMQftwK0xDIAmW3cK2-2r33WaM58tyvX4i0,36666
|
|
525
|
+
flock_core-0.5.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
526
|
+
flock_core-0.5.2.dist-info/entry_points.txt,sha256=UQdPmtHd97gSA_IdLt9MOd-1rrf_WO-qsQeIiHWVrp4,42
|
|
527
|
+
flock_core-0.5.2.dist-info/licenses/LICENSE,sha256=U3IZuTbC0yLj7huwJdldLBipSOHF4cPf6cUOodFiaBE,1072
|
|
528
|
+
flock_core-0.5.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|