Native provider tool calls
StandardDriver::handle_llm_success interprets provider output. Tool calls become StartTools, then RuntimeTurnDriver::run_tool_calls dispatches native mode tools first and then registered ToolProviders.
Execution starts in the CLI bootstrap path, then branches into interactive TUI or autonomous --print. Inside a turn, the active mode decides whether model output becomes native tool calls or Lashlang code execution.
The CLI resolves durable session state and runtime composition before entering the terminal UI or single-shot autonomous runner. App hosts use the app-facing lash facade (LashCore, LashSession, TurnBuilder) and never go through bootstrap::run.
Modes contribute protocol drivers and preambles. The runtime loop does not need provider-specific branches to understand Standard versus RLM behavior.
StandardDriver::handle_llm_success interprets provider output. Tool calls become StartTools, then RuntimeTurnDriver::run_tool_calls dispatches native mode tools first and then registered ToolProviders.
RlmDriver extracts a closed lashlang fence, starts ExecCode, and the RLM executor runs the program through RlmExecutionState against a HostBridge. Read-only host bindings (e.g., history) are projected into Lashlang scope via ProjectedBindings. continue_as is a mode-owned control tool; llm_query is supplied by lash-llm-tools; the stream_mask plugin hides the still-streaming fence from live UI until execution is complete.
Subagents, monitor tasks, and async tool calls share handle-shaped background work. RLM discovers live handles with list_async_handles and resolves them with await.
An RLM block ending with continue_as { task, seed } finishes the low-level turn as TurnOutcome::Handoff { session_id }; continue_as and spawn_agent decode their payload into RlmCreateExtras (in lash-rlm-types), and the successor session is created with SessionRelation::Handoff so the runtime can carry parent ids, turn index, and trace continuity. App hosts normally call session.turn(input).stream(&sink) or .run(); the facade follows handoffs and returns the final TurnResult.
lash is the higher-level app-facing API used by the examples. It hides CLI bootstrap details and lash-core runtime plumbing while leaving providers, app persistence, HTTP protocols, auth, and frontend streaming under the host application's ownership.
LashCoreModePresetModePreset::standard() and ModePreset::rlm(). Hosts can install both and choose a default with default_mode, then override per session with .standard(), .rlm(), or .mode(...).LashSessionrun(TurnInput), turn(TurnInput), read_view(), and lower-level control groups through control(), and can carry a parent session id for app-level parent/child relationships.TurnBuilder.stream(&sink) drives the turn against a TurnActivitySink and returns a rich TurnResult with state, outcome, assistant output, usage, tool calls, execution summary, and errors; .run() returns a TurnOutput with the terminal result and ordered turn activities.TurnInput@path before constructing the turn, either as text markers or as future host-owned attachments.TurnActivitylash API UI contracts.let core = LashCore::standard()
.provider(provider)
.model("anthropic/claude-sonnet-4.6", None)
.max_context_tokens(200_000)
.store_factory(store_factory)
.build()?;
let session = core.session(chat_id).standard().open().await?;
let result = session
.turn(TurnInput::text(user_text))
.stream(&events)
.await?;
examples/agent-service opens a LashSession from the chat id and store for each request, records projected TurnEvents through a TurnActivitySink, and wires an app database, a SessionStoreFactory, Axum routes, and browser streaming. The app-facing walkthrough lives in Lash API.
The lash API is intentionally above raw runtime internals but below an app framework. It should not import CLI/TUI vocabulary, own product chat storage, or dictate HTTP and frontend protocols.
Subagents are sessions, not special messages. Monitors are background tasks that wake future turns from stdout lines. Generic async tool calls are tracked inside the session.
The public subagent entry is lash_subagents::spawn_agent_tool_definition; the per-session RlmSubagentToolsProvider is crate-private. BuiltinMonitorToolPluginFactory and BuiltinTaskControlsPluginFactory live in lash_core::runtime_controls (re-exported by the lash facade as lash::plugins::*). A child can fail terminally with call submit_error { reason } (defined in lash-subagents/src/shared.rs), which surfaces back to the parent through the spawn_agent result.
The terminal receives both live stream events and completed authoritative read views.
run_app drives event handling. Completed turns reconcile with finish_turn_from_read_view; live markdown lanes and activity state bridge partial output until the final projection arrives.
lash-export reads store state plus the full provider trace JSONL when available, then renders chronological projections, prompt snapshots, context percentages, cached-token percentages, and usage bars. lash-trace-viewer reads JSONL traces and displays timeline, LLM calls, stream events, and raw records.