dspy.RLM. The Root RLM executes inside a Daytona Sandbox whose Python interpreter context persists across RLM iterations within a Run. Each new Run starts with a fresh interpreter context, so nothing leaks between Runs.
This page describes how the Root Turn decides what to do next, when it delegates to a native child RLM, and the fixed one-level recursion boundary that keeps the tree shallow and auditable.
One Turn, one Root RLM
A Turn maps to a single nativedspy.RLM invocation. The Root RLM works iteratively inside its Sandbox: it writes Python, inspects results, and refines its plan until it emits SUBMIT. Interpreter state is reused across those iterations so variables from an earlier step remain available in the next one. Replacing a Sandbox mid-Run remounts the Workspace Volume Scope but does not preserve Python globals, so long-lived REPL state only survives while the same Sandbox does.
RLMRunner in src/fleet_rlm/rlm/runner.py builds this single fresh RLM per Turn and wires its instruction fragments from src/fleet_rlm/rlm/instructions.py.
The cheapest-sufficient ladder
Inside the Root RLM, you pick the cheapest option that still solves the sub-problem. Only escalate when the previous rung is insufficient.- Python. Prefer deterministic work directly in the interpreter context.
- Native
llm_query/llm_query_batched. Use these when you need a language model, but not a new recursive agent. No child RLM is spawned. rlm_query(prompt=prompt). Delegate one iterative isolated subproblem to a native child harness. Use this when the subproblem needs its own tool-using agent loop.- Root-only
rlm_query_batched. Fan out ordered, independent child RLMs when you can decompose a task into siblings that do not depend on each other. Only the Root may open this rung. After siblings return, the Root verifies their evidence and synthesizes an answer beforeSUBMIT.
One native child level, no deeper
The recursive child boundary is a fixed product invariant, not a knob:rlm.recursion_max_depth fail startup validation. This keeps traces shallow, budgets predictable, and cleanup deterministic.
Dispatch and bounds for the single child rung live in src/fleet_rlm/rlm/recursive_calls.py, ordered sibling fan-out lives in src/fleet_rlm/rlm/recursive_batch.py, and the child Sandbox lifecycle lives in src/fleet_rlm/daytona/recursive_child_runtime.py.
Child isolation under daytona-recursive
Under the daytona-recursive profile, each child RLM runs in its own dedicated Daytona Sandbox with strict boundaries:
- Fresh, dedicated Daytona Sandbox per child, distinct from the Root Sandbox.
- Ordinary Daytona network egress, the same as any Sandbox.
- The same Volume ID mounted at
recursive/<workspace-id>/<run-id>/<call-index>. This private sibling scope cannot reach the Rootworkspaces/<workspace-id>mount, so a child cannot read or overwrite Root workspace state. - No Fleet Tools and no credentials are exposed to the child.
- Strict cleanup: the child’s scope is purged and its Sandbox is deleted before Root success can commit. If cleanup fails, the Root Turn does not report success.
daytona profile keeps recursion disabled. Enable one child level by switching to the daytona-recursive profile.
Recursion bounds
All recursion bounds live in the[rlm] section of config/fleet.toml. Fleet reserves the shared recursive budget atomically before starting a child, so parallel children cannot double-count against the same allowance.
See the configuration reference for the surrounding
[rlm] keys and profile wiring.
Root synthesis, not child answers
Batched siblings return evidence: extracted facts, structured findings, or scoped conclusions. The Root then verifies that evidence against the original goal and synthesizes the final response before emittingSUBMIT. This keeps children small, replaceable, and auditable, and it keeps final answers grounded in Root-side reasoning that has full Turn context.
The verification and bounded-context expectations are encoded as instruction fragments in src/fleet_rlm/rlm/instructions.py alongside the base, REPL, tool, and optional recursion fragments.
REPL variable mode and large inputs
Nativedspy.RLM in DSPy >= 3.3.0b1 uses the SandboxSerializable contract to hold large inputs as REPL variables inside the child’s persistent Python interpreter context. Those variables are not injected into the model prompt; the RLM works with them programmatically, only surfacing the fragments it needs.
This is upstream DSPy behavior, so fleet-rlm does not maintain wrapper code for large-input handling. You benefit from it automatically when you pass large context objects that implement the contract.
Interpreter reuse within a Run
Within one Run, interpreter calls reuse a single context so Python state persists across RLM iterations in the Root Sandbox. Child RLMs each get their own interpreter context in their own Sandbox, and that context is discarded when the child is deleted. Every later Run receives a fresh interpreter context in a fresh Root Sandbox. Daytona runtime wiring for both Root and recursive-child paths lives insrc/fleet_rlm/composition/daytona.py.
Implementation pointers
See also
Daytona runtime
Sandbox lifecycle, volumes, and how the Root and recursive-child paths are wired.
Agent model
How a Turn maps to one fresh RLM and where the ladder sits in the runtime.
Configuration
The full
[rlm] section and profile wiring for daytona and daytona-recursive.HTTP API
Turn and Run endpoints that drive the Root RLM and observe recursion bounds.