feat(api): context-window usage indicator + honest quota-error classification#6215
Merged
Conversation
added 2 commits
June 18, 2026 15:56
…udget cap
The WebSocket streaming error classifier matched a bare "quota"/"Quota" substring before the LLM error classifier ran.
Any provider 429/billing error whose body contains the word "quota" (e.g. OpenAI "You exceeded your current quota") was relabeled as LibreFang's internal "Token quota exceeded. Try /compact or /new to free up space."
That advice is doubly wrong: a provider rate-limit has nothing to do with context space, and /compact never clears any usage cap.
Narrow the branch to the exact internal Display prefix of LibreFangError::QuotaExceeded ("Resource quota exceeded:") and give it an honest message — it is a usage/spending cap, /compact will not help, and the remedy is to wait for the window to reset or raise [budget].
Every other "quota"-bearing string now falls through to the LLM classifier and gets the correct RateLimit / Billing message.
Also correct two misleading session_ops comments that claimed /new clears the quota: reset_usage only resets the in-memory per-agent token tracker, not the persisted cost/usage windows.
Add GET /api/agents/{id}/session/context returning {used_tokens, max_context_tokens, pct, model, pressure} for an agent's session, so users can see how full the context window is and stop confusing it with the usage budget.
The window denominator is resolved via Kernel::context_report, which applies the same precedence chain the agent loop uses (manifest override > catalog > persisted session > 8192 fallback).
The endpoint honors an optional ?session_id= the same way GET /session does, with the matching cross-agent ownership guard, so a dashboard tab pinned to a non-active session reports that session.
context_report is refactored into context_report_for_session(agent_id, Option<SessionId>), with context_report delegating to it.
The dashboard renders a small progress-bar indicator in the chat ConnectionBar wired through api.ts to a query hook, a hierarchical key factory, and i18n in en/zh/uk, polled at 15s, with loading/error states and a11y attributes.
Integration tests cover the response shape, unknown-agent 404, auth enforcement, the pinned-session path, and the cross-agent rejection.
added 3 commits
June 18, 2026 16:42
The budget message intentionally mentions /compact to say it will NOT help; the test asserted !msg.contains("/compact"), contradicting its own intent (don't *recommend* /compact) and failing the Test/Unit lane. Assert the message steers away from /compact instead. Also apply the two rustfmt diffs (sessions.rs match scrutinee, ws.rs single-line test call) that failed the Quality job.
The context-window integration test asserted body["model"] == "test-model" (the global config default_model), but spawn_named builds agents from AgentManifest::default(), whose model is "default". The endpoint correctly returns the agent's own manifest model, so the test expectation was wrong — assert "default".
…t skeleton, drop dead key The new get_agent_session_context handler returned three hardcoded English error strings; route them through ErrorTranslator (reusing api-error-session-invalid-id / api-error-session-not-found and adding api-error-context-report-failed across all 7 locales). Translate the context-report message before drop(t) since ErrorTranslator is !Send. ContextUsageIndicator pinned a permanent skeleton whenever the query was disabled (no sessionId): a disabled TanStack query reports isLoading=false/data=undefined, so guard on isFetching and render nothing when idle. Drop the unused context_usage_unknown i18n key.
houko
enabled auto-merge (squash)
June 18, 2026 15:38
# Conflicts: # .secrets.baseline
houko
pushed a commit
that referenced
this pull request
Jun 19, 2026
…classifier The #6211 token/context-limit banner keys off the chat surface error string, which on current main is the output of the kernel's `classify_streaming_error` (#6215 merged). Two cases were wrong against that output: - False negative: the canonical context-overflow message is collapsed to "Context is full. Try /compact or /new.", which matched none of the phrase list, so the banner missed the exact case it exists for. Add "context is full". - False positive with harmful advice: an internal usage/spending-budget cap is reported as "Usage budget reached ... not a full context window — /compact will NOT help ...". That string contains "context window" (inside a negation) and so fired the banner, telling the user to start a new session — which does nothing for a persisted budget window. Suppress the banner for usage-budget wording. Add regression cases pinning both behaviours (they fail without the fix) and refresh the CHANGELOG entry to reflect that #6215 has merged and only adds a usage indicator, not a per-turn error classification.
houko
added a commit
that referenced
this pull request
Jun 19, 2026
…mit (#6211) (#6231) * feat(dashboard/chat): guide user to a new session on token/context limit When the latest agent-chat turn fails with a token / context-window or length / quota limit, the chat view now renders an inline guidance banner with a one-click "Start a new session" action that reuses the existing useCreateAgentSession mutation, instead of leaving only a raw error bubble. Detection is a frontend heuristic over the daemon / provider error string (isContextLimitError), because main carries no structured context-exhaustion signal on the chat surface. A structured signal can replace it later (related: in-flight #6215). Scoped to the agent session chat view — channels are config-only surfaces in the dashboard with no conversation UI. Adds en/zh/uk locale strings and ChatPage.limit.test.ts. Closes #6211 * refactor(dashboard/chat): trim multi-line comment blocks and remove issue refs from source * fix(dashboard/chat): align context-limit heuristic with kernel error classifier The #6211 token/context-limit banner keys off the chat surface error string, which on current main is the output of the kernel's `classify_streaming_error` (#6215 merged). Two cases were wrong against that output: - False negative: the canonical context-overflow message is collapsed to "Context is full. Try /compact or /new.", which matched none of the phrase list, so the banner missed the exact case it exists for. Add "context is full". - False positive with harmful advice: an internal usage/spending-budget cap is reported as "Usage budget reached ... not a full context window — /compact will NOT help ...". That string contains "context window" (inside a negation) and so fired the banner, telling the user to start a new session — which does nothing for a persisted budget window. Suppress the banner for usage-budget wording. Add regression cases pinning both behaviours (they fail without the fix) and refresh the CHANGELOG entry to reflect that #6215 has merged and only adds a usage indicator, not a per-turn error classification. --------- Co-authored-by: Evan <[email protected]> Co-authored-by: Claude <[email protected]>
GQAdonis
pushed a commit
to GQAdonis/librefang
that referenced
this pull request
Jun 19, 2026
Merge upstream/main into the BossFang fork; origin/main was 15 commits behind. Notable upstream changes: - librefang#6225 scope the compaction-summary banner to the compacted session (adds canonical_sessions.compacted_summary_session_id). - librefang#6196 deny WASM fs_write to the audit anchor via a capability deny-list. - librefang#6226 / librefang#6227 add agent label and exit-reason metrics to the agent loop. - librefang#6215 context-window usage indicator + honest quota-error classification. - librefang#6217 per-instance sidecar secrets so each agent owns its own handle. - librefang#6194 global Auto-Dream on/off switch on the Memory tab. - librefang#6211 / librefang#6214 / librefang#6212 token/context cap fixes; librefang#6208 refuse to delete the active prompt version. - librefang#6218 browser_tools.rs ToolError migration; librefang#6203 launchctl let-binding build fix; librefang#6224 docs dep bumps; librefang#6193 drop five orphaned email deps. Conflict resolution: - deny.toml: keep our RUSTSEC-2025-0141 (bincode 2.0.x) ignore entry (take ours). - deny.toml: ignore three pre-existing unmaintained advisories newly tripped by the RustSec DB (all transitive via the UAR provider chain, none introduced by this merge): RUSTSEC-2024-0384 (instant), RUSTSEC-2024-0436 (paste), RUSTSEC-2025-0119 (number_prefix). BossFang preservation (SurrealDB schema parity for upstream SQLite v46 / librefang#6225): - Add crates/librefang-storage/src/migrations/sql/032_canonical_sessions_compacted_summary_session_id.surql declaring the new field on the SCHEMAFULL canonical_sessions table (SCHEMAFULL silently drops undefined fields on write), registered as version 32 in migrations/mod.rs. - migrate/sqlite_to_surreal.rs: copy the new column so a SQLite to SurrealDB migration preserves the owning-session pointer rather than dropping it. - backends/surreal_session.rs: preserve compacted_summary_session_id across canonical appends, since the upsert replaces the whole record. - Cargo.lock: pick up the workspace version bump (beta.19 to beta.20) the merge introduced. Verification: - cargo check --workspace --lib — clean. - cargo check -p librefang-storage -p librefang-memory -p librefang-uar-spec — clean. - cargo test -p librefang-storage migration — ok (migration ordering / SurrealDB-3 flexible-syntax invariants). - cargo test -p librefang-memory --lib session — 50 passed (incl. upstream's store_llm_summary round-trip test). - cargo clippy -p librefang-storage -p librefang-memory — clean. - python3 scripts/enforce-branding.py --check — clean; Tauri desktop audit and URL-drift scan both clean.
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
Two coupled problems behind the "Token quota exceeded. Try /compact or /new to free up space." message users hit even with a large/empty context window.
The WebSocket streaming error classifier (
ws.rs: classify_streaming_error) matched a bare"quota"/"Quota"substring before the LLM error classifier ran.Any provider 429 / billing error whose body merely contains the word "quota" (e.g. OpenAI "You exceeded your current quota") was relabeled as LibreFang's internal budget message.
That advice is doubly wrong: a provider rate-limit has nothing to do with context space, and
/compactnever clears any usage cap.This is why setting
[budget]to 0 (unlimited) does not make the message go away — the error is not the internal budget at all.There was no way to see how full the context window actually is, so the usage budget and the context window kept getting conflated.
What
Fix — honest quota-error classification (
fix(api))ws.rsbranch to the exact internal Display prefix ofLibreFangError::QuotaExceeded("Resource quota exceeded:") and give it an honest message: it is a usage/spending cap,/compactwill not help, and the remedy is to wait for the window to reset or raise[budget].llm_errors::classify_error, so a provider 429 →RateLimit("Rate limited, wait ~Ns") and 402/billing →Billing, instead of the bogus internal copy.session_ops.rscomments that claimed/newclears the quota:reset_usageonly resets the in-memory per-agent token tracker, not the persisted cost/usage windows.Feature — context-window usage indicator (
feat(api))GET /api/agents/{id}/session/context→{used_tokens, max_context_tokens, pct, model, pressure}.Kernel::context_report, which applies the same precedence chain the agent loop uses (manifest override > catalog > persisted session > 8192 fallback), so it is always positive.?session_id=exactly likeGET /session, with the matching cross-agent ownership guard, so a dashboard tab pinned to a non-active session reports that session.context_reportis refactored intocontext_report_for_session(agent_id, Option<SessionId>)withcontext_reportdelegating to it.ConnectionBar, wired throughapi.ts→ query hook → hierarchical key factory → i18n (en/zh/uk), polled at 15s, with loading/error states, theme-token colors stepped by pressure, and a11yrole="progressbar"attributes.Review fixes (from the pre-PR adversarial review)
?session_id=the dashboard sent and always reported the active session — now resolved via the session-aware path + ownership guard.max <= 0dead branch in the dashboard component and corrected themax_context_tokensdoc comment (it is never 0 — the window always falls back to 8192).Verification
pnpm typecheck,pnpm lint(0 errors),pnpm test --run(812 pass incl. new key-factory anchoring tests),pnpm build— all green locally.The Rust changes were reviewed for compile-correctness and formatted to rustfmt-stable shapes by hand.
crates/librefang-api/tests/agents_routes_integration.rs:context_endpoint_returns_used_and_max_tokens,context_endpoint_unknown_agent_404,context_endpoint_is_authed,context_endpoint_honors_session_id_param,context_endpoint_rejects_cross_agent_session.ws.rsunit tests assert the internal"Resource quota exceeded:"prefix yields the honest budget message while a provider"...429...quota"string routes toRateLimitand402toBilling.Deferred
ws.rsstring matching with a typedLlmErrorCategorycarried onLibreFangError::LlmDriver, KernelHandle trait uses Result<_, String> for ~40 methods — destroys structured errors #3541-aligned).Assessed as feasible but spanning 3 crates plus the kernel-config golden-fixture regen, and not a correctness prerequisite — the substring narrowing here already fully resolves the reported bug.
Left for its own PR to keep this diff focused.