Skip to content

Add rlm module: config-driven RLM runtime with pluggable interpreters#31

Merged
senamakel merged 4 commits into
mainfrom
feat/rlm-module
Jul 5, 2026
Merged

Add rlm module: config-driven RLM runtime with pluggable interpreters#31
senamakel merged 4 commits into
mainfrom
feat/rlm-module

Conversation

@senamakel

Copy link
Copy Markdown
Member

Summary

Adds a top-level src/rlm/ module (Cargo feature rlm): a recursive-language-model runtime where a driver model writes code cells executed in a sandboxed interpreter whose only host surface is capability calls back into the CapabilityRegistry — sub-LLM queries (llm), tools (tool), and sub-agent delegation (agent). Scripts calling models (and agents that call models) makes the loop recursive by construction.

The three layers

  • RlmInterpreter — the pluggable execution API ("the interpreter exposed as an API"). Built-ins:
    • embedded Rhai (hermetic sandbox — no fs/network/process access, default);
    • external Python / JavaScript / Command child processes (binary + args are configuration) speaking a line-delimited JSON wire protocol, so other harnesses (e.g. openhuman) can bring their own runner.
  • RlmSession — one interpreter bound to one RlmHost; every RlmPolicy limit enforced fail-closed per cell (cell/script/output budgets, call counters, wall-clock deadline, sticky cancel flag, shared checked_child_depth recursion guard). Policy violations kill an external child rather than letting scripts observe their own limits; script-level failures stay catchable (RlmError) so the model can adapt.
  • RlmRunner — the model-driven loop: template → system prompt (placeholders filled from the live registry/policy), fenced code cells in, observations out, stop on final_answer(...). Fence-less replies get one nudge before prose is accepted as the answer.

Config-driven

An entire run is one serde document (RlmConfig::from_json): interpreter spec, driver/sub model registry names, policy, and template (general, context-explorer, orchestrator, or inline).

Verified live (OPENAI_API_KEY)

  • examples/rlm_rhai.rs — context-explorer over 200 injected expense records; found all three planted anomalies via code probing.
  • examples/rlm_python.rs — external python3 querying a real tool, delegating to a real OpenAI-backed sub-agent, returning its summary.

Commands run locally

  • cargo fmt --check
  • cargo clippy --all-targets --features rlm,repl,sqlite,tools -- -D warnings
  • cargo test --features rlm,repl,sqlite,tools (1059 passed; the pre-existing harness::workspace::git tests are flaky in this environment — concurrent git init racing on Xcode's git template copy — and pass on re-run, unrelated to this change)
  • cargo test --features rlm --test live_rlm (live, both pass)
  • cargo run --features rlm --example rlm_rhai / rlm_python (live)

Tests

  • Unit (src/rlm/test.rs, 19): config round-trips, wire-shape stability, fence extraction, template rendering, embedded-Rhai cells, fail-closed limits, runner loop against ScriptedModel.
  • E2E (tests/e2e_rlm.rs, 7): sub-agent delegation from scripts, real python3/node protocol round-trips (skip when binary missing), timeout kill, JSON-config-driven runner.
  • Live (tests/live_rlm.rs, 2): network-gated on OPENAI_API_KEY.

Docs

docs/modules/rlm/README.md (design, wire protocol, sandboxing honesty, config schema); crate docs updated for the new feature flag.

https://claude.ai/code/session_01FFRLM18Fw1mzBhpWMe3B3k

senamakel added 4 commits July 5, 2026 10:55
A driver model writes code cells executed in a sandboxed interpreter whose
only host surface is capability calls back into the CapabilityRegistry
(sub-LLM queries, tools, sub-agent delegation). Ships the embedded Rhai
backend (hermetic) and an external-process backend (Python/Node via a
line-delimited JSON wire protocol), an RlmSession cell API, a template-driven
RlmRunner loop, and serde-serializable RlmConfig for config-driven use by
external harnesses. Gated behind the new `rlm` Cargo feature.

Claude-Session: https://claude.ai/code/session_01FFRLM18Fw1mzBhpWMe3B3k
- Nudge once on fence-less driver replies before accepting prose as the
  answer (models sometimes emit raw unfenced code).
- Stop leaking registry model names into provider requests.
- Teach the Rhai usage guide the syntax gotchas live models trip on.
- Validate tool arguments against the tool schema at the host boundary.
- Add rlm_rhai / rlm_python runnable examples (verified live against OpenAI).

Claude-Session: https://claude.ai/code/session_01FFRLM18Fw1mzBhpWMe3B3k
@coderabbitai

coderabbitai Bot commented Jul 5, 2026

Copy link
Copy Markdown

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 22 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 56268043-2512-40dd-8462-23e979a0d857

📥 Commits

Reviewing files that changed from the base of the PR and between b23e213 and a34846a.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (17)
  • Cargo.toml
  • docs/modules/rlm/README.md
  • examples/rlm_python.rs
  • examples/rlm_rhai.rs
  • src/lib.rs
  • src/rlm/host.rs
  • src/rlm/interpreter/external.rs
  • src/rlm/interpreter/mod.rs
  • src/rlm/interpreter/rhai_cell.rs
  • src/rlm/mod.rs
  • src/rlm/runner.rs
  • src/rlm/session.rs
  • src/rlm/templates.rs
  • src/rlm/test.rs
  • src/rlm/types.rs
  • tests/e2e_rlm.rs
  • tests/live_rlm.rs

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a34846ab82

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/rlm/session.rs
// Bound what flows back into the driver conversation. Truncation is
// explicit (marked) so the model knows it saw a prefix.
if eval.stdout.len() > policy.max_output_bytes {
eval.stdout.truncate(policy.max_output_bytes);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Clamp truncation to UTF-8 boundaries

When a cell prints non-ASCII text and max_output_bytes cuts through a multibyte character, this String::truncate call panics instead of returning a bounded observation. For example, max_output_bytes = 1 with stdout containing é hits a non-character byte boundary; the value truncation below has the same issue, so the limit should choose the nearest valid boundary before truncating.

Useful? React with 👍 / 👎.

Comment on lines +240 to +243
HostCall::Tool {
tool: name.to_string(),
arguments: Value::Null,
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Use empty objects for zero-arg tools

When a script calls a no-argument tool through this tool("name") overload, the host validates Value::Null against the tool schema before invoking it. The normal zero-parameter schema is still an object schema such as { "type": "object", "properties": {}, "required": [] }, so this path rejects valid no-arg tools with a validation error before they run; omitted arguments should be sent as {} instead.

Useful? React with 👍 / 👎.

Comment on lines +457 to +463
let frame = match self.read_frame(deadline).await {
Ok(frame) => frame,
Err(err) => {
// Fail closed: a cell that timed out (or broke the
// protocol) leaves the child in an unknown state.
self.kill().await;
return Err(err);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Honor cancellation for external cells

When an external Python/JS cell is running code that does not make a host capability call, cancellation is not observed here because the loop only waits for protocol frames until the deadline. A shared RlmCancelFlag tripped during something like time.sleep(...) or a busy loop will therefore block until cell_timeout (or the 300s fallback) and leave the child alive instead of failing closed promptly.

Useful? React with 👍 / 👎.

@senamakel senamakel merged commit 3d7bc20 into main Jul 5, 2026
3 checks passed
@senamakel senamakel deleted the feat/rlm-module branch July 5, 2026 18:23
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