Skip to content

skills: opt-in callable_module helper for await <skill>(...)#55

Draft
rasdani wants to merge 1 commit into
mainfrom
skill-callable-module
Draft

skills: opt-in callable_module helper for await <skill>(...)#55
rasdani wants to merge 1 commit into
mainfrom
skill-callable-module

Conversation

@rasdani

@rasdani rasdani commented Apr 23, 2026

Copy link
Copy Markdown
Collaborator

Summary

Move the await <skill>(...)await <skill>.run(...) shorthand out of rlm's kernel-startup f-string and into a tiny public helper skill authors opt into.

  • rlm.skill.callable_module(name) — sets the skill module's __class__ to a ModuleType subclass whose async __call__ forwards to self.run. Call it once from the skill package's __init__.py after run is bound at module scope.
  • src/rlm/__init__.py opts in for rlm itself, so await rlm('sub-task') keeps returning RLMResult inside the kernel.
  • src/rlm/tools/ipython.py drops the kernel-side _CallableModule wrap loop. Kernel startup now just does __import__(name) for each installed skill and import rlm if recursion is allowed. No ModuleType subclassing in the f-string.

Skill author usage

# skills/edit/src/edit/__init__.py
from .edit import PARAMETERS, main, run

__all__ = ["PARAMETERS", "main", "run"]

from rlm.skill import callable_module
callable_module(__name__)

After this, await edit(...) is equivalent to await edit.run(...); edit.run, edit.PARAMETERS stay accessible unchanged.

Coordination

The system prompt continues to advertise "Each skill is an async function by the same name". That promise is only true for skills that have adopted the opt-in, so this PR must merge together with the coordinated research-environments migration:

PrimeIntellect-ai/research-environments#329 (draft): opts in for all seven existing skills (rlm_swe/edit, rlm_deepdive/websearch, rlm_deepdive/open_webpage, rlm_browsecomp/{exa,serper}/{websearch,open_webpage}). Two-line addition per __init__.py, no logic changes.

Merge order: merge #55 first, then #329 once the skill packages can pick up a released rlm that exposes rlm.skill.callable_module. Skills that never opt in stay as plain modules — callers write await <skill>.run(...).

Test plan

  • pytest (2/2) and ruff check clean
  • Direct smoke test: with rlm.skill.callable_module applied to a fake module exposing an async run, await mod(...) and await mod.run(...) return the same value and mod.PARAMETERS is intact
  • import rlm makes rlm callable: callable(rlm) is True and type(rlm).__call__ forwards to rlm.run
  • Coordinated research-environments#329 merged, then re-run vf-eval rlm-swe to confirm await edit(...) still works end-to-end

🤖 Generated with Claude Code

Add `rlm.skill.callable_module(name)`: skill authors call it from
their package's `__init__.py` to make `await <skill>(...)` shorthand
for `await <skill>.run(...)`.

Mechanism: subclasses `types.ModuleType` with an async `__call__` that
forwards to `self.run`, then sets the skill module's `__class__` to
that subclass in place. `run` and `PARAMETERS` stay accessible as
ordinary module attributes.

The kernel's `_inject_startup` drops the ad-hoc `_CallableModule`
wrap that used to force every installed skill into a callable form
from rlm's side. Skills that want the shorthand opt in themselves;
skills that don't stay as plain modules and callers write
`await <skill>.run(...)` explicitly.

rlm's own `__init__.py` opts in (one call to `callable_module(__name__)`)
so `await rlm('sub-task')` keeps working inside the kernel — same
semantics as before, returns an `RLMResult`.

Kernel startup becomes: pre-import each installed skill into the
user namespace, `import rlm` if recursion is allowed. No ModuleType
subclassing in the f-string any more.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant