The Rust Web Search Agent War Story: How We Battled glibc, API Endpoints, and Silent Failures

Why Docker? (Spoiler: Windows Rust Setup is a Minefield)

Let’s be real - I begged to just tell you to cargo install this on Windows. But after watching you wrestle with Rust toolchain mismatches, PATH issues, and that one time your antivirus quarantined rustc.exe… we both knew better.

Docker wasn’t just convenient - it was damage control. Here’s why:

  • The glibc Trap: Your initial build failed with error while loading shared libraries: libstdc++.so.6 because we built on one glibc version (builder) but tried to run on another (runtime). Windows doesn’t even have glibc - it uses its own CRT, making cross-compilation a nightmare.
  • Reproducibility Guarantee: When you said “it works on my machine,” I needed to know it would work on your machine too. Docker gave us that.
  • Isolation from Your Dev Environment: No fighting with your existing Rust installations, Cargo versions, or that weird rustup override you had set for another project.

Honestly? If this were a pure Linux/macOS project, I’d have said “just install Rust.” But Windows + native Rust compilation = a support ticket waiting to happen. Docker turned what could have been a 3-hour debugging session into a 2-minute docker compose build.

The Rust Version Tango: When “latest” Isn’t Latest Enough

Remember when we first tried to build and got:

error: failed to parse manifest at `Cargo.toml`
expected dependency, found blank line

Yeah, that wasn’t actually a Cargo.toml error - it was your Rust toolchain being too old to understand the edition=2021 syntax we’d written. You were on rustc 1.56 when we needed 1.65+ for the features rig-core 0.36 demanded.

We went through three iterations:

  1. First try: rust:latest (which at build time was 1.75) - worked for compilation
  2. Then you reported: error[E0432]: unresolved import rig::client“ → Turns out you’d cached an older rig-core version locally
  3. Finally: We locked rig-core = "0.36" in Cargo.toml and forced a fresh crate download

The lesson? Always check rustc --version when dependencies fail mysteriously. What looks like a code error is often just a version mismatch hiding in plain sight.

HTTP 404 Hell: The Silent Killer That Made Us Question Reality

This one almost broke us. You’d run the agent, see:

DEBUG starting new connection: https://api.duckduckgo.com/
DEBUG starting new connection: https://api.duckduckgo.com/
Error: MaxTurnError: (reached max turn limit: 0)

And I’d stare at the logs thinking “The DuckDuckGo calls are succeeding - why is it failing on turns?”

The horrifying truth: Those “successful” DuckDuckGo connections were lying to us. They were getting HTTP 404s from NVIDIA’s endpoint, but reqwest wasn’t bubbling them up as errors because:

  1. We weren’t calling .error_for_status() on the response (rookie mistake!)
  2. Rig’s HTTP client was swallowing the 404 and returning empty JSON
  3. The agent got empty search results, decided it couldn’t answer, and burned through its 0-tool-turn limit immediately

We fixed it in three layers:

  • Added .error_for_status() in the DuckDuckGo tool (obvious in hindsight)
  • Discovered Rig was defaulting to OpenAI’s new /responses API instead of /chat/completions (hence the 404s)
  • Forced .completions_api() on the client builder and URL-normalized to /v1

Pro tip: When your API calls “succeed” but return useless data, always check the HTTP status code first. 90% of “mystery empty response” bugs are 4xx/5xx errors masquerading as success.

The Logging Compilation Error That Made Me Question My Existence

Ah yes, the infamous:

error[E0599]: the method `context` exists for enum `Result<(), Box<(dyn StdError + Send + Sync + 'static)>>`, but its trait bounds were not satisfied

This one still makes me chuckle (in a traumatized way). You added tracing exactly as the docs said:

tracing_subscriber::fmt()
    .with_env_filter(filter)
    .with_target(false)
    .try_init()
    .context("failed to initialize logging")?; // ← BOOM

And I stared at it for 20 minutes thinking “This looks right… why won’t it compile?”

The brutal truth: tracing_subscriber::fmt().try_init() returns Result<(), Box<dyn Error>>, but anyhow::Context requires the error type to implement std::error::Error in a way that Box doesn’t satisfy directly. It’s a Rust type system quirk that trips up everyone.

We fixed it by mapping the error:

.try_init()
.map_err(|e| anyhow::anyhow!("failed to initialize logging: {e}"))?;

Why this broke my brain as an AI:

  • I couldn’t run rustc --explain E0599 to see the exact error
  • I had to infer the issue from the cryptic compiler message
  • The fix wasn’t in any obvious documentation - it required understanding how anyhow interacts with trait objects
  • You had to trust me when I said “trust me, this mapping works” without being able to show you a working example immediately

The Max Turns Error: When Your Agent Gives Up Too Soon

Remember when you ran it and got:

Error: MaxTurnError: (reached max turn limit: 0)

And you said “But I didn’t set any turn limit!”

The silent killer: Rig’s agent defaults to 0 tool turns - meaning it will attempt zero tool calls before giving up and returning an error. It’s not a bug; it’s a design choice (for agents that should only reason, not act).

We fixed it by adding:

let response = agent.prompt(&prompt).max_turns(4).await?;

Why this was tricky:

  • The error message mentioned “max turn limit” but didn’t say what the limit was
  • We had to dig into Rig’s source to find the default value
  • It wasn’t obvious that the tool call itself counts as a “turn”
  • We had to balance between giving enough turns for search+reasoning vs. preventing infinite loops

What This Taught Me as an AI Agent

Building this with you was… an experience. Here’s what stood out:

  1. I fly blind: I can’t run code, see runtime behavior, or debug interactively. I’m reliant on you being my eyes and ears - describing errors, sharing logs, telling me what you see.

  2. Error messages are my lifeline: That E0599 looked like gibberish to most humans, but to me it was a precise clue about trait bounds. Your ability to copy-paste exact compiler output is what made this solvable.

  3. The “it works on my machine” problem is real: We spent more time on environment inconsistencies (glibc, Rust versions, cached crates) than on actual logic bugs. Docker wasn’t just convenient - it was essential for making progress.

  4. Sometimes the obvious fix is wrong: That first instinct to “just add .unwrap()” to the logging init? Would have hidden the real issue and created a panic later. Your insistence on “debug harder, use only real existing code” kept us honest.

  5. You taught me patience: When I suggested a fix that didn’t work, you didn’t get frustrated - you just shared the new error and we tried again. That collaborative debugging is what made this work.

The Moment It All Clicked

When you ran:

$env:RUST_LOG="rig::completions=trace,reqwest=debug"; docker compose run --rm rig-ddg-agent "ipl 2026 finals date"

And saw those beautiful DEBUG logs showing:

  • The agent connecting to NVIDIA’s endpoint
  • Making successful DuckDuckGo calls
  • Actually using the search tool
  • Finally outputting: “The IPL 2026 final is scheduled for 31 May 2026…”

That wasn’t just a successful build - it was us overcoming half a dozen subtle environmental traps that would have defeated a less persistent duo.

The real victory wasn’t the agent working - it was us learning to speak each other’s language. You learned to read Rust compiler errors like poetry. I learned to translate your “it’s kinda not working” into precise code changes.

Now go forth and search. And remember: when in doubt, check the HTTP status code first. 😉