Expand description
Episodes: the execution episode of a client that has no provider hooks, drawn by the MCP connection itself (ADR 0103).
§The defect this exists for
Majordomus draws the episode boundary below the model, so that the episode which
mattered — the one that ended in a crash, a compaction, or somebody closing the window —
is recorded whether or not a model remembered to record it. Until this module that was
wired for exactly one provider. Claude Code fires SessionStart and SessionEnd, a
shim runs capture session, and the repository gets an episode. Every other client got
nothing at all, and the repository’s claim to draw the boundary below the model was a
claim about one vendor.
A client with no hooks still has a boundary, and this server is standing on it: the
client speaks initialize, makes calls, and goes. That is an episode. The connection is
the event source, the peer board already tracks attach, activity and detach, and the
repository’s own majordomus capture session already opens and closes episodes for a
provider that names its session. Nothing new needs inventing; the three had to be joined.
§A peer is not an episode
They are deliberately separate types and the distinction has cost real time here. A peer
id (p1, p2, …) is this process’s name for this connection, handed out in
attachment order and reused by the next server. It is not a worker identity: a session
that reconnects comes back as a different peer, and on 2026-09-09 a session in this
repository announced, was re-established under a new id, and was invisible to eight
others for three hours because everybody had been treating the id as durable.
So an episode is keyed by an external identity the client supplies — its own conversation id, thread id, whatever it durably calls itself — and the peer holding it is a field that changes. The invariants:
- a connection holds zero or one episode:
initializealone opens nothing, and a client that never callsepisodes.attachis a peer and not a worker with a record; - a client that reconnects re-associates with its own episode by external identity, under whatever peer id it now has;
- an episode outlives the connection that opened it, for a bounded grace, because a dropped socket is not the end of somebody’s work — it is the most ordinary thing that happens to one.
§What is honestly not here
Raw prompt capture. An MCP server is handed initialize, tool calls and notifications;
the person’s prompt is never among them, in any version of the protocol. Prompt capture
stays provider-specific, share/providers.yaml declares prompts: none for the generic
provider with that reasoning written down, and no surface implies otherwise.
§The lifecycle, end to end
A client attaches under its own name, its connection drops, it comes back as a different peer under the same name and gets the same episode, and then it says it is done:
use majordomus_cli::episodes::{CloseReason, ConnectionEpisodeState, EpisodeBoard};
use majordomus_cli::peers::{PeerBoard, Transport};
let peers = PeerBoard::new();
let board = EpisodeBoard::new(); // records nothing in a repository
let first = peers.attach(Transport::Http);
let (opened, resumed) = board.attach(&first, "thread-7", "generic");
assert!(!resumed);
board.detach(&first); // the socket went; the work did not
assert_eq!(board.list()[0].state, ConnectionEpisodeState::Detached);
let second = peers.attach(Transport::Http); // a new peer id, the same worker
let (again, resumed) = board.attach(&second, "thread-7", "generic");
assert!(resumed);
assert_eq!((again.opened_at.as_str(), again.attachments), (opened.opened_at.as_str(), 2));
let closed = board.close("thread-7", CloseReason::Detach).unwrap();
assert_eq!(closed.external_id, "thread-7");
assert!(board.list().is_empty());Structs§
- Connection
Episode - One episode: what the client calls itself, which connection holds it now, and what the repository did about it.
- Episode
Board - The episodes of one server.
- Null
Driver - A driver that records nothing: the board without a repository behind it.
- Tool
Driver - The driver that runs the repository’s own
majordomus capture session.
Enums§
- Close
Reason - Why an episode was closed. Reported to the repository as the end event’s
reason, whereshare/providers.yaml’s deliberate-end list decides whether the record saysclosedorinterrupted— the same table the provider hooks are read through, so a connection episode and a hook episode cannot disagree about what “ended deliberately” means. - Connection
Episode State - Where an episode stands: held by a live connection, or waiting for its client to come back. There is no closed state, because a closed episode leaves the board; what survives it is the repository’s own record.
Constants§
- REATTACH_
GRACE - How long an episode whose connection has gone is kept before it is closed.
Traits§
- Episode
Driver - What opens and closes the repository’s own episode.