Skip to main content
Every user Turn in fleet-rlm runs as exactly one fresh native 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 native dspy.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.
  1. Python. Prefer deterministic work directly in the interpreter context.
  2. 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.
  3. 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.
  4. 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 before SUBMIT.
Children return bounded evidence, not final answers. Final synthesis is always the Root’s job.

One native child level, no deeper

The recursive child boundary is a fixed product invariant, not a knob:
Only the Root may open a native child RLM, and that child may not open another. Policies that still set 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 Root workspaces/<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.
The default 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 emitting SUBMIT. 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

Native dspy.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 in src/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.
Last modified on August 13, 2026