sanctuary-dna 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.
- sanctuary_dna-0.1.0/.gitignore +7 -0
- sanctuary_dna-0.1.0/PKG-INFO +100 -0
- sanctuary_dna-0.1.0/README.md +76 -0
- sanctuary_dna-0.1.0/pyproject.toml +45 -0
- sanctuary_dna-0.1.0/sdna/__init__.py +79 -0
- sanctuary_dna-0.1.0/sdna/ariadne.py +328 -0
- sanctuary_dna-0.1.0/sdna/brain.py +622 -0
- sanctuary_dna-0.1.0/sdna/config.py +258 -0
- sanctuary_dna-0.1.0/sdna/poimandres.py +82 -0
- sanctuary_dna-0.1.0/sdna/runner.py +131 -0
- sanctuary_dna-0.1.0/sdna/sdna.py +188 -0
- sanctuary_dna-0.1.0/sdna/tools.py +180 -0
- sanctuary_dna-0.1.0/tests/test_sdna.py +113 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sanctuary-dna
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Sanctuary DNA - Gnostic agent workflow DSL. Ariadne (threading) + Poimandres (generation) = SDNA spiral
|
|
5
|
+
Project-URL: Homepage, https://github.com/sancovp/sdna
|
|
6
|
+
Project-URL: Repository, https://github.com/sancovp/sdna
|
|
7
|
+
Author-email: Isaac Wostrel-Rubin <isaac@sancovp.com>
|
|
8
|
+
License: MIT
|
|
9
|
+
Keywords: agent,anthropic,claude,composition,sdk,workflow
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Requires-Python: >=3.10
|
|
18
|
+
Requires-Dist: anthropic>=0.40.0
|
|
19
|
+
Requires-Dist: pydantic>=2.0
|
|
20
|
+
Provides-Extra: dev
|
|
21
|
+
Requires-Dist: pytest-asyncio>=0.21; extra == 'dev'
|
|
22
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# SDNA - Sanctuary DNA
|
|
26
|
+
|
|
27
|
+
Gnostic agent workflow composition for Claude Agent SDK.
|
|
28
|
+
|
|
29
|
+
**Ariadne** (threading) + **Poimandres** (generation) = **SDNA** spiral
|
|
30
|
+
|
|
31
|
+
## Installation
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pip install sdna-agent-sdk
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Quick Start
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
from sdna import ariadne, human, inject_file, sdnac, sdna_flow, HermesConfig
|
|
41
|
+
|
|
42
|
+
# 1. Build Ariadne thread (context prep)
|
|
43
|
+
thread = ariadne('my-thread',
|
|
44
|
+
inject_file('spec.md', 'spec'),
|
|
45
|
+
human('Approve spec?', 'approval'), # Pauses for human input
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
# 2. Create HermesConfig (the message)
|
|
49
|
+
config = HermesConfig(
|
|
50
|
+
name="generator",
|
|
51
|
+
system_prompt="You are a code generator...",
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
# 3. Combine into SDNAC
|
|
55
|
+
unit = sdnac('generate-code', thread, config)
|
|
56
|
+
|
|
57
|
+
# 4. Execute
|
|
58
|
+
result = await unit.execute({'initial': 'context'})
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## The Trinity
|
|
62
|
+
|
|
63
|
+
| Component | Role | What It Does |
|
|
64
|
+
|-----------|------|--------------|
|
|
65
|
+
| **Ariadne** | Threader | Context manipulation: inject, weave, dovetail, human input |
|
|
66
|
+
| **Poimandres** | Divine Mind | Generation moment - takes config, runs agent, returns output |
|
|
67
|
+
| **HermesConfig** | The Message | Runner configuration Ariadne sends to Poimandres |
|
|
68
|
+
|
|
69
|
+
## Decision Tree: What to Build
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
Is this continuous improvement / optimization loop?
|
|
73
|
+
├── YES → SDNA^F (SDNAFlowchain)
|
|
74
|
+
│ Optimizer + target pairs. Meta-optimization.
|
|
75
|
+
│
|
|
76
|
+
└── NO → Are you composing multiple agent units in sequence?
|
|
77
|
+
├── YES → SDNAF (SDNAFlow)
|
|
78
|
+
│ Flow of SDNACs. Sequential execution.
|
|
79
|
+
│
|
|
80
|
+
└── NO → SDNAC
|
|
81
|
+
Single unit: AriadneChain → HermesConfig → Poimandres executes
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Ariadne Elements
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
from sdna import (
|
|
88
|
+
ariadne, # Create chain
|
|
89
|
+
human, # Human input stop step
|
|
90
|
+
inject_file, # Inject file contents
|
|
91
|
+
inject_func, # Inject function result
|
|
92
|
+
inject_literal, # Inject literal value
|
|
93
|
+
inject_env, # Inject env variable
|
|
94
|
+
weave, # Context surgery between sessions
|
|
95
|
+
)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## License
|
|
99
|
+
|
|
100
|
+
MIT
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# SDNA - Sanctuary DNA
|
|
2
|
+
|
|
3
|
+
Gnostic agent workflow composition for Claude Agent SDK.
|
|
4
|
+
|
|
5
|
+
**Ariadne** (threading) + **Poimandres** (generation) = **SDNA** spiral
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install sdna-agent-sdk
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
from sdna import ariadne, human, inject_file, sdnac, sdna_flow, HermesConfig
|
|
17
|
+
|
|
18
|
+
# 1. Build Ariadne thread (context prep)
|
|
19
|
+
thread = ariadne('my-thread',
|
|
20
|
+
inject_file('spec.md', 'spec'),
|
|
21
|
+
human('Approve spec?', 'approval'), # Pauses for human input
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
# 2. Create HermesConfig (the message)
|
|
25
|
+
config = HermesConfig(
|
|
26
|
+
name="generator",
|
|
27
|
+
system_prompt="You are a code generator...",
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
# 3. Combine into SDNAC
|
|
31
|
+
unit = sdnac('generate-code', thread, config)
|
|
32
|
+
|
|
33
|
+
# 4. Execute
|
|
34
|
+
result = await unit.execute({'initial': 'context'})
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## The Trinity
|
|
38
|
+
|
|
39
|
+
| Component | Role | What It Does |
|
|
40
|
+
|-----------|------|--------------|
|
|
41
|
+
| **Ariadne** | Threader | Context manipulation: inject, weave, dovetail, human input |
|
|
42
|
+
| **Poimandres** | Divine Mind | Generation moment - takes config, runs agent, returns output |
|
|
43
|
+
| **HermesConfig** | The Message | Runner configuration Ariadne sends to Poimandres |
|
|
44
|
+
|
|
45
|
+
## Decision Tree: What to Build
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
Is this continuous improvement / optimization loop?
|
|
49
|
+
├── YES → SDNA^F (SDNAFlowchain)
|
|
50
|
+
│ Optimizer + target pairs. Meta-optimization.
|
|
51
|
+
│
|
|
52
|
+
└── NO → Are you composing multiple agent units in sequence?
|
|
53
|
+
├── YES → SDNAF (SDNAFlow)
|
|
54
|
+
│ Flow of SDNACs. Sequential execution.
|
|
55
|
+
│
|
|
56
|
+
└── NO → SDNAC
|
|
57
|
+
Single unit: AriadneChain → HermesConfig → Poimandres executes
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Ariadne Elements
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
from sdna import (
|
|
64
|
+
ariadne, # Create chain
|
|
65
|
+
human, # Human input stop step
|
|
66
|
+
inject_file, # Inject file contents
|
|
67
|
+
inject_func, # Inject function result
|
|
68
|
+
inject_literal, # Inject literal value
|
|
69
|
+
inject_env, # Inject env variable
|
|
70
|
+
weave, # Context surgery between sessions
|
|
71
|
+
)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## License
|
|
75
|
+
|
|
76
|
+
MIT
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "sanctuary-dna"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Sanctuary DNA - Gnostic agent workflow DSL. Ariadne (threading) + Poimandres (generation) = SDNA spiral"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = {text = "MIT"}
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
authors = [
|
|
13
|
+
{name = "Isaac Wostrel-Rubin", email = "isaac@sancovp.com"}
|
|
14
|
+
]
|
|
15
|
+
keywords = ["agent", "workflow", "claude", "anthropic", "sdk", "composition"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 3 - Alpha",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"License :: OSI Approved :: MIT License",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3.10",
|
|
22
|
+
"Programming Language :: Python :: 3.11",
|
|
23
|
+
"Programming Language :: Python :: 3.12",
|
|
24
|
+
]
|
|
25
|
+
dependencies = [
|
|
26
|
+
"pydantic>=2.0",
|
|
27
|
+
"anthropic>=0.40.0",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[project.optional-dependencies]
|
|
31
|
+
dev = [
|
|
32
|
+
"pytest>=7.0",
|
|
33
|
+
"pytest-asyncio>=0.21",
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
[project.urls]
|
|
37
|
+
Homepage = "https://github.com/sancovp/sdna"
|
|
38
|
+
Repository = "https://github.com/sancovp/sdna"
|
|
39
|
+
|
|
40
|
+
[tool.hatch.build.targets.wheel]
|
|
41
|
+
packages = ["sdna"]
|
|
42
|
+
|
|
43
|
+
[tool.pytest.ini_options]
|
|
44
|
+
asyncio_mode = "auto"
|
|
45
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SDNA - Sanctuary DNA
|
|
3
|
+
|
|
4
|
+
Gnostic agent workflow DSL for Claude Agent SDK.
|
|
5
|
+
Ariadne (threading) + Poimandres (generation) = SDNA spiral.
|
|
6
|
+
|
|
7
|
+
Components:
|
|
8
|
+
- Ariadne: context threading (inject, weave, human input)
|
|
9
|
+
- Poimandres: generation moment (execute)
|
|
10
|
+
- SDNA: spiral composition (SDNAC → SDNAF → SDNA^F)
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from .config import HermesConfig, DovetailModel, HermesConfigInput
|
|
14
|
+
from .tools import BlockedReport, parse_blocked_from_text, get_cached_reports, clear_cached_reports
|
|
15
|
+
from .runner import agent_step, StepResult, StepStatus
|
|
16
|
+
from .ariadne import (
|
|
17
|
+
AriadneChain, AriadneResult, AriadneStatus,
|
|
18
|
+
AriadneElement, HumanInput, InjectConfig, WeaveConfig, BrainInjectConfig,
|
|
19
|
+
ariadne, human, inject_file, inject_func, inject_literal, inject_env, weave, inject_brain,
|
|
20
|
+
)
|
|
21
|
+
from .brain import Brain, BrainConfig, Neuron, CognitionResult
|
|
22
|
+
from .sdna import (
|
|
23
|
+
SDNAC, SDNAFlow, SDNAFlowchain,
|
|
24
|
+
SDNAResult, SDNAStatus,
|
|
25
|
+
SDNACConfig, OptimizerSDNACConfig, SDNAFlowConfig,
|
|
26
|
+
sdnac, sdna_flow,
|
|
27
|
+
)
|
|
28
|
+
from . import poimandres
|
|
29
|
+
|
|
30
|
+
__all__ = [
|
|
31
|
+
# Config
|
|
32
|
+
"HermesConfig",
|
|
33
|
+
"DovetailModel",
|
|
34
|
+
"HermesConfigInput",
|
|
35
|
+
# Tools
|
|
36
|
+
"BlockedReport",
|
|
37
|
+
"parse_blocked_from_text",
|
|
38
|
+
"get_cached_reports",
|
|
39
|
+
"clear_cached_reports",
|
|
40
|
+
# Runner
|
|
41
|
+
"agent_step",
|
|
42
|
+
"StepResult",
|
|
43
|
+
"StepStatus",
|
|
44
|
+
# Ariadne (context threading)
|
|
45
|
+
"AriadneChain",
|
|
46
|
+
"AriadneResult",
|
|
47
|
+
"AriadneStatus",
|
|
48
|
+
"AriadneElement",
|
|
49
|
+
"HumanInput",
|
|
50
|
+
"InjectConfig",
|
|
51
|
+
"WeaveConfig",
|
|
52
|
+
"BrainInjectConfig",
|
|
53
|
+
"ariadne",
|
|
54
|
+
"human",
|
|
55
|
+
"inject_file",
|
|
56
|
+
"inject_func",
|
|
57
|
+
"inject_literal",
|
|
58
|
+
"inject_env",
|
|
59
|
+
"weave",
|
|
60
|
+
"inject_brain",
|
|
61
|
+
# Brain (neural knowledge retrieval)
|
|
62
|
+
"Brain",
|
|
63
|
+
"BrainConfig",
|
|
64
|
+
"Neuron",
|
|
65
|
+
"CognitionResult",
|
|
66
|
+
# Poimandres (generation moment)
|
|
67
|
+
"poimandres",
|
|
68
|
+
# SDNA (spiral composition)
|
|
69
|
+
"SDNAC",
|
|
70
|
+
"SDNAFlow",
|
|
71
|
+
"SDNAFlowchain",
|
|
72
|
+
"SDNAResult",
|
|
73
|
+
"SDNAStatus",
|
|
74
|
+
"SDNACConfig",
|
|
75
|
+
"OptimizerSDNACConfig",
|
|
76
|
+
"SDNAFlowConfig",
|
|
77
|
+
"sdnac",
|
|
78
|
+
"sdna_flow",
|
|
79
|
+
]
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Ariadne - The Threader
|
|
3
|
+
|
|
4
|
+
Context manipulation: inject, weave, dovetail, human input.
|
|
5
|
+
Ariadne prepares the thread that guides Poimandres.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Dict, Any, List, Union, Optional
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
from enum import Enum
|
|
11
|
+
from pydantic import BaseModel, Field
|
|
12
|
+
import importlib
|
|
13
|
+
import os
|
|
14
|
+
|
|
15
|
+
from .config import DovetailModel
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# =============================================================================
|
|
19
|
+
# ARIADNE ELEMENTS
|
|
20
|
+
# =============================================================================
|
|
21
|
+
|
|
22
|
+
class HumanInput(BaseModel):
|
|
23
|
+
"""Stop step - pause, return to human, resume with answer."""
|
|
24
|
+
prompt: str
|
|
25
|
+
input_key: str
|
|
26
|
+
choices: Optional[List[str]] = None
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class InjectConfig(BaseModel):
|
|
30
|
+
"""Inject external data into context."""
|
|
31
|
+
source: str # "file", "function", "literal", "env"
|
|
32
|
+
inject_as: str
|
|
33
|
+
path: Optional[str] = None
|
|
34
|
+
module: Optional[str] = None
|
|
35
|
+
func: Optional[str] = None
|
|
36
|
+
args: Dict[str, Any] = Field(default_factory=dict)
|
|
37
|
+
value: Optional[Any] = None
|
|
38
|
+
env_var: Optional[str] = None
|
|
39
|
+
default: Optional[str] = None
|
|
40
|
+
|
|
41
|
+
async def execute(self, context: Dict[str, Any]) -> Dict[str, Any]:
|
|
42
|
+
if self.source == "file":
|
|
43
|
+
with open(self.path, 'r') as f:
|
|
44
|
+
context[self.inject_as] = f.read()
|
|
45
|
+
elif self.source == "function":
|
|
46
|
+
mod = importlib.import_module(self.module)
|
|
47
|
+
fn = getattr(mod, self.func)
|
|
48
|
+
resolved = {
|
|
49
|
+
k: context.get(v[1:], v) if isinstance(v, str) and v.startswith("$") else v
|
|
50
|
+
for k, v in self.args.items()
|
|
51
|
+
}
|
|
52
|
+
context[self.inject_as] = fn(**resolved)
|
|
53
|
+
elif self.source == "literal":
|
|
54
|
+
context[self.inject_as] = self.value
|
|
55
|
+
elif self.source == "env":
|
|
56
|
+
context[self.inject_as] = os.environ.get(self.env_var, self.default)
|
|
57
|
+
return context
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class WeaveConfig(BaseModel):
|
|
61
|
+
"""Context surgery - move message ranges between sessions."""
|
|
62
|
+
source_session: Optional[str] = None
|
|
63
|
+
target_session: Optional[str] = None
|
|
64
|
+
start_index: Optional[int] = None
|
|
65
|
+
end_index: Optional[int] = None
|
|
66
|
+
inject_as: str = "woven_context"
|
|
67
|
+
|
|
68
|
+
async def execute(self, context: Dict[str, Any]) -> Dict[str, Any]:
|
|
69
|
+
# TODO: Implement with SDK session access
|
|
70
|
+
context[self.inject_as] = {
|
|
71
|
+
"source": self.source_session,
|
|
72
|
+
"range": (self.start_index, self.end_index),
|
|
73
|
+
"_pending": True,
|
|
74
|
+
}
|
|
75
|
+
return context
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
AriadneElement = Union[HumanInput, InjectConfig, WeaveConfig, DovetailModel, "BrainInjectConfig"]
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
# =============================================================================
|
|
82
|
+
# RESULT
|
|
83
|
+
# =============================================================================
|
|
84
|
+
|
|
85
|
+
class AriadneStatus(str, Enum):
|
|
86
|
+
SUCCESS = "success"
|
|
87
|
+
ERROR = "error"
|
|
88
|
+
AWAITING_INPUT = "awaiting_input"
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@dataclass
|
|
92
|
+
class AriadneResult:
|
|
93
|
+
"""Result of Ariadne chain execution."""
|
|
94
|
+
status: AriadneStatus
|
|
95
|
+
context: Dict[str, Any] = field(default_factory=dict)
|
|
96
|
+
error: Optional[str] = None
|
|
97
|
+
# For AWAITING_INPUT
|
|
98
|
+
pending_prompt: Optional[str] = None
|
|
99
|
+
pending_input_key: Optional[str] = None
|
|
100
|
+
pending_choices: Optional[List[str]] = None
|
|
101
|
+
resume_at: Optional[int] = None
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
# =============================================================================
|
|
105
|
+
# ARIADNE CHAIN
|
|
106
|
+
# =============================================================================
|
|
107
|
+
|
|
108
|
+
class AriadneChain:
|
|
109
|
+
"""
|
|
110
|
+
Chain of context operations.
|
|
111
|
+
Prepares the thread that guides Poimandres.
|
|
112
|
+
"""
|
|
113
|
+
|
|
114
|
+
def __init__(self, name: str, elements: List[AriadneElement]):
|
|
115
|
+
self.name = name
|
|
116
|
+
self.elements = elements
|
|
117
|
+
|
|
118
|
+
async def execute(
|
|
119
|
+
self,
|
|
120
|
+
context: Optional[Dict[str, Any]] = None,
|
|
121
|
+
start_at: int = 0,
|
|
122
|
+
) -> AriadneResult:
|
|
123
|
+
ctx = dict(context) if context else {}
|
|
124
|
+
|
|
125
|
+
for i in range(start_at, len(self.elements)):
|
|
126
|
+
elem = self.elements[i]
|
|
127
|
+
|
|
128
|
+
try:
|
|
129
|
+
if isinstance(elem, HumanInput):
|
|
130
|
+
return AriadneResult(
|
|
131
|
+
status=AriadneStatus.AWAITING_INPUT,
|
|
132
|
+
context=ctx,
|
|
133
|
+
pending_prompt=elem.prompt,
|
|
134
|
+
pending_input_key=elem.input_key,
|
|
135
|
+
pending_choices=elem.choices,
|
|
136
|
+
resume_at=i + 1,
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
elif isinstance(elem, InjectConfig):
|
|
140
|
+
ctx = await elem.execute(ctx)
|
|
141
|
+
|
|
142
|
+
elif isinstance(elem, WeaveConfig):
|
|
143
|
+
ctx = await elem.execute(ctx)
|
|
144
|
+
|
|
145
|
+
elif isinstance(elem, BrainInjectConfig):
|
|
146
|
+
ctx = await elem.execute(ctx)
|
|
147
|
+
|
|
148
|
+
elif isinstance(elem, DovetailModel):
|
|
149
|
+
next_inputs = elem.prepare_next_inputs(ctx)
|
|
150
|
+
ctx.update(next_inputs)
|
|
151
|
+
|
|
152
|
+
except Exception as e:
|
|
153
|
+
return AriadneResult(status=AriadneStatus.ERROR, context=ctx, error=str(e))
|
|
154
|
+
|
|
155
|
+
return AriadneResult(status=AriadneStatus.SUCCESS, context=ctx)
|
|
156
|
+
|
|
157
|
+
def __repr__(self):
|
|
158
|
+
return f"AriadneChain('{self.name}', {len(self.elements)} elements)"
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
# =============================================================================
|
|
162
|
+
# CONVENIENCE CONSTRUCTORS
|
|
163
|
+
# =============================================================================
|
|
164
|
+
|
|
165
|
+
def ariadne(name: str, *elements: AriadneElement) -> AriadneChain:
|
|
166
|
+
"""
|
|
167
|
+
Create an Ariadne chain for context threading.
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
name: Chain identifier
|
|
171
|
+
*elements: HumanInput, InjectConfig, WeaveConfig, or DovetailModel
|
|
172
|
+
|
|
173
|
+
Example:
|
|
174
|
+
thread = ariadne('prep',
|
|
175
|
+
inject_file('spec.md', 'spec'),
|
|
176
|
+
human('Approve?', 'approval'),
|
|
177
|
+
)
|
|
178
|
+
"""
|
|
179
|
+
return AriadneChain(name, list(elements))
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def human(prompt: str, as_key: str, choices: List[str] = None) -> HumanInput:
|
|
183
|
+
"""
|
|
184
|
+
Create a human input stop step. Pauses chain, awaits input, resumes.
|
|
185
|
+
|
|
186
|
+
Args:
|
|
187
|
+
prompt: Question to show the human
|
|
188
|
+
as_key: Context key where answer is stored
|
|
189
|
+
choices: Optional list of choices to present
|
|
190
|
+
|
|
191
|
+
Example:
|
|
192
|
+
human('Which approach?', 'choice', ['A', 'B', 'C'])
|
|
193
|
+
"""
|
|
194
|
+
return HumanInput(prompt=prompt, input_key=as_key, choices=choices)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def inject_file(path: str, as_key: str) -> InjectConfig:
|
|
198
|
+
"""
|
|
199
|
+
Inject file contents into context.
|
|
200
|
+
|
|
201
|
+
Args:
|
|
202
|
+
path: Path to file to read
|
|
203
|
+
as_key: Context key where contents are stored
|
|
204
|
+
|
|
205
|
+
Example:
|
|
206
|
+
inject_file('README.md', 'readme')
|
|
207
|
+
"""
|
|
208
|
+
return InjectConfig(source="file", path=path, inject_as=as_key)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def inject_func(module: str, func: str, as_key: str, **args) -> InjectConfig:
|
|
212
|
+
"""
|
|
213
|
+
Inject function result into context.
|
|
214
|
+
|
|
215
|
+
Args:
|
|
216
|
+
module: Python module path (e.g., 'mypackage.utils')
|
|
217
|
+
func: Function name to call
|
|
218
|
+
as_key: Context key where result is stored
|
|
219
|
+
**args: Arguments to pass (use $key to reference context values)
|
|
220
|
+
|
|
221
|
+
Example:
|
|
222
|
+
inject_func('utils', 'get_data', 'data', id='$user_id')
|
|
223
|
+
"""
|
|
224
|
+
return InjectConfig(source="function", module=module, func=func, args=args, inject_as=as_key)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def inject_literal(value: Any, as_key: str) -> InjectConfig:
|
|
228
|
+
"""
|
|
229
|
+
Inject a literal value into context.
|
|
230
|
+
|
|
231
|
+
Args:
|
|
232
|
+
value: Any value to inject
|
|
233
|
+
as_key: Context key where value is stored
|
|
234
|
+
|
|
235
|
+
Example:
|
|
236
|
+
inject_literal({'mode': 'fast'}, 'config')
|
|
237
|
+
"""
|
|
238
|
+
return InjectConfig(source="literal", value=value, inject_as=as_key)
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def inject_env(env_var: str, as_key: str, default: str = None) -> InjectConfig:
|
|
242
|
+
"""
|
|
243
|
+
Inject environment variable into context.
|
|
244
|
+
|
|
245
|
+
Args:
|
|
246
|
+
env_var: Environment variable name
|
|
247
|
+
as_key: Context key where value is stored
|
|
248
|
+
default: Default if env var not set
|
|
249
|
+
|
|
250
|
+
Example:
|
|
251
|
+
inject_env('API_KEY', 'api_key', default='none')
|
|
252
|
+
"""
|
|
253
|
+
return InjectConfig(source="env", env_var=env_var, default=default, inject_as=as_key)
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def weave(source: str = None, start: int = None, end: int = None, as_key: str = "woven") -> WeaveConfig:
|
|
257
|
+
"""
|
|
258
|
+
Weave message ranges between sessions (context surgery).
|
|
259
|
+
|
|
260
|
+
Args:
|
|
261
|
+
source: Source session ID
|
|
262
|
+
start: Start message index
|
|
263
|
+
end: End message index
|
|
264
|
+
as_key: Context key where woven content is stored
|
|
265
|
+
|
|
266
|
+
Example:
|
|
267
|
+
weave(source='session_123', start=5, end=10, as_key='prior_context')
|
|
268
|
+
"""
|
|
269
|
+
return WeaveConfig(source_session=source, start_index=start, end_index=end, inject_as=as_key)
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
class BrainInjectConfig(BaseModel):
|
|
273
|
+
"""Inject knowledge from Brain neurons into context."""
|
|
274
|
+
brain_directory: str
|
|
275
|
+
query_key: str # Context key containing query (or literal if starts with !)
|
|
276
|
+
inject_as: str
|
|
277
|
+
max_neurons: int = 5
|
|
278
|
+
extensions: list = Field(default_factory=lambda: [".md", ".txt", ".py"])
|
|
279
|
+
|
|
280
|
+
async def execute(self, context: Dict[str, Any]) -> Dict[str, Any]:
|
|
281
|
+
from .brain import Brain, BrainConfig
|
|
282
|
+
|
|
283
|
+
# Resolve query from context or use literal
|
|
284
|
+
if self.query_key.startswith("$"):
|
|
285
|
+
query = context.get(self.query_key[1:], "")
|
|
286
|
+
else:
|
|
287
|
+
query = self.query_key
|
|
288
|
+
|
|
289
|
+
# Create and run brain
|
|
290
|
+
brain_config = BrainConfig(
|
|
291
|
+
name="ariadne_brain",
|
|
292
|
+
directory=self.brain_directory,
|
|
293
|
+
extensions=self.extensions,
|
|
294
|
+
)
|
|
295
|
+
brain = Brain(brain_config)
|
|
296
|
+
brain.load_neurons()
|
|
297
|
+
|
|
298
|
+
# Cognize and synthesize
|
|
299
|
+
result = await brain.think(query)
|
|
300
|
+
context[self.inject_as] = result.instructions
|
|
301
|
+
context[f"{self.inject_as}_neurons"] = [
|
|
302
|
+
{"name": n.name, "relevance": n.relevance}
|
|
303
|
+
for n in result.relevant_neurons
|
|
304
|
+
]
|
|
305
|
+
return context
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def inject_brain(directory: str, query_key: str, as_key: str, max_neurons: int = 5) -> BrainInjectConfig:
|
|
309
|
+
"""
|
|
310
|
+
Inject knowledge from Brain neurons into context.
|
|
311
|
+
|
|
312
|
+
Uses Haiku to find relevant documents and synthesize instructions.
|
|
313
|
+
|
|
314
|
+
Args:
|
|
315
|
+
directory: Path to directory containing neuron files (.md, .txt, .py)
|
|
316
|
+
query_key: Context key with query (use $key) or literal query
|
|
317
|
+
as_key: Context key where synthesized instructions are stored
|
|
318
|
+
max_neurons: Max relevant neurons to use (default 5)
|
|
319
|
+
|
|
320
|
+
Example:
|
|
321
|
+
inject_brain('/docs/mcp-guides', '$user_question', 'knowledge')
|
|
322
|
+
"""
|
|
323
|
+
return BrainInjectConfig(
|
|
324
|
+
brain_directory=directory,
|
|
325
|
+
query_key=query_key,
|
|
326
|
+
inject_as=as_key,
|
|
327
|
+
max_neurons=max_neurons,
|
|
328
|
+
)
|