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.

Files changed (91) hide show
  1. flock/__init__.py +1 -1
  2. flock/agent/__init__.py +30 -0
  3. flock/agent/builder_helpers.py +192 -0
  4. flock/agent/builder_validator.py +169 -0
  5. flock/agent/component_lifecycle.py +325 -0
  6. flock/agent/context_resolver.py +141 -0
  7. flock/agent/mcp_integration.py +212 -0
  8. flock/agent/output_processor.py +304 -0
  9. flock/api/__init__.py +20 -0
  10. flock/api/models.py +283 -0
  11. flock/{service.py → api/service.py} +121 -63
  12. flock/cli.py +2 -2
  13. flock/components/__init__.py +41 -0
  14. flock/components/agent/__init__.py +22 -0
  15. flock/{components.py → components/agent/base.py} +4 -3
  16. flock/{utility/output_utility_component.py → components/agent/output_utility.py} +12 -7
  17. flock/components/orchestrator/__init__.py +22 -0
  18. flock/{orchestrator_component.py → components/orchestrator/base.py} +5 -293
  19. flock/components/orchestrator/circuit_breaker.py +95 -0
  20. flock/components/orchestrator/collection.py +143 -0
  21. flock/components/orchestrator/deduplication.py +78 -0
  22. flock/core/__init__.py +30 -0
  23. flock/core/agent.py +953 -0
  24. flock/{artifacts.py → core/artifacts.py} +1 -1
  25. flock/{context_provider.py → core/context_provider.py} +3 -3
  26. flock/core/orchestrator.py +1102 -0
  27. flock/{store.py → core/store.py} +99 -454
  28. flock/{subscription.py → core/subscription.py} +1 -1
  29. flock/dashboard/collector.py +5 -5
  30. flock/dashboard/graph_builder.py +7 -7
  31. flock/dashboard/routes/__init__.py +21 -0
  32. flock/dashboard/routes/control.py +327 -0
  33. flock/dashboard/routes/helpers.py +340 -0
  34. flock/dashboard/routes/themes.py +76 -0
  35. flock/dashboard/routes/traces.py +521 -0
  36. flock/dashboard/routes/websocket.py +108 -0
  37. flock/dashboard/service.py +44 -1294
  38. flock/engines/dspy/__init__.py +20 -0
  39. flock/engines/dspy/artifact_materializer.py +216 -0
  40. flock/engines/dspy/signature_builder.py +474 -0
  41. flock/engines/dspy/streaming_executor.py +858 -0
  42. flock/engines/dspy_engine.py +45 -1330
  43. flock/engines/examples/simple_batch_engine.py +2 -2
  44. flock/examples.py +7 -7
  45. flock/logging/logging.py +1 -16
  46. flock/models/__init__.py +10 -0
  47. flock/models/system_artifacts.py +33 -0
  48. flock/orchestrator/__init__.py +45 -0
  49. flock/{artifact_collector.py → orchestrator/artifact_collector.py} +3 -3
  50. flock/orchestrator/artifact_manager.py +168 -0
  51. flock/{batch_accumulator.py → orchestrator/batch_accumulator.py} +2 -2
  52. flock/orchestrator/component_runner.py +389 -0
  53. flock/orchestrator/context_builder.py +167 -0
  54. flock/{correlation_engine.py → orchestrator/correlation_engine.py} +2 -2
  55. flock/orchestrator/event_emitter.py +167 -0
  56. flock/orchestrator/initialization.py +184 -0
  57. flock/orchestrator/lifecycle_manager.py +226 -0
  58. flock/orchestrator/mcp_manager.py +202 -0
  59. flock/orchestrator/scheduler.py +189 -0
  60. flock/orchestrator/server_manager.py +234 -0
  61. flock/orchestrator/tracing.py +147 -0
  62. flock/storage/__init__.py +10 -0
  63. flock/storage/artifact_aggregator.py +158 -0
  64. flock/storage/in_memory/__init__.py +6 -0
  65. flock/storage/in_memory/artifact_filter.py +114 -0
  66. flock/storage/in_memory/history_aggregator.py +115 -0
  67. flock/storage/sqlite/__init__.py +10 -0
  68. flock/storage/sqlite/agent_history_queries.py +154 -0
  69. flock/storage/sqlite/consumption_loader.py +100 -0
  70. flock/storage/sqlite/query_builder.py +112 -0
  71. flock/storage/sqlite/query_params_builder.py +91 -0
  72. flock/storage/sqlite/schema_manager.py +168 -0
  73. flock/storage/sqlite/summary_queries.py +194 -0
  74. flock/utils/__init__.py +14 -0
  75. flock/utils/async_utils.py +67 -0
  76. flock/{runtime.py → utils/runtime.py} +3 -3
  77. flock/utils/time_utils.py +53 -0
  78. flock/utils/type_resolution.py +38 -0
  79. flock/{utilities.py → utils/utilities.py} +2 -2
  80. flock/utils/validation.py +57 -0
  81. flock/utils/visibility.py +79 -0
  82. flock/utils/visibility_utils.py +134 -0
  83. {flock_core-0.5.10.dist-info → flock_core-0.5.20.dist-info}/METADATA +69 -61
  84. {flock_core-0.5.10.dist-info → flock_core-0.5.20.dist-info}/RECORD +89 -31
  85. flock/agent.py +0 -1578
  86. flock/orchestrator.py +0 -1746
  87. /flock/{visibility.py → core/visibility.py} +0 -0
  88. /flock/{helper → utils}/cli_helper.py +0 -0
  89. {flock_core-0.5.10.dist-info → flock_core-0.5.20.dist-info}/WHEEL +0 -0
  90. {flock_core-0.5.10.dist-info → flock_core-0.5.20.dist-info}/entry_points.txt +0 -0
  91. {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
+ ]