dash-anirudh/Small-Recursive-Language-Models

0

stars

14

commits

Python

primary language

Apr 12, 2026

updated

README

Small Recursive Language Models

A Framework for Tool-Augmented Recursive Reasoning in Long-Context NLP Tasks

Python 3.10+ License: Apache 2.0 RULER Benchmark

sRLM wraps a small open-source LLM in a persistent Python REPL loop where it can search a document, extract entities, and recursively spawn sub-agents to answer focused sub-questions — all without task-specific fine-tuning. It is evaluated on the RULER long-context benchmark.


Table of Contents


Architecture

sRLM has four layers:

LayerModuleResponsibility
Entry & Configarg_utils, data_utils, logging_utilsCLI args, task data loading, structured logging
Agent / Modelsrc/models/srlm.pyModel loading, deterministic generation
REPL Environmentenv.pyPersistent Python scope, toolset, vector index
Session Orchestrationsrc/utils/model_utils.pyTurn loop, recursion hook, guardrails

Components

RLMAgent

RLMAgent (src/models/srlm.py) wraps any AutoModelForCausalLM:

agent = RLMAgent("Qwen/Qwen2.5-Coder-7B-Instruct")
response = agent.generate(prompt, max_tokens=2000, stop_sequences=["[TURN]"])
  • Single instance shared across the main session and all recursive sub-agents — no reload overhead.
  • Deterministic decoding (do_sample=False) for reproducibility.
  • Loaded with torch.float16 and device_map="auto".

Models available via --agent:

KeyModel ID
qwen7Qwen/Qwen2.5-Coder-7B-Instruct
qwen4Qwen/Qwen3-4B-Instruct-2507
mistral7mistralai/Mistral-7B-Instruct-v0.1
llama8meta-llama/Llama-3.1-8B-Instruct
gemma12google/gemma-3-12b-it

RLMEnvironment

RLMEnvironment (env.py) is a persistent, sandboxed Python execution scope:

  • locals_dict — mutable state visible to the agent: P (full document text), result (answer slot), last_search, _history, _sub_history.
  • globals_dict — Python builtins plus all pre-loaded tools.
  • Stdout/stderr are captured and truncated to prevent context bloat.
  • Setting result to any non-failure value terminates the session immediately.
  • Common standard libraries (re, json, math, collections, statistics, …) are pre-loaded so the model never needs to import them.

Toolset

All tools are injected into globals_dict and called directly from model-generated code — no imports needed.

Core tools (active in all modes):

ToolSignatureDescription
searchsearch(keyword)Regex-based case-insensitive scan over P; results stored in last_search
semantic_searchsemantic_search(query)all-MiniLM-L6-v2 top-k cosine similarity over pre-chunked P; results stored in last_search
extractextract(text, pattern)Entity extraction after a pattern string: extract(t, "winner is") → "Max"
llm_queryllm_query(question)Spawn a recursive sub-agent session to answer a focused sub-question
readread(start, length=1000)Raw character slice of P by index

Embeddings for semantic_search are built once by the main session (SentenceTransformer('all-MiniLM-L6-v2')) and shared with sub-agents — no re-encoding on recursion.

Extended tools (available in --concise mode):

ToolSignatureDescription
store_findingstore_finding(content, key=None)Persist an intermediate discovery to MemoryDB with an optional lookup key
recallrecall(query=, category=, key=)Query MemoryDB by content, category, or key

In concise mode each turn receives filtered [FINDINGS], [RELEVANT_HISTORY], and [RECENT_ERRORS] blocks drawn from MemoryDB instead of the full conversation history.

Session Orchestration

run_rlm_session() (src/utils/model_utils.py) is the core control loop:

for turn in range(max_turns):
    context = build_context(history | memory | summary)
    response = agent.generate(context)
    response = strip_hallucinated_output(response)
    code = extract_code(response)
    if duplicate(code): inject_error_message; continue
    output = env.execute(code)
    if env.locals_dict["result"] is valid: return result
    if error in output: inject REFLEXION_PROMPT
return "Failure: Max turns reached"

Recursive sub-agents. When model-generated code calls llm_query(question), the REPL invokes a registered recursive_hook that launches a nested run_rlm_session with:

  • The same RLMAgent instance.
  • Reduced budget (max_turns=5, max_tokens=1500).
  • Incremented depth (hard-capped at max_depth).
  • Shared embedder, chunks, and embeddings from the parent session.

Prompt Format

Every model response must follow this strict 3-part structure:

[REASONING]
<reflect on current state; identify next action>

[CODE]
```python
# ONE tool call or operation per turn
```

[NEXT]
<expected outcome and plan for next turn>

The loop extracts and executes only the Python inside [CODE]. One operation per turn is enforced by the system prompt.

History Modes

ModeFlagContext injected each turn
Full History(default)Entire accumulated conversation string
Concise--concise[MEMORY_INDEX] + [FINDINGS] + [RELEVANT_HISTORY] + [RECENT_ERRORS]
Concise2--concise2LLM-generated rolling [HISTORY_SUMMARY] + [LAST TURN]

All reported results use Full History (default).

Safety Mechanisms

MechanismDescription
Hallucination strippingstrip_hallucinated_output() truncates at the end of the first [NEXT] section, removing any fake [OUTPUT]/[STATE] the model predicts
Duplicate code detectionLast 3 executed blocks are stored; an identical repeat triggers a forced strategy-change message
Failure marker filteringresult values matching patterns like "none", "not found", "unable to" are cleared; session continues
Recursion depth capllm_query() returns a system message instead of spawning when current_depth >= max_depth
Error reflexionPython execution errors inject REFLEXION_PROMPT to guide the model toward a fix in the next turn

Benchmark & Results

Evaluated on RULER — 13 synthetic long-context tasks, 500 validation samples each, 4K token sequences. Metric: string-match accuracy.

Systems: sRLM with Qwen2.5-Coder-7B-Instruct vs. direct-prompting baselines with the same model and Llama-3.1-8B-Instruct.

TasksRLM (Qwen-7B)
niah_single_194.2
niah_single_294.4
niah_single_382.4
niah_multikey_193.8
niah_multikey_295.6
niah_multikey_376.0
niah_multivalue43.3
niah_multiquery89.75
vt7.08
cwe0.78
fwe47.6
qa_116.09
qa_222.2

Usage

pip install torch transformers sentence-transformers
# Run sRLM on a built-in task
python main.py --agent qwen7 --task RNIAH --max_turns 10 --max_tokens 2000

# Run against RULER (SLURM array job, one task per job)
sbatch scripts/run_ruler_srlm_array.sh

# Run baseline
sbatch scripts/run_ruler_baseline_array.sh

# Evaluate predictions
python eval/evaluate.py --data_dir eval_results/sRLM/preds_agent_qwen7 --benchmark synthetic

CLI arguments:

ArgumentDefaultDescription
--agentqwen7Model key (see table above)
--taskRNIAHSNIAH
--max_turns10Turn limit per session
--max_tokens2000Max generated tokens per turn
--max_depth2Recursion depth cap for llm_query()
--conciseoffEnable memory-filtered history mode
--concise2offEnable rolling LLM-summary history mode

Citation

@misc{srlm2026,
  title = {Small Recursive Language Models: A Framework for Tool-Augmented
           Recursive Reasoning in Long-Context {NLP} Tasks},
  year  = {2026},
  url   = {[https://github.com/anishdash4/Small-Recursive-Language-Models](https://github.com/anishdash4/Small-Recursive-Language-Models)}
}

RULER benchmark — github.com/hsiehjackson/RULER

Contributors

dash-anirudh

14 commits

dash-anirudh/Small-Recursive-Language-Models

0

stars

14

commits

Python

primary language

Apr 12, 2026

updated

README

Small Recursive Language Models

A Framework for Tool-Augmented Recursive Reasoning in Long-Context NLP Tasks

Python 3.10+ License: Apache 2.0 RULER Benchmark

sRLM wraps a small open-source LLM in a persistent Python REPL loop where it can search a document, extract entities, and recursively spawn sub-agents to answer focused sub-questions — all without task-specific fine-tuning. It is evaluated on the RULER long-context benchmark.


Table of Contents


Architecture

sRLM has four layers:

LayerModuleResponsibility
Entry & Configarg_utils, data_utils, logging_utilsCLI args, task data loading, structured logging
Agent / Modelsrc/models/srlm.pyModel loading, deterministic generation
REPL Environmentenv.pyPersistent Python scope, toolset, vector index
Session Orchestrationsrc/utils/model_utils.pyTurn loop, recursion hook, guardrails

Components

RLMAgent

RLMAgent (src/models/srlm.py) wraps any AutoModelForCausalLM:

agent = RLMAgent("Qwen/Qwen2.5-Coder-7B-Instruct")
response = agent.generate(prompt, max_tokens=2000, stop_sequences=["[TURN]"])
  • Single instance shared across the main session and all recursive sub-agents — no reload overhead.
  • Deterministic decoding (do_sample=False) for reproducibility.
  • Loaded with torch.float16 and device_map="auto".

Models available via --agent:

KeyModel ID
qwen7Qwen/Qwen2.5-Coder-7B-Instruct
qwen4Qwen/Qwen3-4B-Instruct-2507
mistral7mistralai/Mistral-7B-Instruct-v0.1
llama8meta-llama/Llama-3.1-8B-Instruct
gemma12google/gemma-3-12b-it

RLMEnvironment

RLMEnvironment (env.py) is a persistent, sandboxed Python execution scope:

  • locals_dict — mutable state visible to the agent: P (full document text), result (answer slot), last_search, _history, _sub_history.
  • globals_dict — Python builtins plus all pre-loaded tools.
  • Stdout/stderr are captured and truncated to prevent context bloat.
  • Setting result to any non-failure value terminates the session immediately.
  • Common standard libraries (re, json, math, collections, statistics, …) are pre-loaded so the model never needs to import them.

Toolset

All tools are injected into globals_dict and called directly from model-generated code — no imports needed.

Core tools (active in all modes):

ToolSignatureDescription
searchsearch(keyword)Regex-based case-insensitive scan over P; results stored in last_search
semantic_searchsemantic_search(query)all-MiniLM-L6-v2 top-k cosine similarity over pre-chunked P; results stored in last_search
extractextract(text, pattern)Entity extraction after a pattern string: extract(t, "winner is") → "Max"
llm_queryllm_query(question)Spawn a recursive sub-agent session to answer a focused sub-question
readread(start, length=1000)Raw character slice of P by index

Embeddings for semantic_search are built once by the main session (SentenceTransformer('all-MiniLM-L6-v2')) and shared with sub-agents — no re-encoding on recursion.

Extended tools (available in --concise mode):

ToolSignatureDescription
store_findingstore_finding(content, key=None)Persist an intermediate discovery to MemoryDB with an optional lookup key
recallrecall(query=, category=, key=)Query MemoryDB by content, category, or key

In concise mode each turn receives filtered [FINDINGS], [RELEVANT_HISTORY], and [RECENT_ERRORS] blocks drawn from MemoryDB instead of the full conversation history.

Session Orchestration

run_rlm_session() (src/utils/model_utils.py) is the core control loop:

for turn in range(max_turns):
    context = build_context(history | memory | summary)
    response = agent.generate(context)
    response = strip_hallucinated_output(response)
    code = extract_code(response)
    if duplicate(code): inject_error_message; continue
    output = env.execute(code)
    if env.locals_dict["result"] is valid: return result
    if error in output: inject REFLEXION_PROMPT
return "Failure: Max turns reached"

Recursive sub-agents. When model-generated code calls llm_query(question), the REPL invokes a registered recursive_hook that launches a nested run_rlm_session with:

  • The same RLMAgent instance.
  • Reduced budget (max_turns=5, max_tokens=1500).
  • Incremented depth (hard-capped at max_depth).
  • Shared embedder, chunks, and embeddings from the parent session.

Prompt Format

Every model response must follow this strict 3-part structure:

[REASONING]
<reflect on current state; identify next action>

[CODE]
```python
# ONE tool call or operation per turn
```

[NEXT]
<expected outcome and plan for next turn>

The loop extracts and executes only the Python inside [CODE]. One operation per turn is enforced by the system prompt.

History Modes

ModeFlagContext injected each turn
Full History(default)Entire accumulated conversation string
Concise--concise[MEMORY_INDEX] + [FINDINGS] + [RELEVANT_HISTORY] + [RECENT_ERRORS]
Concise2--concise2LLM-generated rolling [HISTORY_SUMMARY] + [LAST TURN]

All reported results use Full History (default).

Safety Mechanisms

MechanismDescription
Hallucination strippingstrip_hallucinated_output() truncates at the end of the first [NEXT] section, removing any fake [OUTPUT]/[STATE] the model predicts
Duplicate code detectionLast 3 executed blocks are stored; an identical repeat triggers a forced strategy-change message
Failure marker filteringresult values matching patterns like "none", "not found", "unable to" are cleared; session continues
Recursion depth capllm_query() returns a system message instead of spawning when current_depth >= max_depth
Error reflexionPython execution errors inject REFLEXION_PROMPT to guide the model toward a fix in the next turn

Benchmark & Results

Evaluated on RULER — 13 synthetic long-context tasks, 500 validation samples each, 4K token sequences. Metric: string-match accuracy.

Systems: sRLM with Qwen2.5-Coder-7B-Instruct vs. direct-prompting baselines with the same model and Llama-3.1-8B-Instruct.

TasksRLM (Qwen-7B)
niah_single_194.2
niah_single_294.4
niah_single_382.4
niah_multikey_193.8
niah_multikey_295.6
niah_multikey_376.0
niah_multivalue43.3
niah_multiquery89.75
vt7.08
cwe0.78
fwe47.6
qa_116.09
qa_222.2

Usage

pip install torch transformers sentence-transformers
# Run sRLM on a built-in task
python main.py --agent qwen7 --task RNIAH --max_turns 10 --max_tokens 2000

# Run against RULER (SLURM array job, one task per job)
sbatch scripts/run_ruler_srlm_array.sh

# Run baseline
sbatch scripts/run_ruler_baseline_array.sh

# Evaluate predictions
python eval/evaluate.py --data_dir eval_results/sRLM/preds_agent_qwen7 --benchmark synthetic

CLI arguments:

ArgumentDefaultDescription
--agentqwen7Model key (see table above)
--taskRNIAHSNIAH
--max_turns10Turn limit per session
--max_tokens2000Max generated tokens per turn
--max_depth2Recursion depth cap for llm_query()
--conciseoffEnable memory-filtered history mode
--concise2offEnable rolling LLM-summary history mode

Citation

@misc{srlm2026,
  title = {Small Recursive Language Models: A Framework for Tool-Augmented
           Recursive Reasoning in Long-Context {NLP} Tasks},
  year  = {2026},
  url   = {[https://github.com/anishdash4/Small-Recursive-Language-Models](https://github.com/anishdash4/Small-Recursive-Language-Models)}
}

RULER benchmark — github.com/hsiehjackson/RULER

Contributors

dash-anirudh

14 commits

Languages

Python

100.0%