vystak 0.1.0__tar.gz
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.
- vystak-0.1.0/.gitignore +40 -0
- vystak-0.1.0/PKG-INFO +63 -0
- vystak-0.1.0/README.md +35 -0
- vystak-0.1.0/pyproject.toml +50 -0
- vystak-0.1.0/src/vystak/__init__.py +126 -0
- vystak-0.1.0/src/vystak/hash/__init__.py +6 -0
- vystak-0.1.0/src/vystak/hash/hasher.py +18 -0
- vystak-0.1.0/src/vystak/hash/tree.py +82 -0
- vystak-0.1.0/src/vystak/ir/__init__.py +1 -0
- vystak-0.1.0/src/vystak/providers/__init__.py +23 -0
- vystak-0.1.0/src/vystak/providers/base.py +77 -0
- vystak-0.1.0/src/vystak/provisioning/__init__.py +37 -0
- vystak-0.1.0/src/vystak/provisioning/graph.py +122 -0
- vystak-0.1.0/src/vystak/provisioning/grouping.py +26 -0
- vystak-0.1.0/src/vystak/provisioning/health.py +62 -0
- vystak-0.1.0/src/vystak/provisioning/listener.py +68 -0
- vystak-0.1.0/src/vystak/provisioning/node.py +53 -0
- vystak-0.1.0/src/vystak/schema/__init__.py +93 -0
- vystak-0.1.0/src/vystak/schema/agent.py +49 -0
- vystak-0.1.0/src/vystak/schema/channel.py +23 -0
- vystak-0.1.0/src/vystak/schema/common.py +32 -0
- vystak-0.1.0/src/vystak/schema/config_loader.py +43 -0
- vystak-0.1.0/src/vystak/schema/gateway.py +19 -0
- vystak-0.1.0/src/vystak/schema/loader.py +42 -0
- vystak-0.1.0/src/vystak/schema/mcp.py +15 -0
- vystak-0.1.0/src/vystak/schema/model.py +20 -0
- vystak-0.1.0/src/vystak/schema/multi_loader.py +69 -0
- vystak-0.1.0/src/vystak/schema/openai.py +136 -0
- vystak-0.1.0/src/vystak/schema/platform.py +12 -0
- vystak-0.1.0/src/vystak/schema/provider.py +10 -0
- vystak-0.1.0/src/vystak/schema/resource.py +36 -0
- vystak-0.1.0/src/vystak/schema/secret.py +16 -0
- vystak-0.1.0/src/vystak/schema/service.py +65 -0
- vystak-0.1.0/src/vystak/schema/skill.py +24 -0
- vystak-0.1.0/src/vystak/schema/workspace.py +20 -0
- vystak-0.1.0/src/vystak/stores/__init__.py +5 -0
- vystak-0.1.0/src/vystak/stores/sqlite.py +117 -0
- vystak-0.1.0/tests/test_agent.py +178 -0
- vystak-0.1.0/tests/test_base.py +107 -0
- vystak-0.1.0/tests/test_channel.py +30 -0
- vystak-0.1.0/tests/test_common.py +51 -0
- vystak-0.1.0/tests/test_config_loader.py +91 -0
- vystak-0.1.0/tests/test_gateway.py +61 -0
- vystak-0.1.0/tests/test_graph.py +145 -0
- vystak-0.1.0/tests/test_grouping.py +97 -0
- vystak-0.1.0/tests/test_hasher.py +47 -0
- vystak-0.1.0/tests/test_health.py +76 -0
- vystak-0.1.0/tests/test_integration.py +110 -0
- vystak-0.1.0/tests/test_loader.py +214 -0
- vystak-0.1.0/tests/test_mcp.py +52 -0
- vystak-0.1.0/tests/test_model.py +64 -0
- vystak-0.1.0/tests/test_multi_loader.py +142 -0
- vystak-0.1.0/tests/test_openai_schema.py +160 -0
- vystak-0.1.0/tests/test_platform.py +31 -0
- vystak-0.1.0/tests/test_provider.py +33 -0
- vystak-0.1.0/tests/test_resource.py +68 -0
- vystak-0.1.0/tests/test_schema_exports.py +74 -0
- vystak-0.1.0/tests/test_secret.py +31 -0
- vystak-0.1.0/tests/test_service.py +109 -0
- vystak-0.1.0/tests/test_skill.py +51 -0
- vystak-0.1.0/tests/test_slack_channel.py +52 -0
- vystak-0.1.0/tests/test_sqlite_store.py +111 -0
- vystak-0.1.0/tests/test_tree.py +130 -0
- vystak-0.1.0/tests/test_version.py +5 -0
- vystak-0.1.0/tests/test_workspace.py +51 -0
vystak-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.egg-info/
|
|
6
|
+
dist/
|
|
7
|
+
build/
|
|
8
|
+
.eggs/
|
|
9
|
+
*.egg
|
|
10
|
+
.venv/
|
|
11
|
+
.pytest_cache/
|
|
12
|
+
.ruff_cache/
|
|
13
|
+
.pyright/
|
|
14
|
+
|
|
15
|
+
# TypeScript
|
|
16
|
+
node_modules/
|
|
17
|
+
*.tsbuildinfo
|
|
18
|
+
coverage/
|
|
19
|
+
|
|
20
|
+
# IDE
|
|
21
|
+
.idea/
|
|
22
|
+
.vscode/
|
|
23
|
+
*.swp
|
|
24
|
+
*.swo
|
|
25
|
+
*~
|
|
26
|
+
|
|
27
|
+
# OS
|
|
28
|
+
.DS_Store
|
|
29
|
+
Thumbs.db
|
|
30
|
+
|
|
31
|
+
# Environment
|
|
32
|
+
.env
|
|
33
|
+
.env.local
|
|
34
|
+
|
|
35
|
+
# Vystak generated output (was .agentstack/ before rename)
|
|
36
|
+
.vystak/
|
|
37
|
+
.agentstack/
|
|
38
|
+
|
|
39
|
+
# Tooling workspaces (brainstorm sessions, local agent state)
|
|
40
|
+
.superpowers/
|
vystak-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: vystak
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Vystak core SDK — declarative AI agent orchestration
|
|
5
|
+
Project-URL: Homepage, https://vystak.dev
|
|
6
|
+
Project-URL: Documentation, https://vystak.dev/docs
|
|
7
|
+
Project-URL: Repository, https://github.com/vystak/vystak
|
|
8
|
+
Project-URL: Issues, https://github.com/vystak/vystak/issues
|
|
9
|
+
Author-email: Anatoliy Kolodkin <11351966+akolodkin@users.noreply.github.com>
|
|
10
|
+
License-Expression: Apache-2.0
|
|
11
|
+
Keywords: agent,ai,declarative,infrastructure,langchain,langgraph,orchestration
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
+
Classifier: Topic :: System :: Systems Administration
|
|
22
|
+
Classifier: Typing :: Typed
|
|
23
|
+
Requires-Python: >=3.11
|
|
24
|
+
Requires-Dist: aiosqlite>=0.20
|
|
25
|
+
Requires-Dist: pydantic>=2.0
|
|
26
|
+
Requires-Dist: pyyaml>=6.0
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
|
|
29
|
+
# vystak
|
|
30
|
+
|
|
31
|
+
Core SDK for Vystak — declarative, platform-agnostic orchestration for AI agents.
|
|
32
|
+
|
|
33
|
+
This package provides the foundational schema, hashing engine, provisioning graph, and provider base classes that the rest of the Vystak ecosystem builds on. You typically don't install this directly — use [`vystak-cli`](https://pypi.org/project/vystak-cli/) instead, which pulls it in.
|
|
34
|
+
|
|
35
|
+
## Install
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pip install vystak
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## What's in this package
|
|
42
|
+
|
|
43
|
+
- **`vystak.schema`** — Pydantic models for `Agent`, `Model`, `Provider`, `Platform`, `Channel`, `Service`, `Skill`, `Workspace`, `Secret`, `Mcp`. This is the contract adapters and providers consume.
|
|
44
|
+
- **`vystak.hash`** — content-addressable hashing (`AgentHashTree`) for stateless change detection.
|
|
45
|
+
- **`vystak.provisioning`** — `ProvisionGraph`, a DAG of `Provisionable` nodes for topological resource rollout with health checks.
|
|
46
|
+
- **`vystak.providers`** — `PlatformProvider`, `FrameworkAdapter`, `ChannelAdapter` ABCs.
|
|
47
|
+
- **`vystak.stores`** — async SQLite-backed key-value store for long-term memory.
|
|
48
|
+
|
|
49
|
+
## Example
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
import vystak as ast
|
|
53
|
+
|
|
54
|
+
anthropic = ast.Provider(name="anthropic", type="anthropic")
|
|
55
|
+
model = ast.Model(name="claude", provider=anthropic, model_name="claude-sonnet-4-20250514")
|
|
56
|
+
agent = ast.Agent(name="support-bot", model=model)
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
See the [main repository](https://github.com/vystak/vystak) for the full CLI, adapters, and providers.
|
|
60
|
+
|
|
61
|
+
## License
|
|
62
|
+
|
|
63
|
+
Apache-2.0
|
vystak-0.1.0/README.md
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# vystak
|
|
2
|
+
|
|
3
|
+
Core SDK for Vystak — declarative, platform-agnostic orchestration for AI agents.
|
|
4
|
+
|
|
5
|
+
This package provides the foundational schema, hashing engine, provisioning graph, and provider base classes that the rest of the Vystak ecosystem builds on. You typically don't install this directly — use [`vystak-cli`](https://pypi.org/project/vystak-cli/) instead, which pulls it in.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install vystak
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## What's in this package
|
|
14
|
+
|
|
15
|
+
- **`vystak.schema`** — Pydantic models for `Agent`, `Model`, `Provider`, `Platform`, `Channel`, `Service`, `Skill`, `Workspace`, `Secret`, `Mcp`. This is the contract adapters and providers consume.
|
|
16
|
+
- **`vystak.hash`** — content-addressable hashing (`AgentHashTree`) for stateless change detection.
|
|
17
|
+
- **`vystak.provisioning`** — `ProvisionGraph`, a DAG of `Provisionable` nodes for topological resource rollout with health checks.
|
|
18
|
+
- **`vystak.providers`** — `PlatformProvider`, `FrameworkAdapter`, `ChannelAdapter` ABCs.
|
|
19
|
+
- **`vystak.stores`** — async SQLite-backed key-value store for long-term memory.
|
|
20
|
+
|
|
21
|
+
## Example
|
|
22
|
+
|
|
23
|
+
```python
|
|
24
|
+
import vystak as ast
|
|
25
|
+
|
|
26
|
+
anthropic = ast.Provider(name="anthropic", type="anthropic")
|
|
27
|
+
model = ast.Model(name="claude", provider=anthropic, model_name="claude-sonnet-4-20250514")
|
|
28
|
+
agent = ast.Agent(name="support-bot", model=model)
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
See the [main repository](https://github.com/vystak/vystak) for the full CLI, adapters, and providers.
|
|
32
|
+
|
|
33
|
+
## License
|
|
34
|
+
|
|
35
|
+
Apache-2.0
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "vystak"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Vystak core SDK — declarative AI agent orchestration"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.11"
|
|
7
|
+
license = "Apache-2.0"
|
|
8
|
+
authors = [
|
|
9
|
+
{ name = "Anatoliy Kolodkin", email = "11351966+akolodkin@users.noreply.github.com" },
|
|
10
|
+
]
|
|
11
|
+
keywords = [
|
|
12
|
+
"ai",
|
|
13
|
+
"agent",
|
|
14
|
+
"orchestration",
|
|
15
|
+
"langchain",
|
|
16
|
+
"langgraph",
|
|
17
|
+
"infrastructure",
|
|
18
|
+
"declarative",
|
|
19
|
+
]
|
|
20
|
+
classifiers = [
|
|
21
|
+
"Development Status :: 3 - Alpha",
|
|
22
|
+
"Intended Audience :: Developers",
|
|
23
|
+
"License :: OSI Approved :: Apache Software License",
|
|
24
|
+
"Operating System :: OS Independent",
|
|
25
|
+
"Programming Language :: Python :: 3",
|
|
26
|
+
"Programming Language :: Python :: 3.11",
|
|
27
|
+
"Programming Language :: Python :: 3.12",
|
|
28
|
+
"Programming Language :: Python :: 3.13",
|
|
29
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
30
|
+
"Topic :: System :: Systems Administration",
|
|
31
|
+
"Typing :: Typed",
|
|
32
|
+
]
|
|
33
|
+
dependencies = [
|
|
34
|
+
"pydantic>=2.0",
|
|
35
|
+
"pyyaml>=6.0",
|
|
36
|
+
"aiosqlite>=0.20",
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
[project.urls]
|
|
40
|
+
Homepage = "https://vystak.dev"
|
|
41
|
+
Documentation = "https://vystak.dev/docs"
|
|
42
|
+
Repository = "https://github.com/vystak/vystak"
|
|
43
|
+
Issues = "https://github.com/vystak/vystak/issues"
|
|
44
|
+
|
|
45
|
+
[build-system]
|
|
46
|
+
requires = ["hatchling"]
|
|
47
|
+
build-backend = "hatchling.build"
|
|
48
|
+
|
|
49
|
+
[tool.hatch.build.targets.wheel]
|
|
50
|
+
packages = ["src/vystak"]
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"""Vystak — declarative AI agent orchestration."""
|
|
2
|
+
|
|
3
|
+
__version__ = "0.1.0"
|
|
4
|
+
|
|
5
|
+
# Schema models
|
|
6
|
+
# Hash engine
|
|
7
|
+
from vystak.hash import AgentHashTree, hash_agent, hash_dict, hash_model
|
|
8
|
+
|
|
9
|
+
# Provider ABCs and supporting types
|
|
10
|
+
from vystak.providers import (
|
|
11
|
+
AgentStatus,
|
|
12
|
+
ChannelAdapter,
|
|
13
|
+
DeployPlan,
|
|
14
|
+
DeployResult,
|
|
15
|
+
FrameworkAdapter,
|
|
16
|
+
GeneratedCode,
|
|
17
|
+
PlatformProvider,
|
|
18
|
+
ValidationError,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
# Provisioning engine
|
|
22
|
+
from vystak.provisioning import (
|
|
23
|
+
CommandHealthCheck,
|
|
24
|
+
CycleError,
|
|
25
|
+
HealthCheck,
|
|
26
|
+
HttpHealthCheck,
|
|
27
|
+
NoopHealthCheck,
|
|
28
|
+
Provisionable,
|
|
29
|
+
ProvisionError,
|
|
30
|
+
ProvisionGraph,
|
|
31
|
+
ProvisionResult,
|
|
32
|
+
TcpHealthCheck,
|
|
33
|
+
)
|
|
34
|
+
from vystak.schema import (
|
|
35
|
+
Agent,
|
|
36
|
+
Cache,
|
|
37
|
+
Channel,
|
|
38
|
+
ChannelProvider,
|
|
39
|
+
ChannelType,
|
|
40
|
+
Database,
|
|
41
|
+
Embedding,
|
|
42
|
+
Gateway,
|
|
43
|
+
McpServer,
|
|
44
|
+
McpTransport,
|
|
45
|
+
Model,
|
|
46
|
+
NamedModel,
|
|
47
|
+
ObjectStore,
|
|
48
|
+
Platform,
|
|
49
|
+
Postgres,
|
|
50
|
+
Provider,
|
|
51
|
+
Qdrant,
|
|
52
|
+
Queue,
|
|
53
|
+
Redis,
|
|
54
|
+
Resource,
|
|
55
|
+
Secret,
|
|
56
|
+
Service,
|
|
57
|
+
SessionStore,
|
|
58
|
+
Skill,
|
|
59
|
+
SkillRequirements,
|
|
60
|
+
SlackChannel,
|
|
61
|
+
Sqlite,
|
|
62
|
+
VectorStore,
|
|
63
|
+
Workspace,
|
|
64
|
+
WorkspaceType,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
# Loader
|
|
68
|
+
from vystak.schema.loader import dump_agent, load_agent
|
|
69
|
+
|
|
70
|
+
__all__ = [
|
|
71
|
+
"__version__",
|
|
72
|
+
"Agent",
|
|
73
|
+
"Cache",
|
|
74
|
+
"Channel",
|
|
75
|
+
"ChannelProvider",
|
|
76
|
+
"ChannelType",
|
|
77
|
+
"Database",
|
|
78
|
+
"Embedding",
|
|
79
|
+
"Gateway",
|
|
80
|
+
"McpServer",
|
|
81
|
+
"McpTransport",
|
|
82
|
+
"Model",
|
|
83
|
+
"NamedModel",
|
|
84
|
+
"ObjectStore",
|
|
85
|
+
"Platform",
|
|
86
|
+
"Postgres",
|
|
87
|
+
"Provider",
|
|
88
|
+
"Qdrant",
|
|
89
|
+
"Queue",
|
|
90
|
+
"Redis",
|
|
91
|
+
"Resource",
|
|
92
|
+
"Secret",
|
|
93
|
+
"Service",
|
|
94
|
+
"SessionStore",
|
|
95
|
+
"Skill",
|
|
96
|
+
"SkillRequirements",
|
|
97
|
+
"SlackChannel",
|
|
98
|
+
"Sqlite",
|
|
99
|
+
"VectorStore",
|
|
100
|
+
"Workspace",
|
|
101
|
+
"WorkspaceType",
|
|
102
|
+
"AgentHashTree",
|
|
103
|
+
"hash_agent",
|
|
104
|
+
"hash_dict",
|
|
105
|
+
"hash_model",
|
|
106
|
+
"dump_agent",
|
|
107
|
+
"load_agent",
|
|
108
|
+
"AgentStatus",
|
|
109
|
+
"ChannelAdapter",
|
|
110
|
+
"DeployPlan",
|
|
111
|
+
"DeployResult",
|
|
112
|
+
"FrameworkAdapter",
|
|
113
|
+
"GeneratedCode",
|
|
114
|
+
"PlatformProvider",
|
|
115
|
+
"ValidationError",
|
|
116
|
+
"CommandHealthCheck",
|
|
117
|
+
"CycleError",
|
|
118
|
+
"HealthCheck",
|
|
119
|
+
"HttpHealthCheck",
|
|
120
|
+
"NoopHealthCheck",
|
|
121
|
+
"Provisionable",
|
|
122
|
+
"ProvisionError",
|
|
123
|
+
"ProvisionGraph",
|
|
124
|
+
"ProvisionResult",
|
|
125
|
+
"TcpHealthCheck",
|
|
126
|
+
]
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Leaf-level hashing for Pydantic models and dicts."""
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
import json
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def hash_model(model: BaseModel) -> str:
|
|
10
|
+
"""SHA-256 of canonical JSON representation of a Pydantic model."""
|
|
11
|
+
canonical = json.dumps(model.model_dump(mode="python"), sort_keys=True, default=str)
|
|
12
|
+
return hashlib.sha256(canonical.encode()).hexdigest()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def hash_dict(data: dict) -> str:
|
|
16
|
+
"""SHA-256 of canonical JSON representation of a dict."""
|
|
17
|
+
canonical = json.dumps(data, sort_keys=True, default=str)
|
|
18
|
+
return hashlib.sha256(canonical.encode()).hexdigest()
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""Hash tree composition for agent definitions."""
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
|
|
6
|
+
from vystak.hash.hasher import hash_model
|
|
7
|
+
from vystak.schema.agent import Agent
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class AgentHashTree:
|
|
12
|
+
"""Per-section hashes for an agent, enabling partial deploy detection."""
|
|
13
|
+
|
|
14
|
+
brain: str
|
|
15
|
+
skills: str
|
|
16
|
+
mcp_servers: str
|
|
17
|
+
channels: str
|
|
18
|
+
workspace: str
|
|
19
|
+
resources: str
|
|
20
|
+
secrets: str
|
|
21
|
+
sessions: str
|
|
22
|
+
memory: str
|
|
23
|
+
services: str
|
|
24
|
+
root: str
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _hash_list(items: list) -> str:
|
|
28
|
+
if not items:
|
|
29
|
+
return hashlib.sha256(b"[]").hexdigest()
|
|
30
|
+
individual = sorted(hash_model(item) for item in items)
|
|
31
|
+
combined = "|".join(individual)
|
|
32
|
+
return hashlib.sha256(combined.encode()).hexdigest()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _hash_optional(item) -> str:
|
|
36
|
+
if item is None:
|
|
37
|
+
return hashlib.sha256(b"null").hexdigest()
|
|
38
|
+
return hash_model(item)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def hash_agent(agent: Agent) -> AgentHashTree:
|
|
42
|
+
"""Compute the full hash tree for an agent definition."""
|
|
43
|
+
brain = hash_model(agent.model)
|
|
44
|
+
skills = _hash_list(agent.skills)
|
|
45
|
+
mcp_servers = _hash_list(agent.mcp_servers)
|
|
46
|
+
channels = _hash_list(agent.channels)
|
|
47
|
+
workspace = _hash_optional(agent.workspace)
|
|
48
|
+
resources = _hash_list(agent.resources)
|
|
49
|
+
secrets = _hash_list(agent.secrets)
|
|
50
|
+
sessions = _hash_optional(agent.sessions)
|
|
51
|
+
memory = _hash_optional(agent.memory)
|
|
52
|
+
services = _hash_list(agent.services)
|
|
53
|
+
|
|
54
|
+
sections = "|".join(
|
|
55
|
+
[
|
|
56
|
+
brain,
|
|
57
|
+
skills,
|
|
58
|
+
mcp_servers,
|
|
59
|
+
channels,
|
|
60
|
+
workspace,
|
|
61
|
+
resources,
|
|
62
|
+
secrets,
|
|
63
|
+
sessions,
|
|
64
|
+
memory,
|
|
65
|
+
services,
|
|
66
|
+
]
|
|
67
|
+
)
|
|
68
|
+
root = hashlib.sha256(sections.encode()).hexdigest()
|
|
69
|
+
|
|
70
|
+
return AgentHashTree(
|
|
71
|
+
brain=brain,
|
|
72
|
+
skills=skills,
|
|
73
|
+
mcp_servers=mcp_servers,
|
|
74
|
+
channels=channels,
|
|
75
|
+
workspace=workspace,
|
|
76
|
+
resources=resources,
|
|
77
|
+
secrets=secrets,
|
|
78
|
+
sessions=sessions,
|
|
79
|
+
memory=memory,
|
|
80
|
+
services=services,
|
|
81
|
+
root=root,
|
|
82
|
+
)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Intermediate representation consumed by framework adapters."""
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Provider base classes for platform and resource provisioning."""
|
|
2
|
+
|
|
3
|
+
from vystak.providers.base import (
|
|
4
|
+
AgentStatus,
|
|
5
|
+
ChannelAdapter,
|
|
6
|
+
DeployPlan,
|
|
7
|
+
DeployResult,
|
|
8
|
+
FrameworkAdapter,
|
|
9
|
+
GeneratedCode,
|
|
10
|
+
PlatformProvider,
|
|
11
|
+
ValidationError,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"AgentStatus",
|
|
16
|
+
"ChannelAdapter",
|
|
17
|
+
"DeployPlan",
|
|
18
|
+
"DeployResult",
|
|
19
|
+
"FrameworkAdapter",
|
|
20
|
+
"GeneratedCode",
|
|
21
|
+
"PlatformProvider",
|
|
22
|
+
"ValidationError",
|
|
23
|
+
]
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""Abstract base classes for framework adapters, platform providers, and channel adapters."""
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
|
|
6
|
+
from vystak.schema.agent import Agent
|
|
7
|
+
from vystak.schema.channel import Channel
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class GeneratedCode:
|
|
12
|
+
files: dict[str, str]
|
|
13
|
+
entrypoint: str
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class DeployPlan:
|
|
18
|
+
agent_name: str
|
|
19
|
+
actions: list[str]
|
|
20
|
+
current_hash: str | None
|
|
21
|
+
target_hash: str
|
|
22
|
+
changes: dict[str, tuple[str | None, str]]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass
|
|
26
|
+
class DeployResult:
|
|
27
|
+
agent_name: str
|
|
28
|
+
success: bool
|
|
29
|
+
hash: str
|
|
30
|
+
message: str
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass
|
|
34
|
+
class AgentStatus:
|
|
35
|
+
agent_name: str
|
|
36
|
+
running: bool
|
|
37
|
+
hash: str | None
|
|
38
|
+
info: dict = field(default_factory=dict)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass
|
|
42
|
+
class ValidationError:
|
|
43
|
+
field: str
|
|
44
|
+
message: str
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class FrameworkAdapter(ABC):
|
|
48
|
+
@abstractmethod
|
|
49
|
+
def generate(self, agent: Agent) -> GeneratedCode: ...
|
|
50
|
+
|
|
51
|
+
@abstractmethod
|
|
52
|
+
def validate(self, agent: Agent) -> list[ValidationError]: ...
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class PlatformProvider(ABC):
|
|
56
|
+
@abstractmethod
|
|
57
|
+
def plan(self, agent: Agent, current_hash: str | None) -> DeployPlan: ...
|
|
58
|
+
|
|
59
|
+
@abstractmethod
|
|
60
|
+
def apply(self, plan: DeployPlan) -> DeployResult: ...
|
|
61
|
+
|
|
62
|
+
@abstractmethod
|
|
63
|
+
def destroy(self, agent_name: str) -> None: ...
|
|
64
|
+
|
|
65
|
+
@abstractmethod
|
|
66
|
+
def status(self, agent_name: str) -> AgentStatus: ...
|
|
67
|
+
|
|
68
|
+
@abstractmethod
|
|
69
|
+
def get_hash(self, agent_name: str) -> str | None: ...
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class ChannelAdapter(ABC):
|
|
73
|
+
@abstractmethod
|
|
74
|
+
def setup(self, agent: Agent, channel: Channel) -> None: ...
|
|
75
|
+
|
|
76
|
+
@abstractmethod
|
|
77
|
+
def teardown(self, channel: Channel) -> None: ...
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""Provisioning engine — dependency graph, health checks, and provisionable protocol."""
|
|
2
|
+
|
|
3
|
+
from vystak.provisioning.graph import CycleError, ProvisionError, ProvisionGraph
|
|
4
|
+
from vystak.provisioning.grouping import group_agents_by_platform, platform_fingerprint
|
|
5
|
+
from vystak.provisioning.health import (
|
|
6
|
+
CommandHealthCheck,
|
|
7
|
+
HealthCheck,
|
|
8
|
+
HttpHealthCheck,
|
|
9
|
+
NoopHealthCheck,
|
|
10
|
+
TcpHealthCheck,
|
|
11
|
+
)
|
|
12
|
+
from vystak.provisioning.listener import (
|
|
13
|
+
NullListener,
|
|
14
|
+
PrintListener,
|
|
15
|
+
ProvisionEvent,
|
|
16
|
+
ProvisionListener,
|
|
17
|
+
)
|
|
18
|
+
from vystak.provisioning.node import Provisionable, ProvisionResult
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"CommandHealthCheck",
|
|
22
|
+
"CycleError",
|
|
23
|
+
"group_agents_by_platform",
|
|
24
|
+
"HealthCheck",
|
|
25
|
+
"HttpHealthCheck",
|
|
26
|
+
"NoopHealthCheck",
|
|
27
|
+
"NullListener",
|
|
28
|
+
"platform_fingerprint",
|
|
29
|
+
"PrintListener",
|
|
30
|
+
"Provisionable",
|
|
31
|
+
"ProvisionError",
|
|
32
|
+
"ProvisionEvent",
|
|
33
|
+
"ProvisionGraph",
|
|
34
|
+
"ProvisionListener",
|
|
35
|
+
"ProvisionResult",
|
|
36
|
+
"TcpHealthCheck",
|
|
37
|
+
]
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"""Dependency graph for provisioning resources."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
|
|
5
|
+
from vystak.provisioning.listener import NullListener, ProvisionEvent, ProvisionListener
|
|
6
|
+
from vystak.provisioning.node import Provisionable, ProvisionResult
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class CycleError(Exception):
|
|
10
|
+
pass
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ProvisionError(Exception):
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class ProvisionGraph:
|
|
19
|
+
_nodes: dict[str, Provisionable] = field(default_factory=dict)
|
|
20
|
+
_implicit_deps: dict[str, list[str]] = field(default_factory=dict)
|
|
21
|
+
_listener: ProvisionListener = field(default_factory=NullListener)
|
|
22
|
+
|
|
23
|
+
def set_listener(self, listener: ProvisionListener) -> None:
|
|
24
|
+
"""Set the event listener for provision progress."""
|
|
25
|
+
self._listener = listener
|
|
26
|
+
|
|
27
|
+
def add(self, node: Provisionable) -> None:
|
|
28
|
+
self._nodes[node.name] = node
|
|
29
|
+
|
|
30
|
+
def add_dependency(self, name: str, depends_on: str) -> None:
|
|
31
|
+
self._implicit_deps.setdefault(name, []).append(depends_on)
|
|
32
|
+
|
|
33
|
+
def _all_deps(self, name: str) -> list[str]:
|
|
34
|
+
node = self._nodes[name]
|
|
35
|
+
all_deps = list(node.depends_on)
|
|
36
|
+
all_deps.extend(self._implicit_deps.get(name, []))
|
|
37
|
+
return [d for d in all_deps if d in self._nodes]
|
|
38
|
+
|
|
39
|
+
def _resolve_order(self) -> list[str]:
|
|
40
|
+
reverse: dict[str, list[str]] = {name: [] for name in self._nodes}
|
|
41
|
+
in_degree: dict[str, int] = {name: 0 for name in self._nodes}
|
|
42
|
+
|
|
43
|
+
for name in self._nodes:
|
|
44
|
+
deps = self._all_deps(name)
|
|
45
|
+
in_degree[name] = len(deps)
|
|
46
|
+
for dep in deps:
|
|
47
|
+
reverse[dep].append(name)
|
|
48
|
+
|
|
49
|
+
queue = [n for n, d in in_degree.items() if d == 0]
|
|
50
|
+
order: list[str] = []
|
|
51
|
+
|
|
52
|
+
while queue:
|
|
53
|
+
node = queue.pop(0)
|
|
54
|
+
order.append(node)
|
|
55
|
+
for dependent in reverse[node]:
|
|
56
|
+
in_degree[dependent] -= 1
|
|
57
|
+
if in_degree[dependent] == 0:
|
|
58
|
+
queue.append(dependent)
|
|
59
|
+
|
|
60
|
+
if len(order) != len(self._nodes):
|
|
61
|
+
raise CycleError("Dependency cycle detected in provision graph")
|
|
62
|
+
|
|
63
|
+
return order
|
|
64
|
+
|
|
65
|
+
def execute(self) -> dict[str, ProvisionResult]:
|
|
66
|
+
order = self._resolve_order()
|
|
67
|
+
results: dict[str, ProvisionResult] = {}
|
|
68
|
+
|
|
69
|
+
for name in order:
|
|
70
|
+
node = self._nodes[name]
|
|
71
|
+
|
|
72
|
+
self._listener.on_start(
|
|
73
|
+
ProvisionEvent(
|
|
74
|
+
node_name=name,
|
|
75
|
+
event_type="start",
|
|
76
|
+
message=name,
|
|
77
|
+
)
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
# Pass listener to node if it supports it
|
|
81
|
+
if hasattr(node, "set_listener"):
|
|
82
|
+
node.set_listener(self._listener)
|
|
83
|
+
|
|
84
|
+
result = node.provision(context=results)
|
|
85
|
+
results[name] = result
|
|
86
|
+
|
|
87
|
+
if not result.success:
|
|
88
|
+
self._listener.on_error(
|
|
89
|
+
ProvisionEvent(
|
|
90
|
+
node_name=name,
|
|
91
|
+
event_type="error",
|
|
92
|
+
message=name,
|
|
93
|
+
detail=result.error or "",
|
|
94
|
+
)
|
|
95
|
+
)
|
|
96
|
+
raise ProvisionError(f"Failed to provision {name}: {result.error}")
|
|
97
|
+
|
|
98
|
+
self._listener.on_complete(
|
|
99
|
+
ProvisionEvent(
|
|
100
|
+
node_name=name,
|
|
101
|
+
event_type="complete",
|
|
102
|
+
message=name,
|
|
103
|
+
detail=result.info.get("detail", ""),
|
|
104
|
+
)
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
check = node.health_check()
|
|
108
|
+
self._listener.on_health_check(
|
|
109
|
+
ProvisionEvent(
|
|
110
|
+
node_name=name,
|
|
111
|
+
event_type="health_check",
|
|
112
|
+
message=f"Waiting for {name}",
|
|
113
|
+
)
|
|
114
|
+
)
|
|
115
|
+
check.wait()
|
|
116
|
+
|
|
117
|
+
return results
|
|
118
|
+
|
|
119
|
+
def destroy_all(self) -> None:
|
|
120
|
+
order = self._resolve_order()
|
|
121
|
+
for name in reversed(order):
|
|
122
|
+
self._nodes[name].destroy()
|