Architecture chapter 06

types/contracts

The important architecture lives in small contracts. Provider components, protocol drivers, tool definitions, output contracts, plugin hooks, store commits, read projections, and UI surfaces — each a named seam.

Core Contracts

These abstractions define the system more precisely than crate names do.

AbstractionOwnerRole
TurnMachine, Effect, Responselash-sansioPure turn state machine and host-effect protocol.
TurnOutcome, TurnFinish, TurnStoplash-sansioThree-class turn result: Finished{AssistantMessage|SubmittedValue|ToolValue}, Handoff{session_id}, Stopped{Cancelled|InvalidInput|MaxTurns|ToolFailure|ProviderError|PluginAbort|RuntimeError|SubmittedError{...}|ToolError{...}}.
ModePreamble, ContextProjector, ProtocolDriverHandlelash-sansio + mode cratesMode-specific prompt, request projection, and model-output interpretation.
LashCore, LashSession, TurnBuilderlashApp-facing facade for shared core configuration, per-conversation sessions, and semantic turn streaming.
SessionRelation::{Root, Child, Handoff}lash-core/src/plugin/mod.rsHow a new session relates to its parent: top-level, internal child, or successor produced by continue_as.
StandardCreateExtras, RlmCreateExtras, RlmProjectedSeedSnapshotlash-core/src/plugin/mode.rs + lash-rlm-typesMode-specific session-creation payloads. RLM carries a termination policy and an optional projected-binding seed for handoffs and spawned agents.
PluginSessionContext, SubagentSessionAuthoritylash-core/src/plugin/registry.rsPer-session context handed to plugin factories, plus the marker that identifies a session as a subagent and tracks agent_name, depth, and parent.
SessionStateEnvelopelash-core/src/runtime/state.rsDurable session-state container: graph, policy, turn index, usage, and mode turn options.
FollowedTurn, stream_turn_following_handoffslash-core/src/runtimeInternal handoff-following runtime primitive used by the lash facade. App hosts normally use TurnBuilder::stream, TurnBuilder::run, or TurnBuilder::collect_with.
SessionSnapshotHost, ToolCatalogHost, ToolStateHost, SessionLifecycleHost, SessionGraphHost, TaskHost, DirectCompletionHostlash-core/src/plugin/mod.rs + lash-core/src/runtime/session_manager/*Focused internal runtime-host trait split implemented by RuntimeSessionManager. Public tool code reaches the same powers through explicit ToolContext capabilities instead of receiving the bridge traits.
TurnCommitPipeline, TurnProgress, TurnGraphOverlaylash-core/src/runtimeTurn-scoped graph, checkpoint, usage, and read-state commit policy.
SessionGraph, SessionReadView, ChronologicalProjectionlash-core + lash-cli/src/app/projection.rsDurable event storage, public read seam (both in lash-core), and semantic timeline projection (CLI-side).
RuntimePersistence, RuntimeCommit, GraphCommitDeltalash-core/src/store.rsRuntime persistence contract and optimistic head revision commit shape.
Storelash-sqlite-storeConcrete SQLite implementation for graph nodes, session heads, blobs, checkpoints, usage, tombstones, and vacuum metadata.
ProviderHandle, ProviderComponentslash-core/src/provider/mod.rsPersisted provider handle split by state, auth, readiness, transport, and model policy.
ToolDefinition, ToolSurface, ToolOutputContractlash-sansioTool visibility, model schema, examples, compact docs, and dynamic return contracts.
PluginFactory, SessionPlugin, PluginRegistrarlash-core/src/pluginPlugin lifecycle and hook registration for tools, prompts, modes, runtime events, and plugin actions.
LlmToolsPluginFactory, DirectCompletionHostlash-llm-tools + lash-corellm_query tool registration, output-schema handling, direct completion, and usage relay.
RlmControlToolsProviderlash-mode-rlm/src/control_tools.rsMode-owned RLM control tools such as continue_as, which produces a TurnOutcome::Handoff.
ProjectedBindings, ProjectedValue, ProjectedHostValue, ProjectedReadRequest, ProjectedReadResponselashlang/src/runtime/value.rsRead-only bindings injected from the host into Lashlang scope. RLM uses this to expose history and other projected globals without copying them into VM State; assignment over a projected name is rejected at runtime.
ToolResultProjectionHook, ToolOutputBudgetPluginFactory, ToolOutputBudgetConfiglash-core/src/plugin/tool_result_projection_builtin.rsThree-stage tool-output budgeter — BeforeState, BeforeModel, BeforeHistory — with ToolOutputBudgetMode::{Bytes, Tokens} limits (defaults: 16 KiB, 400 lines).
ToolDiscoveryIndex, CatalogTool, llm_rerank_requestlash-plugin-tool-discovery/src/*Search/load tool discovery pipeline split across catalog, provider, ranking, rerank, and schema-index modules.
UiTimelineItem, ActivityBlock, TuiExtensionslash-cli / lash-tui-extensionsCLI render projection, tool activity display, and host-owned surface slots.

Plugin Registration

Plugins are assembled per session. Each plugin can contribute mode drivers, native tools, tool-surface entries, prompt text, lifecycle hooks, plugin actions, and UI activity.

Plugin contract shape
flowchart LR Factory["PluginFactory"] --> SessionPlugin["SessionPlugin"] SessionPlugin --> Registrar["PluginRegistrar"] Registrar --> Mode["mode hooks
session + driver + native tools"] Registrar --> Tools["ToolProvider
ToolSurfaceContributor"] Registrar --> Prompt["PromptContributor
context transforms"] Registrar --> Runtime["runtime events
plugin actions"] Registrar --> UI["UI activity / panels"] Mode --> Session["Session"] Tools --> Session Prompt --> Session Runtime --> Session UI --> CLI["lash-cli projection"]

Boundary Rules

The current codebase relies on these separations staying explicit.

  1. No UI vocabulary in core runtime.

    UiTimelineItem and rendering stay in lash-cli.

  2. Provider quirks stay at provider boundary.

    OpenAI-compatible schema normalization belongs in request builders, not canonical tool definitions.

  3. Graph storage is not chronological policy.

    Dedupe and ordering live in projections such as ChronologicalProjection.

  4. Background work is handle-shaped.

    Monitors, subagents, and async tool calls are rediscovered through list_async_handles.

  5. Host bindings are projected, not copied.

    RLM exposes history and any host globals to Lashlang via ProjectedBindings. They behave like ordinary names but cannot be reassigned, and the runtime prunes reserved projected names on snapshot restore so successor sessions never inherit a stale copy.