Rig v0.39.0 released! #1924
gold-silver-copper
announced in
Announcements
Replies: 1 comment
|
Awesome work guys 🎉 🎉 |
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Rig v0.39.0 released!
Another wonderful release is here, consisting of over 20 newly merged PRs! THANK YOU to all our contributors <3 This one is special: the agent loop just got a brand-new heart, and we cannot wait to show you what it unlocks.
A Unified, Sans-IO Agent Loop
The headline of 0.39 is a ground-up rearchitecture of how Rig agents actually think: a sans-IO
AgentRunstate machine (#1899). For a long time Rig carried two separate agent prompt loops — one for non-streamingPromptRequest::send()and one for streamingStreamingPromptRequest::send()— and they had quietly drifted apart. Turn counting, tool-call validation, invalid-tool-call recovery, retry budgets, chat-history threading, usage aggregation, final-response construction: each loop reimplemented all of it, which meant the same prompt could behave subtly differently depending on whether you streamed it.We pulled every one of those turn-level decisions out into a single explicit, steppable, I/O-free state machine living in
agent::run::AgentRun. Both loops are now thin drivers over that shared core — the non-streaming driver dropped from ~500 lines to ~370, and the streaming driver from 1,780 to 1,095. A driver just callsnext_step(), gets back anAgentRunStep, performs the actual IO, and feeds the result back viamodel_response(..)/tool_results(..). The state machine owns all the hard semantics, while async hooks, conversation memory, and tracing spans stay driver-side at the transition points.Why should you care? Two big reasons. First, streaming and non-streaming runs now make identical turn-level decisions, so your agent behaves the same no matter which API you reach for. We even resolved three previously-undocumented divergences between the old loops and fixed a streaming double-usage-counting bug along the way.
Second, because the core does no IO and is fully
Serialize/Deserialize, you can now pause a run mid-flight, persist it, and resume it in a different process. Imagine an agent that issues some tool calls, then you serialize the entire run to JSON while those tool calls await human approval, drop it, and pick it right back up later (even on another machine) from that JSON. The newexamples/agent_run_stepping.rsdoes exactly this. Advanced users can also hand-drive the machine for fully custom control flow, including streaming via the turn assembler. And maintainers finally get one place to fix bugs and add features instead of two drifting loops.Real world users of Rig have already taken advantage of
AgentRunin order to better manage their agent loop, and we plan on making it even more powerful by merging our prompt hook system into it, allowing custom hooks for every single step in the agentic loop.Determinism & Tooling
Tool registration got a serious reliability pass in #1913.
ToolSetused to iterate inHashMaporder, which meant thetoolsarray sent to the provider was reordered nondeterministically across processes — quietly defeating provider prompt caching and making agent behavior hard to reproduce.ToolSetnow tracks and iterates in registration order everywhere (tool definitions, documents, embedding schemas), so the tool list presented to the model is identical on every run.The same PR makes registration duplicate-safe. Registering two tools under the same name previously emitted duplicate function declarations that providers reject with an HTTP 400 mid-run. Now it's last-wins-with-a-tracing-warning (the later tool replaces the earlier one in its original slot), turning a runtime crash into a predictable, working outcome. We also corrected the
MaxTurnsErrorDisplay string to be more accurate.ToolSet's internalHashMap+ parallel orderVec(which had to be hand-kept-in-sync) was replaced with a single insertion-orderedIndexMap. No public API change, no new crate in the build graph (indexmap was already a transitive dep), still wasm-friendly — just less code and a stronger invariant.MCP Reliability
If you wire agents to MCP servers, #1921 is a big one. An MCP tool call that never gets a response — for example, when an HTTP streaming transport silently orphans an in-flight request during transparent session re-initialization — used to hang the entire agent loop indefinitely, with no error and no recovery. Brutal to debug.
Now MCP/rmcp tool calls are bounded out of the box with a sensible
DEFAULT_MCP_TOOL_TIMEOUTof 300 seconds, applied automatically at every entry point (AgentBuilder::rmcp_tool/rmcp_tools,McpClientHandler::new,ToolServer::rmcp_tool). On elapse, the call resolves to a recoverable tool error the model can react to, instead of hanging forever. You can tune it per call via the newwith_timeoutAPIs — pass aDurationfor a tighter (or looser) bound, orNoneto keep a legitimately long-running tool unbounded.Provider & Model Support
Reranking arrives as a first-class, provider-agnostic capability in #1917. RAG pipelines lean on it heavily: a fast, cheap retriever pulls a broad candidate set, then a reranker reorders by true relevance so the most useful chunks land at the top of the context window. Rig now ships a new
rerankmodule (theRerankModeltrait,RerankResponse,RerankResult,RerankError) plus aRerankingClienttrait, with the first concrete implementation backed by VoyageAI. Because the trait is provider-agnostic, future providers slot in behind the same API. Thank you @sergiomeneses!Gemini also picked up image generation in #1889 with the popular
gemini-2.5-flash-imagemodel (a.k.a. Nano Banana).Correctness Fixes
A cluster of subtle-but-important ordering fixes landed this cycle. #1898 centralizes how streaming assistant turns are assembled into history, enforcing a single canonical replay order of Reasoning -> Text -> ToolCall(s) via a new
ordered_streaming_assistant_contenthelper. This matters a lot if you stream from OpenAI's Responses API with reasoning models that also call tools: Responses expects prior reasoning to precede assistant output, and the old path could persist text before reasoning, causing replayed multi-turn histories to be rejected. Now they replay cleanly.#1893 fixes context document ordering by no longer mutating chat history during request build. Documents are kept as request-local data on
CompletionRequest, and a newchat_history_with_documents()derives a provider-ready history at the provider boundary — inserting your documents right after the leading system messages and before replayed turns and the current prompt.Huge thanks to @ imV4l on discord for reporting the two issues above, as well as testing the fixes.
#1903 squashes a classic papercut: if you configured a custom
base_urlwith a trailing slash, Rig used to build request URLs with a doubled//, which some gateways and proxies reject or misroute. Nowhttps://hostandhttps://host/both produce the same correct URL — self-hosted and custom endpoints just work. Thank you @eriktews!Finally, #1920 makes the SQLite vector store scale past sqlite-vec's hard
k <= 4096KNN cap. Any store with more than ~4096 embeddings (easy to hit with chunked documents) used to error with "k value in knn query too large" even on a tinytop_n(10). Now, when the required candidate count exceeds the cap, the query automatically falls back to a brute-force scan using the scalarvec_distance_*functions — which returns the exact same ranked results (vec0's KNN is itself an exact linear scan in 0.1.x), just without the SIMD fast path. The fast path is preserved for counts within the cap, and a tighter candidate bound keeps more queries on it.Lockstep Versioning
Rig now has one version number for the whole workspace! No more guessing which version of
rig-sqliteworks with the current version ofrig-core. That means you can pin everything to the same version and trust the crates were built and tested against each other. We first aligned the crates onto this shared version line back in 0.38.1, but 0.39.0 is the first minor bump where they all step forward in unison.Final Notes
A round of smaller-but-appreciated improvements rounded out the release: we de-flaked the tracing span tests and a DeepSeek
permission_controlrace (#1915), pointed the ecosystem link at awesome-rig (#1895), and fixed a possessive-pronoun typo inCONTRIBUTING.md(#1865, thank you @abhicris!).Huge thank you to everyone who made 0.39 happen:
We build Rig, Rig builds us!
Full changelog: #1888
All reactions