smartify-ai 0.1.0__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.
- smartify/__init__.py +3 -0
- smartify/agents/__init__.py +0 -0
- smartify/agents/adapters/__init__.py +13 -0
- smartify/agents/adapters/anthropic.py +253 -0
- smartify/agents/adapters/openai.py +289 -0
- smartify/api/__init__.py +26 -0
- smartify/api/auth.py +352 -0
- smartify/api/errors.py +380 -0
- smartify/api/events.py +345 -0
- smartify/api/server.py +992 -0
- smartify/cli/__init__.py +1 -0
- smartify/cli/main.py +430 -0
- smartify/engine/__init__.py +64 -0
- smartify/engine/approval.py +479 -0
- smartify/engine/orchestrator.py +1365 -0
- smartify/engine/scheduler.py +380 -0
- smartify/engine/spark.py +294 -0
- smartify/guardrails/__init__.py +22 -0
- smartify/guardrails/breakers.py +409 -0
- smartify/models/__init__.py +61 -0
- smartify/models/grid.py +625 -0
- smartify/notifications/__init__.py +22 -0
- smartify/notifications/webhook.py +556 -0
- smartify/state/__init__.py +46 -0
- smartify/state/checkpoint.py +558 -0
- smartify/state/resume.py +301 -0
- smartify/state/store.py +370 -0
- smartify/tools/__init__.py +17 -0
- smartify/tools/base.py +196 -0
- smartify/tools/builtin/__init__.py +79 -0
- smartify/tools/builtin/file.py +464 -0
- smartify/tools/builtin/http.py +195 -0
- smartify/tools/builtin/shell.py +137 -0
- smartify/tools/mcp/__init__.py +33 -0
- smartify/tools/mcp/adapter.py +157 -0
- smartify/tools/mcp/client.py +334 -0
- smartify/tools/mcp/registry.py +130 -0
- smartify/validator/__init__.py +0 -0
- smartify/validator/validate.py +271 -0
- smartify/workspace/__init__.py +5 -0
- smartify/workspace/manager.py +248 -0
- smartify_ai-0.1.0.dist-info/METADATA +201 -0
- smartify_ai-0.1.0.dist-info/RECORD +46 -0
- smartify_ai-0.1.0.dist-info/WHEEL +4 -0
- smartify_ai-0.1.0.dist-info/entry_points.txt +2 -0
- smartify_ai-0.1.0.dist-info/licenses/LICENSE +21 -0
smartify/models/grid.py
ADDED
|
@@ -0,0 +1,625 @@
|
|
|
1
|
+
"""Pydantic models for Grid YAML specification."""
|
|
2
|
+
|
|
3
|
+
from enum import Enum
|
|
4
|
+
from typing import Any, Dict, List, Optional, Union
|
|
5
|
+
from pydantic import BaseModel, Field, field_validator
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
# ============================================================================
|
|
9
|
+
# Enums
|
|
10
|
+
# ============================================================================
|
|
11
|
+
|
|
12
|
+
class NodeKind(str, Enum):
|
|
13
|
+
"""Types of nodes in the grid topology."""
|
|
14
|
+
CONTROLLER = "controller"
|
|
15
|
+
RELAY = "relay"
|
|
16
|
+
SUBSTATION = "substation"
|
|
17
|
+
SPARK = "spark" # Runtime-only, not user-defined
|
|
18
|
+
FOREACH = "foreach"
|
|
19
|
+
EXPR = "expr"
|
|
20
|
+
AGGREGATE = "aggregate"
|
|
21
|
+
APPROVAL = "approval"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class TriggerType(str, Enum):
|
|
25
|
+
"""Types of grid triggers."""
|
|
26
|
+
MANUAL = "manual"
|
|
27
|
+
SCHEDULE = "schedule"
|
|
28
|
+
WEBHOOK = "webhook"
|
|
29
|
+
EVENT = "event"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class ExecutionMode(str, Enum):
|
|
33
|
+
"""How node activation order is determined."""
|
|
34
|
+
PARENT = "parent"
|
|
35
|
+
EXPLICIT = "explicit"
|
|
36
|
+
DYNAMIC = "dynamic"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class AutonomyMode(str, Enum):
|
|
40
|
+
"""Level of autonomous action permitted."""
|
|
41
|
+
OBSERVE = "observe"
|
|
42
|
+
RECOMMEND = "recommend"
|
|
43
|
+
ACT_WITH_LIMITS = "act_with_limits"
|
|
44
|
+
FULL_AUTONOMY = "full_autonomy"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class TripAction(str, Enum):
|
|
48
|
+
"""Actions when a breaker trips."""
|
|
49
|
+
NOTIFY = "notify"
|
|
50
|
+
PAUSE = "pause"
|
|
51
|
+
COOLDOWN = "cooldown"
|
|
52
|
+
REQUIRE_APPROVAL = "require_approval"
|
|
53
|
+
STOP = "stop"
|
|
54
|
+
DOWNGRADE = "downgrade"
|
|
55
|
+
BLOCK = "block"
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class BreakerStatus(str, Enum):
|
|
59
|
+
"""Breaker status levels."""
|
|
60
|
+
OK = "ok"
|
|
61
|
+
WARNING = "warning"
|
|
62
|
+
TRIPPED = "tripped"
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class GridState(str, Enum):
|
|
66
|
+
"""Grid execution states."""
|
|
67
|
+
DRAFT = "draft"
|
|
68
|
+
READY = "ready"
|
|
69
|
+
ENERGIZED = "energized"
|
|
70
|
+
RUNNING = "running"
|
|
71
|
+
COMPLETED = "completed"
|
|
72
|
+
PAUSED = "paused"
|
|
73
|
+
STOPPED = "stopped"
|
|
74
|
+
FAILED = "failed"
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class WorkspaceType(str, Enum):
|
|
78
|
+
"""Workspace environment types."""
|
|
79
|
+
CONTAINER = "container"
|
|
80
|
+
LOCAL = "local"
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class ArtifactType(str, Enum):
|
|
84
|
+
"""Types of output artifacts."""
|
|
85
|
+
CODE = "code"
|
|
86
|
+
CONFIG = "config"
|
|
87
|
+
DOCS = "docs"
|
|
88
|
+
DATA = "data"
|
|
89
|
+
REPORT = "report"
|
|
90
|
+
SCREENSHOT = "screenshot"
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class AggregateStrategy(str, Enum):
|
|
94
|
+
"""Merge strategies for aggregate nodes."""
|
|
95
|
+
MERGE_OBJECTS = "merge_objects"
|
|
96
|
+
CONCAT_ARRAYS = "concat_arrays"
|
|
97
|
+
SUM = "sum"
|
|
98
|
+
FIRST = "first"
|
|
99
|
+
LAST = "last"
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
# ============================================================================
|
|
103
|
+
# Metadata
|
|
104
|
+
# ============================================================================
|
|
105
|
+
|
|
106
|
+
class CompatibilitySpec(BaseModel):
|
|
107
|
+
"""Runtime compatibility requirements."""
|
|
108
|
+
minRuntimeVersion: str = "0.1.0"
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class MetadataSpec(BaseModel):
|
|
112
|
+
"""Grid metadata."""
|
|
113
|
+
id: str
|
|
114
|
+
name: str
|
|
115
|
+
description: Optional[str] = None
|
|
116
|
+
version: str = "1.0.0"
|
|
117
|
+
owner: str = "default"
|
|
118
|
+
tags: List[str] = Field(default_factory=list)
|
|
119
|
+
compatibility: Optional[CompatibilitySpec] = None
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
# ============================================================================
|
|
123
|
+
# Topology
|
|
124
|
+
# ============================================================================
|
|
125
|
+
|
|
126
|
+
class TriggerConfig(BaseModel):
|
|
127
|
+
"""Trigger configuration."""
|
|
128
|
+
requireConfirmation: bool = False
|
|
129
|
+
# Schedule-specific
|
|
130
|
+
cron: Optional[str] = None
|
|
131
|
+
# Webhook-specific
|
|
132
|
+
path: Optional[str] = None
|
|
133
|
+
secret: Optional[str] = None
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class TriggerSpec(BaseModel):
|
|
137
|
+
"""How grid execution is started."""
|
|
138
|
+
type: TriggerType = TriggerType.MANUAL
|
|
139
|
+
config: TriggerConfig = Field(default_factory=TriggerConfig)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
class RetrySpec(BaseModel):
|
|
143
|
+
"""Retry configuration for nodes."""
|
|
144
|
+
maxAttempts: int = Field(default=1, ge=0, le=10)
|
|
145
|
+
backoffSeconds: int = Field(default=5, ge=0, le=300)
|
|
146
|
+
retryOn: List[str] = Field(default_factory=lambda: ["error"])
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
class ForeachSpec(BaseModel):
|
|
150
|
+
"""Configuration for foreach nodes."""
|
|
151
|
+
over: str # Expression returning list
|
|
152
|
+
as_: str = Field(default="item", alias="as")
|
|
153
|
+
maxIterations: int = Field(default=50, le=100)
|
|
154
|
+
parallel: bool = True
|
|
155
|
+
body: Dict[str, Any] = Field(default_factory=dict) # {to: node_id}
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
class AggregateSpec(BaseModel):
|
|
159
|
+
"""Configuration for aggregate nodes."""
|
|
160
|
+
from_: List[str] = Field(alias="from") # Source node IDs
|
|
161
|
+
strategy: AggregateStrategy = AggregateStrategy.MERGE_OBJECTS
|
|
162
|
+
waitFor: Union[str, int] = "all" # "all", "any", or number
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
class ApprovalSpec(BaseModel):
|
|
166
|
+
"""Configuration for approval nodes."""
|
|
167
|
+
prompt: str = "Please review and approve to continue"
|
|
168
|
+
showOutputsFrom: List[str] = Field(default_factory=list)
|
|
169
|
+
timeout: int = Field(default=86400, le=604800) # Max 7 days
|
|
170
|
+
requiredApprovers: int = 1
|
|
171
|
+
allowedApprovers: List[str] = Field(default_factory=list)
|
|
172
|
+
autoApprove: Optional[Dict[str, str]] = None # {when: expression}
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
class PromptSpec(BaseModel):
|
|
176
|
+
"""Prompt configuration for agent nodes."""
|
|
177
|
+
system: Optional[str] = None # System prompt
|
|
178
|
+
template: Optional[str] = None # User message template with {{variable}} placeholders
|
|
179
|
+
context: Optional[List[str]] = None # Additional context sources
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
class NodeSpec(BaseModel):
|
|
183
|
+
"""A node in the grid topology."""
|
|
184
|
+
id: str
|
|
185
|
+
kind: NodeKind
|
|
186
|
+
name: str
|
|
187
|
+
description: Optional[str] = None
|
|
188
|
+
parent: Optional[str] = None
|
|
189
|
+
agent: Optional[str] = None
|
|
190
|
+
capabilities: List[str] = Field(default_factory=list)
|
|
191
|
+
when: Optional[str] = None # Activation condition expression
|
|
192
|
+
parallel: bool = True
|
|
193
|
+
runAfter: Optional[List[str]] = None
|
|
194
|
+
retry: Optional[RetrySpec] = None
|
|
195
|
+
outputSchema: Optional[Dict[str, Any]] = None
|
|
196
|
+
prompt: Optional[PromptSpec] = None # Prompt configuration for agent nodes
|
|
197
|
+
tools: Optional[List[str]] = None # Tools available to this node
|
|
198
|
+
|
|
199
|
+
# Kind-specific configs
|
|
200
|
+
foreach: Optional[ForeachSpec] = None
|
|
201
|
+
aggregate: Optional[AggregateSpec] = None
|
|
202
|
+
approval: Optional[ApprovalSpec] = None
|
|
203
|
+
expr: Optional[str] = None # Expression string for expr nodes
|
|
204
|
+
|
|
205
|
+
@field_validator('kind')
|
|
206
|
+
@classmethod
|
|
207
|
+
def validate_kind(cls, v: NodeKind) -> NodeKind:
|
|
208
|
+
if v == NodeKind.SPARK:
|
|
209
|
+
raise ValueError("spark nodes cannot be user-defined; they are spawned at runtime")
|
|
210
|
+
return v
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
class EdgeSpec(BaseModel):
|
|
214
|
+
"""Explicit connection between nodes."""
|
|
215
|
+
from_: str = Field(alias="from")
|
|
216
|
+
to: Union[str, List[str]]
|
|
217
|
+
when: Optional[str] = None
|
|
218
|
+
condition: Optional[str] = None # Legacy: "always", "never"
|
|
219
|
+
parallel: bool = False
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
class DynamicSpawningLimits(BaseModel):
|
|
223
|
+
"""Limits for dynamic node spawning."""
|
|
224
|
+
maxRelaysPerController: int = 4
|
|
225
|
+
maxSubstationsPerRelay: int = 3
|
|
226
|
+
maxSparksPerSubstation: int = 3
|
|
227
|
+
maxTotalNodes: int = 20
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
class DynamicSpawningDefaults(BaseModel):
|
|
231
|
+
"""Default agents for spawned nodes."""
|
|
232
|
+
relay: Optional[str] = None
|
|
233
|
+
substation: Optional[str] = None
|
|
234
|
+
spark: Optional[str] = None
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
class DynamicSpawningSpec(BaseModel):
|
|
238
|
+
"""Runtime node creation configuration."""
|
|
239
|
+
enabled: bool = True
|
|
240
|
+
enableSparks: bool = True
|
|
241
|
+
limits: DynamicSpawningLimits = Field(default_factory=DynamicSpawningLimits)
|
|
242
|
+
defaults: DynamicSpawningDefaults = Field(default_factory=DynamicSpawningDefaults)
|
|
243
|
+
requireApproval: bool = False
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
class TopologySpec(BaseModel):
|
|
247
|
+
"""Grid topology definition."""
|
|
248
|
+
trigger: TriggerSpec = Field(default_factory=TriggerSpec)
|
|
249
|
+
executionMode: ExecutionMode = ExecutionMode.PARENT
|
|
250
|
+
nodes: List[NodeSpec]
|
|
251
|
+
edges: Optional[List[EdgeSpec]] = None
|
|
252
|
+
dynamicSpawning: DynamicSpawningSpec = Field(default_factory=DynamicSpawningSpec)
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
# ============================================================================
|
|
256
|
+
# Inputs
|
|
257
|
+
# ============================================================================
|
|
258
|
+
|
|
259
|
+
class InputSpec(BaseModel):
|
|
260
|
+
"""Input parameter definition."""
|
|
261
|
+
name: str
|
|
262
|
+
type: str = "string" # string, boolean, number, integer, array, object
|
|
263
|
+
required: bool = False
|
|
264
|
+
default: Optional[Any] = None
|
|
265
|
+
enum: Optional[List[Any]] = None
|
|
266
|
+
description: Optional[str] = None
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
# ============================================================================
|
|
270
|
+
# Environment
|
|
271
|
+
# ============================================================================
|
|
272
|
+
|
|
273
|
+
class SecretSpec(BaseModel):
|
|
274
|
+
"""Secret definition."""
|
|
275
|
+
name: str
|
|
276
|
+
source: str = "env" # "env" or "prompt"
|
|
277
|
+
optional: bool = True
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
class EnvironmentSpec(BaseModel):
|
|
281
|
+
"""Environment variables and secrets."""
|
|
282
|
+
variables: Dict[str, str] = Field(default_factory=dict)
|
|
283
|
+
secrets: List[SecretSpec] = Field(default_factory=list)
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
# ============================================================================
|
|
287
|
+
# Outputs
|
|
288
|
+
# ============================================================================
|
|
289
|
+
|
|
290
|
+
class ArtifactSpec(BaseModel):
|
|
291
|
+
"""Output artifact definition."""
|
|
292
|
+
path: str
|
|
293
|
+
type: Optional[ArtifactType] = None
|
|
294
|
+
description: Optional[str] = None
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
class SuccessCriterion(BaseModel):
|
|
298
|
+
"""Success criterion for grid completion."""
|
|
299
|
+
name: str
|
|
300
|
+
command: Optional[str] = None
|
|
301
|
+
exitCode: int = 0
|
|
302
|
+
optional: bool = False
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
class OutputsSpec(BaseModel):
|
|
306
|
+
"""Expected outputs from the grid."""
|
|
307
|
+
artifacts: List[ArtifactSpec] = Field(default_factory=list)
|
|
308
|
+
successCriteria: List[SuccessCriterion] = Field(default_factory=list)
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
# ============================================================================
|
|
312
|
+
# Agents
|
|
313
|
+
# ============================================================================
|
|
314
|
+
|
|
315
|
+
class ModelPolicy(BaseModel):
|
|
316
|
+
"""Model selection policy."""
|
|
317
|
+
preferred: str = "claude-sonnet-4-20250514"
|
|
318
|
+
fallback: List[str] = Field(default_factory=list)
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
class ContextSource(BaseModel):
|
|
322
|
+
"""Context source for agents."""
|
|
323
|
+
type: str # "artifacts", "memory", "files"
|
|
324
|
+
filter: Optional[str] = None # Glob pattern
|
|
325
|
+
key: Optional[str] = None # Memory key
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
class AgentSpec(BaseModel):
|
|
329
|
+
"""Agent definition."""
|
|
330
|
+
modelPolicy: Optional[ModelPolicy] = None
|
|
331
|
+
systemPrompt: Optional[str] = None
|
|
332
|
+
systemPromptRef: Optional[str] = None # "builtin:controller" or "prompts/file.md"
|
|
333
|
+
maxDelegation: int = 4
|
|
334
|
+
tools: Optional[List[str]] = None
|
|
335
|
+
contextSources: List[ContextSource] = Field(default_factory=list)
|
|
336
|
+
role: Optional[str] = None # "controller", "relay", "substation", "spark"
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
# ============================================================================
|
|
340
|
+
# Tools
|
|
341
|
+
# ============================================================================
|
|
342
|
+
|
|
343
|
+
class ToolSpec(BaseModel):
|
|
344
|
+
"""Tool configuration."""
|
|
345
|
+
id: str
|
|
346
|
+
enabled: bool = True
|
|
347
|
+
config: Dict[str, Any] = Field(default_factory=dict)
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
class McpServerSpec(BaseModel):
|
|
351
|
+
"""MCP server configuration for external tool integration.
|
|
352
|
+
|
|
353
|
+
Allows grids to use tools from external MCP servers.
|
|
354
|
+
|
|
355
|
+
Example:
|
|
356
|
+
mcpServers:
|
|
357
|
+
- id: filesystem
|
|
358
|
+
transport: stdio
|
|
359
|
+
command: npx
|
|
360
|
+
args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
|
|
361
|
+
prefix: fs
|
|
362
|
+
"""
|
|
363
|
+
id: str = Field(..., description="Unique identifier for this MCP server")
|
|
364
|
+
transport: str = Field(
|
|
365
|
+
default="stdio",
|
|
366
|
+
description="Transport type: 'stdio', 'sse', or 'streamable_http'"
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
# For stdio transport
|
|
370
|
+
command: Optional[str] = Field(None, description="Command to start the MCP server")
|
|
371
|
+
args: List[str] = Field(default_factory=list, description="Command arguments")
|
|
372
|
+
env: Optional[Dict[str, str]] = Field(None, description="Environment variables")
|
|
373
|
+
cwd: Optional[str] = Field(None, description="Working directory")
|
|
374
|
+
|
|
375
|
+
# For SSE/HTTP transport
|
|
376
|
+
url: Optional[str] = Field(None, description="URL for SSE or HTTP transport")
|
|
377
|
+
headers: Optional[Dict[str, str]] = Field(None, description="HTTP headers")
|
|
378
|
+
|
|
379
|
+
# Tool naming and filtering
|
|
380
|
+
prefix: Optional[str] = Field(
|
|
381
|
+
None,
|
|
382
|
+
description="Prefix for tool names (e.g. 'fs' -> 'fs_read_file')"
|
|
383
|
+
)
|
|
384
|
+
tools: Optional[List[str]] = Field(
|
|
385
|
+
None,
|
|
386
|
+
description="Only expose these tools (if set)"
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
class ToolsSpec(BaseModel):
|
|
391
|
+
"""Tool configuration section."""
|
|
392
|
+
enabled: List[str] = Field(default_factory=list)
|
|
393
|
+
disabled: List[str] = Field(default_factory=list)
|
|
394
|
+
custom: List[ToolSpec] = Field(default_factory=list)
|
|
395
|
+
mcpServers: List[McpServerSpec] = Field(
|
|
396
|
+
default_factory=list,
|
|
397
|
+
description="External MCP servers to connect to for additional tools"
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
# ============================================================================
|
|
402
|
+
# Guardrails
|
|
403
|
+
# ============================================================================
|
|
404
|
+
|
|
405
|
+
class TokenLimits(BaseModel):
|
|
406
|
+
"""Token usage limits."""
|
|
407
|
+
maxInputTokensPerRequest: int = 100_000
|
|
408
|
+
maxOutputTokensPerRequest: int = 16_000
|
|
409
|
+
maxTotalTokensPerRun: int = 1_000_000
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
class TimeLimits(BaseModel):
|
|
413
|
+
"""Time limits."""
|
|
414
|
+
maxRuntimeSeconds: int = 3600
|
|
415
|
+
maxTaskSeconds: int = 300
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
class CostLimits(BaseModel):
|
|
419
|
+
"""Cost limits."""
|
|
420
|
+
maxCostPerRun: float = 10.0
|
|
421
|
+
maxCostPerDay: float = 100.0
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
class RequestLimits(BaseModel):
|
|
425
|
+
"""Request rate limits."""
|
|
426
|
+
maxRequestsPerMinute: int = 60
|
|
427
|
+
maxConcurrentAgents: int = 5
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
class BreakerSpec(BaseModel):
|
|
431
|
+
"""Breaker configuration."""
|
|
432
|
+
tokens: Optional[TokenLimits] = None
|
|
433
|
+
time: Optional[TimeLimits] = None
|
|
434
|
+
cost: Optional[CostLimits] = None
|
|
435
|
+
requests: Optional[RequestLimits] = None
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
class BreakerActions(BaseModel):
|
|
439
|
+
"""Actions when breakers trip."""
|
|
440
|
+
onTokensLimit: TripAction = TripAction.PAUSE
|
|
441
|
+
onTimeLimit: TripAction = TripAction.STOP
|
|
442
|
+
onCostLimit: TripAction = TripAction.PAUSE
|
|
443
|
+
onRequestsLimit: TripAction = TripAction.COOLDOWN
|
|
444
|
+
cooldownSeconds: int = 60
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
class AutonomySpec(BaseModel):
|
|
448
|
+
"""Autonomy configuration."""
|
|
449
|
+
mode: AutonomyMode = AutonomyMode.ACT_WITH_LIMITS
|
|
450
|
+
requireApprovalFor: List[str] = Field(default_factory=list)
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
class GuardrailsSpec(BaseModel):
|
|
454
|
+
"""Guardrails configuration."""
|
|
455
|
+
breakers: BreakerSpec = Field(default_factory=BreakerSpec)
|
|
456
|
+
breakerActions: BreakerActions = Field(default_factory=BreakerActions)
|
|
457
|
+
autonomy: AutonomySpec = Field(default_factory=AutonomySpec)
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
# ============================================================================
|
|
461
|
+
# Workspace
|
|
462
|
+
# ============================================================================
|
|
463
|
+
|
|
464
|
+
class WorkspaceResourcesSpec(BaseModel):
|
|
465
|
+
"""Workspace resource limits."""
|
|
466
|
+
cpus: int = 2
|
|
467
|
+
memoryMb: int = 4096
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
class WorkspaceMountSpec(BaseModel):
|
|
471
|
+
"""Workspace mount configuration."""
|
|
472
|
+
src: str
|
|
473
|
+
dst: str
|
|
474
|
+
readonly: bool = False
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
class WorkspaceContainerConfig(BaseModel):
|
|
478
|
+
"""Container workspace configuration."""
|
|
479
|
+
image: str = "smartify-sandbox:latest"
|
|
480
|
+
resources: WorkspaceResourcesSpec = Field(default_factory=WorkspaceResourcesSpec)
|
|
481
|
+
mounts: List[WorkspaceMountSpec] = Field(default_factory=list)
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
class WorkspaceSpec(BaseModel):
|
|
485
|
+
"""Workspace configuration."""
|
|
486
|
+
type: WorkspaceType = WorkspaceType.LOCAL
|
|
487
|
+
config: Optional[WorkspaceContainerConfig] = None
|
|
488
|
+
rootPath: str = "./workspace"
|
|
489
|
+
persistent: bool = True
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
# ============================================================================
|
|
493
|
+
# Coordination
|
|
494
|
+
# ============================================================================
|
|
495
|
+
|
|
496
|
+
class CoordinationSpec(BaseModel):
|
|
497
|
+
"""State management configuration."""
|
|
498
|
+
stateBackend: str = "memory" # "memory", "sqlite", "redis"
|
|
499
|
+
checkpointInterval: int = 60
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
# ============================================================================
|
|
503
|
+
# Notifications
|
|
504
|
+
# ============================================================================
|
|
505
|
+
|
|
506
|
+
class WebhookConfigSpec(BaseModel):
|
|
507
|
+
"""Webhook endpoint configuration."""
|
|
508
|
+
url: str
|
|
509
|
+
events: List[str] = Field(
|
|
510
|
+
default_factory=lambda: [
|
|
511
|
+
"run_completed", "run_failed", "approval_needed", "breaker_tripped"
|
|
512
|
+
],
|
|
513
|
+
description="Event types to send: run_started, run_completed, run_failed, "
|
|
514
|
+
"run_paused, run_stopped, approval_needed, breaker_tripped, node_completed"
|
|
515
|
+
)
|
|
516
|
+
secret: Optional[str] = Field(
|
|
517
|
+
None,
|
|
518
|
+
description="Secret for HMAC-SHA256 signature (X-Smartify-Signature header)"
|
|
519
|
+
)
|
|
520
|
+
headers: Dict[str, str] = Field(
|
|
521
|
+
default_factory=dict,
|
|
522
|
+
description="Custom headers to include with webhook requests"
|
|
523
|
+
)
|
|
524
|
+
maxRetries: int = Field(3, ge=0, le=10, description="Maximum retry attempts")
|
|
525
|
+
timeout: float = Field(30.0, ge=1, le=120, description="Request timeout in seconds")
|
|
526
|
+
enabled: bool = True
|
|
527
|
+
|
|
528
|
+
|
|
529
|
+
class NotificationsSpec(BaseModel):
|
|
530
|
+
"""Notification configuration for grid events."""
|
|
531
|
+
webhooks: List[WebhookConfigSpec] = Field(default_factory=list)
|
|
532
|
+
|
|
533
|
+
# Slack notifications (convenience)
|
|
534
|
+
slack: Optional[Dict[str, Any]] = Field(
|
|
535
|
+
None,
|
|
536
|
+
description="Slack notification config: {webhook_url, channel, events}"
|
|
537
|
+
)
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
# ============================================================================
|
|
541
|
+
# Observability
|
|
542
|
+
# ============================================================================
|
|
543
|
+
|
|
544
|
+
class ObservabilitySpec(BaseModel):
|
|
545
|
+
"""Logging and events configuration."""
|
|
546
|
+
logLevel: str = "info"
|
|
547
|
+
enableMetrics: bool = True
|
|
548
|
+
enableTracing: bool = False
|
|
549
|
+
# Legacy: simple webhook URLs (use notifications.webhooks for full config)
|
|
550
|
+
webhooks: List[str] = Field(default_factory=list)
|
|
551
|
+
|
|
552
|
+
|
|
553
|
+
# ============================================================================
|
|
554
|
+
# UI (optional hints)
|
|
555
|
+
# ============================================================================
|
|
556
|
+
|
|
557
|
+
class UISpec(BaseModel):
|
|
558
|
+
"""Dashboard hints."""
|
|
559
|
+
color: Optional[str] = None
|
|
560
|
+
icon: Optional[str] = None
|
|
561
|
+
|
|
562
|
+
|
|
563
|
+
# ============================================================================
|
|
564
|
+
# Root GridSpec
|
|
565
|
+
# ============================================================================
|
|
566
|
+
|
|
567
|
+
class GridSpec(BaseModel):
|
|
568
|
+
"""Root Grid YAML specification."""
|
|
569
|
+
apiVersion: str = "smartify.ai/v1"
|
|
570
|
+
kind: str = "GridSpec"
|
|
571
|
+
metadata: MetadataSpec
|
|
572
|
+
topology: TopologySpec
|
|
573
|
+
inputs: Optional[List[InputSpec]] = None
|
|
574
|
+
environment: Optional[EnvironmentSpec] = None
|
|
575
|
+
outputs: Optional[OutputsSpec] = None
|
|
576
|
+
agents: Optional[Dict[str, AgentSpec]] = None
|
|
577
|
+
tools: Optional[ToolsSpec] = None
|
|
578
|
+
guardrails: Optional[GuardrailsSpec] = None
|
|
579
|
+
workspace: Optional[WorkspaceSpec] = None
|
|
580
|
+
coordination: Optional[CoordinationSpec] = None
|
|
581
|
+
notifications: Optional[NotificationsSpec] = None
|
|
582
|
+
observability: Optional[ObservabilitySpec] = None
|
|
583
|
+
ui: Optional[UISpec] = None
|
|
584
|
+
|
|
585
|
+
@field_validator('kind')
|
|
586
|
+
@classmethod
|
|
587
|
+
def validate_kind(cls, v: str) -> str:
|
|
588
|
+
if v != "GridSpec":
|
|
589
|
+
raise ValueError(f"kind must be 'GridSpec', got '{v}'")
|
|
590
|
+
return v
|
|
591
|
+
|
|
592
|
+
@field_validator('apiVersion')
|
|
593
|
+
@classmethod
|
|
594
|
+
def validate_api_version(cls, v: str) -> str:
|
|
595
|
+
if not v.startswith("smartify.ai/"):
|
|
596
|
+
raise ValueError(f"apiVersion must start with 'smartify.ai/', got '{v}'")
|
|
597
|
+
return v
|
|
598
|
+
|
|
599
|
+
# Convenience properties
|
|
600
|
+
@property
|
|
601
|
+
def id(self) -> str:
|
|
602
|
+
"""Grid ID from metadata."""
|
|
603
|
+
return self.metadata.id
|
|
604
|
+
|
|
605
|
+
@id.setter
|
|
606
|
+
def id(self, value: str) -> None:
|
|
607
|
+
"""Set grid ID in metadata."""
|
|
608
|
+
self.metadata.id = value
|
|
609
|
+
|
|
610
|
+
@property
|
|
611
|
+
def name(self) -> str:
|
|
612
|
+
"""Grid name from metadata."""
|
|
613
|
+
return self.metadata.name
|
|
614
|
+
|
|
615
|
+
@property
|
|
616
|
+
def nodes(self) -> List["NodeSpec"]:
|
|
617
|
+
"""Nodes from topology."""
|
|
618
|
+
return self.topology.nodes
|
|
619
|
+
|
|
620
|
+
@property
|
|
621
|
+
def breakers(self) -> Optional[BreakerSpec]:
|
|
622
|
+
"""Breakers from guardrails."""
|
|
623
|
+
if self.guardrails:
|
|
624
|
+
return self.guardrails.breakers
|
|
625
|
+
return None
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Notification system for Smartify events.
|
|
2
|
+
|
|
3
|
+
Supports webhook notifications for:
|
|
4
|
+
- run_completed
|
|
5
|
+
- run_failed
|
|
6
|
+
- approval_needed
|
|
7
|
+
- breaker_tripped
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from smartify.notifications.webhook import (
|
|
11
|
+
WebhookNotifier,
|
|
12
|
+
WebhookConfig,
|
|
13
|
+
WebhookEvent,
|
|
14
|
+
EventType,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"WebhookNotifier",
|
|
19
|
+
"WebhookConfig",
|
|
20
|
+
"WebhookEvent",
|
|
21
|
+
"EventType",
|
|
22
|
+
]
|