Prompts are typed contracts
Every LM interaction has the same shape: inputs go in, outputs come out, and an instruction says what to do. DSRs takes that literally. You declare the interaction as a Rust struct with#[input] and #[output] fields and a doc-comment instruction, and the library renders the prompt, calls the model, and parses the response back into your types.
QAOutput plugs into anything that accepts that shape. The output type is itself part of the prompt: an enum constrains the model to its variants, an Option makes absence a legal answer, a constraint attaches a rule the value must satisfy. And the parsing layer absorbs the mess models actually produce, so a bad response is a typed error rather than a corrupted string. See Signatures.
The contract is stateless. The thing that executes it, a predictor, carries the changeable parts: few-shot demos, instruction overrides, model choice, tools. This split matters later: it is exactly the surface optimizers edit.
Programs are data
A pipeline in DSRs is not only code that runs. It can also be held as data: aProgram value with a canonical text form, the .dsrs file.
The data form is what unlocks the rest of the library. A text artifact can be diffed in review and hashed for identity. A server can load and run it without your source (dsrs serve). An optimizer can address every instruction and demo set inside it by name and try candidate values as overlays, without mutating anything. A build can embed one back into a binary with include_program!, validated at compile time.
The artifact is produced at build time, not recovered at runtime. The #[module] macro reads your function once and emits two projections of it: the runnable function and the Program. Because both come from a single read of a single source, the artifact cannot drift from the code. DSRs deliberately does not reconstruct structure by observing a run: a recording shows only the path that executed, so branches not taken and parallelism are invisible to it. Structure comes from source; recordings are for replay.
Three authoring lanes
There are three ways to write a pipeline, differing in how much of it the system can see:
The struct lane is ordinary Rust: a struct with predictor fields and a
forward method. Maximum control; the method body is compiled code the system can run but not read. The fx lane is for scripts and experiments: predictors as named function calls, tunable by call-site name. The #[module] lane is the recommended one for pipelines: a plain async function whose body is lowered to a Program at build time.
The bottom rows of the table are constraints, not missing features. A server cannot load a function body that exists only as machine code, and an optimizer cannot restructure steps it cannot see. When part of a #[module] body is plain Rust that cannot be expressed as data, it is kept as a typed hole: named, typed at its boundary, opaque inside.
The rest of the system
Three more mechanisms complete the picture:- Capabilities gate what programs can touch. The program declares its needs in the artifact; the host declares its grants; loading fails on any mismatch, before anything runs.
- Traces record every run. Strict replay reproduces a recorded run with zero live calls, which makes model-dependent tests free and deterministic. Until-divergence replay re-runs only the steps a change actually affects.
- Evaluation and optimizers close the loop: a metric scores outputs (optionally with textual feedback), and optimizers search instruction and demo candidates against it.
