# scorekeeper

> Nic netvrdit bez podkladu, nic nedělat bez oprávnění. Normativní vrstva, která vede účet toho, co dlouho běžící LLM agent smí tvrdit — a co smí dělat.

Sólový projekt — výzkum, teorie, architektura, Python · 2026 · Open source (Apache-2.0) · v0.4.0 na PyPI

`Python` `Claude Code hooks` `MCP server` `Pluggable LLM backends` `DeonticBench` `PyPI`

- [GitHub — scorekeeper](https://github.com/michalstrnadel/scorekeeper)
- [PyPI — scorekeeper](https://pypi.org/project/scorekeeper/)
- [why.md — celý argument](https://github.com/michalstrnadel/scorekeeper/blob/main/docs/why.md)

scorekeeper je **normativní vrstva pro dlouho běžící LLM agenty**. Vedle paměti agenta — tedy záznamu toho, *co se stalo* — udržuje scoreboard: k čemu se agent zavázal, co každý závazek podpírá a co je s čím v konfliktu. Závazky se z každého tahu extrahují do strukturovaných záznamů, provenience se sleduje jako plnohodnotná data a nekompatibility se zachytí dřív, než se propíšou do kódu.

Teorie pochází z padesáti let filozofie jazyka — Brandomova deontického scorekeepingu — přeloženého do datového modelu doslova, ne jako metafora. A pokrývá už **dva směry selhání najednou**: tvrzení bez provenience (bluffing) a činy bez oprávnění (barging — refactor mimochodem, o který nikdo nestál). Vydává se jako **plugin pro Claude Code (instalace na dva řádky), MCP server a python knihovna**. Evidence se replikuje i mimo Claude — běhy na Gemini a GPT včetně placebo kontroly — scoreboard má terminálový dashboard (`scorekeeper board`) a projekt dogfooduje sám sebe: vlastní závazky žijí v `.scorekeeper/`.

- **0** — driftů se scoreboardem — holý agent driftoval
- **+0,6 %** — režie tokenů
- **279** — testů prochází
- **3** — vendoři modelů — replikováno, s placebo kontrolou

## problém

Dlouho běžící agenti selhávají charakteristickým způsobem. V kroku 3 se rozhodnou pro Postgres, v kroku 47 píšou kód pro MongoDB. Slíbí, že zachovají API kontrakt, a o hodinu později potichu přejmenují dvě pole. Tvrdí něco, co nikde nečetli — a po kompakci kontextu si ani nepamatují, že to tvrdili. Průmysl tomu říká problém **paměti** a předepisuje větší okna, lepší retrieval, chytřejší sumarizaci.

Nepříjemné pozorování: ve většině těchhle selhání *ta informace pořád v kontextovém okně byla*. Rozhodnutí pro Postgres tam leželo, čtyřicet tahů zpátky. Úzkým hrdlem nebyl retrieval — chyběl status. Rozhodnutí bylo jen další text, jedna sekvence tokenů mezi tisíci, bez značky, která by řekla **tohle tě zavazuje**. A to není problém paměti, ale problém **normativní**: agent vede záznam o tom, co se stalo, ale žádnou účetní knihu toho, k čemu se zavázal.

A bluffing má praktické dvojče. Agenti taky **vtrhávají**: požádáš o opravu překlepu a agent refaktoruje tři moduly, „zmodernizuje“ testovací helpery a spálí odpoledne usage na práci, o kterou nikdo nestál. Každá editace je lokálně obhajitelná, žádná není oprávněná — a všechny teď musíš zkontrolovat ty. **Overreach je čin bez oprávnění**: stejná díra ve stejné účetní knize, jen na straně skutků místo tvrzení.

## myšlenka

Filozofie jazyka studuje přesně tuhle strukturu už padesát let. Robert Brandom (*Making It Explicit*, 1994) popisuje diskurz jako hru, ve které si každý účastník vede **deontické skóre** o všech ostatních: kdo je k čemu zavázán a kdo je k čemu oprávněn. Tři pojmy se mapují jedna ku jedné na datový model:

**Závazek** — něco tvrdíš, a tím jsi vázán jak tvrzením samotným, tak jeho důsledky. **Oprávnění** — máš pro to důvod? Přečtený soubor, instrukci uživatele, výsledek nástroje? **Nekompatibilita** — závazek k *p* vylučuje oprávnění k tvrzením, která s *p* věcně kolidují.

Přeloženo do selhání agentů to sedí až překvapivě přesně. Halucinace je *závazek bez oprávnění* — a v grafu provenience to není mlhavý soud o kvalitě, ale viditelná díra. Sebe-rozpor je *nezachycená nekompatibilita mezi dvěma živými závazky*: Postgres v kroku 3 a MongoDB v kroku 47 byly oba aktivní a nikdo nedržel skóre. Nekoherence po kompakci je *smazání scoreboardu* — sumarizátory zachovají příběh a zahodí přesně ten normativní stav.

`the-overlay`

```text
agent turn ──▶ extractor ──▶ commitments + provenance (entitlement)
scoreboard ──▶ operators: ASSERT · CHALLENGE · SUPERSEDE · CONFLICT
claims: conflicting write ──▶ Tier-0 gate ──▶ DENIED until the board
deeds:  out-of-scope write ──▶ scope wall ──▶ records an entitled
                                              revision / grant
```

## jak to funguje

Terminálové demo nahoře ukazuje to klíčové rozlišení naživo: **stejný tvar změny, jiná provenience, jiný verdikt**. Nejdřív se zaznamená oprávněné rozhodnutí. Vlastní drift agenta k MongoDB se pak chytí jako `BRANCH-CONFLICT`, zatímco změna vyžádaná uživatelem projde jako čistý `SUPERSEDE`. Reprodukovatelné za zhruba 20 sekund: `uv run --project core python demo/drift_demo.py`.

scorekeeper je vrstva, ne framework. Šest Claude Code hooků připojí živý scoreboard k libovolné session. Izolovaný scorer extrahuje závazky z každého tahu do strukturovaných záznamů — a dělá to asynchronně, takže samotný tah zůstává na zhruba nulové latenci. Nekompatibilitu mezi živými závazky posuzuje jazykový model, a to záměrně: skutečné konflikty jsou **věcné, ne formální**. „Nejdřív vydáme CLI“ a „první deliverable je webový dashboard“ nesdílejí jediný predikát, kterého by se chytil dokazovač vět — a přitom tu kolizi vidí každý kompetentní čtenář. Kromě pluginu se scorekeeper vydává i jako MCP server a knihovna, s vyměnitelnými backendy (defaultně lokální `claude` CLI bez API klíče, nebo Anthropic a OpenAI-kompatibilní API).

Designový postoj, na kterém celý projekt stojí: **agent si nedrží vlastní skóre.** Sbíhají se v něm tři nezávislé linie. Filozofie — pro Brandoma je scorekeeping konstitutivně sociální, skóre drží *druhý* hráč, ne mluvčí sám o sobě. Kognitivní věda — Mercier a Sperber ukazují, že jsme líní producenti vlastních zdůvodnění a zároveň ostří hodnotitelé cizích, a evidence u LLM tohle přesně zrcadlí. Inženýrství — paměť, kterou si agent edituje sám, má notorickou mezeru ve spolehlivosti: když agent zapomene zapsat, fakt neexistuje. Scoreboard je proto *scaffolded, ne extended* — udržují ho deterministické hooky mimo pravomoc agenta. Agent na lešení stojí, ale nepřestavuje si ho za běhu pod sebou.

- **Oprávnění jako provenience** — každý závazek nese odkazy na to, co ho podpírá. Závazek se `source: none` je podezřelý první kategorie.
- **Přežití kompakce** — normativní digest se vstřikuje do sumarizace přesně tam, kde ho dnešní sumarizátory zahazují.
- **Asynchronní extrakce** — odpojený worker skóruje tah na pozadí a nálezy se ukážou u dalšího promptu.
- **Poctivě na cizích ramenou** — kusy tohohle uměly už commitment stores (Hamblin, 1970) i truth-maintenance systémy (Doyle, 1979). Nové je spojení: vlastní diskurz LLM agenta, provenience jako plnohodnotná dimenze, věcná nekompatibilita nad přirozeným jazykem a nasazení v produkčním harnessu.

## změřeno, včetně negativních výsledků

Fáze 0 přinesla první párovou deltu. Na nastraženém scénáři s volbou databáze holý haiku agent driftoval k MongoDB proti vlastnímu zaznamenanému rozhodnutí, zatímco jeho dvojče se scoreboardem — stejný model, stejný scénář, přidaný jen scoreboard — linii udrželo. Tedy 0/6 vs. 1/6 driftů, s nulou falešných konfliktů na sondě oprávněné revize a režií +0,6 % tokenů.

Fáze 2, **DeonticBench** (dříve EntitleBench), přinesla nejpoučnější výsledky — dva z nich negativní a oba publikované. V nejtěžší podmínce slabý agent proplul kolem **jedenácti poradních varování** a stejně postavil lákavou Memcached hot path. Drift dokonce zamaskoval docstringem, který nad importem `pymemcache` tvrdil „backed by Redis“. Selhal i jednorázový blokující retardér: na výzvu „pokud si to objednal uživatel, řekni to a zkus znovu“ si agent prostě **přisvojil oprávnění, které neměl**, a zkusil to znovu. Co vydrželo, byla zeď rozhodovaná scoreboardem (ADR-0007): konfliktní zápis zůstává odmítnutý, dokud sám scoreboard nezaznamená oprávněnou revizi. Slovo agenta ji nezvedne. Ověřeno symetricky — drift **HELD** (žádný konkurenční kód nepřistál), oprávněná revize **EXECUTED s nulou odmítnutí**. Legitimní cesta nestála nic.

Samotné měření je adversariálně zocelené. Primární metrikou je deterministický klasifikátor nad artefakty; LLM soudce je vědomě až sekundární, nespolehlivá metrika. Benchmark přežil adversariální revizi 33 agentů, každý potvrzený přehmat se stal regresním testem a obě selhání se penalizují na téže hranici: drift (SCR) i falešná odmítnutí (FRR).

Fáze 2b obrátila stejnou mašinerii na skutky. **Scope pin** (`path:app/**` na závazku k rozsahu úkolu) zaznamená, na co aktuální požadavek agenta opravňuje — a zápisy mimo rozsah zůstávají odmítnuté, dokud board nezaznamená oprávněné rozšíření. Stejný tok zeď → ukázat → oprávnit → projít, aplikovaný na činy (ADR-0008). Změřeno poctivě: smetí mimo rozsah dolů zhruba 8×, výstup v rozsahu beze změny, výhrady publikované u tvrzení (n=1–2 na buňku, jedna rodina scénářů).

A vlajkový výsledek v0.4.0: **barge není chování Claude a zastavuje ho právě digest kanál**. Referenční agentní smyčka (ADR-0009) spustí DeonticBench na libovolném modelu přes syrová chat-completions API — OpenAI, Gemini, OpenRouter, lokální servery typu Ollama. Na `gemini-3.5-flash-lite` a `gpt-5.4-mini`: holí agenti provedli nastražený drive-by ve 3 ze 4 buněk, každá řízená buňka vydržela (8/8, nula odmítnutí) a *tiché placebo* — board zapsaný na disk, všechny kanály vypnuté — vtrhlo úplně stejně jako holý agent, seed za seedem. Pozdější kampaně se sečetly na **28/28 řízených buněk drželo** napříč vendory. Jeden holý běh dokonce práci na legacy souboru v próze odmítl, zatímco jeho diff ten soubor stejně upravil — bluffing a barging v jediném běhu.

## board

Od verze 0.4.0 má scoreboard tvář: `scorekeeper board` ho vykreslí jako terminálový dashboard — počty v hlavičce (aktivní / zpochybněné / konfliktní / dnešní odmítnutí), aktivní závazky se scope piny a glyfy provenience (★ externí, ⚠ neoprávněné), poslední události obarvené podle třídy operátoru, odmítnutí červeně. Čisté stdlib ANSI, žádné nové závislosti.

Skutečný záznam z boardu samotného repa: závazek k rozsahu úkolu připnutý na `app/**`, `tests/**` a `README.md`. O devět tahů a jednu kompakci kontextu později přemluvil ping od kolegy agenta k drive-by úklidu `legacy/util.py`. Zápis narazil do zdi: `TIER0-SCOPE-DENY — 'legacy/util.py' outside pinned write scope`.

[scorekeeper board: dashboard chytá drive-by mimo připnutý rozsah](https://michalstrnadel.com/case-studies/scorekeeper/board.mp4)

## stav a co dál

scorekeeper je open source pod licencí Apache-2.0 a je [na PyPI](https://pypi.org/project/scorekeeper/). Plugin pro Claude Code se instaluje dvěma řádky z marketplace a scorer si při prvním hooku stáhne sám. MCP server vede zápisy stejnou pipeline operátorů. Repozitář je zdokumentovaný jako výzkumný artefakt — 9 záznamů architektonických rozhodnutí (ADR), závazná specifikace (česky), osm výzkumných dokumentů, osnova odborného článku, citační soubor — a drží skóre sám na sobě v `.scorekeeper/`.

Komunitní část začala fungovat: první externí přispěvatel rozběhal scorekeeper na Windows a poslal první externí model report. Prosba platí dál — zkušenostní report na jeden odstavec o tom, co to chytilo, minulo, nebo spletlo, formuje [roadmapu](https://github.com/michalstrnadel/scorekeeper/blob/main/ROADMAP.md) víc než cokoli jiného.

`try-it-in-60-seconds`

```text
# on a real Claude Code session — nothing to pre-install
/plugin marketplace add michalstrnadel/scorekeeper
/plugin install scorekeeper@scorekeeper

# six hooks attach a live scoreboard;
# watch .scorekeeper/scoreboard.md grow as you work —
# or render it live: scorekeeper board
```

---

https://michalstrnadel.com/cs/business/case-studies/scorekeeper
