Changelog
Stack-side changes, ADR landings, breaking changes — chronological,
most-recent first. For the operator-internal full git-log see the
repo. For the live build version, see /api/version.
2026-06-24 — Cut 2.50: STT konvertiertes Audio zurückgeben (convert=1) + large-v3-turbo als Default
POST /v1/audio/transcriptions kann jetzt das normalisierte Audio zusammen mit
dem Transkript zurückgeben, und das beste STT-Modell ist server-seitig fixiert.
convert=1 — normalisiertes Audio zum Mitnehmen
- Neues multipart-Form-Feld
convert=1(Aliaswith_converted_audio=1; truthy =1/true). Opt-in, additiv — OpenAI-Caller setzen es nie. - Aktiv (und
response_formatin {json,verbose_json, unset}) → Response istmultipart/mixed: Part 1 = angereichertes Transkript-JSON (text+model+language+duration+converted{format,mime,codec,sample_rate,channels,bytes,sha256}), Part 2 = konvertierte RAW-Bytes (16 kHz mono FLAC; kein base64). Invarianten:converted.mime == Content-Type(Part 2),converted.bytes == len(Part 2). Boundary ≥ 32 Byte CSPRNG. - Bei
response_formatin {text,srt,vtt} wirdconvertignoriert (Response bleibttext/srt/vtt). - Variante A (Transkript geht nie verloren): Konvertierung ok → Part 2 =
FLAC; Konvertierung scheitert + Original klein (≤ 16 MB) → Part 2 = Original
(
converted.format= ffprobe-Quellformat); Konvertierung scheitert + Original groß oder Audio über Dauer-Cap →converted=null, kein Part 2, nur Transkript (kein OOM/413). - Konvertierungsort: dedizierter
audio-convert-Sidecar (ffmpeg,AUDIO_CONVERT_URL). Der distroless Gateway-Container hat kein ffmpeg/keine Shell → der Subprocess lebt im Sidecar, das Gateway spricht nur HTTP (umgeht die tokio-Subprocess-Falle).converted.formatIMMER ffprobe-detektiert, nie aus Client-Dateinamen. - DoS-Härtung: ffprobe-Dauer-Vorprüfung VOR der Vollkonvertierung
(on-box: 2000-s-Audio-Bomb in 0,056 s als 413 abgelehnt),
ffmpeg -t-Hard-Cap, Output-Byte-Limit (32 MB). - Dauer-Cap = 420 s (7 min), abgeleitet aus dem KLEINEREN von {Caller-Proxy-Timeout 120 s, Cloudflare-Tunnel-Origin ~100 s} — NICHT aus der 64-MB-Zahl (die Upstream-Response ist proxy-seitig ungecappt RAM-gepuffert; 64 MB ist ein INBOUND-Request-Limit). On-box gemessen: large-v3-turbo ~0,085× realtime (440-s-Clip → 37,3 s), Konvertierung ~negligibel (258 s → 0,15 s) → p99 transcribe+convert+assemble bleibt unter ~100 s.
Bestes STT-Modell fixiert
- Neue env
STT_DEFAULT_MODEL(Defaultlarge-v3-turbo, interndeepdml/faster-whisper-large-v3-turbo-ct2).modelfehlt/leer/auto/whisper-1→ aufgelöst; ein expliziter allowlisteter Slug gewinnt immer. Response echot das effektiv genutzte Modell: JSON-Feldmodel(maßgeblich, kommt durch den Proxy) + HeaderSPASS-Stt-Model(nur dgx-intern — der Caller-Proxy verwirft Upstream-Response-Header). - API-Impact: 🟡 für
model=auto/fehlend ändert sich das EFFEKTIVE Modell bewusst auflarge-v3-turbo(vorher: server-seitiger Default des Station- WhisperLiveKit, nicht gepinnt). Für expliziten Slug unverändert. Identisch zu WhisperLiveKit--modelfür Live↔Datei-Konsistenz. GET /v1/audio/models?type=stt: Struktur/Allowlist unverändert. Das additive"default": true-Flag wurde weggelassen, weil die Toleranz des GoCreate- Frontend-Parsers gegenüber unbekannten Feldern nicht on-box bestätigt werden konnte — nachrüstbar sobald bestätigt.
Backward-Compat
- 🟢 Ohne
convertist die Response byte-identisch (reiner OpenAI-Passthrough); additive JSON-Felder brechen keine Clients. Live-WS-Pfad/ws/audio/streamunberührt (Regression grün verifiziert). Der nachgelagerte Caller-Proxy braucht für den TRANSPORT null Codeänderung; aber für langeconvert=1-Uploads muss er separat ein höheres Audio-Passthrough-Timeout setzen (Status quo erbt 120 s ohne per-Request-Override).
2026-06-21 — /v1/audio/translations Modell-Aliase gefixt
/v1/audio/translations akzeptiert jetzt dieselben Modell-Aliase wie
/v1/audio/transcriptions (auto, large-v3-turbo, large-v3, whisper-1,
faster-whisper/large-v3-turbo). Vorher schlugen diese Aliase auf dem
Translations-Pfad mit HTTP 503 fehl, weil der Sprachdienst sie nicht als
gültige Modell-Kennung erkannte; nur die kanonische native ID lieferte ein
Ergebnis.
- API-Impact: 🟢 additive Bugfix —
model=auto(Default) und alle advertised Aliase liefern auf/v1/audio/translationsjetzt HTTP 200./v1/audio/transcriptionsbleibt unverändert.
2026-06-21 — /readyz als Cluster-Verfügbarkeits-Endpunkt
Der öffentliche /readyz liefert jetzt eine high-level Verfügbarkeits-Ampel
über die ganze Plattform statt nur einzelner Backend-Flags:
status— Gesamt-Ampelok|degraded|critical(schlechteste Komponente bestimmt den Wert).checked_at— RFC3339-Zeitstempel der Erhebung (Stale-Erkennung).degraded_reasons— generische Liste eingeschränkter Fähigkeiten (Rechenkapazität, Suchdienst, Sprachdienst). Keine Hostnamen, keine Zählungen, keine internen Komponenten-Namen.- API-Impact: 🟡 die Detail-Felder von
/readyzwurden auf generische Knoten-Blöcke umgestellt.readybleibt unverändert (truesolange Requests bedient werden können). Siehe Service availability.
2026-06-21 — Cut 2.46: Chat-Summary locale-aware + Tenant-Modell + Re-Summary (CR-0013)
Erweitert die native Chat-Summary (Cut 2.33 / CR-0002) um drei Sub-Features:
- Locale-aware: Titel/Summary in der Sprache des
Accept-Language-Headers (erster Tag, z.B.en/fr/cs); ohne Header der Stack-Default (envDEFAULT_LOCALE, Standardde). Vorher hart Deutsch — en/cs/fr-GoCreate-Nutzer bekamen deutsche Titel. - Tenant-Modell: Der Summary-Pfad nutzt jetzt das Tenant-Setting
compact_summary_model(Cascade DB→yaml→Defaultllama-4-scout) statt hartllama-4-scout-local. EnvSUMMARY_MODELbleibt globaler Override. Vorher ignorierte der Summary-Pfad das Setting (galt nur für /a1-Compaction). - Re-Summary bei Wachstum: Auto-Trigger feuert erneut, wenn die Conversation seit der letzten Generierung um ≥ 10 Messages gewuchs (neue Spalte
conversation_meta.summary_msg_count, additive Migration). Vorher nur einmal nach dem 1. Turn → Titel langer Chats fror ein. - API-Impact: 🟢 additive — keine Schema-Brüche. Neuer optionaler Request-Header
Accept-Languageauf/c1-Pfaden;meta.summary_msg_countist intern (nicht in der List-/Detail-Response surfaced).
2026-05-16 — Cut 2.35: System-Prompts Self-Service für tenant-clients
- Token-Permissions:
godelmann-gocreate-{test,prod}-Tokens bekommenextra_scopes: [system_prompt:write:tenant, system_prompt:write:scope]. Sie können jetzt ihren globalen tenant-Prompt selbst pflegen statt frontend-side per-request inline (Migration weg vom Cut-2.33-CR-0003 Body-Inject-Pattern). - OpenAPI-Coverage: 8 system-prompts-Handler bekommen
#[utoipa::path]-Annotations + sind im/openapi.jsonregistriert. TypeScript-Codegen funktioniert jetzt. - Neue Doku-Page:
/docs/system-prompts— komplette API-Referenz inkl. 3-Level-Hierarchie, Permission-Matrix, Auto-Inject-Pipeline, Audit-Trail. - Kein Code-Change am Resolution-Pfad:
system_prompt_refin/c1/chatlöst weiterhin nur scope-level auf (Cut 2.33-Verhalten unverändert). Tenant-globale Prompts wirken über die existinginject_system_prompt_stack(Cut 2.20+) Pipeline automatisch — keinsystem_prompt_refnötig.
2026-05-16 — Cut 2.34: Image-Gen image_url.url zurück zu data-URL (CR-0004) ⚠️ BREAKING
choices[0].message.images[i].image_url.url ist wieder data:<mime>;base64,<b64> (Industrie-Standard 2026, aligned mit OpenAI gpt-image-1, Anthropic Claude, Google Gemini Imagen). Cut 2.24 hatte das auf /v1/images/<uuid>-references umgestellt; per Operator-Entscheidung 2026-05-16 wird das revertiert weil API-Konformität Vorrang vor BLOB-DB-Optimierung hat.
- Additive Felder:
storage_ref(=/v1/images/<id>für lazy-reload aus persisted conversations),thumbnail_data_url(unverändert),expires_at,id,mime_type,model. GET /v1/images/<uuid>bleibt funktional als convenience-Endpoint.- TTL-content-append:
choices[0].message.contenterhält deterministisch einen Hinweis "⏰ Dieses Bild ist {ttl_minutes} Minuten via lazy-reload (storage_ref) verfügbar." (TTL aus tenant-configimage_default_ttl_hours). - Caller-Frontend:
<img src={image_url.url}>rendert wieder direkt ohne 2. fetch.
Volle Doku: examples-image-gen.md.
2026-05-16 — Cut 2.33: Chat-Summary + System-Prompt Hybrid-Schema (CR-0002 + CR-0003)
CR-0002 — Native Chat-Summary:
- Neue Tabelle
conversation_metafür LLM-generatedtitle(3-5 Worte) +summary(1-2 Sätze). Auto-Trigger nach 1. Assistant-Turn (fire-and-forget gegenllama-4-scout-local, ~500ms-1s lag, envSUMMARY_MODELoverridable). GET /c1/conversations/{id}returnt additivemeta: { title, summary, model, updated_at }-Feld (null bis trigger durch ist).GET /c1/conversationsenricheditems[].title(LLM-Title statt Cut-2.25-80-char-prefix) +items[].summary.- Neuer Endpoint
POST /c1/conversations/{id}/summary?refresh=truefür explicit force-regenerate (synchron, ~500ms-2s); ohne?refreshcached.
CR-0003 — System-Prompt Hybrid (Assistants-API style):
POST /c1/chatbody akzeptiert genau eines vonsystem_prompt | system_prompt_ref | additional_system_prompt. 2+ Felder → HTTP 400invalid_field.system_prompt_reflöst gegensystem_prompts-Store auf (level=scope, scope_id=name).additional_system_promptwird mit Prefix "Zusätzliche Hinweise:\n" persistiert (OpenAI per-Run-Instructions-pattern).- Storage als
role=system-row inmessages-Tabelle (kein Schema-Change, role-CHECK akzeptiert 'system' seit jeher). - Multi-Turn-Calls mit system-Feld bei bestehender system-row → Response-Header
spass-system-prompt-ignored: already_present(explicit signal statt silent-ignore). - Neuer Endpoint
POST /c1/conversations/{id}/system-promptfür nachträgliche Append-Injection (audit-trail-fähig, nicht replace).
2026-05-16 — Cut 2.32: Multi-Tool-Loop Synthesis + openrouter Quota-Code (CR-0001 + CR-0005)
CR-0001 — Multi-Tool-Loop-Resilience für Llama-4-Scout-FP8:
MAX_TOOL_ITERATIONSvon 5 auf 10 erhöht (Anthropic-Industrie-Standard "start with 10-30").- Bei MAX-cap-Hit oder Anti-Loop-Detection (selbe
name+args-Signatur 2x hintereinander) wird kein raw passthrough mehr zurückgegeben — Server triggert einen extra Synthesis-LLM-Call mittools: []+ collected-iter-results-prompt, der eine narrative Antwort erzwingt.finish_reason: "stop"statt"tool_calls",contentist NIE leer. - Neue
dgx_code-Werte im body:"tool_loop_max_iterations"(cap erreicht) oder"tool_loop_anti_loop_synthesised"(anti-loop). Beide HTTP 200. - Stream-Mode: zusätzliche named SSE-events
event: spass.tool-capbzw.event: spass.tool-anti-loopmit{code, iterations, synth_called}vor dem finalen content. tool_normalize.rsErweiterungen: multi-document-JSON-parser (Llama emittiert manchmal newline-separierte JSON-objects statt array), partial-match-policy (mixed known+unknown → nur known normalisiert), schema-toleranz (name|function|toolkeys +parameters|arguments|args|inputfür payload).
CR-0005 — openrouter-Tageslimit als sprechender Error:
- HTTP 400 → 429 wenn upstream-openrouter-body daily-quota-pattern matched.
- Neuer ErrorCode
openrouter_daily_quota_exhausted. - Response-Header
Retry-After: <seconds-bis-naechste-00:00-UTC>. - Body enthält
dgx_code: "openrouter_daily_quota_exhausted"+available_fallbacks: ["godelmann-gocreate-premium-claude-text-premium", "godelmann-gocreate-premium-gemini-text-premium"](Cut 2.43 Matrix-v7-aliases).
Symmetrisch in unary + stream Pfaden (ADR 0015).
2026-05-04 — Cut 2.25c: FTS5-Trigger Robustness Fix
DELETE/UPDATE-Triggers nutzen jetzt direktes DELETE FROM messages_fts WHERE rowid = … statt content-match-'delete'-Command. Self-healing-Migration via PRAGMA user_version=2 rebaut den FTS-Index einmalig beim ersten Boot — beendet eine seltene "database disk image is malformed" (Error 267) Fehlerklasse beim DELETE-Pfad. Live reproduziert + gefixt nach GoCreate-Bug-Report.
2026-05-04 — Cut 2.25 + 2.25b: Auto-Title + Conversation Search
GET /c1/conversations liefert jetzt ein title-Feld pro Eintrag (auto-derived aus erster user-Message, 80 chars max + …). Optional ?q=…&scope=title|fulltext für substring-search.
scope=title(default) — substring-match auf erster user-Message, schnell, deckt den Cmd+K-Pattern ab.scope=fulltext— match auf ALLEN messages mit relevance-ranking +match_excerpt. Cap 50 conversations.
Cut 2.25b stellt den fulltext-Pfad auf SQLite-FTS5 mit unicode61-Tokenizer + diacritics-fold um (z.B. ?q=Marz findet "März"). Schema-Migration ist transparent + idempotent.
2026-05-04 — Cut 2.24: Image-Store + WebP Thumbnails
Image-Gen-Responses haben jetzt einen Server-URL für die volle Auflösung (/v1/images/{uuid}, TTL-expirierend) plus ein WebP-q80-Thumbnail (256 px, ~10 KB) als data-URL für sofortiges Inline-Display. OpenRouter-Industriestandard. Volle Doku in examples-image-gen.md.
2026-05-04 — Cut 2.23d: T5 max_tokens Cap + Cross-Vendor Fallback + model_quota_exhausted
Drei zusammenhängende Hotfixes nach GoCreate-prod-Befund:
max_tokens-Default-Cap (4096) für T5-cloud-aliases (*-latest,flagship) wenn der Caller keinen expliziten Wert schickt. Verhindert dass OpenRouter das Modell-Maximum (z.B. GPT 65536) gegen knappe Daily-Budgets reserviert.- Cross-vendor fallback-chain in
litellm/config.yaml:openai-gpt-latest ↔ anthropic-claude-opus-latest ↔ google-gemini-pro-latest. Bei quota-out beim primären Provider versucht der Server automatisch die anderen beiden Frontier-Modelle. - Neuer ErrorCode
model_quota_exhausted(HTTP 429). Heuristic-pattern-detect in upstream-error-bodies. Body strikt provider-agnostisch (kein OpenRouter-Name, keine Token-Counts) per ADR 0006 v2.
2026-05-04 — Cut 2.23c: User-Identity-Authority Header-Only (ADR 0016) ⚠️ BREAKING
SPASS-User-Id ist jetzt die alleinige Authority für end-user identity über alle 39 user-relevanten Endpoints. Body- und Query-user_id werden mit HTTP 400 invalid_field rejected. Tenant-Flag c1_require_user_id_binding aus Cut 2.19 (ADR 0014) ist obsolet — strict ist immer an, das Flag bleibt im YAML akzeptiert (no-op).
Caller-Migration (alle):
- Statt
{"user_id": "alice", ...}im Body /?user_id=alicein der Query:SPASS-User-Id: aliceals Header. tenant_admin-Token darf den Header für read-only-Endpoints weglassen → tenant-weite Reads (kein user-id-Filter).
Weitere Strukturen:
- Audit-Schema-v2:
user_idundroleals first-class Top-Level-Felder +v: 2Schema-Marker. Pre-existing v1-Files werden beim ersten Boot nachdata/audit/legacy_v1/archiviert.dgx-admin cost-reportliest nur v2. - Agent-Sessions sind jetzt user-scoped (
/a1/agents/*/sessions/*) — DB-Schema-Migrationagent_sessions.user_id NOT NULL DEFAULT ''. Pre-Cut-2.23c-Sessions (DEFAULT '') sind effektiv unzugänglich für end-users; Operator kann sie via SQL einsehen.
Volle ADR: docs/adr/0016-user-identity-authority-header-only.md.
2026-05-04 — Cut 2.23 + 2.23b: dgx-admin CLI + Cockpit-e2e Cross-Cloud
- Neuer Operator-CLI-Bin
dgx-admin(clap-derive, replaces ad-hoc shell-scripts) mit Subcommandcost-report(täglich/monatlich/per-user, EUR + tenant-markup, ECB-rate). Komponente insrc/admin/. Reports landen unterreports/cost/(gitignored). Volle Doku: CLI-Tools. cockpit-e2eerweitert um 4 cloud-coverage-Steps (claude/gpt/gemini/multi-tool, OPT-IN perRUN_CLOUD_E2E=1). 30 Steps total.
2026-05-04 — Cut 2.22: /v1 ↔ /c1 Functional Symmetry (ADR 0015)
/v1/chat/completions und /c1/chat sind jetzt funktional symmetrisch — identische SPASS-Header-Sets, identische Augment-Pipeline, identische Tool-Loop-Behavior. By-design-Asymmetrien (Persistenz, Body-shape, Bulk-Delete) sind in ADR 0015 abschließend aufgezählt. Parity-regression-Test in cockpit-e2e Step 24.
2026-05-03 — Cut 2.14: Model-Slug-Finalization (ADR 0008 v2)
What landed
- T0 force-tier (NEW) —
<model>-localfor force-on-prem (chains Spark1-FP4 → Station-FP8, no cloud-fallback) and<model>-cloudfor force-cloud (chains cloud-1 → cloud-2 → cloud-3, no local-fallback). Use for DSGVO / data-residency policies and operator-mandated cloud deployments. NewSpass-Resolved-Reason: force-backend-explicitvalue. - T5 vendor-latest aliases (NEW) —
<vendor>-<family>-latestas operator-pinned floating-version pointer. Initial pins (2026-05-03):anthropic-claude-opus-latest→claude-opus-4.7google-gemini-pro-latest→gemini-3.1-pro-previewgoogle-gemini-flash-latest→gemini-2.5-flashgoogle-nano-banana-latest→gemini-3.1-flash-image-preview-20260226openai-gpt-latest→gpt-5.5-pro
- Deprecated suffixes REMOVED (breaking) —
*-ollama,*-openrouter,*-nim,*-openrouter-freenow return400 model_not_in_allowlist. Migration:*-cloud-1/*-cloud-2/*-cloud-3/*-cloud-2-freerespectively, or use the unprefixed T1 alias with built-in fallback chain. info::canonical_alias(),info::backend_label_for_slug()updated;tier-slugs.spec.tsandinfo.rs::testscover the new T0/T5 paths and explicitly verify deprecated-rejection.
Trigger
Godelmann-Gocreate enterprise tenant setup (see
docs/godelmann-tenant-spec.md) — needs a models_allowlist with
both DSGVO-strict on-prem aliases and vendor-tracked cloud aliases.
2026-05-03 — Cut 2.13: Per-Tenant Configuration Hierarchy (ADR 0013)
What landed
Three-level configuration cascade per tenant:
- L1 process default (ENV → hardcoded)
- L2 per-tenant YAML in
tokens.yaml::tenants[].defaults(operator-edit + restart, validated on load) - L3 per-tenant SQLite (
tenant_configtable, runtime viaPUT /v1/tenant/config)
Settings are classified into three security groups:
- Group A — code-fixed (no override):
ALLOWED_MODELS, etc. - Group B — yaml-only (L1 + L2):
cost_markup_factor,models_allowlist/models_blacklist, per-token permissions/secrets. - Group C — full cascade (L1 + L2 + L3, runtime tunable):
compact_strategy,compact_keep_last_n,compact_observation_mask,compact_summary_model,image_gen_default_model,image_default_ttl_hours,image_max_ttl_hours,image_gen_rate_per_hour.
New endpoints
GET /v1/tenant/config— effective values + per-key source tag (process/yaml/db) +writable_keys/readonly_keyslists. Scope:tenant_config:read.PUT /v1/tenant/config— atomic multi-key set on Group-C keys. Scope:tenant_config:write.DELETE /v1/tenant/config/{key}— clear one L3 override. Scope:tenant_config:write.
New scopes
tenant_config:read, tenant_config:write (write implies read). Bumps
the catalog from 17 → 19 scopes. tenant_admin role auto-includes
tenant_config:write.
New error codes
tenant_config_key_readonly (400), tenant_config_invalid_value (400).
Integration changes
compact_sessionreadscompact_summary_modelfrom the cascade instead ofcfg.compact_summary_model_defaultdirectly.image_gentool readsimage_gen_default_model,image_default_ttl_hours,image_max_ttl_hours,image_gen_rate_per_hourfrom the cascade per-call.POST /a1/agents/<n>/sessionsbody fields now override the cascade per-session; missing fields fall through to cascade defaults.
Doc
New page: Per-tenant config. ADR 0013 in
docs/adr/. Multi-Tenant section + scope catalog updated.
2026-05-03 — Cut 2.12 follow-up: auto-compact + memory_describe_scope
- Auto-compact in
POST /a1/agents/<n>/sessions/<sid>/messages: before each chat-loop check token-estimate (chars/4 heuristic) over live messages × modelcontext_window(catalog lookup) → if> 80 %ANDstrategy=autoANDlive > keep_last_n, compact NOW and switch to successor for this request. Response carriesSPASS-Session-Compacted: <new-sid>+SPASS-Session-Compacted-Tokensso client persists the new id. Fail-soft on summary-model errors. memory_describe_scoperead-only discovery tool (counts + bytes + key-list, no values). Lets the LLM probe scope-state before deciding which keys tomemory_recall.
2026-05-03 — Cut 2.12: Compaction (Z-chain + C4-hybrid)
POST /a1/agents/<n>/sessions/<sid>/compactandGET /a1/agents/<n>/sessions/<sid>/lineageendpoints.- New session-row columns:
parent_session_id,successor_session_id,archived_at,compact_strategy,compact_keep_last_n,compact_observation_mask. Soft-delete flagcompacted_aton messages. Newsession_summariestable withcompacted_message_idxs. - 308 redirect on
POST .../messageswhen source session is archived → standard HTTP-clients follow transparently to the live successor. - Default summary-model
llama-4-scout(Datenschutz-default; configurable via cascade since Cut 2.13). - New error code
session_compact_conflict(409).
2026-05-03 — Cut 2.11: 9 RAG-CRUD stack-tools (B2 confirm + D4 preview)
Hybrid Option A boilerplate (per-tool file + shared rag_helpers.rs).
Read-only: rag_index_list, rag_index_describe, rag_doc_list,
rag_doc_get. Light-confirm writes: rag_index_create, rag_doc_append,
rag_doc_patch (B2 fingerprint-bound 60-s token + shape preview).
Heavy-preview destructives: rag_index_delete, rag_doc_delete (B2 +
D4 pre-exec-diff with text_excerpt/sample_doc_ids/etc.).
2026-05-03 — Cut 2.10: image_gen + list_models tools + /v1/images CRUD
image_gentool — text-to-image with per-tenant rate-limit, blob store with TTL sweeper, base64 thumbnail (~256 px) for inline LLM context.list_modelsdiscovery tool —categoryfilter (chat / image-gen / embedding / stt / all)./v1/imagesCRUD — list, GET blob, GET metadata, DELETE.- Documented two
nano-bananaquirks (auto-trigger phrase +max_tokensblocks image-gen) indocs/CHAT.mdQ2.
2026-05-03 — Phase 2 (.5–.9): /a1 agent system, RAG, sessions, cost-aggregation
Cuts landed today
- 2.4 + 2.4.b — RAG: tenant-scoped indices, in-memory
InMemoryVectorIndexon top ofnomic-embed-text-v1.5, persistence indata/sqlite/rag/<tenant>.sqlite. Restart-survival verified. - 2.5 — Multi-turn sessions:
POST/GET/DELETE /a1/agents/<name>/sessions, per-tenant SQLite (agent_sessions), agent-pinned at creation. rig'sChattrait drives multi-turn with persisted history. - 2.6 — Test-coverage audit: cross-tenant isolation, ADR-0009 leak-checks on /a1 surfaces, recursion-depth boundary, audit parent-correlation, agent matrix (× tools × rag).
- Cut C — Tool-using agents pinned to
claude-opus-4.7(Llama-4-Scout pythonic-parser quirk: emits text-form tool-calls instead of structured). - 2.7 — Aggregated cost-headers on /a1-outer response. In-process
CostAggregatorrecords per-sub-call,/a1drains before responding. New headerSPASS-Cost-Sub-Calls. - 2.8 —
rag_querystack-tool: any LLM-call (/v1,/c1,/a1) can invoke active-RAG. Tenant-scoped viaToolCtx.identity. Bundledlibrarianagent withtools: [rag_query]. - 2.9 — Document-level CRUD on RAG indices:
append,patch, single- docdelete,list,get.
New error codes
agent_not_found (404), agent_tool_not_registered (400),
recursion_depth_exceeded (400), index_not_found (404),
document_not_found (404), session_not_found (404),
session_agent_mismatch (400), embedding_input_too_large (400).
New SPASS-* headers
SPASS-Caller-Depth (recursion-protection),
SPASS-Parent-Request-Id (audit-tree correlation),
SPASS-Cost-Sub-Calls (aggregator debug).
ADRs
- 0011 —
/a1routing via internal/v1self-call (Path Y). - 0012 — RAG-Foundation Phase 1:
nomic-embed-text-v1.5Station-primary- Spark1-fallback.
Test inventory after Phase 2
- Unit-tests (cargo): 167
- Playwright e2e: 129 (was 86 before Phase 2)
- Phase-2-dedicated: 51 across 6 spec files
2026-05-02 — Cockpit-Bug-Reports F/R/S + ADRs 0006v2/0007/0008/0009/0010
F-Series (streaming + cost-passthrough)
- F.1:
stream_options.include_usage: truedefault-injected when caller sendsstream: truewithout it. - F.2: cost-pipeline V1 with
Spass-Cost-Usd/-Available/-Source/ (-Is-Byoklater removed in V2). - F.3:
Spass-Resolved-Modelreturns canonical alias, not upstream-hash. - F.4:
Spass-Fallback-Usedderived from actual-vs-primary backend. - F.5: caching
prompt_tokens_details.cached_tokensdocumented. - F.6: latency baseline added to
/docs/CHAT.md.
R-Series (routing transparency)
- ADR 0008: 4-tier slug-schema T1/T2/T3/T4 (Quality / Quant / Cloud /
Hardware-direct). Default-chain reversed for
llama-4-scout: Station-FP8 first, Spark1-FP4 second. Spass-Resolved-Backendexposed with generic slot labels (local-fp4/local-fp8/cloud-N) — no implementation-leak.- ADR 0006 v2: implementation-name verschleierung erweitert über Header hinaus (catalog body, OpenAPI description, error messages).
- ADR 0009: hard-fail E2E lint against implementation-leak strings in caller-facing JSON.
S-Series (Spark1 tool-template + 502 cascade)
- S.1: vLLM
--enable-auto-tool-choice+--tool-call-parser=pythonic+--chat-template=tool_chat_template_llama4_pythonic.jinjafor Llama-4 tool-loop support. - 502-Cascade-fix: upstream 4xx propagated as 4xx (
upstream_bad_request) instead of opaque 502 + sanitized error-message via<gateway>/<inference>placeholders. - Pre-flight base64 validation for inline image-payloads
(
image_base64_invalid400 with JSON-pointer). - Defense-in-depth Layer 1:
tool_calls[].function.argumentsJSON-validate (tool_call_arguments_invalid400). - Defense-in-depth Layer 2:
data/audit/debug.jsonl.YYYY-MM-DD— every 4xx/5xx response captures the full request body for post-mortem. - Layer 3 quick-win:
enable_pre_call_checks: truein litellm config for context-window-enforcement.
Cost-Pipeline V2 (ADR 0010)
- EUR-Anzeige via ECB-rate (Frankfurter API stündlich ±10min jitter)
- 5%-Umrechnungsaufschlag + per-tenant markup-factor (default 1.5, Cockpit 1.0, Godelmann 1.1 TBD).
- Lookup-Hierarchie: latest <25h → 30-day-max → hardcoded
1 EUR = 1.25 USD. - INSERT-only SQLite history (
data/cost/exchange_rates.sqlite). - 6 cost-headers:
Spass-Cost-{Eur, Usd, Available, Source, Exchange-Rate, Exchange-Rate-Source}. - Privacy: original-cost + markup-details NICHT exposed, NUR im
audit.jsonlmitevent=cost. Spass-Is-Byokheader entfernt (Stack hat kein BYOK).
2026-05-02 — UI/CSS audit fixes (Cockpit-followup-7 Phase A)
- Dark-mode via
@media (prefers-color-scheme: dark)+ token-flip in/static/design-system.css— alle 17 pages reagieren auf user-preference. - Swagger version-label contrast-fix (1.11 → 4.5+ ratio).
- ReDoc theme.colors override (success/error/primary darker, 4.5+ ratio gegen row-stripe-grey).
/errorscontent sanitized (was leaked LiteLLM/vLLM/Anthropic/etc).playground.html+playground-stt.htmlfont-imports von Google CDN auf self-hosted vendor entfernt — kein CORS-Risk mehr mit Authorization-Header.
2026-04-29 — rust-api ausbau, performance-baseline
- Themed
/openapi,/redoc,/playground,/playground/stt,/docs/*— single design-system.css. - Self-hosted swagger-ui + redoc + Fira fonts (vendor/).
- Conversation-cache LRU (C-3/C-4/C-5 closed in PERFORMANCE.md).
- Pre-warm-task fixes
/readyzcold-start race. - 3-Maschinen-Architektur recherchiert + dokumentiert in
docs/PHASE-1-GATEWAY.md.
2026-04-28 — driver-595 + FP4-Modell
- Globale OOMs mit FP8-Llama-4-Scout (~109 GB) auf Spark unified 121 GB. Driver-Upgrade 580 → 595, Switch auf NVFP4-quant (~62 GB).
- Memory-Discipline-Memo + helper
bin/vllm-restart.sh(drop_caches pre-start workaround für vLLM Issue #35313).
See also
- ADR Index — full ADR-list 0001–0010
/v1/info— current model catalog/api/version— live build-SHA