Co spustitelný soubor v Rustu pod apps/majordomus-cli/ poskytuje klientovi MCP, odkud to pochází a co odmítá. Chování tak, jak je implementováno a testováno; kde se implementace a tento dokument rozcházejí, je chybný dokument a mění se ve stejném commitu jako oprava. Vývojářský detail (architektura, každá volba, schéma druhu) je ve vlastním README.md aplikace.
Co to je
Proces, který klient spouští a který mluví Model Context Protocolem na svém stdin a stdout, obsluhuje AI vrstvu repozitáře (čtení a pojmenované příkazy, které mění stav) a připojuje se k jedinému sdílenému serveru repozitáře: první takový proces v repozitáři naváže loopback HTTP projekci vedle své stdio relace (domovská stránka s výpisem všech povrchů, dokumentace tohoto repozitáře, Swagger UI, dokument OpenAPI, každá routa schopnosti, MCP přes HTTP) a oznámí kde; každý další se k ní připojí.
cargo build --manifest-path apps/majordomus-cli/Cargo.toml
apps/majordomus-cli/target/debug/majordomus mcp --inspect # what would be served, and every diagnostic
apps/majordomus-cli/target/debug/majordomus mcp # serve until the client goes; the first one is the server
apps/majordomus-cli/target/debug/majordomus mcp --standalone # this client alone: no port, no lease, no peers
bin/majordomus-mcp # the same, built first when needed: what a client configuration names
Log na stderr to pojmenuje v okamžiku, kdy server běží:
shared server listening on http://127.0.0.1:8741 — 7 surface(s): api http://127.0.0.1:8741/api/v1, cockpit http://127.0.0.1:8741/cockpit, docs http://127.0.0.1:8741/docs, home http://127.0.0.1:8741/, mcp http://127.0.0.1:8741/mcp, openapi http://127.0.0.1:8741/openapi.json, swagger http://127.0.0.1:8741/swagger; the one server of this checkout ...
Není to démon: nic jej nespouští kromě klienta, nic jej nedrží naživu kromě klientů a končí, když zmizí jeho vlastní klient a odejde poslední připojený. Nedrží žádný stav kromě paměti, nepotřebuje databázi a zapisuje jeden soubor: lease pod .ai/local/state/mcp/, lokální polovinou checkoutu, kterou vrstva vyhrazuje pro provozní stav, odstraněný při zastavení serveru. Je to jedna projekce registru schopností spustitelného souboru (CAPABILITIES.md): stejné schopnosti jsou trasy HTTP a příkazy capabilities a každý nástroj a zdroj zde je odvozen ze záznamu registru, žádný není deklarován v kódu MCP. Rozhodnutí je .ai/repo/adrs/0003-shared-mcp-server-peers-and-client-autostart.md.
Jeden server na checkout
| volba | první proces, který (atomicky) vytvoří .ai/local/state/mcp/server.json, je server; naváže se, zapíše do souboru svou URL a zaloguje ji |
| port | --http-port na --http-host (výchozí: rozhraní, které MAJORDOMUS_HTTP_HOST na tomto stroji jmenuje, a 127.0.0.1, když žádné nejmenuje — níže). Bez --http-port server žádá o port, který si jeho checkout odvodí, při každém startu stejný: 8741 pro primární checkout repozitáře a pro propojený worktree port v rozsahu 8742–9740 odvozený z identity worktree (cli::checkout_port), takže dva checkouty jednoho repozitáře žádají o různé porty a záložka s adresou nebo konfigurace klienta Streamable HTTP přežije restart (I2702). serve, serve ensure a env enter ho odvozují stejně a server.status ho hlásí jako požadovaný port. Obsazený port se nahradí volným a obojí se zaloguje, takže druhý repozitář ani zatoulaný proces nikdy nezabrání klientovi ve startu |
| připojování | pozdější majordomus mcp přečte pronájem, ověří, že server odpovídá za tento kořen, a přemostí svůj stdio na /mcp: jeden HTTP požadavek na zprávu, ping každých dvacet sekund, žádný index ani vlastní registr, takže startuje v milisekundách |
| zastaralý lease | lease, jehož server na tento kořen neodpovídá (proces byl zabit), soubor, který není dokumentem lease, prázdný soubor, nebo lease, jehož vlastník nepublikoval žádné URL během bind grace, převezme další proces a log říká, o který z těchto případů šlo; nic, co klient zanechá, nemůže zamknout ostatní |
| životnost | server slouží, dokud je připojen jeho vlastní klient nebo kterýkoli peer; když odejde klient vlastníka první, log řekne serving until the last peer leaves; když odejde poslední peer, server se zastaví, zavře port a odstraní zápůjčku |
| čerstvost | server sleduje repozitář, který obsluhuje: dříve než odpoví na požadavek, porovná otisk řídicích souborů gitu (HEAD, staging index, packed-refs, reflog, značky merge a rebase — po jednom stat) s tím, při kterém byl postaven jeho aktuální odečet, a požadavek, který zjistí rozdíl, přestaví vrstvu. Commit provedený za běhu serveru je viditelný přes API, přes MCP i v Cockpitu bez restartu; klient připojený před commitem si ponechá relaci i místo na nástěnce peerů. Žádný poll, žádné vlákno, žádný watcher. project.the-server-sees-the-current-tree |
| signály | SIGTERM, SIGINT nebo SIGHUP (serve stop, klient zabíjející svůj server, Ctrl-C v terminálu) požádá server o řádné zastavení: handler jen zaznamená žádost a server zavře své relace a epizody, zavře své listenery a uvolní lease — jen dokud je soubor ještě jeho — než skončí. serve vrací 0; majordomus mcp obsluhující vlastního klienta pak zemře na signál, protože stdio relace jeho klienta byla přerušena. Druhý signál nebo řádné zastavení, které do osmi sekund neskončilo, ukončí proces okamžitě; kill -9 nelze zachytit a další proces převezme zastaralý lease |
| převzetí | přemostěný peer, jehož server zemřel, volí znovu při další zprávě: sám se stane serverem a přenese initialize svého klienta, takže si toho klient nevšimne, nebo se připojí k procesu, který zvítězil první (re-attached to the shared server); když nemůže sloužit ani jedním způsobem (jeho vlastní --strict odmítá degradovanou vrstvu), klient dostane chybu JSON-RPC, která říká proč, nikdy ticho. Zpráva, která se potkala se selháním, se pošle znovu, jen když server prokazatelně neběžel — spojení nikdy nevzniklo nebo server odpověděl 404 (relace ztracena) či 409 (lease ztraceno) — nebo když je idempotentní: každá metoda kromě tools/call a nástroj, jehož deklarované nápovědy provádění to říkají (každé čtení). Volání, které bylo odesláno a nikdy nedostalo odpověď (timeout, přerušené spojení, 5xx), se znovu neposílá: klient dostane -32603 s textem outcome unknown a id požadavku, takže zapisující nástroj nikdy neběží dvakrát |
| odmítnutí | přemostěný peer, jehož server obsluhuje a nepřijme požadavek — požadavek odeslaný před initialize je zodpovězen 400 session_required, zpráva nad limit těla 1 MiB 413 — předá toto odmítnutí klientovi jako chybu JSON-RPC (-32600) jmenující stav a nikoho nevolí; zpráva nad limit je zodpovězena payload too large, aniž by byla odeslána. Volba je pro server, který nelze zastihnout, který požadavek přijal a nikdy neodpověděl, který ztratil relaci a znovu ji neotevře (404), nebo který říká, že ztratil lease (409 lease_lost) |
| meze | zpráva má na obou transportech nejvýše 1 MiB — řádek stdin nad ním se odpoví chybou parsování a relace pokračuje, tělo požadavku nad ním se odpoví 413; odpověď, kterou čte most, registrace rendezvous nebo spojení mesh, má nejvýše 32 MiB; požadavek jmenující MCP-Protocol-Version, kterému tento server nerozumí, se odpoví 400; id relace má 128 bitů ze zdroje náhodnosti operačního systému a relace se při zápisu své odpovědi znovu opatří razítkem, takže dlouhý požadavek nikdy nenajde svou relaci sklizenou za zády (I2147) |
| volby | --discovery a --strict serveru platí pro každou relaci, kterou obsluhuje; můstek je zdědí a log říká, ke kterému serveru se připojil |
--standalone | chování první verze: jen tento klient, žádný port, žádný lease, žádní peeři, nic se nikam nezapisuje |
| degradováno | když nelze lease zapsat nebo nahradit, nebo se sdílený server nespustí, klient je obsloužen sám přesně tak, jako by to udělal --standalone, a log řekne cannot use the shared server s cestou a důvodem |
serve | tentýž sdílený server bez vlastní stdio relace; pokud už nějaká běží, zaloguje URL a skončí s 0 |
Jeden repozitář, každý jeho server
Server obsluhuje checkout. Propojený worktree je vlastní checkout — má manifest, takže má kořen, lease i vlastní server — a do ADR 0035 nic neříkalo, že dva takové servery patří jednomu repozitáři. Nyní indexová trasa (GET /) jmenuje vedle identity samotného checkoutu i git repozitář, ke kterému checkout patří: commit je commit, ze kterého byl odpovídající spustitelný soubor sestaven, celý nebo unknown, a dirty zda jeho strom nesl necommitnuté změny (null, když to sestavení nevědělo) — stejná dvě pole odpovídají GET /api/v1/live a GET /api/v1/ready; stale je null, dokud je spustitelný soubor, ze kterého byl proces načten, stále souborem na disku, a říká proč, jakmile ho přestavba nahradila nebo odstranila — proces pak obsluhuje kód, který už neexistuje, a GET /api/v1/ready odpovídá ready: false se stejným důvodem (I1502); repository_id je checkoutu (otisk jeho kořene, s čím lease sonda porovnává), git_repository_id je repozitáře (otisk adresáře gitu, který sdílí každý worktree; chybí tam, kde se git nedá zeptat), linked_worktree říká, zda jde o primární checkout, a leaseholder říká, zda odpovídající proces je stále tentýž (níže). branch a head jsou větev a commit, z nichž byly obsluhované odpovědi čteny, jak je četl index (I1031). Domovská stránka a shell Swaggeru na /swagger nesou stejnou identitu na jednom řádku vedle svého obsahu; nic z toho není cesta, protože kdo dosáhne socketu, se dozví, který checkout odpověděl, a nikdy kde leží. GET /api/v1/server — nástroj majordomus_server, zdroj majordomus://server — vypisuje každý checkout, který git pro repozitář eviduje, primární první, každý s jeho leasem, stavem jeho serveru a počtem peerů, které drží, a říká, kde stojí server tohoto checkoutu měřeno proti tomu, co by obsluhoval tento spustitelný soubor:
| postavení | význam |
|---|---|
absent | žádný lease: tento checkout nic neobsluhuje |
starting | pronájem bez adresy, dost mladý na to, aby ho jeho vlastník ještě navazoval |
ready | server, který lease jmenuje, odpovídá za tento checkout, ze souboru na disku, ve verzi tohoto spustitelného souboru |
outdated | odpovídá, ale z jiné verze, nebo ze souboru nahrazeného od jeho startu: vše, co říká, je včerejší |
stale | lease jmenuje server, který neodpovídá, nebo vůbec není leasem; důvod říká které |
Seznam je to, co volající dostane, když se na nic neptá, a stojí čtení lease a sondu na checkout — na počítači se sty registrovaných worktree sto od každého. Volající, který chce vědět jen o checkoutu, ve kterém je, to řekne — checkouts=this v query stringu, ve vstupu nástroje nebo --checkouts this na příkazové řádce — a toto čtení nevyčísluje žádný jiný checkout, nečte žádný jiný lease a nesonduje žádný jiný server. Výchozí je celý seznam, protože to je odpověď, kterou tato schopnost dávala dříve, než pole existovalo.
Lease samotný je jeden typ, čtený jednou (lease::LeaseDocument, lease::LeaseFile::read): volba, serving jen pro čtení, snímek prostředí a stav ho všechny parsují stejným čtením a nese version serveru vedle spustitelného souboru, z něhož byl spuštěn. .ai/local/state/mcp/server.json je stále místem, kde ho člověk čte pomocí cat; stav je místem, kde ho čte program.
Server, který už není tím serverem
Lease lze převzít, i když proces, který ho držel, stále běží a je zdravý — protože jeho spustitelný soubor byl nahrazen rebuildem, což volba čte jako „servíruje kód, který už není na disku“, nebo protože je to starší release než volící klient. Instalátor ukládá každý release do vlastního adresáře, takže upgrade mění cestu spustitelného souboru a kontrola rebuildu to nikdy nevidí; kontrola releasu ano. Rozhoduje jediný verdikt (lease::version_verdict) pro volbu, serve ensure i server.status, řadící releasy podle precedence sémantického verzování definované release::version::Version, takže server na 1.0.0-alpha.1 je novější než 0.29.0 a starší než 1.0.0-alpha.2 a 1.0.0:
| release serveru je | klient tohoto spustitelného souboru |
|---|---|
| totéž | připojuje |
| starší | přebírá lease; starší server předá své relace dál, jak je uvedeno níže |
| novější | se připojí a jeho odpověď initialize nejprve říká, že tento klient je starší |
| nepublikováno, nebo neřazeno jako release (není to sémantická verze, například lokální tag sestavení) | připojí se; server.status hlásí outdated |
Starší klient nikdy nepřevezme novější server, takže dva releasy vedle sebe nebojují o lease: drží ho novější (I2163). Nahrazený proces se nezabíjí. Drží svůj socket, svou nástěnku peerů a generaci vrstvy, kterou načetl, a obsluhuje relace, které už měl, dokud neskončí. Je to záměrné: klient uprostřed odpovědi nemá přijít o svůj server proto, že někdo spustil cargo build.
Co nesmí dělat, je dál být tím serverem. Od okamžiku, kdy jeho vlastní čtečka zjistí, že lease už nenese jeho token:
| říká to jeho index | GET / odpovídá leaseholder: false; každé další pole — repository, repository_id, git_repository_id — je o něm stejně pravdivé jako o aktuálním serveru, což je přesně důvod, proč je třeba je jedním polem oddělit |
| sonda jej odmítne | lease::probe klade tři otázky, ne dvě: server Majordomus, tento checkout, stále držitel leasu. Klient s adresou z doby před převzetím se dozví, že tam žádný server není, místo aby dostal nástěnku, kterou nikdo jiný nevidí |
| nepřebírá nikoho nového | initialize bez relace dostane 409 lease_lost a odkaz na spouštěč jako cestu k aktuálnímu serveru |
| opouští mesh | objevování a kooperace se zastaví; tento checkout reprezentuje jen nástupce, takže vyřazený proces nemůže dál nahrazovat své ověřené odkazy |
| jeho otevřené relace se předají dál | odpovídá se jim po deklarovanou lhůtu zaneprázdnění (server.busy_grace_seconds), takže rozpracovaný požadavek a ty za ním skončí tam, kde začaly; poté je každá relace uzavřena a dostane 404, most klienta otevře novou, je odmítnut s 409 lease_lost a zvolí aktuální server; když nezbude žádná relace, nahrazený proces skončí (I2127) |
tests/mesh_lease_retirement.rs dokazuje převzetí se skutečnými servery: vyřazený proces stále odpovídá svému připojenému klientovi MCP, přestane se účastnit meshe a nástupce se synchronizuje se vzdáleným runtime bez opakovaných opětovných připojení.
Server příliš starý na to, aby odpověděl na třetí otázku, sonda přijme. Na tom endpointu jej nelze odlišit od aktuálního a jeho odmítnutí by bylo horším selháním: živý server považovaný za mrtvý se převezme, a tak se stane, že jeden checkout má dva.
Bylo to změřeno dřív, než to bylo napsáno: 2026-09-10 měl tento repozitář dva servery, jeden lease, a relace, jejíž prostředí nesla starší adresu, četla nástěnku peerů s jedním peerem, zatímco nástěnka, kterou sdíleli všichni ostatní, měla dva.
Čtyři významy slova „připraveno“
Tento spustitelný soubor odpovídá „ready“ čtyřikrát a audit, který podnítil ADR 0035, počítal tři z nich za nehodu (docs/ENTRY_AUDIT.md, příčina 5). Nejsou to jedna otázka špatně rozdělená: jsou to čtyři otázky o čtyřech subjektech a sloučení kterýchkoli dvou by ztratilo to jediné, co ten čtenář potřebuje. Každá je zde pojmenována se svým vlastníkem, aby další čtenář nemusel znovu objevovat, na kterou se dívá.
| otázka | odpověď | kdo se ptá |
|---|---|---|
| Je výstup producenta plochy na disku? | http::Served::ready(surface_id) — adresář, do kterého producent zapisuje, v sobě má soubory; route, na kterou tento spustitelný soubor odpovídá, je vždy připravena | úvodní stránka (GET /), která povrch bez výstupu vykreslí jako not built místo servírování 404 |
| Může tento proces odpovědět na požadavek? | health.ready — GET /api/v1/ready: registr a index sestavený při startu, jak vrstva načetla, a zda spustitelný soubor, z něhož byla načtena, je stále na disku — nahrazený ji činí nepřipravenou, s důvodem v stale — a zda tento proces stále drží lease checkoutu: server, kterému lease převzal jiný proces, také není připraven. Každý důvod je vypsán v reasons jako code: words (lease_lost, stale a layer_degraded, což ho nečiní nepřipraveným: zhoršená vrstva se poskytuje tak, jak se četla, s diagnostikou). health.report nese mesh jako samostatnou dimenzi, posuzovanou tak, jak mesh doctor posuzuje runtime, a unknown v procesu, v němž žádný server neběží. Jen lokální inicializace | sonda připravenosti hostingové platformy. Záměrně se nespojuje s ničím mimo tento proces: kontrola připravenosti, která sonduje závislost, shodí nasazení kvůli něčemu, co není tento proces |
| Přijímá něco spojení na adrese, kterou pronájem publikoval? | environment::ServiceAvailability — jedno TCP spojení s pevným rozpočtem a bez překladu jmen (environment::probe::reachable) | snapshot prostředí, který běží na shellovém promptu (majordomus env, .envrc) a nesmí utratit HTTP round trip ani sáhnout na DNS, aby řekl, co ví |
| Je to, co tam odpovídá, aktuální? | ServerStanding — server.status, z lease::probe (identita tohoto checkoutu přes HTTP) a verze a spustitelný soubor, které lease nese | každý, kdo musí důvěřovat tomu, co server říká: serve ensure, serve stop, kontrola server příkazu health.report a briefing relace |
Čtěte sloupec shora dolů a žebříček je jasný: třetí se ptá, zda je socket otevřený, čtvrtý, zda proces za ním patří tomuto checkoutu, ze souboru na disku, v tomto buildu. Zastaralý server odpoví na třetí a selže na čtvrtém, což je přesně třída, která tento repozitář už stála den.
health.report nese čtvrtý a jen čtvrtý. První je pro každý povrch zvlášť a patří na stránku, která povrchy vykresluje; druhý je výrok o procesu, který report vyřizuje, a ten nemůže být nepravdivý tam, kde report vzniká; a třetí nerozezná živý server od socketu, který drží někdo jiný. Kontrola deleguje na capability::builtin::server::standing_at — stejné čtení, ze kterého odpovídá server.status — takže report o zdraví a stav nemohou říct dvě různá slova o jednom leasu.
Zajištění serveru a jeho zastavení
majordomus serve status [--checkouts this|repository] [--format json]
where this checkout's server stands, and every server of the repository
majordomus serve ensure [--idle S] [--wait S] [--port P]
a ready server for this checkout, started if it must be
majordomus serve stop [--wait S] end the server this checkout's lease names
serve ensure přečte lease a vyzkouší server, který jmenuje, přesně jako volba, a konverguje: ready se vytiskne a nic se nespustí; na starting se čeká; absent a stale spustí server jako vlastní proces — tento spustitelný soubor, serve --fallback --idle S, jeho log na .ai/local/state/mcp/server.log, ve vlastní skupině procesů, aby přežil shell, který o něj požádal — a čekají, až je připraven; outdated spustí server jen tehdy, když by volba lease převzala (tentýž spustitelný soubor, vyměněný na disku, nebo starší release než tento spustitelný soubor) a jinak se hlásí s nápravou, protože server jiného buildu, který odpovídá, tento příkaz nekončí. Spuštěno dvakrát nespustí podruhé nic; spuštěno třemi shelly najednou nechá volba jeden ze tří serverů navázat a ostatní ustoupí. Volání je omezeno --wait; server, který se včas nepřipravil, se hlásí se stavem, jehož dosáhl, a exit kódem 10.
Server, který ensure spustí, nemá vlastního klienta. Skončí, když ho --idle sekund (ve výchozím stavu patnáct minut) nic nepoužilo — není připojena žádná relace MCP, nebyl zodpovězen žádný požadavek HTTP a není otevřen žádný živý kanál /events, takže člověk, který čte Cockpit nebo volá REST API, si server, který používá, ponechá (I2131) — což zachovává linii ADR 0003 — žádný proces bez klienta — platnou v čase, ne v každém okamžiku: vstup agenta má server dlužen před prvním připojením a checkout, ve kterém nikdo nepracuje, žádný nedrží.
serve stop pošle signál serveru, který jmenuje lease, pokud tento server odpovídá za tento checkout, a čeká, až lease toho serveru zmizí — dokument, který přečetl, podle jeho tokenu, nejen cesta — a pak až proces skončí. SIGTERM, který pošle, je uspořádané zastavení, ne kill: server dá připojeným peerům dvě sekundy na odchod, zavře jejich relace a epizody, které nesly, zavře své listenery (rozpracovaná odpověď se dokončí první), uvolní svůj lease a vyprázdní mesh. Soubor odstraní jen proces, jehož token soubor stále nese: každé odstranění a každé přepsání lease ho znovu čte pod zámkem vedle něj (.ai/local/state/mcp/server.lock), takže server zastavený těsně poté, co byl jeho lease převzat, nechá lease svého nástupce na pokoji. serve pak skončí s exit 0 vlastním návratem; zastavení, které nedoběhne do osmi sekund, nebo druhý SIGTERM ho přesto ukončí. serve ensure stále čekající ve volbě převezme uvolněnou cestu během milisekund, takže čekání na cestu by hlásilo server, který se nezastavil, ačkoli už se zastavil; převzetí se místo toho pojmenuje v odpovědi. Lease, který jmenuje server jiného checkoutu, nebo takový, který neodpovídá, se ponechá na pokoji a řekne se to; nic zde nezabíjí proces, který nebyl vyžádán jménem.
Kdo volá ensure. Volá ho startovní událost providera (session.ensure_server_on_start v politice, ve výchozím stavu zapnuto): jediný okamžik, kdy je dlužen server, který ještě nikdo nespustil, je příchod agenta, a briefing, který událost zapíše, nese jeden řádek — Shared server: ready http://127.0.0.1:8741 pid 123 — aby pracovník věděl už před prvním voláním nástroje, zda nástěnka, kterou má číst, existuje. Událost nikdy nesestavuje spustitelný soubor: chybějící nebo starší než jeho zdroje se v tom řádku pojmenuje a nechá být, protože hook není místo pro spouštění kompilátoru a server ze zastaralého kódu by odpovídal stromem ze včerejška. Launcher klienta MCP (bin/majordomus-mcp) konverguje vždy stejně přes volbu; shell vstupující do repozitáře (.envrc) je informován a není obsloužen, protože project.envrc-is-an-adapter zakazuje vstupnímu hooku cokoli spouštět a shell není klient.
Oznámení přežije server, kterému bylo učiněno. Most vidí každý rámec, který jeho klient pošle, takže si drží argumenty posledního oznámení, které server přijal, a řekne je znovu, kamkoli klient dopadne dál: po opětovném připojení, jakmile je relace znovu otevřena; po převzetí na nástěnku serveru, kterým se stal vlastní proces mostu. Worker, který oznámil jednou, je na nástěnce každého serveru, který ho obsluhuje, aniž by musel oznamovat znovu; pokyn oznámit znovu po opětovném připojení zůstává v bootstrapu pro jediný případ, který most nepokryje: klient, jehož vlastní proces je serverem a restartuje se.
Před čím volba nyní chrání. Vlastník udržuje své lease mladé, zatímco se vrstva načítá (Lease::keep_alive), takže studený start pomalejší než odklad vazby se nikdy nepovažuje za opuštěný; převzetí odstraní jen soubor, který posoudilo, nikdy ten, který mezitím dorazil, a dva voliči, kteří posoudili jedno lease jako zastaralé, skončí s právě jedním lease, protože každý před odstraněním znovu přečte soubor pod zámkem vedle něj; vlastník, jehož lease bylo převzato během vazby, odmítne publikovat a degraduje, místo aby přepsal adresu vítěze, a publikuje přes vlastní dočasný soubor, synchronizovaný před přejmenováním; a server, jehož lease je později převzato, je přestane nárokovat — nic, co dělá při odchodu, se nedotkne souboru, který patří někomu jinému — po dobu odkladu zaneprázdněnosti obsluhuje peery, které má, každého z nich předá aktuálnímu serveru a skončí. Čtečka samotného serveru také zapomíná relace HTTP, které přestaly pingovat, na každé cestě, a ne jen když vlastník čeká na odchod peerů, takže mrtvý peer nikdy nezůstane attached na nástěnce.
Rozhraní pojmenovává stroj
Lokální server se váže na loopback. Tato výchozí hodnota se nehýbe: vrstva, její diagnostika, její peeři a dva nástroje, které odpovídají o .ai/local/, nejsou pro každého hostitele v síti, ve které se notebook zrovna nachází.
Jeden stroj může chtít být dosažitelný — druhý stroj se připojující přes LAN, peer meshe vytáčející se dovnitř, telefon otevírající Cockpit — a do 0.14 byl jediný způsob, jak to říct, majordomus serve --host 0.0.0.0, psané ručně. To trvalo přesně tak dlouho jako proces: serve ensure, což spouští start relace a vstup do shellu, spouští server vůbec bez hostitele, takže příští zastavení z nečinnosti nebo příští relace vrátily checkout na loopback bez jediného slova a serve status celou dobu hlásilo desired 127.0.0.1.
Rozhraní se tedy čte z jednoho místa, které dědí každý starter:
export MAJORDOMUS_HTTP_HOST=0.0.0.0 # every interface; or one address of this machine| kdo server spouští | co váže |
|---|---|
majordomus serve | --host, je-li zadáno; jinak MAJORDOMUS_HTTP_HOST; jinak 127.0.0.1 |
majordomus mcp (klient volící sám sebe) | --http-host, je-li zadáno; jinak MAJORDOMUS_HTTP_HOST; jinak 127.0.0.1 |
majordomus serve ensure (start relace, vstup do shellu) | nepředává žádného hostitele; server, který spustí, proměnnou zdědí |
majordomus serve --deployment <id> | blok listen objektu nasazení a nic jiného: deklarovaná adresa se nepřepisuje prostředím stroje |
| kterýkoli z prvních tří, v linked worktree | 127.0.0.1, když pouze MAJORDOMUS_HTTP_HOST pojmenovává rozhraní; příznak stále váže tam, kde říká. Proměnná je tvář stroje v jeho síti, což je server primárního checkoutu; server worktree obsluhuje své vlastní relace a jeho příkazy meshe jdou přes primární (MESH.md) |
Z toho, že je to proměnná a ne nastavení, plynou čtyři věci:
- Patří stroji, nikdy repozitáři. Nic sledovaného ho nemůže nastavit. Klíč v
.ai/repo/policy.yamlby způsobil, že by každý klon veřejného repozitáře naslouchal v jakékoli síti, ve které se probudí. - Příkazová řádka vyhrává, prázdná hodnota není hodnota a hodnota, kterou nelze nikam navázat, odmítne start a pojmenuje adresu.
- Varování platí. Vazba zvolená proměnnou stále loguje, že každý hostitel dosahující na rozhraní může číst vrstvu; řádek logu před ním říká, že adresu pojmenovala proměnná, takže vazba není nikdy nevysledovatelná. Jen objekt deploymentu deklaruje expozici a jen deklarovaná je tichá.
server.statusodpovídá za prostředí, ve kterém se ptá.desired.hostje to, co by server spuštěný z toho prostředí navázal, takže adresa, kterou server naváže, a adresa, kterou stav nazývá žádoucí, jsou jedno rozlišení (cli::resolve_http_host) a nemohou se rozcházet. Běžící server odpovídáserve statusa odpovídá za prostředí, v němž byl spuštěn.
Nastavte to tam, kde to uvidí každý, kdo cokoli spouští — ve vlastním startovacím souboru shellu (~/.zshenv, ~/.profile). .envrc.local je pro jednoho z nich příliš pozdě: .envrc vyhodnotí majordomus-env enter, což zajistí server, dříve než načte .envrc.local, takže server, který spustí první cd, by proměnnou neměl, zatímco vše spuštěné z toho shellu poté by ji mělo.
Už běžící server si ponechá adresu, na kterou se navázal. Po nastavení proměnné majordomus serve stop a příští serve ensure — nebo příští relace — ho zvedne na pojmenovaném rozhraní.
Na tomto povrchu nejsou žádné účty ani tokeny, takže požadavek se posuzuje podle toho, odkud přišel (ADR 0126). Navázání mimo loopback poskytne každému dosažitelnému hostiteli povrch pro čtení a nic víc: požadavek z adresy, která není loopback, spustí schopnost jen tehdy, když schopnost čte (effect: read) nebo když je jejím vstupem podepsaná zpráva, kterou handler ověří proti politice důvěry — link.hello, link.sync a register meshe. Každá jiná trasa a každý nástroj MCP, který něco mění, odpoví volajícímu forbidden (HTTP 403) dříve, než se spustí jeho handler, včetně executions.start, který by jinak spustil libovolnou schopnost. Rozhodnutí se činí na každý požadavek, ne na relaci: id relace MCP je jméno, nikoli důkaz. Čtení se stále poskytuje celé síti, nešifrovaně; chcete-li zapisovat z jiného počítače, připojte se k loopback serveru přes SSH tunel (ssh -L 8741:127.0.0.1:8741 <machine>), jehož požadavky přicházejí z loopbacku.
Požadavek z loopbacku musí tento server pojmenovat adresou nebo localhost: požadavek, který jmenuje jiného hostitele, se odmítne forbidden dřív, než odpoví kterýkoli povrch, včetně čtení, protože přesně to posílá stránka, jejíž doména byla přinucena překládat na tento stroj (DNS rebinding). Požadavek z jiného hostitele smí stroj pojmenovat tak, jak jej zná. /swagger se servíruje s Content-Security-Policy, která připouští jen jeho připnuté soubory z CDN, jejichž integritní hashe stránka jmenuje, a jeho jediný inline skript.
Čím se spor posuzuje
O tom, který proces vlastní lease, rozhodují čtyři čísla a pátým zůstává vlastník schopen odpovídat. Jsou deklarována v bloku server: souboru .ai/repo/policy.yaml a čtou se jednou při otevření repozitáře: mcp i serve je čtou před první volbou, takže hodnoty níže jsou ty, které používá každá volba v procesu (I2129); dříve byla hodnota přečtená před politikou po celý život procesu zkompilovaným výchozím.
| klíč | o čem rozhoduje |
|---|---|
probe_timeout_seconds | jak dlouho sonda čeká, až současný vlastník odpoví, než se toto ticho počítá jako důkaz. Příliš málo a živý, ale pomalý vlastník je posouzen jako zastaralý a převzat zatímco ještě obsluhuje; příliš mnoho a zaseknutý drží server checkoutu tak dlouho |
bind_grace_seconds | jak dlouho se lease, který zatím nejmenuje žádnou URL, nechá být — server zapíše svůj lease dřív, než může obsluhovat, takže čerstvý lease bez URL je vlastník, který startuje, ne mrtvý |
join_timeout_seconds | jak dlouho proces čeká na připojení k souboru leasu nebo jeho vytvoření, než odmítne, místo aby čekal věčně |
busy_grace_seconds | jak dlouho se čeká na živého vlastníka, který neodpovídá, a ptá se ho znovu, než se jeho lease převezme. Volba dává zaneprázdněnému vlastníkovi tuto dobu a stejně tak serve ensure, než cokoli zahájí: sonda, která vyprší proti živému procesu, není mrtvý server, ale ten, který mlčí tak dlouho, je zaseknutý a nahradí se. Server, který ensure spustil po vyčkání této lhůty, je informován, který lease posoudil, takže jeho volba na něj nečeká podruhé |
read_deadline_seconds | jak dlouho server nechá spojení bez plně přečteného požadavku — čekání na hlavičku, udržování mezi požadavky nebo čtení těla — než spojení zavře. Klient posílající jeden bajt za sekundu by jinak držel vlákno požadavku navždy a dost takových klientů by nechalo výše uvedenou sondu bez odpovědi. Spojení, na jehož požadavek se odpovídá, nebo které bylo povýšeno na živý kanál, se tím nikdy nezavře. Každé zavření se počítá v server.status (connections.closed_by_deadline), vedle požadavků odmítnutých 503, protože všech 64 handlerů požadavků bylo zaneprázdněno (connections.refused_busy); sonda GET / se zodpoví i tehdy. Knihovna HTTP drží vlákno na každém spojení, dokud žije, a může zařadit do fronty spojení, které přijde ve vlně, dokud se nějaké neuvolní; termín je také tím, co omezuje toto čekání (I2156) |
idle_seconds | jak dlouho server spuštěný pro checkout, který nevlastní žádný klient — příkazem serve ensure nebo shellem vstupujícím do checkoutu — přežije svého posledního peera, než se sám zastaví; 900, pokud není deklarováno. --idle na příkazové řádce to přepíše. Server spuštěný ručně pouhým serve a bez --idle běží, dokud není zastaven, jako dříve: jeho vlastníkem je ten, kdo jej spustil (I2701) |
Do 2026-09-15 byly všechny zkompilované konstanty v apps/majordomus-cli/src/lease.rs: neměnné bez přebuildování, nikde neuvedené tam, kde by to čtenář hledal, a neviditelné pro každou projekci — přitom probe_timeout je přesně to číslo, které rozhoduje, zda živý vlastník drží, co vlastní. To je rozhodnutí o tom, jak je repozitář dozorován, nikoli implementační detail.
Schéma (share/schemas/majordomus/policy/policy.v1.schema.json) vlastní blok s additionalProperties: false, takže překlepnuté časování je odmítnuto místo čtení jako chybějící; test/cases/354_a_lease_contest_is_judged_by_a_declaration.sh to drží a lease::Timings::from_policy nese doc test dokazující, že deklarovaná hodnota je ta, která se používá, a že klíč, který politika vynechá, si drží svou konstantu. Dodané hodnoty jsou konstanty, které nahradily, takže jejich deklarace nezměnila chování — záměrně: změna způsobu, jak se soutěž posuzuje, má být vidět jako změna, a ne dorazit uvnitř refaktoringu.
Spuštění z klienta
Kořen tohoto repozitáře nese konfiguraci, kterou čte každý klient, všechny jmenují stejný launcher, takže první klient, který repozitář otevře, se stane serverem a ostatní se připojí:
| klient | soubor | co jmenuje |
|---|---|---|
| Claude Code | .mcp.json | server stdio, bin/majordomus-mcp; Claude Code se jednou zeptá, zda důvěřovat serveru projektu |
| Gemini CLI | .gemini/settings.json | stejný launcher pod mcpServers.majordomus |
| Codex | .codex/config.toml | [mcp_servers.majordomus], načteno, když je projekt důvěryhodný |
| bb | nic vlastního | orchestrátor: agent, kterého spouští (Claude Code, Codex, agent ACP), čte svůj vlastní soubor výše, takže vlákno bb se připojuje přes agenta, ne přes bb (ADR 0024). Claude Code pod bb běží s settingSources: project, což načte .mcp.json; server načtený ze souboru nastavení dostane dvě sekundy před prvním tahem, takže studený checkout, který musí sestavit exekutor, ho při init ukáže jako pending a poté jako připojený |
| anything speaking Streamable HTTP | the running server's /mcp | initialize answers with an Mcp-Session-Id; every later request carries it; DELETE /mcp ends the session; an idle session expires and the client re-initialises on the 404, as the transport prescribes |
The provider rows are the providers whose declaration names a client configuration; the whole
set, with what each reads and where it keeps its scratch checkouts, is
docs/generated/providers.md, generated from the same declaration.
bin/majordomus-mcp sestaví spustitelný soubor, když chybí nebo je starší než jeho zdroje (MAJORDOMUS_BUILD_PROFILE=release pro release build; MAJORDOMUS_BIN jmenuje spustitelný soubor a nikdy nebuilduje; MAJORDOMUS_NO_BUILD=1 odmítne sestavit a skončí s exit 12 s příkazem ke spuštění), nastaví MAJORDOMUS_SHARE na share/ distribuce vedle něj, nezapisuje nic na stdout a předá každý argument majordomus mcp. Klient nečeká na build. mcp je jediný příkaz, který klient spustí a pak na něj čeká, a klient, který nedostane handshake, hlásí server jako selhavší a pokračuje bez něj. Takže když tento checkout nemá spustitelný soubor ke spuštění, nebo takový, který nemůže přebuildovat — není cargo, je méně volného místa než práh buildu (MAJORDOMUS_MIN_FREE_MB, výchozí 5120), build selhal — bin/majordomus-mcp místo toho spustí nainstalovaný release (MAJORDOMUS_INSTALLED_BIN, výchozí majordomus v ~/.local/bin, kam jej dává install.sh) a na stderr řekne, který release a proč. Zastaralý spustitelný soubor, který lze přebuildovat, se přebuilduje jako dříve, MAJORDOMUS_MCP_BUILD=1 způsobí, že mcp builduje jako každý jiný příkaz, a každý jiný příkaz stále končí s exit 12, když nemá co spustit: záložní řešení je pro klienta, který by jinak byl odmítnut, nikdy způsob, jak nehlášeně spustit jiný release. Soubor uvnitř repozitáře se nikdy nebere za nainstalovaný release, ani takový, který neodpovídá na --version. ADR 0172; případ je test/cases/3177_a_client_always_gets_a_server.sh.
just mcp, just serve a just inspect v kořeni dělají totéž pro člověka; just docs-ui otevře Swagger UI běžícího serveru. Tentýž dokument, s tagy, příklady a stavy, je referencí API na webu na /docs/api/ a servíruje se surový na /openapi.json webu; instrukce initialize, OpenAPI info a GET / se otevírají jedním shrnutím z about.rs (CAPABILITIES.md).
Relace, která začíná připojením
Hook může jen přidat kontext; nic než první zpráva nezačne tah. Takže relace, která je pouze otevřena, dostane briefing a čeká, až člověk něco napíše, a dokud někdo nenapíše, není na nástěnce (ADR 0156, ADR 0172). share/shell/session-start.sh to pro Claude Code uzavírá: načtený z ~/.bashrc nebo ~/.zshrc obalí holé claude tak, že uvnitř repozitáře s vrstvou .ai/ je první zpráva majordomus prompt render session-start — vlastní prompt repozitáře (.ai/repo/prompts/session-start.md): řekni, jak jsi připojen, přečti nástěnku, najdi práci, která je dlužná, zkontroluj kolize, oznam a pak pracuj uvnitř rozsahu úlohy a vše drž lokální. Jakýkoli argument je vlastní argument člověka a předává se dál, stejně jako běh mimo dozorovaný repozitář, s MAJORDOMUS_NO_AUTOSTART=1, nebo když se prompt nedá vykreslit. Prompt je prompt repozitáře, takže jeho změna je commit a aktualizuje se každý checkout, který jej stáhne; soubor se dodává v share/ release a aktualizuje se s ním. test/cases/3221_a_bare_session_start_joins_the_cluster.sh to dokazuje v bash a zsh. Není nainstalován za vás: instalátor se nedotýká ničeho mimo svůj prefix (docs/DISTRIBUTION.md), takže načtení souboru je jediný řádek, který člověk přidá.
Claude in the browser
Three Claude surfaces meet a browser. Only one of them reaches this server today, and the other two must not be made to look as if they did:
| povrch | reaches this server through | reaches a page through | state |
|---|---|---|---|
| Claude Code (terminal, desktop, IDE) | .mcp.json above: stdio, then the shared server, as a loopback caller that reads and changes | the Claude in Chrome extension's bridge: the mcp__claude-in-chrome__* tools, on with claude --chrome or /chrome | works: the one path that drives a page and the live mesh in one session |
| Claude in Chrome side panel | nothing yet | samo o sobě | an agent of its own on claude.ai, which calls only a connector Anthropic's cloud reaches over the public internet; a server on loopback or a LAN address is out of its reach |
| claude.ai chat (web, desktop) | nothing yet | — | the same: a custom connector or nothing |
A tunnel from the public internet to the loopback server would give a vendor's requests the
loopback caller's right to change the repository (ADR 0126 judges a caller by its address), so
none is set up. The side panel and the chat reach the mesh when a vendor caller has a listener
of its own that authenticates it and serves reads only (I3285, which covers a claude.ai custom
connector as well as a ChatGPT one), and the hosted hub serves the mesh over TLS to an
authenticated caller only (the owner's decision of 2026-10-21: TLS first, then the hub, and no
anonymous caller reads the mesh). Until then a side-panel session reads what is published:
https://majordomus.dev and its /ui/, a snapshot built without a running mesh, which is why it
says the mesh is not observed. To work on the live mesh from a page, drive the page from Claude
Code.
Set it up once, per person rather than per repository:
- In Claude Code run
/chromeand choose Enabled by default. It is a user setting: Claude Code ignoresCLAUDE_CODE_ENABLE_CFCin a project's settings ("a project can't turn on Claude in Chrome", Claude Code documentation,code.claude.com/docs/en/chrome), so this repository does not try. - Sign the extension in to the claude.ai organization that
/statusnames in Claude Code. Claude Code needs a claude.ai login (Pro, Max, Team, Enterprise); an API key or asetup-tokenkeeps the bridge off. - The first enable writes the native messaging host — on macOS
~/Library/Application Support/Google/Chrome/NativeMessagingHosts/com.anthropic.claude_code_browser_extension.json, which runsclaude --chrome-native-host— and Chrome finds it after a restart.
Every browser action still passes the extension's own per-site permissions; a rule in
.claude/settings.json does not lift them, and this repository pre-approves no browser tool.
When it does not connect — list_connected_browsers answers an empty list, or a browser tool says
"Browser extension is not connected":
- The extension is not running, or it is signed in to another account or organization than
/statusnames. Sign it in again with that organization. /chrome→ Reconnect extension, then restart Chrome and Claude Code.- An organization IP allowlist that blocks
bridge.claudeusercontent.comblocks the bridge (Claude Code documentation,code.claude.com/docs/en/network-config). - A JavaScript dialog open in a controlled tab stops every later browser event until a person dismisses it.
The mesh half is diagnosed as always, from the same session: majordomus mesh doctor, and
majordomus_peers for the board.
Peery
Každá relace je peer: vlastní stdio klient serveru, každý přemostěný majordomus mcp a každý klient mluvící přímo /mcp. Peer je pojmenován tím, co jeho klient poslal v initialize (clientInfo.name a version: claude-code, codex, gemini-cli, nebo jak se klient sám nazývá), číslován p1, p2, ... v pořadí připojení, s transportem, kdy se připojil a kdy byl naposledy viděn. instructions výsledku initialize řeknou klientovi URL serveru, jeho vlastní id peeru a každého dalšího peera s tím, co oznámil, před prvním voláním nástroje.
| nástroj | schopnost | argumenty | odpovědi |
|---|---|---|---|
majordomus_peers | peers.list | checkouts? | každý pracovník repozitáře s checkoutem, na kterém je, vlastní id volajícího, oznámení každého peera, každá dvojice claimů, které se potkávají, a které checkouty odpověď pokryla |
majordomus_announce | peers.announce | intent, scope? | záznam volajícího peera a peeři, s jejichž zabraným rozsahem se střetává |
Oznámení je jeden řádek záměru a cesty relativní k repozitáři, kterých se peer chystá dotknout. Nástěnka žije v paměti serveru a zmizí s procesem; peers.announce je druhu command, protože mění tuto paměť, a klientům MCP se oznamuje jako ne read-only. Přes prosté HTTP není žádný volající, takže POST /api/v1/peers/announce se odmítne (422) a GET /api/v1/peers odpovídá bez caller.
Nástěnka patří repozitáři (ADR 0044)
Server servíruje checkout, takže repozitář, na němž se pracuje přes linked worktree, má jednu nástěnku na worktree — a až do ADR 0044 odpovídal peers.list jednou z nich, zatímco bootstrap v CLAUDE.md a AGENTS.md říkal každému workeru, že viděl repozitář. Změřeno 2026-09-11: sedm živých serverů, jeden git_repository_id, sedm nástěnek, devět agentů napříč šedesáti worktree, z nichž každý četl nástěnku, která držela jen jeho.
peers.list nyní sbírá. Vyjmenuje checkouty, které git registruje, a přečte zápůjčku každého — týmiž čtečkami, jaké používá server.status — vlastní nástěnku vezme z paměti tohoto procesu a každého dalšího checkoutu, jehož server odpovídá, se zeptá na jeho vlastní nástěnku samotnou (checkouts=this). Ten parametr drží sběr hluboký jeden skok: server dotázaný na this nevyjmenovává žádný checkout a nezkouší žádný server, takže se nikdy nemůže zeptat zpět. Bez něj není žádné opakování, protože server příliš starý na to, aby parametr znal, by odpověděl celou svou nástěnkou a cyklus je jediné selhání, které to mít nesmí; takový checkout se místo toho hlásí jako nepřečtený s důvodem.
| pole | co říká |
|---|---|
peers[].checkout | checkout, na kterém peer je — jeho id, worktree, větev a zda je to tento. Není to dekorace: p1 je první relace každé nástěnky, takže sloučený výpis bez něj drží několik p1 |
overlaps | každá dvojice nároků, které se potkají, nyní napříč checkouty i v rámci jednoho, každý jmenující checkout, na kterém je druhý worker |
boards | jedna položka na checkout, který odpověď pokrývala, dosažený či nikoli, s jeho postavením a důvodem, proč nebyl přečten |
complete | zda šlo přečíst každou pokrytou nástěnku. false znamená, že se nešlo zeptat checkoutu — krátká nástěnka se nikdy nemá zaměnit za prázdný repozitář |
checkouts | repository (výchozí) nebo this: jen tento checkout, nic nevyčísluje, nic nesonduje, čte jednu nástěnku z paměti |
Id peera je pozice na jedné nástěnce, přidělená v pořadí připojení a po opětovném připojení přidělená znovu — relace, která byla dnes ráno p3, je p1, jakmile se její most znovu připojí. Nic podle něj nesmí korelovat pracovníka v čase; checkout, který peer nese, je trvalá polovina jeho identity. Oznámení rovněž patří spojení: most zopakuje poslední oznámení svého klienta po opětovném připojení nebo převzetí a pracovník, jehož proces mostu je nahrazen, oznamuje znovu.
Stojí jedno čtení lease na registrovaný checkout, jednu sondu na checkout, jehož lease pojmenovává adresu, a jeden round trip na server, který odpoví. V tomto repozitáři 2026-09-11 — 118 registrovaných checkoutů, 7 živých serverů — trvalo vyčíslení a sondování, které server.status už provádí, 3,0 s wall.
Claim se zodpovídá, nejen zaznamenává. peers.announce porovná rozsah, který dostane, se všemi ostatními oznámeními a vrátí peery, jejichž claimy se s ním setkávají, s dvojicemi cest, které se setkávají: dva claimy se setkávají, když jsou stejné nebo je jeden uvnitř druhého (apps obsahuje apps/majordomus-cli; app ne, protože claim je cesta, ne prefix řetězce). peers.list hlásí tytéž kolize napříč celou nástěnkou, každou dvojici jednou. Stále to není vynucování, ani check --overlap shellového nástroje, které hlásí rozsahy úloh jiných worktree a končí s 0. Odmítá rozsah vlastní úlohy z start --scope, při check, při finish a v hooku pre-push — ale kolize je nyní známa v okamžiku svého vzniku, a ne objevena později v historii větve.
Nástěnka dosáhne i na workera, který o ni nikdy nepožádá. Čtení bylo dobrovolné a dobrovolná spolupráce selhala: relace se ohlásila, její transport se obnovil pod novým id peera a osm dalších ji tři hodiny nevidělo; dvě relace postavily týž subsystém, protože se ani jedna nepodívala. Proto majordomus context — příkaz, který bootstrap nařizuje každému workerovi spustit před prací — nese sekci PEERS mezi GIT a TASK: kdo další je připojen, co si každý z nich nárokuje a konkrétně které z těch nároků se dotýkají rozsahu aktuální úlohy. Čte lease sdíleného serveru (který je vázán na repozitář, takže jej linked worktree najde přes --git-common-dir v primárním checkoutu) a ptá se GET /api/v1/peers. Nápověda nikdy nenese zátěž: bez lease, bez jq či curl, se serverem, který neodpoví do dvou sekund, nebo s nástěnkou, na které nikdo není, zůstane context přesně takový, jaký byl, protože repozitář s jediným workerem nesmí dostat sekci o tom, že je sám. test/cases/106_context_peers.sh to celé drží.
Oznámení přežije spojení, které ho učinilo. Relace, která se znovu připojila, dřív mlčky ztratila vše, co řekla, sama sobě i všem ostatním; nástěnka nyní drží oznámení odešlého peera a vypisuje ho s attached: false, takže to, na čem relace řekla, že pracuje, přežije přerušený socket. Peer, který nikdy neoznámil, nezanechá nic, uchovává se nejnovějších 32 odešlých peerů, aby server, který běžel celý den, nebyl muzeum, a připojený peer se nikdy nevyhodí, aby se uvolnilo místo pro toho, kdo odešel. count z peers.list jsou skutečně připojení peeři; pole peers je delší, když nástěnka drží to, co kdo řekl, než odešel.
Epizody
Peer je spojení. Epizoda je posezení práce a pro klienta bez vlastních hooků poskytovatele je spojení jediné, co může nakreslit její hranici (ADR 0103). Do té doby bylo kreslení hranice pod modelem zapojeno jen pro Claude Code a tvrzení repozitáře, že to dělá, bylo tvrzením o jednom dodavateli.
| nástroj | schopnost | argumenty | odpovědi |
|---|---|---|---|
majordomus_session_attach | episodes.attach | external_id | epizoda, kterou toto spojení nyní drží, zda byla obnovena, a lhůta pro opětovné připojení |
majordomus_session_detach | episodes.detach | external_id? | epizoda tak, jak byla uzavřena, a co ohlásila koncová událost repozitáře |
majordomus_episodes | episodes.list | žádné | každá epizoda, kterou tento server drží, otevřená i odpojená, a vlastní epizoda volajícího |
stateDiagram-v2 [*] --> Attached: initialize, a peer attaches and no episode opens Attached --> Open: episodes.attach opens the episode, or resumes this client's own Open --> Open: every message is the episode's heartbeat Open --> Detached: the connection goes, detached and not closed Detached --> Open: episodes.attach again resumes the same episode under a new peer id Open --> Closed: episodes.detach closes it deliberately into a session record Detached --> Closed: no reconnect in 15 minutes, the reaper closes it as interrupted Open --> Closed: the server stops, closed as shutdown Detached --> Closed: the server stops, closed as shutdown Closed --> [*]
initialize nic neotevírá. Klient, který otevře server jen proto, aby přečetl jedno pravidlo, není pracovník a nezanechá žádný záznam; attach je volání, které provede klient, který ví, že chce epizodu. To je také to, co drží záruku, kterou drží test/cases/90_mcp_shared_server.sh — obsluha repozitář vůbec nemění.
Identita patří klientovi, nikdy ne id peera. external_id je to, čemu klient trvale říká sezení, ve kterém je — id jeho konverzace nebo vlákna — a jeho celým úkolem je přežít opětovné připojení. Id peera se přiděluje na připojení a je pokaždé, když se klient vrátí, jiný řetězec; považovat jej za trvalé je to, co udělalo relaci neviditelnou pro osm dalších po tři hodiny dne 2026-09-09.
Přerušené spojení se odpojí; nezavře se. Zmizení klienta řídí dvoje hodiny a odpovídají na dvě otázky. SESSION_IDLE_TIMEOUT (90 s) rozhoduje, kdy se socket zapomene. episodes::REATTACH_GRACE (15 minut) rozhoduje, kdy je práce u konce. Čtenář logů uvidí spojení vyklizené dlouho před epizodou, kterou neslo, a je to záměr.
Nic zde nezapisuje záznam relace. Nástěnka spouští majordomus capture session --provider generic --event start|end — stejný příkaz, který spouští shim hooku poskytovatele, se stejným tvarem payloadu, přes stejný čtecí kód — a hlásí, co řekl, doslova, v poli repository epizody. Druhý zapisovač záznamu, který už hooky zapisují, by byla opakovaná sémantická definice, kterou CAPABILITIES.md zakazuje. Vlastní úložiště repozitáře je také to, co epizodu obnoví po restartu serveru: zabitý server nezapíše žádnou koncovou událost, epizoda zůstane otevřená v .ai/local/state/sessions-open/ a další attach pod stejnou identitou je session start --if-open keep, který ji zachová.
Zachycení surového promptu tu není a je deklarováno, že není. MCP server dostává initialize, volání nástrojů a oznámení; prompt osoby mezi nimi není nikdy, v žádné verzi protokolu. share/providers.yaml říká prompts: none pro generického poskytovatele, s tímto zdůvodněním v poli důkazů, a pod prompts neexistuje hodnota connection, po které by kdokoli mohl sáhnout. Co každý poskytovatel umí a kde to bylo ověřeno, je majordomus product providers a majordomus capture status.
Co rozhoduje o tom, co se obsluhuje
Program nejmenuje žádný soubor repozitáře kromě dvou konvencí, které vrstva sama dokumentuje, .ai/manifest.yaml a sources.yaml v sekci knowledge, a jak se který druh čte, zjišťuje za běhu z distribuce nástroje. Zbytek jsou data:
| rozhoduje | čteno z |
|---|---|
| kde je vrstva | nejbližší předek obsahující .ai/manifest.yaml; .git a .majordomus/ nejsou značky |
| které sekce existují | sections: v manifestu |
| které soubory jsou zdroje, jakého druhu | .ai/repo/knowledge/sources.yaml, jeden pathspec a druh na třídu, přes git index |
| jak se druh čte a jaké klíče smí nést | share/kinds.yaml a share/schemas/<kind>.schema.json v distribuci plus vlastní soubory repozitáře pod .ai/repo/knowledge/ |
| které nástroje existují | spustitelné schopnosti s expozicí nástrojem MCP, složené pod apps/majordomus-cli/src/capability/builtin/ |
Důsledky, na které se repozitář může spolehnout:
- nové pravidlo, prompt, profil, milník, úkol, tvrzení nebo dokument se servíruje, jakmile jej
git addvloží do stagingového indexu, bez změny spustitelného souboru a bez restartu: staging přesune soubor, který server sleduje, takže příští požadavek přestaví vrstvu (project.the-server-sees-the-current-tree); - nová třída v
sources.yamlpojmenovávající známý druh nebo nový druh se schématem pod.ai/repo/knowledge/se servíruje stejně; .ai/local/se nikdy neservíruje, ať je sledován, či ne;- čtené YAML je podmnožina, kterou definuje
SCHEMAS.md, takže soubor, který shell nástroj odmítne, se odmítne i zde, se jmenovaným stejným řádkem.
Zdroje
| URI | obsah |
|---|---|
majordomus://repository | JSON: kořen, schéma vrstvy, sekce, stav gitu, režim objevování, přítomné druhy, každá diagnostika |
majordomus://scope | JSON: rozsah repozitáře tak, jak byl přečten, jeho původ a každý sledovaný soubor spočtený proti němu (SCOPE.md) |
majordomus://<kind>/<identity> | soubor tak, jak byl přečten, text/markdown nebo application/yaml |
URI se rozřeší jednou, jednou funkcí, ať se na něj ptá kdokoli: resources/read, nástroj majordomus_get i GET /api/v1/object odpovídají na stejné URI stejně. URI, které registr nezná, se na žádném z nich nenajde. majordomus://repository je dotaz (repository.info) s expozicí zdroje: přečtený jako zdroj je to report jako text JSON; přečtený přes majordomus_get je to report jako data (answer) se stejným textem vedle (content). Registr odmítne dotaz vystavený jako zdroj, jehož vstup cokoli vyžaduje, protože čtení nic nedodá.
Objekt má také adresu, kterou URI není: majordomus://rule/project.x@1 je identita a /cockpit/objects/rule/project-x-1 je to, kam je čtenář poslán. Adresa se odvozuje z druhu a identity jednou funkcí, takže existuje pro každý objekt a je stejná na každé ploše; majordomus_entity odpovídá na obě podoby a majordomus_kinds hlásí všechny dvě identity jednoho druhu, které se redukují na jednu adresu. majordomus_entity také jmenuje veřejnou stránku dokumentace objektu (documentation: projekce, route, url, nebo reason, jeho druh není publikován), čtenou z site/data/publication.toml repozitáře a base_url webu — null, když repozitář žádnou publikaci nedeklaruje.
Identita jsou identitní pole druhu spojená pomocí @ — majordomus.scope-integrity@1 pro pravidlo, continue pro prompt, implementation pro profil, M001 pro milník — nebo, u druhu bez identitních polí (politika, dokument), cesta relativní k repozitáři. Každý vypsaný zdroj nese _meta.majordomus s druhem, identitou a proveniencí: cesta, adresář, třída, která jej objevila, sekce manifestu, pod kterou spadá, a jeho velikost.
Nástroje
| nástroj | schopnost | argumenty | odpovědi |
|---|---|---|---|
majordomus_list | objects.list | kind?, tag? | objekty, shrnuté |
majordomus_get | objects.get | uri | označeno source: declarative, soubor vrstvy s metadaty, původem, typem média a obsahem; nebo builtin, dotaz, který URI promítá (majordomus://repository) s jeho answer, původem schopnosti a stejným textem jako content |
majordomus_search | objects.search | query, kind?, limit? | zásahy podřetězce bez ohledu na velikost písmen, každý s jedním řádkem úryvku |
majordomus_entity | entity.show | uri?, kind? + slug? | jeden objekt jako adresovatelný uzel: jeho odvozená route, reference, které deklaruje, reference, které se na něj vyřeší, plochy, které za něj odpovídají, stav spustitelných artefaktů, které jmenuje, a jeho veřejná stránka dokumentace |
majordomus_kinds | entity.kinds | žádné | každý druh, který index drží, s trasou jeho výpisu, kolik objektů je adresovatelných a každá kolize tras, která existuje |
majordomus_repository | repository.info | žádné | dokument majordomus://repository |
majordomus_scope | repository.scope | žádné | dokument majordomus://scope: deklarace, její původ, součet |
majordomus_scope_classify | repository.scope_classify | path | zda je cesta relativní k repozitáři uvnitř rozsahu, nebo mimo něj, důvod a pravidlo, které rozhodlo |
majordomus_capabilities | capabilities.list | kind?, exposure? | každá schopnost s jejími projekcemi |
majordomus_capability | capabilities.describe | id | jedna schopnost: schémata, původ, každá projekce |
majordomus_peers | peers.list | checkouts? | každý pracovník repozitáře, sesbíraný z nástěnky každého checkoutu (výše) |
majordomus_announce | peers.announce | intent, scope? | zaznamenává, na čem pracuje volající peer (výše) |
majordomus_mcp | mcp.projection | effect? | popsaná projekce (níže): server, verze protokolu, transporty a připojené relace, obsluhované metody, každý nástroj s efektem a nápovědami, zapisovatelé, zdroje a stav konfigurace každého deklarovaného klienta zde |
majordomus_perf | perf.counters | žádné | čítače práce a časování fází tohoto procesu: co se stalo jednou při startu, co se děje při každém volání |
majordomus_worktrees | worktree.topology | žádné | dokument majordomus://worktrees: kontejner, trunk, každý worktree s jeho postavením a diagnostikou, každá větev, součty |
majordomus_worktree_status | worktree.status | path? | jeden worktree — vlastní worktree repozitáře nebo ten, který obsahuje path — s jeho postavením, kanonickou cestou, nezapsanou prací a tím, zda je tam, kam patří; cesta v jiném repozitáři se odmítne |
majordomus_worktree_inspect | worktree.inspect | branch | kanonická cesta větve, zda existuje, co cestu zabírá, worktree, který ji drží |
majordomus_worktree_migration_plan | worktree.migration_plan | žádné | každý špatně umístěný worktree s tím, kam patří, jak by se přesunul a co tomu brání; výjimky; nic nemění |
Tabulka výše je základní sada, ne celek: seznam nástrojů je projekce registru schopností, roste s každým složeným modulem a seznam v próze, který by tvrdil úplnost, by byl příště nepravdivý. majordomus_capabilities a docs/generated/capabilities.md jsou odvozená, úplná reference; to, co je napsáno zde, je to, co čtenář potřebuje před jejím otevřením.
Relace a kontinuita
Dva z nástrojů odpovídají o .ai/local/ — polovině vrstvy, která jmenuje tento stroj — a jsou důvodem, proč se server váže na rozhraní loopback. Servírují se a nikdy nepublikují: žádný generátor je nezapisuje do docs/generated/ a žádná stránka webu žádný z nich nenese.
| nástroj | schopnost | odpovědi |
|---|---|---|
majordomus_continuity | continuity.state | co by dostal pracovník pokračující zde: epizodu, na kterou ukazatel vede, aktivní úlohu, předávku a checkpoint, které se pro tento worktree a větev rozřeší, s jejich štítky, blokátory |
majordomus_lifecycle_episodes | lifecycle.episodes | každá otevřená epizoda úložiště — nejen ta, kterou sleduje ukazatel — s její relací poskytovatele, jejím stavem (current, open, foreign, stranded) a posledním řádkem knihy označeným jí |
majordomus_lifecycle_recovery | lifecycle.recovery | epizody, které se už nemohou samy uzavřít, a příkaz, který každou vyčistí, dočasné soubory, které zabitý close nechal ve sledované sekci relací, rozložení ukazatele a aritmetika zahájených proti uzavřeným |
majordomus_lifecycle_runtime | lifecycle.runtime | commit, u něhož byl sestaven index tohoto procesu, proti commitu, na kterém repozitář právě je, a zda se shodují |
majordomus_lifecycle_providers | lifecycle.providers | pro každého poskytovatele: události lifecycle, které jeho adaptér deklaruje, zda umí archivovat prompty a vynucovací záznamy, které tento repozitář zapojuje do jeho hooku |
majordomus_lifecycle_closed | lifecycle.closed | sledované záznamy, které klon obdrží: kolik, kolik na této branch a nejnovějších dvacet |
První odpovídá na otázku workera a ostatní na otázky operátora, což je jiná otázka, ne nadmnožina: continuity.state následuje state/session-current.yaml a tento ukazatel je symlink, který přesměruje nejnovější startovní událost. CONTINUITY.md obsahuje model a celou cestu.
Popsaná projekce
mcp.projection — nástroj majordomus_mcp, zdroj majordomus://mcp, trasa GET /api/v1/mcp, stránka Cockpitu /cockpit/mcp a první řádky majordomus mcp --inspect — odpovídá na to, co by tento dokument jinak musel vypisovat a ručně udržovat pravdivé:
majordomus mcp --inspect # its lines beginning server, protocol, effect, writes and client are this answer
curl -s http://127.0.0.1:8741/api/v1/mcp | jq '{server, protocol_versions, effects, writers, clients}'
curl -s 'http://127.0.0.1:8741/api/v1/mcp?effect=repository_mutation' | jq '.tools[].name'| pole | co to je | odkud pochází |
|---|---|---|
server, protocol_versions | kdo odpovídá na initialize a s jakými verzemi | konstanty, kterými server odpovídá; verze je verzí spustitelného souboru |
transports | stdio a http a relace, které jsou nyní připojeny přes každou z nich | nástěnka peers tohoto procesu v okamžiku dotazu |
serving | metody, které může požadavek jmenovat; že prompty a notifikace nejsou poskytovány | seznam, který dispečer konzultuje, než se podívá na požadavek |
tools, tool_count, effects, writers | každý nástroj (nebo nástroje jednoho effect) s jeho schopností, účinkem a nápovědami | registr; účinek je klasifikace schopnosti |
resources | kolik schopností odpovídá na URI, kolik objektů vrstva drží, tvar URI | registr a index |
clients | každý klient, pro nějž distribuce deklaruje konfiguraci, a zda je zdejší soubor wired, foreign nebo absent | share/providers.yaml a jedno čtení každého souboru |
findings | konfigurace, která existuje a nejmenuje launcher; žádný klient vůbec nenakonfigurován | odvozeno z clients, každé s nápravou |
Nic v tom není vlastním seznamem, takže se nemůže rozejít s tools/list: schopnost, která získá nástroj, je v odpovědi, na stránce Cockpitu i v generované referenci při dalším požadavku, aniž by se upravil jiný soubor.
Co smí nástroj měnit
Anotace nástroje se nepíší v kódu MCP: jsou to klasifikované nápovědy schopnosti (ExecutionPolicy::hints), které vyplývají z jejího efektu, a efekt samotný nese _meta.majordomus.effect.
| účinek | co volání mění | readOnlyHint | destructiveHint | idempotentHint | openWorldHint |
|---|---|---|---|---|---|
read | nic | true | false | true | false |
process_state | paměť tohoto procesu a nic mimo ni (peer, který se ohlašuje) | false | false | false | false |
repository_mutation | vlastní soubory repozitáře | false | true | false | false |
Klasifikace je konzervativní ve směru, který volající přežije: nic, co něco mění, se neohlašuje jako bezpečné k opakování, protože žádný handler nedeklaroval, že druhé volání je prázdná operace, a cokoli, co zapisuje repozitář, se ohlašuje jako schopné přepsat nebo odstranit. openWorldHint je všude false, protože žádný handler nesahá za hranice stroje: pozorování forge a konzultace poradce jsou příkazy, které spouští člověk, ne schopnosti. Které nástroje zapisují repozitář, není nikdy seznam v tomto dokumentu: jmenují je instrukce initialize, odvozené z registru, a také majordomus mcp --inspect. Každý nástroj nese kanonické id v _meta.majordomus.id a své inputSchema a outputSchema z kanonických schémat. Odmítnuté volání je výsledek s isError: true, důvodem jako textem a kategorií jako jedním slovem v _meta.majordomus.error.code — invalid_input, not_found, refused nebo forbidden, stejným slovem, jakým odpovídá trasa HTTP jako error.code, protože obojí čte z téže chyby. Odmítnutý výsledek nenese structuredContent: výstupní schéma popisuje úspěch. Neznámý nástroj, metoda nebo zdroj je chyba protokolu.
Chování při selhání
Každý řádek jmenuje selhání, co klient pozoruje a co to dokazuje: testy, které ho řídí, nebo uvedené omezení, které říká, proč to nedělá nic. Řádek bez obojího, nebo takový, který cituje test či případ, jenž tento strom nemá, test/cases/3056_every_failure_class_names_its_proof.sh odmítne.
| state | co se děje | důkaz |
|---|---|---|
žádné .ai/manifest.yaml nad pracovním adresářem | kód 12, uvedený počáteční adresář | test/cases/3015_an_unsupervised_repository_exits_12_from_both_tools.sh |
projektová data pod .majordomus/ a žádný manifest | exit 12, jmenující majordomus migrate | apps/majordomus-cli/tests/repository_discovery.rs::the_pre_ai_layout_is_refused_by_name |
manifest nebo sources.yaml poškozený, neznámý klíč, nepodporované schéma | exit 10, pojmenovaná cesta a klíč nebo řádek | apps/majordomus-cli/tests/repository_discovery.rs::a_nearer_broken_manifest_is_an_error_not_a_skip |
| jeden soubor, který se nemůže stát objektem | vyloučeno; chybová diagnostika jmenuje svou cestu a stabilní kód; index je degraded a dál slouží; --strict místo toho skončí s 10 | apps/majordomus-cli/tests/metadata_contract.rs::malformed_front_matter_is_named_and_excluded, apps/majordomus-cli/tests/mcp_stdio.rs::strict_refuses_a_degraded_layer_before_serving |
git nepoužitelné | --discovery vcs (výchozí) skončí s 13 pojmenovávajícím --discovery filesystem; blok gitu v majordomus://repository čte unavailable s důvodem | apps/majordomus-cli/tests/index_behavior.rs::without_git_vcs_discovery_is_refused_and_the_walk_still_works |
| nenalezen žádný sdílený adresář (druhy a schémata) | exit 12, jmenuje každý vyzkoušený adresář a jako nápravu jmenuje --share / MAJORDOMUS_SHARE | apps/majordomus-cli/tests/metadata_contract.rs::without_a_share_directory_nothing_starts_and_the_search_is_named |
| neplatné schéma druhu nebo soubor schématu, nebo repozitář předefinovávající distribuovaný druh | exit 10, oba soubory pojmenovány | apps/majordomus-cli/tests/external_extension.rs::a_repository_defines_a_new_kind_with_its_schema_and_it_is_served_without_a_rust_change |
| klient zavře svou rouru | proces skončí s 0, až odejde poslední připojený peer, pokud to byl server | apps/majordomus-cli/tests/mcp_stdio.rs::a_client_that_closes_its_read_end_ends_the_session |
| port je obsazen | místo toho se naváže volný port; obojí se zaloguje | apps/majordomus-cli/tests/mcp_shared.rs::a_taken_port_falls_back_to_a_free_one_and_serve_defers_to_the_running_server, apps/majordomus-cli/tests/serve_lifecycle.rs::ensure_takes_a_free_port_when_the_one_it_asks_for_is_taken |
| pronájem pojmenovává server, který za tento kořen neodpovídá | je převzat; stale lease se zaloguje s URL | apps/majordomus-cli/tests/mcp_shared.rs::a_stale_lease_is_taken_over |
| sdílený server, ke kterému je most připojen, zemře | most se ujme při své další zprávě, nebo se znovu připojí k procesu, který se ho ujal; klient se nikdy znovu neinicializuje | apps/majordomus-cli/tests/mcp_shared.rs::a_bridged_peer_takes_over_when_its_server_dies, apps/majordomus-cli/tests/mcp_shared.rs::a_bridged_peer_re_attaches_when_another_process_took_the_lease_first |
most nemůže převzít (jeho --strict, porušená vrstva) | požadavek klienta je zodpovězen chybou JSON-RPC (-32603), která pojmenovává proč; relace stdio zůstává otevřená | apps/majordomus-cli/tests/mcp_shared.rs::a_peer_that_cannot_serve_the_layer_says_so_instead_of_taking_over |
| soubor lease je poškozený, prázdný, nebo je bez URL déle než odklad vazby | je převzat; corrupt lease, empty lease nebo abandoned lease se zaloguje s cestou | apps/majordomus-cli/tests/mcp_shared.rs::a_corrupt_lease_is_taken_over, apps/majordomus-cli/tests/mcp_shared.rs::a_lease_of_another_schema_and_an_old_empty_file_are_taken_over, apps/majordomus-cli/tests/mcp_shared.rs::an_abandoned_lease_is_taken_over_without_waiting_when_it_is_old |
lease nelze vytvořit, připojit ani nahradit (souborový systém odmítající zápisy pod .ai/local/), nebo nelze spustit sdílený server | klient běží samostatně, jako by běžel --standalone: cannot use the shared server se zapíše do logu s cestou a důvodem, potom serving this client alone; žádný port, žádný lease, žádní peeři; chyby samotné vrstvy stále končí výstupním kódem uvedeným výše | apps/majordomus-cli/tests/mcp_shared.rs::an_unwritable_lease_directory_degrades_to_a_standalone_session |
server dostane SIGTERM, SIGINT nebo SIGHUP | zastavuje se řádně: relace a epizody uzavřeny, listenery uzavřeny, lease uvolněn, pokud je stále jeho vlastní; serve končí s 0, majordomus mcp pak zemře na signál; druhý signál nebo osm sekund jej ukončí okamžitě; jeho mosty znovu volí při další zprávě | apps/majordomus-cli/tests/mcp_shared.rs::sigterm_removes_the_lease_before_the_process_dies, apps/majordomus-cli/tests/mcp_shared.rs::sigint_and_sighup_remove_the_lease_before_the_process_dies, apps/majordomus-cli/tests/lease_holder.rs::serve_stop_ends_the_server_in_order_and_closes_its_episodes |
server je ukončen pomocí SIGKILL | při ukončení nic neběží; pronájem, který zanechá, pojmenovává proces, který už neexistuje, a další klient ho okamžitě převezme | apps/majordomus-cli/tests/serve_lifecycle.rs::a_stale_lease_and_a_killed_server_are_both_recovered, apps/majordomus-cli/tests/lease_busy.rs::a_silent_server_whose_process_is_gone_is_taken_over_at_once |
| vlastník lease žije a je zamrzlý: nic neodpovídá | uchazeč čeká deklarovanou lhůtu, pak převezme lease; log jmenuje vlastníka, kterého se vzdal | apps/majordomus-cli/tests/lease_busy.rs::a_live_server_that_stays_silent_loses_its_lease_after_the_grace, apps/majordomus-cli/tests/serve_lifecycle.rs::a_live_owner_that_never_answers_is_still_taken_over, apps/majordomus-cli/tests/lease_declared.rs::a_busy_owner_is_given_up_on_after_the_declared_grace_and_the_log_names_it |
| lease serveru převzal jiný proces | server to oznámí, nepřijme žádného nového klienta a neomezený server skončí sám | apps/majordomus-cli/tests/lease_lost.rs::a_server_that_lost_its_lease_says_so_and_takes_on_nobody_new, apps/majordomus-cli/tests/cov_runtime_a_server.rs::an_unbounded_server_whose_lease_is_taken_over_ends_by_itself |
--http-host, --host nebo MAJORDOMUS_HTTP_HOST jmenuje adresu, která není loopback | obsluhováno, s varováním, že každý hostitel dosahující na toto rozhraní může číst vrstvu, její diagnostiku a její peery; když to zadala proměnná, říká to řádek před tím | apps/majordomus-cli/tests/mcp_shared.rs::a_non_loopback_bind_is_warned_about, test/cases/992_the_local_bind_is_the_machines_to_name.sh |
MAJORDOMUS_HTTP_HOST jmenuje adresu, na kterou nelze nic navázat | proces skončí nenulovým kódem s cannot bind <address> a nezanechá žádný lease | test/cases/992_the_local_bind_is_the_machines_to_name.sh |
klient HTTP odejde bez DELETE /mcp | jeho relace vyprší po devadesáti sekundách ticha; server, jehož vlastník již odešel, tehdy skončí, nikdy později | apps/majordomus-cli/tests/shared_units.rs::the_endpoint_opens_reaps_and_closes_sessions, apps/majordomus-cli/tests/serve_in_use.rs::a_server_read_over_http_outlives_its_idle_timeout_and_ends_when_left_alone |
| klient HTTP posílá požadavek pomalu, nebo vůbec | jeho spojení se zavře při deklarovaném čtecím deadline a započítá se; ostatní klienti dostanou odpověď | apps/majordomus-cli/tests/http_slow_client.rs::slow_client_released_test |
| každý obslužný kód požadavku je držen | další požadavek je okamžitě odmítnut jako zaneprázdněný; sonda živosti je stále zodpovězena | apps/majordomus-cli/tests/http_slow_client.rs::past_the_handler_bound_a_request_is_refused_and_the_probe_is_answered |
| handler požadavku se nikdy nevrátí (zamrzlý worker) | jeho worker je držen, dokud běží: termín čtení omezuje příchod požadavku, nikoli jeho zpracování | omezení: netestováno — žádný test nezmrazí handler a nic jej neomezuje; odmítnutí busy výše je to, co klient potká, když je držen každý handler |
| disk je plný | neměřeno: lease, žurnál meshe a generovaný výstup zapisují každý vlastní chybovou cestou | omezení: netestováno — žádný test ani případ nezaplňuje souborový systém, takže není ukázáno, co zápis odmítnutý pro nedostatek místa udělá každému zapisovači |
| adresář žurnálu meshe odmítá zápisy (jen pro čtení) | na master se vlastní událost, jejíž připojení selže, nehlásí volajícímu jako nedurabilní | omezení: netestováno na masteru — PR #1179 a #1196 (I2706, případ 3052) hlásí odmítnuté připojení jako nedurabilní a testují jej od začátku do konce; dokud nepřistanou, žádný test na tomto stromě to nedělá |
| proces je zabit mezi vytvořením lease a jeho prvním zápisem | zbylý prázdný lease se převezme, jakmile je starší než lhůta vazby, jak říká řádek pro prázdný lease | omezení: netestováno jako kill — žádný test v tom okamžiku proces neukončí; stav, který zanechá, je ten, který ručně sestavuje apps/majordomus-cli/tests/mcp_shared.rs::a_lease_of_another_schema_and_an_old_empty_file_are_taken_over |
| selže zápis keep-alive leasu | selhání se zahodí a příští tick to zkusí znovu; nic se nelogguje ani nehlásí | omezení: netestováno — Lease::keep_alive zahazuje chybu záměrně a žádný test nevyvolává selhávající touch |
Dva soubory jednoho druhu, které si nárokují jednu identitu, jsou oba vyloučeny a oba pojmenovány, jak vyžaduje smlouva pravidel. Nic se neopravuje, nedoplňuje výchozími hodnotami ani nepřepisuje.
Záměrně neposkytováno
- Prompty MCP. Prompt assety repozitáře vykreslují
{{CONTEXT}}z lokálního stavu checkoutu, který vlastní nástroj shellu; servírované nevykreslené by vypadaly jako hotové. Jsou to zdroje (majordomus://prompt/<name>), nikoli prompty. - Hierarchie bootstrap souborů. Kořenový
README.md,AGENTS.mda další soubory providerů se servírují jako dokumenty se zaznamenaným adresářem; nic je nesloučí ani neseřadí, protože repozitář nedefinuje žádnou sémantiku slučování. Zaznamenáno v.ai/repo/adrs/0001-rust-cli-and-stdio-mcp.md. - Mutace repozitáře, která není schopností. Nástroj zapisuje do repozitáře jen tehdy, když jeho schopnost deklaruje
.writes_repository(); handler je ten, který dosahuje trasa HTTP a příkazový řádek, takže odmítnutí tam je stejné odmítnutí zde a MCP nepřidává žádného vlastního zapisovatele. - Odběry, oznámení o změně seznamu a stream iniciovaný serverem na
/mcp(tento server nic neposílá bez vyzvání). HTTP projekci téhož registru poskytuje sdílený server amajordomus serve; vizCAPABILITIES.md. - Trvalá koordinace. Nástěnka peerů je paměť jednoho procesu a sesbíraná nástěnka je čtení několika z nich v okamžiku dotazu: na čem peer pracuje napříč relacemi a stroji, to je záznam úlohy a rozsah nástroje shellu, ne tohle. Větev pushnutá před začátkem relace nenese žádné oznámení a jejím claimem není o nic méně; čtečkou pro ni je
scripts/collision-check.
Co to dokazuje
test/cases/72_rust_mcp.sh sestaví exekutor a mluví s ním přes roury uvnitř repozitáře, který napsal init nástroje shellu, a čte majordomus://repository přes nástroj a čtení zdroje, aby viděl jednu odpověď; 76_capabilities_projections.sh přečte jednu schopnost zpět přes každé rozhraní; 90_mcp_shared_server.sh spustí dva klienty přes bin/majordomus-mcp v takovém repozitáři a ověří, že oba obsluhuje jeden server, že každý vidí druhého, že lease přichází a odchází se serverem a že tři konfigurace klientů jmenují launcher. Vlastní sada crate (cargo test --manifest-path apps/majordomus-cli/Cargo.toml) pokrývá příkazovou řádku, výběr kořene, kontrakt metadat, determinismus, kolečko protokolem, stdout jen protokolu, nemutaci, sekvenci přidat–odebrat–rozbít u externího rozšíření a v tests/mcp_shared.rs sdílený server nad skutečnými rourami a sockety: volbu, most, relace /mcp, záložní port, odkládání serve, --standalone, převzetí po zabití, opětovné připojení, zamítnutí, když přebírající nemůže obsluhovat, převzetí poškozeného, cizího, prázdného nebo opuštěného lease, dva klienty startující ve stejný okamžik, nezapisovatelný adresář lease degradující na samostatnou relaci, SIGTERM zastavující server v pořadí a nezanechávající žádný lease (tests/lease_holder.rs: signál po převzetí šetřící lease nástupce a serve stop uzavírající epizody serveru a končící 0), poškozený provoz na /mcp a průhlednost mostu: přemostěná relace a restartovaný server odpovídají bajt po bajtu tak, jak odpověděl první server. Doktrína za tabulkou selhání je pravidlo project.shared-server-resilience. tests/server_status.rs drží případ dvou worktrees a případ zastaralého lease; tests/health_server.rs drží kontrolu server z health.report proti skutečnému serveru, checkoutu, který nikdo neobsluhuje, a lease jmenujícímu adresu, na které nikdo neodpovídá, a ověřuje, že kontrola a status říkají o jednom lease jedno slovo. test/cases/992_the_local_bind_is_the_machines_to_name.sh drží rozhraní: loopback, když žádné nic nejmenuje, proměnná následovaná serve, serverem, který spouští serve ensure, a tím, který volí mcp, vítězný příznak, ignorovaná prázdná hodnota, zachované varování, shodující se desired.host a nenavazatelná adresa zamítnutá bez lease; 85_deployment_bind.sh drží vedle toho deklarovanou adresu. tests/hot_path.rs pošle stovky rámců a vyžaduje nezměněné startovní čítače (majordomus_perf); majordomus bench změří čas každého nástroje přes skutečný podřízený proces (CAPABILITIES.md). Tvrzení jsou v CLAIMS.yaml pod mcp-, hot-path-no-rebuild a benchmark-coverage-derived.