Lifecycle instrumentation for DSPy's RLM (Recursive Language Model).
Project description
DSPy RLM Hooks
Lifecycle instrumentation for DSPy's RLM (Recursive Language Model).
Explore the Documentation »
Report Bug
·
Request Feature
Table of Contents
About
DSPy RLM Hooks injects lifecycle hooks into DSPy's internal RLM iteration loop, giving you full control over every stage of code generation, execution, and history tracking.
- Code Rewriting — Fix or augment LLM-generated code before it runs
- Variable Injection — Seed the interpreter with persistent variables and imports
- Result Auditing — Transform, validate, or retry on errors
- History Management — Inspect and modify the REPL history between iterations
- Sync & Async — Hooks work in either mode; coroutines are auto-detected
- PredictRLM Support — Same hook API works on PredictRLM instances
Requires DSPy 3.1+ and Pydantic 2+.
Architecture
RLM Hook Lifecycle
┌──────────────────────────┐
│ pre_iteration_hook │
└────────────┬─────────────┘
│
│ inject vars,
│ prepend code
▼
┌──────────────────────────┐
│ Generate Code │
└────────────┬─────────────┘
│
▼
┌──────────────────────────┐
│ pre_execution_hook │
└────────────┬─────────────┘
│
│ rewrite code
▼
┌──────────────────────────┐
│ Execute Code │
└────────────┬─────────────┘
│
▼
┌──────────────────────────┐
│ post_execution_hook │
└────────────┬─────────────┘
│
│ transform result
▼
┌──────────────────────────┐
│ post_iteration_hook │
└──────────────────────────┘
Hooks fire at each stage of an RLM iteration, allowing inspection and modification of behaviour.
Quick Start
Install
Install dspy-rlm-hooks with uv (recommended):
uv add dspy-rlm-hooks
Or with pip:
pip install dspy-rlm-hooks
Basic Usage
import dspy
from dspy_rlm_hooks import enable_rlm_hooks, PreIterationOutput
rlm = dspy.RLM(...)
def inject_math(iteration, variables, history, input_args):
return PreIterationOutput(
extra_vars={"tool": "calculator"},
python_code="import math",
)
enable_rlm_hooks(rlm, pre_iteration_hook=inject_math)
result = rlm(question="What is the square root of 1764?")
Usage
All Four Hooks
A realistic example showing how each hook can be used to build a safe, instrumented agent:
from dspy_rlm_hooks import (
enable_rlm_hooks,
PreIterationOutput,
PreExecutionOutput,
PostExecutionOutput,
PostIterationOutput,
)
from dspy.primitives.repl_types import REPLHistory
import re
# ── Pre-iteration: seed interpreter with a regex toolkit ──
def pre_iteration(iteration, variables, history, input_args):
"""Inject a regex helper and seed variables before every iteration."""
return PreIterationOutput(
extra_vars={"search_pattern": r"TODO|FIXME|HACK"},
python_code="""
import re
def grep(pattern, text):
return re.findall(pattern, text)
""",
)
# ── Pre-execution: block dangerous code ──
FORBIDDEN = re.compile(r"\b(eval|exec|compile|__import__)\b")
def pre_execution(iteration, code, variables, history, input_args):
"""Sanitise generated code before it reaches the interpreter."""
if FORBIDDEN.search(code):
safe_code = FORBIDDEN.sub("# BLOCKED", code)
return PreExecutionOutput(code=safe_code)
return PreExecutionOutput(code=code)
# ── Post-execution: retry on error ──
def post_execution(iteration, code, result, variables, history, input_args):
"""If execution raised an error, wrap a hint so the LLM retries next round."""
if isinstance(result, str) and result.startswith("[Error]"):
return PostExecutionOutput(
result=f"{result}\n# Hint: the variable 'search_pattern' is already in scope."
)
return PostExecutionOutput(result=result)
# ── Post-iteration: enforce a price budget ──
MAX_COST_USD = 0.50
def _estimate_cost(pred):
# In production, derive this from response.usage or similar.
return 0.015
def make_budget_hook(max_cost=MAX_COST_USD):
"""Return a post_iteration hook with isolated, per-request state.
Create a new hook for every RLM session so budgets don't leak
across concurrent requests on a multi-threaded or async server.
"""
accumulated_cost = 0.0
def post_iteration(iteration, pred, code, result, history: REPLHistory):
nonlocal accumulated_cost
accumulated_cost += _estimate_cost(pred)
if accumulated_cost >= max_cost:
return PostIterationOutput(history=history, stop=True)
return PostIterationOutput(history=history)
return post_iteration
# ── Wire everything up ──
enable_rlm_hooks(
rlm,
pre_iteration_hook=pre_iteration,
pre_execution_hook=pre_execution,
post_execution_hook=post_execution,
post_iteration_hook=make_budget_hook(max_cost=0.50),
)
result = rlm(question="Find all TODO comments in the codebase")
Async Hooks
Return a coroutine and the system handles it automatically:
async def fetch_context(iteration, variables, history, input_args):
context = await remote_cache.get(input_args["question"])
return PreIterationOutput(extra_vars={"cached_context": context})
enable_rlm_hooks(rlm, pre_iteration_hook=fetch_context)
Disabling Hooks
from dspy_rlm_hooks import disable_rlm_hooks
disable_rlm_hooks(rlm)
Removes all monkey-patched overrides and reverts to original behaviour.
PredictRLM Support
enable_rlm_hooks works on both dspy.RLM and
PredictRLM with the same API.
The function auto-detects the RLM type and uses the appropriate mechanism.
Install with the predict-rlm extra:
uv add "dspy-rlm-hooks[predict-rlm]"
Quick Example
from predict_rlm import PredictRLM
from dspy_rlm_hooks import enable_rlm_hooks, PreExecutionOutput
rlm = PredictRLM("query -> answer")
def sanitize_code(iteration, code, variables, history, input_args):
"""Block dangerous code patterns."""
if "os.system" in code:
code = code.replace("os.system", "# BLOCKED")
return PreExecutionOutput(code=code)
enable_rlm_hooks(rlm, pre_execution_hook=sanitize_code)
result = rlm(query="...")
Hook Reference
| Hook | When it fires | What it can do |
|---|---|---|
| PreIteration | Before action generation | Inject variables (extra_vars) and persistent code (python_code) |
| PreExecution | After code generation, before running | Rewrite or sanitise the generated code string |
| PostExecution | After code runs, before history processing | Transform, audit, or replace the raw result |
| PostIteration | After result is folded into history | Save learnings, trigger side effects, modify history, or set stop=True to force final extraction |
Development
Code Quality
This project uses several tools to maintain code quality:
- Ruff: Linting and formatting
- isort: Import sorting
- pytest: Testing framework
- ty: Type checking
- deptry: Dependency analysis
Available commands:
# Run all quality checks
uv run poe clean-full
# Individual checks
uv run poe lint # Ruff linting
uv run poe format # Ruff formatting
uv run poe sort # Import sorting
uv run poe typecheck # Type checking
uv run poe deptry # Dependency analysis
Testing
Run tests using pytest:
# Run all tests
uv run pytest
# Run specific test
uv run pytest path/to/test.py::test_name
Contributing
Quick workflow:
- Fork and branch:
git checkout -b feature/name - Make changes
- Run checks:
uv run poe clean-full - Commit and push
- Open a Pull Request
License
MIT (as declared in pyproject.toml).
Built by thememium
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file dspy_rlm_hooks-0.1.7.tar.gz.
File metadata
- Download URL: dspy_rlm_hooks-0.1.7.tar.gz
- Upload date:
- Size: 13.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.21 {"installer":{"name":"uv","version":"0.11.21","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e4d8c7c2b0de5f70e1a833c6e317e0546e0289e15410937497a2b720b988600d
|
|
| MD5 |
31401b38011b00b885ac7c7de3950752
|
|
| BLAKE2b-256 |
6dcfcc200d4f15a2fb97dc962cb77c9b0c71e60fa6a681ee01256f0f708f905a
|
File details
Details for the file dspy_rlm_hooks-0.1.7-py3-none-any.whl.
File metadata
- Download URL: dspy_rlm_hooks-0.1.7-py3-none-any.whl
- Upload date:
- Size: 16.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.21 {"installer":{"name":"uv","version":"0.11.21","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
55688d2965c5ce249e78bd462744476d06d95aea7ab652a86f396dee6bd6b396
|
|
| MD5 |
11f42c565c815c6453f632efef2377e7
|
|
| BLAKE2b-256 |
8913cdfc83b672d7487452a5b2355bf39f23ee6b904ecc79017416d022cdf985
|