Add rlm module: config-driven RLM runtime with pluggable interpreters#31
Conversation
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
|
Warning Review limit reachedYou’ve reached a temporary PR review limit under our Fair Usage Limits Policy. Next review available in: 22 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the 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 configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (17)
Comment |
There was a problem hiding this comment.
💡 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".
| // 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); |
There was a problem hiding this comment.
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 👍 / 👎.
| HostCall::Tool { | ||
| tool: name.to_string(), | ||
| arguments: Value::Null, | ||
| }, |
There was a problem hiding this comment.
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 👍 / 👎.
| 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); |
There was a problem hiding this comment.
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 👍 / 👎.
Summary
Adds a top-level
src/rlm/module (Cargo featurerlm): 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 theCapabilityRegistry— 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:RlmSession— one interpreter bound to oneRlmHost; everyRlmPolicylimit enforced fail-closed per cell (cell/script/output budgets, call counters, wall-clock deadline, sticky cancel flag, sharedchecked_child_depthrecursion 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 onfinal_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— externalpython3querying a real tool, delegating to a real OpenAI-backed sub-agent, returning its summary.Commands run locally
cargo fmt --checkcargo clippy --all-targets --features rlm,repl,sqlite,tools -- -D warningscargo test --features rlm,repl,sqlite,tools(1059 passed; the pre-existingharness::workspace::gittests are flaky in this environment — concurrentgit initracing 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
src/rlm/test.rs, 19): config round-trips, wire-shape stability, fence extraction, template rendering, embedded-Rhai cells, fail-closed limits, runner loop againstScriptedModel.tests/e2e_rlm.rs, 7): sub-agent delegation from scripts, realpython3/nodeprotocol round-trips (skip when binary missing), timeout kill, JSON-config-driven runner.tests/live_rlm.rs, 2): network-gated onOPENAI_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