Blend deterministic Rust code with LLM-powered reasoning.
Hat tip to Dave Herman for the name.
Do things deterministically that are deterministic. File discovery, iteration, and I/O happen in Rust. Summarization, analysis, and judgment happen via the LLM.
use determinishtic::Determinishtic;
use sacp_tokio::AcpAgent;
#[tokio::main]
async fn main() -> Result<(), determinishtic::Error> {
let d = Determinishtic::new(AcpAgent::zed_claude_code()).await?;
// Rust handles the deterministic parts
let files = std::fs::read_dir("./docs")?
.filter_map(|e| e.ok())
.filter(|e| e.path().extension() == Some("md".as_ref()))
.collect::<Vec<_>>();
for entry in files {
let contents = std::fs::read_to_string(entry.path())?;
// LLM handles the non-deterministic reasoning
let summary: String = d.think()
.text("Summarize in one sentence:")
.display(&contents)
.await?;
println!("{}: {}", entry.path().display(), summary);
}
Ok(())
}Add to your Cargo.toml:
[dependencies]
determinishtic = "0.1"
sacp = "11.0.0-alpha.1"
[dev-dependencies]
sacp-tokio = "11.0.0-alpha.1"
tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] }The main entry point. Wraps a connection to an LLM agent and provides the think() method.
// Connect to an agent
let d = Determinishtic::new(AcpAgent::zed_claude_code()).await?;
// Or use an existing connection (e.g., inside a proxy)
let d = Determinishtic::from_connection(cx.connection_to());A builder for composing prompts with embedded tools. Created via d.think().
let result: MyOutput = d.think()
.text("Analyze this data:")
.display(&data)
.textln("")
.text("Focus on trends and anomalies.")
.await?;The output type must implement JsonSchema and Deserialize - the LLM returns structured data by calling a return_result tool.
Register tools that the LLM can call during reasoning:
use sacp::tool_fn_mut;
let mut results = Vec::new();
let output: Summary = d.think()
.text("Process each item using the provided tool")
.tool(
"process_item",
"Process a single item and return the result",
async |input: ItemInput, _cx| {
let output = process(&input);
results.push(output.clone());
Ok(output)
},
tool_fn_mut!(),
)
.await?;Tools can capture references from the stack frame - no 'static requirement. The tool_fn_mut!() macro is required due to Rust async closure limitations.
.tool()- Register a tool and mention it in the prompt.define_tool()- Register a tool without mentioning it in the prompt
Run the summarize_docs example:
cargo run --example summarize_docs -- --agent claude-code ./docsAvailable agents: claude-code, gemini, codex
See the mdbook for detailed documentation and RFCs.
MIT OR Apache-2.0