flock-core 0.5.8__py3-none-any.whl → 0.5.10__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of flock-core might be problematic. Click here for more details.

Files changed (52) hide show
  1. flock/agent.py +149 -62
  2. flock/api/themes.py +6 -2
  3. flock/artifact_collector.py +6 -3
  4. flock/batch_accumulator.py +3 -1
  5. flock/cli.py +3 -1
  6. flock/components.py +45 -56
  7. flock/context_provider.py +531 -0
  8. flock/correlation_engine.py +8 -4
  9. flock/dashboard/collector.py +48 -29
  10. flock/dashboard/events.py +10 -4
  11. flock/dashboard/launcher.py +3 -1
  12. flock/dashboard/models/graph.py +9 -3
  13. flock/dashboard/service.py +143 -72
  14. flock/dashboard/websocket.py +17 -4
  15. flock/engines/dspy_engine.py +174 -98
  16. flock/engines/examples/simple_batch_engine.py +9 -3
  17. flock/examples.py +6 -2
  18. flock/frontend/src/services/indexeddb.test.ts +4 -4
  19. flock/frontend/src/services/indexeddb.ts +1 -1
  20. flock/helper/cli_helper.py +14 -1
  21. flock/logging/auto_trace.py +6 -1
  22. flock/logging/formatters/enum_builder.py +3 -1
  23. flock/logging/formatters/theme_builder.py +32 -17
  24. flock/logging/formatters/themed_formatter.py +38 -22
  25. flock/logging/logging.py +21 -7
  26. flock/logging/telemetry.py +9 -3
  27. flock/logging/telemetry_exporter/duckdb_exporter.py +27 -25
  28. flock/logging/trace_and_logged.py +14 -5
  29. flock/mcp/__init__.py +3 -6
  30. flock/mcp/client.py +49 -19
  31. flock/mcp/config.py +12 -6
  32. flock/mcp/manager.py +6 -2
  33. flock/mcp/servers/sse/flock_sse_server.py +9 -3
  34. flock/mcp/servers/streamable_http/flock_streamable_http_server.py +6 -2
  35. flock/mcp/tool.py +18 -6
  36. flock/mcp/types/handlers.py +3 -1
  37. flock/mcp/types/types.py +9 -3
  38. flock/orchestrator.py +204 -50
  39. flock/orchestrator_component.py +15 -5
  40. flock/patches/dspy_streaming_patch.py +12 -4
  41. flock/registry.py +9 -3
  42. flock/runtime.py +69 -18
  43. flock/service.py +19 -6
  44. flock/store.py +29 -10
  45. flock/subscription.py +6 -4
  46. flock/utilities.py +41 -13
  47. flock/utility/output_utility_component.py +31 -11
  48. {flock_core-0.5.8.dist-info → flock_core-0.5.10.dist-info}/METADATA +134 -4
  49. {flock_core-0.5.8.dist-info → flock_core-0.5.10.dist-info}/RECORD +52 -51
  50. {flock_core-0.5.8.dist-info → flock_core-0.5.10.dist-info}/WHEEL +0 -0
  51. {flock_core-0.5.8.dist-info → flock_core-0.5.10.dist-info}/entry_points.txt +0 -0
  52. {flock_core-0.5.8.dist-info → flock_core-0.5.10.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,531 @@
1
+ """Context Provider - Security Boundary Layer.
2
+
3
+ The Context Provider is the CRITICAL SECURITY BOUNDARY between agents
4
+ (untrusted business logic) and the blackboard store (infrastructure).
5
+
6
+ SECURITY FIX (2025-10-16): This module implements the fix for three
7
+ critical security vulnerabilities:
8
+
9
+ - Vulnerability #1 (READ BYPASS): Agents could bypass visibility via ctx.board.list()
10
+ - Vulnerability #2 (WRITE BYPASS): Agents could bypass validation via ctx.board.publish()
11
+ - Vulnerability #3 (GOD MODE): Agents had unlimited ctx.orchestrator access
12
+
13
+ Solution: Context Provider enforces visibility filtering BEFORE agents see data.
14
+ Agents can NO LONGER bypass security because they don't have direct store access.
15
+
16
+ References:
17
+ - .flock/flock-research/context-provider/SECURITY_ANALYSIS.md
18
+ - docs/specs/007-context-provider-security-fix/PLAN.md
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ from abc import ABC, abstractmethod
24
+ from dataclasses import dataclass
25
+ from datetime import datetime, timedelta
26
+ from typing import Any, Protocol
27
+ from uuid import UUID
28
+
29
+ from flock.artifacts import Artifact
30
+ from flock.store import BlackboardStore, FilterConfig
31
+ from flock.visibility import AgentIdentity
32
+
33
+
34
+ @dataclass
35
+ class ContextRequest:
36
+ """Request for agent context.
37
+
38
+ This carries all information needed for providers to filter context
39
+ with mandatory visibility enforcement.
40
+
41
+ Attributes:
42
+ agent: Agent instance requesting context
43
+ correlation_id: Workflow identifier for filtering
44
+ store: Blackboard store for querying artifacts
45
+ agent_identity: Agent identity for visibility checks (includes labels, tenant_id)
46
+ exclude_ids: Set of artifact IDs to exclude from context (e.g., input artifacts)
47
+ """
48
+
49
+ agent: Any # Agent type to avoid circular import
50
+ correlation_id: UUID
51
+ store: BlackboardStore
52
+ agent_identity: AgentIdentity
53
+ exclude_ids: set[UUID] | None = None
54
+
55
+
56
+ class ContextProvider(Protocol):
57
+ """Protocol for context providers.
58
+
59
+ Context Providers are the MANDATORY security boundary between agents
60
+ and the blackboard store. All providers MUST enforce visibility filtering.
61
+
62
+ SECURITY REQUIREMENT: Every provider implementation MUST call
63
+ artifact.visibility.allows(agent_identity) before returning artifacts.
64
+ Any provider that doesn't enforce visibility is a SECURITY BUG.
65
+
66
+ Implementations:
67
+ - DefaultContextProvider: Filters by correlation_id + visibility (default behavior)
68
+ - FilteredContextProvider: Wraps FilterConfig for declarative filtering + visibility
69
+
70
+ Usage:
71
+ # Global provider
72
+ flock = Flock(context_provider=MyProvider())
73
+
74
+ # Per-agent provider
75
+ agent.with_context(MyProvider())
76
+ """
77
+
78
+ async def __call__(self, request: ContextRequest) -> list[Artifact]:
79
+ """Fetch context with MANDATORY visibility enforcement.
80
+
81
+ Args:
82
+ request: Context request with agent identity and correlation
83
+
84
+ Returns:
85
+ List of Artifact objects that agent is allowed to see.
86
+
87
+ SECURITY: Implementation MUST filter by visibility using:
88
+ artifact.visibility.allows(request.agent_identity)
89
+ """
90
+ ...
91
+
92
+
93
+ class BaseContextProvider(ABC):
94
+ """Base class enforcing MANDATORY visibility filtering for all context providers.
95
+
96
+ **SECURITY BY DESIGN**: Subclasses implement get_artifacts() to query/filter
97
+ artifacts. Visibility filtering and exclude_ids handling are enforced by this
98
+ base class and CANNOT BE BYPASSED.
99
+
100
+ This makes it architecturally impossible to create an insecure provider that
101
+ forgets to check visibility. The security logic is centralized and guaranteed.
102
+
103
+ Architecture:
104
+ - Subclass implements: get_artifacts() - custom query/filtering logic
105
+ - Base class enforces: visibility filtering, exclude_ids
106
+ - Result: 75% less code, 100% security coverage
107
+
108
+ Example:
109
+ >>> class MyProvider(BaseContextProvider):
110
+ ... async def get_artifacts(self, request):
111
+ ... # Just return artifacts - base class handles visibility!
112
+ ... artifacts, _ = await request.store.query_artifacts(...)
113
+ ... return artifacts
114
+ """
115
+
116
+ @abstractmethod
117
+ async def get_artifacts(self, request: ContextRequest) -> list[Artifact]:
118
+ """Query and filter artifacts (will be visibility-filtered automatically).
119
+
120
+ Subclasses implement this to define their filtering logic:
121
+ - DefaultContextProvider: Query all artifacts
122
+ - CorrelatedContextProvider: Query by correlation_id
123
+ - RecentContextProvider: Query all + sort by time + limit
124
+ - TimeWindowContextProvider: Query all + filter by time window
125
+ - EmptyContextProvider: Return empty list
126
+
127
+ Args:
128
+ request: Context request with agent identity, store, correlation_id
129
+
130
+ Returns:
131
+ List of artifacts (will be visibility-filtered by base class)
132
+ """
133
+
134
+ async def __call__(self, request: ContextRequest) -> list[Artifact]:
135
+ """Fetch context with MANDATORY visibility enforcement (cannot be bypassed).
136
+
137
+ SECURITY IMPLEMENTATION (enforced by base class):
138
+ 1. Get artifacts from subclass (custom filtering logic)
139
+ 2. Filter by visibility (security filtering) - MANDATORY, CANNOT BE BYPASSED
140
+ 3. Exclude specific artifacts (if requested)
141
+
142
+ Args:
143
+ request: Context request with agent identity
144
+
145
+ Returns:
146
+ List of Artifact objects agent can see (visibility-filtered)
147
+ """
148
+ # Step 1: Get artifacts from subclass implementation
149
+ artifacts = await self.get_artifacts(request)
150
+
151
+ # Step 2: CRITICAL SECURITY STEP - Filter by visibility (ENFORCED BY BASE CLASS)
152
+ # This is the FIX for Vulnerability #1 (READ BYPASS)
153
+ # Subclasses CANNOT bypass this - it's architecturally impossible
154
+ visible_artifacts = [
155
+ artifact
156
+ for artifact in artifacts
157
+ if artifact.visibility.allows(request.agent_identity)
158
+ ]
159
+
160
+ # Step 3: Exclude specific artifacts (e.g., input artifacts to avoid duplication)
161
+ if request.exclude_ids:
162
+ visible_artifacts = [
163
+ artifact
164
+ for artifact in visible_artifacts
165
+ if artifact.id not in request.exclude_ids
166
+ ]
167
+
168
+ return visible_artifacts
169
+
170
+
171
+ class DefaultContextProvider(BaseContextProvider):
172
+ """Default context provider - shows ALL artifacts on blackboard with MANDATORY visibility enforcement.
173
+
174
+ **EXPLICIT IS BETTER THAN IMPLICIT**: This provider shows agents everything on the
175
+ blackboard they're allowed to see (visibility-filtered). No magic correlation filtering!
176
+
177
+ If you want correlation-based filtering, use CorrelatedContextProvider explicitly.
178
+
179
+ This provider implements the secure replacement for the old vulnerable pattern:
180
+ Old (INSECURE): all_artifacts = await ctx.board.list()
181
+ New (SECURE): context = await provider(request)
182
+
183
+ Security Properties:
184
+ - ✅ Shows ALL artifacts on blackboard (no hidden filtering)
185
+ - ✅ Enforces visibility (security boundary) - CANNOT BE BYPASSED (via BaseContextProvider)
186
+ - ✅ Returns only artifacts agent is allowed to see
187
+ - ✅ No direct store access exposed to agents
188
+
189
+ This fixes Vulnerability #1 (READ BYPASS) where agents could access
190
+ any artifact regardless of visibility by calling ctx.board.list().
191
+
192
+ Example:
193
+ >>> # Global: All agents see everything they're allowed to
194
+ >>> flock = Flock(context_provider=DefaultContextProvider())
195
+ >>>
196
+ >>> # Per-agent: This agent sees full blackboard
197
+ >>> agent.context_provider = DefaultContextProvider()
198
+ """
199
+
200
+ async def get_artifacts(self, request: ContextRequest) -> list[Artifact]:
201
+ """Query ALL artifacts from blackboard (no filtering).
202
+
203
+ Visibility filtering is enforced by BaseContextProvider automatically.
204
+
205
+ Args:
206
+ request: Context request with store access
207
+
208
+ Returns:
209
+ All artifacts from blackboard (will be visibility-filtered by base class)
210
+ """
211
+ artifacts, _ = await request.store.query_artifacts(
212
+ FilterConfig(), # Empty filter = get everything
213
+ limit=-1, # Get all artifacts
214
+ )
215
+ return artifacts
216
+
217
+
218
+ class FilteredContextProvider(BaseContextProvider):
219
+ """Context provider with declarative filtering + MANDATORY visibility enforcement.
220
+
221
+ This provider combines declarative filtering (FilterConfig) with security
222
+ enforcement (visibility). It implements Phase 4 of the security fix.
223
+
224
+ Security Properties:
225
+ - ✅ Filters by FilterConfig (declarative filtering: tags, types, correlation, etc.)
226
+ - ✅ Enforces visibility (security boundary) - CANNOT BE BYPASSED (via BaseContextProvider)
227
+ - ✅ Returns only artifacts matching BOTH filters AND visibility
228
+ - ✅ No direct store access exposed to agents
229
+
230
+ Example:
231
+ >>> # Filter by tags + enforce visibility
232
+ >>> provider = FilteredContextProvider(
233
+ ... FilterConfig(tags={"important", "urgent"}), limit=10
234
+ ... )
235
+ >>> agent.with_context(provider)
236
+
237
+ >>> # Filter by type + enforce visibility
238
+ >>> provider = FilteredContextProvider(
239
+ ... FilterConfig(type_names={"Task", "Report"}), limit=50
240
+ ... )
241
+ """
242
+
243
+ def __init__(self, filter_config: FilterConfig, limit: int = 50):
244
+ """Initialize FilteredContextProvider with declarative filters.
245
+
246
+ Args:
247
+ filter_config: FilterConfig specifying declarative filters
248
+ limit: Maximum number of artifacts to return (default: 50)
249
+ """
250
+ self.filter_config = filter_config
251
+ self.limit = limit
252
+
253
+ async def get_artifacts(self, request: ContextRequest) -> list[Artifact]:
254
+ """Query artifacts using FilterConfig (declarative filtering).
255
+
256
+ Visibility filtering is enforced by BaseContextProvider automatically.
257
+
258
+ Args:
259
+ request: Context request with store access
260
+
261
+ Returns:
262
+ Artifacts matching FilterConfig (will be visibility-filtered by base class)
263
+ """
264
+ artifacts, _ = await request.store.query_artifacts(
265
+ self.filter_config,
266
+ limit=self.limit,
267
+ )
268
+ return artifacts
269
+
270
+
271
+ class BoundContextProvider:
272
+ """Security wrapper that binds a provider to a specific agent identity.
273
+
274
+ SECURITY FIX (2025-10-17): This wrapper prevents engines from forging
275
+ Context objects with fake agent_identity values. Even if an engine creates
276
+ a fake Context with a different agent_identity, this wrapper will use the
277
+ trusted identity that was bound at creation time by the orchestrator.
278
+
279
+ The orchestrator creates a BoundContextProvider for each agent execution,
280
+ binding it to the agent's true identity. Engines cannot bypass this because
281
+ they would need to create a fake BoundContextProvider, but they don't have
282
+ access to the real bound identity.
283
+
284
+ Example Attack (prevented):
285
+ >>> # Malicious engine tries to escalate privileges
286
+ >>> fake_ctx = Context(
287
+ ... ...
288
+ ... agent_identity=AgentIdentity(name="admin", labels={"admin"}), # FAKE
289
+ ... )
290
+ >>> # Provider ignores fake identity, uses bound identity instead
291
+ >>> context = await bound_provider(
292
+ ... request
293
+ ... ) # Still filters as original agent
294
+ """
295
+
296
+ def __init__(
297
+ self, inner_provider: ContextProvider, bound_agent_identity: AgentIdentity
298
+ ):
299
+ """Create provider bound to specific agent identity.
300
+
301
+ Args:
302
+ inner_provider: Wrapped provider (e.g., DefaultContextProvider)
303
+ bound_agent_identity: Trusted agent identity from orchestrator
304
+ """
305
+ self._inner = inner_provider
306
+ self._bound_identity = bound_agent_identity
307
+
308
+ async def __call__(self, request: ContextRequest) -> list[Artifact]:
309
+ """Fetch context using BOUND agent identity (ignoring request.agent_identity).
310
+
311
+ SECURITY: This method ignores request.agent_identity because it could
312
+ come from untrusted engine code. Instead, it uses the bound identity
313
+ that was set by the orchestrator at Context creation time.
314
+
315
+ Args:
316
+ request: Context request (agent_identity field is IGNORED)
317
+
318
+ Returns:
319
+ List of Artifact objects filtered by BOUND identity (not request identity)
320
+ """
321
+ # SECURITY: Replace untrusted agent_identity with trusted bound identity
322
+ secure_request = ContextRequest(
323
+ agent=request.agent,
324
+ correlation_id=request.correlation_id,
325
+ store=request.store,
326
+ agent_identity=self._bound_identity, # Use trusted identity, ignore request
327
+ exclude_ids=request.exclude_ids,
328
+ )
329
+ return await self._inner(secure_request)
330
+
331
+
332
+ class CorrelatedContextProvider(BaseContextProvider):
333
+ """Context provider that filters by correlation_id + visibility.
334
+
335
+ **EXPLICIT WORKFLOW ISOLATION**: Use this when you want agents to see only
336
+ artifacts from their specific workflow (correlation_id).
337
+
338
+ This is the explicit version of what DefaultContextProvider used to do implicitly.
339
+ Now you choose: full blackboard (DefaultContextProvider) or workflow-scoped
340
+ (CorrelatedContextProvider).
341
+
342
+ Security Properties:
343
+ - ✅ Filters by correlation_id (workflow boundary)
344
+ - ✅ Enforces visibility (security boundary) - CANNOT BE BYPASSED (via BaseContextProvider)
345
+ - ✅ Returns only workflow artifacts agent is allowed to see
346
+
347
+ Example:
348
+ >>> # Global: All agents only see their workflow
349
+ >>> flock = Flock(context_provider=CorrelatedContextProvider())
350
+ >>>
351
+ >>> # Per-agent: This agent only sees workflow artifacts
352
+ >>> agent.context_provider = CorrelatedContextProvider()
353
+ >>>
354
+ >>> # Use case: Multi-tenant SaaS with workflow isolation
355
+ >>> # Each workflow (correlation_id) is isolated from others
356
+ """
357
+
358
+ async def get_artifacts(self, request: ContextRequest) -> list[Artifact]:
359
+ """Query artifacts by correlation_id (workflow filtering).
360
+
361
+ Visibility filtering is enforced by BaseContextProvider automatically.
362
+
363
+ Args:
364
+ request: Context request with correlation_id
365
+
366
+ Returns:
367
+ Workflow artifacts (will be visibility-filtered by base class)
368
+ """
369
+ artifacts, _ = await request.store.query_artifacts(
370
+ FilterConfig(correlation_id=str(request.correlation_id)),
371
+ limit=-1, # Get all workflow artifacts
372
+ )
373
+ return artifacts
374
+
375
+
376
+ class RecentContextProvider(BaseContextProvider):
377
+ """Context provider that shows only the N most recent artifacts.
378
+
379
+ **TOKEN COST CONTROL**: Perfect for keeping context small and relevant by
380
+ showing only the most recent artifacts (sorted by creation time).
381
+
382
+ Security Properties:
383
+ - ✅ Limits context to N most recent artifacts
384
+ - ✅ Enforces visibility (security boundary) - CANNOT BE BYPASSED (via BaseContextProvider)
385
+ - ✅ Reduces token costs by limiting context size
386
+
387
+ Example:
388
+ >>> # Global: All agents see only last 10 artifacts
389
+ >>> flock = Flock(context_provider=RecentContextProvider(limit=10))
390
+ >>>
391
+ >>> # Per-agent: This agent sees only last 50 artifacts
392
+ >>> agent.context_provider = RecentContextProvider(limit=50)
393
+ >>>
394
+ >>> # Use case: High-volume systems where full history is too expensive
395
+ >>> # Agent only needs recent context to make decisions
396
+ """
397
+
398
+ def __init__(self, limit: int = 50):
399
+ """Initialize RecentContextProvider with artifact limit.
400
+
401
+ Args:
402
+ limit: Maximum number of recent artifacts to return (default: 50)
403
+ """
404
+ self.limit = limit
405
+
406
+ async def get_artifacts(self, request: ContextRequest) -> list[Artifact]:
407
+ """Query all artifacts and return N most recent.
408
+
409
+ Visibility filtering is enforced by BaseContextProvider automatically.
410
+
411
+ Args:
412
+ request: Context request with store access
413
+
414
+ Returns:
415
+ N most recent artifacts (will be visibility-filtered by base class)
416
+ """
417
+ artifacts, _ = await request.store.query_artifacts(
418
+ FilterConfig(),
419
+ limit=-1, # Get all artifacts
420
+ )
421
+
422
+ # Sort by creation time (most recent first) and limit
423
+ artifacts.sort(key=lambda a: a.created_at, reverse=True)
424
+ return artifacts[: self.limit]
425
+
426
+
427
+ class TimeWindowContextProvider(BaseContextProvider):
428
+ """Context provider that shows only artifacts from the last X hours.
429
+
430
+ **TIME-BASED FILTERING**: Perfect for real-time monitoring or event-driven
431
+ systems where only recent data is relevant.
432
+
433
+ Security Properties:
434
+ - ✅ Filters artifacts by time window (last X hours)
435
+ - ✅ Enforces visibility (security boundary) - CANNOT BE BYPASSED (via BaseContextProvider)
436
+ - ✅ Automatic cleanup of old context (no manual pruning needed)
437
+
438
+ Example:
439
+ >>> # Global: All agents see only last hour
440
+ >>> flock = Flock(context_provider=TimeWindowContextProvider(hours=1))
441
+ >>>
442
+ >>> # Per-agent: This agent sees last 24 hours
443
+ >>> agent.context_provider = TimeWindowContextProvider(hours=24)
444
+ >>>
445
+ >>> # Use case: Real-time monitoring dashboard
446
+ >>> # Only show events from last hour, ignore old data
447
+ """
448
+
449
+ def __init__(self, hours: int = 1):
450
+ """Initialize TimeWindowContextProvider with time window.
451
+
452
+ Args:
453
+ hours: Number of hours to look back (default: 1)
454
+ """
455
+ self.hours = hours
456
+
457
+ async def get_artifacts(self, request: ContextRequest) -> list[Artifact]:
458
+ """Query all artifacts and filter by time window.
459
+
460
+ Visibility filtering is enforced by BaseContextProvider automatically.
461
+
462
+ Args:
463
+ request: Context request with store access
464
+
465
+ Returns:
466
+ Artifacts within time window (will be visibility-filtered by base class)
467
+ """
468
+ cutoff = datetime.now() - timedelta(hours=self.hours)
469
+
470
+ artifacts, _ = await request.store.query_artifacts(
471
+ FilterConfig(),
472
+ limit=-1, # Get all artifacts
473
+ )
474
+
475
+ # Filter by time window
476
+ return [artifact for artifact in artifacts if artifact.created_at >= cutoff]
477
+
478
+
479
+ class EmptyContextProvider(BaseContextProvider):
480
+ """Context provider that returns NO historical context.
481
+
482
+ **STATELESS AGENTS**: Use this for purely functional agents that only
483
+ transform input → output without needing any historical context.
484
+
485
+ This is the ultimate token saver - zero context overhead!
486
+
487
+ Security Properties:
488
+ - ✅ Returns empty context (no artifacts)
489
+ - ✅ Enforces visibility (N/A - no artifacts to filter)
490
+ - ✅ Maximum token savings (zero context tokens)
491
+
492
+ Example:
493
+ >>> # Global: All agents are stateless (no context)
494
+ >>> flock = Flock(context_provider=EmptyContextProvider())
495
+ >>>
496
+ >>> # Per-agent: This agent is purely functional
497
+ >>> translator.context_provider = EmptyContextProvider()
498
+ >>>
499
+ >>> # Use case: Simple transformation agents
500
+ >>> # Agent: English → Spanish (no history needed)
501
+ >>> # Agent: Markdown → HTML (no history needed)
502
+ >>> # Agent: Image → Thumbnail (no history needed)
503
+ """
504
+
505
+ async def get_artifacts(self, request: ContextRequest) -> list[Artifact]:
506
+ """Return no artifacts (stateless agent).
507
+
508
+ Visibility filtering is enforced by BaseContextProvider automatically
509
+ (though there's nothing to filter).
510
+
511
+ Args:
512
+ request: Context request (ignored)
513
+
514
+ Returns:
515
+ Empty list (no artifacts)
516
+ """
517
+ return []
518
+
519
+
520
+ __all__ = [
521
+ "BaseContextProvider",
522
+ "BoundContextProvider",
523
+ "ContextProvider",
524
+ "ContextRequest",
525
+ "CorrelatedContextProvider",
526
+ "DefaultContextProvider",
527
+ "EmptyContextProvider",
528
+ "FilteredContextProvider",
529
+ "RecentContextProvider",
530
+ "TimeWindowContextProvider",
531
+ ]
@@ -44,7 +44,9 @@ class CorrelationGroup:
44
44
  self.created_at_sequence = (
45
45
  created_at_sequence # Global sequence when first artifact arrived
46
46
  )
47
- self.created_at_time: datetime | None = None # Timestamp when first artifact arrived
47
+ self.created_at_time: datetime | None = (
48
+ None # Timestamp when first artifact arrived
49
+ )
48
50
 
49
51
  # Waiting pool: type -> list of artifacts
50
52
  self.waiting_artifacts: dict[str, list[Artifact]] = defaultdict(list)
@@ -121,8 +123,8 @@ class CorrelationEngine:
121
123
  # Correlation state per (agent, subscription_index)
122
124
  # Key: (agent_name, subscription_index)
123
125
  # Value: dict[correlation_key, CorrelationGroup]
124
- self.correlation_groups: dict[tuple[str, int], dict[Any, CorrelationGroup]] = defaultdict(
125
- dict
126
+ self.correlation_groups: dict[tuple[str, int], dict[Any, CorrelationGroup]] = (
127
+ defaultdict(dict)
126
128
  )
127
129
 
128
130
  def add_artifact(
@@ -210,7 +212,9 @@ class CorrelationEngine:
210
212
 
211
213
  # Remove expired groups
212
214
  expired_keys = [
213
- key for key, group in groups.items() if group.is_expired(self.global_sequence)
215
+ key
216
+ for key, group in groups.items()
217
+ if group.is_expired(self.global_sequence)
214
218
  ]
215
219
 
216
220
  for key in expired_keys: