docs(rust): add config reference + async-openai integration to close conversion gap#659
Merged
Merged
Conversation
…ersion friction Investigation of the cycles-client-rust 13:1 clone-to-install ratio (907 clones, 67 installs over 6 weeks). Surface quality is fine: crates.io description is clean, docs.rs renders at 99.77%, README has badges + cargo block above the quickstart, /examples has 5 files. The crate itself is in good shape. Two real gaps identified in the cycles-docs surface: 1. **Missing Rust client config reference.** Python, TypeScript, and Spring Boot all have `configuration/*-client-configuration-reference.md` docs. Rust did not. An evaluator hitting the docs to look up retry tuning, custom reqwest::Client wiring, the blocking variant, or env var prefix customization had nowhere to land. 2. **No real LLM-integration example.** The Rust quickstart and Rust integration guide both use `call_llm()` / `openai_call()` / `stream_llm_response()` as placeholders. An evaluator who copied the sample code had to invent the async-openai wiring themselves — including streaming token capture, OpenAI vs Cycles error mapping, and cap-to-max_tokens application. That's the friction point that most plausibly explains the ratio. Changes: - **NEW** `configuration/rust-client-configuration-reference.md` — CyclesConfig fields, env vars (default and custom prefix), builder API method reference, custom reqwest::Client guidance, blocking client variant behind feature flag, retry tuning examples. Verified against cycles-client-rust src/config.rs. - **NEW** `how-to/integrating-cycles-with-async-openai.md` — concrete composition of `runcycles` with `async-openai`: basic chat completion with token extraction from `response.usage`, cap application to max_tokens, streaming via ReservationGuard with include_usage handling, OpenAI vs Cycles error separation, token-to-microcents helper, brief Anthropic variant via anthropic-sdk-rs. Gotchas section flags the five common stumbles (missing usage, None content, key leakage in metadata, runtime mismatch). - **MOD** `quickstart/getting-started-with-the-rust-client.md` — Configuration section now links to the new config reference; Next Steps re-ordered to surface both new docs prominently. - **MOD** `how-to/integrating-cycles-with-rust.md` — callout near the top pointing readers who want a real LLM example to the new async-openai doc. - **MOD** `.vitepress/config.ts` — added Rust client config to the sidebar Configuration section; added async-openai entry under the Rust how-to subsection. - **MOD** `how-to/index.md` — added "OpenAI (Rust / async-openai)" to the LLM providers row (previously listed only Python and TypeScript for OpenAI specifically). Glossary linker ran on both new docs; added 7 contextual links across them. Out of scope (separate repo, can't edit from here): - The cycles-client-rust /examples directory has 5 files but no LLM integration example. Adding examples/async_openai_completion.rs and examples/axum_middleware.rs would close a parallel gap on the repo side. Worth filing on cycles-client-rust as a follow-up issue. - A README quickstart that opens with the async-openai example rather than the abstract `call_llm()` would compound this; same parallel gap on the repo side.
Codex flagged substantial code-accuracy errors that would have produced
non-compiling examples. Verified each fix against the live cycles-client-rust
source (src/config.rs, src/blocking.rs, src/lifecycle.rs) and async-openai
0.30.x docs.
Apply/skip tally: 12 applied, 0 pushed back.
Config reference fixes:
- Retry section: I described the retry engine as if it ships today.
In runcycles 0.2.x, retry.rs is dead-code and ReservationGuard::commit
does not retry. Reframed the section as "fields present on the struct
for a future engine; no runtime effect in 0.2.x." Removed the
"Disabling retry" and "Aggressive retry" examples that used builder
methods (retry_initial_delay/multiplier/max_delay) that do not exist
on CyclesClientBuilder.
- Programmatic CyclesConfig example: removed `..Default::default()`
(CyclesConfig does not implement Default). Spell out all fields, or
use the builder.
- Subject defaults: my "Resolution order" section claimed config-level
subject defaults are auto-applied to request subjects. Verified
against lifecycle.rs: with_cycles uses WithCyclesConfig.subject (or
Subject::default()), NOT the CyclesConfig subject. Rewrote the
section to clarify that subject fields are stored on the config but
not auto-applied; recommended building one Subject and reusing it.
- Blocking client: I said it "mirrors the async client's surface."
Verified against blocking.rs: only low-level protocol methods are
exposed (create_reservation, commit_reservation, release_reservation,
extend_reservation, decide, create_event, list_reservations,
get_reservation, get_balances). No with_cycles, no ReservationGuard.
Updated the section to list what is actually available.
- Blocking snippet: added missing `?` after `BlockingCyclesClient::new(...)`
(it returns `Result<Self, Error>`) and imported `BalanceParams`.
- Builder method reference table: removed retry_initial_delay,
retry_multiplier, retry_max_delay (not on builder); noted these are
reachable only by constructing CyclesConfig directly.
- Removed "Unlike most clients in the corpus" internal-review tone.
async-openai doc fixes:
- "dominant Rust client" → "widely used".
- "only the type names change" → reframed as not-portable (the
provider-side type paths differ in meaningful ways).
- Streaming code: I described needing
`stream_options.include_usage = true` but never set it on the request.
Added `.stream_options(ChatCompletionStreamOptions { include_usage: true })`
to the request builder, imported the type.
- Error handling section: completely rewrote. with_cycles wraps closure
errors as `Error::Validation(format!("guarded function failed: {e}"))`
— the typed OpenAIError is stringified and lost. Switched the
error-aware pattern to ReservationGuard so the typed OpenAIError
reaches the caller. Added explicit guidance on when to use
with_cycles vs ReservationGuard.
- Anthropic section: demoted from concrete code (using crate API names
I had not verified) to a brief "Other Rust LLM clients" note that
describes what to adapt without making specific API claims about
anthropic-sdk-rust.
- Streaming usage setup moved INTO the code block (was only mentioned
in the gotchas section).
- Glossary anchor `#tokens` → `#token` (the glossary heading is
singular).
Other:
- how-to/integrating-cycles-with-rust.md:283: updated stale link from
/quickstart/getting-started-with-the-rust-client#configuration to
/configuration/rust-client-configuration-reference.
Sources verified against cycles-client-rust HEAD (src/config.rs,
src/blocking.rs, src/lifecycle.rs) and async-openai 0.30.1 docs.rs.
Apply/skip tally: 5 applied, 0 pushed back.
Applied:
- Config reference line 29: "applied to every request unless overridden"
contradicted the corrected section at :153-174 and lifecycle.rs. Replaced
with explicit "stored on the config but not auto-applied to per-request
subjects in 0.2.x" + pointer to the detailed section.
- Config reference retry framing: narrowed "Transport or Api { 5xx }" to
"transient commit failures (network, 5xx, timeouts) surface as Transport
or Api{..}" since 4xx can also surface; softened "stable when the engine
ships" to "documented here because they are already public" (avoids any
implied semver promise about the future engine).
- Config reference blocking method list: added
`create_reservation_with_metadata` (it IS public on BlockingCyclesClient
per src/blocking.rs); added `config()` accessor, noted it's not a
protocol method.
- async-openai error-handling pattern: `guard.release(...)` takes
`impl Into<String>`, not a `ReleaseRequest`. Replaced
`guard.release(ReleaseRequest::new(Some(...)))` with
`guard.release(format!(...))`. Removed the now-unused `ReleaseRequest`
import.
- async-openai line 366: "elsewhere in the corpus" → "elsewhere in the
docs" (internal-review tone).
Codex verified against cycles-client-rust at runcycles 0.2.4 plus
async-openai 0.30.1 docs.rs.
This was referenced May 16, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Investigation of the cycles-client-rust 13:1 clone-to-install ratio (907 clones, 67 installs over 6 weeks). For comparison, the TypeScript client runs 1.17:1 and Python runs 0.37:1. Rust is an order of magnitude off — even allowing for Rust ecosystem culture (more source-inspection than npm/pip), 67 installs after 6 weeks is low for a crate with otherwise solid presentation.
The crate itself is in good shape: clean crates.io page with proper description and keywords, docs.rs renders at 99.77% coverage, the GitHub README has badges + cargo block + 3-import quickstart, and /examples has 5 files. No obvious surface defect.
The friction is in the docs surface in this repo. Two concrete gaps:
Gap 1: Missing Rust client configuration reference
Python, TypeScript, and Spring Boot all have a `configuration/*-client-configuration-reference.md` doc. Rust did not. An evaluator looking up retry tuning, custom `reqwest::Client` injection, the blocking variant, or env var prefix customization had nowhere to land. The sidebar's Configuration section literally listed three clients, not four.
Gap 2: No real LLM-integration example
The Rust quickstart and integration guide both use `call_llm()` / `openai_call()` / `stream_llm_response()` as placeholders. An evaluator who copied the sample code had to invent the async-openai wiring themselves — including streaming token capture (the `stream_options.include_usage` gotcha), OpenAI-vs-Cycles error mapping, and cap-to-max_tokens application.
This is the most plausible explanation for the conversion gap: a Rust developer who lands on the quickstart, sees `call_llm()`, and bounces without ever running `cargo add runcycles`.
What's in this PR
Two new docs:
`configuration/rust-client-configuration-reference.md` — full `CyclesConfig` field reference (verified against cycles-client-rust src/config.rs), env vars including the Rust-specific custom prefix support, builder API method-by-method, custom `reqwest::Client` injection guidance, blocking client variant behind feature flag, retry tuning examples.
`how-to/integrating-cycles-with-async-openai.md` — concrete composition of runcycles + async-openai: basic chat completion with token extraction from `response.usage`, cap application to `max_tokens`, streaming via `ReservationGuard` (with explicit handling of OpenAI's `stream_options.include_usage` requirement), OpenAI-vs-Cycles error separation, token-to-microcents helper for USD-denominated budgets, brief Anthropic variant via `anthropic-sdk-rs`. Gotchas section flags the five common stumbles.
Discoverability:
Glossary linker: ran on both new docs; added 7 contextual links.
What this PR doesn't fix (separate repo)
The cycles-client-rust repo itself has a parallel gap. Worth filing as a follow-up issue against that repo:
I cannot edit that repo from this PR — flagging it here so it's tracked.
Expected impact
The 907 → 67 conversion is what's measurable. If even a modest fraction of evaluators were bouncing because of the missing async-openai example or the missing config reference, this should narrow the ratio over the coming weeks. Not predicting a specific lift — Rust ecosystem culture (source-inspection over install) means the ratio will stay above 1:1 even with these fixes. But the trajectory should be observable in install-counts cache over the next 2–4 weeks.
Test plan