rlm/protocol

Use RLM when the model should write Lashlang programs instead of issuing one provider-native tool call at a time. Lash executes the code in a persistent VM, every effect crosses the host boundary, and the turn commits once.

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.

program action

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.

host boundary

Lashlang has no direct filesystem, process, network, provider, or database access. Every useful operation crosses the linked host surface and the runtime effect controller.

turn commit

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.

vm globals

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.

bindings

history plus host-injected projected values are read-only names. Duplicate session/turn names are rejected before execution.

prompt hints

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.

1. prompt

The runtime renders the system prompt, projects history, appends the iteration tail, and calls the provider.

2. code

The model emits prose plus one closed lashlang block. Stream masking keeps raw code out of normal assistant-prose UI.

3. execute

The executor resolves host descriptors, links against the active LashlangHostEnvironment, and runs the code in the current VM state.

4. observe

print output, operation results, diagnostics, and images become the next iteration's history context.

5. finish

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.

project state

Bind read-only values with RlmProjectedBindings. See Projected bindings.

shape final output

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.

add tools

Expose host operations through tool providers and the RLM surface. See Tools.

spawn agents

Install SubagentsPluginFactory so RLM can call spawn_agent. See Subagents.

background work

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.

bad code

Parser, linker, or runtime diagnostics are fed back into the next iteration. Repeated failures eventually hit the mode's turn limit.

missing binding

Lazy ProjectionRef values must resolve in the active resolver. If the resolver changed after resume, execution fails loudly instead of substituting null.

no finish

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.

read on ·