Files
cluster-knaster/docs/spec/SPEC-core.md
T

9.7 KiB
Raw Blame History

SPEC-core: Registry, Skills, Router, Ausführung, Verifier

Paket: core/src/knaster. Alle Schnittstellen zu Modellen laufen über das OpenAI-kompatible API von LiteLLM.

1. Registry (knaster.registry)

Deklarativer Teil (Git): sites/<cluster>/registry.yaml. Laufzeitzustand (DB): slot_state.

slots:
  tier1-general:
    tier: 1                 # 0 = stark/extern, 1 = schwaches LLM, (Stufe 2/3 brauchen keinen Slot)
    device: gpu             # gpu | cpu | external
    engine: vllm            # vllm | llamacpp | openrouter | ...
    node: gpu-main          # Rolle/Node, für Healer und Placement
    litellm_model: tier1-general
    base_model: qwen3-14b-awq
    max_context: 32768
    effective_context: 24000   # Startwert; Kalibrierung überschreibt im Laufzeitzustand
    supports_lora: true
    fallbacks: [tier1-flex]    # nur bei Infrastruktur-Fehlern, nicht bei Qualitätsfehlern
adapters:
  - name: rechnungen-v3       # = Modellname, unter dem vLLM den Adapter bereitstellt
    base_model: qwen3-14b-awq
    cluster: rechnungen
    version: 3
    status: active            # active | candidate | retired

Laufzeitzustand pro Slot: status ∈ {ok, degraded, unavailable}, effective_context (kalibriert, optional), reason, updated_at. Für den Router gilt: Ein Slot ist verfügbar, wenn status == ok.

2. Skills (knaster.skills)

Dateiformat ist SKILL.md mit YAML-Frontmatter, kompatibel zu agentskills/Hermes (name, description). Eigene Felder stehen unter metadata.knaster:

Feld Typ Default Bedeutung
version int 1
status draft/active/demoted/retired draft nur active und demoted werden geroutet
stage 0–3 0 freigegebene Ausführungsstufe
execution llm/program/classifier llm Art der Ausführung ab Stufe ≥ 1
slot str tier1-general Ziel-Slot für Stufe 1
cluster str = name Adapter-Cluster
adapter auto/none auto Adapter verwenden, falls einer aktiv ist
threshold float 0.80 minimale Ähnlichkeit
triggers list[str] [] zusätzliche Beispielformulierungen fürs Embedding
output.format text/json text
output.schema JSON-Schema – für den Verifier
verifier list [] Prüfungen, siehe §5
max_output_tokens int 1024
program str – Name des registrierten Programms (Stufe 2)
classifier str – Name des Classifiers (Stufe 3)

Body: Markdown-Anleitung. Abschnitte, deren Überschrift mit ## Beispiele beginnt, gelten als optional. Die kompakte Variante des Skills enthält sie nicht.

demoted bedeutet: Der Skill wird erkannt, aber auf Stufe 0 ausgeführt, mit dem Skill als Hinweis.

3. Router (knaster.router)

Eingabe ist RouteRequest{input: str, hints?: {skill?: str}}. Ausgabe ist RouteDecision:

stage, slot?, model?, skill?, skill_version?, adapter?, prompt_variant (full|compact|none),
similarity?, estimated_tokens, reasons[], signals[], attempt

Ablauf:

  1. Embedding der Eingabe über EmbeddingClient (LiteLLM /v1/embeddings, Slot embed-default).
  2. Kandidaten: Score pro Skill = max(cos(q, description), cos(q, trigger_i)). Davon die Top-k (k = 5).
  3. Optionales Reranking der Top-k (Reranker, llama.cpp /v1/rerank). Die Reihenfolge folgt dann dem Rerank-Score, der Schwellwert prüft weiterhin die Cosine-Ähnlichkeit.
  4. Gibt es keinen Kandidaten oder liegt die Ähnlichkeit unter threshold, wird mit no_skill auf Stufe 0 geroutet.
  5. Konflikt: Liegt der Zweitplatzierte innerhalb von conflict_margin (0,02) und ist kein Reranker aktiv, wird mit skill_conflict auf Stufe 0 geroutet. Ist ein Reranker aktiv, wird nur das Signal gesetzt.
  6. Bei status == demoted oder stage == 0 geht es auf Stufe 0, mit Skill als Hinweis.
  7. Stufe 3/2: Classifier bzw. Programm, ohne Slot.
  8. Stufe 1: Slot des Skills, falls verfügbar, sonst dessen fallbacks. Ist keiner verfügbar, wird mit slot_unavailable auf Stufe 0 geroutet.
  9. Token-Budget (§4): erst full versuchen, dann compact, dann die Fallback-Slots mit größerem Kontext. Passt nichts, wird mit context_exceeded auf Stufe 0 geroutet. Passt es auch auf Stufe 0 nicht, ist das Ergebnis ein Fehler too_large mit dem Signal needs_split.
  10. Adapter: Wenn slot.supports_lora, skill.adapter == auto und ein aktiver Adapter für (slot.base_model, skill.cluster) existiert, wird model = "<slot.litellm_model>/<adapter.name>" gesetzt (LiteLLM-Wildcard-Route auf den vLLM des Slots), sonst model = slot.litellm_model.
  11. Canary: Hat der Slot im Laufzeitzustand ein canary_model (vom Lifecycle gesetzt), geht der Anteil canary_share der passenden Anfragen auf den Kandidaten. Bei Adapter-Kandidaten gilt das nur für den Cluster des Kandidaten. Die Entscheidung trägt dann canary: true.

escalate(decision, reason):

  • Bei Infrastruktur-Fehlern (endpoint_error, context_overflow) wird auf derselben Stufe der nächste Fallback-Slot genommen, sonst wird eine Stufe höher gegangen.
  • Qualitätsfehler (verifier_failed, invalid_format, degenerate_output, output_truncated, classifier_low_confidence, program_error) eskalieren: 3/2 → 1 (falls der Skill execution: llm als Rückfall erlaubt, sonst → 0) und 1 → 0.
  • Maximal 3 Eskalationen. Auf Stufe 0 ist danach Schluss, das Ergebnis lautet failed.

4. Token-Budget (knaster.budget)

estimate_tokens(text) ist ein konservativer Schätzer (ceil(len/3)) und ersetzbar durch einen echten Tokenizer pro Slot. Bedarf = Systemprompt + Eingabe + max_output_tokens + Overhead (64). Ein Prompt passt, wenn Bedarf ≤ effective_context des Slots (Laufzeitwert vor Registry-Wert).

5. Ausführung (knaster.executor) und Verifier (knaster.verifier)

Executor:

  • Stufe 1/0: Nachrichten bauen (System = Skill-Body in der gewählten Variante plus Formatanweisung, User = Eingabe), dann POST /v1/chat/completions.
  • Stufe 2: registriertes Programm fn(input:str) -> str.
  • Stufe 3: EmbeddingClassifier.predict(vector) -> (label, confidence). Liegt die Konfidenz unter dem Minimum, wird classifier_low_confidence gemeldet.

Erkannte Signale (knaster.problems.Signal):

Signal Erkennung
output_truncated finish_reason == "length"
degenerate_output Anteil wiederholter 4-Gramme > 0,5 bei ≥ 40 Wörtern
invalid_format Format json, aber nicht parsebar
context_overflow HTTP 400 mit „context length“ / „maximum context“ / „too many tokens“ im Text
endpoint_error Timeout, Verbindungsfehler, HTTP 5xx/429

Verifier-Prüfungen (metadata.knaster.verifier): json_schema (gegen output.schema), regex {pattern}, contains {value}, not_contains {value}, max_chars {value}, judge {slot, rubric} (das LLM antwortet PASS oder FAIL: Grund). Ergebnis ist VerifyResult{passed, checks[{type, passed, detail}]}. Bei Format json wird json_schema immer geprüft, falls ein Schema vorhanden ist.

6. Fälle und Datensätze (knaster.cases)

  • Tabelle cases: id, ts, input, skill, skill_version, stage, slot, model, adapter, messages (JSON, modellneutral), output, passed, signals, split (train/eval, deterministisch über sha256(input) mod 10 < 2 → eval), origin (route/graduation/shadow).
  • Tabelle preference_pairs: Entsteht, wenn in einem /v1/run eine Stufe ≥ 1 durch den Verifier fällt und eine höhere Stufe besteht. Inhalt: prompt messages, rejected, chosen, skill, split.
  • Export als JSONL im Format {"messages":[...], "meta":{...}} bzw. {"prompt":[...],"chosen":"...","rejected":"..."}.

7. Kalibrierung (knaster.calibration)

effective_context(observations, target=0.9, min_n=20, buckets=[2k,4k,8k,16k,32k,64k,128k]): Die Beobachtungen (Prompt-Tokens, bestanden) werden in Buckets gruppiert. Ergebnis ist die Obergrenze des größten Buckets, für den gilt: Dieser und alle kleineren Buckets mit n ≥ min_n haben eine Pass-Rate ≥ target. Ohne ausreichende Daten ist das Ergebnis None, und der Registry-Wert bleibt.

8. HTTP-API (knaster.api)

Methode Pfad Zweck
GET /health Liveness
GET /metrics Prometheus
POST /v1/route RouteDecision
POST /v1/run komplette Schleife: Route → Ausführung → Verifier → Eskalation, dazu Fall speichern
GET /v1/registry Slots plus Laufzeitzustand
POST /v1/registry/slots/{slot}/state Zustand setzen (Healer/Mensch)
POST /v1/maintenance/routine Service-Routine starten (SPEC-maintenance)
POST /v1/maintenance/command Prompt-Befehl
GET /v1/maintenance/proposals offene Vorschläge
POST /v1/healer/alertmanager Alertmanager-Webhook (SPEC-healer)
GET/POST /v1/lifecycle/candidates Kandidaten (SPEC-lifecycle)
POST /v1/lifecycle/candidates/{id}/report Ergebnis eines Jobs melden
POST /v1/skills/{name}/graduate Graduierungslauf mit mitgelieferten Varianten
POST /v1/skills/reload Skill-Dateien neu laden (nach Git-Pull)
POST /v1/maintenance/proposals/{seq}/approve bzw. /reject Freigabe per Knopf (n8n/UI) statt per Prompt

9. Canary-Prober (knaster.canary)

Läuft im Service-Prozess (KNASTER_CANARY_INTERVAL, Standard 60 s, 0 = aus). Er prüft jeden Slot außer Stufe 0 und Rerank mit einer Mini-Anfrage („Antworte nur mit OK“) bzw. einem Embedding. Daraus entstehen die Metriken knaster_slot_endpoint_up, knaster_slot_canary_ok und knaster_slot_canary_latency_seconds, jeweils mit den Labels slot, node, docker_host, container, pve_node, vmid aus ops der Registry. Daraus leiten sich die Alerts SlotEndpointDown und SlotCanaryFailing für den Healer ab.

Authentifizierung: Header Authorization: Bearer <KNASTER_API_TOKEN> auf allen /v1/*-Pfaden.