# 03 — Modelová vrstva: routing, fallback, circuit breaker, auto-registrace

Dokumentační průzkum. Datum průzkumu: **2026-10-02**. Všechny internetové zdroje byly načteny
k tomuto datu; u sekundárních zdrojů je uvedeno, zda jde o oficiální dokumentaci, nebo o
sekundární materiál.

**Hygiena dotazů:** Do vyhledávání šly výhradně obecná veřejná terminologie a názvy veřejných
produktů (LiteLLM, OpenRouter, Anthropic, OpenAI, Gemini, Ollama, llama.cpp, Modelfile, …).
Nebyl odeslán žádný kód, název třídy, cesta k souboru ani obsah lokálních souborů tohoto
repozitáře. Interní názvy provider konfigurací z projektu jako dotazy nebyly použity.

**Znacení:**

- `[LOCAL CODE] cesta:řádky` — tvrzení ověřené čtením kódu v tomto repozitáři
- `[PRIMARY DOC] URL + datum` — oficiální dokumentace
- `[SECONDARY] URL` — kvalitní sekundární zdroj (inženýrský průvodce, výzkum, vendor blog)
- `[NEOVĚŮENO]` — hypotéza, kterou nelze potvrdit bez živého běhu

---

## A) Srovnávací tabulka

| Prvek | Současná implementace `[LOCAL CODE]` | Běžný vzor v praxi | Hodnocení |
|---|---|---|---|
| Routingové strategie | 6 strategií v enumu `RouteStrategy`: `cost_optimized`, `performance`, `reliability`, `local_first`, `capability`, `round_robin` — `src/cyberbrain26/model_layer/router.py:28-36`, dispatch `router.py:181-194` | LiteLLM: `simple-shuffle` (default, **doporučený pro produkci**), `usage-based-routing-v2`, `latency-based-routing`, `least-busy`, `cost-based-routing`, `order` (priorita), `weight` `[PRIMARY DOC] https://docs.litellm.ai/docs/routing` (2026-10-02). OpenRouter: `order`, `sort` (price/throughput/latency), `allow_fallbacks`, `data_collection`, `zdr` `[PRIMARY DOC] https://openrouter.ai/docs/guides/routing/provider-selection` (2026-10-02) | **podezřele** — chybí `simple-shuffle`/`weight` a priorita `order`; většina strategií je nerozlišitelná |
| Default strategie | `default_strategy = COST_OPTIMIZED` — `router.py:43`; gateway to posílá jako `strategy="cost_optimized"` — `control_plane/model_gateway/__init__.py:47,429` | LiteLLM default je `simple-shuffle`, nikoli cost-based; cost-based je asynchronní varianta vyžadující cenovou mapu `[PRIMARY DOC] https://docs.litellm.ai/docs/routing` | **podezřele** — default není doporučený vzor z upstreamu |
| Skórování modelu | Dvě různé funkce: `_basic_score` (`router.py:96-114`) a `ModelIntelligence.score_model` (`model_intelligence.py:272-316`). Gateway vždy předá `model_intelligence`, takže `_basic_score` je mimo gateway **nedosažitelná** | Jedna autoritativní skórovací funkce; LiteLLM používá `litellm_model_cost_map` + `input_cost_per_token`/`output_cost_per_token` `[PRIMARY DOC] https://docs.litellm.ai/docs/routing` | **podezřele** — duplicitní logika, mrtvý kód |
| „Cost optimized" při lokálním provideru | `_route_cost_optimized` — `router.py:326-350`: k `_score_model` přičte `+0.15` za `local_available` a `+0.1` za `is_free` | LiteLLM `cost-based-routing` používá reálnou cenu z cenové mapy; model mimo mapu se bere jako `$1` `[PRIMARY DOC] https://docs.litellm.ai/docs/routing` | **podezřele** — bonusy jsou konstanty, ne cena; u všech tří providerů jsou cloud i lokální modely `free_status=FREE`, takže „cena" je de facto rovna |
| `performance` strategie | `router.py:352-363` — identické skórování jako `_basic_score`/`score_model`, žádná latence | LiteLLM: `latency-based-routing` s TTL oknem (`routing_strategy_args.ttl`) a bufferem `lowest_latency_buffer` `[PRIMARY DOC] https://docs.litellm.ai/docs/routing` | **chybí** — „performance" je přejmenované `reliability`, latence se nikdy neměří |
| `reliability` strategie | `router.py:365-381` — jen `+0.2` za `official`/`verified` trust | LiteLLM: `allowed_fails` + `cooldown_time` (default 3 / 30 s) řeší dostupnost, ne důvěryhodnost `[PRIMARY DOC] https://docs.litellm.ai/docs/routing` | **podezřele** — spolehlivost zaměněna za důvěryhodnost vydavatele |
| `local_first` | `router.py:383-398` — pool = lokální modely, jinak vše | Odpovídá `prefer_local` v gateway `model_gateway/__init__.py:273-277`; ale `chat()` používá `_resolve_route()`, ne router | **správně v izolaci, neefektivně v praxi** — viz oddíl F |
| `round_robin` | `router.py:434-449` — inkrementální index modulo počet kandidátů, bez váhy | LiteLLM `weight`/`rpm`/`tpm`-založené shuffle `[PRIMARY DOC] https://docs.litellm.ai/docs/routing` | **podezřele** — kandidáti nejsou tříďeni stabilně; při změně sady kandidátů se rozložení láme |
| `capability` | `router.py:400-432` — filtrování v `_get_candidates` + směšování 50/50 se skóre | LiteLLM: filtrování nasazuje `required_params`, region, kontext `[PRIMARY DOC] https://docs.litellm.ai/docs/proxy/reliability` | **správně** — jediná strategie, která má reálnou filtrační semantiku |
| Propojení routeru s voláním | `ModelGateway.chat()` volá `_resolve_route()` (`model_gateway/__init__.py:244, 260-287`), který router **obchází** a vybírá provider podle `prefer_local`/statusu. `router.route()` volá jen veřejné API `gateway.route()` (`model_gateway/__init__.py:415-447`) | LiteLLM: router je jediná vstupní cesta, strategie se aplikuje na skutečný dispatch `[PRIMARY DOC] https://docs.litellm.ai/docs/routing` | **podezřele / fakticky nefunkční** — `RouteStrategy` nemá vliv na reálné volání |
| Fallback chain | `RouterConfig.fallback_chain` existuje (`router.py:44`), ale **nikde se nečte**. Místo toho `_get_fallback_candidates()` (`router.py:303-324`) skóruje všechny aktivní modely a seřadí je; `max_fallback_depth=5` (`router.py:47`) ořezuje seznam | LiteLLM: explicitní `fallbacks=[{...}]` mapa, `default_fallbacks`, `content_policy_fallbacks`, `context_window_fallbacks`, `max_fallbacks` default 5 `[PRIMARY DOC] https://docs.litellm.ai/docs/proxy/reliability` (2026-10-02) | **chybí** — deklarovaná konfigurace je mrtvá; chybí dělení na typy chyb |
| Fallback v gateway | `_fallback_chat()` (`model_gateway/__init__.py:325-349`) projde `config.fallback_providers` a znovu zavolá **stejný `model`** jiným providerem | Fallback musí mít vlastní mapování model→provider; LiteLLM fallback je na úrovni *model group* `[PRIMARY DOC] https://docs.litellm.ai/docs/proxy/reliability` | **podezřele** — cloudový model (např. `gpt-4o`) se posílá do lokálního Ollama, kde neexistuje → fallback skoro vždy znovu selže |
| Circuit breaker – stavy | Jen binární `open: bool` + `failures: int` + `last_failure: float` — `router.py:86, 133-147`. Žádný stav `HALF_OPEN`, žádný limit sond | Tři stavy CLOSED/OPEN/HALF_OPEN: Resilience4j `[SECONDARY] https://resilience4j.readme.io/v0.17.0/docs/circuitbreaker`; Azure Architecture Center `[SECONDARY] https://learn.microsoft.com/en-us/azure/architecture/patterns/circuit-breaker`; SmallRye `[SECONDARY] https://smallrye.io/docs/smallrye-fault-tolerance/6.11.1/reference/circuit-breaker.html` | **chybí** — half-open s omezeným počtem sond |
| Circuit breaker – práh | `circuit_breaker_threshold = 3` (absolutní počet) — `router.py:49` | Procento z rolling window: SmallRye `failureRatio=0.5`, `requestVolumeThreshold=20`; Resilience4j `failureRateThreshold=50 %`, `ringBufferSizeInClosedState=100`; systémový průvodce doporučuje „50 % z posledních 20 požadavků" | **podezřele** — absolutní počet se při nízkém provozu chová jako „1 chyba = výpadek" |
| Circuit breaker – okno | Žádné; `_record_success` (`router.py:144-147`) nuluje counter při **jediném** úspěchu | Azure: failure counter je časově omezený a samočinně se resetuje, aby občasné chyby nespálily jistič `[SECONDARY] https://learn.microsoft.com/en-us/azure/architecture/patterns/circuit-breaker` | **chybí** — jeden šťastný požadavek vymaže historii selhání |
| Circuit breaker – čas | `circuit_breaker_timeout = 30.0` s — `router.py:50,126` | Resilience4j `waitDurationInOpenState=60 s`; SmallRye `delay=5 s`; průvodce „30–60 s" `[SECONDARY] https://systemdesign.ops4life.com/guides/circuit-breaker/` | **správně v řádu** — 30 s je běžná hodnota |
| Circuit breaker – zapojení do volání | `_record_failure()` se volá **jen** v `route_with_fallback()` při `ModelRouterError` (`router.py:228`). `ModelGateway.chat()` (`model_gateway/__init__.py:246-258`) chyby jističi **nehlasuje** | Jistič musí obalovat skutečné volání, jinak je neúčinný `[SECONDARY] https://learn.microsoft.com/en-us/azure/architecture/patterns/circuit-breaker` | **chybí / kritické** — jistič se v produkční cestě nikdy neotevře |
| Circuit breaker – filtrování kandidátů | `_get_candidates` filtruje (`router.py:296-299`), ale `_get_fallback_candidates` **nefiltruje** (`router.py:303-324`) | Otevřený jistič má být vyloučen ze všech sad kandidátů | **podezřele** — fallback může vybrat providera s otevřeným jističem |
| Retry | `LiteLLMConfig.max_retries=3`, `timeout=60.0` (`litellm_client.py:19-20`); Ollama `timeout=60.0` (`ollama_client.py:21`); llama.cpp `timeout=600.0` (`llama_cpp_client.py:20`). Retry/timeout se předávají do volání LiteLLM, u httpx klientů jen jako timeout | LiteLLM: `num_retries: 3`, `request_timeout: 10`, `allowed_fails: 3`, `cooldown_time: 30` jako jeden koherentní blok `[PRIMARY DOC] https://docs.litellm.ai/docs/proxy/reliability` | **podezřele** — timeout 600 s na lokální inference vs 60 s na cloud; žádná společná politika |
| `health_check_interval` | Deklarováno `router.py:48`, **nikde se nečte** (grep potvrdil jediný výskyt) | Periodický health check s intervalem, který se skutečně pouští | **chybí** — mrtvá konfigurace |
| `ModelDiscoveryConfig.check_interval_hours` | Deklarováno `model_intelligence.py:41`, **nikde se nečte**; žádný plánovač/periodicita neexistuje | Periodické zjišťování modelů jako samostatná úloha | **chybí** — deklarovaná 24h perioda není realizovaná |
| `ModelDiscoveryConfig.auto_register` | `True` ve výchozím stavu (`model_intelligence.py:43`); registrace probíhá **před** validací (`model_intelligence.py:335-340`) | OWASP AISVS V6: allowlist schválených zdrojů, pinning revizí (commit SHA), vnitřní zrcadlo, review identity vydavatele `[SECONDARY] https://github.com/OWASP/AISVS/blob/main/research/chapters/C06-Supply-Chain/C06-Supply-Chain.md` | **podezřele / kritické** — automatická registrace před validací, bez allowlistu a pinu |
| Validace metadat | `validate_metadata()` kontroluje jen prázdné `model_id`/`provider` a záporné `context_window`/`max_tokens` (`model_intelligence.py:193-213`) | Validace provenance: checksum, podepsaný artefakt, revize, licence | **chybí** |
| `test_connectivity` | Čte **příznak** `provider.api_availability or model.local_available` (`model_intelligence.py:215-234`) — žádný síťový test | Health check musí reálně ověřit dosažitelnost | **podezřele** — tautologický test, nic neověřuje |
| `functional_test` | `passed = registered and status == ACTIVE` (`model_intelligence.py:249-251`). Po `auto_register` je `registered` vždy `True` | Funkční test musí poslat prompt a ověřit odpověď | **podezřele** — tautologie |
| Deduplikace | `deduplicate()` klíčuje pouze podle `model_id`, ignoruje provider (`model_intelligence.py:180-187`); navíc **nikde se nez volá** z `run_pipeline()` | Klíč musí zahrnovat `(provider, model_id)`, jinak se ztrácí varianty napříč providery | **podezřele** |
| Klíč registru | `ModelRegistry.register()` generuje **nový unikátní klíč pokaždé** s inkrementem: `f"{provider}:{model_id}#{counter}"` (`model_registry.py:163-185`) | Idempotentní (provider, model_id) → aktualizace, ne přímnožení | **podezřele** — opakovaná discovery by registry neomezeně nafukovala |
| Prompt caching | Žádná podpora. Chybí `cache_control`, `cache_control_options`, `prompt_cache_key`, `keep_alive`, `num_ctx` v tělech požadavků (`ollama_client.py:112-116, 156-160`; `llama_cpp_client.py:99-109`; grep na `keep_alive`/`cache` v `model_layer/` nic nevrací) | Anthropic: `cache_control` breakpointy, 5 min / 1 h TTL `[PRIMARY DOC] https://platform.claude.com/docs/en/build-with-claude/prompt-caching` (2026-10-02); OpenAI: automatické explicitní/implicitní cache breakpointy, min. 1 024 tokenů, čtení 0,1×, zápis 1,25× `[PRIMARY DOC] https://developers.openai.com/api/docs/guides/prompt-caching` (2026-10-02); Gemini: implicitní (2 048–4 096 tok.) a explicitní cache s TTL `[PRIMARY DOC] https://ai.google.dev/gemini-api/docs/generate-content/caching` (2026-10-02); Ollama: `keep_alive` default 5 min `[PRIMARY DOC] https://docs.ollama.com/faq` (2026-10-02); Ollama runner: prefix reuse napříč sloty KV cache `[SECONDARY] https://github.com/ollama/ollama/blob/cb003359/runner/llamarunner/cache.go` | **chybí** |
| Řízení kontextového okna | Žádné. `ModelRecord.context_window` se jen vyčte ve skóru (`model_intelligence.py:290-298`) a ve validaci; žádný limit na délku zprávy, žádná komprese, žádná sumarizace. CLI posílá `max_tokens=512` a 2 zprávy na krok (`cli.py:160-166`) | LangChain Deep Agents: offload velkých tool výstupů, prahová komprese na 85 % okna, LLM sumarizace `[SECONDARY] https://www.langchain.com/blog/context-management-for-deepagents`; AWS: sliding window vs sumarizace, spouštění na 50–70 % využití `[SECONDARY] https://github.com/awslabs/fullstack-solution-template-for-agentcore/blob/5c9446bf/docs/CONTEXT_MANAGEMENT.md` | **chybí** |
| Session affinity pro cache | Chybí | LiteLLM `session_affinity` — připne konverzaci k deploymentu, aby provider-side cache fungoval `[PRIMARY DOC] https://docs.litellm.ai/docs/routing` | **chybí** |
| Systémový prompt | `lang_prompts` ve CLI: 3 jazyky, fallback na `en` (`cli.py:154-159`); systémová zpráva se skládá s `step.description` a jde do `gateway.chat(model=model, ...)` (`cli.py:160-166`) | System prompt jako stabilní **prefix** kvůli cache; u lokálních providerů `SYSTEM` z Modelfile může být přepsán požadavkem | **podezřele** — prompt je stabilní, ale cache ho nevyužije; Ollama/llama.cpp klienti systémovou zprávu odděleně nepřenášejí |
| Testy routeru | `tests/test_model_layer.py:597-661` — 6 strategií, specific model, no-candidates, circuit breaker, fallback, historie. Defaulty klientů `:672-699, 710-742, 752-787` | Testy mají ověřovat výsledek strategie, ne jen to, že se něco vrátí | **podezřele** — `test_route_cost_optimized` jen tvrdí `model_id is not None`; `test_circuit_breaker` (`:642-651`) volá privátní metody přímo, ne produkční cestu |
| Test s reálnou sítí | `tests/test_model_layer.py:771-782` (`test_is_available` u llama.cpp klienta) dělá živý `httpx.get("http://127.0.0.1:8080/")` a porovnává s výsledkem klienta | Testy jednotkové vrstvy nemají dělat síťové volání | **podezřele** — test je environmentálně závislý |

---

## B) Routingové strategie — co přesně dělá ta v repu

### B.1 Sjednocené skórování (skutečné jádro)

Všech šest strategií se opírá o `_score_model()` (`router.py:90-94`), která v gateway deleguje na
`ModelIntelligence.score_model()` (`model_intelligence.py:272-316`). Skóre je součet:

| Složka | Body | Řádek |
|---|---|---|
| `free_status == FREE` | +25 | `model_intelligence.py:276-277` |
| `FREE_FOR_TEST` / `FREE_FOR_STUDY` | +15 / +10 | `:278-282` |
| `OFFICIAL` / `VERIFIED` / `COMMUNITY` trust | +25 / +20 / +10 | `:283-288` |
| `context_window` ≥128k / ≥32k / ≥8k / ≥2k | +20 / +15 / +10 / +5 | `:290-298` |
| schopnosti (6 flagů) | max +20 | `:300-309` |
| `status == ACTIVE` | +10 | `:311-312` |

Výsledek se dělí stem a ořízne na 1.0 (`:316`). **Nejsou v tom latence, dostupnost, reálná cena,
historická úspěšnost ani zpětná vazba z reálného provozu.**

`ModelGateway` navíc registruje všechny cloudové modely jako `free_status=ModelFreeStatus.FREE`
a `trust=ModelTrust.OFFICIAL` (`model_gateway/__init__.py:183-198`). Tedy i `gpt-4o` a
`claude-3-5-sonnet` dostávají +25 za „free" a +25 za „official". V tomto stavu je **sloupec
„free" v registru informativní nula** a nedává smysl v žádné strategii.

### B.2 `cost_optimized` — výchozí strategie

`_route_cost_optimized()` (`router.py:326-350`) bere kandidáty, přičte `+0.15` za `local_available`
a `+0.1` za `is_free`, seřadí a vrátí prvního.

**Proč je „cost optimized" při převážně bezplatných providerech smysluplné jen částečně:**
Konstantní bonusy vyjadřují kategorii „je to zdarma / je to lokální", nikoli **cenu**. V prostředí,
kde jsou všichni tři providery (lokální inference, cloudový gateway) v registru označeny jako
free, je toto kritérium **bezrozlišující**. Správná analogie by vyžadovala cenovou mapu
(`input_cost_per_token` / `output_cost_per_token`) jako v LiteLLM `cost-based-routing`
`[PRIMARY DOC] https://docs.litellm.ai/docs/routing`, kde se i model mimo cenovou mapu bere
za `$1`. Navíc `RouterConfig.prefer_free=True` (`router.py:46`) a `prefer_local` default `False`
(`router.py:45`) se v gateway přepisuje z `ModelGatewayConfig` (`model_gateway/__init__.py:83-86`),
kde je `prefer_local=True` — dva zdroje pravdy pro stejný přepínač.

Pro čistě lokální prototyp, kde cloudový provider není používán, je `local_first`
nebo prosté statické pravidlo „nejprve lokální" jednodušší a determinističtější než
`cost_optimized`. `cost_optimized` dává smysl až v okamžiku, kdy se objeví reálně placená
alternativa a rozdíl v ceně je měřitelný.

### B.3 `performance`

`router.py:352-363` používá **stejné** skórování jako výchozí `score_model` a navíc nic.
Rozdíl oproti `cost_optimized` je pouze v `reason` a v bonusu za lokálnost. „Performance"
tedy **neměří žádnou latenci** — což je jediná věc, kterou by „performance" měla znamenat.
Latence v LiteLLM se řeší strategií `latency-based-routing` s TTL oknem průměrování
`[PRIMARY DOC] https://docs.litellm.ai/docs/routing`. **Hodnocení: chybí.**

### B.4 `reliability`

`router.py:365-381` přičte `+0.2` za `official`/`verified` trust. Spolehlivost je zde zaměněna
za **důvěryhodnost vydavatele**. Důvěryhodnost je legitimní bezpečnostní signál, ale nesplňuje
definici reliability (dostupnost, míra chyb). Dostupnost v kódu reprezentuje circuit breaker
(`router.py:120-147`), který je s tímto skórem propojen jen nepřímo.

### B.5 `local_first`

`router.py:383-398`: pool = lokální modely, jinak všechny kandidáty; `fallback=True` a důvod
„local first", pokud žádný lokální model není. Logika je **správná** a jako izolovaná
funkce dává smysl. Problém je v tom, že ji `chat()` nepoužívá (viz oddíl F).

### B.6 `capability`

`router.py:400-432`: pokud jsou zadané capability, směsuje base score a poměr matchů 50/50.
Filtrace v `_get_candidates()` (`router.py:282-294`) navíc vyřadí modely bez požadovaných
schopností. **Jediná strategie s korektní filtrační semantikou** — a odpovídá tomu, co
LiteLLM dělá přes `require_parameters` a `enforce_fallback_model_access`
`[PRIMARY DOC] https://docs.litellm.ai/docs/proxy/reliability`.

### B.7 `round_robin`

`router.py:434-449`: `selected = candidates[idx % len(candidates)]`. Inkrement je per provider
(citlivka `_rr_counters` klíčovaná názvem **provideru prvního kandidáta**, `:438`). Dva problémy:

1. Kandidáti nejsou před řazením stabilizováno tříděni — `_get_candidates` vrací listy
   sestavené přes `set()` v registru (`model_registry.py:215-221`, `:248-254` — iterace
   po `set`), takže **pořadí kandidátů se mezi voláními může měnit**. Rotace pak není rovnoměrná.
2. Chybí váhy a kapacita. LiteLLM i OpenRouter kombinují rotaci s `weight`, `rpm`/`tpm`
   nebo `max_parallel_requests` `[PRIMARY DOC] https://docs.litellm.ai/docs/routing`.
   Tady by navíc byla vhodná per-provider kapacita — bez ní se round-robin posílá požadavky
   na lokální Ollama bez ohledu na jeho obsazenost.

---

## C) Circuit breaker — současné hodnoty vs. doporučení

### C.1 Současný stav

```
RouterConfig.circuit_breaker_threshold = 3      # router.py:49
RouterConfig.circuit_breaker_timeout  = 30.0   # router.py:50
```

Stav: `{"failures": int, "last_failure": float, "open": bool}` (`router.py:135-137`).
Přechod do OPEN: `failures >= 3` (`router.py:140-142`). Návrat: `time.time() - last_failure > 30`
→ `open=False, failures=0` (`router.py:126-131`). Úspěch → `failures=0, open=False` (`router.py:144-147`).

### C.2 Zásadní problém: jistič není zapojen do reálného volání

`_record_failure()` má **jediné** volání v celém repozitáři — `router.py:228` uvnitř
`route_with_fallback()`, a to výhradně při výjimce `ModelRouterError`. `ModelRouter.route()`
tuto výjimku vyhazuje jen když **nejsou žádné kandidáty** (`router.py:178-179`), ne když
provider selhal. Skutečná selhání providera zachycuje až `ModelGateway.chat()`
(`model_gateway/__init__.py:246-258`), které do routeru **nehlásí nic**.

Důsledek: v produkční cestě `chat()` zůstane `self._circuit_breakers` **prázdný** a
`_check_circuit_breaker()` vždy vrací `True` (`router.py:122-123`). Jistič je v této podobě
**funkčně neúčinný** — což je také důvod, proč test
`tests/test_model_layer.py:642-651` prochází: volá privátní `_record_failure()` přímo
a obchází produkční cestu. Test tedy neprokazuje nic o chování v běhu.

### C.3 Proč rozlišovat trvalou poruchu od přechodného selhání

| Typ poruchy | Příklad | Správná reakce |
|---|---|---|
| Trvalá porucha | Neexistující API key, zrušený endpoint, vadná konfigurace | Jistič otevřít **a neposílat další požadavky** — každý retry jen prodlužuje výpadek a spálí kvótu |
| Přechodné selhání | Rate limit (429), krátký 5xx, timeout sítě, přetížená lokální inference | Omezený počet **retry s backoffem**, potom teprve failover na jiný provider |
| Selhání požadavku, ne služby | `context_window_exceeded`, content policy refusal | **Nepočítat do jističe** — provider je zdravý, jen požadavek je nevhodný |

LiteLLM to rozlišuje explicitně: `fallbacks` (429/500), `content_policy_fallbacks`,
`context_window_fallbacks` jako **tři oddělené mapy** `[PRIMARY DOC] https://docs.litellm.ai/docs/proxy/reliability`.
V současném kódu je `_fallback_chat()` (`model_gateway/__init__.py:325-349`) jediná větev
a chytá **všechno** (`except Exception`), takže:
selhání sítě, chyba přesného modelu i přetečení kontextu jsou nerozlišené a vedou ke stejnému
přeskoku na jiný provider. Z pohledu bezpečnosti je to **příliš široké** — úspěšný fallback na
jiný provider po odmítnutí content policy znamená obejití bezpečnostní kontroly.

### C.4 Hodnoty: co je v pořádku a co ne

| Parametr | Současná hodnota | Doporučení vzoru | Zdroj | Hodnocení |
|---|---|---|---|---|
| Absolutní práh | `3` selhání | Procento v rolling window: `failureRatio=0.5`, `requestVolumeThreshold=20`; Resilience4j `failureRateThreshold=50 %` + `ringBufferSizeInClosedState=100` | SmallRye, Resilience4j, ops4life `[SECONDARY]` | **podezřele** |
| Okno | žádné; jediný úspěch maže historii | Časově omezený counter s automatickým resetem | Azure `[SECONDARY] https://learn.microsoft.com/en-us/azure/architecture/patterns/circuit-breaker` | **chybí** |
| Sleep window | `30.0` s | 30–60 s; Resilience4j default 60 s | Resilience4j, ops4life `[SECONDARY]` | **správně** |
| Half-open | chybí (rovnou do CLOSED) | Omezený počet sond: SmallRye `successThreshold=1`, `ringBufferSizeInHalfOpenState=2-10`, průvodce 3–5 sond | SmallRye, Resilience4j, ops4life `[SECONDARY]` | **chybí** |
| Počet selhání před otevřením při nízkém provozu | 3 absolutně | Vyžadovat minimální počet požadavků v okně | SmallRye `requestVolumeThreshold=20` | **chybí** |
| Kombinace s retry | LiteLLM klient `max_retries=3` + 3 selhání | 1–2 retrye, a neposílat retry, když je jistič otevřený; 3 retrye × 50 % práh = 3násobné zesílení zátěže | ops4life `[SECONDARY] https://systemdesign.ops4life.com/guides/circuit-breaker/` | **podezřele** |
| Eskalace chyb | neexistuje (viz C.3) | Rozdělit `fallbacks` / `content_policy_fallbacks` / `context_window_fallbacks` | LiteLLM `[PRIMARY DOC]` | **chybí** |

Souhrn: **hodnoty 3 / 30 s odpovídají výchozím hodnotám LiteLLM (`allowed_fails: 3`,
`cooldown_time: 30`) `[PRIMARY DOC] https://docs.litellm.ai/docs/proxy/reliability`, což je
zajímavá shoda — ale současná implementace používá jiný model (absolutní počet bez okna,
bez half-open) a navíc není připojena k cestě, která skutečně volá providery.

---

## D) Auto-registrace modelů — hodnocení rizik a návrh principu

### D.1 Co kód dělá

`ModelDiscoveryConfig` (`model_intelligence.py:36-46`) má výchozí `auto_register=True`,
`auto_classify=True`, `validate_connectivity=True`, `validate_functional=True`,
`check_interval_hours=24.0`.

`run_pipeline()` (`:322-359`) pro každý model spouští:
1. `normalize()` (`:153-174`) — lowercases `model_id`, překládá aliasy providerů
   (`"local-ai" → "litellm"` mimo jiné, `:157-162`), a **proměňuje `UNKNOWN` trust na
   `COMMUNITY` a `UNKNOWN` status na `ACTIVE`** (`:169-172`).
2. **`auto_register` → `model_registry.register(model)` (`:335-337`)**
3. teprve potom `validate_metadata()` (`:339-340`)
4. `test_connectivity()` (`:342-344`) — čte jen příznak (`:225`)
5. `functional_test()` (`:346-348`) — kontroluje, že model je zaregistrovaný (`:249-251`)
6. `classify()`, `score_model()`

`discover_from_providers()` (`:75-88`) **nečte žádný vzdálený endpoint** — přebírá
`provider.available_models`, což je statický seznam zapsaný v kódu
(`model_gateway/__init__.py:102-113, 132-140, 159`). `_discover_provider_models()` (`:90-106`)
z něj dělá `ModelRecord` a mapuje důvěryhodnost providera na důvěryhodnost modelu.

### D.2 Rizika

**R1 — Registrace před validací (nejvážnější, přímá chyba pořadí).** `register()` proběhne
v kroku 2, `validate_metadata()` až v kroku 3 (`:335-340`). Model s prázdným `model_id`,
prázdným providerem nebo záporným `context_window` se **dostane do registru**, i když
validace následně vrátí `valid: False`. Výsledek validace se nikde nepropaguje do stavu registru
ani do stavu providera; pipeline prosty pokračuje. Výchozí `ModelStatus` totiž navíc je
`UNKNOWN` (`model_registry.py:110`), takže chybě označený model nemusí být vůbec routovatelný
(`_get_candidates` filtruje `list_active()`, `router.py:280`), ale v registru **je** a
`list_all()` ho vrací.

**R2 — Auto-promoce nedůvěryhodného na důvěryhodné.** `normalize()` (`:169-172`) přiřadí
modelu s `UNKNOWN` trust hodnotu `COMMUNITY` a modelu s `UNKNOWN` status `ACTIVE`.
`ModelIntelligence.score_model()` pak modelu přidí `+10` za `COMMUNITY` a `+10` za `ACTIVE`
(`:283-288, :311-312`). **Výchozí stav je tedy implicitně výhodnější než deklarované
unknown.** Chybí explicitní trust `UNTRUSTED`/`REJECTED` nebo stav `PENDING`, který by
zablokoval routování do doby, než model projde validací.

**R3 — Tautologické kontroly.** `test_connectivity()` (`:215-234`) nekontaktuje nic —
`reachable = provider.api_availability or model.local_available` (`:225`). Příznak
`api_availability=True` je u všech builtin providerů natvrdo `True`
(`model_gateway/__init__.py:101, 129, 157`), takže test **vždy projde**. `functional_test()`
(`:236-255`) má `passed = registered and status == ACTIVE` — a protože registrace proběhla
o dva kroky dříve, je to **definicky `True`**. Oba testy jsou bezobsahné; v kódu jsou
komentáře popisující pipeline, ale žádný skutečný provozní test neprobíhá.

**R4 — Prompt injection a nebezpečné šablony z metadat modelu.** Registr uchovává
`ModelRecord.metadata` a `safety_profile` (`model_registry.py:116-121`). U lokálních
providerů se metadata **načítají z hostovaného systému**:
Ollama `show_model()` vrací `template` i `system` z Modelfile a dokumentace uvádí, že
parametr `template` v požadavku **přepíše** šablonu z Modelfile a `system` přepíše systémovou
promtu `[PRIMARY DOC] https://docs.ollama.com/api` přes `github.com/ollama/ollama/blob/.../docs/api.md`
`[SECONDARY] https://github.com/ollama/ollama/blob/e6f9bfc0e8818e2c679b459c28a0d0f3c8d51dcb/docs/api.md`.
U GGUF souborů je `tokenizer.chat_template` uložen **v metadatech téhož souboru jako váhy** a
běží jako Jinja2 program před zpracováním vstupu; výzkum ukázal, že podmínkové backdoory
v šabloně snižují faktickou přesnost z ~90 % na ~15 % a nejsou zachycávány automatickými
skenery největší platformy pro distribuci otevřených vah `[SECONDARY] https://arxiv.org/html/2602.04653v3`;
existuje i konkrétní PoC přes GGUF metadata `[SECONDARY] https://huggingface.co/pragnyanramtha/gguf-chat-template-backdoor-poc`.
**Jak se to týká této vrstvy:** pokud by se `available_models` doplnily o modely z externího
katalogu nebo by se `metadata`/`safety_profile` načítaly z nedůvěryhodného zdroje, dostal by se
do registru popis, který později může vstoupit do systémového promptu. Skutečné riziko
prompt injection z **model cards / metadat** (skryté Unicode znaky, sentinel fráze typu
„ignore previous instructions", `[SYSTEM]`, anomálie v chat template) je dokumentované
`[SECONDARY] https://docs.chain305.com/ai-ml-supply-chain/46-detect-prompt-injection-model-cards/`.

**R5 — Nevyloučené aliasy normalizace.** `normalize()` překládá `"local-ai" → "litellm"`
(`:161`). Jde o **bezpečnostně problematické ztotožnění**: provider označený jako lokální se
tím přejmenuje na cloudového gateway providera. Při jakémkoli budoucím použití tohoto aliasu
pro řízení routingu by šlo o **cílené přesměrování lokálního provozu do cloudu**, což je
přesně to, co AGENTS.md zakazuje („nepřidávej tiché odesílání lokálních dat ke cloudovému
poskytovateli"). V současném stavu je alias pouze kosmetický (funkce `normalize()` se v
`_discover_provider_models()` nepoužívá), ale je to past, která čeká na zapnutí.

**R6 — Registry bez idempotence a bez pinu verze.** `ModelRegistry.register()` generuje nový
klíč s inkrementem (`model_registry.py:163-165`), takže každá další discovery by přidala
další kopii téhož `(provider, model_id)`. `ModelRecord.version` existuje (`:84`) ale není
součástí klíče ani nikde vyhodnocováno. OWASP AISVS V6.1.2 vyžaduje allowlist schválených
zdrojů, **pining revizí (commit SHA)** a vnitřní zrcadlo, protože veřejné ID jako
`org/name` není stabilní — výzkum „Model Namespace Reuse" ukázal, že smazané a znovu
zaregistrované jméno na veřejném hubu zůstává odkazované i v katalozích
`[SECONDARY] https://github.com/OWASP/AISVS/blob/main/research/chapters/C06-Supply-Chain/C06-Supply-Chain.md`.
Doporučení je i pinovat checksumy, podepis (Sigstore/OpenSSF OMS) a vyhnout se
pickle/joblib formátům ve prospěch `safetensors`
`[SECONDARY] https://microsoft.github.io/zerotrustassessment/docs/workshop-guidance/devsecops/DS_124`.

**R7 — Dedupikace navíc není v cestě a je chybná.** `deduplicate()` (`:180-187`) klíčuje
pouze podle `model_id`, takže by spojila `llama3` z Ollama s `llama3` z jiného providera
(klíč v registru je naopak `provider:model_id`). `run_pipeline()` ji **nezavolá** vůbec —
grep potvrdil jediné volání v `tests/test_model_layer.py:515`.

### D.3 Doporučení (návrh principu, nikoli implementace)

1. **Allowlist místo auto-registrace.** Modely se registrují pouze, projdou-li kontrolou
   proti explicitně schválenému seznamu `(provider, model_id, revision)`. Co není v seznamu,
   může být objeveno a **zaznamenáno jako kandidát**, nesmí však být routovatelné.
2. **Validace před zápisem.** Registrace je až poslední krok pipeline; model, který validaci
   neprošel, se buď nezapíše, nebo zapíše se stavem `PENDING`/`UNVERIFIED`, který routing
   filtřeje stejně jako `DEGRADED`.
3. **Výchozí stav `UNKNOWN` nesmí být tiše povýšen.** Auto-promoce `UNKNOWN → COMMUNITY/ACTIVE`
   je bezpečnostně opačná k tomu, co systém potřebuje: nedůvěřený vstup musí být
   **výchozí nedostupný**, ne výchozí použitelný.
4. **Pinning.** `model_id` + `version`/`revision` jako součást identity, ne jako volitelný
   metadatový atribut. Kontrolovaný allowlist s lidským schválením při každé změně.
5. **Oddělit „objev" od „aktivace".** Formálně: `discovered` / `validated` / `active`
   jako tři různé stavy, s automatickým nárůstem doby mezi nimi.
6. **Metadata jako nedůvěřená data.** `metadata`, `safety_profile` a jakýkoli template/system
   přebíraný z providera nesmí vstoupit do systémového promptu bez sanitizace (odstranění
   skrytých Unicode znaků a sentinel frází) a bez oddělení od rolí.
7. **Zrušit alias `"local-ai" → "litellm"`.** Jakékoli ztotožnění lokálního providera
   s cloudovým je v tomto projektě zásadně nebezpečné.

---

## E) Prompt caching a kontextové okno

### E.1 Přehled podle providera

| Provider / mechanismus | Jak funguje | Min. délka | TTL / životnost | Náklady | Kdy je vhodné | Kdy je nevhodné |
|---|---|---|---|---|---|---|
| **Anthropic** `[PRIMARY DOC] https://platform.claude.com/docs/en/build-with-claude/prompt-caching` (2026-10-02) | `cache_control` na content bloku (`explicit`) nebo jeden top-level `cache_control` (`automatic`, breakpoint se posouvá) | 1 024 tok. (Sonnet) / 4 096 tok. (Opus, Haiku 4.5) | default **5 min**, obnovuje se na každém hitu; volitelně **1 h** | zápis 1,25× (5 min) / 2× (1 h) základní vstupní ceny; čtení **0,1×** | Dlouhá opakovaná konverzace, stabilní systémový prompt, velké dokumenty | Prompt pod minimem; obsah měnící se před breakpointem; pomalejší než 5 min mezi turny (použít 1 h) |
| **OpenAI** `[PRIMARY DOC] https://developers.openai.com/api/docs/guides/prompt-caching` (2026-10-02) | Automaticky; `prompt_cache_key` ovlivňuje routing na stejný stroj. GPT-5.6+: `prompt_cache_options.mode` = `implicit` (výchozí) nebo `explicit`, breakpointy `prompt_cache_breakpoint`, max 4 zápisy | **1 024** viditelných vstupních tokenů (starší modely 1 024–2 048); hit po přírůstcích 128 | 30min exact TTL přes `prompt_cache_options.ttl`; starší modely model-dependent až 24 h (`in_memory`) | GPT-5.6+: čtení 0,1×, zápis 1,25×; starší modely: čtení zlevněné, zápis bez příplatku | Stabilní prefix, opakované systémové prompty a definice nástrojů | Krátké prompty; prefix se mění před breakpointem; různé providery/regiony mezi požadavky |
| **Gemini** `[PRIMARY DOC] https://ai.google.dev/gemini-api/docs/generate-content/caching` (2026-10-02) | **Implicitní** (default Gemini 2.5+) a **explicitní** (`client.caches.create` s TTL a reference) | implicit: 2 048 (Gemini 2.5) / 4 096 (Gemini 3.x); minimální TTL 1 min | explicitní: TTL default **1 h**, minimum 1 min, bez maxima; cache = **prefix promptu** | explicitní je placená funkce: účtováno podle počtu tokenů a délky uložení; sleva na cache tokeny (75–90 % dle modelu `[SECONDARY] https://cloud.google.com/vertex-ai/generative-ai/docs/context-cache/context-cache-overview`) | Velký opakovaný kontext (dokumenty, repozitář) | Obsah se mění před začátkem cache; krátké prompty; model nerozlišuje cache od běžných tokenů |
| **Ollama** `[PRIMARY DOC] https://docs.ollama.com/faq` (2026-10-02) + `[SECONDARY] https://github.com/ollama/ollama/blob/cb003359/runner/llamarunner/cache.go` | `keep_alive` (default **5 min**) drží model v paměti, takže **slot s KV cache zůstane živý**; runner hledá slot s **nejdelším společným prefixem** (`findLongestCacheSlot`) a znovu použije `numPast` tokenů; při `multiUserCache` volí „best" slot. `OLLAMA_KV_CACHE_TYPE` (`f16`/`q8_0`/`q4_0`) snižuje paměť KV cache | není minimální délka, ale je **nutné, aby prefix seděl** | `keep_alive` jako dobu nebo číslo; `-1` = trvale | nulová cena; reálná úspora je **čas** (nepřepočítává se prefix) + nezničuje se model v paměti | Opakované prompty k témuž načtenému modelu v krátkém časovém okně | Dlouhé prodlevy mezi dotazy (prefix se ztratí); střídání více modelů v okně 5 min (každý zvlášť drží paměť); různé prefixy u jednoho modelu |
| **llama.cpp server** | Serverový prompt cache: shoda dosud zpracovaného prefixu znovu použita; `n_ctx` (default **2 048** v tomto repozitáři, `llama_cpp_client.py:22`) určuje délku zadrženého kontextu | shoda musí být dostatečně dlouhá | dokud slot drží prefix a `n_ctx` není překročen | nulová cena, úspora prefillu | Dlouhé stabilní systémové prompty, agentní smyčky se stejným prefixem | Přepínání různých promptů mezi requesty; `n_ctx` menší než skutečný potřebný kontext |

### E.2 Kdy je cache nevhodná — souhrnně

1. **Krátký prompt.** Všechny cloudové providery mají minimum 1 024–4 096 tokenů
   `[PRIMARY DOC]` Anthropic/OpenAI/Gemini. Prompt kratší než minimum se **nikdy** neuloží.
2. **Střídání více providerů.** Cache je vázána na konkrétní stroj/region (`prompt_cache_key`
   u OpenAI řídí routing na cache), na konkrétní `ModelRecord` u Gemini, na konkrétní slot
   KV cache u Ollama. Při round-robin mezi providery je **hit rate přibližně 1/N** (N = počet
   providerů), pokud se nemění pořadí. Proto LiteLLM nabízí `session_affinity` — připnutí
   konverzace k jednomu deploymentu právě kvůli provider-side cachi
   `[PRIMARY DOC] https://docs.litellm.ai/docs/routing`.
   **V tomto repozitáři session affinity chybí a round_robin existuje** → cache by byl
   záměrně zničen právě v kombinaci těchto dvou.
3. **Krátký interval mezi requesty > nejkratší TTL.** Anthropic 5 min; při delších pauzách
   je třeba 1 h varianta nebo `pre-warm` request `[PRIMARY DOC] Anthropic`.
4. **Nedeterministický prefix.** Jakákoli změna **před** breakpointem zruší celý zisk;
   změna **za** breakpointem je bezpečná `[PRIMARY DOC] OpenAI`.
5. **Krátký lifetime procesu.** Ollama default 5 min unload; při CLI-invokaci (`cyberbrain run`,
   `cli.py:143-174`) se proces spouští, pošle pár požadavků a skončí. Prefix cache se v tomto
   režimu **neuplatní** vůbec.

### E.3 Řízení kontextového okna

| Přístup | Princip | Kdy | Zdroj |
|---|---|---|---|
| Sliding window | Nechat posledních N zpráv, starší zahodit | Krátké konverzace, jednoduché agenty | `[SECONDARY] https://github.com/awslabs/fullstack-solution-template-for-agentcore/.../CONTEXT_MANAGEMENT.md` |
| Offload velkých výstupů nástrojů | Výstup > ~20 k tokenů uložit na disk, do kontextu vložit cestu + náhled | Agenti s code/s browse nástroji | `[SECONDARY] https://www.langchain.com/blog/context-management-for-deepagents` |
| Právě-in-kontextu shrnutí | Levný LLM call shrne starší zprávy; originál zůstane na disku jako kanonický záznam | Dlouhé stavové relace | tamtéž |
| Dělení bloků (parallel compaction) | Shrnutí po blocích, každý pracovník vidí celou historii → **prefix cache reuse napříč pracovníky** | Velmi dlouhé běhy | `[SECONDARY] https://arxiv.org/html/2605.23296v1` |
| Prahové spouštění | Komprese na 70 % (běžně) / 50 % (dlouho běžící agenti), nikoli až na overflowu | Obecně | `[SECONDARY] CONTEXT_MANAGEMENT.md`; `[SECONDARY] https://engineersofai.com/docs/agentic-ai/agent-memory/in-context-working-memory` |
| Kontextový fallback na větší model | Při `ContextWindowExceededError` přesměrovat na model s větším oknem | Heterogenní fleet modelů | `[PRIMARY DOC] https://docs.litellm.ai/docs/proxy/reliability` |
| Pre-call token check | Odmítnout požadavek před odesláním, pokud vstup překračuje okno | Každý routing s více modely | `[PRIMARY DOC] https://docs.litellm.ai/docs/proxy/reliability` — `enable_pre_call_checks` + `model_info.max_input_tokens` |

Důležité upozornění z těchto zdrojů: kvalita degronuje **před** překročením okna
(a „lost in the middle" — výkon klesá i když se obsah vejde)
`[SECONDARY] https://engineersofai.com/docs/agentic-ai/agent-memory/in-context-working-memory`;
shrnutí je ztrátové a objem výstupu model neřídí spolehlivě podle instrukce
`[SECONDARY] https://arxiv.org/html/2605.23296v1`. Proto je preventivní řízení kontextu
výhodnější než reaktivní fallback.

### E.4 Co z toho plyne pro lokálního prototyp

- **Prompt caching u lokálních providerů není prioritou.** Ollama cache je podmíněná
  `keep_alive` a přežitím stejného slotu; pro CLI běh (krátký proces) se neprosadí.
  Přínos by měl **delší běžící agent** s opakovaným stejným prefixem.
- **Cache u cloudového providera** by byl nejúčinnější pro stabilní systémový prompt —
  a ten je v tomto projektu skutečně stabilní: tři jazykové varianty, fallback na `en`
  (`cli.py:154-159`). Chybí ale `cache_control` / `prompt_cache_key` v `LiteLLMClient.chat()`
  (`litellm_client.py`) a chybí session affinity.
- **`n_ctx=2048`** jako default llama.cpp klienta (`llama_cpp_client.py:22`, ověřeno testem
  `tests/test_model_layer.py:752-757`) je **velmi malý** pro plánovacího agenta. S 2 k tokenů
  má smysl jen velmi krátký prompt; jakýkoli smysluplný systémový prompt plus historie
  ho překročí. Konfigurace to neřeší — `n_ctx` se v požadavku vůbec neposílá.
- **Řízení kontextu chybí kompletně.** Neexistuje ani počítání tokenů, ani limit
  vstupní délky, ani `enable_pre_call_checks` obdoba. `ModelRecord.context_window` je
  čistě dekorativní — používá se jen ve skórování (`model_intelligence.py:290-298`).
  CLI navíc drží 2 zprávy na krok (`cli.py:161-164`), takže dnes je to nevyjádřený problém;
  jakmile se objeví více kroků v jednom kontextu, přetečení přijde bez jakéhokoli varování.

---

## F) Co chybí a co nelze ověřit bez živého běhu

### F.1 Problémy zjištěné čtením kódu (ověřitelné, potvrzené)

1. **`RouteStrategy` nemá vliv na skutečné volání.** `chat()` jde přes `_resolve_route()`
   (`model_gateway/__init__.py:244`), který router obchází. Výběr strategie se uplatní jen
   v diagnostickém `gateway.route()` (`:415-447`). Všechny strategie jsou v produkční cestě
   **nedosažitelné**.
2. **Circuit breaker se v produkční cestě nikdy neotevře** (viz C.2). Jediné volání
   `_record_failure()` je v `route_with_fallback()` při chybě, která v této cestě nevzniká.
3. **`RouterConfig.fallback_chain`, `RouterConfig.health_check_interval` a
   `ModelDiscoveryConfig.check_interval_hours` jsou mrtvé konfigurace** — grep potvrdil
   jediný výskyt každé z nich (definice) v celém repozitáři.
4. **`_get_fallback_candidates()` nefiltruje otevřené jističe** (`router.py:303-324`),
   na rozdíl od `_get_candidates()` (`router.py:296-299`).
5. **`_fallback_chat()` posílá stejný model jinému provideru** (`model_gateway/__init__.py:338-340`).
   Cloudový model poslaný do lokálního Ollama většinou neexistuje → fallback skončí
   `ModelNotAvailableError` (`:347-349`).
6. **`except Exception` v `_fallback_chat()`** (`:341`) pohlcuje i chyby, které by měly
   být bezpečnostní odmítnutí (content policy, context overflow) — fallback je použit jako
   obejití restrikce.
7. **Registrace před validací** v `run_pipeline()` (`model_intelligence.py:335-340`).
8. **`normalize()` povýší `UNKNOWN` → `COMMUNITY`/`ACTIVE`** (`:169-172`) a mapuje
   `"local-ai"` → `"litellm"` (`:161`).
9. **`test_connectivity()` a `functional_test()` nic neověřují** (`:215-255`).
10. **`deduplicate()` není v pipeline a je chybná** (klíč bez providera, `:180-187`).
11. **Registrace není idempotentní** — `model_registry.py:163-165`.
12. **Cloudové modely jsou v registru označeny `FREE` + `OFFICIAL`**
    (`model_gateway/__init__.py:183-198`), takže „free" neodlišuje lokální od placeného cloudu.
13. **Žádná podpora prompt cachingu ani `keep_alive`** v klientech (grep v `model_layer/`
    nevrátil `keep_alive`, `cache`, `prompt_cache`).
14. **Test `tests/test_model_layer.py:771-782` dělá živou síťovou žádost na localhost**
    (`httpx.get("http://127.0.0.1:8080/")`) — test jednotkové vrstvy je environmentálně
    závislý a běh by se „prospěšně" změnil podle toho, zda server běží.

### F.2 Co nelze ověřit bez živého běhu (bylo výslovně zakázáno cokoli spouštět)

- `[NEOVĚŮENO]` Zda `ModelRouter` vůbec někdy v reálném běhu vrací jiný model než `gemini-flash`.
  Staticky: `_route_cost_optimized()` seřadí cloudové modely (free +25, official +25,
  ACTIVE +10 = 60/100 = 0.6) proti lokálním (free +25, community +10, ACTIVE +10 = 45/100 = 0.45),
  takže s `prefer_local=True` (+0.15) by lokální vyhrál 0.60 vs 0.45+0.10=0.55. S výchozím
  `RouterConfig()` (prefer_local=False) by vyhrál cloud. **Při `prefer_local=True` v
  `ModelGatewayConfig` a `prefer_free=True` se tipá na remízu** (lokální 0.45+0.15=0.60,
  cloud 0.6+0.1=0.70 — cloud vyhrává). Toto je statická úvaha; skutečné pořadí závisí na
  `model_registry.list_active()`, jehož pořadí pochází z iterace přes `set()`.
- `[NEOVĚŮENO]` Zda se `deduplicate()`/registry chová jako předpovědí F.1.11 při opakované discovery.
  Vyžaduje běh pipeline dvakrát a porovnání `count()`.
- `[NEOVĚŮENO]` Zda `health_check()` gateway (`model_gateway/__init__.py:471-497`) v reálném
  prostředí vrací `healthy: True` — testy (`tests/test_model_layer.py:821-828`) pouze
  kontrolují přítomnost klíčů, nikoli hodnoty, a jeden z klientů dělá živou síťovou žádost.
- `[NEOVĚŮENO]` Reálná latence a realita `prefer_free`/`prefer_local` priority za provozu —
  k tomu je potřeba běh s lokální i cloudovým providerem, což je mimo scope tohoto průzkumu.
- `[NEOVĚŮENO]` Zda `n_ctx=2048` (`llama_cpp_client.py:22`) je v praxi dostatečné pro
  plánovacího agenta. Vyžaduje běh s reálným systémovým promptem; lze jen odhadnout z délky
  promptu v `cli.py:154-158` (řádově desítky tokenů, tedy reálně vychází OK pro 2 k).
- `[NEOVĚŮENO]` Chování LiteLLM klienta při chybějícím API key a bez chyby importu —
  `LiteLLMClient._ensure_initialized()` (`litellm_client.py:33-46`) vyhazuje `ImportError`,
  který `chat()` zachytí (`model_gateway/__init__.py:250`) a půjde do fallbacku. Chování
  v reálném prostředí s chybějící klíčovou hodnotou nebylo ověřeno.

### F.3 Bezpečnostní poznámka

Tato vrstva je v projektu součástí řetězce **identita → politika → oprávnění → bezpečnostní
kontroly → vyhodnocení → audit**. Zjištěná rizika se týkají především **dostupnosti
(fallback, jistič)** a **klasifikace důvěryhodnosti modelů (auto-registrace)**. Nebyla
provedena bezpečnostní revize sandboxu ani jiných vrstev; jakékoli tvrzení o tom, že
modelová vrstva je či není bezpečná, by překračovalo rozsah tohoto dokumentu. Konkrétně:
doporučení v oddílu D (allowlist, pinning, validace před zápisem, zrušit alias
`"local-ai" → "litellm"`) jsou **návrhy principů**, nikoli bezpečnostní záruhy.

---

## METODIKA A OMEZENÍ

### Postup

1. **Krok 1 — lokální čtení (pouze read).** Pročteny soubory:
   `src/cyberbrain26/model_layer/router.py`, `model_intelligence.py`, `model_registry.py`,
   `provider_registry.py`, `litellm_client.py` (hlavička), `ollama_client.py` (hlavička + těla
   požadavků), `llama_cpp_client.py` (hlavička + těla požadavků), `__init__.py`,
   `src/cyberbrain26/control_plane/model_gateway/__init__.py`, `src/cyberbrain26/cli.py`
   (sekce `run`), `tests/test_model_layer.py`. Použit grep pro ověření mrtvých konfigurací
   (`check_interval_hours`, `health_check_interval`, `fallback_chain`, `deduplicate`,
   `keep_alive`, `cache`, `prompt_cache`).
2. **Krok 2 — internet.** Webfetch na oficiální dokumentace, websearch na primární
   dokumentaci a kvalitní sekundární zdroje. Dotazy obsahovaly výhradně obecnou veřejnou
   terminologii a názvy veřejných produktů.
3. **Krok 3 — syntéza** do tohoto dokumentu. Nic nebylo spuštěno, nic nebylo nainstalováno,
   nic nebylo změněn mimo vytvoření tohoto souboru.

### Použité URL

**Primární dokumentace (načteno 2026-10-02):**

1. LiteLLM — Fallbacks (Provider Failover): `https://docs.litellm.ai/docs/proxy/reliability`
2. LiteLLM — Router / Load Balancing: `https://docs.litellm.ai/docs/routing`
3. OpenRouter — Provider Routing: `https://openrouter.ai/docs/guides/routing/provider-selection`
4. Anthropic — Prompt caching: `https://platform.claude.com/docs/en/build-with-claude/prompt-caching`
   (také `https://docs.anthropic.com/en/docs/build-with-claude/prompt-caching`)
5. OpenAI — Prompt caching: `https://developers.openai.com/api/docs/guides/prompt-caching`
   (také `https://developers.openai.com/cookbook/examples/prompt_caching101`,
   `https://developers.openai.com/cookbook/examples/prompt_caching_201`)
6. Google Gemini — Context caching (generateContent): `https://ai.google.dev/gemini-api/docs/generate-content/caching`
   (také `https://ai.google.dev/gemini-api/docs/caching`)

**Sekundární zdroje:**

7. Resilience4j — CircuitBreaker: `https://resilience4j.readme.io/v0.17.0/docs/circuitbreaker`
8. Microsoft — Azure Architecture Center, Circuit Breaker Pattern:
   `https://learn.microsoft.com/en-us/azure/architecture/patterns/circuit-breaker`
9. SmallRye Fault Tolerance — Circuit Breaker:
   `https://smallrye.io/docs/smallrye-fault-tolerance/6.11.1/reference/circuit-breaker.html`
10. ops4life — Circuit Breaker (system design průvodce s doporučenými hodnotami):
    `https://systemdesign.ops4life.com/guides/circuit-breaker/`
11. Ollama — FAQ (keep_alive, KV cache): `https://docs.ollama.com/faq`
12. Ollama — API docs (parametr `template`, `system`, `keep_alive`):
    `https://github.com/ollama/ollama/blob/e6f9bfc0e8818e2c679b459c28a0d0f3c8d51dcb/docs/api.md`
13. Ollama runner — `cache.go` (prefix reuse napříč sloty KV cache):
    `https://github.com/ollama/ollama/blob/cb003359/runner/llamarunner/cache.go`
14. Google Cloud Vertex AI — Context caching overview (limity, slevy):
    `https://cloud.google.com/vertex-ai/generative-ai/docs/context-cache/context-cache-overview`
15. LangChain — Context Management for Deep Agents:
    `https://www.langchain.com/blog/context-management-for-deepagents`
16. AWS Labs — Context management (Strands / LangGraph):
    `https://github.com/awslabs/fullstack-solution-template-for-agentcore/blob/5c9446bf/docs/CONTEXT_MANAGEMENT.md`
17. EngineersOfAI — In-Context Working Memory:
    `https://engineersofai.com/docs/agentic-ai/agent-memory/in-context-working-memory`
18. arXiv — Parallel Context Compaction for Long-Horizon LLM Agent Serving:
    `https://arxiv.org/html/2605.23296v1`
19. OWASP AISVS — C06 Supply Chain (allowlist, pinning, namespace reuse):
    `https://github.com/OWASP/AISVS/blob/main/research/chapters/C06-Supply-Chain/C06-Supply-Chain.md`
20. Microsoft Zero Trust — Supply chain security pro AI/ML pipeline:
    `https://microsoft.github.io/zerotrustassessment/docs/workshop-guidance/devsecops/DS_124`
21. Google Security Blog — Model signing se Sigstore:
    `https://blog.google/security/taming-wild-west-of-ml-practical-mode/`
22. AI Security Playbook — supply chain modelů (scannery, pinning, AIBOM):
    `https://aisecurity.zone/model/supply-chain/`
23. arXiv — Inference-Time Backdoors via Hidden Instructions in LLM Chat Templates:
    `https://arxiv.org/html/2602.04653v3`
24. Hugging Face — GGUF chat-template backdoor PoC:
    `https://huggingface.co/pragnyanramtha/gguf-chat-template-backdoor-poc`
25. Chainsaw — Detect Prompt-Injection in Model Cards and Prompt Templates:
    `https://docs.chain305.com/ai-ml-supply-chain/46-detect-prompt-injection-model-cards/`
26. Anthropic Cookbook — prompt_caching.ipynb (minimální délky, 4 breakpointy):
    `https://github.com/anthropics/anthropic-cookbook/blob/main/misc/prompt_caching.ipynb`

### Omezení průzkumu

1. **Nic nebylo spuštěno.** Nebyl spuštěn runtime, neproběhly testy, nebyly provedeny žádné
   síťové požadavky na localhost ani na providery. Všechna tvrzení o chování v běhu jsou
   buď odvozena ze statického čtení kódu, nebo výslovně označena `[NEOVĚŮENO]`.
2. **Nic nebylo změněn** kromě vytvoření `docs/research/agent-config/03-modelova-vrstva.md`.
   Produkční kód, konfigurace (včetně `pyproject.toml`, `.env*`, CI, `spaces_config.json`)
   ani `AGENTS.md` nebyly dotčeny. Nic nebylo smazáno, nestagováno, necommitováno, nepushováno.
   Nebyly použity `sudo`, `reset`, `stash` ani `checkout`; nebyly instalovány žádné balíčky.
3. **Hygiena dotazů dodržena.** Do vyhledávání nebyly odeslány žádné identifikátory tohoto
   repozitáře — žádný kód, název třídy, cesta k souboru ani obsah lokálních souboru.
   Interní názvy provider konfigurací z projektu nebyly použity jako dotazy; provideri
   jsou v dokumentu označeni obecně (lokální inference, cloudový gateway) nebo veřejnými
   názvy produktů (Ollama, llama.cpp), což jsou veřejné názvy veřejných produktů.
4. **Aktuálnost zdrojů.** LiteLLM, OpenRouter i dokumentace providerů se mění rychle;
   LiteLLM dokumentace výslovně uvádí, že `mock_testing_fallbacks` je od proxy v1.85.0
   zastaralý. Uvedené hodnoty a parametry jsou stav ke dni načtení (2026-10-02) a je
   potřeba je při použití ověřit proti aktuální dokumentaci.
5. **Hodnocení bezpečnosti není revizí.** Oddíl D navrhuje principy, ale neprovedla se
   bezpečnostní revize modelové vrstvy ani jiných vrstev. Tvrzení v tomto dokumentu
   není bezpečnostní certifikací.
6. **Nepřítomnost zdroje.** Všechny výše uvedené URL byly načteny nebo vyhledány. Nebyl
   žádný zdroj, který se nepodařilo načíst a který by v dokumentu chyběl. Pokud je
   některé tvrzení v sekundárním zdroji (např. `systemdesign.ops4life.com`,
   `docs.chain305.com`) nižší kvality než primární dokumentace, je to v textu uvedeno
   označením `[SECONDARY]`.
