ryeos-engine 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.
rye/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ """RYE OS - AI operating system on Lillux microkernel."""
2
+
3
+ __version__ = "0.1.0"
rye/constants.py ADDED
@@ -0,0 +1,62 @@
1
+ """RYE Constants
2
+
3
+ Centralized constants for the AI directory name, item types, and tool actions.
4
+ """
5
+
6
+ # The name of the working directory used in all three spaces.
7
+ # Every space follows: base_path / AI_DIR / {type_dir} / {item_id}
8
+ AI_DIR = ".ai"
9
+
10
+
11
+ class ItemType:
12
+ """Item type constants."""
13
+
14
+ DIRECTIVE = "directive"
15
+ TOOL = "tool"
16
+ KNOWLEDGE = "knowledge"
17
+
18
+ ALL = [DIRECTIVE, TOOL, KNOWLEDGE]
19
+
20
+ # Type directory mappings
21
+ TYPE_DIRS = {
22
+ DIRECTIVE: "directives",
23
+ TOOL: "tools",
24
+ KNOWLEDGE: "knowledge",
25
+ }
26
+
27
+ # File extensions to search per item type (tools use dynamic lookup)
28
+ CONTENT_EXTENSIONS = {
29
+ DIRECTIVE: [".md"],
30
+ KNOWLEDGE: [".md", ".yaml", ".yml"],
31
+ }
32
+
33
+
34
+ class Action:
35
+ """Tool action constants."""
36
+
37
+ SEARCH = "search"
38
+ SIGN = "sign"
39
+ LOAD = "load"
40
+ EXECUTE = "execute"
41
+
42
+ ALL = [SEARCH, SIGN, LOAD, EXECUTE]
43
+
44
+
45
+ # Instruction injected into the thread runner's prompt when executing a directive.
46
+ # Used by thread_directive._build_prompt() to instruct the LLM.
47
+ # This is the single most important string for cross-model directive compliance.
48
+ # It must be explicit enough that even weak models follow the body as instructions
49
+ # rather than summarizing, describing, or re-executing.
50
+ DIRECTIVE_INSTRUCTION = (
51
+ "ZERO PREAMBLE. Your very first output token must be directive content — "
52
+ "never narration. Do NOT say 'I need to follow', 'Let me start', "
53
+ "'Here is the output', or ANY framing text.\n\n"
54
+ "You are the executor of this directive. Follow the body step by step.\n\n"
55
+ "<render> → output EXACTLY the text inside. Nothing before, nothing after.\n"
56
+ "<instruction> → follow silently. Do NOT narrate.\n\n"
57
+ "RULES:\n"
58
+ "- Do NOT summarize or describe what you are about to do.\n"
59
+ "- Do NOT re-call execute — you already have the instructions.\n"
60
+ "- If a step says STOP and wait, you MUST stop and wait.\n\n"
61
+ "Begin now with step 1."
62
+ )
@@ -0,0 +1,158 @@
1
+ """Standalone directive parsing: validate inputs, interpolate placeholders.
2
+
3
+ Extracted from ExecuteTool._run_directive() so that both execute.py and
4
+ thread_directive.py can reuse the same logic without circular imports.
5
+ """
6
+
7
+ import os
8
+ import re
9
+ from pathlib import Path
10
+ from typing import Any, Dict, List, Optional
11
+
12
+ from rye.constants import ItemType
13
+ from rye.utils.integrity import verify_item, IntegrityError
14
+ from rye.utils.parser_router import ParserRouter
15
+
16
+ # {input:key} — required, kept as-is if missing
17
+ # {input:key?} — optional, empty string if missing
18
+ # {input:key:default} — fallback to default if missing (colon separator)
19
+ # {input:key|default} — fallback to default if missing (pipe separator)
20
+ _INPUT_REF = re.compile(r"\{input:(\w+)(\?|[:|][^}]*)?\}")
21
+ _DOLLAR_INPUT_RE = re.compile(r"\$\{inputs\.(\w+)\}")
22
+
23
+ # {env:VAR} — required, kept as-is if missing
24
+ # {env:VAR:default} — fallback to default if env var not set
25
+ _ENV_REF = re.compile(r"\{env:(\w+)(?::([^}]*))?\}")
26
+
27
+ def _resolve_env_refs(value: str) -> str:
28
+ """Resolve {env:VAR} and {env:VAR:default} placeholders from os.environ."""
29
+
30
+ def _replace(m: re.Match) -> str:
31
+ var = m.group(1)
32
+ default = m.group(2)
33
+ env_val = os.environ.get(var)
34
+ if env_val is not None:
35
+ return env_val
36
+ if default is not None:
37
+ return default
38
+ return m.group(0)
39
+
40
+ return _ENV_REF.sub(_replace, value)
41
+
42
+
43
+ def _resolve_input_refs(value: str, inputs: Dict[str, Any]) -> str:
44
+ """Resolve {input:name} and ${inputs.name} placeholders in a string."""
45
+
46
+ def _replace(m: re.Match) -> str:
47
+ key = m.group(1)
48
+ modifier = m.group(2)
49
+ if key in inputs:
50
+ return str(inputs[key])
51
+ if modifier == "?":
52
+ return ""
53
+ if modifier and modifier[0] in (":", "|"):
54
+ return modifier[1:]
55
+ return m.group(0)
56
+
57
+ result = _INPUT_REF.sub(_replace, value)
58
+ # Also resolve ${inputs.name} syntax
59
+ if "${inputs." in result:
60
+ result = _DOLLAR_INPUT_RE.sub(
61
+ lambda m: str(inputs[m.group(1)]) if m.group(1) in inputs else m.group(0),
62
+ result,
63
+ )
64
+ return result
65
+
66
+
67
+ def _interpolate_parsed(parsed: Dict[str, Any], inputs: Dict[str, Any]) -> None:
68
+ """Interpolate {input:name} and {env:VAR} refs in body, actions, and content fields."""
69
+ for key in ("body", "content", "raw"):
70
+ if isinstance(parsed.get(key), str):
71
+ parsed[key] = _resolve_env_refs(parsed[key])
72
+ parsed[key] = _resolve_input_refs(parsed[key], inputs)
73
+
74
+ for action in parsed.get("actions", []):
75
+ for k, v in list(action.items()):
76
+ if isinstance(v, str):
77
+ action[k] = _resolve_input_refs(v, inputs)
78
+ for pk, pv in list(action.get("params", {}).items()):
79
+ if isinstance(pv, str):
80
+ action["params"][pk] = _resolve_input_refs(pv, inputs)
81
+
82
+
83
+ def parse_and_validate_directive(
84
+ *,
85
+ file_path: Path,
86
+ item_id: str,
87
+ parameters: Dict[str, Any],
88
+ project_path: Optional[Path] = None,
89
+ ) -> Dict[str, Any]:
90
+ """Parse a directive file, validate inputs, and interpolate placeholders.
91
+
92
+ Returns a dict with ``status`` set to ``"success"`` or ``"error"``.
93
+
94
+ On success the dict also contains:
95
+ parsed – the parsed directive data (with placeholders resolved)
96
+ inputs – the final validated input values (params + defaults)
97
+ declared_inputs – the raw input declarations from the directive
98
+
99
+ On error the dict contains an ``error`` message and, where applicable,
100
+ ``item_id`` and ``declared_inputs`` for caller diagnostics.
101
+ """
102
+ # 1. Integrity check
103
+ try:
104
+ verify_item(file_path, ItemType.DIRECTIVE, project_path=project_path)
105
+ except IntegrityError as exc:
106
+ return {"status": "error", "error": str(exc), "item_id": item_id}
107
+
108
+ # 2. Read and parse
109
+ content = file_path.read_text(encoding="utf-8")
110
+ parsed = ParserRouter().parse("markdown/xml", content)
111
+
112
+ if "error" in parsed:
113
+ return {"status": "error", "error": parsed.get("error"), "item_id": item_id}
114
+
115
+ # 3. Input validation
116
+ inputs = dict(parameters)
117
+ declared_inputs: List[Dict] = parsed.get("inputs", [])
118
+ declared_names = {inp["name"] for inp in declared_inputs}
119
+
120
+ # Reject unknown parameters early so the caller can correct
121
+ unknown = [k for k in parameters if k not in declared_names]
122
+ if unknown and declared_inputs:
123
+ return {
124
+ "status": "error",
125
+ "error": f"Unknown parameters: {', '.join(unknown)}. "
126
+ f"Valid inputs: {', '.join(declared_names)}",
127
+ "item_id": item_id,
128
+ "declared_inputs": declared_inputs,
129
+ }
130
+
131
+ # Apply defaults
132
+ for inp in declared_inputs:
133
+ if inp["name"] not in inputs and "default" in inp:
134
+ inputs[inp["name"]] = inp["default"]
135
+
136
+ # Check required inputs
137
+ missing = [
138
+ inp["name"]
139
+ for inp in declared_inputs
140
+ if inp.get("required") and inp["name"] not in inputs
141
+ ]
142
+ if missing:
143
+ return {
144
+ "status": "error",
145
+ "error": f"Missing required inputs: {', '.join(missing)}",
146
+ "item_id": item_id,
147
+ "declared_inputs": declared_inputs,
148
+ }
149
+
150
+ # 4. Interpolate placeholders
151
+ _interpolate_parsed(parsed, inputs)
152
+
153
+ return {
154
+ "status": "success",
155
+ "parsed": parsed,
156
+ "inputs": inputs,
157
+ "declared_inputs": declared_inputs,
158
+ }
@@ -0,0 +1,19 @@
1
+ """RYE Executor - Data-driven tool execution with chain resolution.
2
+
3
+ Components:
4
+ - PrimitiveExecutor: Main executor routing tools to Lillux primitives
5
+ - ChainValidator: Validates tool execution chains
6
+ - LockfileResolver: Resolves lockfile paths with 3-tier precedence
7
+ """
8
+
9
+ from rye.executor.primitive_executor import PrimitiveExecutor, ExecutionResult
10
+ from rye.executor.chain_validator import ChainValidator, ChainValidationResult
11
+ from rye.executor.lockfile_resolver import LockfileResolver
12
+
13
+ __all__ = [
14
+ "PrimitiveExecutor",
15
+ "ExecutionResult",
16
+ "ChainValidator",
17
+ "ChainValidationResult",
18
+ "LockfileResolver",
19
+ ]
@@ -0,0 +1,305 @@
1
+ """ChainValidator - Validates tool execution chains before execution.
2
+
3
+ Ensures:
4
+ - Space compatibility (lower precedence cannot depend on higher)
5
+ - I/O compatibility (child outputs match parent inputs)
6
+ - No circular dependencies
7
+ - Version constraints satisfaction
8
+ """
9
+
10
+ import logging
11
+ from dataclasses import dataclass, field
12
+ from typing import Any, Dict, List
13
+
14
+ from packaging import version
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ @dataclass
20
+ class ChainValidationResult:
21
+ """Result of chain validation."""
22
+
23
+ valid: bool = True
24
+ issues: List[str] = field(default_factory=list)
25
+ warnings: List[str] = field(default_factory=list)
26
+ validated_pairs: int = 0
27
+
28
+
29
+ class ChainValidator:
30
+ """Validates tool execution chains for integrity and compatibility.
31
+
32
+ Validation rules:
33
+ 1. Space compatibility: Tools can only depend on equal or higher precedence spaces
34
+ 2. I/O compatibility: Child outputs must satisfy parent inputs
35
+ 3. Version constraints: Parent's child_constraints must be satisfied
36
+ 4. No circular dependencies (handled during chain building)
37
+ """
38
+
39
+ # Space precedence (higher number = higher precedence)
40
+ SPACE_PRECEDENCE = {
41
+ "project": 3,
42
+ "user": 2,
43
+ "system": 1,
44
+ }
45
+
46
+ def validate_chain(self, chain: List[Dict[str, Any]]) -> ChainValidationResult:
47
+ """Validate entire execution chain.
48
+
49
+ Chain order: [tool, runtime, ..., primitive]
50
+ Each pair (chain[i], chain[i+1]) is validated.
51
+
52
+ Args:
53
+ chain: List of chain element dicts with keys:
54
+ - item_id: Tool identifier
55
+ - space: "project", "user", or "system"
56
+ - tool_type: Type of tool
57
+ - executor_id: Delegation target (None for primitives)
58
+ - inputs: Optional list of input types
59
+ - outputs: Optional list of output types
60
+ - version: Optional version string
61
+ - child_constraints: Optional version constraints for dependencies
62
+
63
+ Returns:
64
+ ChainValidationResult with validation details
65
+ """
66
+ result = ChainValidationResult()
67
+
68
+ if not chain:
69
+ return result
70
+
71
+ if len(chain) == 1:
72
+ # Single element chain (primitive) - no pairs to validate
73
+ return result
74
+
75
+ # Validate each (child, parent) pair
76
+ # In chain order: child → parent (child delegates to parent)
77
+ for i in range(len(chain) - 1):
78
+ child = chain[i]
79
+ parent = chain[i + 1]
80
+
81
+ self._validate_pair(child, parent, result)
82
+ result.validated_pairs += 1
83
+
84
+ # Check for space consistency
85
+ self._validate_space_consistency(chain, result)
86
+
87
+ return result
88
+
89
+ def _validate_pair(
90
+ self,
91
+ child: Dict[str, Any],
92
+ parent: Dict[str, Any],
93
+ result: ChainValidationResult,
94
+ ) -> None:
95
+ """Validate a (child, parent) pair in the chain.
96
+
97
+ Child delegates to parent via executor_id.
98
+ """
99
+ # 1. Validate space compatibility
100
+ self._validate_space_compatibility(child, parent, result)
101
+
102
+ # 2. Validate I/O compatibility
103
+ self._validate_io_compatibility(child, parent, result)
104
+
105
+ # 3. Validate version constraints
106
+ self._validate_version_constraints(child, parent, result)
107
+
108
+ def _validate_space_compatibility(
109
+ self,
110
+ child: Dict[str, Any],
111
+ parent: Dict[str, Any],
112
+ result: ChainValidationResult,
113
+ ) -> None:
114
+ """Validate that tools from different spaces are compatible.
115
+
116
+ Rule: A tool can depend on tools from equal or higher precedence spaces only.
117
+
118
+ Valid:
119
+ - project → user (project has higher precedence)
120
+ - project → system
121
+ - user → system
122
+ - same space → same space
123
+
124
+ Invalid:
125
+ - user → project (user cannot depend on project-specific tools)
126
+ - system → project/user (system is immutable)
127
+ """
128
+ child_space = child.get("space", "")
129
+ parent_space = parent.get("space", "")
130
+
131
+ child_precedence = self.SPACE_PRECEDENCE.get(child_space, 0)
132
+ parent_precedence = self.SPACE_PRECEDENCE.get(parent_space, 0)
133
+
134
+ # Lower precedence depending on higher precedence: Invalid
135
+ if child_precedence < parent_precedence:
136
+ result.issues.append(
137
+ f"Tool '{child.get('item_id')}' from {child_space} space cannot "
138
+ f"depend on '{parent.get('item_id')}' from {parent_space} space. "
139
+ f"Lower precedence spaces cannot depend on higher precedence spaces."
140
+ )
141
+ result.valid = False
142
+
143
+ def _validate_io_compatibility(
144
+ self,
145
+ child: Dict[str, Any],
146
+ parent: Dict[str, Any],
147
+ result: ChainValidationResult,
148
+ ) -> None:
149
+ """Validate that child outputs match parent inputs.
150
+
151
+ If both declare I/O types, ensure compatibility.
152
+ Missing declarations are treated as compatible (warnings only).
153
+ """
154
+ child_outputs = set(child.get("outputs", []))
155
+ parent_inputs = set(parent.get("inputs", []))
156
+
157
+ # Skip if either side doesn't declare types
158
+ if not child_outputs or not parent_inputs:
159
+ return
160
+
161
+ # Check if parent's required inputs are satisfied
162
+ missing = parent_inputs - child_outputs
163
+
164
+ if missing:
165
+ result.issues.append(
166
+ f"I/O mismatch: '{parent.get('item_id')}' requires inputs "
167
+ f"{list(missing)} not provided by '{child.get('item_id')}' "
168
+ f"(outputs: {list(child_outputs)})"
169
+ )
170
+ result.valid = False
171
+
172
+ def _validate_version_constraints(
173
+ self,
174
+ child: Dict[str, Any],
175
+ parent: Dict[str, Any],
176
+ result: ChainValidationResult,
177
+ ) -> None:
178
+ """Validate version constraints between parent and child.
179
+
180
+ Parent can specify child_constraints with min_version/max_version.
181
+ """
182
+ parent_constraints = parent.get("child_constraints", {})
183
+ child_id = child.get("item_id", "")
184
+ child_version = child.get("version")
185
+
186
+ if not parent_constraints or child_id not in parent_constraints:
187
+ return
188
+
189
+ if not child_version:
190
+ result.warnings.append(
191
+ f"'{child_id}' has no version but '{parent.get('item_id')}' "
192
+ f"specifies version constraints"
193
+ )
194
+ return
195
+
196
+ constraints = parent_constraints[child_id]
197
+ min_version = constraints.get("min_version")
198
+ max_version = constraints.get("max_version")
199
+
200
+ if min_version and not self._version_satisfies(child_version, ">=", min_version):
201
+ result.issues.append(
202
+ f"Version constraint failed: '{child_id}' version {child_version} "
203
+ f"< minimum required {min_version}"
204
+ )
205
+ result.valid = False
206
+
207
+ if max_version and not self._version_satisfies(child_version, "<=", max_version):
208
+ result.issues.append(
209
+ f"Version constraint failed: '{child_id}' version {child_version} "
210
+ f"> maximum allowed {max_version}"
211
+ )
212
+ result.valid = False
213
+
214
+ def _version_satisfies(self, version_str: str, op: str, constraint: str) -> bool:
215
+ """Check if version satisfies constraint using proper semver.
216
+
217
+ Supports:
218
+ - Standard semver: 1.0.0, 2.1.3
219
+ - Pre-releases: 1.0.0-alpha, 1.0.0-beta.2
220
+ - Build metadata: 1.0.0+build.123
221
+ """
222
+ try:
223
+ v = version.parse(version_str)
224
+ c = version.parse(constraint)
225
+
226
+ if op == ">=":
227
+ return v >= c
228
+ elif op == "<=":
229
+ return v <= c
230
+ elif op == "==":
231
+ return v == c
232
+ elif op == ">":
233
+ return v > c
234
+ elif op == "<":
235
+ return v < c
236
+ elif op == "!=":
237
+ return v != c
238
+ else:
239
+ logger.warning(f"Unknown version operator: {op}")
240
+ return True
241
+ except version.InvalidVersion:
242
+ logger.warning(f"Invalid version format: {version_str} or {constraint}")
243
+ return True # Invalid versions pass (warning logged)
244
+
245
+ def _validate_space_consistency(
246
+ self,
247
+ chain: List[Dict[str, Any]],
248
+ result: ChainValidationResult,
249
+ ) -> None:
250
+ """Validate overall space consistency in the chain.
251
+
252
+ Additional checks beyond pair validation.
253
+ """
254
+ # Check if system tools are in the middle of a mutable chain
255
+ spaces = [e.get("space") for e in chain]
256
+
257
+ # Find transitions from system back to mutable
258
+ for i in range(len(spaces) - 1):
259
+ if (spaces[i] or "").startswith("system") and spaces[i + 1] in ("project", "user"):
260
+ result.issues.append(
261
+ f"Invalid chain: system tool '{chain[i].get('item_id')}' "
262
+ f"cannot delegate to mutable {spaces[i + 1]} tool "
263
+ f"'{chain[i + 1].get('item_id')}'"
264
+ )
265
+ result.valid = False
266
+
267
+ def validate_tool(self, tool: Dict[str, Any]) -> ChainValidationResult:
268
+ """Validate a single tool's metadata.
269
+
270
+ Lightweight validation without chain context.
271
+
272
+ Args:
273
+ tool: Tool metadata dict
274
+
275
+ Returns:
276
+ ChainValidationResult
277
+ """
278
+ result = ChainValidationResult()
279
+
280
+ # Check required fields
281
+ if not tool.get("item_id"):
282
+ result.issues.append("Missing required field: item_id")
283
+ result.valid = False
284
+
285
+ # Check space is valid
286
+ space = tool.get("space")
287
+ if space and space not in self.SPACE_PRECEDENCE:
288
+ result.issues.append(f"Invalid space: {space}")
289
+ result.valid = False
290
+
291
+ # Check executor_id is valid if present
292
+ executor_id = tool.get("executor_id")
293
+ tool_type = tool.get("tool_type")
294
+
295
+ if tool_type == "primitive" and executor_id is not None:
296
+ result.warnings.append(
297
+ f"Primitive '{tool.get('item_id')}' has executor_id set (should be None)"
298
+ )
299
+
300
+ if tool_type == "runtime" and executor_id is None:
301
+ result.warnings.append(
302
+ f"Runtime '{tool.get('item_id')}' has no executor_id (should delegate)"
303
+ )
304
+
305
+ return result