Skip to content

Releases: datalium/strata

Datalium Strata 0.9.0-preview.1

Pre-release

Choose a tag to compare

@github-actions github-actions released this 29 Oct 11:39

The first version with a number, and the first one published: on nuget.org, on crates.io and as a GitHub release with the strata CLI. Before it, applications pinned a commit of this repository.

Why 0.9 and why a preview: the 2026-09-21 evaluation called the state of that day 0.3.0 and set gates for what follows. The gate for 0.5.0 (a real application has run on it and its findings are fixed) has been met. Of the four for 0.9, crash-durable checkpointing, storage hardening and benchmarks are built, and this preview, published on both registries, is the fourth.

Built by the Datalium Strata team, developed privately from June 2026 until this release.

The packages

.NET Rust What it is
Datalium.Strata datalium-strata, datalium-strata-macros The engine: typed state graphs, compile-time validation, streaming, interrupts and resumes, waits and timers, parallel groups and maps, subgraphs, retries, tools generated at compile time, durable execution. The .NET package includes the [StrataTool] source generator.
Datalium.Strata.Storage datalium-strata-memory Checkpoint and history stores: in-memory, SQLite, Postgres, Redis.
Datalium.Strata.Cloud datalium-strata-cloud The Strata Cloud checkpoint store, decisions client and decision worker.
Datalium.Strata.Hosting datalium-strata-host Registration and settings (.NET), the wake, recovery and retention drivers, health.
Datalium.Strata.AI datalium-strata-ai Model calls and agents: chat from a node, the model node, the agent loop with typed answers, model routes, embeddings and the long-term memory helper.

A .NET application usually references Datalium.Strata.Hosting alone: it brings the other four, and the source generator with them.

Changes before this version

For applications that pinned a commit of this repository before there were versions. Newest first. Each entry names the design document with the detail.

  • A tool's own ArgumentException is no longer mistaken for bad arguments (design/20, design/44). .NET's generated dispatch now throws ToolArgumentsException (Datalium.Strata.Tools, an ArgumentException, with ToolName and Problems) for arguments it can't bind, before the tool runs. StrataAgent refuses only that back to the model. Before, it also caught a tool's own ArgumentException or JsonException (a domain check, say), hiding a real failure; that is now the tool's failure, which fails the node or, under ToolFailures.ReturnToModel, goes back as tool 'x' failed: ….
    • The model is told what to fix, in the same words in both engines: invalid arguments for tool 'lookup': argument 'orderId' must be a string, … argument 'count' is missing, … the arguments aren't a JSON object. Before, .NET passed on the parser's message, which named a generated record and a line number. The rule is ToolArguments.Problems / Rust tools::argument_problems, pinned by the new shared fixture schema/fixtures/tools/argument-problems.json; Rust's #[strata_tool] now uses it for ToolArgumentParseFailure.
    • .NET's generated dispatch refuses a missing required argument, and null for a non-nullable parameter, as the manifest says and as Rust always did. Before, the tool ran with null or 0.
    • The registry's error.type invalid_arguments now means only this. A tool's own JsonException is reported by its type name.
    • Compatibility: a tool that relied on getting null or 0 for an omitted required parameter is now refused; give the parameter a default, or make it nullable. Code that matched an agent's refusal text on the parser's words sees the new words. A hand-written IStrataToolProvider that threw JsonException or ArgumentException for bad arguments should throw ToolArgumentsException to keep the agent's refusal.
  • An agent's options can change by turn (design/44). StrataAgentOptions.ConfigureTurn (Action<AgentTurn, ChatOptions>) and Rust's AgentOptions::with_configure_turn (Fn(&AgentTurn, &mut ChatRequest)) change one turn's copy of the options before its model call, after the agent has set the tools and response format. AgentTurn gives the turn's number, the tool turns before it, the repairs so far and whether it is a repair. For example, a tool call can be required on the first turn only; with ChatOptions.ToolMode / tool_choice it applied to every turn, so the model could never answer. New .NET AgentTurn; Rust AgentTurn, ConfigureTurn.
  • A cancelled streaming run no longer hangs on a full channel (design/17). The engine's own NodeEnd, Retry and Error chunks waited for room with no token. A consumer that stopped reading and then cancelled the run (an await foreach over ReadAllAsync(token) with the run's token, say) could leave the run blocked forever, its completion never finishing. Now those chunks are dropped once the level's token fires, as NodeStart already was. Both engines.
  • .NET: a handler cancelled while it waits on a full stream sees OperationCanceledException. EmitTokenAsync / EmitDiagnosticAsync usually threw ChannelClosedException instead, because the run ended and closed the channel before the handler's token reached the write. Code that caught OperationCanceledException missed it.
  • .NET's recovery count is public (design/33, SG-39). RecoveryCount (Datalium.Strata.Graph) with MetadataKey (strata.recoveries) and Of(checkpoint) is now public, as Rust's recovery::RECOVERIES_KEY and recoveries_of already were. It is how many times a thread has been recovered since a node last committed. RecoverOptions.MaxRecoveries limits it for the driver only; RecoverAsync and RecoverStreaming always recover, so an application that recovers threads itself reads RecoveryCount.Of on the latest checkpoint and stops at its own limit, instead of reading the key by name. No behaviour or wire format changed.
  • Exported definitions mark interrupt boundaries (design/48, SG-37). ToDefinition() / to_definition() now writes "interrupt": { "timeout_ms": … } on every interrupt boundary that is a node: the timeout in whole milliseconds, or null for a boundary without one. A plain node, a parallel group, a subgraph node or a map node can each be a boundary; a wait node can't. A boundary's routes are still its on_reject / on_timeout edges. A level with a boundary is definition schema_version 1.6.0, a superset of 1.5.0; every other level exports exactly as before. The shared validator gains STRATA_ERR_BOUNDARY_IN_MAP for a boundary in a map's template, which .compile() already refused. strata graph validate, check_shape and .NET's ToTopology() refuse (exit 2) an interrupt below 1.6.0 or on a wait node, a negative timeout_ms (.NET; Rust's type refuses it), and, at a 1.6.0 level only, an on_reject edge from a node that isn't a boundary or an on_timeout edge from a node with no timeout. strata graph view annotates each boundary (Approve: interrupt boundary, rejects to Abort, times out after 30m to Escalate, or …, then the thread ends when a timeout has no route). New .NET GraphNodeDetail.Interrupt (GraphInterrupt(TimeoutMs)), GraphNodeDefinition.Interrupt (GraphInterruptDefinition), GraphDefinition.BoundaryVersion, ValidationErrorKind.BoundaryInMap; Rust GraphNodeDef.interrupt (GraphInterruptDef), schema::BOUNDARY_GRAPH_SCHEMA_VERSION, ValidationErrorKind::BoundaryInMap. Compatibility: an exported graph with a boundary changes bytes and version, so a snapshot of one changes, and an older CLI refuses it (unsupported schema_version '1.6.0'): upgrade the CLI that gates definitions first. A Rust match on ValidationErrorKind naming every variant breaks. Checkpoints are unchanged. New wire format: graph definition 1.6.0.
  • The .NET Redis memory store's search no longer drops records (design/45). On Redis 8, an FT.SEARCH that returns the documents' fields can leave documents out of its reply, though the index has them: about one search in twenty-five straight after a write under load missed one record or all of them, which the read-your-writes fix couldn't help. SearchAsync now asks the index for keys and distances only and reads each candidate's hash in one pipeline, as the Rust store always has, and checks it against the search again. A search costs one more round trip. No API or wire format changed.
  • **A ...
Read more