Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 17 additions & 3 deletions src/rlm/kernel_shim.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,19 +85,30 @@ def _make_proxy(name: str, parameters: dict) -> types.ModuleType:
return mod


def _parse_enabled_tools() -> frozenset[str] | None:
"""Mirror of rlm.tools._parse_enabled_tools (duplicated to keep this
module independent of rlm.__init__, which pulls in openai)."""
raw = os.environ.get("RLM_ENABLED_TOOLS", "edit").strip()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicated default diverges from named constant

Low Severity

tools.py introduces a DEFAULT_ENABLED_TOOLS constant to centralize the default, but the duplicated _parse_enabled_tools() in kernel_shim.py hardcodes the string "edit" directly. If someone updates DEFAULT_ENABLED_TOOLS, the kernel shim's default silently diverges, causing the system prompt and kernel to disagree on which skills are enabled.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit d4b7209. Configure here.

if raw == "*":
return None
return frozenset(name.strip() for name in raw.split(",") if name.strip())


def install_shims(skills_dir: str) -> list[str]:
"""Register proxy modules for all skills found in *skills_dir*.

Always installs shims regardless of whether a same-named module is
already importable — this guarantees the kernel uses the rlm
checkout's version of each skill, not an unrelated package.
Respects ``RLM_ENABLED_TOOLS`` so that disabled skills are neither
importable nor listed in the system prompt. The CLI itself remains
on PATH — we only gate the Python shim and the prompt exposure.

Returns the list of skill names that were shimmed.
"""
skills_path = Path(skills_dir)
if not skills_path.is_dir():
return []

enabled = _parse_enabled_tools()

shimmed = []
for skill_dir in sorted(skills_path.iterdir()):
if not (skill_dir / "pyproject.toml").is_file():
Expand All @@ -113,6 +124,9 @@ def install_shims(skills_dir: str) -> list[str]:
else:
continue

if enabled is not None and name not in enabled:
continue

# Skip if the CLI isn't on PATH
if not shutil.which(name):
continue
Expand Down
20 changes: 19 additions & 1 deletion src/rlm/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,32 @@ def _normalize_skill_name(name: str) -> str:
return name.replace("-", "_")


DEFAULT_ENABLED_TOOLS = "edit"


def _parse_enabled_tools() -> frozenset[str] | None:
"""Parse RLM_ENABLED_TOOLS into an allowlist of skill names.

Returns None when the env var is ``*`` (allow every discovered skill).
Empty string disables every skill. Unset defaults to ``edit``.
"""
raw = os.environ.get("RLM_ENABLED_TOOLS", DEFAULT_ENABLED_TOOLS).strip()
if raw == "*":
return None
return frozenset(name.strip() for name in raw.split(",") if name.strip())

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Enabled tools not normalized, breaking hyphenated skill matching

Low Severity

_parse_enabled_tools() returns raw user-provided names without applying _normalize_skill_name, but get_installed_skills() normalizes discovered names (hyphens → underscores) before the set intersection skills &= enabled. A skill whose distribution suffix contains hyphens (e.g. rlm-skill-web-search → discovered as web_search) would never match if the user writes web-search in RLM_ENABLED_TOOLS. Both sides of the intersection need consistent normalization.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit d4b7209. Configure here.



def get_installed_skills() -> list[str]:
"""Return installed skill names discovered from distribution metadata."""
"""Return installed skill names, filtered by RLM_ENABLED_TOOLS."""
skills: set[str] = set()
prefix = "rlm-skill-"
for dist in metadata.distributions():
name = dist.metadata.get("Name", "")
if name.startswith(prefix):
skills.add(_normalize_skill_name(name[len(prefix) :]))
enabled = _parse_enabled_tools()
if enabled is not None:
skills &= enabled
return sorted(skills)


Expand Down