SDK compatibility and public contracts
Published crate
rho-sdk is published on crates.io. This page documents the public contract of the published crate. For additive changes between releases, see the changelog.
Stability labels
For the published crate:
- the documented public API, snapshot schema rules, event lifecycle, cancellation/drop/shutdown behavior, capability defaults, and other behavior explicitly marked as contract are stable under Cargo SemVer
- breaking changes to that contract require a major version bump, not a minor or patch release
- consumers should depend on a major-compatible Cargo range and read the changelog when adopting new minors
- migrations from pre-stable application-private modules are covered only by the historical upgrade guide
- provider-private wire formats and upstream service behavior are never stabilized by the core crate
- terminal-event delivery on non-cooperative exits remains a documented known limitation
Internal modules, task/channel implementation details, exact allocation, delta chunk boundaries, provider-private payloads, application UI, and undocumented formatting remain implementation details.
A public item being exported does not by itself make every derived representation a persistence contract. The durable boundary is explicitly documented below.
Features and capability defaults
rho-sdk deliberately declares an empty default feature set:
rho-sdk = { version = "1", default-features = false }The crate currently has no optional Cargo features. Default, --no-default-features, and --all-features therefore select the same minimal headless surface. CI deduplicates equivalent feature graphs and tests each distinct mode once. Built-in provider transports, SQLite persistence, operating-system keychain access, web access, and coding tools remain application-owned adapters. If any moves into rho-sdk, it must use a named opt-in feature and must not grant workspace capabilities without explicit runtime configuration.
Removing or renaming a public feature, or moving an existing API behind a feature, is a breaking change. Adding an opt-in feature is additive unless it changes existing behavior.
Runtime and supported platforms
The SDK requires a host-provided Tokio runtime when it creates sessions or runs. Public provider and tool futures are Send, and the SDK does not initialize a runtime, terminal, logger, credential store, network client, or global configuration for the host.
CI compiles and tests the workspace on the current GitHub-hosted stable Rust toolchain for Linux, macOS, and Windows. The release binary matrix additionally builds Linux x86_64 GNU, macOS x86_64 and arm64, and Windows x86_64 MSVC artifacts. Other Rust targets may work, but are not part of the supported CI platform contract.
Public API inventory
The intentional SDK surface is grouped below. Application provider transports, login flows, Config, OS credential storage, the TUI, and built-in coding tools are not SDK exports.
Crate-root runtime surface
- Runtime construction:
Rho,RhoBuilder,SystemPrompt,ShutdownOutcome - Sessions and runs:
SessionOptions,Session,SessionState,UserInput,Run,RunEvent,RunOutcome,StopReason - Cancellation and reasoning:
CancellationToken,ReasoningLevel,ParseReasoningLevelError - IDs:
SessionId,RunId,ToolCallId,HostInputId,Revision,InvalidId - Host input:
HostChoice,HostQuestion,SelectionMode,HostInputRequest,HostInputResponse - Diagnostics:
DiagnosticsSnapshot,PromptSource,PromptSourceKind - Errors and secrets:
Error,ProviderError,ProviderErrorKind,Retryability,SecretString - Compaction:
CompactionPolicy,CompactionRequest,CompactionOutput,CompactionOutcome,CompactionState,CompactionTrigger,Compactor,CompactionFuture,ScriptedCompactor - Persistence:
SessionSnapshot,SessionStore,SessionStoreFuture,InMemorySessionStore, and the supported schema-version constants - Workspace and approvals:
Workspace, capability and process types, policies, approvals, authorization outcomes, and resolved workspace paths - Tool results re-exported at the root
rho_sdk::model
The provider-neutral model exports are the tool-call, content, message, model identity, provider-context, request/response/event, and usage DTOs. Their public fields and variants support provider implementations and serialized history. Adding a required field or exhaustive variant is source-breaking. Compatible serialized additions must be optional or defaulted. Provider-native context is opaque JSON scoped by exact ModelIdentity; it must not contain credentials.
Use Message::semantic() and exhaustively match SemanticMessage when classifying transcript entries or finding user-turn boundaries. The borrowed view distinguishes ToolImageSupplement from User, while preserving every existing assistant form. Matching the serialized Message::User variant alone does not identify a human submission. Tool images use that wire role only to preserve the existing exhaustive Message enum and ToolResult struct literals. The semantic view is additive and does not change snapshot serialization. Message::as_tool_image_supplement() remains available for attribution-only lookups. Neither helper authenticates history or grants authority: imported or user-controlled history can forge the supplement encoding.
NEXT_MAJOR(rho-sdk): put tool images directly on ToolResult and remove supplemental user-role image messages.
rho_sdk::provider and rho_sdk::tool
The provider extension surface includes ModelProvider, ModelRequestOptions, its explicit future and event-channel types, mid-turn steering ports (ProviderSteeringRequest, ProviderSteeringReceiver, send_turn_stream_steerable), and scripted downstream test support. The tool extension surface includes Tool, ToolRegistry, ToolInvocation, ToolContext, output/error/metadata/progress types, security declarations, and scripted downstream test support. Both extension traits are object-safe, require Send + Sync, and return Send futures.
send_turn_stream, send_turn_stream_with_options, and send_turn_stream_steerable stay as three methods in this major. NEXT_MAJOR(rho-sdk): collapse send_turn_stream, send_turn_stream_with_options and send_turn_stream_steerable into one method taking a request context that carries options and the steering port.
ToolInvocation::arguments and into_arguments intentionally support borrowed and owned argument parsing. ToolRegistry::len, is_empty, and async_tool_names intentionally allow hosts to inspect registry construction without relying on tool specs. These convenience methods are part of the planned stable public surface.
ModelEvent and ToolCall cannot grow in this major, so a provider that accepted an async tool call records it as SDK metadata on ProviderContextBlock::async_tool_call (kind rho.sdk.async_tool_call.v1) rather than a first-class event or field. That block is never replayed as provider-native context and is never counted as a handoff omission. NEXT_MAJOR(rho-sdk): replace the async-call marker block with a ModelEvent::ToolCallAccepted { id, execution: ToolExecutionMode } variant and an execution field on ToolCall.
RunEvent::ToolDetached is a sibling of ToolStarted because this major cannot add fields to existing event variants. NEXT_MAJOR(rho-sdk): fold ToolDetached into ToolStarted as an execution field.
Application compatibility shims
Private provider construction shims remain tracked for removal by issue #256. They delegate through explicit application credential sources and do not form part of the SDK API. New application code must use ProviderBuildOptions, an explicit ProviderCredentialSource, and build_sdk_provider_with_source.
Public data behavior
Public values follow these conventions:
| Family | Clone and equality | Serialization | Redaction and Debug |
|---|---|---|---|
| IDs and revision | IDs clone and compare by full string; revisions copy and compare numerically | IDs are transparent non-empty strings; revision is a transparent integer | IDs are visible in Display and Debug; do not put secrets in IDs |
| Provider-neutral history and usage | Deep clone and structural equality | Serde representation supports snapshots and historical message compatibility | Derived Debug can reveal prompts, images, tool arguments/results, summaries, usage, and opaque context |
SessionSnapshot and CompactionState | Deep clone and structural equality | Versioned JSON persistence boundary | Derived Debug reveals snapshot content; snapshots are sensitive |
| Events and outcomes | Owned, cloneable, structural equality where implemented | Not a versioned wire format unless a separate event format is explicitly introduced | Event Debug can reveal model and tool content |
| Builders, runtime, sessions, runs, tokens, senders, and trait objects | Operational handles have type-specific clone/ownership behavior and are not value records | Not serializable | Custom Debug is diagnostic only and is not a general secret scrubber |
| Workspace, policy, approvals, host input, tool metadata/output/errors | Structural equality where implemented; policies/handlers are behavior | Not a durable SDK schema | Paths, commands, URLs, diffs, questions, answers, and error text may be sensitive |
Cloning a value duplicates its sensitive data in memory. Equality means structural/value equality, not semantic equivalence, authorization equivalence, constant-time secret comparison, or provider compatibility. Debug output is never a safe logging boundary unless the specific type and all nested values have passed the redaction audit.
Public extensible event, policy, capability, operation, error-category, state, and stop-reason enums are non-exhaustive where compatible growth is expected. Downstream code must retain wildcard branches and must not depend on the current variant count.
Serialization contract
Stable persistence boundary
SessionSnapshot JSON is the intended versioned 1.0 persistence boundary. Schema version 2 adds an optional opaque prompt-cache key. Schema version 1 remains supported and migrates in memory; serialization always emits version 2. Deserialization rejects malformed, older-than-supported, and newer schemas. New and imported snapshots sanitize raw aborted-assistant reasoning. Hosts can apply the same rule with SessionSnapshot::sanitize_history.
Nested provider-neutral message history preserves Rho's historical externally tagged enum representation, including legacy assistant messages, enriched assistants, and aborted assistants. Opaque provider context is data, not a public provider wire contract. Replay requires an exact identity unless a known backend format permits cross-model replay.
The following are not promised as durable formats:
- Rust
Debugoutput RunEventserialization, because events do not currently implement a separately versioned wire protocol- diagnostics snapshots
- builder or runtime configuration
- host-input channels, approval requests in flight, cancellation state, and active runs
- provider-private HTTP/WebSocket payloads
- the Rho application's SQLite schema or
~/.rho/config.toml
A compatible snapshot evolution must either remain readable under the same schema with defaults for additive fields or introduce an explicit new schema and migration. It must not silently reinterpret provider context, restore raw reasoning, add ambient authority, or turn metadata into executable configuration. Removing a readable schema or changing the meaning of an existing field is a breaking persistence change and requires a major SDK release after 1.0.
Application configuration compatibility
The Rho application's ~/.rho/config.toml is owned by rho-coding-agent, not rho-sdk. The application currently reads its grouped format and still accepts the previous flat format, rewriting it into groups on a later save. SDK construction never discovers or writes this file.
Application values can affect an SDK-backed run when the application explicitly maps them:
| Application value | Runtime effect | Stored in SessionSnapshot |
|---|---|---|
| provider/model/auth | Provider construction and exact model identity | Provider identity and per-message provenance/context are stored; credentials and auth mode are not |
| reasoning | Request policy for future turns | No |
| compaction thresholds/targets | Application compactor and policy construction | Compaction results/state are stored; policy settings are not |
| system prompt and discovered instructions | Prompt construction | Prompt content can appear as a system history message; discovery settings/sources are not stored |
| tools, output limits, web search, RTK behavior | Application tool registry and adapters | Tool calls/results may appear in history; authority and limits are not stored |
| display, keybindings, internal-agent models, update checks | Application/UI behavior | No |
| credential-store entries and environment variables | Provider/tool adapter construction | Never |
Changing application configuration does not mutate an existing snapshot. Restoring a snapshot under different runtime options applies the newly supplied runtime for future work while preserving snapshot history. Hosts must validate and disclose that combination, especially provider handoff and newly granted tools.
Behavioral contracts
The following pages are normative for the stable 1.0 behavioral contract:
- event ordering, bounded buffering, retry, cancellation, drop, and shutdown
- history commits, compaction, snapshot atomicity, failure, and restore
- tools, workspace paths, process/network limits, and approval behavior
- security defaults and host obligations
Where code and documentation disagree, treat it as a bug to resolve in the next release, not permission to select the less secure behavior. See known limitations for remaining gaps.
Deprecation policy
After the 1.0 cutover:
- Public API or behavior planned for removal is marked deprecated in Rust and documented in release notes with a replacement and rationale.
- Deprecated stable APIs remain available through the rest of the current major release whenever safety and security permit.
- Removal or a breaking signature, snapshot, event, capability-default, or documented semantic change requires the next major release.
- Additive non-exhaustive variants, new optional methods with defaults, and additive opt-in features may ship in minor releases.
- A security or soundness defect may require faster restriction or removal. The release must clearly identify the exception, impact, migration, and any advisory.
- Provider or feature deprecations caused by upstream shutdown are announced as early as practical, but an upstream service cannot be kept operational by SemVer.
- Compatibility shims must have an owner, replacement, and tracked removal target. They are not permanent hidden contracts.
Minimum supported Rust version
Embedders must build with a Rust toolchain at least as new as the package.rust-version declared by the published rho-sdk crate on crates.io. That field is the source of truth for hosts.
Contributor-facing MSRV values for both rho-sdk and rho-coding-agent, and the rules for raising them, live in development. CI keeps those development docs synchronized with Cargo metadata.
Semantic-version and downstream checks
Public Rust items, documented event ordering and cancellation behavior, feature names, and versioned persisted formats are compatibility contracts. CI runs cargo-semver-checks against the pull-request base when that revision contains rho-sdk. Breaking changes after the first stable major require a major version bump and must not ship in a minor or patch release.
The excluded workspace in fixtures/downstream has its own committed lockfile and crates with exactly one direct dependency, rho-sdk. CI compiles those representative integrations from their reproducible dependency graph. Because the fixture is outside the Cargo workspace, the release workflow regenerates its lockfile after release-please changes the SDK version. It also checks all SDK feature modes, workspace tests, Clippy, rustdoc tests, architecture rules, package contents, and publish dry runs. Run the local compatibility checks with:
python3 scripts/check_sdk_compatibility.py --test-features --test-downstream