Skip to content

Repository files navigation

Recursive Language Model (RLM) with SimpleAgents (Python)

RLM Poster

GitHub Repo PyPI Package Monthly Downloads Docs

Production-ready Recursive Language Model orchestration with SimpleAgents

Hello folks - this repo shows a practical, from-scratch implementation of a Recursive Language Model inspired by:

The goal is simple: keep long context outside the one-shot prompt, reason with executable steps, and still keep workflow orchestration and traceability.

What this repo contains

  • rlm_runner.py - core recursive loop (parse repl code, execute, finalize)
  • handlers.py - tool handlers (execute_repl, llm_query) used by workflow llm_call nodes
  • rlm_orchestrator.yaml - llm_call + switch orchestrator with tool-calling
  • traditional_orchestrator.yaml - traditional one-shot llm_call baseline workflow
  • run.py - CLI runner (direct or workflow mode)
  • benchmark_rlm.py - benchmark harness (baseline vs RLM)
  • tests/ - smoke + utility tests

Visual architecture

flowchart TD
    A[Query and Long Context] --> B["RLM Root Loop<br/>model to repl code to execute"]
    B --> C[llm_query sub-call optional]
    B --> D[FINAL or FINAL_VAR]
    C --> B
    D --> E[Final answer + trace]

    F[run.py] --> G[run_workflow_yaml]
    G --> H[rlm_orchestrator.yaml]
    H --> I[llm_call with tools]
    I --> J[execute_repl and llm_query handlers]
Loading

Why this matches the RLM idea

As per the paper, this implementation keeps the prompt data as external state (context variable), and lets the model work over it through symbolic computation (repl code blocks), instead of forcing all reasoning into one token stream.

Build from scratch (step by step)

1) Start a new Python project

uv init

2) Add dependencies

In pyproject.toml, add:

  • simple-agents-py
  • python-dotenv
  • pyyaml
  • pytest (dev)

Then install:

uv sync
uv sync --extra dev

3) Set up provider env

cp .env.example .env

Set these vars:

  • WORKFLOW_PROVIDER=openai
  • WORKFLOW_API_BASE=<openai-compatible-base-url>
  • WORKFLOW_API_KEY=<api-key>
  • WORKFLOW_MODEL=gemini-3-flash

4) Implement RLM core (rlm_runner.py)

Core flow:

  1. Build a root prompt with query + context metadata.
  2. Ask model for output containing ```repl ... ``` blocks.
  3. Execute those blocks in controlled REPL scope.
  4. Detect finalization via FINAL(...) or FINAL_VAR(...).
  5. Save per-turn traces (code, stdout, error, termination reason).

5) Add workflow integration

In rlm_orchestrator.yaml:

  • Use llm_call nodes with output_schema
  • Use switch routing for deterministic branching
  • Add tool declarations for execute_repl and llm_query

In handlers.py:

  • Implement tool handlers with deterministic signatures:
    • execute_repl(...) for sandboxed code execution over state
    • llm_query(...) for concise semantic sub-calls

6) Add CLI (run.py)

  • --mode direct: run runner directly
  • --mode workflow: run through workflow engine
  • support --query, --context, --context-file, and optional trace path

7) Add benchmark + tests

  • benchmark compares one-shot truncated baseline vs RLM methods
  • tests validate parser/utilities and smoke-path behavior

Running this project

Install

uv sync
uv sync --extra dev

Workflow mode (recommended)

uv run python run.py \
  --mode workflow \
  --query "Summarize this context in one sentence." \
  --context "RLM keeps context external and reasons with code."

Direct mode

uv run python run.py \
  --mode direct \
  --query "Summarize in 2 bullets" \
  --context "Your long context here"

Tests

uv run pytest -q

Benchmark: proving behavior

uv run python benchmark_rlm.py \
  --records 400 \
  --baseline-context-chars 2000 \
  --output benchmark_report.json

Check these proof signals:

  • accuracy by method
  • avg_turns, avg_subcalls
  • per-task trace, termination_reason, correct

Compared methods include:

  • baseline_direct_truncated (direct one-shot with truncated context)
  • traditional_workflow_truncated (YAML one-shot workflow baseline)
  • rlm_direct (RLM runner)
  • rlm_workflow (RLM via YAML llm_call + tool calls)

Learnings from building this

Here are my real takeaways:

  1. External state is the key difference
    Bigger context windows help, but external memory + symbolic steps give better control.

  2. Finalization contract is very important
    Models may compute correctly in code but phrase final text loosely. Trace-based checks keep outputs reliable.

  3. Workflow + RLM is a strong combo
    Workflow provides orchestration and guardrails; RLM provides flexible decomposition.

  4. Limits are non-negotiable
    max_turns, max_subcalls, and safe REPL scope are essential for production safety.

  5. Measure first, then scale
    Get benchmark harness in place early, then improve with async subcalls, deeper recursion, and tougher tasks.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

Packages

Used by

Contributors

Languages