URSA-OSCAR 1.1.4 — Local-model malformed-tool-call diagnostic
URSA-OSCAR 1.1.4
Two production-debugged data fixes, one architectural cleanup, and a UX polish for local-model users. The full 1.1.2 → 1.1.4 changeset (a separate 1.1.3 Release was skipped; Docker Hub :1.1.3 images still work for anyone who pulled them; this Release supersedes them).
Fixed in 1.1.4
- Malformed-tool-call diagnostic for under-capable local models (
backend/src/ursa_oscar/api/ai.py). When a local model isn't capable enough to format an OpenAI-style tool call against URSA's full 18-tool surface (Qwen3-4b on CPU is the canonical example), it typically emits{"as text content trying to write a JSON tool-call, then finishes withstop_reason="stop". The chat panel previously rendered the partial JSON literally, leaving the user staring at a confusing single{. The chat handler now detects this shape (text content ≤10 chars starting with{, stop_reason=stop, no tool_calls) and surfaces a friendly diagnostic error with concrete next steps: switch to Claude API, use a larger local model (Qwen3-30b-instruct, Llama-3.3-70b-instruct), or run on GPU instead of CPU. Two regression tests cover the detection firing for the failure shape and not misfiring on legitimate short responses likeYes..
Fixed in 1.1.3
-
Session boundary detection no longer splits on clock-minute boundaries (
backend/src/ursa_oscar/analytics/edf_parser.py). The EDF importer grouped files into sessions by hour-and-minute filename prefix. When a session's events file landed at01:04:53and the waveforms file landed at01:05:00(7 seconds apart but crossing the minute mark), they were treated as two separate sessions: one events-only, one waveforms-only. Mask-on duration inflated by the length of the affected session. Replaced with sliding-window temporal clustering at 30-second tolerance. Operators with affected nights must force re-import to pick up the corrected session boundaries viaURSA_OSCAR_WATCH_FORCE_REIMPORT=trueand a watcher recreate. Three regression tests cover the minute-boundary case, the 50-second-real-restart case, and the multi-minute-separation case. -
Thinking-mode model support in the OpenAI-compat adapter (
backend/src/ursa_oscar/ai_proxy/providers/openai_compat.py,frontend/src/components/AiChatPanel.tsx). Qwen3 (via LocalAI / Ollama) emits chain-of-thought indelta.reasoning; DeepSeek-R1 usesdelta.reasoning_content. Both naming conventions are now read and surfaced as a newreasoningevent type. The chat panel renders these as a collapsible "Reasoning" trail above the assistant's content, open by default while in-flight, collapsed once the answer arrives. Reasoning content stays out of the assistant message'scontentfield so follow-up turns don't carry the chain-of-thought back to the LLM. Stream timeout bumped from 120s to 300s on the read path. -
Settings page image-version chips are now self-introspecting, eliminating the dual-update problem (
backend/src/ursa_oscar/api/system.py,mcp-server/src/ursa_oscar_mcp/__main__.py,watcher/src/ursa_oscar_watcher/__main__.py,frontend/vite.config.ts). Previously the chips read fromURSA_OSCAR_*_IMAGE_VERSIONenv vars that the operator had to keep in sync with theimage:lines in their compose file. Now each service is the source of truth for its own version: API readsimportlib.metadata.version('ursa-oscar-backend'); MCP exposes an unauthenticated/versionendpoint the API queries; the watcher writes/data/versions/watcher.txtat startup; the web bundle bakes__URSA_WEB_VERSION__via Vite at build time. Env-var overrides are retained for testing scenarios but the common case requires no operator coordination. 11 regression tests cover the introspection helpers.
Fixed in 1.1.2
-
Test connection button reflects the saved config, not in-flight edits (
frontend/src/pages/SettingsAi.tsx). The button used to enable the moment the operator typed an API key, but the/ai/testendpoint probes the persisted config. Five disabled states now, each with a tooltip naming the missing piece. Local LLM presets skip the key requirement. -
Gemini provider preset refreshed (
backend/src/ursa_oscar/ai_proxy/providers/presets.py). The default model list shipped withgemini-2.0-flash-expandgemini-1.5-pro(both deprecated by Google as of May 2026) andgemini-1.5-flash(on the deprecation track). Replaced withgemini-3.5-flash. Also rolled OpenRouter's stalegoogle/gemini-2.0-flashtogoogle/gemini-3.5-flash.
README addition
The README now includes a section on the AI value proposition: the MCP-exposed-data-plus-specialized-AI-conversation pattern that motivated URSA in the first place, and the in-app AI assistant as the fallback path for deployments that can't expose a public MCP endpoint. The reusable architectural pattern is linked to the new template repo at https://github.com/burrellka/mcp-server-template.
Re-import note for operators
If your URSA installation has nights where mask-on duration looks inflated (typically 10+ hours when the real value should be 6-7 hours), at least one of those nights hit the minute-boundary bug. The fix corrects future imports automatically; existing nights need a force re-import to rewrite session boundaries.
Easiest path:
- In your compose env, set
URSA_OSCAR_WATCH_FORCE_REIMPORT=true docker compose up -d --force-recreate ursa-oscar-watcher- Wait one watcher poll cycle (default 30 seconds). The watcher re-imports every night present on the SD card source.
- Set
URSA_OSCAR_WATCH_FORCE_REIMPORT=falseagain (or remove the override) and recreate the watcher.
The re-import is idempotent for nights not affected by the bug; nights with the corrupted session pair get rewritten with the corrected single-session shape.
No data migrations
No schema changes. No env-var changes. Same /data volume layout. Upgrade is docker compose pull && docker compose up -d --force-recreate.
Architecturally locked in 1.0 (carried into 1.1.x)
- Single-tenant. One operator, one instance.
- Self-hosted. No cloud sync, no SaaS option.
- Password authentication only. No SSO / OAuth providers for operator login.
- No email password recovery. Recovery is filesystem-level (delete
auth.json, re-bootstrap). - Statistical method declaration in every analytical response.
- Sample-size discipline as refusals, not warnings (
INSUFFICIENT_DATAreturns when below the floor).
Docker images
All four images are available at brain40/ursa-oscar-*:1.1.4 + :latest:
brain40/ursa-oscar-api:1.1.4brain40/ursa-oscar-mcp:1.1.4brain40/ursa-oscar-web:1.1.4brain40/ursa-oscar-watcher:1.1.4
Upgrade
Operators on 1.0.x, 1.1.0, 1.1.1, 1.1.2, or 1.1.3: bump the four image tags to 1.1.4 and docker compose up -d --force-recreate. No data migrations. After upgrade, follow the Re-import note above if you have affected nights from the session-boundary bug.
Test coverage
- Backend: 380+ tests pass (including 53/53 AI proxy, 9/9 EDF parser with new regression coverage, 11/11 version-introspection)
- MCP: 40+ tests pass
- Watcher: 25+ tests pass
- Frontend:
tsc -b && vite buildclean
License
GNU GPL-3.0-or-later, preserving OSCAR's license terms.
Attribution
Built in the spirit and license terms of the OSCAR project. URSA-OSCAR is independent of OSCAR (different codebase, different deployment model) but downstream of OSCAR's file-format work.