A snapshot: read at commit 707a04c7ce8 on 2026-10-18T00:06:01Z by majordomus 0.29.0. Nothing on this page is read again. See it live
lifecycle.episodes
Every open episode
What it is
Every open episode in this checkout's store, with the provider session that owns it, the worktree and branch it opened on, where it stands, and what the ledger last saw it do. `continuity.state` reports the one episode the pointer resolves to, which is right for a briefing and blind to every other window open on the same worktree.
Descriptor
- Canonical id
lifecycle.episodes- Module
lifecycle- Kind
- query
- Stability
- behaviorallyverified
- Provenance
- declaredcached
- Source
apps/majordomus-cli/src/capability/builtin/lifecycle.rs- Cache
process, 2 entries, 2s- Benchmark
required- Tags
- continuitysessionepisode
Run
queryA query. It reads and changes nothing.
Projections
| Interface | As | Where |
|---|---|---|
| MCP tool | majordomus_lifecycle_episodes | - |
| MCP resource | majordomus://lifecycle/episodes | - |
| HTTP | GET /api/v1/lifecycle/episodes | Swagger |
| Command line | not exposed | - |
Schemas
Input · Empty
{
"type": "object",
"additionalProperties": false,
"description": "No input.",
"title": "Empty"
}Output · Episodes
{
"type": "object",
"properties": {
"present": {
"type": "boolean",
"description": "Whether the open-episode store exists. False in a fresh clone, which is not a fault."
},
"store": {
"type": "string",
"description": "The store, repository-relative."
},
"worktree": {
"type": "string",
"description": "This worktree, absolute. Every standing above is decided against it."
},
"current": {
"type": "string",
"description": "The episode id the pointer resolves to, when it resolves to one."
},
"episodes": {
"type": "array",
"items": {
"$ref": "#/$defs/Episode"
},
"description": "Every open episode, newest first by recorded start."
},
"findings": {
"type": "array",
"items": {
"type": "string"
},
"description": "What a reader should know before trusting the above: a file that does not parse, a\npointer that resolves to nothing. Empty is the good case."
}
},
"required": [
"present",
"store",
"worktree"
],
"description": "Every open episode of this checkout's store — the answer `continuity.state` cannot give,\nbecause it reads the pointer and this reads the store.\n\n`current` is the one episode the pointer resolves to, and it is one row of `episodes`\nrather than a separate thing: the whole correction this type carries is that the pointed-at\nepisode is *a* member of the set and not the set. On the day this was written the store\nheld five open episodes in one checkout and every surface could name one of them.\n\n`present: false` is a fresh clone, which is not a fault — nobody has opened an episode\nhere yet — and is reported as itself rather than as an error.\n\n```\nuse majordomus_cli::capability::builtin::lifecycle::Episodes;\nuse serde_json::json;\n\n// a clone in which no episode has ever been opened: absence, answered as absence\nlet fresh: Episodes = serde_json::from_value(json!({\n \"present\": false,\n \"store\": \".ai/local/state/sessions-open\",\n \"worktree\": \"example/a-fresh-clone\",\n})).unwrap();\nassert!(!fresh.present);\nassert!(fresh.episodes.is_empty() && fresh.current.is_empty());\nassert!(fresh.findings.is_empty(), \"nothing to warn about; it is simply new\");\n\n// and a store with work in it: `current` names one of the rows, never a sixth thing\nlet busy: Episodes = serde_json::from_value(json!({\n \"present\": true,\n \"store\": \".ai/local/state/sessions-open\",\n \"worktree\": \"example/wt\",\n \"current\": \"s-0002\",\n \"episodes\": [\n {\"session_id\": \"s-0001\", \"path\": \"a.yaml\", \"standing\": \"open\",\n \"events\": 3, \"note\": \"this worktree, not pointed at\"},\n {\"session_id\": \"s-0002\", \"path\": \"b.yaml\", \"standing\": \"current\",\n \"events\": 9, \"note\": \"the pointer resolves to it\"},\n ],\n})).unwrap();\nassert_eq!(busy.episodes.len(), 2, \"the pointer is blind to the first of these\");\nassert!(busy.episodes.iter().any(|e| e.session_id == busy.current));\n```",
"title": "Episodes",
"$defs": {
"Episode": {
"type": "object",
"properties": {
"session_id": {
"type": "string",
"description": "The episode id the ledger stamps on every line this worker wrote."
},
"path": {
"type": "string",
"description": "Repository-relative path of the open record. The body is at the path."
},
"provider": {
"type": "string",
"description": "The provider whose event opened it, when one did. Empty for an episode opened by\nhand, which is the one episode no provider session owns."
},
"provider_session": {
"type": "string",
"description": "That provider's own session identity: the file's stem, and the string the prompt\narchive stamps on the same worker's records."
},
"owner": {
"type": "string",
"description": "Who opened it."
},
"worker": {
"type": "string",
"description": "The worker identity, when one was supplied. Never inferred."
},
"worktree": {
"type": "string",
"description": "The worktree it opened in, absolute, as recorded."
},
"branch": {
"type": "string",
"description": "The branch it opened on."
},
"start_head": {
"type": "string",
"description": "The commit it opened at."
},
"started_at": {
"type": "string",
"description": "When it opened, as recorded."
},
"standing": {
"$ref": "#/$defs/EpisodeStanding",
"description": "Where it stands."
},
"events": {
"type": "integer",
"format": "uint",
"minimum": 0,
"description": "How many ledger lines carry this episode id. Zero for an episode that opened and did\nnothing, which is ordinary and not a fault."
},
"last_activity": {
"type": "string",
"description": "The timestamp of the newest ledger line carrying this episode id, as recorded.\nEmpty when it has written none."
},
"last_event": {
"type": "string",
"description": "That line's event name."
},
"tasks": {
"type": "array",
"items": {
"type": "string"
},
"description": "The tasks this episode's ledger lines name, in the order it first touched each.\n\nA relation, not a container. An episode may touch three tasks or none, and a task may\nspan three episodes; the two are joined here by what the ledger recorded rather than\nby either one owning the other. An episode with no task is ordinary — work outside a\ntask is permitted — and it is emphatically not a reason for the episode's own records\nto go unwritten, which is the premise ADR 0052 removes."
},
"note": {
"type": "string",
"description": "Why it stands where it does, in words. Never empty: a standing a reader cannot act\non is the failure this subsystem is being corrected for."
}
},
"required": [
"session_id",
"path",
"standing",
"events",
"note"
],
"description": "One open episode, as its file records it and as the ledger has seen it.\n\nEverything here is recorded rather than derived, and almost everything is optional,\nwhich is the point: an episode opened by hand has no provider, a worker that supplied no\nidentity has none, and an episode that opened and did nothing has no ledger line and so\nno `last_activity`. Each of those is ordinary and none is a fault, so each is carried as\nabsence — the empty fields are skipped on the wire — and a reader must handle their\nabsence rather than expect a placeholder.\n\nThe two fields that are always there are `standing` and `note`: where the episode stands,\nand why it stands there in words. A standing a reader cannot act on is the failure this\nsubsystem was corrected for, so `note` is never empty.\n\n```\nuse majordomus_cli::capability::builtin::lifecycle::{Episode, EpisodeStanding};\nuse serde_json::json;\n\n// the smallest true record: an episode opened by hand that has written nothing yet\nlet e: Episode = serde_json::from_value(json!({\n \"session_id\": \"s-0001\",\n \"path\": \".ai/local/state/sessions-open/by-hand.yaml\",\n \"standing\": \"open\",\n \"events\": 0,\n \"note\": \"open in this worktree; the pointer names another episode\",\n})).unwrap();\n\nassert_eq!(e.standing, EpisodeStanding::Open);\nassert!(e.provider.is_empty(), \"no provider session owns a hand-opened episode\");\nassert!(e.last_activity.is_empty(), \"it has written no ledger line\");\nassert!(e.tasks.is_empty(), \"work outside a task is permitted\");\nassert!(!e.note.is_empty(), \"a standing always says why\");\n\n// and absence stays absence: the empty fields are not serialised back as \"\"\nlet wire = serde_json::to_value(&e).unwrap();\nassert!(wire.get(\"provider\").is_none());\nassert_eq!(wire[\"events\"], 0, \"a count of zero is a measurement, so it is kept\");\n```"
},
"EpisodeStanding": {
"oneOf": [
{
"type": "string",
"const": "current",
"description": "This checkout's pointer resolves to it. `continuity.state` is about this one."
},
{
"type": "string",
"const": "open",
"description": "Open, and it names this worktree, but the pointer names a different episode. Two\nwindows of one provider on one worktree are two workers, and only one is pointed at."
},
{
"type": "string",
"const": "foreign",
"description": "It names another worktree of this repository. Reported, never treated as this\ncheckout's — a record about somebody else's work quietly becoming yours is the\nfailure the two-tier resolution rule exists to prevent."
},
{
"type": "string",
"const": "stranded",
"description": "The worktree it names is gone from disk. Nothing can close it: the close path\ncomposes the record from git in the worktree the episode opened in."
}
],
"description": "Where an open episode stands, as far as the repository can observe it.\n\nIt is worth being precise about what is *not* here. Whether the provider that opened an\nepisode is still attached to its conversation is not a fact of this repository: the\nepisode file records no pid, the peer board records no episode id, and a client that\nexits without firing its end event leaves a file identical to one a live worker is using.\nGuessing would produce exactly the confident-and-wrong answer this subsystem exists to\navoid, so the vocabulary below is the observable one — where the file is, whose worktree\nit names, and whether the pointer follows it — and `note` always says which.\n\nRead as a scale, the four run from \"this checkout's own work\" to \"nobody's work any\nmore\", and only the last is a fault: `current` and `open` are both this worktree's live\nepisodes and differ only in which one the pointer happens to aim at, `foreign` belongs to\nanother worktree and is reported rather than adopted, and `stranded` is the one that\nneeds a person, because nothing can close it.\n\n```\nuse majordomus_cli::capability::builtin::lifecycle::EpisodeStanding;\n\n// the standings that are somebody's live work, against the one that is nobody's\nlet live = [EpisodeStanding::Current, EpisodeStanding::Open, EpisodeStanding::Foreign];\nassert!(live.iter().all(|s| *s != EpisodeStanding::Stranded));\n\n// serialised as the word `as_str` gives, so the two renderings cannot drift apart\nfor s in [EpisodeStanding::Current, EpisodeStanding::Foreign, EpisodeStanding::Stranded] {\n assert_eq!(serde_json::to_value(s).unwrap(), serde_json::json!(s.as_str()));\n}\n```"
}
}
}Examples
These are the capability's own benchmark cases: the same inputs the benchmark runs and the OpenAPI document shows.
default
{}