Kontext
Majordomus kreslí hranici epizody pod modelem. Argument je v lib/capture.sh a je dobrý: worker, kterému se řekne otevřít relaci, ji otevře, když si vzpomene, což nikdy není ta epizoda, na které záleželo — ta, která skončila pádem, compaction nebo zavřením okna. Poskytovatel to ví, spustí událost a hranice se nakreslí tam, nebo je fikce.
Změřeno 2026-09-11 to bylo zapojeno pro jednoho poskytovatele a vlastní zpráva nástroje o situaci byla špatná ve dvou směrech najednou.
Hlásil jednoho poskytovatele ze šesti. share/providers.yaml deklaruje agents, bb, claude-code, codex, gemini a generic. majordomus capture status procházel mj_capture_providers — poskytovatele s řádkem v tabulce adaptérů promptů — a vypsal dva řádky, oba claude-code. Ostatních pět nebylo hlášeno jako nepodporované; nebylo hlášeno vůbec. Slovník pro ně měl slovo, unsupported, jehož vlastní definice zněla „žádný adaptér: žádná zdokumentovaná událost nepředá příkazu prompt před během modelu“ — a protože smyčka šla přes tabulku adaptérů, nemohl se k němu dostat žádný poskytovatel, na kterého by se dalo zeptat. Stav, který nelze vypsat, není stav. Kdo se zeptal „umí Codex v tomto repozitáři zachytit prompty“, dostal ticho a ticho se čte jako ne.
A mlčení se stalo nepravdivým. Komentář nad tabulkou adaptérů říká, že poskytovatel v ní chybějící „není podporován: nemá zdokumentovanou událost, která předá prompt osoby příkazu dřív, než model běží“. To bylo pravdivé tvrzení o světě, když bylo napsáno, a dnes už není:
| hranice relace | prompt před modelem | |
|---|---|---|
| Claude Code | SessionStart, SessionEnd, PreCompact, PostCompact | UserPromptSubmit (prompt_text) |
| Codex CLI | SessionStart, SessionEnd, PreCompact, PostCompact | UserPromptSubmit (prompt) |
| Gemini CLI | SessionStart, SessionEnd, PreCompress | BeforeAgent (prompt), BeforeModel |
Všechny tři ověřeny proti dokumentaci vendora 2026-09-11; citace jsou v share/providers.yaml, jedna na buňku. Dva produkty dodaly systémy hooků a tento nástroj dál naznačoval, že je nemají, tím, že je vynechával. Vynechání bylo tvrzením o cizím produktu, pro které nástroj neměl důkazy a nikdy nebyl požádán je obhajovat, protože nic v deklaraci se nikdy nezeptalo „kde jsi to ověřil“.
Třetí fakt je ten, o kterém mandát byl. I s adaptéry pro všechny tři by kontinuita epizody byla výsadou nástrojů, které náhodou dodávají hooky. Klient bez nich — poskytovatel generic, „worker bez vlastní konvence“ — by neměl žádnou hranici a tvrzení repozitáře, že vede hranici pod modelem, by zůstalo tvrzením o těch dodavatelích, kteří vyhověli.
Rozhodnutí
Jedna. Schopnost poskytovatele je deklarována jako data, s citací u každé buňky.
share/providers.yaml získá blok offers: na poskytovatele: episode (hooks | connection | none), prompts (hook | none) a řetězec důkazu pro každý — URL dokumentace dodavatele, nebo prohlášení, že žádná taková dokumentace neexistuje, spolu s tím, co se hledalo. Slova se deserializují do enumů, takže hodnota mimo slovník selže při čtení souboru s uvedenou cestou, místo aby degradovala na none.
Klíč je offers a ne lifecycle, protože lifecycle: na záznamu poskytovatele už pojmenovává něco jiného: události, které řídí adaptér tohoto nástroje pro poskytovatele (seznam, čtený schopností lifecycle.providers a držený proti MJ_CAPTURE_LIFECYCLE testem test/cases/170). Ten seznam je zapojení; offers je schopnost dodavatele. Jsou to dva fakty, držené ve dvou klíčích, z důvodu uvedeného níže pod Dvě.
Každý povrch je projekcí oné jediné deklarace. Nese ji share::ProviderDeclaration, nese ji product::ProductProvider a odtud z ní bez vlastního řádku vycházejí příkazová řádka (majordomus product providers), HTTP route (/api/v1/product/providers), nástroj a resource MCP (majordomus_providers, majordomus://product/providers), generovaný docs/generated/providers.{json,yaml} i produktový dataset webu. Přidání poskytovatele nebo změna buňky je jedna úprava a jedno majordomus generate.
Dvě. Schopnost není zapojení a capture status hlásí obojí.
Nyní šest slov, ne pět. unsupported je vyhrazeno pro poskytovatele, který danou věc nemůže udělat, a citace deklarace je důvod, proč se vypíše. unadapted je nové a je poctivým názvem stavu, ve kterém jsou dnes Codex a Gemini: poskytovatel událost dokumentuje, tato distribuce nedodává žádný řádek adaptéru a mezera je v nástroji. Sloučení obou — což dělal kód — způsobilo, že tento nástroj řekl něco nepravdivého o cizím produktu. unconfigured, named, wired a verified se nemění.
capture status prochází sjednocení deklarovaných poskytovatelů a tabulek adaptérů, takže je hlášen každý poskytovatel, s jeho deklarovanou schopností v jednom sloupci a stavem tohoto checkoutu v druhém.
Tři. Klient bez hooků dostane svou epizodu ze spojení.
Pro episode: connection je hranicí spojení MCP:
stateDiagram-v2 state "peer attached, no episode" as peer state "episode open" as open state "detached, not closed" as detached state "closed" as closed [*] --> peer : initialize note right of peer : a reader is not a worker peer --> open : episodes.attach(external_id) opens it, or resumes the client's own open --> open : every message the client sends is its heartbeat open --> detached : connection lost detached --> open : episodes.attach again, same episode, new peer open --> closed : episodes.detach, deliberately detached --> closed : no reconnect in 15 min, by the reaper, as interrupted open --> closed : server stops, shutdown closed --> [*]
apps/majordomus-cli/src/episodes.rs drží nástěnku. Nezapisuje záznam relace: spouští majordomus capture session --provider generic --event start|end, tentýž příkaz, který spouští shim hooku poskytovatele, se stejným tvarem payloadu, přes stejného čtenáře. lib/capture.sh připouští poskytovatele, jehož deklarovaný zdroj epizod je connection, bez řádku adaptéru, protože poskytovatel, který nevyvolává žádnou událost, nemá žádný vendorův tvar payloadu, na který by se adaptoval, a nic k instalaci.
Čtyři. Peer není epizoda a epizoda je klíčována externí identitou.
Id peera (p1, p2, …) je jméno tohoto procesu pro toto připojení. Přiděluje se v pořadí připojení, další server ho znovu použije a není identitou pracovníka — 2026-09-09 se relace v tomto repozitáři ohlásila, byla znovu navázána pod novým id a tři hodiny byla neviditelná pro osm dalších, protože ji všichni brali jako jednu. Epizoda je proto klíčována identitou, kterou klient dodává a umí ji reprodukovat, peer, který ji drží, je měnitelné pole a invarianty jsou: připojení drží nula nebo jednu epizodu; initialize samo žádnou; znovu se připojující klient se k své epizodě přiřadí jménem; a epizoda přežije své připojení o omezenou odkladnou dobu, protože zahozený socket je to nejběžnější, co se rozdělané práci stává, a není jejím koncem.
Pět. Co se netvrdí.
Zachycení surových promptů zůstává specifické pro providera. Serveru MCP se předávají initialize, volání nástrojů a notifikace; prompt člověka mezi nimi není, v žádné verzi protokolu. generic.offers.prompts je none s tímto zdůvodněním zapsaným v poli důkazů a pod prompts záměrně neexistuje hodnota connection, po které by mohl sáhnout jakýkoli provider.
Zamítnuté alternativy
Otevřít epizodu při initialize. Učinilo by to „každý klient dostane epizodu“ doslova pravdivým a je to špatně dvakrát. Klient, který otevře server jen kvůli přečtení jednoho pravidla, není pracovník a neměl by zanechat záznam; a test/cases/90_mcp_shared_server.sh tvrdí, že obsluha repozitář nijak nemění, což je záruka cennější než pohodlí. Attach je jedno volání a klient, který chce epizodu, je klient, který ví, že ji chce.
Klíčovat epizodu id peera. Důvod, proč je to špatně, je popsán výše a zaplatilo se za něj hodinami, ne argumenty.
Uzavřít epizodu, když se spojení přeruší. Publikovalo by to záznam, že práce skončila, pokaždé, když skončí socket. Odklad opětovného připojení je patnáct minut proti devadesátivteřinovému timeoutu nečinnosti transportu a jsou to dvoje hodiny, protože odpovídají na dvě otázky: žije tento socket a skončila tato práce.
Zapsat záznam relace ze spustitelného souboru v Rustu. Druhý zapisovatel záznamu, který už hooky poskytovatele zapisují, je opakovaná sémantická definice, kterou ADR 0004 zakazuje. Nástěnka spouští jediný příkaz, který to už dělá.
Dodat adaptéry pro Codex a Gemini v této změně. Jsou skutečné a jsou jinou prací: každý potřebuje zapisovač formátu konfigurace, který tento nástroj dnes nezapisuje (tabulka [hooks] v TOML, blok hooks v JSON), a každý nese výhrady, které adaptér musí zakódovat — Gemini nečeká na SessionEnd, jeho PreCompress nemůže blokovat, SessionEnd.reason Codexu je konstanta. Deklarace schopnosti s citací a hlášení unadapted pojmenovává mezeru dnes přesně; implementovat ji hádáním by nepojmenovalo.
Důsledky
capture statushlásí dvanáct řádků, ne dva, a každý nese důkazy, které za ním stojí. Dva z nich říkajíunadapted, což je v této distribuci trvalá viditelná mezera, ne mlčení.majordomus product providersa každá jeho projekce získávají matici schopností. Nemuselo se nic upravovat povrch po povrchu, což je smyslem.- Obecný klient MCP — cokoli, co lze nasměrovat na
bin/majordomus-mcp— dostane epizodu, pracovní kontext, briefing a uzavřený záznam, bez spolupráce dodavatele. - Nová blokující invarianta je
project.a-provider-capability-cites-its-evidence: deklarovaná schopnost bez citace je odmítnuta. Její validátor jescripts/provider-evidence-checkatest/cases/200_generic_mcp_episode.shřídí celý životní cyklus přes skutečný transport. - Zmizení klienta nyní řídí dvoje hodiny a čtenář logů uvidí spojení uklizené dlouho před epizodou, kterou neslo. To je zamýšleno a je to zde zapsáno, protože by to jinak vypadalo jako únik.