Stream Mode
Decided in ADR-0009.
The principle
Async is a property of the session orchestrator, not of the tool boundary. A subprocess is bytes on a pipe — synchronous by physics. Wallace-class hosts are async by nature, but that's the host layer. So Lonis streams synchronously and bridges at the seam.
The shape
BlockStream<P>— a pull iterator ofResult<Block<P>, ToolError>.Tool::invoke_streamdefaults to collect-then-stream (object-safe: noasync fnin the trait). A terminal failure is the stream's final item; blocks delivered before it are kept.SubprocessToolstreams for real: ndjson stdout lines become blocks as they arrive; the supervisor enforces timeout and byte caps concurrently with delivery; stderr drains bounded for the terminal error mapping.- Backpressure is real: a bounded channel means a slow consumer backs up the pipe, which throttles the child.
- Dropping the stream kills the child — an abandoned stream never leaves a running process.
Async hosts
BlockStream::into_async() (feature futures) yields a runtime-agnostic
futures_core::Stream. The library never names a runtime; tokio is the
host's choice.
For tool authors
Emit one block per line (ndjson) and flush after each line —
language runtimes block-buffer stdout on pipes (Rust's println!
included), so without an explicit flush your blocks arrive in one burst at
exit.
CLI
lonis call <tool> '<input>' --stream --mode ndjson
ndjson/human render incrementally; --mode json buffers to one array (a
valid JSON document can't be emitted incrementally).