Skip to main content
The dsrs binary is the .dsrs toolchain: check, fmt, serve. It ships as the dsrs-cli crate; every subcommand is a plain library function (dsrs_cli::check, dsrs_cli::fmt, dsrs_cli::serve), and the binary itself is argument parsing plus process exit codes. All subcommands exit 0 on success and non-zero on failure.
The toolchain covers the whole journey of a program file: printed from your code, checked, served on another host, and embeddable back into a Rust build.

dsrs check <program>

Parses and validates a .dsrs artifact via Program::load_dsrs, running the full pipeline: lex and parse with positions, lowering, Program::validate, hash sealing. This is exactly what Interpreter::load would accept, which makes check a pre-commit gate for a human and a regeneration signal for a model loop. On success it prints one line to stdout: ok: program with the program name, its 16-hex hash, the node, signature, model, and tool counts, and the caps set when non-empty. On failure the parser’s error goes to stderr with the artifact path and a line N, column M: expected ... position, and the exit code is non-zero.

dsrs fmt <program> [--write]

Prints (or rewrites) the canonical form of an artifact. The canonical form is Program::to_dsrs, the same text that seals program_hash and that bake writes. Formatting is parse then print, never token shuffling, so an artifact that does not parse does not format.

dsrs serve <program> [flags]

Serves a .dsrs program over HTTP. Startup is fail-fast, before the port binds: parse, apply the optional overlay (named form, verified against the program’s hash and slot kinds), then Interpreter::load with the grants from --allow. Models are constructed from the artifact configs with secrets from provider environment variables; a QuickJS sandbox is added when none was supplied. Once bound, the address is printed on stderr.
Host tools cannot be served: a program declaring a host tool needs an embedding host that supplies the binding (include_program! plus your own code, or a sandboxed js tool instead). The load surfaces that refusal with a hint rather than a bare unbound-tool error.

HTTP endpoints

Error responses are always {"error": ...} with the interpreter’s own message: a non-object request body and input-surface rejections are 400, everything else (LM failures, parse failures, budget, routing) is 500.

From code to endpoint

The full workflow: print a program out of your code, check it, format it, serve it.

1. Print the program

Every #[module] exposes program(). Call to_dsrs() on it to get the canonical text, then save it:
One note before serving: a file printed from a #[module] holds a placeholder model line like model default = "unbound:default", because the real model was bound in your code. Open the file and point each model line at a real model, for example:

2. Check it

Errors come with line and column positions, so check works as a pre-commit gate:

3. Format it

A file that does not parse does not format; fix check errors first.

4. Serve it

Model API keys come from the usual provider environment variables (for example OPENAI_API_KEY). The flag table above lists --host, --port, --overlay, and --allow.

5. Call it

Add ?trace=1 to POST /run to also get a full trace of the run; the endpoint table above lists /schema, /program, and /healthz.

Capabilities: needs versus allows

The program states what it needs in its caps { ... } block. The host states what it allows with --allow. Serving refuses to start when the program asks for more than the host allows, and it prints the missing set. This is on purpose: a program can never quietly get more access than you granted. Two more refusals to know about:
  • A program with host holes cannot be served by dsrs serve, because the Rust code lives in the binary that defined it. Embed the program with include_program! instead; see Runtime.
  • Same story for host tools: the server has no implementation to bind. Sandboxed JavaScript tools serve fine, because their code travels inside the file.

Common mistakes

Serving a file with an unbound placeholder model. Edit the model lines to real models first. Forgetting --allow. If the file declares caps { net:search }, serving without --allow net:search refuses at startup. That is the design working, not a bug. Editing the file by hand and skipping dsrs check. Always check after hand edits. The parser’s error messages carry positions and are meant to be followed.

See also

  • The .dsrs file: the artifact format the toolchain operates on.
  • Program and nodes: what check validates, what fmt prints, and how bake produces new files.
  • Runtime: Interpreter, RuntimeEnv, capability grants, overlays, and embedding with include_program!.
  • Traces: the trace artifact POST /run?trace=1 returns.