Skip to content

lifecycle.closed

The tracked, durable projection of closed episodes: how many there are, how many closed on this branch, and the newest twenty. This is the only half of the subsystem that survives a clone; everything under .ai/local/ names this machine and travels nowhere.

The tracked, durable projection of closed episodes: how many there are, how many closed on this branch, and the newest twenty. This is the only half of the subsystem that survives a clone; everything under .ai/local/ names this machine and travels nowhere.

Exposure

Where this one definition is reachable. Absence is explicit: a surface not listed does not carry it.

surfaceas
MCPtool majordomus_lifecycle_closed
HTTPGET /api/v1/lifecycle/closed operationId lifecycle.closed
command line—

Schemas

The canonical input and output, from the Rust types; the MCP tool schema and the OpenAPI parameters and responses are derived from these.

input · Empty

No input.

No properties: the capability takes no input.

JSON Schema
{
  "additionalProperties": false,
  "description": "No input.",
  "title": "Empty",
  "type": "object"
}

output · ClosedSessions

The tracked half of the subsystem: the records a clone receives.

total is exact and newest is a window, and keeping them as two numbers is the whole design of this type. The tracked directory grows without bound and a Cockpit card is not a listing page, so the rows are capped — but a capped list that did not carry the true total would quietly understate the repository, which is the shape of defect this repository has been bitten by often enough to name. window says how many rows came back, so a reader can see the cap rather than infer it.

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);
propertytyperequireddescription
newest array no The newest CLOSED_WINDOW, newest first.
on_this_branch integer yes How many of them closed on the branch this checkout is on.
total integer yes How many closed records the index holds. Exact.
window integer yes How many are carried in newest, against the total.
JSON Schema
{
  "$defs": {
    "ClosedSession": {
      "description": "One closed episode of the tracked, durable projection — the half of this subsystem that\nsurvives a clone.\n\nIt carries both a `path` and a `uri` because they answer different questions: the path\nis where the record is in this checkout, and the `majordomus://session/<identity>`\nidentifier is what every other surface addresses the same record by, so a reader can\nfollow it to the object page without constructing one.\n\nEverything but the identity, the identifier and the path is optional, because a record\nwritten by an older close carries fewer fields and is still a record.\n\n```\nuse majordomus_cli::capability::builtin::lifecycle::ClosedSession;\nuse serde_json::json;\n\nlet s: ClosedSession = serde_json::from_value(json!({\n    \"session_id\": \"s-0042\",\n    \"uri\": \"majordomus://session/2026-09-12-s-0042\",\n    \"path\": \".ai/repo/project/sessions/2026-09-12-s-0042.md\",\n    \"created_at\": \"2026-09-12T15:44:00Z\",\n    \"branch\": \"master\",\n    \"head\": \"cd04012f1\",\n    \"outcome\": \"landed\",\n})).unwrap();\n\nassert!(s.uri.starts_with(\"majordomus://session/\"), \"addressable as an object\");\nassert_eq!(s.outcome, \"landed\");\n\n// an older record with less in it is still one, and says so by absence\nlet sparse: ClosedSession = serde_json::from_value(json!({\n    \"session_id\": \"s-0001\",\n    \"uri\": \"majordomus://session/2026-08-01-s-0001\",\n    \"path\": \".ai/repo/project/sessions/2026-08-01-s-0001.md\",\n})).unwrap();\nassert!(sparse.outcome.is_empty() && sparse.title.is_empty());\n```",
      "properties": {
        "branch": {
          "description": "The branch it closed on.",
          "type": "string"
        },
        "created_at": {
          "description": "When it closed, as recorded.",
          "type": "string"
        },
        "head": {
          "description": "The commit it closed at.",
          "type": "string"
        },
        "outcome": {
          "description": "The typed outcome the close recorded.",
          "type": "string"
        },
        "path": {
          "description": "Repository-relative path.",
          "type": "string"
        },
        "session_id": {
          "description": "The episode id.",
          "type": "string"
        },
        "title": {
          "description": "Its title, when the record carries one.",
          "type": "string"
        },
        "uri": {
          "description": "`majordomus://session/<identity>`, for the object page.",
          "type": "string"
        }
      },
      "required": [
        "session_id",
        "uri",
        "path"
      ],
      "type": "object"
    }
  },
  "description": "The tracked half of the subsystem: the records a clone receives.\n\n`total` is exact and `newest` is a window, and keeping them as two numbers is the whole\ndesign of this type. The tracked directory grows without bound and a Cockpit card is not\na listing page, so the rows are capped — but a capped list that did not carry the true\ntotal would quietly understate the repository, which is the shape of defect this\nrepository has been bitten by often enough to name. `window` says how many rows came\nback, so a reader can see the cap rather than infer it.\n\n```\nuse majordomus_cli::capability::builtin::lifecycle::ClosedSessions;\nuse serde_json::json;\n\nlet row = |id: &str| json!({\n    \"session_id\": id,\n    \"uri\": format!(\"majordomus://session/{id}\"),\n    \"path\": format!(\".ai/repo/project/sessions/{id}.md\"),\n    \"branch\": \"master\",\n});\nlet c: ClosedSessions = serde_json::from_value(json!({\n    \"total\": 137,\n    \"on_this_branch\": 41,\n    \"newest\": [row(\"s-0137\"), row(\"s-0136\")],\n    \"window\": 2,\n})).unwrap();\n\n// the window is what came back; the total is what exists\nassert_eq!(c.window, c.newest.len());\nassert!(c.total > c.window, \"the rows are capped and the total is not\");\nassert!(c.on_this_branch <= c.total);\n```",
  "properties": {
    "newest": {
      "description": "The newest `CLOSED_WINDOW`, newest first.",
      "items": {
        "$ref": "#/$defs/ClosedSession"
      },
      "type": "array"
    },
    "on_this_branch": {
      "description": "How many of them closed on the branch this checkout is on.",
      "format": "uint",
      "minimum": 0,
      "type": "integer"
    },
    "total": {
      "description": "How many closed records the index holds. Exact.",
      "format": "uint",
      "minimum": 0,
      "type": "integer"
    },
    "window": {
      "description": "How many are carried in `newest`, against the total.",
      "format": "uint",
      "minimum": 0,
      "type": "integer"
    }
  },
  "required": [
    "total",
    "on_this_branch",
    "window"
  ],
  "title": "ClosedSessions",
  "type": "object"
}

Policies

benchmark
required — a target on every transport the exposure declares; the cases are the input type's
cache
process — up to 2 entries in the process, 30s each, scoped by the registry fingerprint

Benchmark targets

Derived from the registry for this repository: one requirement per transport, and the cases the input type provides. The whole matrix is on the benchmarks page.

transportstatecasestargets
directcovered1default cold+warm
mcpcovered1default cold+warm
httpcovered1default cold+warm

Claims implemented beside it

Attached by the file that implements each claim; every one links to its page with the implementation and the test that proves it.

Searches the titles and descriptions of every page this site publishes.