What This Is
RLM is a core execution mode. You still open normal LashSession handles and run normal turns; the protocol changes the model action from provider-native tool calls to one paired <lashlang> block per iteration.
The model writes optional prose, then a <lashlang> block closed by </lashlang>. Lash executes only the source inside that block in the active AgentFrame VM and records reasoning, code, print output, diagnostics, images, and terminal values.
Lashlang has no direct filesystem, process, network, provider, or database access. Every useful operation crosses the linked host surface and the runtime effect controller.
Each iteration may observe more data. The session graph commits only when the turn finishes, stops, or switches AgentFrame.
Mode Fit
RLM is the mode for turns where the model should compose state reads, tools, loops, validation, subagents, or background processes inside one turn.
Minimal Example
Install the RLM mode and runtime plugin stack, then open the session in RLM mode.
use std::sync::Arc;
use lash::TurnInput;
let factory = lash::rlm::RlmProtocolPluginFactory::new(
lash::rlm::RlmProtocolPluginConfig::default(),
Arc::new(lash::persistence::InMemoryLashlangArtifactStore::new()),
);
let core = lash::LashCore::rlm_builder(factory)
.plugins(lash::plugins::runtime_plugin_stack())
.provider(provider)
.model(
lash::ModelSpec::from_token_limits(model_id, None, 200_000, None)
.expect("valid model metadata"),
)
.effect_host(Arc::new(lash::durability::InlineEffectHost::default()))
.attachment_store(Arc::new(lash::persistence::InMemoryAttachmentStore::new()))
.build()?;
let session = core.session("task-42").open().await?;
let output = session
.turn(TurnInput::text(
"Inspect the task and finish a concise result.",
))
.run()
.await?;
For a complete core/provider setup, start from Quickstart and switch the mode to RLM.
Provider Request Shape
RLM sends a normal chat-style request with no native provider tools. Tool and host work starts only after the model emits Lashlang.
LlmRequest {
messages: [
System(rendered system prompt),
...chronological history messages,
User(current iteration tail),
],
tools: [],
tool_choice: None,
}
- System prompt
- The prompt template plus RLM execution instructions for the effective host surface, full tool contracts for every catalog member (rendered under their Lashlang call-path), projected-variable sections, and plugin prompt hooks. RLM has no native tools array, so every member is documented in the prompt; the long tail of non-resident tools is reached on demand through a host
DeferredToolResolver(the CLI's MCP example wires one) and advertised via a catalogue-preview prompt contribution. - History messages
- Prior user messages, tool calls, protocol events, and RLM steps. Long fields are previewed with
history[N]handles for full lookup in Lashlang. - Iteration tail
- Volatile user-role instructions for this iteration: iteration number, turn causes, finalization rule, required output schema, final-answer format, and context guidance.
Variables And State
Keep the three state channels separate: persistent VM globals, read-only projected bindings, and prompt hints.
Variables assigned in fenced code persist in the active AgentFrame's RLM execution state. A fresh AgentFrame starts clean unless values are passed through seed.
history plus host-injected projected values are read-only names. Duplicate session/turn names are rejected before execution.
The prompt lists names, types, and small previews. It does not inline every bound value; large values should be projected through refs.
print {
history_entries: len(history),
board_turn: board.turn,
board_cells: len(board.cells)
}
Execution Loop
Each iteration has one model response and at most one executed Lashlang block. Anything after the first closed block is ignored by the driver.
The runtime renders the system prompt, projects history, appends the iteration tail, and calls the provider.
The model emits prose plus one closed lashlang block. Stream masking keeps raw code out of normal assistant-prose UI.
The executor resolves host descriptors, links against the active LashlangHostEnvironment, and runs the code in the current VM state.
print output, operation results, diagnostics, and images become the next iteration's history context.
finish finishes as TurnFinish::FinalValue; prose can finish as TurnFinish::AssistantMessage when the mode's final-answer policy allows it.
Common Tasks
Most RLM host work is about shaping the names and abilities available to the program.
Bind read-only values with RlmProjectedBindings. See Projected bindings.
Use RlmTurnBuilderExt::require_finish(), require_finish_schema(...), or allow_prose_or_finish() per turn. Use RlmSessionBuilderExt::final_answer_format(...) when a root RLM session should finish Markdown, a raw value, or custom final-answer guidance. See RLM finish.
Expose host operations through tool providers and the RLM surface. See Tools.
Install SubagentsPluginFactory so RLM can call spawn_agent. See Subagents.
Enable process abilities and durable execution scopes for workflow-backed background work. See Durable workflows and Lashlang effects.
Failure Modes
RLM failures are usually malformed model code, unavailable projected state, disabled host abilities, or missing terminal behavior.
Parser, linker, or runtime diagnostics are fed back into the next iteration. Repeated failures eventually hit the mode's turn limit.
Lazy ProjectionRef values must resolve in the active resolver. If the resolver changed after resume, execution fails loudly instead of substituting null.
When finish is required and the model never emits it, the turn stops after the configured max-turn policy. See RLM finish.
Where Next
This page owns the RLM guide. These pages own the deeper contracts.