.dsrs program file.
Host tools
A host tool is a normal Rust function marked#[tool]. The body is the implementation:
caps("...") is optional and declares what the tool is allowed to touch, for example net:search or fs:read; the host checks these labels before it runs a program, so nothing gets network or file access silently. See Capabilities for the full model.
The function stays plain Rust; you can still call shout("hi".to_string()) yourself in tests. Tools can be async, and they can fail. A fallible tool returns Result<T, E> where E: Display:
#[tool] generates the original function unchanged, plus a module carrying the tool’s signature, a rig tool wrapper for host binding, and __dsrs_tool() metadata consumed by #[agent] and #[module]. The rules: no generics, no self, plain identifier parameters, at least one parameter, an explicit return type. The attribute accepts only caps("...", ...); anything else is a compile error.
Declaring an agent
An agent is the tool-loop sibling of#[predict]: a bodyless function whose doc comment is the instruction, whose parameters are the input fields, and whose return type is a single output field named after the function.
#[predict]: no async, no generics, no self, plain identifier parameters, at least one parameter, an explicit return type.
Options
Stop tools
Sometimes the model should end the loop with one explicit call instead of writing a final message. Declare a tool whose inputs match the agent’s output fields and list it instop_tools(...):
research here). A stop tool missing from tools(...) is a compile error: “stop tool name is not in tools(…)”.
Standalone or inside a module
#[agent] generates a standalone async fn returning Result<Predicted<research::SigOutput>, PredictError>. Called directly, it runs the static-lane tool loop (Predict with ToolLoopMode::Auto). Inside a #[module] body, the same call lowers to a first-class AgentLoop node in the program graph, and the loop options (max_turns, budget, context) apply only to this lowered form.
caps(...) ceiling must cover what its tools declare; caps("demo:shout") on the module matches caps("demo:shout") on the tool, and a tool whose needs exceed the ceiling is a load error. Use the standalone form for a quick agent call, and the module form when the agent is one step in a larger pipeline.
Sandboxed tools
A sandboxed tool is not Rust. It is a small piece of JavaScript stored inside the.dsrs text file itself, executed in a sandbox, so it cannot reach the network or the disk unless the file declares a capability and the host grants it. In a .dsrs file it looks like this:
Related surfaces
ReAct<S> is the struct-lane equivalent of #[agent]: a thought, action, observation loop over a set of tools that extracts a typed answer, built and configured as a Rust struct rather than declared as a bodyless function. See Modules.
Code Mode is the many-tools-to-one-script alternative. Instead of advertising N tool schemas and paying one round trip per call, the model sees a single run_js meta-tool whose description lists your tools as a JavaScript API; it writes one script that calls them as plain functions and returns one value. See Code Mode.
Common mistakes
Forgetting the body on a host tool.#[tool] needs a real function with a body. A bodyless function is a step, not a tool.
A stop tool with the wrong input names. The stop tool’s arguments become the agent’s output. If the names do not match the agent’s output fields, the output cannot be filled in.
Skipping the doc comment. The doc comment is the tool’s description. It is the main thing the model uses to pick a tool, so write it for the model, not for other programmers.
See also
- Capabilities for tool needs, program ceilings, and host grants
- Code Mode for presenting many tools as one script surface
- Modules for
ReAct<S>and the other struct-lane strategies - The module macro for the body rules that lower agent calls
- The .dsrs file for sandboxed tool syntax in the artifact
