Turn Lifecycle
The runtime handles runtime effects; TurnMachine holds protocol state. Reply-producing nondeterministic work crosses RuntimeEffectController with invocation metadata before the response returns to the machine. Hosts can seed read-only projected values into a turn's lashlang scope through RlmProjectedBindings: inline bindings store values, lazy bindings store ProjectionRefs resolved by RLM immediately before execution.
AgentFrameSwitch Continuations
RLM's continue_as tool ends the current trajectory and creates a fresh AgentFrame in the same session with the packed task + seed. The low-level runtime exposes this as TurnOutcome::AgentFrameSwitch { frame_id, task }. App hosts call TurnBuilder::stream_to, run, or pull-style stream; the lash facade drives AgentFrame chains and returns the final turn result. The underlying runtime primitive is stream_turn_with_agent_frames.
task + seed"] Frame --> Turn
Tool Result Projection
ToolOutputBudgetPluginFactory registers a single exclusive tool-result projector. The full ToolCallOutput is persisted in the durable graph, while the projector derives the budgeted ModelToolReturn the model and rolled-up history see. Registering more than one projector is rejected at build time.
| Payload | Projection |
|---|---|
| String values | Truncated to the budget when over the byte/token limit or line count; the head/tail direction is chosen per tool. |
| Structured values | Rendered to text with their string leaves truncated recursively; non-string scalars and shape pass through. |
| Attachments | Passed through untouched as ModelToolReturnPart::Attachment. |
batch results | Each inner call's value is projected before the aggregate is rendered. |
Limits are configured via ToolOutputBudgetConfig (ToolOutputBudgetMode::{Bytes, Tokens}; defaults: 16 KiB, 400 lines). RLM budgets its own print observation history with a separate BudgetedJsonProjector built by print_history_projector.
Graph To UI Projection
Chronological rendering is a projection policy, not a storage policy. The graph remains durable and branchable; UI and exports choose readable order. lash-export walks an entire session tree: load_tree_from_paths assembles a LoadedSessionTree from the SQLite store and trace JSONL, and render_tree emits a multi-view HTML.
SQLite/Postgres state + blobs/attachments + session_head"] --> State["PersistedSessionRead"] State --> Graph["SessionGraph
nodes + leaf_node_id"] Graph --> ReadModel["internal SessionReadModel cache
active_events, messages"] ReadModel --> View["SessionReadView"] View --> Chrono["ChronologicalProjection"] Chrono --> Timeline["lash-cli UiTimeline"] Chrono --> Export["lash-export HTML/JSON"] Chrono --> RlmHistory["RLM history projection"] Timeline --> Render["render_block_into
lash-tui Frame"]
Usage And Trace Flow
Token accounting is part of the runtime data flow. Parent turns, subagents, and direct LLM helper calls all report usage into the session ledger, and host applications can read deltas from that ledger.
llm_query, spawn_agent, continue_as, process admins"] Surface --> Submit["finish JSON schema validation"] Runtime --> Usage["SessionUsageReport
ledger by source + model"] Usage --> Deltas["usage deltas
host reports"] Runtime --> Trace["provider JSONL trace
LLM started/completed"] Runtime --> Store["session store
SQLite or Postgres"] Trace --> Export["lash-export prompt snapshots
context %, cache-read %, token bars"] Store --> Export
Important Flow Rules
These rules are the easiest ones to break when refactoring runtime and UI code together.
-
Normalize host input before prompt work.
InputItem::TextandInputItem::ImageRefbecome typed message parts and attachment references; host file and dir mentions are resolved into text markers before they reach the runtime. -
Runtime satisfies effects; sans-IO applies responses.
Provider calls, direct completions, tools, process admin, checkpoints, sleeps, execution-surface sync, and code execution cross
RuntimeEffectControllerbefore responses return toTurnMachine. -
Commit policy stays turn-scoped.
TurnBoundaryapplies graph deltas, checkpoint boundaries, and usage reconciliation. -
UI vocabulary stays in CLI crates.
UiTimeline, activity blocks, render blocks, and chrome surfaces do not belong inlash. -
Usage is a ledger, not an exporter-only calculation.
Runtime usage events carry source/model/token deltas; exporters add trace-derived prompt context detail when the JSONL trace is available.
-
Tool output is projected before it is read.
The state, model, and history all read through projector hooks. Skipping these, or projecting twice, breaks budget guarantees and the parity between persisted state and what the model actually saw.
-
AgentFrame switches are not a separate channel.
An AgentFrame switch is an outcome of the same turn path. App hosts get chaining through
TurnBuilder::stream_to,run, or pull-stylestream; lower-level runtime callers opt in withstream_turn_with_agent_frames. The session id, turn index, attachment store, and trace ids carry across the boundary automatically.