flock-core 0.5.0b75__py3-none-any.whl → 0.5.1__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.1.dist-info}/METADATA +20 -13
- {flock_core-0.5.0b75.dist-info → flock_core-0.5.1.dist-info}/RECORD +10 -7
- {flock_core-0.5.0b75.dist-info → flock_core-0.5.1.dist-info}/WHEEL +0 -0
- {flock_core-0.5.0b75.dist-info → flock_core-0.5.1.dist-info}/entry_points.txt +0 -0
- {flock_core-0.5.0b75.dist-info → flock_core-0.5.1.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,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: flock-core
|
|
3
|
-
Version: 0.5.
|
|
4
|
-
Summary:
|
|
3
|
+
Version: 0.5.1
|
|
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-File: LICENSE
|
|
7
7
|
Requires-Python: >=3.10
|
|
8
8
|
Requires-Dist: aiosqlite>=0.20.0
|
|
9
9
|
Requires-Dist: devtools>=0.12.2
|
|
10
|
-
Requires-Dist: dspy==3.0.
|
|
10
|
+
Requires-Dist: dspy==3.0.4b1
|
|
11
11
|
Requires-Dist: duckdb>=1.1.0
|
|
12
12
|
Requires-Dist: fastapi>=0.117.1
|
|
13
13
|
Requires-Dist: hanging-threads>=2.0.7
|
|
@@ -31,7 +31,7 @@ Requires-Dist: websockets>=15.0.1
|
|
|
31
31
|
Description-Content-Type: text/markdown
|
|
32
32
|
|
|
33
33
|
<p align="center">
|
|
34
|
-
<img alt="Flock Banner" src="
|
|
34
|
+
<img alt="Flock Banner" src="docs/assets/images/flock.png" width="800">
|
|
35
35
|
</p>
|
|
36
36
|
<p align="center">
|
|
37
37
|
<a href="https://docs.flock.whiteduck.de" target="_blank"><img alt="Documentation" src="https://img.shields.io/badge/docs-online-blue?style=for-the-badge&logo=readthedocs"></a>
|
|
@@ -675,13 +675,15 @@ Flock uses a fundamentally different coordination pattern than most multi-agent
|
|
|
675
675
|
- Type-safe retrieval API (`get_by_type()`)
|
|
676
676
|
|
|
677
677
|
**⚠️ What's missing for large-scale production:**
|
|
678
|
-
- **
|
|
679
|
-
- **Advanced retry logic** - Basic only
|
|
678
|
+
- **Advanced retry logic** - Basic only (exponential backoff planned)
|
|
680
679
|
- **Event replay** - No Kafka integration yet
|
|
681
680
|
- **Kubernetes-native deployment** - No Helm chart yet
|
|
682
681
|
- **OAuth/RBAC** - Dashboard has no auth
|
|
683
682
|
|
|
684
|
-
|
|
683
|
+
**✅ Available today:**
|
|
684
|
+
- **Persistent blackboard** - SQLiteBlackboardStore (see above)
|
|
685
|
+
|
|
686
|
+
All missing features planned for v1.0
|
|
685
687
|
|
|
686
688
|
### Recommended Use Cases Today
|
|
687
689
|
|
|
@@ -873,16 +875,21 @@ pip install flock-core
|
|
|
873
875
|
# Set API key
|
|
874
876
|
export OPENAI_API_KEY="sk-..."
|
|
875
877
|
|
|
876
|
-
# Try the
|
|
878
|
+
# Try the examples
|
|
877
879
|
git clone https://github.com/whiteducksoftware/flock-flow.git
|
|
878
880
|
cd flock-flow
|
|
879
|
-
|
|
881
|
+
|
|
882
|
+
# CLI examples with detailed output
|
|
883
|
+
uv run python examples/01-cli/01_declarative_pizza.py
|
|
884
|
+
|
|
885
|
+
# Dashboard examples with visualization
|
|
886
|
+
uv run python examples/02-dashboard/01_declarative_pizza.py
|
|
880
887
|
```
|
|
881
888
|
|
|
882
889
|
**Learn by doing:**
|
|
883
|
-
- 📚 [
|
|
884
|
-
-
|
|
885
|
-
-
|
|
890
|
+
- 📚 [Examples README](examples/README.md) - 12-step learning path from basics to advanced
|
|
891
|
+
- 🖥️ [CLI Examples](examples/01-cli/) - Detailed console output examples (01-12)
|
|
892
|
+
- 📊 [Dashboard Examples](examples/02-dashboard/) - Interactive visualization examples (01-12)
|
|
886
893
|
- 📖 [Documentation](https://docs.flock.whiteduck.de) - Complete online documentation
|
|
887
894
|
- 📘 [AGENTS.md](AGENTS.md) - Development guide
|
|
888
895
|
|
|
@@ -959,6 +966,6 @@ We're calling this 0.5 to signal:
|
|
|
959
966
|
|
|
960
967
|
---
|
|
961
968
|
|
|
962
|
-
**Last Updated:** October
|
|
969
|
+
**Last Updated:** October 13, 2025
|
|
963
970
|
**Version:** Flock 0.5.0 (Blackboard Edition)
|
|
964
971
|
**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.1.dist-info/METADATA,sha256=1JRDjG80BKu6R0vwSpZRPXCGlYLKiHoDSVwpRLezdNQ,36531
|
|
525
|
+
flock_core-0.5.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
526
|
+
flock_core-0.5.1.dist-info/entry_points.txt,sha256=UQdPmtHd97gSA_IdLt9MOd-1rrf_WO-qsQeIiHWVrp4,42
|
|
527
|
+
flock_core-0.5.1.dist-info/licenses/LICENSE,sha256=U3IZuTbC0yLj7huwJdldLBipSOHF4cPf6cUOodFiaBE,1072
|
|
528
|
+
flock_core-0.5.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|