episodes.attach
Otevře epizodu provádění pro volajícího klienta, nebo obnoví tu, kterou už měl. 'external_id' je vlastní trvalé jméno klienta pro sezení — id jeho konverzace nebo vlákna — a musí přežít opětovné připojení: klient, který se vrátí pod stejnou identitou, dostane znovu svou vlastní epizodu, pod jakýmkoli peer id, které má nyní, místo druhé. Peer id tohoto serveru nestačí; přiděluje se na připojení. Poté vlastní provoz klienta udržuje epizodu naživu, ztráta spojení ji odpojí, ne uzavře, a uzavře ji majordomus_session_detach, zastavení serveru nebo reaper po uplynutí odkladu opětovného připojení. Epizodu repozitáře otevírá stejný příkaz, který spouští hook providera, a co ohlásil, je v odpovědi. Vyžaduje relaci MCP: přes prosté HTTP není připojení, ke kterému by se vázalo.
Otevře epizodu provádění pro volajícího klienta, nebo obnoví tu, kterou už měl. 'external_id' je vlastní trvalé jméno klienta pro sezení — id jeho konverzace nebo vlákna — a musí přežít opětovné připojení: klient, který se vrátí pod stejnou identitou, dostane znovu svou vlastní epizodu, pod jakýmkoli peer id, které má nyní, místo druhé. Peer id tohoto serveru nestačí; přiděluje se na připojení. Poté vlastní provoz klienta udržuje epizodu naživu, ztráta spojení ji odpojí, ne uzavře, a uzavře ji majordomus_session_detach, zastavení serveru nebo reaper po uplynutí odkladu opětovného připojení. Epizodu repozitáře otevírá stejný příkaz, který spouští hook providera, a co ohlásil, je v odpovědi. Vyžaduje relaci MCP: přes prosté HTTP není připojení, ke kterému by se vázalo.
- command
- behaviorally_verified
- module episodes
- #episodes
- #session
- #coordination
Zpřístupnění
Kde je tato jedna definice dosažitelná. Absence je explicitní: povrch, který není uveden, ji nenese.
| povrch | jako |
|---|---|
| MCP | nástroj majordomus_session_attach |
| HTTP | POST /api/v1/episodes/attach operationId episodes.attach |
| příkazová řádka | — |
Schémata
Kanonický vstup a výstup z typů Rustu; schéma nástroje MCP a parametry a odpovědi OpenAPI se odvozují z nich.
input · AttachInput
episodes.attach: který kus práce toto připojení nese.
Identita je jediné pole a neznámé je odmítnuto místo ignorováno, takže klient, který pošle peer id pod jiným názvem, se to dozví hned:
use majordomus_cli::capability::builtin::episodes::AttachInput;
let input: AttachInput = serde_json::from_str(r#"{"external_id":"conv-42"}"#).unwrap();
assert_eq!(input.external_id, "conv-42");
assert!(serde_json::from_str::<AttachInput>(r#"{"external_id":"x","peer":"p1"}"#).is_err());
assert!(serde_json::from_str::<AttachInput>("{}").is_err(), "the identity is required");| vlastnost | typ | povinné | popis |
|---|---|---|---|
| external_id | string | ano | Vlastní trvalé jméno epizody od klienta — id jeho konverzace, id vlákna nebo jak jinak nazývá sezení, v němž je.
Musí přežít znovupřipojení, protože to je celá jeho úloha: klient, který se vrátí pod stejnou identitou, dostane znovu svou vlastní epizodu a ne druhou. Postačí cokoli, co klient dokáže reprodukovat; nepostačí peer id tohoto serveru, které se přiděluje pro každé spojení a je při každém znovupřipojení klienta jiným řetězcem. |
JSON Schema
{
"additionalProperties": false,
"description": "The input of `episodes.attach`: which piece of work this connection is carrying.\n\nThe identity is the only field, and an unknown one is refused rather than ignored, so a\nclient that sends a peer id under another name learns it at once:\n\n```\nuse majordomus_cli::capability::builtin::episodes::AttachInput;\nlet input: AttachInput = serde_json::from_str(r#\"{\"external_id\":\"conv-42\"}\"#).unwrap();\nassert_eq!(input.external_id, \"conv-42\");\nassert!(serde_json::from_str::<AttachInput>(r#\"{\"external_id\":\"x\",\"peer\":\"p1\"}\"#).is_err());\nassert!(serde_json::from_str::<AttachInput>(\"{}\").is_err(), \"the identity is required\");\n```",
"properties": {
"external_id": {
"description": "The client's own durable name for this episode — its conversation id, thread id, or\nwhatever it calls the sitting it is in.\n\n**It must survive a reconnect**, because that is the whole of its job: a client that\ncomes back under the same identity is given its own episode again rather than a\nsecond one. Anything the client can reproduce will do; what will not do is this\nserver's peer id, which is handed out per connection and is a different string every\ntime the client reconnects.",
"type": "string"
}
},
"required": [
"external_id"
],
"title": "AttachInput",
"type": "object"
}výstup · Attached
episodes.attach: epizoda, kterou spojení nyní drží, zda byla obnovena místo otevřena a jak dlouho přežije ztrátu spojení.
use majordomus_cli::capability::builtin::episodes::Attached;
let schema = serde_json::to_value(schemars::schema_for!(Attached)).unwrap();
for field in ["episode", "resumed", "reattach_grace_seconds"] {
assert!(schema["required"].as_array().unwrap().iter().any(|f| f == field), "{field}");
}| vlastnost | typ | povinné | popis |
|---|---|---|---|
| epizoda | reference | ano | Epizoda, kterou toto spojení nyní drží. |
| reattach_grace_seconds | integer | ano | Jak dlouho tato epizoda přežije své spojení, než ji reaper uzavře, v sekundách. Uvedeno, ne ponecháno k objevení: klient, který zná odklad, může rozhodnout, zda se vyplatí znovu připojit. |
| obnoveno | boolean | ano | Zda byla obnovena epizoda už existující pod touto identitou, místo aby se otevřela nová. Pravda po znovupřipojení, restartu klienta nebo pádu, a pole, které volající kontroluje, aby věděl, zda v něčem pokračuje. |
JSON Schema
{
"$defs": {
"ConnectionEpisode": {
"description": "One episode: what the client calls itself, which connection holds it now, and what the\nrepository did about it.\n\n```\nuse majordomus_cli::episodes::{ConnectionEpisode, EpisodeBoard};\nuse majordomus_cli::peers::{PeerBoard, Transport};\nlet peer = PeerBoard::new().attach(Transport::Http);\nlet (episode, _) = EpisodeBoard::new().attach(&peer, \"conv-42\", \"generic\");\nlet episode: ConnectionEpisode = episode;\nassert_eq!((episode.external_id.as_str(), episode.provider.as_str()), (\"conv-42\", \"generic\"));\nassert_eq!(episode.peer.as_ref(), Some(&peer));\n// never empty: a board with no repository behind it says so\nassert!(episode.repository.contains(\"without a driver\"));\n```",
"properties": {
"attachments": {
"description": "How many times a connection has taken it over, the first attach included.",
"format": "uint32",
"minimum": 0,
"type": "integer"
},
"external_id": {
"description": "The client's own durable name for this piece of work, and the key everything here is\naddressed by. Never a peer id: see the module documentation.",
"type": "string"
},
"last_activity_seconds_ago": {
"description": "Seconds since the client last spoke on it, through any connection.",
"format": "uint64",
"minimum": 0,
"type": "integer"
},
"opened_at": {
"description": "When it was first opened, RFC 3339, UTC. Unchanged by a re-attach: the episode is\nthe work, and the work did not restart because a socket did.",
"type": "string"
},
"peer": {
"anyOf": [
{
"$ref": "#/$defs/PeerId"
},
{
"type": "null"
}
],
"description": "The connection holding it, when one does. `None` while detached — which is a state\nan episode is *supposed* to reach and come back from, not a fault."
},
"provider": {
"description": "The provider id this episode is recorded under, as `share/providers.yaml` names one.",
"type": "string"
},
"repository": {
"description": "What the repository's own episode command reported, verbatim — the record it opened,\nor the reason it could not. Never interpreted here, and never empty: a subsystem\nthat silently does nothing is the failure mode this field exists to make visible.",
"type": "string"
},
"state": {
"$ref": "#/$defs/ConnectionEpisodeState",
"description": "Open, or detached and waiting for its client."
}
},
"required": [
"external_id",
"provider",
"opened_at",
"last_activity_seconds_ago",
"attachments",
"state",
"repository"
],
"type": "object"
},
"ConnectionEpisodeState": {
"description": "Where an episode stands: held by a live connection, or waiting for its client to come\nback. There is no closed state, because a closed episode leaves the board; what survives\nit is the repository's own record.\n\n```\nuse majordomus_cli::episodes::{ConnectionEpisodeState, EpisodeBoard};\nuse majordomus_cli::peers::{PeerBoard, Transport};\nlet peer = PeerBoard::new().attach(Transport::Stdio);\nlet board = EpisodeBoard::new();\nassert_eq!(board.attach(&peer, \"w\", \"generic\").0.state, ConnectionEpisodeState::Open);\nassert_eq!(board.detach(&peer)[0].state, ConnectionEpisodeState::Detached);\nassert_eq!(serde_json::to_string(&ConnectionEpisodeState::Detached).unwrap(), \"\\\"detached\\\"\");\n```",
"oneOf": [
{
"const": "open",
"description": "A connection holds it and the client is speaking.",
"type": "string"
},
{
"const": "detached",
"description": "The connection has gone; the episode is kept for `REATTACH_GRACE` so the client\ncan come back to it. This is the state a crash lands in, and the one a reconnect\nrecovers from.",
"type": "string"
}
]
},
"PeerId": {
"description": "A peer's identity for the life of the server: `p1`, `p2`, ... in attachment order.\n`p1` is the session that started the server.",
"type": "string"
}
},
"description": "The answer of `episodes.attach`: the episode the connection now holds, whether it was\nresumed rather than opened, and how long it outlives a lost connection.\n\n```\nuse majordomus_cli::capability::builtin::episodes::Attached;\nlet schema = serde_json::to_value(schemars::schema_for!(Attached)).unwrap();\nfor field in [\"episode\", \"resumed\", \"reattach_grace_seconds\"] {\n assert!(schema[\"required\"].as_array().unwrap().iter().any(|f| f == field), \"{field}\");\n}\n```",
"properties": {
"episode": {
"$ref": "#/$defs/ConnectionEpisode",
"description": "The episode this connection now holds."
},
"reattach_grace_seconds": {
"description": "How long this episode survives its connection before the reaper closes it, in\nseconds. Stated rather than left to be discovered: a client that knows the grace can\ndecide whether reconnecting is worth it.",
"format": "uint64",
"minimum": 0,
"type": "integer"
},
"resumed": {
"description": "Whether an episode already under this identity was resumed rather than a new one\nopened. True after a reconnect, a client restart or a crash, and the field a caller\nchecks to know whether it is continuing something.",
"type": "boolean"
}
},
"required": [
"episode",
"resumed",
"reattach_grace_seconds"
],
"title": "Attached",
"type": "object"
}Politiky
- benchmark
- povinné — cíl na každém transportu, který expozice deklaruje; případy jsou ty ze vstupního typu
- cache
- vypnuto — každé volání spustí handler
Cíle benchmarku
Odvozeno z registru pro tento repozitář: jeden požadavek na transport a případy, které poskytuje typ vstupu. Celá matice je na stránce měření výkonu.