Skip to main content

Module lifecycle

Module lifecycle 

Source
Expand description

The lifecycle module: the episode lifecycle as something a person can watch.

The module is named for the lifecycle rather than for the session because session is already a kind of the layer — the closed records under the sessions section — and the registry composes one module per declarative kind. A capability module that took the same namespace would be composed twice and the registry refuses to build, which is the right answer: two different things called session in one namespace is exactly the ambiguity that refusal exists to catch.

super::continuity answers one question well — what would a worker resuming in this checkout be handed? — and it answers it about one episode, the one state/session-current.yaml points at. That is the right answer for a briefing and the wrong answer for an operator, because the pointer is a symlink that the most recent session start re-aims: on 2026-09-11 this repository held five open episodes in one checkout and continuity.state could name exactly one of them. Four workers were invisible to every surface the tool has, and the episode that never closes is the one nobody can see.

So this module reads the store rather than the pointer, and it reports the things a subsystem needs somebody to look at:

  • lifecycle.episodes — every open episode in the local store, which of them this checkout points at, which belong to another worktree, and what the ledger last saw each do.
  • lifecycle.recovery — the episodes that can no longer close themselves, the temporary files a killed close left in the tracked directory, whether the pointer has been migrated to the per-provider layout, and the arithmetic ADR 0052 asks for: how many session.started events the ledger holds against how many session.closed.
  • lifecycle.runtime — the commit this process is serving answers about, against the commit the repository is on right now. A long-lived server that built its index once and held it served a six-hour-old HEAD from the API, from MCP and from this Cockpit with nothing saying the picture was old (fix/server-sees-the-current-tree). Whether that is still possible is a fact this page should state rather than a fact a reader should assume.
  • lifecycle.providers — which lifecycle events each provider’s adapter declares, whether it can capture prompts, and which of this repository’s enforcement entries are actually wired to its hook. Declared, never inferred from the presence of a file.
  • lifecycle.closed — the tracked durable projection under the layer’s sessions section: the only half of this subsystem that survives a clone.

Read, never written. Like continuity, this module is one of the lifecycle’s readers; the writer is the shell tool. A second writer would be a second account of events the ledger already holds, which is what that design refuses.

Served, never published. Everything under .ai/local/ names this machine, so none of it is projected into docs/generated/ or site/ — the claim local-state-ignored owns that, over the whole local half (ADR 0014). The half that belongs to these five is that none of them declares a command line, because a command line is how a value reaches a script, a log and eventually a commit; test/cases/171 asserts exactly that.

No thresholds and no clock. Nothing here decides that a record is old. Age is session.freshness in the policy and it is super::continuity’s to judge; a second engine for it in this file would be the second source of truth ADR 0052 names. What this module reports is what it can observe without an opinion: a timestamp as recorded, a count, a path that exists or does not.

§The shape of the module, and the constraint it is under

Two of the paragraphs above are properties of the declaration rather than intentions, so they can be read off it. Every one of the five capabilities is served — over HTTP and over MCP — and none of them declares a command line, which is what “served, never published” amounts to mechanically: a command line is how a value reaches a script, a log and eventually a commit, and nothing under .ai/local/ may take that path.

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

let m = lifecycle::module();
assert_eq!(m.id.as_str(), "lifecycle");

// declaration order, which is the order the five are read in
let ids: Vec<&str> = m.capabilities.iter().map(|e| e.capability.id.as_str()).collect();
assert_eq!(ids, [
    "lifecycle.episodes",
    "lifecycle.recovery",
    "lifecycle.runtime",
    "lifecycle.providers",
    "lifecycle.closed",
]);

// served everywhere a worker in front of this checkout can reach, and nowhere a
// script can: the local half of the layer never becomes a committed value
for e in &m.capabilities {
    let id = e.capability.id.as_str();
    assert!(e.capability.exposure.http.is_some(), "{id} is not served over HTTP");
    assert!(e.capability.exposure.mcp.is_some(), "{id} is not served over MCP");
    assert!(e.capability.exposure.cli.is_none(), "{id} declares a command line");
}

Structs§

Balance
What the ledger says about episode lifecycle against what the store holds.
ClosedSession
One closed episode of the tracked, durable projection — the half of this subsystem that survives a clone.
ClosedSessions
The tracked half of the subsystem: the records a clone receives.
Episode
One open episode, as its file records it and as the ledger has seen it.
Episodes
Every open episode of this checkout’s store — the answer continuity.state cannot give, because it reads the pointer and this reads the store.
Orphan
A temporary file left in a record directory by a close that did not finish.
Pointer
What the pointer is and whether it leads anywhere.
ProviderLifecycle
One provider’s lifecycle surface: what its adapter declares it can do, and what this repository has wired to it.
ProviderLifecycles
Every provider the distribution ships a bootstrap for, with the lifecycle half of each.
Recovery
What this checkout’s session store needs somebody to do.
RuntimeView
The commit this process answers about, against the commit the repository is on.
Stranded
An open episode that cannot close itself, and what to do about it.

Enums§

EpisodeStanding
Where an open episode stands, as far as the repository can observe it.
PointerLayout
The layout of the pointer, which is how far the per-provider migration has got here.

Constants§

EPISODES_URI
The URI under which lifecycle.episodes is read as an MCP resource.
RECOVERY_URI
The URI under which lifecycle.recovery is read as an MCP resource.

Functions§

module
The module’s one declaration: the five capabilities, their schemas and every projection of them.