id: runtimes
title: "Runtimes"
description: "How runtimes configure tool execution — interpreter resolution, arg templates, anchoring, and the 8 standard runtimes"
category: internals
tags: [runtimes, interpreter, execution, python, node, javascript, bash, mcp, subprocess, state-graph, rust]
version: "1.0.0"A runtime is a YAML configuration that describes how to invoke a tool. It bridges tools (Python scripts, JavaScript, YAML configs) and primitives (subprocess, HTTP). Every tool declares an executor_id pointing to a runtime, which then points to a primitive.
Runtimes are YAML files in .ai/tools/rye/core/runtimes/ (system space). You can override or extend them by placing custom runtimes in project space at .ai/tools/rye/core/runtimes/my-runtime.yaml.
.ai/tools/rye/core/runtimes/
├── bash/bash.yaml # Execute shell commands
├── mcp/
│ ├── http.yaml # Call MCP tools via HTTP
│ └── stdio.yaml # Spawn MCP servers over stdio
├── node/node.yaml # Run JavaScript/TypeScript with Node
├── python/
│ ├── function.yaml # Call Python execute() in-process
│ ├── script.yaml # Run Python scripts in subprocess
│ └── lib/ # Shared Python runtime libraries
├── rust/
│ ├── runtime.yaml # Rust binary runtime
│ ├── lillux/ # Process lifecycle manager
│ └── (lillux binary handles all Rust operations)
└── state-graph/
├── runtime.yaml # Walk declarative graph YAML
└── walker.py # Graph walking engine
| Runtime | Language | Execution | When to Use |
|---|---|---|---|
| python/function | Python | In-process (fast) | Pure Python logic, fast startup, single-threaded, no shell access needed |
| python/script | Python | Subprocess with isolation | Heavy I/O, long-running, needs subprocess isolation, can use shell commands |
| node/node | JavaScript/TypeScript | Subprocess with Node resolution | JavaScript tools, TypeScript (via tsx), Node.js ecosystem dependencies |
| bash/bash | Bash/Shell | Direct /bin/bash execution |
Shell scripts, system administration, jq pipes, CLI composition |
| mcp/stdio | MCP (stdin/stdout) | Subprocess, launch MCP server | Call tools from external MCP servers (e.g., brave-search, filesystem) |
| mcp/http | MCP (HTTP/SSE) | HTTP request to MCP server | Call tools from long-running HTTP MCP servers (e.g., Claude-native servers) |
| rust/runtime | Rust | Compiled binary on PATH | Cross-platform process management and file watching |
| state-graph/runtime | YAML Graph | Subprocess, dispatch rye_execute | Declarative workflows, condition branches, node-by-node execution |
Every runtime can configure interpreter resolution — finding the right Python, Node, bash, etc. to execute tools. There are 3 resolution types, plus a static environment option:
Finds a binary inside local project directories (virtualenvs, node_modules, etc.):
Python example:
env_config:
interpreter:
type: local_binary
binary: python # Binary name to find
candidates: [python3] # Alternative names to try
search_paths: [".venv/bin", ".venv/Scripts"] # Relative to project root
var: RYE_PYTHON # Environment variable to set
fallback: python3 # If not found, use thisNode example:
env_config:
interpreter:
type: local_binary
binary: tsx # Binary name to find
search_paths: ["node_modules/.bin"] # Relative to anchor root
search_roots: ["{anchor_path}"] # Where to start searching
var: RYE_NODE # Environment variable to set
fallback: node # If not found, use thisHow it works:
- For each path in
search_paths, look forbinary(and each name incandidates) search_rootscontrols wheresearch_pathsare resolved from (defaults to project root)- If found, set the
varenvironment variable to that path - If not found, fall back to the
fallbackbinary from$PATH
Used by: python/script, python/function, state-graph/runtime, node/node, mcp/stdio, mcp/http
Finds a binary on the system $PATH:
env_config:
interpreter:
type: system_binary
binary: python3 # Binary to find on PATH
var: RYE_PYTHONHow it works:
- Search
$PATHfor the named binary - Set
varto the resolved path
Used by: fallback for any runtime when local binary is not found
Run a resolve command and use its stdout as the interpreter path:
env_config:
interpreter:
type: command
resolve_cmd: ["pyenv", "which", "python"] # Command to run
var: RYE_PYTHON
fallback: python3How it works:
- Execute
resolve_cmdand capture stdout (trimmed) - Set
varto the resolved path - If the command fails, fall back to
fallback
Used by: projects using version managers (pyenv, nvm, mise, etc.)
Directly set environment variables without any interpreter resolution:
env_config:
env:
PATH: "${PATH}" # Pass through PATH
NODE_ENV: development # Set static valueHow it works:
- Expand
${PATH}from current environment - Set all other keys as static values
- No interpreter binary resolution occurs
Used by: bash/bash (PATH already has bash), other simple tools
The anchor system solves module/dependency resolution by establishing a project root where tool dependencies live. It enables tools to load libraries relative to their anchor path.
anchor:
enabled: true # Enable anchoring for this runtime
mode: auto # 'auto' or 'always'
markers_any: [__init__.py, pyproject.toml] # Search for these markers
root: tool_dir # 'tool_dir' (default) or other value
lib: lib # Relative lib path for runtime libraries
cwd: "{anchor_path}" # Optional: change working directory
env_paths:
PYTHONPATH:
prepend: ["{anchor_path}", "{runtime_lib}"] # Prepend to PATH-like varsMode: auto
- Start from tool directory (e.g.,
.ai/tools/rye/bash/) - Walk up parent directories looking for marker files (
__init__.py,pyproject.toml) - Stop at the first match — that's the anchor root
- If no marker found, use tool directory as anchor
Mode: always
- Anchor is always the tool directory (no upward search)
Once anchored, use these in env_paths and config.args:
| Variable | Expands To | Example |
|---|---|---|
{anchor_path} |
Root of anchored project | /home/user/project/.ai/tools/rye/bash |
{runtime_lib} |
Combined anchor lib + runtime lib | /home/user/project/.ai/tools/rye/bash/lib |
Example: Python runtime with anchoring:
anchor:
enabled: true
markers_any: [__init__.py, pyproject.toml]
root: tool_dir
lib: lib
env_paths:
PYTHONPATH:
prepend: ["{anchor_path}", "{runtime_lib}"]When a tool is executed:
- Anchor root found at
/project/.ai/tools/rye/bash PYTHONPATHis set to/project/.ai/tools/rye/bash:/project/.ai/tools/rye/bash/lib- Tool can
importmodules from those directories without package-level imports
Note: The node runtime also uses
env_pathsto prependnode_modules/.bintoPATH, enabling local binaries to be found without full paths.
Runtimes use two-stage templating to build the final command:
${VAR_NAME} is replaced with environment variables. This happens first, before tool execution:
env_config:
interpreter:
type: local_binary
binary: python
candidates: [python3]
search_paths: [".venv/bin", ".venv/Scripts"]
var: RYE_PYTHON
fallback: python3
env:
PYTHONUNBUFFERED: "1"
config:
command: "${RYE_PYTHON}" # Resolved to /path/to/python3
args:
- "{tool_path}"When RYE_PYTHON is resolved to /project/.venv/bin/python3, the args become:
command: /project/.venv/bin/python3
args:
- "{tool_path}" # Still waiting for stage 2{param_name} is replaced with values passed to the runtime. This happens second, after the tool is called:
| Variable | Value | Example |
|---|---|---|
{tool_path} |
Absolute path to tool file | /project/.ai/tools/rye/bash.py |
{params_json} |
Tool parameters as JSON (passed via input_data/stdin to avoid OS argument length limits) |
{"command":"ls -la"} |
{project_path} |
Project root | /project |
{system_space} |
System space root | /usr/local/lib/python3.11/site-packages/ryeos/rye/.ai |
{server_config_path} |
MCP server config path | /project/.ai/tools/mcp/servers/context7.yaml |
{anchor_path} |
Anchored root | /project/.ai/tools/rye/bash |
{runtime_lib} |
Runtime lib directory | /project/.ai/tools/rye/bash/lib |
{command} |
For bash/bash: command arg | ls -la |
{tool_name} |
For MCP runtimes: tool name | query-docs |
config:
command: "${RYE_PYTHON}"
args:
- "{tool_path}"
- "--project-path"
- "{project_path}"
input_data: "{params_json}"When executing rye/bash with params {"command":"echo hello"}:
${RYE_PYTHON}→/project/.venv/bin/python3{tool_path}→/project/.ai/tools/rye/bash.py{params_json}→{"command":"echo hello"}(piped via stdin){project_path}→/project
Final command:
echo '{"command":"echo hello"}' | /project/.venv/bin/python3 /project/.ai/tools/rye/bash.py --project-path /projectNote: All standard runtimes pass
{params_json}viainput_data(stdin) instead of CLI arguments. This avoids OS argument length limits (E2BIG/ARG_MAX) regardless of payload size.
A custom runtime is just a YAML file in .ai/tools/rye/core/runtimes/:
# .ai/tools/rye/core/runtimes/ruby_runtime.yaml
version: "1.0.0"
tool_type: runtime
executor_id: rye/core/primitives/execute
category: rye/core/runtimes
description: "Ruby runtime — executes Ruby scripts with bundler support"
env_config:
interpreter:
type: local_binary # Find Ruby in local directories or fallback
binary: ruby
search_paths: [".rbenv/shims", ".rvm/rubies/default/bin"]
var: RYE_RUBY
fallback: ruby
env:
BUNDLE_GEMFILE: "{anchor_path}/Gemfile"
anchor:
enabled: true
mode: auto
markers_any: [Gemfile, Gemfile.lock]
root: tool_dir
lib: lib/ruby
cwd: "{anchor_path}"
env_paths:
RUBYLIB:
prepend: ["{anchor_path}", "{runtime_lib}"]
verify_deps:
enabled: true
scope: anchor
recursive: true
extensions: [.rb, .yaml, .yml, .json]
exclude_dirs: [.bundle, .git, node_modules]
config:
command: "${RYE_RUBY}"
args:
- "{tool_path}"
- "--project-path"
- "{project_path}"
input_data: "{params_json}"
timeout: 300
config_schema:
type: object
properties:
script:
type: string
description: Ruby script pathCreate a tool that uses the runtime:
# .ai/tools/my/ruby_example.py
"""A Ruby tool example."""
__version__ = "1.0.0"
__tool_type__ = "python"
__executor_id__ = "rye/core/runtimes/ruby/ruby" # Point to custom runtime
__category__ = "my/ruby"
__tool_description__ = "Example Ruby tool"
CONFIG_SCHEMA = {
"type": "object",
"properties": {
"name": {"type": "string", "description": "Name to greet"}
},
"required": ["name"]
}
def execute(params: dict, project_path: str) -> dict:
return {"success": True, "message": f"Hello {params['name']}"}rye_sign my/ruby_example
rye_execute my/ruby_example --name Alice# rye/core/runtimes/python/function
# Fastest option: imports Python module and calls execute() directly
# Use for: pure Python logic, compute-heavy tasks, no subprocess needed
# Executor: rye/core/primitives/execute (uses inline Python code)
version: "1.0.0"
tool_type: runtime
executor_id: rye/core/primitives/execute
category: rye/core/runtimes/python
description: "Python function runtime - calls execute(params, project_path) in-process"
env_config:
interpreter:
type: local_binary
binary: python
candidates: [python3]
search_paths: [".venv/bin", ".venv/Scripts"]
var: RYE_PYTHON
fallback: python3
env:
PYTHONUNBUFFERED: "1"
PROJECT_VENV_PYTHON: "${RYE_PYTHON}"
anchor:
enabled: true
mode: auto
markers_any: ["__init__.py", "pyproject.toml"]
root: tool_dir
lib: lib
env_paths:
PYTHONPATH:
prepend: ["{anchor_path}", "{runtime_lib}"]
verify_deps:
enabled: true
scope: anchor
recursive: true
extensions: [".py", ".yaml", ".yml", ".json"]
exclude_dirs: ["__pycache__", ".venv", "node_modules", ".git", "config"]
# Args passed to python -c (inline code loader)
# Loads module, finds execute(), calls with params and project_path
# Params are piped via stdin (input_data) to avoid E2BIG on large payloads
config:
command: "${RYE_PYTHON}"
args:
- "-c"
- |
import sys,json,importlib.util,inspect,asyncio
spec=importlib.util.spec_from_file_location("tool",sys.argv[1])
mod=importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
fn=getattr(mod,"execute",None)
if not fn:sys.exit("No execute() in "+sys.argv[1])
p=json.loads(sys.stdin.read())
if inspect.iscoroutinefunction(fn):r=asyncio.run(fn(p,sys.argv[2]))
else:r=fn(p,sys.argv[2])
print(json.dumps(r,default=str)if r is not None else'{}')
- "{tool_path}"
- "{project_path}"
input_data: "{params_json}"
timeout: 300
config_schema:
type: object
properties:
script:
type: string
description: Python module with execute(params, project_path) function# rye/core/runtimes/python/script
# Runs Python tool with __main__ entry point
# Use for: subprocess isolation, long-running tasks, I/O, subprocess commands
# Executor: rye/core/primitives/execute
version: "1.0.0"
tool_type: runtime
executor_id: rye/core/primitives/execute
category: rye/core/runtimes/python
description: "Python script runtime - runs Python scripts with __main__ entry point"
env_config:
interpreter:
type: local_binary
binary: python
candidates: [python3]
search_paths: [".venv/bin", ".venv/Scripts"]
var: RYE_PYTHON
fallback: python3
env:
PYTHONUNBUFFERED: "1"
PROJECT_VENV_PYTHON: "${RYE_PYTHON}"
anchor:
enabled: true
mode: auto
markers_any: ["__init__.py", "pyproject.toml"]
root: tool_dir
lib: lib
env_paths:
PYTHONPATH:
prepend: ["{anchor_path}", "{runtime_lib}"]
verify_deps:
enabled: true
scope: anchor
recursive: true
extensions: [".py", ".yaml", ".yml", ".json"]
exclude_dirs: ["__pycache__", ".venv", "node_modules", ".git", "config"]
# Args: tool.py gets --project-path; params are piped via stdin
# Tool must have: if __name__ == "__main__": argparse --project-path, read params from stdin
config:
command: "${RYE_PYTHON}"
args:
- "{tool_path}"
- "--project-path"
- "{project_path}"
input_data: "{params_json}"
timeout: 300
config_schema:
type: object
properties:
script:
type: string
description: Python script path or inline code
args:
type: array
items:
type: string
description: Script arguments
module:
type: string
description: "Module to run with -m flag"# rye/core/runtimes/node/node
# Runs JavaScript/TypeScript with Node.js
# Use for: JavaScript tools, TypeScript (via tsx), Node ecosystem
# Executor: rye/core/primitives/execute
version: "1.0.0"
tool_type: runtime
executor_id: rye/core/primitives/execute
category: rye/core/runtimes/node
description: "Node.js runtime executor - runs JavaScript/TypeScript with Node interpreter resolution"
env_config:
interpreter:
type: local_binary
binary: tsx
search_paths: ["node_modules/.bin"]
search_roots: ["{anchor_path}"]
var: RYE_NODE
fallback: node
env:
NODE_ENV: development
anchor:
enabled: true
mode: auto
markers_any: ["package.json"]
root: tool_dir
lib: lib/node
cwd: "{anchor_path}"
env_paths:
NODE_PATH:
prepend: ["{anchor_path}", "{anchor_path}/node_modules"]
PATH:
prepend: ["{anchor_path}/node_modules/.bin"]
verify_deps:
enabled: true
scope: anchor
recursive: true
extensions: [".js", ".ts", ".mjs", ".cjs", ".json", ".yaml", ".yml"]
exclude_dirs: ["node_modules", "__pycache__", ".git", "dist", "build"]
# Args: tool.js gets --project-path; params are piped via stdin
config:
command: "${RYE_NODE}"
args:
- "{tool_path}"
- "--project-path"
- "{project_path}"
input_data: "{params_json}"
timeout: 300
config_schema:
type: object
properties:
script:
type: string
description: JavaScript file path
args:
type: array
items:
type: string
description: Script arguments# rye/core/runtimes/bash/bash
# Executes shell commands via /bin/bash
# Use for: shell scripts, system administration, CLI composition, jq pipes
# Executor: rye/core/primitives/execute
version: "1.0.0"
tool_type: runtime
executor_id: rye/core/primitives/execute
category: rye/core/runtimes/bash
description: "Bash runtime - executes shell commands directly"
env_config:
env:
PATH: "${PATH}"
config:
command: "/bin/bash"
args:
- "-c"
- "{command}"
- "{tool_path}"
- "{project_path}"
input_data: "{params_json}"
timeout: 300
config_schema:
type: object
properties:
command:
type: string
description: Shell command to execute# rye/core/runtimes/mcp/http
# Calls MCP tools via HTTP/SSE transport
# Use for: external MCP servers, long-lived HTTP connections, streaming
# Executor: rye/core/primitives/execute (launches connect.py)
version: "1.0.0"
tool_type: runtime
executor_id: rye/core/primitives/execute
category: rye/core/runtimes/mcp
description: "MCP HTTP runtime - executes MCP tools via HTTP transport"
env_config:
interpreter:
type: local_binary
binary: python
candidates: [python3]
search_paths: [".venv/bin", ".venv/Scripts"]
var: RYE_PYTHON
fallback: python3
env:
PYTHONUNBUFFERED: "1"
config:
command: "${RYE_PYTHON}"
args:
- "{system_space}/tools/rye/core/mcp/connect.py"
- "--server-config"
- "{server_config_path}"
- "--tool"
- "{tool_name}"
- "--project-path"
- "{project_path}"
input_data: "{params_json}"
timeout: 60
config_schema:
type: object
properties:
server:
type: string
description: "Relative tool ID to server config (e.g., mcp/servers/context7)"
tool_name:
type: string
description: MCP tool name to call
timeout:
type: number
description: Execution timeout in seconds
default: 60
required: [server, tool_name]# rye/core/runtimes/mcp/stdio
# Spawns MCP servers and calls tools via stdin/stdout
# Use for: local MCP servers, lightweight stdio transports
# Executor: rye/core/primitives/execute (launches connect.py)
version: "1.0.0"
tool_type: runtime
executor_id: rye/core/primitives/execute
category: rye/core/runtimes/mcp
description: "MCP stdio runtime - executes MCP tools via stdio transport"
env_config:
interpreter:
type: local_binary
binary: python
candidates: [python3]
search_paths: [".venv/bin", ".venv/Scripts"]
var: RYE_PYTHON
fallback: python3
env:
PYTHONUNBUFFERED: "1"
config:
command: "${RYE_PYTHON}"
args:
- "{system_space}/tools/rye/core/mcp/connect.py"
- "--server-config"
- "{server_config_path}"
- "--tool"
- "{tool_name}"
- "--project-path"
- "{project_path}"
input_data: "{params_json}"
timeout: 60
config_schema:
type: object
properties:
server:
type: string
description: "Relative tool ID to server config (e.g., mcp/servers/rye-os)"
tool_name:
type: string
description: MCP tool name to call
timeout:
type: number
description: Execution timeout in seconds
default: 60
required: [server, tool_name]The Rust runtime executes the lillux Rust binary found on $PATH:
lillux: Unified Rust binary. Subcommands includeexec(run-and-wait with stdout/stderr capture, timeout, stdin piping, cwd, and env support),spawn(detached/daemonized),kill(graceful SIGTERM → SIGKILL / TerminateProcess),status(is-alive check), pluscas,sign,verify,keypair,envelope, andtime. All process operations inExecutePrimitivedelegate to lillux — it is a hard dependency (no POSIX fallbacks).
# rye/core/runtimes/rust/runtime
# Executes compiled Rust binaries found on PATH
# Use for: cross-platform process management, file watching, performance-critical operations
# Executor: rye/core/primitives/execute
version: "1.0.0"
tool_type: runtime
executor_id: rye/core/primitives/execute
category: rye/core/runtimes/rust
description: "Rust runtime — executes compiled Rust binaries found on PATH"
env_config:
interpreter:
type: system_binary
binary: lillux
var: LILLUX_WATCH
env:
RUST_BACKTRACE: "0"
config:
command: "${LILLUX_WATCH}"
args:
- "--db"
- "{db_path}"
- "--thread-id"
- "{thread_id}"
- "--timeout"
- "{timeout}"
timeout: 600# rye/core/runtimes/state-graph/runtime
# Walks declarative graph YAML, dispatching rye_execute for each node
# Use for: declarative workflows, condition branches, node-by-node execution
# Executor: rye/core/primitives/execute
version: "1.0.0"
tool_type: runtime
executor_id: rye/core/primitives/execute
category: rye/core/runtimes/state-graph
description: "State graph runtime — walks graph YAML tools, dispatching rye_execute for each node"
env_config:
interpreter:
type: local_binary
binary: python
candidates: [python3]
search_paths: [".venv/bin", ".venv/Scripts"]
var: RYE_PYTHON
fallback: python3
env:
PYTHONUNBUFFERED: "1"
PROJECT_VENV_PYTHON: "${RYE_PYTHON}"
anchor:
enabled: true
mode: always
root: tool_dir
lib: ../python/lib
env_paths:
PYTHONPATH:
prepend: ["{anchor_path}", "{runtime_lib}"]
verify_deps:
enabled: true
scope: anchor
recursive: true
extensions: [".py", ".yaml", ".yml", ".json"]
exclude_dirs: ["__pycache__", ".venv", "node_modules", ".git", "config"]
# Args: walker.py loads graph YAML and executes node by node
config:
command: "${RYE_PYTHON}"
args:
- "-c"
- |
import sys,os,json,importlib.util
ap=sys.argv[4]
walker=os.path.join(ap,"walker.py")
if not os.path.isfile(walker):
sys.exit("walker.py not found at: "+repr(walker))
spec=importlib.util.spec_from_file_location("walker",walker)
mod=importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
graph=mod._load_graph_yaml(sys.argv[1])
r=mod.run_sync(graph,json.loads(sys.argv[2]),sys.argv[3])
print(json.dumps(r,default=str))
- "{tool_path}"
- "{params_json}"
- "{project_path}"
- "{anchor_path}"
timeout: 600
config_schema:
type: object
properties:
graph:
type: object
description: Graph YAML structure- Lillux Primitives — The subprocess and HTTP primitives runtimes delegate to
- Executor Chain — How tools resolve to runtimes to primitives
- Authoring Tools — Write tools that use runtimes
- Custom Runtimes — Create runtimes for new languages