Skip to main content

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
  1. About
  2. Quick Start
  3. Usage
  4. Development
  5. Contributing
  6. License

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

Requires DSPy 3.1+ and Pydantic 2+.

(back to top)

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.

(back to top)

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?")

(back to top)

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.

(back to top)

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

(back to top)

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

(back to top)

Contributing

Quick workflow:

  1. Fork and branch: git checkout -b feature/name
  2. Make changes
  3. Run checks: uv run poe clean-full
  4. Commit and push
  5. Open a Pull Request

(back to top)

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

dspy_rlm_hooks-0.1.5.tar.gz (10.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

dspy_rlm_hooks-0.1.5-py3-none-any.whl (12.3 kB view details)

Uploaded Python 3

File details

Details for the file dspy_rlm_hooks-0.1.5.tar.gz.

File metadata

  • Download URL: dspy_rlm_hooks-0.1.5.tar.gz
  • Upload date:
  • Size: 10.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.14 {"installer":{"name":"uv","version":"0.11.14","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

Hashes for dspy_rlm_hooks-0.1.5.tar.gz
Algorithm Hash digest
SHA256 7245e27aaca63566ed03e5c5cb025d5ca9497600916ea8ac5b7e2c1821ac677c
MD5 3e8a2f25736a23874ed27e20a60d4a64
BLAKE2b-256 c870b1b7c243dc1c2e9b3255a7e6977ab39194c6c8f5ef99bebb992a56f9ddda

See more details on using hashes here.

File details

Details for the file dspy_rlm_hooks-0.1.5-py3-none-any.whl.

File metadata

  • Download URL: dspy_rlm_hooks-0.1.5-py3-none-any.whl
  • Upload date:
  • Size: 12.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.14 {"installer":{"name":"uv","version":"0.11.14","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

Hashes for dspy_rlm_hooks-0.1.5-py3-none-any.whl
Algorithm Hash digest
SHA256 d4b84c5917f8e89e15994560ffb74483dfa816818e1920b9c88e20f0883855a6
MD5 df940805c236f6e5e71eb2ffea276a13
BLAKE2b-256 1cb584617c8e3f1916049561b9dc5ab4b6f1d275b6c14b0fe3de32815ddcf027

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page