rlmy 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.
rlmy/__init__.py ADDED
@@ -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"
rlmy/agent/__init__.py ADDED
@@ -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
+
rlmy/agent/commands.py ADDED
@@ -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
+ ))