flock-core 0.5.10__py3-none-any.whl → 0.5.20__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/__init__.py +1 -1
- flock/agent/__init__.py +30 -0
- flock/agent/builder_helpers.py +192 -0
- flock/agent/builder_validator.py +169 -0
- flock/agent/component_lifecycle.py +325 -0
- flock/agent/context_resolver.py +141 -0
- flock/agent/mcp_integration.py +212 -0
- flock/agent/output_processor.py +304 -0
- flock/api/__init__.py +20 -0
- flock/api/models.py +283 -0
- flock/{service.py → api/service.py} +121 -63
- flock/cli.py +2 -2
- flock/components/__init__.py +41 -0
- flock/components/agent/__init__.py +22 -0
- flock/{components.py → components/agent/base.py} +4 -3
- flock/{utility/output_utility_component.py → components/agent/output_utility.py} +12 -7
- flock/components/orchestrator/__init__.py +22 -0
- flock/{orchestrator_component.py → components/orchestrator/base.py} +5 -293
- flock/components/orchestrator/circuit_breaker.py +95 -0
- flock/components/orchestrator/collection.py +143 -0
- flock/components/orchestrator/deduplication.py +78 -0
- flock/core/__init__.py +30 -0
- flock/core/agent.py +953 -0
- flock/{artifacts.py → core/artifacts.py} +1 -1
- flock/{context_provider.py → core/context_provider.py} +3 -3
- flock/core/orchestrator.py +1102 -0
- flock/{store.py → core/store.py} +99 -454
- flock/{subscription.py → core/subscription.py} +1 -1
- flock/dashboard/collector.py +5 -5
- flock/dashboard/graph_builder.py +7 -7
- flock/dashboard/routes/__init__.py +21 -0
- flock/dashboard/routes/control.py +327 -0
- flock/dashboard/routes/helpers.py +340 -0
- flock/dashboard/routes/themes.py +76 -0
- flock/dashboard/routes/traces.py +521 -0
- flock/dashboard/routes/websocket.py +108 -0
- flock/dashboard/service.py +44 -1294
- flock/engines/dspy/__init__.py +20 -0
- flock/engines/dspy/artifact_materializer.py +216 -0
- flock/engines/dspy/signature_builder.py +474 -0
- flock/engines/dspy/streaming_executor.py +858 -0
- flock/engines/dspy_engine.py +45 -1330
- flock/engines/examples/simple_batch_engine.py +2 -2
- flock/examples.py +7 -7
- flock/logging/logging.py +1 -16
- flock/models/__init__.py +10 -0
- flock/models/system_artifacts.py +33 -0
- flock/orchestrator/__init__.py +45 -0
- flock/{artifact_collector.py → orchestrator/artifact_collector.py} +3 -3
- flock/orchestrator/artifact_manager.py +168 -0
- flock/{batch_accumulator.py → orchestrator/batch_accumulator.py} +2 -2
- flock/orchestrator/component_runner.py +389 -0
- flock/orchestrator/context_builder.py +167 -0
- flock/{correlation_engine.py → orchestrator/correlation_engine.py} +2 -2
- flock/orchestrator/event_emitter.py +167 -0
- flock/orchestrator/initialization.py +184 -0
- flock/orchestrator/lifecycle_manager.py +226 -0
- flock/orchestrator/mcp_manager.py +202 -0
- flock/orchestrator/scheduler.py +189 -0
- flock/orchestrator/server_manager.py +234 -0
- flock/orchestrator/tracing.py +147 -0
- flock/storage/__init__.py +10 -0
- flock/storage/artifact_aggregator.py +158 -0
- flock/storage/in_memory/__init__.py +6 -0
- flock/storage/in_memory/artifact_filter.py +114 -0
- flock/storage/in_memory/history_aggregator.py +115 -0
- flock/storage/sqlite/__init__.py +10 -0
- flock/storage/sqlite/agent_history_queries.py +154 -0
- flock/storage/sqlite/consumption_loader.py +100 -0
- flock/storage/sqlite/query_builder.py +112 -0
- flock/storage/sqlite/query_params_builder.py +91 -0
- flock/storage/sqlite/schema_manager.py +168 -0
- flock/storage/sqlite/summary_queries.py +194 -0
- flock/utils/__init__.py +14 -0
- flock/utils/async_utils.py +67 -0
- flock/{runtime.py → utils/runtime.py} +3 -3
- flock/utils/time_utils.py +53 -0
- flock/utils/type_resolution.py +38 -0
- flock/{utilities.py → utils/utilities.py} +2 -2
- flock/utils/validation.py +57 -0
- flock/utils/visibility.py +79 -0
- flock/utils/visibility_utils.py +134 -0
- {flock_core-0.5.10.dist-info → flock_core-0.5.20.dist-info}/METADATA +69 -61
- {flock_core-0.5.10.dist-info → flock_core-0.5.20.dist-info}/RECORD +89 -31
- flock/agent.py +0 -1578
- flock/orchestrator.py +0 -1746
- /flock/{visibility.py → core/visibility.py} +0 -0
- /flock/{helper → utils}/cli_helper.py +0 -0
- {flock_core-0.5.10.dist-info → flock_core-0.5.20.dist-info}/WHEEL +0 -0
- {flock_core-0.5.10.dist-info → flock_core-0.5.20.dist-info}/entry_points.txt +0 -0
- {flock_core-0.5.10.dist-info → flock_core-0.5.20.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
"""Agent output processing - validation, filtering, and artifact creation.
|
|
2
|
+
|
|
3
|
+
Phase 4: Extracted from agent.py to eliminate C-rated complexity in _make_outputs_for_group.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
from typing import TYPE_CHECKING, Any
|
|
10
|
+
|
|
11
|
+
from flock.core.artifacts import Artifact
|
|
12
|
+
from flock.logging.logging import get_logger
|
|
13
|
+
from flock.registry import type_registry
|
|
14
|
+
from flock.utils.runtime import Context, EvalResult
|
|
15
|
+
from flock.utils.type_resolution import TypeResolutionHelper
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from flock.agent import AgentOutput, OutputGroup
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
logger = get_logger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class OutputProcessor:
|
|
26
|
+
"""Handles agent output validation, filtering, and artifact creation.
|
|
27
|
+
|
|
28
|
+
This module encapsulates all output processing logic including:
|
|
29
|
+
- Engine contract validation (expected vs actual artifact counts)
|
|
30
|
+
- WHERE filtering (reduces artifacts based on predicates)
|
|
31
|
+
- VALIDATE checks (fail-fast on validation errors)
|
|
32
|
+
- Dynamic visibility resolution
|
|
33
|
+
- Artifact matching and payload extraction
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(self, agent_name: str):
|
|
37
|
+
"""Initialize OutputProcessor for a specific agent.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
agent_name: Name of the agent (for error messages and logging)
|
|
41
|
+
"""
|
|
42
|
+
self._agent_name = agent_name
|
|
43
|
+
self._logger = logging.getLogger(__name__)
|
|
44
|
+
|
|
45
|
+
async def make_outputs_for_group(
|
|
46
|
+
self,
|
|
47
|
+
ctx: Context,
|
|
48
|
+
result: EvalResult,
|
|
49
|
+
output_group: OutputGroup,
|
|
50
|
+
) -> list[Artifact]:
|
|
51
|
+
"""Phase 3/5: Validate, filter, and create artifacts for specific OutputGroup.
|
|
52
|
+
|
|
53
|
+
This function:
|
|
54
|
+
1. Validates that the engine fulfilled its contract (produced expected count)
|
|
55
|
+
2. Applies WHERE filtering (reduces artifacts, no error)
|
|
56
|
+
3. Applies VALIDATE checks (raises ValueError if validation fails)
|
|
57
|
+
4. Applies visibility (static or dynamic)
|
|
58
|
+
5. Creates final artifacts with agent metadata
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
ctx: Context for this group
|
|
62
|
+
result: EvalResult from engine for THIS group
|
|
63
|
+
output_group: OutputGroup defining expected outputs
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
List of artifacts matching this group's outputs
|
|
67
|
+
|
|
68
|
+
Raises:
|
|
69
|
+
ValueError: If engine violated contract or validation failed
|
|
70
|
+
"""
|
|
71
|
+
produced: list[Artifact] = []
|
|
72
|
+
|
|
73
|
+
for output_decl in output_group.outputs:
|
|
74
|
+
# 1. Find ALL matching artifacts for this type
|
|
75
|
+
expected_canonical = type_registry.resolve_name(output_decl.spec.type_name)
|
|
76
|
+
|
|
77
|
+
matching_artifacts: list[Artifact] = []
|
|
78
|
+
for artifact in result.artifacts:
|
|
79
|
+
artifact_canonical = TypeResolutionHelper.safe_resolve(
|
|
80
|
+
type_registry, artifact.type
|
|
81
|
+
)
|
|
82
|
+
if artifact_canonical == expected_canonical:
|
|
83
|
+
matching_artifacts.append(artifact)
|
|
84
|
+
|
|
85
|
+
# 2. STRICT VALIDATION: Engine must produce exactly what was promised
|
|
86
|
+
# (This happens BEFORE filtering so engine contract is validated first)
|
|
87
|
+
expected_count = output_decl.count
|
|
88
|
+
actual_count = len(matching_artifacts)
|
|
89
|
+
|
|
90
|
+
if actual_count != expected_count:
|
|
91
|
+
raise ValueError(
|
|
92
|
+
f"Engine contract violation in agent '{self._agent_name}': "
|
|
93
|
+
f"Expected {expected_count} artifact(s) of type '{output_decl.spec.type_name}', "
|
|
94
|
+
f"but engine produced {actual_count}. "
|
|
95
|
+
f"Check your engine implementation to ensure it generates the correct number of outputs."
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
# 3. Apply WHERE filtering (Phase 5)
|
|
99
|
+
# Filtering reduces the number of published artifacts (this is intentional)
|
|
100
|
+
# NOTE: Predicates expect Pydantic model instances, not dicts
|
|
101
|
+
model_cls = type_registry.resolve(output_decl.spec.type_name)
|
|
102
|
+
|
|
103
|
+
if output_decl.filter_predicate:
|
|
104
|
+
original_count = len(matching_artifacts)
|
|
105
|
+
filtered = []
|
|
106
|
+
for a in matching_artifacts:
|
|
107
|
+
# Reconstruct Pydantic model from payload dict
|
|
108
|
+
model_instance = model_cls(**a.payload)
|
|
109
|
+
if output_decl.filter_predicate(model_instance):
|
|
110
|
+
filtered.append(a)
|
|
111
|
+
matching_artifacts = filtered
|
|
112
|
+
logger.debug(
|
|
113
|
+
f"Agent {self._agent_name}: WHERE filter reduced artifacts from "
|
|
114
|
+
f"{original_count} to {len(matching_artifacts)} for type {output_decl.spec.type_name}"
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
# 4. Apply VALIDATE checks (Phase 5)
|
|
118
|
+
# Validation failures raise errors (fail-fast)
|
|
119
|
+
if output_decl.validate_predicate:
|
|
120
|
+
if callable(output_decl.validate_predicate):
|
|
121
|
+
# Single predicate
|
|
122
|
+
for artifact in matching_artifacts:
|
|
123
|
+
# Reconstruct Pydantic model from payload dict
|
|
124
|
+
model_instance = model_cls(**artifact.payload)
|
|
125
|
+
if not output_decl.validate_predicate(model_instance):
|
|
126
|
+
raise ValueError(
|
|
127
|
+
f"Validation failed for {output_decl.spec.type_name} "
|
|
128
|
+
f"in agent '{self._agent_name}'"
|
|
129
|
+
)
|
|
130
|
+
elif isinstance(output_decl.validate_predicate, list):
|
|
131
|
+
# List of (callable, error_msg) tuples
|
|
132
|
+
for artifact in matching_artifacts:
|
|
133
|
+
# Reconstruct Pydantic model from payload dict
|
|
134
|
+
model_instance = model_cls(**artifact.payload)
|
|
135
|
+
for check, error_msg in output_decl.validate_predicate:
|
|
136
|
+
if not check(model_instance):
|
|
137
|
+
raise ValueError(
|
|
138
|
+
f"{error_msg}: {output_decl.spec.type_name}"
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
# 5. Apply visibility and create artifacts (Phase 5)
|
|
142
|
+
for artifact_from_engine in matching_artifacts:
|
|
143
|
+
metadata = {
|
|
144
|
+
"correlation_id": ctx.correlation_id,
|
|
145
|
+
"artifact_id": artifact_from_engine.id, # Preserve engine's ID
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
# Determine visibility (static or dynamic)
|
|
149
|
+
visibility = output_decl.default_visibility
|
|
150
|
+
if callable(visibility):
|
|
151
|
+
# Dynamic visibility based on artifact content
|
|
152
|
+
# Reconstruct Pydantic model from payload dict
|
|
153
|
+
model_instance = model_cls(**artifact_from_engine.payload)
|
|
154
|
+
visibility = visibility(model_instance)
|
|
155
|
+
|
|
156
|
+
# Override metadata visibility
|
|
157
|
+
metadata["visibility"] = visibility
|
|
158
|
+
|
|
159
|
+
# Re-wrap the artifact with agent metadata
|
|
160
|
+
artifact = output_decl.apply(
|
|
161
|
+
artifact_from_engine.payload,
|
|
162
|
+
produced_by=self._agent_name,
|
|
163
|
+
metadata=metadata,
|
|
164
|
+
)
|
|
165
|
+
produced.append(artifact)
|
|
166
|
+
# Phase 6 SECURITY FIX: REMOVED publishing - orchestrator now handles it
|
|
167
|
+
# This fixes Vulnerability #2 (WRITE Bypass) - agents can no longer publish directly
|
|
168
|
+
# await ctx.board.publish(artifact)
|
|
169
|
+
|
|
170
|
+
return produced
|
|
171
|
+
|
|
172
|
+
async def make_outputs(
|
|
173
|
+
self, ctx: Context, result: EvalResult, output_groups: list[OutputGroup]
|
|
174
|
+
) -> list[Artifact]:
|
|
175
|
+
"""Output creation method for all output groups.
|
|
176
|
+
|
|
177
|
+
This method processes all output groups from a single engine evaluation.
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
ctx: Execution context
|
|
181
|
+
result: EvalResult from engine
|
|
182
|
+
output_groups: All output groups for the agent
|
|
183
|
+
|
|
184
|
+
Returns:
|
|
185
|
+
List of produced artifacts
|
|
186
|
+
"""
|
|
187
|
+
if not output_groups:
|
|
188
|
+
# Utility agents may not publish anything
|
|
189
|
+
return list(result.artifacts)
|
|
190
|
+
|
|
191
|
+
produced: list[Artifact] = []
|
|
192
|
+
|
|
193
|
+
# For Phase 2: Iterate ALL output_groups (even though we only have 1 engine call)
|
|
194
|
+
# Phase 3 will modify this to call engine once PER group
|
|
195
|
+
for output_group in output_groups:
|
|
196
|
+
for output_decl in output_group.outputs:
|
|
197
|
+
# Phase 6: Find the matching artifact from engine result to preserve its ID
|
|
198
|
+
matching_artifact = self.find_matching_artifact(output_decl, result)
|
|
199
|
+
|
|
200
|
+
payload = self.select_payload(output_decl, result)
|
|
201
|
+
if payload is None:
|
|
202
|
+
continue
|
|
203
|
+
metadata = {
|
|
204
|
+
"correlation_id": ctx.correlation_id,
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
# Phase 6: Preserve artifact ID from engine (for streaming message preview)
|
|
208
|
+
if matching_artifact:
|
|
209
|
+
metadata["artifact_id"] = matching_artifact.id
|
|
210
|
+
|
|
211
|
+
artifact = output_decl.apply(
|
|
212
|
+
payload, produced_by=self._agent_name, metadata=metadata
|
|
213
|
+
)
|
|
214
|
+
produced.append(artifact)
|
|
215
|
+
# Phase 6: REMOVED publishing - orchestrator now handles it
|
|
216
|
+
# await ctx.board.publish(artifact)
|
|
217
|
+
|
|
218
|
+
return produced
|
|
219
|
+
|
|
220
|
+
def prepare_group_context(
|
|
221
|
+
self, ctx: Context, group_idx: int, output_group: OutputGroup
|
|
222
|
+
) -> Context:
|
|
223
|
+
"""Phase 3: Prepare context specific to this OutputGroup.
|
|
224
|
+
|
|
225
|
+
Creates a modified context for this group's engine call, potentially
|
|
226
|
+
with group-specific instructions or metadata.
|
|
227
|
+
|
|
228
|
+
Args:
|
|
229
|
+
ctx: Base context
|
|
230
|
+
group_idx: Index of this group (0-based)
|
|
231
|
+
output_group: The OutputGroup being processed
|
|
232
|
+
|
|
233
|
+
Returns:
|
|
234
|
+
Context for this group (may be the same instance or modified)
|
|
235
|
+
"""
|
|
236
|
+
# For now, return the same context
|
|
237
|
+
# Phase 4 will add group-specific system prompts here
|
|
238
|
+
# Future: ctx.clone() and add group_description to system prompt
|
|
239
|
+
return ctx
|
|
240
|
+
|
|
241
|
+
def find_matching_artifact(
|
|
242
|
+
self, output_decl: AgentOutput, result: EvalResult
|
|
243
|
+
) -> Artifact | None:
|
|
244
|
+
"""Phase 6: Find artifact from engine result that matches this output declaration.
|
|
245
|
+
|
|
246
|
+
Returns the artifact object (with its ID) so we can preserve it when creating
|
|
247
|
+
the final published artifact. This ensures streaming events use the same ID.
|
|
248
|
+
|
|
249
|
+
Args:
|
|
250
|
+
output_decl: Output declaration to match
|
|
251
|
+
result: Engine result containing artifacts
|
|
252
|
+
|
|
253
|
+
Returns:
|
|
254
|
+
Matching artifact or None if not found
|
|
255
|
+
"""
|
|
256
|
+
if not result.artifacts:
|
|
257
|
+
return None
|
|
258
|
+
|
|
259
|
+
# Normalize the expected type name to canonical form
|
|
260
|
+
expected_canonical = type_registry.resolve_name(output_decl.spec.type_name)
|
|
261
|
+
|
|
262
|
+
for artifact in result.artifacts:
|
|
263
|
+
# Normalize artifact type name to canonical form for comparison
|
|
264
|
+
artifact_canonical = TypeResolutionHelper.safe_resolve(
|
|
265
|
+
type_registry, artifact.type
|
|
266
|
+
)
|
|
267
|
+
if artifact_canonical == expected_canonical:
|
|
268
|
+
return artifact
|
|
269
|
+
|
|
270
|
+
return None
|
|
271
|
+
|
|
272
|
+
def select_payload(
|
|
273
|
+
self, output_decl: AgentOutput, result: EvalResult
|
|
274
|
+
) -> dict[str, Any] | None:
|
|
275
|
+
"""Extract payload from engine result for output declaration.
|
|
276
|
+
|
|
277
|
+
Args:
|
|
278
|
+
output_decl: Output declaration defining expected type
|
|
279
|
+
result: Engine result containing artifacts
|
|
280
|
+
|
|
281
|
+
Returns:
|
|
282
|
+
Payload dict or None if not found
|
|
283
|
+
"""
|
|
284
|
+
# Normalize the expected type name to canonical form
|
|
285
|
+
expected_canonical = type_registry.resolve_name(output_decl.spec.type_name)
|
|
286
|
+
|
|
287
|
+
# Try to find payload in artifacts first
|
|
288
|
+
if result.artifacts:
|
|
289
|
+
for artifact in result.artifacts:
|
|
290
|
+
# Normalize artifact type name to canonical form for comparison
|
|
291
|
+
artifact_canonical = TypeResolutionHelper.safe_resolve(
|
|
292
|
+
type_registry, artifact.type
|
|
293
|
+
)
|
|
294
|
+
if artifact_canonical == expected_canonical:
|
|
295
|
+
return artifact.payload
|
|
296
|
+
|
|
297
|
+
# Fallback to state entries keyed by type name
|
|
298
|
+
maybe_data = result.state.get(output_decl.spec.type_name)
|
|
299
|
+
if isinstance(maybe_data, dict):
|
|
300
|
+
return maybe_data
|
|
301
|
+
return None
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
__all__ = ["OutputProcessor"]
|
flock/api/__init__.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""HTTP API service layer for Flock.
|
|
2
|
+
|
|
3
|
+
This module contains HTTP service implementations and API models for
|
|
4
|
+
serving the Flock orchestrator over HTTP with REST endpoints.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from flock.api.models import (
|
|
8
|
+
ArtifactPublishRequest,
|
|
9
|
+
ArtifactPublishResponse,
|
|
10
|
+
CorrelationStatusResponse,
|
|
11
|
+
)
|
|
12
|
+
from flock.api.service import BlackboardHTTPService
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"ArtifactPublishRequest",
|
|
17
|
+
"ArtifactPublishResponse",
|
|
18
|
+
"BlackboardHTTPService",
|
|
19
|
+
"CorrelationStatusResponse",
|
|
20
|
+
]
|
flock/api/models.py
ADDED
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
"""Pydantic response models for Flock REST API.
|
|
2
|
+
|
|
3
|
+
Provides proper OpenAPI schemas for all public API endpoints.
|
|
4
|
+
This improves API documentation and enables SDK generation.
|
|
5
|
+
|
|
6
|
+
All models maintain 100% backwards compatibility with existing wire format.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from typing import Any, Literal
|
|
10
|
+
|
|
11
|
+
from pydantic import BaseModel, Field
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# ============================================================================
|
|
15
|
+
# Agent Models
|
|
16
|
+
# ============================================================================
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class AgentSubscription(BaseModel):
|
|
20
|
+
"""Subscription configuration for an agent."""
|
|
21
|
+
|
|
22
|
+
types: list[str] = Field(description="Artifact types this subscription consumes")
|
|
23
|
+
mode: str = Field(description="Subscription mode (e.g., 'all', 'any')")
|
|
24
|
+
delivery: str = Field(description="Delivery mode (e.g., 'immediate', 'batch')")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class Agent(BaseModel):
|
|
28
|
+
"""Single agent representation."""
|
|
29
|
+
|
|
30
|
+
name: str = Field(description="Unique name of the agent")
|
|
31
|
+
description: str = Field(default="", description="Human-readable description")
|
|
32
|
+
subscriptions: list[AgentSubscription] = Field(
|
|
33
|
+
description="List of subscriptions this agent listens to"
|
|
34
|
+
)
|
|
35
|
+
outputs: list[str] = Field(description="Artifact types this agent can produce")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class AgentListResponse(BaseModel):
|
|
39
|
+
"""Response for GET /api/v1/agents."""
|
|
40
|
+
|
|
41
|
+
agents: list[Agent] = Field(description="List of all registered agents")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# ============================================================================
|
|
45
|
+
# Artifact Models
|
|
46
|
+
# ============================================================================
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class VisibilityInfo(BaseModel):
|
|
50
|
+
"""Artifact visibility configuration."""
|
|
51
|
+
|
|
52
|
+
kind: str = Field(description="Visibility kind (e.g., 'Public', 'Private')")
|
|
53
|
+
# Additional visibility fields added dynamically
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class ArtifactBase(BaseModel):
|
|
57
|
+
"""Base artifact representation with common fields."""
|
|
58
|
+
|
|
59
|
+
id: str = Field(description="Unique artifact identifier (UUID)")
|
|
60
|
+
type: str = Field(description="Artifact type name")
|
|
61
|
+
payload: dict[str, Any] = Field(description="Artifact payload data")
|
|
62
|
+
produced_by: str = Field(description="Name of agent/source that produced this")
|
|
63
|
+
visibility: dict[str, Any] = Field(description="Visibility configuration")
|
|
64
|
+
visibility_kind: str = Field(description="Visibility kind (Public/Private/etc)")
|
|
65
|
+
created_at: str = Field(
|
|
66
|
+
description="Timestamp when artifact was created (ISO 8601)"
|
|
67
|
+
)
|
|
68
|
+
correlation_id: str | None = Field(
|
|
69
|
+
None, description="Optional correlation ID for workflow tracking"
|
|
70
|
+
)
|
|
71
|
+
partition_key: str | None = Field(None, description="Optional partition key")
|
|
72
|
+
tags: list[str] = Field(default_factory=list, description="List of tags")
|
|
73
|
+
version: int = Field(description="Artifact version number")
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class ConsumptionRecord(BaseModel):
|
|
77
|
+
"""Record of an artifact being consumed by an agent."""
|
|
78
|
+
|
|
79
|
+
artifact_id: str = Field(description="ID of the artifact that was consumed")
|
|
80
|
+
consumer: str = Field(description="Name of the agent that consumed it")
|
|
81
|
+
run_id: str = Field(description="Run ID of the consumption")
|
|
82
|
+
correlation_id: str = Field(description="Correlation ID of the consumption")
|
|
83
|
+
consumed_at: str = Field(description="Timestamp of consumption (ISO 8601)")
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class ArtifactWithConsumptions(ArtifactBase):
|
|
87
|
+
"""Artifact with consumption metadata included."""
|
|
88
|
+
|
|
89
|
+
consumptions: list[ConsumptionRecord] = Field(
|
|
90
|
+
default_factory=list, description="List of consumption records"
|
|
91
|
+
)
|
|
92
|
+
consumed_by: list[str] = Field(
|
|
93
|
+
default_factory=list,
|
|
94
|
+
description="List of unique agent names that consumed this artifact",
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class PaginationInfo(BaseModel):
|
|
99
|
+
"""Pagination metadata."""
|
|
100
|
+
|
|
101
|
+
limit: int = Field(description="Number of items per page")
|
|
102
|
+
offset: int = Field(description="Offset into the result set")
|
|
103
|
+
total: int = Field(description="Total number of items matching the query")
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class ArtifactListResponse(BaseModel):
|
|
107
|
+
"""Response for GET /api/v1/artifacts."""
|
|
108
|
+
|
|
109
|
+
items: list[ArtifactBase | ArtifactWithConsumptions] = Field(
|
|
110
|
+
description="List of artifacts (may include consumption data if embed_meta=true)"
|
|
111
|
+
)
|
|
112
|
+
pagination: PaginationInfo = Field(description="Pagination information")
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class ArtifactPublishRequest(BaseModel):
|
|
116
|
+
"""Request body for POST /api/v1/artifacts."""
|
|
117
|
+
|
|
118
|
+
type: str = Field(description="Artifact type name")
|
|
119
|
+
payload: dict[str, Any] = Field(
|
|
120
|
+
default_factory=dict, description="Artifact payload data"
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class ArtifactPublishResponse(BaseModel):
|
|
125
|
+
"""Response for POST /api/v1/artifacts."""
|
|
126
|
+
|
|
127
|
+
status: Literal["accepted"] = Field(description="Publication status")
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class ArtifactSummary(BaseModel):
|
|
131
|
+
"""Summary statistics for artifacts."""
|
|
132
|
+
|
|
133
|
+
# Define based on actual summary structure from store
|
|
134
|
+
# This is a placeholder - update based on actual implementation
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
class ArtifactSummaryResponse(BaseModel):
|
|
138
|
+
"""Response for GET /api/v1/artifacts/summary."""
|
|
139
|
+
|
|
140
|
+
summary: dict[str, Any] = Field(description="Summary statistics")
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
# ============================================================================
|
|
144
|
+
# Agent Run Models
|
|
145
|
+
# ============================================================================
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
class AgentRunInput(BaseModel):
|
|
149
|
+
"""Input artifact for agent run."""
|
|
150
|
+
|
|
151
|
+
type: str = Field(description="Artifact type name")
|
|
152
|
+
payload: dict[str, Any] = Field(
|
|
153
|
+
default_factory=dict, description="Artifact payload data"
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class AgentRunRequest(BaseModel):
|
|
158
|
+
"""Request body for POST /api/v1/agents/{name}/run."""
|
|
159
|
+
|
|
160
|
+
inputs: list[AgentRunInput] = Field(
|
|
161
|
+
default_factory=list, description="List of input artifacts"
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
class ProducedArtifact(BaseModel):
|
|
166
|
+
"""Artifact produced by agent run."""
|
|
167
|
+
|
|
168
|
+
id: str = Field(description="Artifact ID (UUID)")
|
|
169
|
+
type: str = Field(description="Artifact type name")
|
|
170
|
+
payload: dict[str, Any] = Field(description="Artifact payload data")
|
|
171
|
+
produced_by: str = Field(description="Name of agent that produced this")
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
class AgentRunResponse(BaseModel):
|
|
175
|
+
"""Response for POST /api/v1/agents/{name}/run."""
|
|
176
|
+
|
|
177
|
+
artifacts: list[ProducedArtifact] = Field(
|
|
178
|
+
description="Artifacts produced by the agent run"
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
# ============================================================================
|
|
183
|
+
# Schema Discovery Models
|
|
184
|
+
# ============================================================================
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
class ArtifactTypeSchema(BaseModel):
|
|
188
|
+
"""Schema information for an artifact type."""
|
|
189
|
+
|
|
190
|
+
model_config = {"populate_by_name": True} # Allow using 'schema' as field name
|
|
191
|
+
|
|
192
|
+
name: str = Field(description="Type name")
|
|
193
|
+
schema_: dict[str, Any] = Field(
|
|
194
|
+
alias="schema", description="JSON Schema for this type"
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
class ArtifactTypesResponse(BaseModel):
|
|
199
|
+
"""Response for GET /api/artifact-types."""
|
|
200
|
+
|
|
201
|
+
artifact_types: list[ArtifactTypeSchema] = Field(
|
|
202
|
+
description="List of all registered artifact types with their schemas"
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
# ============================================================================
|
|
207
|
+
# Agent History Models
|
|
208
|
+
# ============================================================================
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
class AgentHistorySummary(BaseModel):
|
|
212
|
+
"""Summary of agent execution history."""
|
|
213
|
+
|
|
214
|
+
agent_id: str = Field(description="Agent identifier")
|
|
215
|
+
summary: dict[str, Any] = Field(description="History summary statistics")
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
# ============================================================================
|
|
219
|
+
# Correlation Status Models
|
|
220
|
+
# ============================================================================
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
class CorrelationStatusResponse(BaseModel):
|
|
224
|
+
"""Response for GET /api/v1/correlations/{correlation_id}/status."""
|
|
225
|
+
|
|
226
|
+
correlation_id: str = Field(description="The correlation ID")
|
|
227
|
+
state: Literal["active", "completed", "failed", "not_found"] = Field(
|
|
228
|
+
description="Workflow state: active (work pending), completed (success), failed (only errors), not_found (no artifacts)"
|
|
229
|
+
)
|
|
230
|
+
has_pending_work: bool = Field(
|
|
231
|
+
description="Whether the orchestrator has pending work for this correlation"
|
|
232
|
+
)
|
|
233
|
+
artifact_count: int = Field(
|
|
234
|
+
description="Total number of artifacts with this correlation_id"
|
|
235
|
+
)
|
|
236
|
+
error_count: int = Field(description="Number of WorkflowError artifacts")
|
|
237
|
+
started_at: str | None = Field(
|
|
238
|
+
None, description="Timestamp of first artifact (ISO 8601)"
|
|
239
|
+
)
|
|
240
|
+
last_activity_at: str | None = Field(
|
|
241
|
+
None, description="Timestamp of most recent artifact (ISO 8601)"
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
# ============================================================================
|
|
246
|
+
# Health & Metrics Models
|
|
247
|
+
# ============================================================================
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
class HealthResponse(BaseModel):
|
|
251
|
+
"""Response for GET /health."""
|
|
252
|
+
|
|
253
|
+
status: Literal["ok"] = Field(description="Health status")
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
__all__ = [
|
|
257
|
+
# Agent models
|
|
258
|
+
"Agent",
|
|
259
|
+
"AgentSubscription",
|
|
260
|
+
"AgentListResponse",
|
|
261
|
+
# Artifact models
|
|
262
|
+
"ArtifactBase",
|
|
263
|
+
"ArtifactWithConsumptions",
|
|
264
|
+
"ArtifactListResponse",
|
|
265
|
+
"ArtifactPublishRequest",
|
|
266
|
+
"ArtifactPublishResponse",
|
|
267
|
+
"ArtifactSummaryResponse",
|
|
268
|
+
"PaginationInfo",
|
|
269
|
+
"ConsumptionRecord",
|
|
270
|
+
# Agent run models
|
|
271
|
+
"AgentRunRequest",
|
|
272
|
+
"AgentRunResponse",
|
|
273
|
+
"ProducedArtifact",
|
|
274
|
+
# Schema discovery
|
|
275
|
+
"ArtifactTypesResponse",
|
|
276
|
+
"ArtifactTypeSchema",
|
|
277
|
+
# History
|
|
278
|
+
"AgentHistorySummary",
|
|
279
|
+
# Correlation status
|
|
280
|
+
"CorrelationStatusResponse",
|
|
281
|
+
# Health
|
|
282
|
+
"HealthResponse",
|
|
283
|
+
]
|