A durable, performant and customizable agent harness in Rust, built for scalable backend deployments rather than single-user coding agents.
Key features:
- Durable execution (crash contract)
- A turn's terminal commits atomically; recovery on a compatible node loads its last committed checkpoint, and uncommitted work may repeat
- Many nodes share one PostgreSQL store, and survive being killed, partitioned and restarted
- Durable processes, waits and cancellation, for approvals, callbacks and triggers
- Sessions fork, park and resume
- Clients follow a turn or process with durable cursors, so a late follower catches up
- Performance
- About 10 ms of engine overhead per tool round on PostgreSQL
- Deep customization through the plugin system
- Swappable interfaces
- Store: SQLite and PostgreSQL provided
- Attachments: S3-compatible storage provided
- Tracing: a
TraceSinkseam for host sinks, and an OpenTelemetry adapter provided - Models: Anthropic, OpenAI, Gemini and any OpenAI-compatible endpoint
- Tools: your own tool providers, plus MCP servers
- Deferred tools: a host resolver supplies tools on demand, so a large catalog need not be loaded up front
- Two main execution modes, as plugins
- Standard: native provider tool calling
- RLM: the model works in a persistent REPL
- RLM, a persistent REPL over code mode
- The model writes code cells in a persistent REPL: state carries from cell to cell, and printed values come back as observations
- Tools are typed functions the code calls, so one cell can loop over and combine many calls, and run them concurrently as tasks
- Runs on the Lash VM, a small kernel language built for it; a cell saves its state whenever it waits, and a crashed cell resumes from there without re-executing committed effect outcomes; unfinished calls follow their execution policy
- Dialects lower to the kernel and none is built into it; TypeScript provided
- Model code runs in resettable worker processes, and every effect crosses the host
- Deferred tools resolve mid-session, and a granted tool stays callable after a restart
- Workflows
- Durable workflows built on the same kernel
- The agent writes them in TypeScript, or in any dialect that lowers to the kernel
- Every dialect lowers to one representation, the kernel document
- A host reads and edits that document through typed transactions, with no dialect code, which is what a workflow-editing UI needs
Alpha: works today, API still moving fast — pin to an exact
=0.1.0-alpha.Nversion when you embed.
The SDK package is named lash-runtime; alias it as lash in your dependencies
and enable the mcp feature for MCP support.
MCP support uses the upstream rmcp SDK from crates.io. Nested x-mcp-header
annotations are validated and mirrored at their exact properties paths.
Lash recovers from committed state. These guarantees require acknowledged
commits to survive the failure: SQLite file stores need SqliteSynchronous::Full
on storage that honors sync requests; PostgreSQL needs durable WAL flushes and
a failover policy that preserves acknowledged commits. An in-memory SQLite store
does not survive loss of its process. See the storage assumption
and SQLite's configuration.
- Committed effect outcomes are never re-executed. Recovery loads the
outcome instead of starting its body again. This says nothing about how many
times an external action ran before its outcome committed. Store-local
effects committed in the same transaction as their outcome are atomic with it.
Enforcement: round recovery
(
RoundMember::recovery) and store-local settlement. Law: the mixed-round crash matrix (a_mixed_round_holds_once_and_repeatable_rules_across_every_cut). - Computation since the last committed checkpoint may run again. This
includes clock, random, projection reads, declared
Readtools, printing and logging; read answers may differ, and diagnostics may repeat or be lost. Planned cell reads have no individual outcome record: their results survive only in the next saved cell state. AReadrouted through a recorded call can also run again when interrupted, but never retries a reported failure. Enforcement: kernel evaluation, printing, read execution (perform_read), and read planning. Laws: cell reads (a_read_lost_with_its_unsaved_stretch_is_made_again_on_sqlite_memoryanda_saved_read_is_never_made_again_on_sqlite_memory). ExecutionPolicy::Onceeffects interrupted mid-body settle asInterruptedand are never re-run. The started row commits before the body starts. Recovery cannot tell whether an external action happened before the crash, soInterruptedis uncertainty, not proof that nothing happened.Oncealso forbids retry after a reported failure; it does not promise successful completion. Enforcement: policy (ExecutionPolicy) and round recovery. Law: the mixed-round crash matrix.Repeatableeffects may run again under their recorded retry policy and deadlines, subject to the current declaration's veto. Recovery of a started attempt without an outcome re-enters the same ordinal; it does not consume a new retry ordinal. Thusmax_attemptsbounds admitted ordinals, not total body entries across repeated crashes. External systems must deduplicate oncall_idto make these repeats safe. Enforcement: policy (permits_repeat,reruns_interrupted), round recovery and retry admission. Laws: the mixed-round crash matrix and attempt cap (the_attempt_cap_ends_a_repeatable_call).- A durably recorded response of a durable turn-model call is never
purchased again. The turn commits the provider's raw response, with what
its stream hooks saw, as the call's receipt (
model.answer) before any stream-finished or response hook runs. A crash after the receipt commits derives the response again from the receipt on any node: hooks run again, the provider is not asked again. Recovery re-sends the pinned request only for a call with no receipt, while its recorded deadline allows; a crash after the provider answered but before the receipt commits can therefore still cost a second provider call, whose answer may differ (ADR 0132 §4, ADR 0142). Model calls a tool owns (direct calls) and the unowned client are outside this guarantee. Enforcement: model send, receipt and restore and phases. Laws: model answer receipts. - Exactly-once external behaviour is the host's job. Lash supplies a stable
ToolCallId, exposed asAttemptContext::call_id(), across crash recovery and reported-failure retries of the same logical tool call. The host and external system must use it as their deduplication key, with durable reconciliation for uncertain outcomes. A local ledger alone does not atomically commit a remote action. A fresh logical call has a fresh id; business deduplication across distinct calls needs a domain key. This tool id does not promise idempotent model billing. Enforcement: identity derivation and attempt context. Laws: tool call identity. See the host's external-effect contract and ADR 0117.
Observation replay is delivery of retained events, not re-execution of their producers. Live streams and logs have their own retention and delivery contracts; see observing turns and observing processes.
Runnable apps under `examples/
The agent workbench is the product E2E host: it owns its UI and product state, opens a Lash session per chat, and resumes browser observation with durable cursors. It carries Standard and RLM turns and durable processes. Hosts own events, routing and scheduling. The upgrade node and external consumer remain structural harness adapters for upgrade and public API proofs.
OPENROUTER_API_KEY=sk-or-... just agent-workbench 3000
# then open http://127.0.0.1:3000MIT