490de0cd17 fix(chat): drop "local provider" misdirect from empty-response copy + log empty 2xx streams (#3335) (#3415)
## Summary

- Replace the misleading **"Try a different model or check your local provider in Settings → AI → LLM"** copy on the empty-response classifier — that sent **Managed-route users toward a remedy that doesn't exist for them**, because there is no local provider on the Managed route. The new copy explicitly names the credits / billing remedy alongside the model / provider-config remedies (chat surface), and a tighter ≤120-char form for the cron notification drawer.
- Add a forensic `warn`-level log in `stream_native_chat` for the exact upstream shape that triggers `AgentError::EmptyProviderResponse` — a streaming chat call that returns **HTTP 2xx with zero text, zero thinking, and zero tool calls**. Captures `elapsed_ms`, `sse_chunks_parsed`, `raw_bytes_received`, `has_usage`, `has_openhuman_meta` so production data can disambiguate the three plausible upstream root causes (credit exhaustion served as 200 + empty body, upstream provider stall, real degenerate model output).
- Strictly additive — no behavioural change to request paths, no new Sentry suppression. The diagnostic log fires only on the empty-2xx case (otherwise the existing aggregated log carries the same fields at `info`).

## Problem

Issue #3335 (`iamrhn`): on Managed settings, every chat turn surfaces:

> *"The model returned an empty response. Try a different model or check your local provider in Settings → AI → LLM."*

The "local provider" remedy is nonsensical for Managed users. The Sentry data confirms this is real and broad:

- **TAURI-RUST-4JX**: 2,633 events, 55 distinct clients in 14 days. Releases include current `0.57.13` (PR #2790's Sentry suppression silenced the events but the underlying user-visible bug remains).
- Latest event breadcrumbs (Managed route, `reasoning-v1`, web channel):

  ```
  [provider:OpenHuman] outbound chat/completions -> https://api.tinyhumans.ai/openai/v1/chat/completions
  [stream]   OpenHuman POST … (stream=true, tools=21)
  …~7 s gap, no [stream] error breadcrumb, no [llm_provider] api_error breadcrumb…
  [agent]    provider responded — parsed tool_calls=0 text_chars=0
  [agent_loop] provider returned an empty final response (i=1, no text, no tool calls) — surfacing as error
  ```

  HTTP status was 2xx (no `streaming API error` line), so none of the existing classifiers (`is_budget_exhausted_http_400`, `is_provider_access_policy_denied_http_403`, …) at `compatible.rs:1028-1060` fire. The streaming aggregator builds a `ChatResponse` with `content: None`, the agent loop returns `AgentError::EmptyProviderResponse`, and the chat surface renders the wrong-remediation copy.

This is exactly the shape #3386 (team-filed) identifies as path **(b)**: the OpenHuman managed backend returns 200 + empty SSE under credit exhaustion / upstream stall instead of a typed 400 + `Insufficient budget`. The deeper fix lives in `tinyhumansai/backend`; this PR addresses the **two client-side wins that don't require the server change**.

## Solution

**(1) Copy fix — chat surface (`web_errors.rs::classify_inference_error` empty_response arm).** Replace the `"Try a different model or check your local provider"` text with a three-remedy form that's accurate for **all** providers: *"This usually means your inference credits are exhausted (Settings → Billing), the upstream model is temporarily unhealthy, or your provider configuration is rejecting the request (Settings → AI → LLM). Try one of those, or pick a different model."* New regression test `classify_inference_error_empty_response_copy_names_billing_remedy_and_drops_local_provider_misdirect` locks in: contains `Settings → Billing`, contains `Settings → AI → LLM`, contains `different model`, does NOT contain `local provider`, and `provider` stays `None`. Existing test `classify_inference_error_empty_response_is_actionable_and_retryable` continues to pass.

**(2) Copy fix — cron drawer (`cron/scheduler.rs::agent_error_to_user_message`).** Shorter form for the drawer's ≤120-char convention: *"Empty model response. Out of credits (Settings → Billing) or try a different model in Settings → AI → LLM."* New regression test `agent_error_to_user_message_classifies_empty_provider_response_for_3335` mirrors the chat-side assertions; `EmptyProviderResponse` is added to the existing `agent_error_to_user_message_canned_strings_are_short` variants list to lock in the ≤120-char contract (was previously absent — fit incidentally, but nothing enforced it).

**(3) Diagnostic log — `compatible.rs::stream_native_chat`.** Right after the existing aggregated `info` log, emit a `warn` when `text_accum.is_empty() && thinking_accum.is_empty() && tool_call_count == 0`:

```
[stream] {name} empty 2xx stream — model={..} elapsed_ms={..} sse_chunks={..} raw_bytes={..} has_usage={..} has_openhuman_meta={..}
```

`stream_started_at: Instant`, `sse_chunks_parsed: usize`, and `raw_bytes_received: usize` are added as append-only counters (never read by the request path). The signal disambiguates:

- `elapsed_ms` small + `sse_chunks=0` + `raw_bytes` small → backend closed the SSE near-immediately (credit reject or auth)
- `elapsed_ms` large + `sse_chunks=0` + `raw_bytes=0` → upstream stall / timeout
- `sse_chunks>0` + `has_usage=true` + accumulators empty → backend streamed metadata-only chunks (tokens counted, no content delivered)

**(4) Deliberately deferred — the in-session budget correlator from #3386.** `EmptyProviderResponse`'s flattened Display carries no `" API error"` infix for `extract_provider_name` to anchor on, so the classifier here cannot tell which provider was used without plumbing the typed `provider: Option<String>` through the AgentError. That plumbing affects ~10 string-match test/observability sites and balloons the scope. The diagnostic log is the cheapest path to the production data that decides whether the plumbing is worth doing — once we have evidence on which path is dominant (200+empty under credit exhaustion vs. upstream stall vs. true degenerate output), the next iteration of the empty-response arm can be provider-aware.

## Submission Checklist

> If a section does not apply to this change, mark the item as `N/A` with a one-line reason. Do not delete items.

- [x] Tests added or updated (happy path + at least one failure / edge case) per [Testing Strategy](../gitbooks/developing/testing-strategy.md#failure-path-requirement) — three new tests: `classify_inference_error_empty_response_copy_names_billing_remedy_and_drops_local_provider_misdirect` (web chat copy), `agent_error_to_user_message_classifies_empty_provider_response_for_3335` (cron drawer copy), plus `EmptyProviderResponse` added to `agent_error_to_user_message_canned_strings_are_short` variants (locks in the ≤120-char contract that was previously unenforced).
- [x] **Diff coverage ≥ 80%** — every new/changed line in `web_errors.rs` and `cron/scheduler.rs` is exercised by the three new regression tests; the `compatible.rs` diagnostic log is observability-only (no behavioural branching) and covered by the existing streaming-chat test surface that drives it. Run `pnpm test:rust` locally to verify.
- [x] Coverage matrix updated — `N/A: behaviour-only change` (error-message copy + observability log, no new feature ID).
- [x] All affected feature IDs from the matrix are listed in the PR description under `## Related` — `N/A: behaviour-only change, no matrix rows touched`.
- [x] No new external network dependencies introduced (mock backend used per [Testing Strategy](../gitbooks/developing/testing-strategy.md#mock-policy)) — `N/A: tests are pure unit-level over the classifier and the AgentError variant; no provider HTTP exercised`.
- [x] Manual smoke checklist updated if this touches release-cut surfaces ([`docs/RELEASE-MANUAL-SMOKE.md`](../docs/RELEASE-MANUAL-SMOKE.md)) — `N/A: error-copy + log only, no UI flow or release-cut surface affected`.
- [x] Linked issue closed via `Closes #NNN` in the `## Related` section.

## Impact

- **Desktop (chat surface)**: Managed-route users hitting empty-response failures now see an actionable, non-misleading message that names `Settings → Billing` as the most likely fix (per Sentry / #3386 evidence) without losing the existing `Settings → AI → LLM` and model-switch remedies. Self-hosted users see the same three-remedy framing — strictly more accurate than the prior "local provider" claim for users on cloud providers.
- **Desktop (cron notifications)**: Same behavioural change, shorter copy to fit the ≤120-char drawer-render budget. Now enforced by the existing length test.
- **Observability**: Net +1 `warn` log per streaming chat turn that returns 2xx-with-empty-body. This is the failure case being investigated; non-empty streams are unaffected. Lands in Sentry breadcrumbs even after `AgentError::skips_sentry()` (#2790) silences the parent event, unblocking the data needed to decide whether the in-session budget correlator from #3386 is the right next move or whether the fix has to be server-side.
- **Performance / security**: No request-path behaviour change. The added counters are stack-local `usize` increments. The diagnostic log redacts nothing new — provider name, model name, and counts are all already in the existing aggregated `info` log; only the byte total and elapsed ms are net-new and both are content-free.
- **Migration / compatibility**: None. Pure string + observability change.

## Related

- Closes: #3335
- Related: #3386 (the team-filed deep-dive that this PR addresses without requiring the per-session budget correlator), #3104 (cascade-error reports likely sharing the same empty-stream root cause), #2790 (the Sentry suppression that silenced TAURI-RUST-4JX without fixing the user-visible bug), #3199 (the empty-response arm being modified), #3121 (the budget-exhausted copy whose remedy this copy now points users toward), #3092 (closed parent issue for the chat-error cluster).
- Follow-up PR(s)/TODOs: Once the diagnostic log produces 24–48 h of production data, decide between (a) opening a backend issue against `tinyhumansai/backend` to start returning `400 + Insufficient budget` on streaming under credit exhaustion (the right server-side fix), and (b) plumbing `provider: Option<String>` through `AgentError::EmptyProviderResponse` so the classifier can be provider-aware (the #3386 in-session correlator approach).

---

## AI Authored PR Metadata (required for Codex/Linear PRs)

> Keep this section for AI-authored PRs. For human-only PRs, mark each field `N/A`.

### Linear Issue

- Key: N/A — GitHub issue #3335, not Linear-tracked.
- URL: N/A

### Commit & Branch

- Branch: `fix/3335-managed-empty-response-diagnostic-and-copy`
- Commit SHA: `7064cbfee`

### Validation Run

- [x] `pnpm --filter openhuman-app format:check` — `N/A: no frontend / TS files changed`
- [x] `pnpm typecheck` — `N/A: no frontend / TS files changed`
- [x] Focused tests: `cargo test --lib empty_response` (10 pass), `cargo test --lib empty_provider` (5 pass), `cargo test --lib cron::scheduler::tests::agent_error` (11 pass), `cargo test --lib compatible::tests` (153 pass).
- [x] Rust fmt/check (if changed): `cargo fmt --manifest-path Cargo.toml` + `cargo check --manifest-path Cargo.toml --lib` — both clean (only pre-existing unrelated warnings).
- [x] Tauri fmt/check (if changed): `N/A: no app/src-tauri files changed`

### Validation Blocked

- `command:` N/A
- `error:` N/A
- `impact:` N/A

### Behavior Changes

- Intended behavior change: User-facing error copy for `AgentError::EmptyProviderResponse` no longer claims a "local provider" exists; new copy names `Settings → Billing` (credits) alongside `Settings → AI → LLM` (provider config) and the model-switch fallback.
- User-visible effect: Managed-route users who hit empty-response failures now receive remedy text that matches the most likely actual cause (credit exhaustion, per #3386 evidence). Self-hosted users see the same three-remedy framing, which remains accurate for their configurations.

### Parity Contract

- Legacy behavior preserved: `AgentError::EmptyProviderResponse` Display, `classify_inference_error` error_type / source / retryable / provider fields, `agent_error_to_user_message` dispatch matrix, the `Settings → AI → LLM` deep-link reference. Existing test `classify_inference_error_empty_response_is_actionable_and_retryable` continues to pass unchanged.
- Guard/fallback/dispatch parity checks: The empty-response arm in `web_errors.rs` keeps the same `error_type: "empty_response"`, `source: "agent_loop"`, `retryable: true`, `retry_after_ms: None`, `provider: None`, `fallback_available: None` — only the message string changes. The cron `agent_error_to_user_message` dispatch matrix is unchanged; only the `EmptyProviderResponse` branch's returned string changes.

### Duplicate / Superseded PR Handling

- Duplicate PR(s): None — no other open PR references #3335.
- Canonical PR: This one.
- Resolution: N/A (no prior PR to supersede).


<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

## Release Notes

* **Bug Fixes**
  * Improved error messaging when model returns an empty response, now guiding users to check inference credits (Settings → Billing), try a different model, or verify LLM provider configuration (Settings → AI → LLM).

* **Improvements**
  * Added enhanced diagnostic logging for empty response scenarios to better support troubleshooting.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Closes #3335

Co-authored-by: M3gA-Mind <megamind@mahadao.com>
2026-06-22 18:25:06 +05:30
2026-05-20 16:44:35 -07:00
2026-02-20 13:03:15 +04:00
2026-02-20 13:03:15 +04:00

OpenHuman

The Tet

tinyhumansai%2Fopenhuman | Trendshift OpenHuman - An open source AI harness built with the human in mind | Product Hunt OpenHuman - An open source AI harness built with the human in mind | Product Hunt

OpenHuman - An open source AI harness built with the human in mind | Product Hunt OpenHuman - An open source AI harness built with the human in mind | Product Hunt

OpenHuman is your Personal AI super intelligence: local memory, managed services where needed, simple and powerful.

DiscordRedditX/TwitterDocsFollow @senamakel (Creator)

🇺🇸 English | 🇨🇳 简体中文 | 🇯🇵 日本語 | 🇰🇷 한국어 | 🇩🇪 Deutsch | 🇵🇰 اردو

Early Beta Latest Release GitHub Stars License 简体中文 日本語 한국어 Deutsch اردو

Early Beta: Under active development. Expect rough edges.

Local + managed services, upfront: OpenHuman stores its Memory Tree, Obsidian-style Markdown vault, workspace config, and local runtime state on your machine. The default managed experience still uses OpenHuman-hosted services for account sign-in, model routing, web search proxying, and managed integration/OAuth flows through the Composio connector layer. Choose custom/local settings if you want to bring your own model, search, or Composio credentials; some real-time triggers and hosted features still require the managed backend.

Install

Download installers from tinyhumans.ai/openhuman or from the GitHub Releases page. For terminal installs, the native package paths below are preferred — they ride your OS package-manager's signing chain.

These paths verify the artifact through your OS package manager's signing chain (Homebrew bottle hash, signed apt repo, MSI signature).

macOS (Homebrew tap):

brew tap tinyhumansai/core
brew install openhuman

Linux (Debian/Ubuntu — signed apt repo):

sudo apt-get install -y --no-install-recommends gnupg2 curl ca-certificates
curl -fsSL https://tinyhumansai.github.io/openhuman/apt/KEY.gpg \
  | sudo gpg --dearmor -o /etc/apt/keyrings/openhuman.gpg
echo "deb [signed-by=/etc/apt/keyrings/openhuman.gpg arch=amd64] \
  https://tinyhumansai.github.io/openhuman/apt stable main" \
  | sudo tee /etc/apt/sources.list.d/openhuman.list
sudo apt-get update
sudo apt-get install -y openhuman

Linux (Arch — AUR): the openhuman-bin AUR recipe is in the repo. Once published, Arch users can install it with yay -S openhuman-bin.

Windows: download the signed .msi from the latest release and run it.

Manual .dmg / .deb / .AppImage / .msi: grab the installer for your platform directly from the latest release page.

Linux: the AppImage can crash on launch under Wayland (and on Arch-based distros with sharun: Interpreter not found!) — see #2463 for the cause and env-var workarounds. The .deb package above avoids those failure modes on Debian/Ubuntu.

Alternative: script install (no integrity check)

Warning — unverified install. These scripts are served live from raw.githubusercontent.com and do not ship a separate signature, so curl … | bash and irm … | iex have no way to detect tampering of the script bytes. Prefer the native package paths above whenever possible. If you must use the script, see "Verified script install" below.

# macOS or Linux x64
curl -fsSL https://raw.githubusercontent.com/tinyhumansai/openhuman/main/scripts/install.sh | bash

# Windows (PowerShell)
irm https://raw.githubusercontent.com/tinyhumansai/openhuman/main/scripts/install.ps1 | iex

Verified script install status

A separately signed script-install path is not currently available. Issue #2620 is closed after the native package paths were promoted, but current release assets do not include install.sh.asc / install.ps1.asc for pre-execution script verification. Treat the script install path as unverified and prefer the native package options above when possible.

What is OpenHuman?

OpenHuman is an open-source agentic assistant designed to integrate with you in your daily life. Each bullet links to the deeper writeup in the docs.

  • Simple, UI-first & Human A clean desktop experience and short onboarding paths take you from install to a working agent in a few clicks — no config-first setup, no terminal required. The agent has a face: a desktop mascot that speaks, reacts to its surroundings, joins your Google Meets as a real participant, remembers you across weeks, and keeps thinking in the background even when you've stopped typing.

  • 118+ third-party integrations with auto-fetch: plug into Gmail, Notion, GitHub, Slack, Stripe, Calendar, Drive, Linear, Jira and the rest of your stack with one-click OAuth. Every connection is exposed to the agent as a typed tool, and every twenty minutes the core walks each active connection and pulls fresh data into the memory tree. No prompts, no polling loops you have to write, so the agent already has tomorrow's context this morning.

    Managed integrations use OpenHuman's Composio connector layer. OAuth handshakes and integration tool calls are proxied through the managed backend by default. If you want to run Composio directly instead, configure direct mode with your own Composio API key; real-time trigger webhooks then need to be hosted and wired by you.

  • Memory Tree + Obsidian Wiki: a local-first knowledge base built from your data and your activity. Everything you connect is canonicalized into ≤3k-token Markdown chunks, scored, and folded into hierarchical summary trees stored in SQLite on your machine. The same chunks land as .md files in an Obsidian-compatible vault you can open, browse and edit, inspired by Karpathy's obsidian-wiki workflow.

  • Batteries included: web search, a web-fetch scraper, a full coder toolset (filesystem, git, lint, test, grep), and native voice (STT in, ElevenLabs TTS out, mascot lip-sync, live Google Meet agent) are wired in by default. By default, model routing uses the OpenHuman backend to select and proxy the right LLM for each workload (reasoning, fast, or vision). One subscription includes all models. No "install a plugin to read files" friction. Use optional local AI via Ollama for supported on-device workloads.

  • Smart token compression (TokenJuice): every tool call, scrape result, email body, and search payload is run through a token compression layer before it touches any LLM Model. HTML is converted to Markdown, long URLs are shortened, and verbose tool output is deduped and summarized via a configurable rule overlay etc... CJK, emoji, and other multi-byte text are preserved grapheme-by-grapheme — never stripped. You get the same information but at a fraction of the tokens. Reducing cost & latency by up to 80%.

  • Messaging channels and privacy & security: inbound/outbound across the channels you already use, with workflow data that stays on device, encrypted locally, treated as yours.

Contributing from source

New contributor? Start with CONTRIBUTING.md for the fork/PR workflow and local validation commands, or use the copy-paste AI-agent prompt in CONTRIBUTING-BEGINNERS.md. The short path is:

  1. Install Git, Node.js 24+, pnpm 10.10.0, Rust 1.93.0 (rustfmt + clippy), CMake, Ninja, ripgrep, and the platform desktop build prerequisites.
  2. Fork and clone the repo, then run git submodule update --init --recursive before pnpm install so the vendored Tauri/CEF sources are present.
  3. Use pnpm dev for web-only UI work, pnpm --filter openhuman-app dev:app for the desktop shell, and focused checks such as pnpm typecheck, pnpm format:check, and cargo check -p openhuman --lib before opening a PR.

Deeper docs: Architecture · Getting Set Up · Cloud Deploy.

Context in minutes, not weeks

OpenHuman is the first agent harness that gets to know you in minutes. Inspired by Karpathy's LLM Knowledgebase. Most agents start cold. Hermes learns by watching you work; OpenClaw waits for plugins to ferry context in. Either way, you spend days or weeks before the agent knows enough about your stack to be genuinely useful.

OpenHuman context-building diagram

OpenHuman summarizes and compresses all your documents, emails & chats; and creates a memory graph that lets your agent remember everything about you.

OpenHuman skips the wait. Connect your accounts, let auto-fetch pull data locally on a 20-minute loop, and then have Memory Trees compress everything into Markdown files stored intelligently in a Karpathy-style Obsidian wiki.

In just one sync pass, the agent has full (compressed) context of your inbox, your calendar, your repos, your docs, your messages. No training period. No "give it a few weeks.". It becomes you, controlled by you.

Already self-host agentmemory across other coding agents? OpenHuman ships an optional Memory backend that proxies to it — set memory.backend = "agentmemory" in config.toml and the same durable store powers OpenHuman alongside Claude Code, Cursor, Codex, and OpenCode. See the agentmemory backend page for setup.

OpenHuman vs Other Agent Harnesses

High-level comparison (products evolve, so verify against each vendor). OpenHuman is built to minimize vendor sprawl, keep workflow knowledge on-device, and give the agent a persistent memory of your data, not only chat.

Claude Cowork OpenClaw Hermes Agent OpenHuman
Open-source 🚫 Proprietary MIT MIT GNU
Simple to start Desktop + CLI ⚠️ Terminal-first ⚠️ Terminal-first Clean UI, minutes
Cost ⚠️ Sub + add-ons ⚠️ BYO models ⚠️ BYO models One sub + TokenJuice
Memory Chat-scoped ⚠️ Plugin-reliant Self-learning 🚀 Memory Tree + Obsidian vault, optional agentmemory backend
Integrations ⚠️ Few connectors ⚠️ BYO ⚠️ BYO 🚀 118+ via OAuth
Auto-fetch 🚫 None 🚫 None 🚫 None 20-min sync into memory
API sprawl 🚫 Extra keys 🚫 BYOK 🚫 Multi-vendor One account
Model routing 🚫 Single model ⚠️ Manual ⚠️ Manual Built-in
Native tools Code-only Code-only Code-only Code + search + scraper + voice

Star us on GitHub

Building toward AGI and artificial consciousness? Star the repo and help others find the path.

Star History Chart

Contributors Hall of Fame

Show some love and end up in the hall of fame. Contributors get free merch and special access to our Discord.

OpenHuman contributors
S
Description
No description provided
Readme GPL-3.0
214 MiB
Languages
Rust 59.1%
TypeScript 37.9%
JavaScript 1.6%
Shell 1.2%
CSS 0.1%