rlmy 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.
rlmy-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,114 @@
1
+ Metadata-Version: 2.4
2
+ Name: rlmy
3
+ Version: 0.1.0
4
+ Summary: An interactive RLM (Recursive Language Model) agent built on DSPy
5
+ License: MIT
6
+ Requires-Python: >=3.11
7
+ Description-Content-Type: text/markdown
8
+ Requires-Dist: dspy>=3.2.1
9
+ Requires-Dist: boto3>=1.42.0
10
+ Requires-Dist: nest-asyncio>=1.6.0
11
+ Requires-Dist: mcp>=1.26.0
12
+ Requires-Dist: rich>=14.0.0
13
+ Requires-Dist: markitdown>=0.1.5
14
+ Requires-Dist: prompt-toolkit>=3.0.50
15
+ Requires-Dist: pydantic>=2.12.0
16
+ Requires-Dist: platformdirs>=4.0.0
17
+ Requires-Dist: strands-agents-tools>=0.4.1
18
+ Provides-Extra: dev
19
+ Requires-Dist: pytest>=8.0; extra == "dev"
20
+ Requires-Dist: pytest-cov; extra == "dev"
21
+ Requires-Dist: ruff; extra == "dev"
22
+
23
+ # rlmy
24
+
25
+ An interactive RLM (Recursive Language Model) agent built on DSPy.
26
+
27
+ ## What is this?
28
+
29
+ - An AI coding agent that runs in your terminal
30
+ - Uses DSPy's RLM framework — the LLM writes and executes Python code iteratively
31
+ - Connects to MCP servers (Slack, internal tools, etc.) as additional tools
32
+ - Maintains conversation state across turns (trajectory persistence)
33
+ - Supports cooperative interrupt (Ctrl+C pauses gracefully)
34
+
35
+ ## Key Features
36
+
37
+ - **Iterative REPL**: LLM writes code, sees output, writes more code — until it solves the problem
38
+ - **MCP Integration**: Connect any MCP-compatible tool server
39
+ - **Filesystem Tools**: Read, write, edit files with safety guards (read-before-write)
40
+ - **Shell Access**: Run shell commands with deny-list safety and approval system
41
+ - **Trajectory Persistence**: Resume sessions where you left off
42
+ - **Conversation Continuity**: Prior context injected into new turns
43
+ - **Configurable Models**: Use any DSPy-compatible LM (Anthropic, Bedrock, OpenAI, Groq, Ollama)
44
+
45
+ ## Prerequisites
46
+
47
+ - Valid LLM credentials (Anthropic API key, AWS profile for Bedrock, etc.)
48
+ - Python 3.12+ and Deno are installed automatically by the setup script
49
+
50
+ ## Installation
51
+
52
+ Recommended (installs Deno + uv + rlmy in one command):
53
+
54
+ curl -LsSf https://raw.githubusercontent.com/diego-lima/rlmy/main/setup_install.sh | bash
55
+
56
+ Alternative (if you already have Deno and uv):
57
+
58
+ uv tool install rlmy
59
+
60
+ ## Quick Start
61
+
62
+ rlmy
63
+
64
+ - First run asks which AI model to use (model selection wizard)
65
+ - Workspaces are created in `~/.config/rlmy/sandboxes/`
66
+ - Ctrl+C pauses gracefully (doesn't lose work)
67
+
68
+ To skip the wizard (headless/CI):
69
+
70
+ export RLM_MAIN_MODEL='bedrock/us.anthropic.claude-sonnet-4-6'
71
+ export RLM_SUB_MODEL='bedrock/us.anthropic.claude-sonnet-4-6'
72
+ rlmy
73
+
74
+ ## Try It Out
75
+
76
+ If your credentials are set, run `rlmy` and type:
77
+
78
+ > *"curl https://calmcode.io/static/data/pokemon.json and teach me something surprising about it."*
79
+
80
+ Watch it fetch the data, explore it with code, and teach you something you didn't know. This is the RLM loop in action: it'll iterate until it has a neat insight.
81
+
82
+ ## Configuration
83
+
84
+ - Priority: env vars > config file > wizard
85
+ - Config file: `~/.config/rlmy/config.toml`
86
+ - Supported model formats: any DSPy model string (e.g., `bedrock/us.anthropic.claude-sonnet-4-6`, `bedrock/us.anthropic.claude-opus-4-6-v1`)
87
+
88
+ ## MCP Tools (optional)
89
+
90
+ - Config location: `~/.config/rlmy/mcp_servers.json`
91
+ - The setup script creates an empty template
92
+ - Edit it to connect Slack, internal tools, or any MCP-compatible server
93
+ - Agent starts without MCP if config is empty (no crash)
94
+
95
+ ## CLI Options
96
+
97
+ - `--sandbox-root PATH`: Override sandbox directory (default: `~/.config/rlmy/sandboxes/`)
98
+ - `--cache-path PATH`: Override workspace cache file
99
+
100
+ ## Architecture
101
+
102
+ - Built on DSPy's experimental RLM module
103
+ - InterruptableRLM: cooperative SIGINT + trajectory injection
104
+ - Sandboxed code execution via Deno + Pyodide (WASM)
105
+ - Tools are plain Python functions registered with DSPy
106
+
107
+ ## License
108
+
109
+ MIT
110
+
111
+ ## Status
112
+
113
+ - Early release — works well for the author, may have rough edges
114
+ - Feedback welcome via GitHub issues
rlmy-0.1.0/README.md ADDED
@@ -0,0 +1,92 @@
1
+ # rlmy
2
+
3
+ An interactive RLM (Recursive Language Model) agent built on DSPy.
4
+
5
+ ## What is this?
6
+
7
+ - An AI coding agent that runs in your terminal
8
+ - Uses DSPy's RLM framework — the LLM writes and executes Python code iteratively
9
+ - Connects to MCP servers (Slack, internal tools, etc.) as additional tools
10
+ - Maintains conversation state across turns (trajectory persistence)
11
+ - Supports cooperative interrupt (Ctrl+C pauses gracefully)
12
+
13
+ ## Key Features
14
+
15
+ - **Iterative REPL**: LLM writes code, sees output, writes more code — until it solves the problem
16
+ - **MCP Integration**: Connect any MCP-compatible tool server
17
+ - **Filesystem Tools**: Read, write, edit files with safety guards (read-before-write)
18
+ - **Shell Access**: Run shell commands with deny-list safety and approval system
19
+ - **Trajectory Persistence**: Resume sessions where you left off
20
+ - **Conversation Continuity**: Prior context injected into new turns
21
+ - **Configurable Models**: Use any DSPy-compatible LM (Anthropic, Bedrock, OpenAI, Groq, Ollama)
22
+
23
+ ## Prerequisites
24
+
25
+ - Valid LLM credentials (Anthropic API key, AWS profile for Bedrock, etc.)
26
+ - Python 3.12+ and Deno are installed automatically by the setup script
27
+
28
+ ## Installation
29
+
30
+ Recommended (installs Deno + uv + rlmy in one command):
31
+
32
+ curl -LsSf https://raw.githubusercontent.com/diego-lima/rlmy/main/setup_install.sh | bash
33
+
34
+ Alternative (if you already have Deno and uv):
35
+
36
+ uv tool install rlmy
37
+
38
+ ## Quick Start
39
+
40
+ rlmy
41
+
42
+ - First run asks which AI model to use (model selection wizard)
43
+ - Workspaces are created in `~/.config/rlmy/sandboxes/`
44
+ - Ctrl+C pauses gracefully (doesn't lose work)
45
+
46
+ To skip the wizard (headless/CI):
47
+
48
+ export RLM_MAIN_MODEL='bedrock/us.anthropic.claude-sonnet-4-6'
49
+ export RLM_SUB_MODEL='bedrock/us.anthropic.claude-sonnet-4-6'
50
+ rlmy
51
+
52
+ ## Try It Out
53
+
54
+ If your credentials are set, run `rlmy` and type:
55
+
56
+ > *"curl https://calmcode.io/static/data/pokemon.json and teach me something surprising about it."*
57
+
58
+ Watch it fetch the data, explore it with code, and teach you something you didn't know. This is the RLM loop in action: it'll iterate until it has a neat insight.
59
+
60
+ ## Configuration
61
+
62
+ - Priority: env vars > config file > wizard
63
+ - Config file: `~/.config/rlmy/config.toml`
64
+ - Supported model formats: any DSPy model string (e.g., `bedrock/us.anthropic.claude-sonnet-4-6`, `bedrock/us.anthropic.claude-opus-4-6-v1`)
65
+
66
+ ## MCP Tools (optional)
67
+
68
+ - Config location: `~/.config/rlmy/mcp_servers.json`
69
+ - The setup script creates an empty template
70
+ - Edit it to connect Slack, internal tools, or any MCP-compatible server
71
+ - Agent starts without MCP if config is empty (no crash)
72
+
73
+ ## CLI Options
74
+
75
+ - `--sandbox-root PATH`: Override sandbox directory (default: `~/.config/rlmy/sandboxes/`)
76
+ - `--cache-path PATH`: Override workspace cache file
77
+
78
+ ## Architecture
79
+
80
+ - Built on DSPy's experimental RLM module
81
+ - InterruptableRLM: cooperative SIGINT + trajectory injection
82
+ - Sandboxed code execution via Deno + Pyodide (WASM)
83
+ - Tools are plain Python functions registered with DSPy
84
+
85
+ ## License
86
+
87
+ MIT
88
+
89
+ ## Status
90
+
91
+ - Early release — works well for the author, may have rough edges
92
+ - Feedback welcome via GitHub issues
@@ -0,0 +1,46 @@
1
+ [project]
2
+ name = "rlmy"
3
+ version = "0.1.0"
4
+ description = "An interactive RLM (Recursive Language Model) agent built on DSPy"
5
+ readme = "README.md"
6
+ license = {text = "MIT"}
7
+ requires-python = ">=3.11"
8
+ dependencies = [
9
+ "dspy>=3.2.1",
10
+ "boto3>=1.42.0",
11
+ "nest-asyncio>=1.6.0",
12
+ "mcp>=1.26.0",
13
+ "rich>=14.0.0",
14
+ "markitdown>=0.1.5",
15
+ "prompt-toolkit>=3.0.50",
16
+ "pydantic>=2.12.0",
17
+ "platformdirs>=4.0.0",
18
+ "strands-agents-tools>=0.4.1",
19
+ ]
20
+
21
+ [project.scripts]
22
+ rlmy = "rlmy.cli:main"
23
+
24
+ [project.optional-dependencies]
25
+ dev = [
26
+ "pytest>=8.0",
27
+ "pytest-cov",
28
+ "ruff",
29
+ ]
30
+
31
+ [build-system]
32
+ requires = ["setuptools>=68"]
33
+ build-backend = "setuptools.build_meta"
34
+
35
+ [tool.setuptools.packages.find]
36
+ where = ["src"]
37
+
38
+ [tool.pytest.ini_options]
39
+ pythonpath = ["src"]
40
+ testpaths = ["tests"]
41
+ addopts = "-v"
42
+
43
+ [tool.ruff]
44
+ line-length = 88
45
+ target-version = "py311"
46
+
rlmy-0.1.0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,8 @@
1
+ """
2
+ RLMY - Interactive RLM agent built on DSPy.
3
+
4
+ A pip-installable RLM agent with workspace management, trajectory persistence,
5
+ and cooperative interrupt handling.
6
+ """
7
+
8
+ __version__ = "0.1.0"
@@ -0,0 +1,14 @@
1
+ """
2
+ RLMY Agent — core agent logic, trajectory, and workspace management.
3
+
4
+ Key exports:
5
+ InterruptableRLM — interrupt-safe RLM subclass with prior trajectory injection
6
+ RLMContext — mutable context passed to contextual tools
7
+ """
8
+
9
+ # NOTE: Heavy imports (InterruptableRLM, etc.) are deferred to avoid loading
10
+ # the entire agent stack on `import rlmy`. Import directly from submodules:
11
+ # from rlmy.agent.rlm import InterruptableRLM, RLMContext
12
+ # from rlmy.agent.trajectory import save_trajectory, load_trajectory
13
+ # from rlmy.agent.sandbox import SandboxManager
14
+
@@ -0,0 +1,155 @@
1
+ """
2
+ Purpose: Slash command registry — decoupled matching, dispatch signaling, help display.
3
+ Usage:
4
+ from rlmy.agent.commands import REGISTRY, SlashCommandSignal
5
+ cmd = REGISTRY.match(user_text) # returns SlashCommand or None
6
+ raise SlashCommandSignal(cmd) # for context-dependent commands
7
+ Key Components:
8
+ SlashCommand — immutable command descriptor (name, aliases, action key, description)
9
+ SlashCommandSignal — exception raised when a context-dependent command is triggered
10
+ CommandRegistry — register, match, help panel
11
+ REGISTRY — module-level singleton with all commands pre-registered
12
+ Conventions:
13
+ Commands return action keys (strings), NOT callbacks.
14
+ Self-contained commands (exit, help) are handled by prompt_user() directly.
15
+ Context-dependent commands (compact) raise SlashCommandSignal for the caller to handle.
16
+ """
17
+
18
+ from dataclasses import dataclass, field
19
+ from typing import Optional
20
+
21
+ from rich.panel import Panel
22
+ from rich.text import Text
23
+
24
+
25
+ @dataclass(frozen=True)
26
+ class SlashCommand:
27
+ """
28
+ Purpose: Immutable descriptor for a slash command.
29
+ Attributes:
30
+ name: Primary command string (e.g., "/compact")
31
+ aliases: Alternative triggers (e.g., ["/c"])
32
+ action: Action key dispatched to callers (e.g., "compact"). NOT a callback.
33
+ description: Human-readable description for /help display.
34
+ self_contained: If True, prompt_user() handles it directly (e.g., exit, help).
35
+ If False, SlashCommandSignal is raised for the caller.
36
+ """
37
+ name: str
38
+ action: str
39
+ description: str
40
+ aliases: list[str] = field(default_factory=list)
41
+ self_contained: bool = False
42
+
43
+
44
+ class SlashCommandSignal(Exception):
45
+ """
46
+ Purpose: Raised by prompt_user() when user triggers a context-dependent command.
47
+
48
+ Why an exception: prompt_user() doesn't own the conversation loop or trajectory.
49
+ It can't execute context-dependent commands itself. The exception propagates to
50
+ the caller (contextual_ask_user_guidance or mainmcp's between-turns loop) which
51
+ has the necessary context to dispatch.
52
+
53
+ Attributes:
54
+ command: The matched SlashCommand
55
+ action: Shortcut to command.action for easy dispatch
56
+ """
57
+
58
+ def __init__(self, command: SlashCommand):
59
+ self.command = command
60
+ self.action = command.action
61
+ super().__init__(f"Slash command: {command.name}")
62
+
63
+
64
+ class CommandRegistry:
65
+ """
66
+ Purpose: Central registry for slash commands. Match user input, render help.
67
+
68
+ Usage Patterns:
69
+ REGISTRY.register(SlashCommand("/foo", "foo", "Do foo"))
70
+ cmd = REGISTRY.match("/foo") # returns SlashCommand
71
+ cmd = REGISTRY.match("hello") # returns None
72
+ panel = REGISTRY.help_panel() # Rich Panel for /help display
73
+ """
74
+
75
+ def __init__(self):
76
+ self._commands: list[SlashCommand] = []
77
+ # Lookup: normalized trigger string → SlashCommand
78
+ self._lookup: dict[str, SlashCommand] = {}
79
+
80
+ def register(self, cmd: SlashCommand) -> None:
81
+ """Register a command. All triggers (name + aliases) are indexed."""
82
+ self._commands.append(cmd)
83
+ for trigger in [cmd.name] + cmd.aliases:
84
+ key = trigger.strip().lower()
85
+ if key in self._lookup:
86
+ raise ValueError(
87
+ f"Duplicate trigger '{key}': already registered to '{self._lookup[key].name}'"
88
+ )
89
+ self._lookup[key] = cmd
90
+
91
+ def match(self, text: str) -> Optional[SlashCommand]:
92
+ """
93
+ Match user input against registered commands.
94
+
95
+ Args:
96
+ text: The full user input text (already stripped by prompt_user).
97
+
98
+ Returns:
99
+ SlashCommand if the ENTIRE text matches a trigger, else None.
100
+ Partial matches (e.g., "/comp" for "/compact") do NOT match.
101
+ """
102
+ return self._lookup.get(text.lower())
103
+
104
+ def help_panel(self) -> Panel:
105
+ """Build a Rich Panel listing all registered commands."""
106
+ lines = Text()
107
+ for cmd in self._commands:
108
+ triggers = ", ".join([cmd.name] + cmd.aliases)
109
+ lines.append(f" {triggers}", style="bold cyan")
110
+ lines.append(f" — {cmd.description}\n", style="dim")
111
+ return Panel(
112
+ lines,
113
+ title="[bold]Available Commands[/bold]",
114
+ border_style="blue",
115
+ padding=(1, 2),
116
+ )
117
+
118
+
119
+ # =============================================================================
120
+ # Module-level singleton — all commands registered here
121
+ # =============================================================================
122
+
123
+ REGISTRY = CommandRegistry()
124
+
125
+ REGISTRY.register(SlashCommand(
126
+ name="/quit",
127
+ action="exit",
128
+ description="Exit the program",
129
+ aliases=["/q"],
130
+ self_contained=True,
131
+ ))
132
+
133
+ REGISTRY.register(SlashCommand(
134
+ name="/help",
135
+ action="help",
136
+ description="Show available commands",
137
+ aliases=["/h", "/?"],
138
+ self_contained=True,
139
+ ))
140
+
141
+ REGISTRY.register(SlashCommand(
142
+ name="/compact",
143
+ action="compact",
144
+ description="Compact trajectory to free LLM attention (irreversible)",
145
+ aliases=["/c"],
146
+ self_contained=False,
147
+ ))
148
+
149
+ REGISTRY.register(SlashCommand(
150
+ name="/reset",
151
+ action="reset",
152
+ description="Clear trajectory and start fresh",
153
+ aliases=[],
154
+ self_contained=False,
155
+ ))