Přeskočit na obsah

Tato stránka byla automaticky přeložena z angličtiny, jediného jazyka, ve kterém se tento web píše. Číst originál.

HTTP API: lifecycle

Životní cyklus epizody tak, jak ho vidí operátor: každá otevřená epizoda úložiště tohoto checkoutu, a ne jen ta, kterou sleduje ukazatel, epizody, které se už samy nedokážou uzavřít, commit, o kterém tento proces odpovídá, proti commitu, na kterém je repozitář, co deklaruje, že umí adaptér každého providera, a sledované záznamy, které klon dostává. Čteno z lokální poloviny vrstvy a z indexu; nic zde nezapisuje.

Vykresleno z docs/generated/openapi.json, který spustitelný soubor generuje ze svého registru schopností a CI jej odmítne, je-li zastaralý. Surový dokument je na /openapi.json; běžící server servíruje stejný dokument na vlastní /openapi.json a Swagger UI na /swagger.

životní cyklus

Životní cyklus epizody tak, jak ho vidí operátor: každá otevřená epizoda úložiště tohoto checkoutu, a ne jen ta, kterou sleduje ukazatel, epizody, které se už samy nedokážou uzavřít, commit, o kterém tento proces odpovídá, proti commitu, na kterém je repozitář, co deklaruje, že umí adaptér každého providera, a sledované záznamy, které klon dostává. Čteno z lokální poloviny vrstvy a z indexu; nic zde nezapisuje.

GET /api/v1/lifecycle/closed lifecycle.closed

Záznamy, které klon dostane

Sledovaná, trvalá projekce uzavřených epizod: kolik jich je, kolik se jich uzavřelo na této větvi a nejnovějších dvacet. Toto je jediná polovina subsystému, která přežije klon; vše pod .ai/local/ jmenuje tento stroj a nikam necestuje.

  • query
  • behaviorally_verified
  • vyžaduje se měření výkonu
  • cache procesu (max_entries=2, ttl_seconds=30)
  • Nástroj MCP majordomus_lifecycle_closed

Žádné parametry.

Vyzkoušejte

Port je ten, který server zalogoval; 8741 je ten, o který žádá jako první. Hodnoty jsou první příklad operace.

curl -s "http://127.0.0.1:8741/api/v1/lifecycle/closed"

Testováno v apps/majordomus-cli/tests/http_serve.rs, který přehrává routu s těmito hodnotami proti serveru na skutečném socketu.

Odpovědi

statustělovýznam
200 ClosedSessions Záznamy, které klon přijímá: výsledek.
400 ErrorBody invalid_input: Vstup neodpovídá schématu: parametr špatného typu, neznámý parametr nebo hodnota, kterou schopnost odmítne.
404 ErrorBody not_found: Vstup pojmenovává něco, co repozitář nemá.
500 ErrorBody internal: Schopnost selhala z vlastního důvodu; zpráva ho pojmenovává.
výchozí ErrorBody Jakékoli jiné selhání, které transport hlásí: 405 method_not_allowed pro jinou metodu na této cestě, 400 invalid_input pro tělo, které není objektem JSON.
GET /api/v1/lifecycle/episodes lifecycle.episodes

Každá otevřená epizoda

Každá otevřená epizoda v úložišti tohoto checkoutu, s relací providera, která ji vlastní, worktree a branchí, na které se otevřela, kde stojí a co kniha naposledy viděla, že dělala. `continuity.state` hlásí tu jednu epizodu, na kterou ukazuje pointer, což je správné pro briefing a slepé ke každému jinému oknu otevřenému na stejném worktree.

  • query
  • behaviorally_verified
  • vyžaduje se měření výkonu
  • cache process (max_entries=2, ttl_seconds=2)
  • Nástroj MCP majordomus_lifecycle_episodes
  • Zdroj MCP {"name": "lifecycle-episodes", "uri": "majordomus://lifecycle/episodes"}

Žádné parametry.

Vyzkoušejte

Port je ten, který server zalogoval; 8741 je ten, o který žádá jako první. Hodnoty jsou první příklad operace.

curl -s "http://127.0.0.1:8741/api/v1/lifecycle/episodes"

Testováno v apps/majordomus-cli/tests/http_serve.rs, který přehrává routu s těmito hodnotami proti serveru na skutečném socketu.

Odpovědi

statustělovýznam
200 Epizody Každá otevřená epizoda: výsledek.
400 ErrorBody invalid_input: Vstup neodpovídá schématu: parametr špatného typu, neznámý parametr nebo hodnota, kterou schopnost odmítne.
404 ErrorBody not_found: Vstup pojmenovává něco, co repozitář nemá.
500 ErrorBody internal: Schopnost selhala z vlastního důvodu; zpráva ho pojmenovává.
výchozí ErrorBody Jakékoli jiné selhání, které transport hlásí: 405 method_not_allowed pro jinou metodu na této cestě, 400 invalid_input pro tělo, které není objektem JSON.
GET /api/v1/lifecycle/providers lifecycle.providers

Co deklaruje adaptér každého poskytovatele

Na providera: události životního cyklu, které jeho adaptér deklaruje ve vlastním slovníku providera, zda umí archivovat prompty a které záznamy vynucování tohoto repozitáře pojmenovávají jeho hook. Deklarováno v share/providers.yaml a v politice; nikdy se nehádá z toho, zda je na disku shim.

  • query
  • behaviorally_verified
  • vyžaduje se měření výkonu
  • cache process (max_entries=2)
  • Nástroj MCP majordomus_lifecycle_providers

Žádné parametry.

Vyzkoušejte

Port je ten, který server zalogoval; 8741 je ten, o který žádá jako první. Hodnoty jsou první příklad operace.

curl -s "http://127.0.0.1:8741/api/v1/lifecycle/providers"

Testováno v apps/majordomus-cli/tests/http_serve.rs, který přehrává routu s těmito hodnotami proti serveru na skutečném socketu.

Odpovědi

statustělovýznam
200 ProviderLifecycles Co deklaruje adaptér každého poskytovatele: výsledek.
400 ErrorBody invalid_input: Vstup neodpovídá schématu: parametr špatného typu, neznámý parametr nebo hodnota, kterou schopnost odmítne.
404 ErrorBody not_found: Vstup pojmenovává něco, co repozitář nemá.
500 ErrorBody internal: Schopnost selhala z vlastního důvodu; zpráva ho pojmenovává.
výchozí ErrorBody Jakékoli jiné selhání, které transport hlásí: 405 method_not_allowed pro jinou metodu na této cestě, 400 invalid_input pro tělo, které není objektem JSON.
GET /api/v1/lifecycle/recovery lifecycle.recovery

Co úložiště potřebuje, aby někdo udělal

Otevřené záznamy, které se už nemohou samy uzavřít, dočasné soubory, které zabité uzavření zanechalo ve sledované sekci relací, zda byl ukazatel migrován na rozvržení po providerech a aritmetika, která říká, zda je zohledněna každá epizoda, kterou ledger viděl začít. Každý nález nese příkaz, který jej odstraní.

  • query
  • behaviorally_verified
  • vyžaduje se měření výkonu
  • cache process (max_entries=2, ttl_seconds=2)
  • Nástroj MCP majordomus_lifecycle_recovery
  • Zdroj MCP {"name": "lifecycle-recovery", "uri": "majordomus://lifecycle/recovery"}

Žádné parametry.

Vyzkoušejte

Port je ten, který server zalogoval; 8741 je ten, o který žádá jako první. Hodnoty jsou první příklad operace.

curl -s "http://127.0.0.1:8741/api/v1/lifecycle/recovery"

Testováno v apps/majordomus-cli/tests/http_serve.rs, který přehrává routu s těmito hodnotami proti serveru na skutečném socketu.

Odpovědi

statustělovýznam
200 Obnova Co úložiště potřebuje, aby někdo udělal: výsledek.
400 ErrorBody invalid_input: Vstup neodpovídá schématu: parametr špatného typu, neznámý parametr nebo hodnota, kterou schopnost odmítne.
404 ErrorBody not_found: Vstup pojmenovává něco, co repozitář nemá.
500 ErrorBody internal: Schopnost selhala z vlastního důvodu; zpráva ho pojmenovává.
výchozí ErrorBody Jakékoli jiné selhání, které transport hlásí: 405 method_not_allowed pro jinou metodu na této cestě, 400 invalid_input pro tělo, které není objektem JSON.
GET /api/v1/lifecycle/runtime lifecycle.runtime

Commit, o kterém tento proces odpovídá

Commit, na kterém byl postaven servírovaný index, proti commitu, na kterém repozitář je, čteno při tomto volání. Server, který zmrazil index při startu, odpovídá na každou otázku o commitu před hodinami tak, že odpověď nic neříká; toto je čtení, které to dokáže rozeznat.

  • query
  • behaviorally_verified
  • vyžaduje se měření výkonu
  • cache process (max_entries=2, ttl_seconds=2)
  • Nástroj MCP majordomus_lifecycle_runtime

Žádné parametry.

Vyzkoušejte

Port je ten, který server zalogoval; 8741 je ten, o který žádá jako první. Hodnoty jsou první příklad operace.

curl -s "http://127.0.0.1:8741/api/v1/lifecycle/runtime"

Testováno v apps/majordomus-cli/tests/http_serve.rs, který přehrává routu s těmito hodnotami proti serveru na skutečném socketu.

Odpovědi

statustělovýznam
200 RuntimeView Commit, o kterém tento proces vypovídá: výsledek.
400 ErrorBody invalid_input: Vstup neodpovídá schématu: parametr špatného typu, neznámý parametr nebo hodnota, kterou schopnost odmítne.
404 ErrorBody not_found: Vstup pojmenovává něco, co repozitář nemá.
500 ErrorBody internal: Schopnost selhala z vlastního důvodu; zpráva ho pojmenovává.
výchozí ErrorBody Jakékoli jiné selhání, které transport hlásí: 405 method_not_allowed pro jinou metodu na této cestě, 400 invalid_input pro tělo, které není objektem JSON.

Schémata

Komponenty, které používají jen operace tohoto tagu. Každý řádek je pole daného typu, jeho doc komentář je popis; typ vlastněný jinou stránkou na ni odkazuje.

Vyvážení

object
Co ledger říká o životním cyklu epizody oproti tomu, co drží úložiště.

Invariant, který ADR 0052 žádá, vyjádřený jako aritmetika, kterou si člověk může ověřit: každá epizoda, která začala, se buď uzavřela, nebo je stále otevřená. Když started přesáhne closed + open, rozdíl jsou epizody, jejichž uzavření nebylo nikdy zaznamenáno — tvar výpadku, který ADR popisuje, kdy události stále přicházely a záznamy, které měly vytvořit, ne.

Znaménko záleží a oba směry znamenají opak. Kladné unaccounted je výpadek: epizody, které začaly, nejsou otevřené a nevytvořily žádný uzavřený záznam — práce, která proběhla a nezanechala nic. Záporné je běžné účetnictví: ledger byl rotován, nebo byl záznam commitnut z checkoutu, jehož řádky ledgeru nejsou v tomto, takže záznamů je víc než zahajovacích řádků, které je vysvětlují.

use majordomus_cli::capability::builtin::lifecycle::Balance;
use serde_json::json;

let of = |started, closed, open, unaccounted, agrees| -> Balance {
    serde_json::from_value(json!({
        "started": started, "closed": closed, "open": open,
        "unaccounted": unaccounted, "agrees": agrees, "note": "as measured",
    })).unwrap()
};

// the good case: every episode that started either closed or is still open
let sound = of(40, 37, 3, 0, true);
assert_eq!(sound.started as i64 - sound.closed as i64 - sound.open as i64, sound.unaccounted);
assert!(sound.agrees);

// the outage ADR 0052 describes: four closes that were never recorded
let lost = of(40, 33, 3, 4, false);
assert!(lost.unaccounted > 0 && !lost.agrees);

// and a rotated ledger, which is not a fault in either half
let rotated = of(2, 37, 3, -38, false);
assert!(rotated.unaccounted < 0, "more records than lines to account for them");
vlastnosttyppovinnépopis
zahájeno integer (uint) ano session.started řádků v knize.
uzavřeno integer (uint) ano session.closed řádků.
open integer (uint) ano Otevřené záznamy v úložišti.
nevyúčtováno celé číslo (int64) ano started - closed - open. Nula je dobrý případ; záporná znamená, že kniha byla rotována pod záznamy, což je běžné.
shoduje se boolean ano Zda se ta tři shodují.
poznámka string ano Co čísla znamenají, slovy.

Používá Recovery

ClosedSession

object
Jedna uzavřená epizoda sledované, trvalé projekce — polovina tohoto podsystému, která přežije klon.

Nese jak path, tak uri, protože odpovídají na různé otázky: cesta je místo, kde je záznam v tomto checkoutu, a identifikátor majordomus://session/<identity> je to, čím tentýž záznam oslovuje každý jiný povrch, takže ho čtenář může následovat na stránku objektu, aniž by ji musel sestavovat.

Vše kromě identity, identifikátoru a cesty je volitelné, protože záznam zapsaný starším uzavřením nese méně polí a je pořád záznamem.

use majordomus_cli::capability::builtin::lifecycle::ClosedSession;
use serde_json::json;

let s: ClosedSession = serde_json::from_value(json!({
    "session_id": "s-0042",
    "uri": "majordomus://session/2026-09-12-s-0042",
    "path": ".ai/repo/project/sessions/2026-09-12-s-0042.md",
    "created_at": "2026-09-12T15:44:00Z",
    "branch": "master",
    "head": "cd04012f1",
    "outcome": "landed",
})).unwrap();

assert!(s.uri.starts_with("majordomus://session/"), "addressable as an object");
assert_eq!(s.outcome, "landed");

// an older record with less in it is still one, and says so by absence
let sparse: ClosedSession = serde_json::from_value(json!({
    "session_id": "s-0001",
    "uri": "majordomus://session/2026-08-01-s-0001",
    "path": ".ai/repo/project/sessions/2026-08-01-s-0001.md",
})).unwrap();
assert!(sparse.outcome.is_empty() && sparse.title.is_empty());
vlastnosttyppovinnépopis
session_id string ano Id epizody.
uri string ano majordomus://session/<identity>, pro stránku objektu.
path string ano Cesta relativní k repozitáři.
created_at string ne Kdy se uzavřelo, jak je zaznamenáno.
větev string ne Větev, na které se uzavřela.
head string ne Commit, na kterém byl uzavřen.
výsledek string ne Typovaný výsledek, který uzavření zaznamenalo.
název string ne Jeho název, pokud ho záznam nese.

Používá ClosedSessions

ClosedSessions

object
Sledovaná polovina subsystému: záznamy, které klon obdrží.

total je přesné a newest je okno a držet je jako dvě čísla je celý smysl tohoto typu. Sledovaný adresář roste bez omezení a karta Cockpitu není stránka výpisu, takže řádky jsou omezeny — ale omezený seznam, který by nenesl skutečný součet, by repozitář potichu podhodnotil, což je tvar vady, který tento repozitář pokousal natolik často, že si zaslouží jméno. window říká, kolik řádků se vrátilo, aby čtenář viděl strop, místo aby ho odhadoval.

use majordomus_cli::capability::builtin::lifecycle::ClosedSessions;
use serde_json::json;

let row = |id: &str| json!({
    "session_id": id,
    "uri": format!("majordomus://session/{id}"),
    "path": format!(".ai/repo/project/sessions/{id}.md"),
    "branch": "master",
});
let c: ClosedSessions = serde_json::from_value(json!({
    "total": 137,
    "on_this_branch": 41,
    "newest": [row("s-0137"), row("s-0136")],
    "window": 2,
})).unwrap();

// the window is what came back; the total is what exists
assert_eq!(c.window, c.newest.len());
assert!(c.total > c.window, "the rows are capped and the total is not");
assert!(c.on_this_branch <= c.total);
vlastnosttyppovinnépopis
celkem integer (uint) ano Kolik uzavřených záznamů index drží. Přesně.
on_this_branch integer (uint) ano Kolik z nich se uzavřelo na branch, na které tento checkout je.
nejnovější pole ClosedSession → ClosedSession ne Nejnovější CLOSED_WINDOW, nejnovější první.
okno integer (uint) ano Kolik jich nese newest, vůči celku.

Používá lifecycle.closed

Epizoda

object
Jedna otevřená epizoda, jak ji zaznamenává její soubor a jak ji viděl registr.

Vše zde je zaznamenáno, ne odvozeno, a téměř vše je volitelné, což je smysl: ručně otevřená epizoda nemá poskytovatele, worker, který nedodal identitu, žádnou nemá, a epizoda, která se otevřela a nic neudělala, nemá řádek v knize, a tedy ani last_activity. Každé z toho je běžné a nic není chyba, takže je každé neseno jako absence — prázdná pole se na drátě přeskakují — a čtenář musí jejich absenci zvládnout, ne očekávat zástupnou hodnotu.

Dvě pole, která jsou vždy přítomna, jsou standing a note: kde epizoda stojí a proč tam stojí, slovy. Stav, podle kterého čtenář nemůže jednat, je selhání, kvůli kterému byl tento subsystém opraven, takže note není nikdy prázdné.

use majordomus_cli::capability::builtin::lifecycle::{Episode, EpisodeStanding};
use serde_json::json;

// the smallest true record: an episode opened by hand that has written nothing yet
let e: Episode = serde_json::from_value(json!({
    "session_id": "s-0001",
    "path": ".ai/local/state/sessions-open/by-hand.yaml",
    "standing": "open",
    "events": 0,
    "note": "open in this worktree; the pointer names another episode",
})).unwrap();

assert_eq!(e.standing, EpisodeStanding::Open);
assert!(e.provider.is_empty(), "no provider session owns a hand-opened episode");
assert!(e.last_activity.is_empty(), "it has written no ledger line");
assert!(e.tasks.is_empty(), "work outside a task is permitted");
assert!(!e.note.is_empty(), "a standing always says why");

// and absence stays absence: the empty fields are not serialised back as ""
let wire = serde_json::to_value(&e).unwrap();
assert!(wire.get("provider").is_none());
assert_eq!(wire["events"], 0, "a count of zero is a measurement, so it is kept");
vlastnosttyppovinnépopis
session_id string ano Id epizody, kterým kniha označí každý řádek, který tento pracovník zapsal.
path string ano Cesta otevřeného záznamu relativní k repozitáři. Tělo je na té cestě.
provider string ne Poskytovatel, jehož událost ji otevřela, pokud nějaká. Prázdné pro epizodu otevřenou ručně, což je jediná epizoda, kterou žádná relace poskytovatele nevlastní.
provider_session string ne Identita relace tohoto poskytovatele: kmen souboru a řetězec, kterým archiv promptů označuje záznamy téhož workera.
vlastník string ne Kdo to otevřel.
worker string ne Identita workera, když byla dodána. Nikdy se neodvozuje.
worktree string ne Worktree, ve kterém se otevřela, absolutní, jak bylo zaznamenáno.
větev string ne Větev, na které se otevřela.
start_head string ne Commit, na kterém se otevřela.
started_at string ne Kdy se otevřela, jak bylo zaznamenáno.
postavení EpisodeStanding → EpisodeStanding ano Jak to stojí.
events integer (uint) ano Kolik řádků ledgeru nese id této epizody. Nula pro epizodu, která se otevřela a nic nedělala, což je běžné a není to porucha.
last_activity string ne Časové razítko nejnovějšího řádku knihy nesoucího id této epizody, jak bylo zaznamenáno. Prázdné, když nezapsala žádný.
last_event string ne Název události toho řádku.
úlohy array of string ne Úlohy, které jmenují řádky knihy této epizody, v pořadí, v jakém se jich poprvé dotkla.

Vztah, ne kontejner. Epizoda se může dotknout tří úloh nebo žádné a úloha může zahrnovat tři epizody; obojí se tu spojuje tím, co zaznamenal ledger, a ne tím, že by jedno vlastnilo druhé. Epizoda bez úlohy je běžná — práce mimo úlohu je povolena — a rozhodně to není důvod, aby se záznamy epizody nepsaly, což je předpoklad, který ADR 0052 odstraňuje.

poznámka string ano Proč stojí tam, kde stojí, slovy. Nikdy prázdné: postavení, se kterým čtenář nemůže nic udělat, je právě ta vada, kterou se tento subsystém napravuje.

Používá Episodes

EpisodeStanding

"current" | "open" | "foreign" | "stranded"
Kde otevřená epizoda stojí, pokud to repozitář dokáže pozorovat.

Stojí za to být přesný v tom, co tady není. Zda poskytovatel, který otevřel epizodu, je stále připojen ke své konverzaci, není fakt tohoto repozitáře: soubor epizody nezaznamenává pid, nástěnka peerů nezaznamenává id epizody a klient, který skončí, aniž by vyvolal svou koncovou událost, zanechá soubor shodný s tím, který používá živý pracovník. Hádání by dalo přesně tu sebevědomě špatnou odpověď, které se tento subsystém snaží vyhnout, takže slovník níže je pozorovatelný — kde soubor je, čí worktree jmenuje a zda ho ukazatel sleduje — a note vždy říká, který.

Čteno jako škála, čtyři běží od „práce tohoto checkoutu“ po „už ničí práce“ a jen poslední je závada: current a open jsou obě živé epizody tohoto worktree a liší se jen tím, na kterou ukazatel právě míří, foreign patří jinému worktree a hlásí se, nepřebírá, a stranded je ta, která potřebuje člověka, protože ji nic nemůže uzavřít.

use majordomus_cli::capability::builtin::lifecycle::EpisodeStanding;

// the standings that are somebody's live work, against the one that is nobody's
let live = [EpisodeStanding::Current, EpisodeStanding::Open, EpisodeStanding::Foreign];
assert!(live.iter().all(|s| *s != EpisodeStanding::Stranded));

// serialised as the word `as_str` gives, so the two renderings cannot drift apart
for s in [EpisodeStanding::Current, EpisodeStanding::Foreign, EpisodeStanding::Stranded] {
    assert_eq!(serde_json::to_value(s).unwrap(), serde_json::json!(s.as_str()));
}
  • currentUkazatel tohoto checkoutu se na něj vyřeší. continuity.state se týká tohoto.
  • openOtevřená a jmenuje tento worktree, ale ukazatel jmenuje jinou epizodu. Dvě okna jednoho poskytovatele na jednom worktree jsou dva workeři a ukazuje se jen na jednoho.
  • foreignJmenuje jiný worktree tohoto repozitáře. Hlásí se, nikdy se nepovažuje za checkout tohoto — záznam o cizí práci, který se tiše stane vaším, je přesně selhání, kterému má dvouúrovňové pravidlo rozlišení předejít.
  • strandedWorktree, který jmenuje, z disku zmizel. Nic ji nemůže uzavřít: cesta uzavření skládá záznam z gitu ve worktree, ve kterém se epizoda otevřela.

Používá Episode

Epizody

object
Každá otevřená epizoda úložiště tohoto checkoutu — odpověď, kterou continuity.state dát nemůže, protože čte ukazatel, kdežto toto čte úložiště.

current je ta jedna epizoda, na kterou ukazatel míří, a je jedním řádkem episodes, ne samostatnou věcí: celá oprava, kterou tento typ nese, je, že epizoda, na kterou se ukazuje, je jedním členem množiny, ne množinou. V den, kdy to bylo napsáno, úložiště drželo pět otevřených epizod v jednom checkoutu a každý povrch mohl pojmenovat jen jednu z nich.

present: false je čerstvý klon, což není závada — nikdo zde ještě neotevřel epizodu — a hlásí se jako takový, ne jako chyba.

use majordomus_cli::capability::builtin::lifecycle::Episodes;
use serde_json::json;

// a clone in which no episode has ever been opened: absence, answered as absence
let fresh: Episodes = serde_json::from_value(json!({
    "present": false,
    "store": ".ai/local/state/sessions-open",
    "worktree": "example/a-fresh-clone",
})).unwrap();
assert!(!fresh.present);
assert!(fresh.episodes.is_empty() && fresh.current.is_empty());
assert!(fresh.findings.is_empty(), "nothing to warn about; it is simply new");

// and a store with work in it: `current` names one of the rows, never a sixth thing
let busy: Episodes = serde_json::from_value(json!({
    "present": true,
    "store": ".ai/local/state/sessions-open",
    "worktree": "example/wt",
    "current": "s-0002",
    "episodes": [
        {"session_id": "s-0001", "path": "a.yaml", "standing": "open",
         "events": 3, "note": "this worktree, not pointed at"},
        {"session_id": "s-0002", "path": "b.yaml", "standing": "current",
         "events": 9, "note": "the pointer resolves to it"},
    ],
})).unwrap();
assert_eq!(busy.episodes.len(), 2, "the pointer is blind to the first of these");
assert!(busy.episodes.iter().any(|e| e.session_id == busy.current));
vlastnosttyppovinnépopis
přítomno boolean ano Zda existuje úložiště otevřených epizod. False v čerstvém klonu, což není chyba.
úložiště string ano Úložiště, relativně k repozitáři.
worktree string ano Tento worktree, absolutně. Každé postavení výše se rozhoduje vůči němu.
aktuální string ne Id epizody, na které se ukazatel vyřeší, když se na nějakou vyřeší.
epizody pole Episode → Episode ne Každá otevřená epizoda, nejnovější první podle zaznamenaného začátku.
zjištění array of string ne Co by měl čtenář vědět, než uvěří výše uvedenému: soubor, který se nenaparsuje, ukazatel, který se nevyřeší na nic. Prázdné je dobrý případ.

Používá lifecycle.episodes

Osiřelé

object
Dočasný soubor ponechaný v adresáři záznamů uzavřením, které neskončilo.

Cesta uzavření zapisuje trvalý záznam do .tmp.XXXX v cílovém adresáři a přejmenovává jej, takže žádný čtenář nikdy nevidí poloviční záznam. Proces zabitý mezi tím zanechá dočasný soubor, kde je nesledovaný, neviditelný pro každého čtenáře sekce a — protože adresář je sledován — nabídnutý příštímu git add ..

bytes se nese, protože je to jediná věc, která rozhoduje, co se souborem dělat: dočasný soubor nulové délky je uzavření, které zemřelo dřív, než cokoli zapsalo, a lze jej prostě odstranit, zatímco velký je záznam, který byl zapsán a nikdy přejmenován, a před smazáním stojí za přečtení.

use majordomus_cli::capability::builtin::lifecycle::Orphan;
use majordomus_cli::order::canonical;

let mut orphans = vec![
    Orphan { path: ".ai/repo/project/sessions/.tmp.b2".into(), bytes: 4_812 },
    Orphan { path: ".ai/repo/project/sessions/.tmp.a1".into(), bytes: 0 },
];
canonical(&mut orphans);

// filed under the path, which names one file and so makes the list total
let paths: Vec<&str> = orphans.iter().map(|o| o.path.as_str()).collect();
assert_eq!(paths, [
    ".ai/repo/project/sessions/.tmp.a1",
    ".ai/repo/project/sessions/.tmp.b2",
]);

// and the size is what tells a dead stub from a record that was written
assert_eq!(orphans[0].bytes, 0, "nothing was written before the close died");
assert!(orphans[1].bytes > 0, "read this one before removing it");
vlastnosttyppovinnépopis
path string ano Cesta relativní k repozitáři.
bajty integer (uint64) ano Jeho velikost v bajtech, podle které čtenář pozná prázdný stub od ztraceného záznamu.

Používá Recovery

Ukazatel

object
Čím ukazatel je a zda někam vede.

layout a resolves jsou nezávislé a zajímavý je případ dvojice Pointer + resolves: false: symlink, který přežil epizodu, již pojmenovával, což je to, co po sobě zanechá zabitý close nebo ručně odstraněný záznam. Rozvržení je správné, cíl je pryč a continuity.state nemá co hlásit, přestože úložiště může stále obsahovat otevřené epizody — jde tedy o visící ukazatel, ne o prázdné úložiště.

use majordomus_cli::capability::builtin::lifecycle::{Pointer, PointerLayout};
use serde_json::json;

// the healthy case: the current layout, and it leads to an episode
let live: Pointer = serde_json::from_value(json!({
    "layout": "pointer",
    "target": "sessions-open/claude-abc.yaml",
    "resolves": true,
    "session_id": "s-0002",
    "note": "the pointer resolves to an open episode",
})).unwrap();
assert_eq!(live.layout, PointerLayout::Pointer);
assert!(live.resolves && !live.session_id.is_empty());

// and the dangling one: the right layout, aimed at a record that is no longer there
let dangling: Pointer = serde_json::from_value(json!({
    "layout": "pointer",
    "target": "sessions-open/claude-gone.yaml",
    "resolves": false,
    "note": "the symlink outlived the episode it named",
})).unwrap();
assert_eq!(dangling.layout, PointerLayout::Pointer, "the layout is not the fault");
assert!(!dangling.resolves);
assert!(dangling.session_id.is_empty(), "there is no episode to name");
vlastnosttyppovinnépopis
rozvržení PointerLayout → PointerLayout ano Rozvržení.
cíl string ne Co symlink jmenuje, tak jak je napsáno, když jím je.
rozlišuje boolean ano Zda čtení přes něj vydá epizodu.
session_id string ne Epizoda, na kterou se rozlišuje.
poznámka string ano Co to znamená, slovy.

Používá Recovery

PointerLayout

"pointer" | "inline" | "absent"
Rozvržení ukazatele, podle něhož je vidět, jak daleko tu migrace po poskytovatelích postoupila.

Dokud byla epizoda klíčována relací poskytovatele, session-current.yaml byl otevřeným záznamem: jeden běžný soubor, jedna epizoda na checkout. Sedm souběžných relací dne 2026-09-09 vytvořilo v tomto rozvržení mezi sebou jeden záznam. Běžný soubor zde dnes je checkout, který migraci nespustil, a jeho epizody se přepisují.

Nálezem je jen jedna ze tří, a to prostřední. Pointer je současné rozvržení a Absent znamená, že tu není otevřena žádná epizoda — čerstvý klon nebo pracovník mezi epizodami — zatímco Inline je checkout stále na rozvržení před migrací, kde druhá souběžná relace tiše přepíše záznam první. Takže otázka, kterou čtenář této hodnotě klade, není „které rozvržení“, ale „je to Inline“.

use majordomus_cli::capability::builtin::lifecycle::PointerLayout;

// the two that are fine, and the one that loses records
let loses_records = |l: PointerLayout| l == PointerLayout::Inline;
assert!(!loses_records(PointerLayout::Pointer));
assert!(!loses_records(PointerLayout::Absent), "no episode open is not a fault");
assert!(loses_records(PointerLayout::Inline));

// serialised as the word `as_str` gives
assert_eq!(
    serde_json::to_value(PointerLayout::Absent).unwrap(),
    serde_json::json!(PointerLayout::Absent.as_str()),
);
  • pointerSymlink do sessions-open/: rozložení podle poskytovatele.
  • inlineBěžný soubor: rozvržení před migrací, jedna epizoda na checkout.
  • absentNic tu není. Není otevřena žádná epizoda, což není chyba.

Používá Pointer

ProviderLifecycle

object
Lifecycle povrch jednoho poskytovatele: co jeho adaptér deklaruje, že umí, a co je k němu v tomto repozitáři zapojeno.

Obě poloviny pocházejí z různých míst a nesmějí se zaměňovat. lifecycle a prompt_capture jsou deklarace distribuce — co dodávaný adaptér pro tohoto poskytovatele vůbec umí — zatímco wired je politika tohoto repozitáře, která jmenuje háček onoho poskytovatele v wired_by. Kterákoli může být prázdná, zatímco druhá je plná, a právě tato dvojice je zajímavým nálezem: poskytovatel, kterého tento repozitář zapojuje, ale pro kterého nástroj nedodává žádný životní cyklus, je háček, který se nikdy nespustí.

Ani jedna polovina se nevyvozuje ze souboru na disku. Zda je shim skutečně nainstalován, je otázka pro majordomus capture status a tento typ na ni záměrně neodpovídá.

use majordomus_cli::capability::builtin::lifecycle::ProviderLifecycle;
use serde_json::json;

// a provider the tool adapts and this repository wires: both halves present
let full: ProviderLifecycle = serde_json::from_value(json!({
    "id": "claude",
    "title": "Claude Code",
    "lifecycle": ["SessionStart", "SessionEnd", "PreCompact"],
    "prompt_capture": true,
    "client_config": ".mcp.json",
    "wired": ["session.start", "session.close"],
    "note": "adapted and wired",
})).unwrap();
assert!(!full.lifecycle.is_empty() && !full.wired.is_empty());
assert!(full.prompt_capture);

// and one the tool ships no adapter for: it loses the automation and none of the model,
// because every command remains the same
let unadapted: ProviderLifecycle = serde_json::from_value(json!({
    "id": "some-editor",
    "title": "Some Editor",
    "prompt_capture": false,
    "note": "no lifecycle adapter ships for it",
})).unwrap();
assert!(unadapted.lifecycle.is_empty(), "nothing will fire for it");
assert!(unadapted.client_config.is_empty(), "and it reads no project-scoped config");
vlastnosttyppovinnépopis
id string ano Id poskytovatele: název souboru (bez přípony) bootstrap šablony.
název string ano Jméno, pod kterým to člověk zná.
životní cyklus array of string ne Události životního cyklu, které jeho adaptér deklaruje, ve vlastním slovníku providera (SessionStart, SessionEnd, PreCompact). Prázdné znamená, že nástroj pro něj nedodává žádný adaptér životního cyklu: takový provider ztrácí automatizaci, ale nic z modelu, protože každý příkaz zůstává stejný.
prompt_capture boolean ano Zda jeho adaptér umí archivovat prompty workeru.
client_config string ne Soubor, ze kterého čte konfiguraci klienta MCP v rozsahu projektu, pokud ji má.
zapojeno array of string ne Položky vynucování politiky tohoto repozitáře, které pojmenovávají hook tohoto poskytovatele v wired_by. Deklarované zapojení, nikoli soubor na disku: zda je shim nainstalován, je otázka pro majordomus capture status, což je příkaz, který ho vlastní.
poznámka string ano Co znamenají obě dohromady.

Používá ProviderLifecycles

ProviderLifecycles

object
Každý provider, pro nějž distribuce dodává bootstrap, s polovinou životního cyklu každého z nich.

with_lifecycle je počet nad providers, a ne druhý seznam, takže se oba nemohou rozejít; je tu proto, že „kolik z providerů, které podporujeme, skutečně dostane automatizaci“ je otázka, na kterou tato schopnost existuje, a čtenář by neměl muset skládat seznam, aby to zjistil.

use majordomus_cli::capability::builtin::lifecycle::ProviderLifecycles;
use serde_json::json;

let p: ProviderLifecycles = serde_json::from_value(json!({
    "providers": [
        {"id": "claude", "title": "Claude Code", "lifecycle": ["SessionStart"],
         "prompt_capture": true, "note": "adapted"},
        {"id": "plain", "title": "Plain", "prompt_capture": false, "note": "not adapted"},
    ],
    "with_lifecycle": 1,
})).unwrap();

// the count is over the list, so it is checkable against it
let counted = p.providers.iter().filter(|x| !x.lifecycle.is_empty()).count();
assert_eq!(p.with_lifecycle, counted);
assert_eq!(p.providers.len(), 2, "a provider with no adapter is still reported");
assert!(p.findings.is_empty());
vlastnosttyppovinnépopis
providers pole ProviderLifecycle → ProviderLifecycle ano Poskytovatelé v pořadí deklarace.
with_lifecycle integer (uint) ano Kolik z nich deklaruje adaptér životního cyklu.
zjištění array of string ne Co by čtenář měl vědět: poskytovatel, kterého tento repozitář zapojuje, ale distribuce pro něj nedeklaruje žádný životní cyklus, a podobně.

Používá lifecycle.providers

Obnova

object
Co úložiště relací tohoto checkoutu potřebuje, aby někdo udělal.

findings je celý report řečený jednou a prázdné findings je verdikt: není co obnovovat. To je pole, podle kterého se větví — tři podrobné seznamy a aritmetika jsou to, co čtenář konzultuje poté, co se rozhodne, že je na co se dívat, a každý z nich může být prázdný z dobrého důvodu, zatímco jiný není.

use majordomus_cli::capability::builtin::lifecycle::Recovery;
use serde_json::json;

// a healthy checkout: the current layout, nothing stranded, the arithmetic agreeing
let clean: Recovery = serde_json::from_value(json!({
    "present": true,
    "pointer": {"layout": "pointer", "target": "sessions-open/c.yaml",
                "resolves": true, "session_id": "s-1", "note": "resolves"},
    "balance": {"started": 8, "closed": 7, "open": 1,
                "unaccounted": 0, "agrees": true, "note": "they agree"},
})).unwrap();

assert!(clean.findings.is_empty(), "an empty findings list is the good verdict");
assert!(clean.stranded.is_empty() && clean.orphans.is_empty());
assert!(clean.balance.agrees);

// a fresh clone has no local half at all, which is also nothing to recover
let fresh: Recovery = serde_json::from_value(json!({
    "present": false,
    "pointer": {"layout": "absent", "resolves": false, "note": "no episode is open here"},
    "balance": {"started": 0, "closed": 0, "open": 0,
                "unaccounted": 0, "agrees": true, "note": "nothing has happened yet"},
})).unwrap();
assert!(!fresh.present && fresh.findings.is_empty());
vlastnosttyppovinnépopis
přítomno boolean ano Zda lokální polovina vůbec existuje.
ukazatel Pointer → Pointer ano Ukazatel a jeho rozvržení.
uvízlé pole Stranded → Stranded ne Epizody, které se už nemohou uzavřít samy.
osiřelé pole Orphan → Orphan ne Dočasné soubory zbylé ve sledované sekci relací.
bilance Zůstatek → Zůstatek ano Aritmetika zahájených/uzavřených/otevřených.
zjištění array of string ne Vše výše, řečené jednou, pro čtenáře, který chce souhrn. Prázdné je dobrý případ a znamená, že není co obnovovat.

Používá lifecycle.recovery

RuntimeView

object
Commit, o kterém tento proces odpovídá, vůči commitu, na kterém repozitář je.

Obě poloviny se zde čtou a žádná se nepředpokládá. served je to, z čeho byl sestaven index, který tento proces drží; repository je to, co git říká nyní, při tomto volání. Server, který následuje repozitář, hlásí obojí shodně a za důkaz platí jedno vyvolání git; server, který zmrazil svůj index při startu, hlásí rozdílné, což je jediný způsob, jak to čtenář mohl zjistit.

agree je pole s provozním významem a to, co říká, není „repozitář je v pořádku“, ale „odpovědi, které čtete, se týkají stromu, na který se díváte“. Když je false, každá další schopnost, kterou tento proces obsluhuje, odpovídá o commitu, který už není vyčekovaný, a nic jiného v API by vám to neřeklo.

use majordomus_cli::capability::builtin::lifecycle::RuntimeView;
use serde_json::json;

let view = |served: &str, repo: &str, agree: bool| -> RuntimeView {
    serde_json::from_value(json!({
        "root": "example/repo",
        "served_head": served, "served_branch": "master", "served_working_tree": "clean",
        "repository_head": repo, "repository_branch": "master",
        "repository_working_tree": "clean",
        "agree": agree, "objects": 1319, "index_state": "ok",
        "note": "as measured on this call",
    })).unwrap()
};

// a server following the repository: one `git` invocation buys the proof
let current = view("a1b2c3d", "a1b2c3d", true);
assert!(current.agree);
assert_eq!(current.served_head, current.repository_head);
assert_eq!(current.index_state, "ok");

// and one that froze its index at start-up: every answer it serves is about the old commit
let frozen = view("a1b2c3d", "9f8e7d6", false);
assert!(!frozen.agree);
assert_ne!(frozen.served_head, frozen.repository_head);
vlastnosttyppovinnépopis
root string ano Kořen repozitáře, který tento proces obsluhuje.
served_head string ne Commit, na kterém byl servírovaný index sestaven.
served_branch string ne Její větev, nebo DETACHED.
served_working_tree string ne Zda byl pracovní strom čistý, když se index budoval.
repository_head string ne Commit, na kterém repozitář je, čtený při tomto volání.
repository_branch string ne Její větev nyní.
repository_working_tree string ne Pracovní strom nyní.
souhlasí boolean ano Zda se obě shodují na commitu a větvi.
objekty integer (uint) ano Kolik objektů obsahuje obsluhovaný index a zda byl degradován.
index_state string ano ok nebo degraded.
poznámka string ano Co porovnání znamená a co dělat, když nesouhlasí.

Používá lifecycle.runtime

Uvízlé

object
Otevřená epizoda, která se nemůže sama uzavřít, a co s tím.

Náprava je zde mechanická — odstranit soubor — protože je správná v každém checkoutu a nepotřebuje nic, co ještě není nainstalováno. lib/recover.sh na feature/session-store-recovery jsou tytéž tři degradace viděné ze strany zápisu: klasifikuje každého kandidáta čtením, tiskne důkazy před akcí a uvízlou epizodu uzavře do skutečného záznamu místo mazání. Až to přistane, náprava, kterou nález nese, by se měla stát majordomus recover episodes a obě detekce by se měly stát jednou — čtenář a zapisovatel, kteří si každý sám rozhodují, co znamená „uvízlý“, jsou dvě definice, a tento repozitář tento tvar pokousal dost často na to, aby jej pojmenoval.

use majordomus_cli::capability::builtin::lifecycle::Stranded;
use serde_json::json;

let s: Stranded = serde_json::from_value(json!({
    "session_id": "s-0007",
    "path": ".ai/local/state/sessions-open/claude-7.yaml",
    "reason": "the worktree it opened in is gone from disk",
    "remedy": "rm .ai/local/state/sessions-open/claude-7.yaml",
})).unwrap();

// the reason is about the world and the remedy is a command: a finding with no
// remedy is a complaint, so both are required fields rather than optional ones
assert!(!s.reason.is_empty() && !s.remedy.is_empty());
assert!(s.remedy.contains(&s.path), "the remedy acts on the file it names");
vlastnosttyppovinnépopis
session_id string ano Id epizody.
path string ano Otevřený záznam, relativně k repozitáři.
důvod string ano Proč uvízla.
náprava string ano Příkaz, který to odstraní. Nález bez nápravy je stížnost.

Používá Recovery

Prohledává názvy a popisy všech stránek, které tento web publikuje.