# Telegram Multi-Instance Bus Architecture

## Status

Implemented for paired bots where Telegram exposes private-chat Threaded Mode. Classic/private-chat mode remains the default whenever Telegram threads are unavailable, and live Telegram client smoke remains the release gate for client-visible thread UX.

This document uses **thread** as the canonical product term because Telegram clients present the tabbed UI as threads. The Bot API calls the underlying primitive a `Topic` / `ForumTopic`, but project language follows user-perceived client reality rather than API naming. Use **topic** only when discussing Bot API method names, service-message names, or transport-level evidence. User/operator UX and product docs should say thread.

This document supersedes the narrower API-topic framing. Telegram threads are a UI/routing substrate, but the deeper design problem is multi-instance coordination: one bot token has one Telegram API update bus, while multiple live Pi agent instances may want to expose their own Telegram workspace through that bus.

Named bot profiles are optional and orthogonal to this design. The ordinary unnamed profile keeps the existing setup, connect, lock, state, log, Unix socket, and Windows named-pipe paths. When operators configure additional profiles, each profile is an independent bot runtime with its own lock key, observable state, thread ownership, and leader/follower IPC endpoints; leader election and follower routing never cross profile boundaries.

## Problem

Classic `pi-telegram` mode binds one private Telegram DM to one live Pi instance through one bot token and one singleton polling owner. The lock currently answers: "which Pi instance owns Telegram control/polling?"

That model is safe, but it leaves concurrency on the table:

- Only one live Pi instance can receive Telegram updates for a bot token.
- Moving `/telegram-connect` changes the active Telegram control owner instead of letting several instances coexist.
- Multiple projects, tmux panes, remote workers, or long-running Pi instances require separate bot tokens or manual ownership switching.
- A thread workspace is only useful if it routes to a live agent instance, not to a dead session record that can no longer answer.

Telegram itself has one relevant constraint: for a bot token, `getUpdates` must be owned by one poller. The architecture must embrace that by electing one local Telegram bus leader and routing work to follower instances.

## Goal

Support a Threaded Mode multi-instance runtime where:

```text
one bot token -> one local Pi organism -> ephemeral bus leader -> many live Pi instances -> many Telegram targets
```

The leader is a temporary transport role, not the ontological owner of the system. A terminal-visible Pi instance may become the initial leader because it is the operator's visible harness, while additional terminal-visible Pi instances can explicitly register as followers through `/telegram-connect` and one of them can later take over bus leadership if the leader exits.

A practical Telegram UI can then use threads:

```text
one private bot chat -> one thread per live Pi instance
```

The operator experience:

1. Start one Pi instance; it becomes the Telegram bus leader and polls Telegram.
2. Start another Pi instance with `pi-telegram` and run `/telegram-connect`; the follower registers instead of fighting for `getUpdates`. Use `/telegram-connect as=Navigator`, or `/telegram-connect work as=Navigator` for a named profile, to name a fresh Workspace Thread. On later process reopen, a profile-scoped exact-`cwd` Workspace with a remembered Thread reconnects as a follower automatically while the leader remains live.
3. The leader provisions or reuses a Telegram thread target for that instance. Use `/name Navigator` in that Telegram Thread later to change its display name through leader-authorized persistence.
4. Messages, callbacks, reactions, files, voice, previews, and menus in that target route to the owning live Pi instance.
5. If the leader exits, remaining followers elect/promote a new leader, which resumes polling and keeps the registered target routes alive where possible.

## Non-goals

- Do not let more than one process call `getUpdates` for the same bot token.
- Do not treat Telegram as a raw terminal, PTY, or process supervisor.
- Do not couple the first design to Pi sessions if live instance ownership is the better runtime truth.
- Do not expose arbitrary group participants to prompts, controls, or artifacts.
- Do not require Threaded Mode for classic private-chat users; classic private-chat mode remains valid and should not receive slot/thread-name guidance.
- Do not implement leader election through unsafe lock stealing without stale-owner proof (dead PID, or a bus-proven unresponsive owner).

## Terms

- `Telegram bus`: The singleton local capability to poll Telegram updates and send Telegram API calls for one bot token.
- `Leader`: The live Pi instance that currently owns the Telegram bus and calls `getUpdates`; this is an ephemeral role transferable after its PID dies or the bus proves it unresponsive.
- `Follower`: A live Pi instance that wants Telegram presence but routes Telegram API access through the leader.
- `Bus lifecycle`: Transient recovery state only. Stable identity is the bus role (`leader` / `follower`); lifecycle surfaces exceptional handoff states such as `electing`, not duplicate roles with labels like `leader-active`.
- `Agent instance`: A running Pi process/session with its own extension state, queue, active turn, model, tools, and lifecycle hooks.
- `Telegram target`: The concrete Telegram destination for an instance, represented as `{ chatId, threadId? }`.
- `Thread target`: A Telegram UI thread destination, represented as `{ chatId, threadId: message_thread_id }` over Bot API topic transport.
- `Classic target`: The existing private-chat target, represented as `{ chatId: allowedUserId }`.

## Core Shift

In Threaded Mode, the lock means "this instance is the current Telegram bus leader" rather than "this instance is the only usable Telegram extension".

Classic ownership meaning:

```text
tmp/pi-telegram/state.json / profiles.<profile>.transport -> polling/control owner
```

Threaded Mode meaning:

```text
tmp/pi-telegram/state.json / profiles.<profile>.transport -> bus leader identity (no periodic heartbeat)
```

Followers do not poll. They register with the leader and receive routed inbound updates from it. Followers still own their local queue, active-turn state, previews, final delivery planning, model switches, and Pi lifecycle. The leader owns only Telegram transport and update fanout. Same-session context refresh preserves bus membership; a changed Pi session ID suspends the old receiver/heartbeat and requires acknowledged successor registration before readiness, without an explicit Thread disconnect. A new follower process also attempts a restore-only registration at session startup when the selected profile's local state contains a Workspace binding for the exact `cwd` and session ID. The recent-event log distinguishes missing session bindings, disabled Threaded Mode, leader-side restore refusal, and a restore that returned without connecting; none of these refusals silently provisions a new Thread. The Telegram bus belongs to the local set of cooperating visible Pi instances rather than to the first terminal session forever: if the visible terminal leader exits, a live registered follower can take over leadership.

### Session-Owned Journal Storage

Runtime storage lives under `<agent-dir>/tmp/pi-telegram`. The pre-0.52.0 `tmp/telegram` tree is neither written, migrated nor deleted. A live fresh owner there blocks competing polling as read-only evidence; its endpoint and secret grant no current authority.

The root keeps only `state.json` (per-profile `transport`, `workspace`, `admission` and observational `runtime` sections) and `logs.jsonl`; see [Consolidated Runtime Root](./architecture.md#consolidated-runtime-root). Journals have separate roles:

- Polling and local leader admission use the journal named by the profile's `transport` section. With a current Pi session ID, the first leader without a pointer or existing root custody hosts `sessions/<id>/inbox[.<profile>].json`.
- Follower recipient admission uses `sessions/<id>/journal.<recipient hash>[.<profile>].json`. Recipient binding identity and its existing 16-hex hash stay unchanged; equal hashes in different sessions remain distinct custody.
- Each journal owns its adjacent `.segments/` and `.retained/` family. Existing root `inbox` and explicit legacy `follower-inbox-<hash>` custody remain readable without file moves or migration.
- Slots `A`–`Z` never determine filenames. Canonical safe lowercase session names, including ordinary UUIDs, remain verbatim; unsafe/case-aliasing IDs use reversible UTF-8 percent encoding. No `session-` prefix, `unknown` folder or first-hash lookup exists.

The polling resolver prefers a valid owners-named path, then an existing profile root inbox, then the hosting session path. Without a session ID, the root inbox remains a compatibility fallback; ordinary session-aware startup does not create a new root inbox. Release keeps a pid-less `{ journalPath }` pointer, not a live owner. Successors continue the same cursor/custody; leader `/new` keeps polling into the original hosting folder. A missing named file does not select a new host: normal store creation may recreate the same path.

Workspace `journalSources` retains exact `{ sessionId, recipientBindingKey }` addresses (maximum 256 per binding), including predecessors across upsert and `/new` re-key. Returned tuples are detached; malformed/over-capacity publication and malformed cold loading refuse rather than erase evidence. Active follower paths require current Pi context and leader-acknowledged session ID agreement, including preparation before receiver readiness. Queue handoff requires the authenticated recipient registry's session ID before donor offer/removal. A dead-owner reclaimer without an exact session resolver refuses session-qualified custody; it never substitutes a flat same-hash journal.

Production protection uses scoped non-repairing historical reads, recipient-process writer checks and a fresh strict session namespace census even when binding addresses are complete. The catalog includes flat/session families and exactly one named polling source; other inboxes remain protected evidence. Missing resolution, corrupt/unclassified storage, uncommitted retention, missing committed originals or retained-only families without an exact snapshot leave protection unknown. Discovery/census is neither writer closure nor readiness/deletion permission.

#### Metadata-only Evidence Pruning

Production `createTelegramWorkspaceJournalEvidencePruner` is composed into the capacity-pressure runner; it is not periodic or startup cleanup. Only a failed fresh allocation with a verified full A–Z occupancy may call it for inactive bindings: the eligible retirement candidate when one exists, otherwise the protected-capacity candidates. `pruneTelegramWorkspaceJournalEvidence` acquires target admission before entering the shared Workspace gate and invoking a fresh source-capture callback. Its read-only protection observer reuses the same strict namespace census, exact resolvers, scoped references and writer checks as ordinary retirement protection. Legacy addresses and exact `(sessionId, recipientBindingKey)` pairs never collapse together. Every retained address and the shared source must have matching available evidence; incomplete/corrupt/wrong-scope capture refuses. Only empty addresses with positively quiescent writers are removed by subset CAS; busy/live/unknown-writer addresses stay retained, including alongside a removable subset. Completeness is never manufactured. Threads acknowledges the exact current metadata frame through `persistWorkspaceJournalEvidence`, checking the captured state path, transport authority and expected frame inside the snapshot file transaction before rename: refused publication is not reported as committed, and publication-result/authority loss never compensates an already published subset. Neither source files nor independent Restore/retirement/temporary roots are deleted or rewritten. A positive metadata ACK supplies the current binding frame to subsequent queue reclamation; stale/failed publication is never presented as pruning success. All ordinary protection and retirement-fence/deletion-permit checks remain independent and mandatory afterward. This is not a prerequisite or closure requirement for the separately approved optimistic session-family sweep.

Native filesystem fixtures cover address separation, late pending input, publication refusal/loss and stale retry. Full-capacity composition uses real binding resolvers, a strict session namespace, references and the shared gate; pending or independently delivery-protected custody still blocks deletion, corrupt namespaces remain untouched, epoch loss retains published metadata without deletion, and free capacity never calls the pruning port. Registry/writer/leader premises and the deletion transport are supplied; these are not actual Pi/Telegram or native-Windows acceptance.

Demand-driven pressure reclamation now resolves every retained session address through the same exact recipient resolver and existing reference scope. Whole unoffered queued receipts and death proof for every planned owner are mandatory before any terminal CAS; pending, unavailable/corrupt or alive/unverifiable custody refuses. Current binding/leader/local-work/delivery protection is rechecked before each journal-owned mutation, and fresh all-clear protection remains mandatory before ordinary retirement can delete a Thread. Partial terminal disposal is retained, never rolled back or replayed; retry handles only still-owned receipts. Native filesystem cases cover identical hashes in two sessions, source/authority refusal and interrupted second-journal publication; they do not prove live or native-Windows acceptance.

#### In-Process Follower Succession

After the predecessor worker stops and before the successor starts, lifecycle `prepareBinding` invokes `prepareActiveFollowerSuccession`. Its process-local tracker records the first prepared session without adoption. Only a changed session ID under the same recipient key adopts plain unclaimed `pending` entries; claims, provenance, routing-input custody, queue owners/receipts/handoffs, failures and exclusion vetoes prevent adoption. Cold process startup never adopts predecessor input.

Each eligible entry first commits away through private retention and a predecessor tombstone under `session-successor:<id>`, then appends to the successor journal. No file moves. Failure refuses readiness and retains the predecessor tracker for retry. A crash or failed append after commit leaves the original privately retained but non-executable; automatic transfer completion is not implemented, and retry never re-appends a committed source. Accepted queue custody stays with its predecessor owner. Restore cancellation proof accepts only `telegram-owner:` authority, so adoption cannot settle Restore.

Pending-entry deduplication does not prevent a generic delivery retry after the recipient entry completed and was removed; it can execute again, with or without adoption. Temporary-Thread Forward issuance is not a generic deduplication protocol. Native gateway/IPC fixtures supply Pi host hooks, not actual `newSession` ordering or live acceptance.

#### Optimistic Session-Family Sweeping

After a current follower-prune pass, the leader runs a best-effort sweep, throttled to once per 10 minutes by a process-local clock. All Workspace-bound sessions, live registrations and this process's own session are kept. In other canonical session folders it deletes only current-profile `journal.<hash>.json`, `.segments/` and `.retained/` families, including private originals, then removes the folder only if empty. Other profiles, unknown files and noncanonical folders remain. The matcher never selects any `inbox` family, so polling-host folders survive without a slot.

This operator-approved disposable-session policy is not Thread deletion, receipt settlement or whole-system reference closure. Historical Restore/retirement/temporary references into a swept family may stop resolving and remain protective; they grant no replay or fabricated completion. Strict protection reads themselves never repair or delete storage.

### Follower Registration And Session Readiness

The assembled follower receiver admits a registration generation only after its journal-binding preparation succeeds for the same generation and current Pi context. Registration identity is available during preparation so the worker can bind, but early inbound delivery receives a negative ACK rather than writing into a retained old journal. Readiness also captures the Pi session ID before binding preparation and rechecks it after the await and at every readiness read, alongside the session generation. A reused context object cannot preserve readiness across session replacement, even if a host fails to advance its generation. The assembly's `getReadySessionId()` exposes only that still-current prepared ID, never the ID of an unfinished preparation. Same-session refresh awaits binding preparation through `setContext` without re-registering or changing its bus generation. A changed session ID now refuses refresh before binding preparation: local readiness must match the ID acknowledged by the current leader registration, not merely its bus generation. The existing suspended session-replacement handoff re-registers separately; only successful fresh registration can prepare the successor ID. Registration requests capture that exact ID before startup and recheck it across all awaits, so changed-ID late replies cannot finalize registration even when a reused context or unchanged host generation would otherwise conceal the switch. Refusal never removes old-session custody or rewrites the leader's retained binding. Preparation failure keeps inbound delivery unavailable and records a diagnostic; a later refresh can retry. Normal delivery retry proceeds after successful preparation. Registration requests also retain their original Pi session generation and local attempt authority across awaits. A late reply after session drift, stop or a newer request cannot publish success or tear down newer local authority; a context refresh likewise protects its retained membership from an older pending request. This refusal does not undo remote Thread provisioning or authorize deletion.

A follower may route Bot API work or capability checks only after authenticated registration; an unregistered process that does not own the direct transport lock remains transport-passive. Follower request settlement remains owned by the existing local IPC operation budget rather than being inferred from Bot API method names; followers never invoke `getUpdates`.

## Target Abstraction

The bridge uses a first-class target abstraction:

```ts
type TelegramTarget = {
  chatId: number;
  threadId?: number;
};
```

Private-chat mode uses `{ chatId: allowedUserId }`.

Threaded Mode uses `{ chatId: privateChatId, threadId: messageThreadId }` over Bot API topic transport.

Every session/instance-scoped path carries or preserves a target:

- Inbound update routing.
- Queue item identity.
- Active turn state.
- Preview draft state.
- Final replies and reply deduplication.
- Voice and attachment uploads.
- Menu/status/settings/queue/section messages.
- Button callback ownership.
- Reactions.
- Typing/record-voice chat actions.
- Direct local/TUI Telegram delivery.

## Binding Model

A thread maps to a currently running Pi instance, not to a historical session file. `instanceId` is the live routing owner, while `instanceProfileKey` is a reuse hint for reclaiming a compatible current thread across process replacement.

```text
runtime owner: live instance id
reuse hint: cwd/profile/user-chosen alias/session id when available
```

This keeps thread liveness honest: if an instance is registered, there is a live owner to answer. It also avoids coupling `/new`, compaction, and session-file internals to Telegram routing. Restarted projects may still reclaim previous current bindings through profile-aware reuse when that does not conflict with live ownership.

## Instance Identity

A registered instance exposes:

```json
{
  "instanceId": "uuid-or-runtime-id",
  "pid": 12345,
  "cwd": "/home/user/project",
  "startedAt": "2026-05-20T10:00:00.000Z",
  "owner": { "kind": "leader", "cwd": "/home/user/project" },
  "threadName": "<valid-instance-identity>",
  "target": { "chatId": 123456789, "threadId": 42 },
  "status": "idle|active|queued|compacting|disconnected",
  "lastHeartbeatAt": "2026-05-20T10:00:05.000Z"
}
```

`instanceId` is liveness identity. `owner` is explicit current binding identity (`leader`, `manual-follower`, or `pending-topic`). Internal compatibility keys may be derived, but `state.json` should not hide ownership direction inside legacy string keys. `threadName` is the stable named-mode and restoration identity; `displayTitle` is the separately acknowledged user-facing projection. Fresh threads receive a compact palette name from the assigned slot; Names projects that saved name, while Letters and Directories project other titles without replacing it.

## Approved Next Contract: Directory Names And Reclaimable Slots

Status: approved design with a locally tested pure selection policy in `lib/workspace-slots.ts` and profile-isolated display preference persistence/default resolution in `lib/config.ts`. Workspace claims now reserve global letters before provisioning and preserve legacy binding keys. An exact claim assigns the first free letter to a missing-slot binding or the selected member of a duplicate-slot set, but persistence waits for successful target recovery; unresolved duplicates block unrelated fresh allocation. If authenticated follower re-registration finds that its retained target record carries another letter, the exact claim is canonical: registration repairs that record before binding commit and leaves the unrelated binding that owns the stale letter intact. Sticky suffix metadata and acknowledged `displayTitle` persist in Workspace bindings. `lib/thread-display.ts` provides the three-mode projection plus serialized title reconciliation wired into leader startup and follower registration. Heartbeat ACKs carry acknowledged display titles to followers and the current-thread/TUI projection uses them without changing restoration identity. Settings now exposes Letters (default), Names, and Directories; follower changes use the capability-gated leader-owned setting path. Live bot chooser/notice labels and cross-instance agent-target resolution use acknowledged titles without granting routing authority. The Thread store now persists the first proven `inactiveSinceMs` transition with exact confirmed target absence or fenced non-destructive detachment of a confirmed-dead follower or quiescent quitting leader whose tab is preserved; successful active provisioning clears it. Pressure selection, intents, mocked execution, and recovery are implemented. A 2/2 same-model independent post-fix quorum cleared the admission-composition blocker at 0.96 confidence per reviewer, and the operator has since authorized demand-driven rotation. Fresh allocation now invokes the existing retirement lifecycle under full capacity; disposable-client behavior was accepted in the operator's 0.52.0 live smoke.

The pure policy distinguishes a free letter, a proposed pressure-reclamation victim, and protected/invalid capacity. Its caller must supply a validated profile-wide snapshot, reservations, proven inactivity start, and explicit protection classification; duplicate legacy letters block selection. The policy performs no filesystem or Telegram operations and does not establish liveness or deletion authority. It proposes a victim only when every profile-wide letter is occupied or reserved; elapsed time alone never triggers retirement.

### Cross-Process Admission Fence

Production retirement requires a durable profile-scoped reader/writer ledger owned by a dedicated `workspace-admission` domain. The ledger uses the existing atomic file-transaction primitive but does not hold a filesystem mutex across asynchronous work. It stores versioned admission leases and at most one destructive fence per exact Workspace target. Every record carries bot/profile identity, exact or conservative scope, a collision-resistant retry-stable operation id, operation kind, process id plus process-birth identity, and acquisition time. A destructive fence additionally carries the exact retirement-intent id, binding key, slot, target, and leader epoch. Missing, malformed, unreadable, ambiguously published, or unverifiable-owner state fails closed.

- Journal append, target-scoped Bot API issuance, provisioning, registration, and binding/target replacement must first transact against the ledger. They prune only leases whose process-birth owner is proven dead, reject a matching destructive fence, durably add an admission lease, perform the asynchronous or durable operation, then remove the exact lease transactionally. Exact-target leases conflict with that target; chat-only API work uses chat-wide scope; undecodable journal admission uses profile-wide scope. Duplicate release and process-crash recovery are idempotent, while an unknown release outcome remains protective.
- Retirement acquires its fence transactionally only after exact profile/epoch/intent validation and only when no exact, chat-wide, or profile-wide lease conflicts. Once the fence is durable, new matching admission is rejected. After the final protection recheck, the ledger durably advances `fenced` → `deletion-issued` and returns the sole deletion permit; a retry after an ambiguous publication or crash observes the issued phase but receives no second permit. Exact confirmed deletion or already-absence advances to `commit-ready`, and only durable binding plus intent removal permits fence completion. A protection change before permit issuance releases the fence; an unknown deletion, ambiguous commit, authority loss after issuance, or crash retains it.
- A successor may resume destructive work only with the exact durable retirement intent and the existing profile/binding/leader-epoch adoption checks. Terminal `commit-ready` or `deletion-rejected` recovery may finish after intent removal under the exact profile/fence authority and binding postcondition, without another deletion. It preserves original request/acquisition time and phase, replacing only the fence owner/epoch. An adopted `deletion-issued` fence must resolve through exact already-absence or retained unknown outcome and can never issue another deletion request. A different intent never clears or steals the fence. Startup restore-only and fresh capacity paths cannot bypass it; they return a truthful temporary-unavailable result without allocation or eviction.
- The isolated ledger and process regressions now cover admission-before-fence, fence-before-admission, chat/profile-wide conflicts, concurrent contenders, proven-dead lease cleanup, unverifiable-owner protection, malformed-state rejection, exact release idempotence, ambiguous publication recovery, one-shot deletion permits, and successor fence adoption. Production journal stores resolve an admission adapter that conservatively derives batch scopes, holds leases through atomic publication, and releases partial acquisitions on rejection. The production common JSON/multipart client likewise resolves its adapter per current bot/profile: target/chat scopes span all transport retries through settlement, malformed declared targets use profile scope, targetless methods bypass the ledger, and release failure cannot replay a settled request. One production Workspace operation runtime resolves admission before a shared local gate. Leader assembly uses it around chat-wide leader provisioning and profile-wide follower provisioning/registration, disconnect/dead cleanup and preserved-owner detachment, leader/follower rename, and display preference/reconciliation; Sync topic lifecycle and routing restore/reclaim use the same profile-wide boundary, so fence rejection precedes store reads and mutation. The isolated retirement executor now requires the matching durable fence, closes admission before its final protection recheck, persists `deletion-issued` before calling an executor-only deletion port with the sole permit, retains unknown outcomes without reissue, and commits only from confirmed `commit-ready`; successor recovery is process-tested. Retained fences now project their uppercase slot into the Thread store's optional external reservations, blocking generic allocation, Workspace claims, and reclamation selection even after binding commit; malformed or unreadable evidence fails closed. The profile runtime resolves a separate `workspace-admission[.<profile>].json`, stores only token-hash authority, preserves named-profile identity across switching, permits changed-token rebind only from a provably empty ledger, and rejects rotation while leases or a fence remain. The admission side is production-wired through leader/follower mutations, topic lifecycle, reroute restore/reclaim, manual disconnect/session-restart cleanup, and exact stale-target recovery; journal-evidence pruning also requires exact-target admission through durable publication. Disconnect/restart cleanup acquires profile admission before the shared leader gate and holds it across cleanup intent, API, binding, and durable settlement, so both retained fence phases reject it before state access. The common async admission runner and API adapter reject concurrent reuse of one live operation ID before lease acquisition, while clearing that process-local guard after acquisition failure or complete settlement so retry-stable durable recovery remains separate. A 2/2 same-model post-fix quorum independently verified the complete production mutation map, delayed reconciliation fix, fence-first and lease-held schedules, and absence of nested-gate deadlock at 0.96 confidence per reviewer. Demand-driven retirement is now operator-authorized and composed with the same ledger/gate. Local regression evidence does not replace disposable client acceptance.
- Production mutation coverage is intentionally caller-owned rather than embedded in generic Thread-store primitives.
- The shared profile-wide Workspace operation runtime covers observed topic lifecycle, the complete unbound-target and reroute restore/reclaim handlers, manual disconnect/session-restart cleanup, and leader assembly provisioning, registration, cleanup, rename, and display operations. Detached post-provision reconciliation reacquires a fresh profile lease through the same runtime before delayed API/store work. Every admission lease precedes one process-local gate and lasts through API plus durable settlement.
- Journal append and common JSON/multipart transport use their own exact/chat/profile leases through publication or settlement. Follower promotion/target replacement, exact stale-target recovery, and journal-evidence pruning likewise hold dedicated admission through their complete mutation. These operations cannot overlap retirement because fence acquisition conflicts with the retained lease even when they do not need the shared local queue.
- Retirement intent preparation/adoption/execution owns its exact gate-and-ledger protocol. The allocation wrapper invokes it outside ordinary registration/provisioning admission, after a typed capacity failure or to reconcile a retained retirement. Status projection and polling/routing bot-mode writes change only diagnostic or capability metadata; they neither create nor remove Thread/Workspace authority and serialize through the store's local persistence queue.
- Production callers of reservation, provision/cleanup intent, target-record, Workspace-binding, and display mutators are contained by the owners above. The generic store remains policy-free for isolated tests and domain composition; calling a primitive directly is not production retirement authority.
- Workspace identity remains the selected bot profile plus normalized exact full `cwd`; directory basenames are presentation, never routing keys. Each concurrent binding receives one profile-wide unique lowercase slot from `a` through `z`, persisted on the wire/store as its uppercase equivalent, independent of directory and leader/follower role. This replaces the two competing displayed allocation identities; immutable legacy `instanceSlot` and `bindingKey` remain recovery keys, not another displayed pool.
- Automatic display mode is a bot-profile setting shared by Telegram Thread titles and Pi TUI status. The selector offers `letters`, `names`, `directory-snake`, then `directory-title`; absent or invalid values resolve to `letters`. Letters show `A`, `B`, `C`; Names shows the generated dictionary name for the slot, such as `Anchor` for `A`. The retired `directories` value is unsupported: it resolves to `letters`, is omitted from Settings, and is not rewritten automatically. A durable per-Workspace `manualThreadName`, set through Telegram `/name`, overrides any automatic projection until explicitly reset. Bare `/name` immediately enters exact-target rename input: cancel is always available, while reset is shown only when a manual override exists; no intermediate action-selection step exists. Store the automatic preference at `profiles.<name>.threadDisplayMode`, not as a process-local choice or a setting shared by unrelated bots.
- Directory formatting operates on the shortest path-segment suffix that distinguishes the normalized exact cwd from every other displayed cwd, while routing and identity continue to use the unmodified full cwd. Each segment is tokenized deterministically at runs of non-letter/non-number characters, lowercase-or-number to uppercase transitions, and the acronym boundary before an uppercase-plus-lowercase word (`apiPRDServer` becomes `api`, `PRD`, `Server`). Empty tokens are discarded. `directory-snake` lowercases tokens and joins them with `_`; segment qualifiers also join with `_`, so `/work/API tools` previews as `api_tools`. `directory-title` joins words with spaces and qualified segments with ` / `; an all-uppercase token containing a letter is preserved (`PRD` remains `PRD`), while every other token is lowercased and capitalized (`api_tools` becomes `Api Tools`). Unicode letters and numbers participate in tokenization and locale-independent Unicode case conversion; punctuation is only a separator. Root and token-empty segments retain the existing safe fallback instead of fabricating an empty title.
- Settings previews are computed by the same pure projector used for initial creation and reconciliation, against the current binding set rather than illustrative hard-coded text. Slot suffixes are applied after base formatting (`api_tools_a` and `Api Tools A`) and remain outside abbreviation/case normalization; title-case suffixes use one plain space and never a middle-dot separator. Manual names are never normalized. Case-insensitive or 128-character-bounded collisions remain fail-closed under the existing ambiguity contract. New persisted modes and follower requests require a new negotiated display-format capability; an older peer may continue operating only while the effective profile mode remains Letters or Names, and cannot silently reinterpret either new value.
- The unreachable legacy `directories` projector remains only for decoding old internal snapshots; effective configuration falls back to Letters. The `directory-snake` and `directory-title` modes derive suffix visibility exclusively from the leader's current authenticated live-owner snapshot: one live binding for a normalized exact cwd has no suffix; two or more live bindings for that cwd all expose their globally assigned slots; dormant retained bindings never affect the count. `showSlotSuffix` remains readable for legacy presentation but is ignored by both new projectors and is never written merely because live concurrency changed. The leader captures exact profile, epoch, binding target, and follower registration generation before projection; a provisioning candidate counts only after its authenticated exact owner is admitted to the same serialized Workspace mutation, while an owner losing authority is excluded before reconciliation. Registration/provision completion, authenticated disconnect or confirmed-dead prune, target replacement, and promotion schedule one serialized reconciliation of every affected live same-cwd binding. A failed or partial Telegram rename retains acknowledged per-binding progress and retries only under a fresh live snapshot; dormant tabs are not renamed until they regain authenticated ownership. Leader startup treats its authenticated follower roster as unsettled for one follower-staleness window: automatic display reconciliations requested by election or incremental re-registration are deferred and coalesced until that boundary. A same-cwd follower returning within the window therefore preserves the already acknowledged suffix without an `unsuffixed → suffixed` Telegram rename; if it stays absent, the settled reconcile removes the suffix once. Explicit mode changes remain immediate, and leader stop/restart invalidates the pending timer. Equal basenames from different paths still require deterministic parent-path qualification independently from same-cwd suffixing. Preserve the existing `threadName` as generated/recovery identity while automatic modes are selected. New manual names live only in `manualThreadName`; do not guess manual provenance from a legacy name or palette membership. Telegram `/name Name` changes the manual override and displayed title only for its exact originating target. Leader and follower requests carry that target through final generation/binding checks, so replacement cannot redirect a stale dialog mutation. Reset uses the same negotiated `workspace-thread-rename-v1` capability and exact follower generation; the leader computes the current automatic projection from the latest live snapshot, edits the exact target, clears only `manualThreadName`, and persists before acknowledging. Follower metadata refresh preserves an acknowledged display title only while target and registration generation stay unchanged. Named-profile setup preserves the latest saved automatic preference even if another instance changes it while the token form is open.
- Fresh provisioning projects the candidate together with retained bindings and sends the active mode's title in `createForumTopic`. The exact targeted provision retains creation-title evidence until the Workspace commit publishes the binding and consumes that evidence together. Recovery preserves it even when a starting record already exists; an untargeted or unknown creation never authorizes a title commit. Proven deletion removes exact-target pending creation evidence, including when no current record was committed. Older contradictory pending/deleted snapshots settle that evidence durably before replacement; closed targets and pending cleanup block recovery until reconciliation, rather than becoming active again. The same exact-target check protects follower reconnect/carried-target shortcuts and the final Workspace commit, before creation evidence can be consumed. A matching carried pending target resumes through the provisioner that owns its reserved slot and acknowledged title instead of allocating that slot again. The shared provision-commit helper first commits the claim, then applies the acknowledged title with exact-binding comparison, preserving generic stale-title rejection on target replacement. Switching display mode changes projection only: preserve Thread ID, binding identity, slot, queue ownership, and routing. `displayTitle` records a successful Telegram edit independently of `threadName`, survives same-target registration updates, and is cleared on target replacement. The title reconciler captures profile, mode, leader epoch, and exact live-binding authority before each edit, rechecks after ACK and persistence, and skips dormant bindings. Failed persistence retains acknowledged dirty metadata for a later persist without repeating that API edit; a late or unknown ACK never commits a title to a replacement binding. Keep the stable palette/manual name separate from the current display title so switching back does not generate a different name. The leader owns Telegram title edits and acknowledged follower/TUI convergence, with generation/profile fencing and truthful partial-failure recovery. Successful registration ACKs optionally carry the acknowledged `displayTitle` with the exact target and registration generation, making it available before the initial status refresh. Heartbeats carry later title changes; stale generations cannot update display state. Connected notices use acknowledged titles while runtime `threadName` remains the stable restoration identity. Live bot chooser/notice labels, prompt attribution, and cross-instance agent-target name selection use the same acknowledged projection, but candidate liveness and the captured numeric `{chatId, threadId}` remain authoritative. Ambiguous projected names fail closed. Older peers can ignore the optional field, and no separate polling connection or follower snapshot-read loop is needed; do not expose a setting control that merely stores a preference without updating its promised surfaces.
- Reopening a retained inactive binding restores its slot and name without taking leadership. Explicit connection from the same directory may allocate a second binding. Startup restore remains restore-only: it must not evict another Workspace or allocate a fresh binding merely because all remembered bindings are owned. Explicit connection already skips a live peer's migrated binding instead of attempting to adopt it; that admission rule is independent of the display redesign.
- Prefer free letters in deterministic order. Only under full slot exhaustion may retirement choose the eligible binding with the oldest proven inactivity start, not necessarily slot `a` after `z`. Time alone never retires a binding. If every slot is protected or its ownership is unverifiable, reject new allocation with a truthful capacity explanation; never evict a live owner or silently expand into `aa`.
- Inactivity begins at a verified transition to no live owner; an idle but connected process is active. Exact confirmed Thread deletion also proves that the binding has no active Telegram target: the common Thread store records first inactivity with invalidation, even if an earlier stale observation already removed the live record. Fenced invalidation publishes this metadata only at its durable commit; closure or an unknown API outcome alone supplies no inactivity. With automatic cleanup disabled, confirmed-dead follower pruning instead acquires profile admission and commits non-destructive owner detachment. It requires one exact owner record and one matching Workspace binding with the same letter; final publication rechecks the registered positive PID's absence, prune generation, leader epoch, profile and replacement registrations. It retains the tab, Workspace identity, slot and journals without claiming that Telegram deleted the Thread. For graceful leader quit with cleanup disabled, the [lifecycle boundary](./architecture.md#runtime-ownership) supplies completed quiescence and exact session/epoch/profile authority for the same atomic detachment; reload/new/resume/fork never enter it. A failed pre-commit publication retains the owner record; a lost post-rename acknowledgement leaves the committed fact intact. No-op snapshot equality cannot bypass the fenced transition into memory. Missing/ambiguous legacy identity remains protected. Live-owner, accepted-work and delivery protection still apply independently before retirement. Do not derive inactivity from last user message, heartbeat silence alone, snapshot-write time, file age, or any elapsed-time threshold. Legacy records without sufficient evidence have no `inactiveSinceMs` and remain ineligible rather than inheriting an invented old timestamp. Malformed inactivity values are ignored without dropping the binding. Repeated inactive observations retain the first timestamp for pressure ordering; stale same-target upserts cannot postpone it, while target replacement or successful active provisioning clears it.
- Eligibility excludes live or unverifiable owners, exact transient claims, accepted pending/active work, unresolved delivery authority, and pending provisioning, handoff, or cleanup operations. The store can capture one candidate snapshot from bindings, live records, process-local exact claims, reservations, and persisted provision/cleanup intents, but requires the caller to supply separate tri-state evidence for external live ownership, accepted work, and delivery authority. Only three explicit `clear` results plus valid continuous inactivity produce `eligible`; any protected or unknown dimension fails closed. Before future deletion, `workspace-retirement` selects the oldest eligible pressure victim only after all slots are occupied or reserved. It captures the profile and leader epoch, rechecks protection, then persists one durable Workspace retirement intent containing the complete expected binding, pressure reason, leader epoch, and request time. The intent itself protects that binding from competing selection; only the exact captured intent may be excluded during a fenced recheck. Duplicate intent ids, bindings, targets, or letters fail closed. Each new Workspace binding durably accumulates the registration/profile keys used to route its historical follower journals and marks that set complete. An existing legacy binding may learn current keys but cannot claim its unknown historical set is complete without separate discovery proof. Accepted-work policy treats an exact local queued/active target or any unsettled entry in a binding-specific follower journal as protected. Shared leader-journal entries protect only a decoded exact target; undecodable relevant entries, unreadable sources, and incomplete source enumeration remain unknown. The read-only evidence capture resolves the shared leader journal and every recorded follower key; missing resolvers, read errors, or an incomplete legacy key set make coverage unknown. A complete readable capture may prune a known empty follower-journal key only when a separate process/writer check is explicitly clear and the exact binding, profile, and leader epoch remain current; nonempty, unreadable, writable, or unknown keys remain. For incomplete legacy metadata, `journal` can discover canonical profile-exact follower snapshot and segment roots. Matching paths with unexpected/unreadable shape make discovery incomplete; successfully read discovered journals are target-scoped evidence because their one-way filename hash cannot recover the historical routing key. Legacy completeness remains unset, so later retirement repeats discovery rather than forgetting that historical domain. Live composition includes decoded process-birth proof, registry liveness, queue/journal state, and delivery-authority discovery.
- Reclamation deletes the exact inactive Telegram Thread and therefore removes its history; this is distinct from dropping a local label. The leader owns a durable, exact-binding-and-epoch-fenced retirement intent. The store validates and persists this contract, and preparation resumes an exact current-epoch intent after persistence failure or reload. The isolated executor requires both the caller-owned exclusive gate and durable admission ledger. It acquires or exactly adopts the matching fence, rechecks protection after admissions close, persists `deletion-issued`, and invokes an executor-only `deleteForumTopic` port with the sole non-retried permit. Unknown results keep the binding, intent, slot, and issued fence; retries and successors cannot issue again and require exact already-absence proof. Exact parsed Telegram rejection instead follows the cancellation protocol below, retaining the binding and letter. Success or confirmed absence advances to `commit-ready`, then a store-owned commit hides the free slot while persistence is in flight, restores in-memory authority on known failure, and removes binding+intent before exact fence completion. One failure-resilient Workspace operation runtime owns the shared gate for topic lifecycle, reroute restore/reclaim, leader/follower provisioning, delayed post-provision reconciliation, disconnect and confirmed-dead cleanup, rename, and display reconciliation; registration cannot enter the live registry until its gated provisioning completes. Timer-owned mutation never inherits expired registration authority: it reacquires profile admission before cleanup and remains blocked without store/API effects behind either retained fence phase. The executor consumes that runtime's exposed gate rather than nesting another lock. A successor leader cannot execute the old epoch directly. It may durably adopt exactly one intent into its current epoch only when the profile, complete binding snapshot, and fresh protection recheck still match; known persistence failure restores the old intent and request time. Leader composition now exposes external protection from exact live registry targets, active/queued local targets, shared and historical follower journals, and profile-exact discovery for incomplete legacy history. An accepted item without an exact target for the candidate chat remains unknown. Exact JSON and multipart Bot API operations are counted at the shared direct-client boundary through final settlement; a message-scoped edit/delete without thread identity conservatively protects all bindings in its chat. Historical follower owner keys are decoded before process-birth liveness checks. Exact durable intents block matching claims, activation, title/journal commits, and binding upserts, preventing normal mutation from making their snapshots stale. Closed-but-existing topics do not count as confirmed absence. The former process-local TOCTOU is closed in the isolated ledger/executor and production journal/API/mutation/allocation composition, including delayed post-provision cleanup. Fresh leader/follower allocation now retries once after a confirmed pressure retirement. Allocation requests serialize before taking ordinary leases; failure releases those leases before retirement enters the shared mutation gate and acquires its destructive fence. Restore-only follower startup never enters this path. Protected or unverifiable capacity still fails closed. Reuse changes only the letter assignment: routing remains exact numeric target authority, so traffic for the retired target cannot reach the replacement. Recheck eligibility before Telegram mutation and serialize against re-registration. Release the letter only after confirmed deletion or exact already-absent evidence and a successful durable retirement commit. The current confirmed cleanup path records inactivity only after its exact deletion/already-absent result; an unconfirmed API outcome records no inactivity. Unknown API outcomes, failed persistence, owner replacement, and interrupted cleanup retain the reservation and recovery evidence, preventing reassignment or deletion of a replacement target.
- A retired binding no longer promises its former name/letter on reopen; the Workspace may be provisioned anew. Retained message ownership and journal evidence must never route old work through a recycled letter: full binding generation and target remain mandatory independently of display labels.
- Implement and validate using isolated stores and mocked Telegram APIs. Do not delete live Threads, migrate live journals/locks manually, publish, or restart operator instances as part of local preparation. Operator smoke on disposable Threads is a separate gate.

### Demand-Driven Slot Rotation

A fresh leader allocation or authenticated explicit follower registration first attempts ordinary reuse/allocation. Only a typed slot-unavailable result enters pressure retirement; unrelated failures and restore-only follower startup never initiate eviction. The leader serializes allocation requests, releases their ordinary Workspace leases, then holds the shared mutation gate across fresh snapshot capture, retirement preparation/recovery, and fenced execution. A retained retirement is reconciled before admitting another fresh allocation; a `commit-ready` fence left after binding/intent removal can finish without another Telegram request.

The executor validates exact bot/profile, leader epoch, fence owner, binding, target, slot, operation and issuance time immediately before `deleteForumTopic`. This executor-only transport uses the existing client without ordinary target admission, which its own fence intentionally blocks; both API retries and network-family fallback are disabled. Only `true` or a definite HTTP 400 absent-Thread response confirms deletion. A parsed Telegram `ok: false` response with matching HTTP/error codes in `400 | 401 | 403 | 404 | 429`, the exact `deleteForumTopic` method and target, and no confirmed-absence evidence instead proves rejection. The executor durably records `deletion-rejected`, withdraws only the matching retirement intent, persists that withdrawal while retaining the binding, then releases the exact fence. Reconstruction finishes cancellation even after intent removal or lost publication acknowledgements; it does not require deletion eligibility or issue another delete. Every fresh attempt uses a new operation identity, even within the same clock tick. A runtime that does not understand the retained rejection phase cannot recover it; use a supporting build, not manual ledger edits. Transport failures, HTTP 5xx, malformed/mismatched responses and forged status fields remain unknown: they retain the binding and fence without reissuing or assigning the letter. No background timer or elapsed-age threshold performs rotation.

Protection reads use the binding's strict non-repairing journal inspector. Corruption, unsupported platforms/schemas, incomplete historical coverage and unresolved named-profile paths remain unknown, never empty-work evidence. Legacy inactivity timestamps are not fabricated. If full pressure is blocked only by queued work, rotation may inspect candidates oldest-first and invoke the journal's existing exact dead-owner recovery—never raw file or queue clearing. The current binding, leader/profile authority, local queue, live owner and delivery authority must all remain clear; source enumeration must be complete; each relevant entry must form a whole unoffered receipt group for the same target and owner; and every grouped PID plus process birth must be freshly proven dead before the first mutation. The journal rechecks the exact receipt, owner/acquisition, source IDs, handoff absence and liveness transactionally. Any live/unverifiable owner, partial/mixed group, mutation refusal, replacement or unknown evidence stops that candidate. A committed prefix may remain discarded after interruption, but it grants no deletion authority: rotation performs a fresh complete protection capture and ordinary retirement eligibility check before preparing the destructive fence. Unrelated journal work is untouched, no payload is replayed, and restore-only startup never enters this path. Deletion removes **the Telegram Thread and all its messages** as documented by [`deleteForumTopic`](../.agents/skills/telegram-bot/api.md#deleteforumtopic); retirement removes that exact local Workspace binding, not Pi session files, project files, State Flow memory, or durable update journals. Reopening a retired session may create a fresh Thread rather than restoring its deleted history.

### Ambiguous Retirement Recovery Evidence

Production `confirmTargetAbsent` remains unwired until an approved observation can establish exact Thread absence. An empty `editForumTopic` is not that observation: [TDLib `edit_forum_topic` at `ea97bcdd`](https://github.com/tdlib/td/blob/ea97bcdd3a15523c58ddfe772b4547187cf5bbeb/td/telegram/ForumTopicManager.cpp#L565-L592) returns local success when neither name nor icon is changed, before issuing a topic-edit RPC. A successful empty edit therefore proves neither target presence nor absence. Supplying a retained name or icon would instead risk overwriting a later external edit and is not an implicit read.

`sendChatAction/cancel` is accepted by upstream Bot API source but is not in the documented action set and is not a verified existence lookup. [TDLib action handling](https://github.com/tdlib/td/blob/ea97bcdd3a15523c58ddfe772b4547187cf5bbeb/td/telegram/DialogActionManager.cpp#L350-L437) can skip an unneeded action, and its [query handler](https://github.com/tdlib/td/blob/ea97bcdd3a15523c58ddfe772b4547187cf5bbeb/td/telegram/DialogActionManager.cpp#L37-L105) also maps a cancelled query to success. Do not interpret that success as Thread evidence or replace it with a typing indicator during recovery.

An explicit operator-confirmed diagnostic send could provide a real target-bound API outcome, but it can leave a message when the Thread exists or the acknowledgement is lost. Approval of that recovery behavior and control is separate from automatic rotation; it is not implemented and no work is scheduled. Until then, ambiguous or legacy `deletion-issued` attempts retain their fence. Never infer absence from cached client visibility, release because a target is present, retry an unknown deletion, or edit runtime files to force progress.

## Approved Next Contract: Session-Aware Workspace Identity

Status: shipped in 0.52.0 after local validation and the operator's live smoke.

A durable Workspace binding is owned by the selected bot profile plus `{ cwd, sessionId? }`. `cwd` is the normalized exact full path. When present, `sessionId` is the exact non-empty bounded value returned by the public `ctx.sessionManager.getSessionId()` API. Session display names, selector positions, session file paths, process instance IDs, and lifecycle generations are metadata or transient fences, never substitutes for the stable session ID. Absence is a real identity value `{ cwd, ∅ }`, not a wildcard; malformed present values still fail closed.

The persisted binding stores the exact session ID and derives its new binding key from the existing collision-verified Workspace directory key plus a full SHA-256 digest of the session ID. Exact persisted values remain the collision check; the digest is an index component, not independent authority. Reopening, `/resume`, `/continue`/continue-recent, or process replacement with the same `{ cwd, sessionId }` reclaims the same retained target and profile-wide letter when current ownership, accepted-work, admission, and retirement evidence allow it. When target deduplication resolves a provisional claim to an already retained binding, the committed binding's canonical target, slot, and generated name must drive the live record and reported result; provisional metadata must not consume a second letter. `/new` and `/fork` produce distinct session IDs and therefore distinct bindings even in the same directory. `/reload` changes neither component.

Legacy cwd-only bindings remain exact compatibility identities whose absent session component is itself a unique key. They are neither wildcard session bindings nor adoption candidates: session-aware claims never stamp, migrate, consume, or restore them, and instead allocate their own `{ cwd, sessionId }` binding subject to the same global slot and protection rules. `{ cwd, ∅ }`, `{ cwd, session-a }`, and `{ cwd, session-b }` may coexist as majestic degradation for older state and peers. The 0.46 runtime reads strict legacy records as inert independent `{ cwd, ∅ }` bindings but never migrates, adopts, or routes a session through them. Protocol v2 is session-native and intentionally incompatible with protocol-v1 0.45.x peers. Upgrade requires stopping all Telegram instances first, updating them together, and then starting them again; the first v2 process becomes leader with its own session-qualified binding.

Local `/resume` suppresses the same-process source-target handoff. A carried explicit connect intent or established connection starts the destination through normal owner acquisition/follower registration before ordinary auto-restore; it restores that session's exact binding or provisions a new one. Pending Thread-creation evidence records its Workspace binding key. Recovery rejects a different or unproven session key without consuming evidence or repeating creation; legacy pending evidence is reusable only when its target already proves the exact requested binding. Profile/CWD equality and a shared PID never authorize session reassignment. Telegram `/new` remains the separately authenticated, durable continuity exception described above.

Routing authority remains the authenticated live owner plus exact numeric Telegram target. Session identity selects and restores a durable binding but never authorizes message delivery by itself. Slot pressure and retirement continue to operate on exact binding snapshots; they must carry the session component so cleanup, displacement, and letter reuse cannot target another session in the same directory.

## Leader Election

Leader election is liveness-proof-gated and lock-backed. Since 0.54 the leader writes no periodic heartbeat: the transport section changes only on acquire, release, takeover or entry migration, and the owner's one- and two-second ownership checks are lock-free reads. A dead PID is stale immediately. A live PID is replaceable only with bus proof: an authenticated side-effect-free `bus.probe` to the leader endpoint that either stays unanswered for the eight-second window, or finds a refusing endpoint twice one window apart (a just-started leader binds well within it). Any reply, including an older leader rejecting the unknown kind, proves it alive; a missing endpoint proves nothing, because a live classic-mode leader has none. Classic mode therefore never takes over a live PID automatically: `/telegram-connect` asks the operator. Entries written by older releases still carry `heartbeatMs` and keep the eight-second stale rule. The serialized expected-owner transaction, binding the proof to the exact owner epoch, remains the final cross-platform election authority.

1. On startup, read the Telegram lock.
2. If no leader exists, acquire leadership and start polling.
3. If a live leader exists, restore a remembered profile-scoped exact-`cwd` follower binding automatically; otherwise wait for explicit `/telegram-connect` rather than provisioning a new Thread from startup alone.
4. If the leader PID is dead, or its re-registration fails and the bus proves it unresponsive, attempt an atomic leadership takeover; ordinary `/telegram-connect` on a follower is not a leadership move while the leader is live.
5. Heartbeat acknowledgements carry the authenticated live follower-slot roster. If several followers detect stale or unresponsive leadership, the lowest observed live slot attempts promotion immediately; higher slots defer one bounded election grace and re-check the lock. Atomic compare/write acquisition remains the final ownership authority, and a missing lower-slot follower cannot block a higher survivor beyond that grace.

Followers first try to re-register after leader reload or unknown-heartbeat responses, carrying their last known target, slot, and thread name so the new leader can reuse the same binding. Follower Bot API calls already admitted by the active Pi turn wait for that bounded re-registration and capture its new exact generation before entering transport; they do not fail merely because recovery temporarily cleared local registration, and they never replay after an ambiguous transport commit. After the grace window followers promote only when the exact observed leader lease has become stale or inactive; an unavailable IPC endpoint never authorizes replacing a still-live owner. If the exact carried target is absent from persisted bindings, the leader first runs the same synchronous visibility probe: success recovers it instead of creating another Telegram thread, explicit stale evidence provisions a replacement, and ambiguous failure rejects registration. An ambiguous absent-target probe persists only non-routable `probe-required` restoration evidence, so targetless retries, successor follower processes with the same exact Workspace claim, and leader reloads probe that exact target again instead of activating it or provisioning a speculative replacement. A carried slot survives only when that slot remains free. Every successful reuse refreshes the binding timestamp. The leader never restores persisted followers into the live registry speculatively. Absent follower records remain durable restart hints until explicit stale, deleted, offline, or reconciliation evidence invalidates them; only fresh authenticated registration creates live routing authority. This preserves real thread bindings through reload and process-absence gaps without allowing historical records or competing pollers to masquerade as live state.

## Leader/Follower Communication

### Protocol identity and compatibility

The session-native local wire contract has protocol version `3`, independent from the npm package version. Version 3 moved journal source serialization from the `telegram.json` transaction to the journal-owned `runtime/journals.transaction`; because v2 peers would not exclude v3 journal writers, the base version check rejects them instead of mixing lock semantics. Follower registration and the leader acknowledgement carry `{ protocolVersion, runtimeBuild, capabilities }`. Capability names are canonical, unique, and sorted. A leader rejects missing or mismatched protocol identity before provisioning a target or publishing the follower into live routing; a strict follower likewise rejects an acknowledgement without compatible leader identity. Different package builds remain compatible when their protocol versions agree. `durable-follower-admission-v1` gates source forwarding, while `queue-handoff-v1` independently gates live semantic queue transfer; every participant in a routed handoff must advertise it. `workspace-follower-auto-connect-v1` gates restore-only startup admission. Session identity has been part of the base protocol since v2 rather than an optional capability. Every cwd-scoped registration carries the exact bounded session ID; missing identity is rejected before provisioning or publication. Reconnect, replacement, and promotion preserve it. Older-protocol peers are rejected by the base version check, avoiding mixed semantic branches. `thread-display-mode-v1` gates exact-generation follower display-setting requests and Letters/Directories require compatible connected followers; returning to Names allows legacy peers. Registration checks the current display-mode requirement before provisioning and again before live publication. The leader serializes config persistence and title application. `workspace-thread-rename-v1` independently gates follower rename requests whose exact registration generation is checked before the leader mutates Telegram and persists the Workspace binding. `session-replacement-intent-v1` gates Telegram `/new` from a follower Thread: followers never write `state.json`, so `follower.publishSessionReplacement` asks the leader to CAS-publish the durable intent only when the live registration generation, leader epoch, Profile, exact `cwd`, source session, Thread target, and the leader's own Workspace binding slot/name all match, with a bounded unexpired TTL. The intent records the source runtime instance; only a successor registration from that instance or its authenticated same-process handoff (`previousInstanceId`) may re-key the binding, and `follower.settleSessionReplacement` claims it once only after the leader's store shows that successor session bound to the same target. A missing capability, stale generation, or mismatch fails the command closed.

The current root advertises `workspace-restore-v1`; all participating peers and potential leader successors must be upgraded before operator-controlled use. The retired `leader.replaceFollowerTarget` envelope is no longer parsed or produced, even with current authentication and registration generation. Its `leader.workspaceRestore` envelope carries only an operation ID, exact recipient registration generation and `apply`/`inspect` mode; the receiver derives binding and destination from the retained intent, not caller-supplied target data. Authentication and an explicitly enabled receiver are mandatory. The controller checks both peers' capability and protocol compatibility, captures live registration/session/slot/endpoint authority, sends once without transport retry, then validates the request, operation, recipient, observed target, slot and boolean readiness against the current registration. A lost response leaves the durable issuance consumed; a separate read-only inspection can prove readiness without applying again. Neither the controller nor receiver settles source input or writes follower-owned canonical state. A successor registration/process may inspect only its actual same-session canonical binding and local target; readiness records the new observer separately without changing the original issuance. An old local target cannot be repaired through inspection, and successor apply is rejected. Interrupted startup, terminal source/cleanup settlement and live behavior were accepted in the operator's 0.52.0 live smoke.

Negotiated identities remain on the live follower registry and appear in `/telegram-status --debug` plus the observational state snapshot. The Threaded Mode capability monitor owns one in-flight probe across lifecycle generations: stop/restart invalidates a late read, and a replacement monitor waits for the previous request to settle instead of creating overlapping transport transitions. Durable follower admission is authorized only when both peers advertise `durable-follower-admission-v1`, never inferred from package version: a capable runtime rejects missing support before provisioning, inbound routing, or election-roster eligibility. Authentication and exact registration generation remain mandatory independently of protocol compatibility. `follower.register` is the explicit bootstrap request and may provision; capability-gated `follower.restoreWorkspace` is startup-only and may claim, probe, or replace a remembered binding but returns `workspace-binding-unavailable` without allocating when no binding or claim-fenced legacy exact-`cwd` record exists. Both carry a fresh generation; every later request is exact-generation-fenced against the live registry entry. `bus.ack` is response-only and is rejected if submitted as a server request. Leader forwarding never synthesizes authority for an unknown recipient and preserves the follower's exact durable receipt end to end.

Foreign update forwarding returns an explicit `accepted`, `retryable`, or `terminal-rejected` settlement. Acceptance requires an acknowledgement for the exact request whose receipt contains the expected stable `deliveryId` and source `update_id`; a callback error popup never substitutes for that receipt. Missing or negative acknowledgements, stale registrations, absent follower context, binding rejection, journal admission failure, and missing or mismatched receipts all retain the leader source. Message, edited-message, reaction, and callback paths share this contract. The follower remembers each admitted `deliveryId` in a process-local window (24 hours, at most 4,096 deliveries); a repeated delivery inside it, including after its journal entry completed and was removed, is acknowledged with the same receipt without another append or handler execution. This absorbs a leader retry after a lost acknowledgement; it is bounded at-most-once behavior, not exactly-once across recipient restart or window eviction.

The delivery id excludes the replaceable runtime instance and registration generation: it derives from envelope kind, source `update_id`, and the stable manual-follower binding. Stored message ownership carries that binding and may rebind to the current authenticated registration after follower replacement, preserving one retry identity while still fencing each attempt by the current generation. A lost acknowledgement retries with the same delivery identity and becomes accepted only when the exact durable receipt returns. Journal deduplication covers retained entries, not lifetime completion history: after ordinary receipt completion removes an entry, cursorless admission can accept that source again. Stable delivery IDs therefore do not by themselves establish exactly-once execution. Owned local IPC evidence reproduces the gap: a proxy drops actual ACK bytes; real forwarding, authenticated receiving, paired admission, journals and admission workers leave the leader source in retry-wait after follower completion. The next attempt reaches an injected authorized callback handler twice in total, then both journals settle. A queued-message variant likewise re-admits the source under a fresh acquisition after a real worker receipt handoff. This proves repeated handler/queue-admission invocation in the composed local pipeline, not actual Pi queue dispatch, live Telegram effects or model execution. Delivery custody, including role-dependent local replay, must be reconciled before a consume-once guarantee can be claimed; the [design counterexamples](./architecture.md#busjournal-design-acceptance) rule out treating origin disappearance as proof of a specific handoff.

Transport leadership does not own already-queued semantics. Each queued journal receipt binds the acquiring runtime instance, OS process birth, session generation, and acquisition. A replacement leader or follower process sees another live process's receipt as foreign and cannot replay or settle it; the original process may complete its local Pi queue after transport moves. Startup preserves foreign and legacy unowned receipts. Before a replacement admission worker starts, tri-state pid/process-birth proof: an absent PID or mismatched stable Linux/macOS birth identity permits recovery, while a live matching owner stays `alive` and Windows or inaccessible birth metadata stays `unverifiable`; both non-dead outcomes preserve the receipt may transactionally recover a dead owner's complete receipt to pending, after which replacement replay creates fresh queue authority. Registration publishes the replacement's exact pid/process-birth identity first; a concurrent recovery that observes it returns `owner-alive`, while live or unverifiable owners remain untouched.

Live-process handoff combines journal CAS with authenticated bounded bus payloads. A donor-generated one-time token is hashed together with the exact receipt, donor acquisition, and recipient runtime/process/session identity. Offering retains donor ownership but freezes donor completion/discard and dead-owner recovery. Prompt payloads serialize queue data; control payloads serialize only stable `status`/`model` identity and rebuild closures at the recipient. Leader and follower receivers require exact live donor/recipient registration generations, reject malformed/oversized payloads, stage one complete receipt idempotently, accept its journal authority, and acknowledge only that exact receipt plus newly minted owner. Receipts name their source journal binding, while the donor derives the recipient follower-journal binding from the authenticated stable profile. The recipient accepts only that matching active lifecycle. Production advertises `queue-handoff-v1` with this exact role/path composition; legacy-unbound or unavailable bindings fail closed.

As part of staging, the exact recipient presents the token and atomically receives a fresh acquisition carrying the handoff digest; donor settlement is then stale, and the donor trusts only an ACK carrying that accepted owner. Registration carries recipient process-birth and session generation so journal authority matches the live runtime exactly. The coordinator contract shares one ordering across leader→follower and follower→follower paths: offer, route bounded payload, require the exact staged-and-accepted receipt-and-owner ACK, remove donor work, then publish recipient dispatch readiness. Negative or mismatched acknowledgement cancels the still-unaccepted offer and retains donor work. A lost acknowledgement after recipient acceptance cannot revoke the accepted owner; cancellation fails closed and donor memory remains frozen for exact accepted-owner reconciliation. Repeated exact acceptance is idempotent; a different token cannot claim the accepted receipt. Queue authority has no elapsed-time lease: only exact handoff, owner action, or transaction-rechecked PID/process-birth death proof may move it.

### Live-Rebind Channel

The 0.53.0 channel preserved protocol v2 and negotiates live rebinding independently from package version; 0.54 moves the base protocol to v3 without changing these capabilities. Release 0.53.0 advertises the following capabilities; an instance still running an older build does not acquire them until it is reloaded. Compatible package versions alone cannot supply missing capabilities:

- `live-thread-rebind-save-v1`, `live-thread-rebind-apply-v1`, `live-thread-rebind-settle-v1`: Exact recipient save/apply/release and source settlement through the shared channel.
- `live-thread-rebind-command-set-v1`: One `selectedCommand: { name, target }` descriptor and `observe-command` / `command-observed` completion observation, rather than separate status/control wire forms. Retired descriptors and capability names refuse without a compatibility shim.
- `selected-menu-delivery-v1`: Restricted captured-recipient text/menu effects, independently fenced from source settlement; it is not a generic guarded follower API or arbitrary Bot API grant.

Every participating peer must negotiate the required capabilities and retain current authentication, registration generation, Pi context/session, journal binding and exact recipient authority. The wire validates syntax; Commands' captured registry decides availability before hold/append. Supported built-ins and registered prompt templates reuse one plan set across compatible leader selections and held followers; extension commands/groups/legacy registrations do not gain follower execution. Continue keeps its control lane, while expanded templates are ordinary default-lane prompts even when their text starts with a slash.

Semantic reports, queued receipts, completion observation and delivery remain separate. Native exact ACK and donor CAS still own source disposition; lost or mismatched evidence retains uncertainty without handler replay, another queue or fallback admission. This section owns the unified command-channel policy; [architecture](./architecture.md#live-rebind-module-map) owns the module map. Existing Workspace Restore receivers/readers and queue-handoff retain their custody contracts; the new loaded root prefers live rebinding rather than using legacy Restore as an uncertain-input fallback. Source-enabled bounded cleanup is not proof of an actual deletion or client acceptance. Windows runs the same strict paths, exercised by the CI matrix; actual Pi/client acceptance and release are not inferred from supplied IPC fixtures.

### Local IPC endpoint with bounded native paths

Leader opens a local Node `net` endpoint: a Unix-domain socket under the agent temp directory on Unix-like platforms, or a deterministic Windows named pipe (`\\.\pipe\pi-telegram-...`) on native Windows. A filesystem-style endpoint supplied by legacy state or a transport harness normalizes deterministically to the same Windows pipe boundary before listen/connect. On Unix, an endpoint that would exceed conservative domain-socket pathname limits maps to a private user-scoped, hash-derived path under the OS temp directory; ordinary agent paths remain under `tmp/pi-telegram`. On Unix, each server listens on a private generation socket and atomically publishes the stable profile path as a relative symlink; delayed shutdown closes only its private path and cannot remove a replacement generation's link. Followers register, heartbeat, and exchange routed events. The transport boundary owns endpoint derivation, socket-vs-pipe detection, bounded operation-aware retry policy, timeout/transient IPC error classification, endpoint reachability probes, and request-scoped transport events. Follower registration uses a longer registration-specific response timeout than ordinary heartbeat/forwarding calls because the leader may need to provision a Telegram thread before it can return the assigned target; timing out that handshake leaves a visible tab with no follower heartbeat. Keep this handshake to the true critical path: create/reuse the target, persist the live binding, and return it. Connected notices and replaced-thread reconciliation cleanup are non-critical and should run after registration so a follower becomes routable before Telegram client/server UI convergence work finishes.

Pros:

- Natural request/response for sending Telegram API calls through the leader.
- Can route inbound updates to followers while preserving one poller.
- Good fit for live process membership.

Cons:

- Adds IPC lifecycle and security concerns.
- Cross-machine workers need tunneling or a different transport.

Alternative transports such as file-backed mailboxes or an external daemon remain out of the current product boundary. Local IPC is the default internal bus while the public design stays compatible with a future daemon if deployment needs outgrow one host.

## 0.45.0 Disposable Operator Acceptance

Local evidence is not this operator gate: the initial-title/pending-recovery fixes have passing persisted-store/caller regressions and two successful same-model independent PASS reviews of the final recovery guards (`run:telegram-follower-pending-guard-verification`). Both raw reports and terminal branch evidence were inspected. Review scope covers generic recovery, follower shortcuts, and final Workspace settlement; broader cross-session/persistence conclusions remain bounded by the inspected paths and existing tests, not exhaustive fault injection. Model diversity, live-client behavior, and native Windows evidence are absent.

Run this only with an operator-approved disposable bot/profile and disposable Threads. Do not reuse production journals, locks, admission ledgers, accepted work, or retirement intents. Record the package commit/build, Pi version, OS, Telegram client/version, profile name, exact working directories, and test Thread IDs before starting.

1. Start one Pi in directory A, enable private-chat Threaded Mode, and run `/telegram-connect`. Confirm leader ownership, one Thread created directly with the selected display-mode title, the same title in the connected notice and initial Pi status, and no generated-name flash or second polling owner.
2. Start a Pi in directory B and connect it. Confirm follower registration rather than takeover, a distinct globally ordered slot/name, the selected title in creation/notice/initial status without waiting for a heartbeat, exact prompt/reply routing, and no traffic in the leader Thread.
3. Explicitly connect a second Pi from directory A. Confirm it receives a separate binding/slot without copying the first target. Restart each follower independently and confirm restore-only startup reuses its remembered exact-directory binding without allocating a new Thread.
4. Rename the leader and a follower from their respective Telegram Threads with `/name Name`. Confirm each manual override converges in Telegram, Pi status, choosers, notices, and agent-target labels under all three display modes. Reset each override to the current automatic projection; confirm target IDs never change and same-basename directory suffixes remain sticky after a sibling disconnects.
5. From leader and follower Threads, exercise ordinary prompts, callback buttons, one file, and one voice response. Confirm each result remains reply-anchored to the originating numeric Thread and no upload, notice, or final is duplicated.
6. From `All`, create an unbound disposable Thread. Test forward and Replace/restore separately. Confirm accepted content reaches only the selected live instance, Restore carries the selected identity onto the source target, and only the confirmed old/chooser targets are deleted.
7. Replace a follower session, then stop the leader and allow follower promotion. Confirm profile, target, slot, saved name, acknowledged title, accepted queue work, and routing survive without a new Thread or competing poller.
8. With cleanup enabled, run confirmed `/telegram-disconnect` and one session-restart cleanup on disposable Threads. Confirm cleanup intent/API/store settlement completes before transport release; on an induced transient failure, the exact intent/slot remains pending rather than deleting or reusing another target.
9. In the separately authorized disposable rotation scenario, exhaust A–Z with known bindings and request one fresh connection. Confirm only the oldest proven inactive, fully unprotected Thread is deleted, including its messages, before its letter is reused. Verify live/queued/unknown candidates remain intact and restore-only startup never evicts. Elapsed time or heartbeat silence alone must not cause deletion.
10. Capture `/telegram-status --debug`, relevant redacted runtime logs, screenshots of Telegram titles/routes, and the disposable state snapshots after each role/mode transition. Stop immediately on wrong-target delivery, duplicate publication, unexpected deletion, slot reuse, stale-title authority, split-brain polling, or loss of accepted work.

Native Windows must additionally complete the named-pipe and process-lifecycle checklist below. A PASS requires every applicable step with captured client/platform evidence; local tests and mocked Telegram do not substitute for this acceptance.

## Native Windows Smoke Plan

Native Windows support should not require WSL. The baseline transport uses Windows named pipes for leader/follower IPC, but live verification still needs an operator with a native Windows Pi install.

Manual smoke checklist:

1. With Threaded Mode disabled, connect one Pi, attempt a second classic connection, confirm the ownership-handoff prompt, complete takeover, and verify the displaced process loses Bot API mutation authority without losing accepted local queue state.
2. In an explicitly approved disposable agent directory, stop all owners, corrupt only `state.json`, then run `/telegram-connect`: verify one `state-reset` diagnostic, a fresh private envelope owned by the connecting leader, normal polling, and unchanged `telegram.json`/released `tmp/telegram`. Inspect replacement snapshots for complete atomic JSON.
3. Enable Telegram private-chat Threaded Mode for the paired bot.
4. Start Pi in one Windows terminal and run `/telegram-connect`; verify it becomes the leader, gets a named Telegram thread, and publishes a native `\\.\pipe\...` endpoint in explicit diagnostics.
5. Start Pi in a second Windows terminal and run `/telegram-connect`; verify it registers as follower rather than offering takeover, creates/uses its assigned thread, terminal status shows `<ThreadName> Follower` while idle, and a follower prompt flips it to `<ThreadName> Active` while work is running.
6. From the follower thread, send a prompt that requests inline buttons; tap a button and verify the follow-up prompt queues in the follower instance.
7. From the follower thread, request a voice reply and/or attachment; verify upload routes through the leader transport into the follower thread.
8. Force a live Threaded Mode capability downgrade and verify the current leader keeps classic polling while followers disconnect instead of attempting takeover; restore capability and reconnect explicitly.
9. With `🧹 Thread cleanup` enabled (default), quit the follower Pi normally without an explicit disconnect; verify graceful shutdown deletes its current tab through the leader before local suspension. Repeat with a double `Ctrl+C`; if Pi misses the graceful envelope, verify stale-heartbeat recovery deletes the same exact tab only after the OS confirms that follower PID has exited. Disable the setting, quit another follower normally or abruptly, and verify its tab remains as a restart hint. Reconnect, run `/telegram-disconnect`, confirm the prompt, and verify the leader confirms deletion before local polling stops.
10. With `🧹 Thread cleanup` enabled, quit the leader normally and verify it deletes only its own tab before releasing transport; a remaining follower may then promote without recreating the deleted leader tab. If deletion is interrupted after intent persistence, start or promote a successor and verify it completes the exact pending cleanup before publishing its follower endpoint or provisioning its own tab; when the successor has the same stable leader profile and its old binding remains active, it must adopt that binding first and cancel the superseded cleanup without calling Telegram close, delete, or create APIs.
11. Generate enough diagnostics to cross the rotation threshold, reload the leader, and verify `logs.jsonl` and `logs/logs._prev.jsonl` preserve complete ordered records. Confirm ordinary status hides raw pipe internals while explicit debug diagnostics show the active `pipe` endpoint and classified request failures.

If any step fails, capture `telegram-status --debug`, `tmp/pi-telegram/state.json`, `tmp/pi-telegram/logs.jsonl`, and, after rotation, `tmp/pi-telegram/logs/logs._prev.jsonl`. Debug status prints local leader/follower endpoints with their active transport kind (`pipe` or `socket`), while the runtime log records request-scoped transport failures with envelope kind, request id, retry attempt, endpoint, and classified IPC error. Rotation preserves the prior mixed-profile segment as `logs/logs._prev.jsonl` so the evidence is not immediately overwritten.

### Native Windows Assumption Audit

Current portability audit:

- Local bus transport: adapted. Unix-like platforms use filesystem socket paths; native Windows uses named pipes so no POSIX socket pathname is required.
- Bus endpoint permissions: Unix sockets/directories use `chmod`; Windows named-pipe endpoints skip POSIX chmod/unlink path handling because the pipe is not a filesystem node.
- Ownership/config/state/temp files: path construction uses `path.join`/`path.resolve` under the Pi agent directory. File permission calls remain best-effort private-mode hardening; native Windows may emulate POSIX modes, so broad Windows ACL auditing is outside this extension's current local-bus baseline.
- Process liveness: lock ownership uses `process.kill(pid, 0)`, which Node supports on Windows for existence checks. Cross-user permission failures are treated as alive, matching Unix semantics.
- Shell/provider commands: outbound handler command templates remain operator-configured and platform-dependent; Threaded Mode bus portability does not guarantee every configured STT/TTS/shell provider is Windows-native.
- Manual follower identity: process ids are local liveness hints, paired with OS process-birth metadata where available rather than treated as cross-machine identifiers. Linux uses `/proc` start ticks and macOS uses the parent process start time. Fallback generation strings identify a runtime but are not independent death proofs: Windows or inaccessible process-birth metadata is `unverifiable` while the PID remains live. A fresh authenticated session handoff carries the previous runtime identity and exact target through initial registration, allowing fallback identities to migrate without provisioning another thread.

Remaining risk is live native Windows behavior: named-pipe creation/connect timing, antivirus/firewall/ACL interference, and provider command availability need operator smoke evidence.

## Telegram Thread UX

In Telegram private-chat Threaded Mode:

- The private bot chat is a tabbed instance workspace, not a classic `General + threads` forum.
- `All` is an aggregate view, not a process launcher. Explicit new instances use live Pi follower registration: the operator starts Pi in a terminal and runs `/telegram-connect`; owner-created empty threads are observed but not treated as a Pi instance until the user chooses a route or restore action.
- The leader proactively creates or reclaims its own thread on startup/activation when Threaded Mode is available, so the visible leader has the same two-way binding as followers.
- **Unbound thread detection**: when the owner writes in an unknown `message_thread_id`, the bridge checks effective Threaded Mode state. If the current leader has no active bound thread, that new thread is reclaimed for the leader and the prompt is served locally. Otherwise the bridge preserves prompts and commands in that Telegram thread and shows the complete forward plus replace/restore chooser. Successful forward deletes the chooser and confirmed temporary source through `thread-reconciler`; successful restore always deletes the chooser, rebinds the source, and deletes only the selected instance's replaced old thread. Partial foreign batch delivery retries only remaining messages; incomplete thread or chooser deletion retains a cleanup-only retry control without redispatching routed content or leaving an expired visible button.
- Unknown later threads and threadless prompt messages are not silently routed to the leader and never launch hidden Pi processes. The default and only operator path for a new visible instance is starting a visible second Pi process and letting it register as follower through `/telegram-connect`. Returning to the same normalized working-directory Workspace reclaims its persisted Thread after an authenticated process claim and required visibility proof; the dormant binding alone is never routing authority. Explicit stale/deleted observations invalidate that restoration hint before a fresh Thread is provisioned.
- Thread lifecycle service messages (`forum_topic_created`, `forum_topic_closed`, `forum_topic_reopened`, deletion/stale send errors) update observations and binding state. Closed/deleted leader or follower threads can be reclaimed or recreated deliberately. Current same-process targets remain quiet across reload; reclaiming a dormant Workspace target sends the truthful connected notice as its visibility probe, marks a proven stale target deleted, and provisions a fresh Thread. Unknown `forum_topic_created` service events are observation-only and are not destructive cleanup proof.
- Bidirectional binding is a core UX requirement, not an implementation detail: Pi instances actively advertise/remember their thread identity, while the bot observes Telegram-client thread state and reflects it back into instance state. This keeps the system responsive, recognizable, and controllable even when the operator closes tabs, writes from `All`, or a follower later becomes leader.

In Telegram private-chat Threaded Mode:

- The private bot DM becomes the operator's multi-instance dashboard.
- Each live bound instance gets one visible thread.
- Each instance has a durable single-letter slot (`A`-`Z`) assigned by the extension and a bridge-authored `threadName`.
- New slots advance through the alphabet and wrap after `Z` only to a free slot, intentionally capping concurrent visible instances to the alphabet without duplicating occupied letters. The compact `bot.lastSlot` cursor persists while its binding remains live or recoverable, including true `Z → A` wraparound. Pending provisions, reservations, and retained restart bindings occupy their slots until explicit stale/deleted evidence invalidates them.
- Workspace identity is profile-scoped normalized exact `cwd`, independent from process role and lifetime. Readable bounded directory keys are collision-verified against the exact path. Workspace claims reserve a profile-wide letter before asynchronous Bot API work, across both same-directory and different-directory instances; dormant bindings and transient claims reserve their letters. The pure policy uses lowercase `a`–`z`, while the existing transport `slot` field carries the same letter in uppercase. Legacy `instanceSlot` suffixes remain immutable binding-key components, not a second display-slot allocator. Fresh Workspace allocation prefers the first free letter; at full capacity it rotates the oldest proven inactive, fully unprotected binding and retries once. Protected or unverifiable capacity remains blocked. Claims are transient; successful target/name/slot bindings persist as restart hints. On upgrade, an exact-`cwd` leader record or a manual-follower record matching the current/previous authenticated process is claim-fenced into the first compatible Workspace slot, preserving its target, name, and ordering slot without creating a replacement Thread. After a fully cold restart with several dormant bindings for the same directory and no exact session handoff, the bindings are reclaimed in new claim/opening order; process labels or prior terminal opening order do not identify a particular lowercase slot.
- A follower that later becomes leader keeps its Workspace target, uppercase slot, and thread name; leadership changes are transport role changes, not identity resets. Immediately after follower promotion succeeds, the new leader retains a short-lived process-local handoff bound to its exact Telegram profile owner key and refreshes it before session replacement. The replacement session consumes it only after acquiring leader authority, converts any surviving manual-follower record for that target into the current leader binding, persists the Workspace target/slot/name under its exact claim, and only then runs ordinary topic provisioning; neither handoff nor dormant binding is live routing authority.
- Instance-thread names are short and recognizable. Default provisioning chooses an unused baked 4-6 letter single-word Latin name, excluding current bindings, dormant Workspace hints, and in-flight provisions before creating the Telegram Thread. If the assigned slot's five-name palette is occupied, selection continues through the remaining palettes rather than duplicating an identity. The uppercase slot remains separate ordering metadata. Bare slot titles are fallback/legacy state only. Existing human-named Threads are preserved across reloads and leadership changes; a concurrent process in the same Workspace receives the next Workspace suffix and a distinct Thread.
- A thread-local `/start` opens that instance's menu.
- Prompts typed in a thread route to the owning instance.
- Replies, previews, files, voice, and buttons stay in that thread.
- Queue controls and reactions affect only that instance target.
- Telegram's native `…typing` indicator for real agent work is refreshed only in that instance's exact Thread. Aggregate `All` mirroring is intentionally omitted because it duplicates every keepalive against the shared chat and amplifies Telegram flood control. Telegram turns, local prompts, and autonomous continuations all use the exact instance target. Terminal `Active` remains Telegram-turn-specific. Startup/connect/reload/recovery must not send activity by themselves.
- Generic heartbeat pruning remains silent and preserves the thread as a restart hint. With cleanup enabled, a later exact-PID death confirmation may delete it without posting an `Instance offline` notice. With cleanup disabled, a fenced owner-detachment commit records inactivity but retains the Thread and its Workspace slot for restoration or fully guarded capacity rotation. Confirmed deletion removes live routing authority but retains the Workspace binding's friendly name and uppercase ordering slot; a later authenticated reopen probes the old target, replaces it only on exact stale/deleted evidence, and carries that name and slot onto the new Thread.
- If the same binding identity returns, authenticated registration can reclaim the thread after the required visibility proof.

## Bot API Evidence For Private-Chat Threaded Mode

The local Bot API reference in [`../.agents/skills/telegram-bot/api.md`](../.agents/skills/telegram-bot/api.md) supports private bot Threaded Mode through bot capability fields and thread-target transport:

- `User` returned by `getMe` can include `has_topics_enabled` and `allows_users_to_create_topics`; these are the private-chat Threaded Mode capability fields and are the startup/runtime probe source for this extension.
- `createForumTopic` works in a private chat with a user and returns a `ForumTopic`, so the returned `message_thread_id` is persistable as an instance thread target.
- Private-thread management uses Bot API methods such as `editForumTopic`, `closeForumTopic`, `reopenForumTopic`, `deleteForumTopic`, and related unpin methods. Thread-unavailable errors from these methods are degradation evidence when Threaded Mode is disabled or unavailable for the bot.
- `Message` exposes `message_thread_id` and `is_topic_message`; an incoming private-chat message with `message_thread_id` is a live Threaded Mode observation and can trigger progressive upgrade.
- Topic lifecycle service messages include `forum_topic_created`, `forum_topic_edited`, `forum_topic_closed`, `forum_topic_reopened`, `general_forum_topic_hidden`, and `general_forum_topic_unhidden`.
- `message_thread_id` is supported by the send/upload methods the bridge uses or may need: `sendMessage`, `sendPhoto`, `sendDocument`, `sendVoice`, `sendMediaGroup`, `sendSticker`, `sendRichMessage`, `sendMessageDraft`, `sendRichMessageDraft`, and `sendChatAction`.

Non-goal: group detection is not the control-plane model for this extension. Threaded Mode lives in the private bot chat, so startup and runtime switching must not depend on group chat metadata or group admin capability fields.

Remaining live-verification points:

- Callback query messages can be `InaccessibleMessage` without `message_thread_id`; reroute controls use stored source/chooser identity, and ordinary generated buttons retain their message-ownership routing. Client-visible Restore and cleanup still require live smoke evidence.
- Whether message-reaction updates carry thread identity in the current Bot API shape. The reference exposes chat id and message id for reactions, so routing may need stored message ownership.
- Live client evidence now covers the probe-confirmed single-artifact multipart Rich final through both direct leader and registered follower transport: an assigned follower Telegram turn produced one reply-anchored PNG plus final text without a duplicate upload or notice. Deterministic bus tests additionally cover target-scoped multipart authorization, envelope preservation, and replacement-generation fencing.

Implemented behavior stays evidence-gated: when Telegram client or Bot API behavior differs from the contract above, capture a minimized fixture or documented client caveat before changing routing.

## Inbound Routing

The leader polls all updates for the bot token. It classifies each update into a target key:

```text
targetKey = chatId + ':' + (threadId ?? 'private')
```

Then it dispatches:

- If target belongs to the leader instance, handle locally.
- If target belongs to a follower, forward the normalized update/event to that follower.
- If target is unknown but authorized and setup allows provisioning, offer or create a binding.
- If target is unknown or unauthorized, ignore or send a safe denial.

Follower instances receive normalized events, not raw Telegram transport internals where possible. The follower still runs the same queue/routing logic, but Telegram API calls go back through the leader transport port.

## Outbound Routing

Followers do not call Telegram Bot API directly for routed Telegram work. Instead, they call a leader-owned transport port:

Explicit `telegram_message(..., thread)` delivery also uses the bus as an agent-message plane. The leader resolves the case-insensitive name or numeric id against live leader/follower registrations, rejects ambiguous, stale, same-instance, cross-chat, and replayed requests, then coordinates visible Bot API delivery with one source-attributed synthetic turn routed through the destination instance's ordinary queue. The destination sees `[telegram|thread:<destination>|from-thread:<source>]`, not an impersonated user message. Generation fencing and request-ledger deduplication apply to both resolution and routing.

```text
follower reply/preview/upload/chat-action/download/callback-answer -> leader IPC -> Telegram API
```

This preserves one API bus and one set of rate-limit/retry diagnostics. The current local bus routes JSON calls, multipart uploads, chat actions, message deletes, callback/guest answers, and file downloads through the leader when a follower is registered.

Every outbound request carries its target. The leader injects `message_thread_id` when `target.threadId` exists.

## Leader/Follower Capability Parity Matrix

Threaded Mode should make follower threads behave like normal Telegram instance surfaces, with the leader acting only as transport owner. Any feature in the matrix below that works for the leader must either work for followers or have an explicit documented exception.

| Surface                          | Leader behavior                                                                                                                                                                                                                                       | Follower requirement                                                                                                                                                                                                                                            | Routing/ownership invariant                                                                                                                                                                                                                         | Regression evidence                                                                                                       |
| --- | --- | --- | --- | --- |
| Prompt intake                    | Thread prompt queues locally                                                                                                                                                                                                                          | Thread prompt is forwarded and queued by the owning follower                                                                                                                                                                                                    | Target ownership routes by `{ chatId, threadId }` before local handling                                                                                                                                                                             | Routing tests for foreign target message forwarding                                                                       |
| Queued-message removal reactions | 👎/👻/💔/💩/🗑 marks a pending prompt/media turn for deletion when it reaches dispatch                                                                                                                                                                            | Same reaction on a queued follower prompt marks that follower's pending turn for deletion before model dispatch                                                                                                                                                                  | When the leader forwards a prompt to a follower, it records `chatId/messageId -> follower instance` because Bot API reaction updates expose chat/message but not thread id                                                                          | Update runtime regression records forwarded message ownership and forwards the later reaction                             |
| Queue priority reactions         | 👍/⚡/❤/🕊/🔥 prioritizes queued prompts                                                                                                                                                                                                               | Same reactions prioritize follower queued prompts                                                                                                                                                                                                               | Reaction forwarding uses stored message ownership, then follower mutates its local queue                                                                                                                                                            | Reaction mutation tests plus forwarded-reaction coverage                                                                  |
| Message edits                    | Edits update matching queued prompt text                                                                                                                                                                                                              | Edits in a follower thread update that follower's queued prompt                                                                                                                                                                                                 | Message target ownership forwards edits to the owning instance; stored message ownership is the fallback when Telegram edit payloads omit thread id                                                                                                 | Update routing tests for foreign target and message-owned edited-message forwarding                                       |
| Callbacks/buttons/menus          | Callback handled by the owning instance/menu state                                                                                                                                                                                                    | Follower callbacks are forwarded to the owning follower; follower menu sends/edits/deletes route through leader transport                                                                                                                                       | Leader records ownership for follower-sent Bot API messages so callbacks can route by message id even when Telegram omits thread id; Bot API edit/delete lacks thread id, so follower bus allows validated same-chat message operations             | Callback forwarding, generated-button target, bus follower-sent ownership, and bus edit/delete allowlist tests            |
| Replies/finals                   | Final replies land in the same thread                                                                                                                                                                                                                 | Follower finals go through leader transport into follower thread                                                                                                                                                                                                | Outbound calls carry target and inject `message_thread_id`                                                                                                                                                                                          | Reply delivery and bus API tests                                                                                          |
| Previews/Rich Drafts             | Draft previews use the active thread target                                                                                                                                                                                                           | Follower previews use the same native draft lifecycle through the leader                                                                                                                                                                                        | Preview transport preserves target and draft id                                                                                                                                                                                                     | Preview thread-target tests                                                                                               |
| Attachments/voice                | Files and voice upload in the instance thread                                                                                                                                                                                                         | Follower uploads route through leader multipart transport                                                                                                                                                                                                       | Multipart calls are target-scoped and follower-authorized                                                                                                                                                                                           | Bus allowlist and outbound delivery tests                                                                                 |
| Native activity status           | `sendChatAction(typing)` refreshes Telegram's native `…typing` indicator only in the assigned Thread for every agent run, including local and autonomous work; aggregate `All` is not mirrored, terminal identity remains `connected`, and active work stays included in the green Queue count | Followers route the exact Thread action through leader transport while retaining their stable terminal `follower` identity and green activity count; the leader admits one concurrent action per chat and shares Telegram 429 cooldown across that chat's Thread keys                                                                                       | Agent lifecycle targets the active Telegram turn when present and otherwise the instance binding; one keyed loop avoids duplicate aggregate sends/rate-limit pressure                                                                               | Agent-start binding, typing-loop, target-routing, and terminal-status regressions                                         |
| Leader election / promotion      | Current leader keeps its thread across reload                                                                                                                                                                                                         | A promoted follower keeps its existing thread, slot, and name when elected and after later reload                                                                                                                                                               | Promotion converts the current follower binding into the leader profile before forced lock acquisition, so leader startup reuses it instead of provisioning a new thread                                                                            | Follower heartbeat recovery passes binding snapshot into promotion; own-topic provisioner reuses promoted bindings        |
| `/start` command/menu bootstrap  | Registers visible bot commands and opens the menu                                                                                                                                                                                                     | Follower `/start` can refresh the bot command menu through the leader and open its local menu without warnings                                                                                                                                                  | Bot command registration is a validated global Bot API call allowed through trusted follower bus transport                                                                                                                                          | Bus allowlist regression for `setMyCommands`                                                                              |
| Follower reconnect               | Existing leader binding is reused only when still usable                                                                                                                                                                                              | Same-process `/new` or `/reload` suspends the old follower socket/context and automatically re-registers the new session to the exact prior target; explicit reconnect to a genuinely closed/stale Telegram tab still recreates a visible thread before success | A short-lived handoff carries the assigned target across session replacement, and the leader transfers that binding to the new runtime instance id by stable manual-follower identity; stale Bot API errors remain the proof for fresh provisioning | Session handoff/refresh, leader binding-transfer, persisted leader-reload reuse, and stale-target replacement regressions |
| Unbound thread reroute/restore   | Prompt- and command-created temporary threads expose forward plus replace/restore; forward deletes the chooser/source, while restore deletes the chooser, rebinds the source, and removes only the replaced old thread                                  | Same complete chooser exposes all currently live bus leader/follower targets; restore is offered from concrete unbound threads, not historical snapshots                                                                                                                 | Live bus roster plus active target bindings define the selectable set; history/state snapshots are not authority                                                                                                                                    | Routing chooser regressions for live target filtering and restore rows                                                    |
| Status/menu diagnostics          | Status reflects leader role, queue, and target                                                                                                                                                                                                        | Follower status reflects follower role, thread name, queue, and bus health                                                                                                                                                                                      | Status is local runtime truth plus bus registration state, not leader queue state                                                                                                                                                                   | Status and bus diagnostics tests                                                                                          |

## Queue And State Scoping

Each instance owns its own queue and active turn state. The leader does not become a central queue scheduler for all agents; that would be a separate daemon-mode architecture.

Target-scoped state requirements:

- Queue item identity includes target plus source message id.
- Reply deduplication is keyed by target, not just chat id.
- Preview draft state is keyed by target.
- Button callbacks store target and owning instance id.
- Reactions resolve to target/instance before mutation.
- Attachments generated by a follower are uploaded by the leader into the follower's target.

## Configuration

There is no public `telegram.json` switch for the bus. Telegram private-chat Threaded Mode is the runtime switch: when Telegram exposes threads for the bot, the bridge enables the local bus; when Telegram runs as an ordinary private DM, the bridge uses classic private-chat flow as the base mode.

Typical config remains just bot identity and authorization, stored in the canonical default profile:

```json
{
  "profiles": {
    "default": {
      "botToken": "...",
      "allowedUserId": 123456789
    }
  }
}
```

Named bots use sibling `profiles.<name>` entries. Shared bridge settings remain top-level.

Rules:

- Classic mode is selected by Telegram capability: when private-chat threads are unavailable or disabled, the polling owner uses ordinary single-DM behavior and blocked instances do not register as followers. During a live downgrade from Threaded Mode, the current bus leader becomes the classic polling owner after two 2.5-second capability-monitor probes and followers disconnect; if classic polling restore fails transiently, later monitor ticks retry the restore instead of allowing a follower takeover. Followers must not turn the downgrade into a takeover while active thread bindings prove the singleton owner was already established by the bus leader. A follower that loses its leader reads the leader-persisted `threadMode`; when it is `disabled`, the follower halts recovery and goes offline with one diagnostic instead of retrying registration, keeping its Thread binding. It stays offline after Threaded Mode returns until the operator runs `/telegram-connect`. A deliberate local stop halts recovery the same way, and `/telegram-disconnect` without a live leader still stops locally, reporting the Thread as kept rather than deleted.
- Telegram private-chat Threaded Mode enables local leader/follower behavior automatically. The leader owns `getUpdates`; registered followers route Telegram API work through the leader. `/telegram-connect` registers as follower when a live leader exists and does not offer manual takeover in that state. The TUI status bar keeps `telegram connected`, `telegram leader`, or `telegram follower` as stable transport identity during work; active work and queued items share the green Queue count instead of replacing that identity with an `active` processing label. Follower registration is unique by live profile/target: a reload or session replacement must replace stale registry entries rather than leaving multiple routable ids for one Telegram thread, and fallback target ownership must not classify leader records as followers.
- The thread chat is the owner's private bot DM (`allowedUserId`); no `topics.chatId` config is needed. Thread names are assigned by the bridge from a baked compact per-slot palette. There is no agent-facing `telegram_rename_thread` tool and no separate user-facing slash command for manual thread renames.
- Thread reuse is extension-owned through profile-scoped Workspace bindings; there is no separate `topics` config surface in the active private-chat thread model. Leader/follower record keys remain live role projections, while normalized exact-`cwd` bindings preserve identity and deterministic same-directory process suffixes across role and process changes.
- Thread cleanup remains conservative and centralized: destructive close/delete actions are planned and applied through `thread-reconciler` with proof-before-delete checks, leader-epoch fencing, and retry-preserving failure semantics.
- `allowedUserId` remains the primary authorization boundary unless explicit allowlists are added. Forum/group membership alone must not grant control.

## Runtime State

Current state under the agent dir:

- `tmp/pi-telegram/state.json` `profiles.<profile>.transport`: authoritative transport owner for `default` or a named profile, holding the bus leader identity, capability secret, generation, cleanup fencing epoch and polling-journal pointer. Mutations serialize through `runtime/state.json.transaction`; followers never write it. The local bus endpoint is derived from the agent directory; a `busSocketPath` field is tolerated but not required.
- `profiles.<profile>.workspace`: canonical Workspace/thread state; `profiles.<profile>.runtime`: volatile observable/debug projection, never routing authority, with `source: "snapshot"` and `writtenAtMs`. Every process on one profile reads this shared file, but only the active transport owner may publish either section; followers become writers only after promotion. Runtime publication never touches the canonical section, so a stale status writer cannot erase leader records. The `runtime` section mirrors `/telegram-status`-style projections: `runtime` identifies leader/follower role, lifecycle activity, and the exact polling phase/progress snapshot; `liveRoster` mirrors followers/current targets/reservations; `diagnostics` mirrors status/debug signals. The `workspace` section keeps top-level `bot` capability state such as `threadMode: "unknown" | "enabled" | "disabled"`; `threads` stores current routeable bindings; `workspaceBindings` stores dormant exact-`cwd` target/name/slot reuse hints; `bot.lastSlot` stores the compact slot cursor used when all current threads are gone; and `reservations` records short-lived slot collision guards.
- Local bus endpoints: Unix-like platforms expose stable `tmp/pi-telegram/runtime/bus.<hash>.sock` (leader) and `runtime/f.<hash>.sock` (follower) symlinks backed by colocated private generation sockets, with bounded private external shortening only for over-long paths; native Windows uses deterministic named pipes under `\\.\pipe\pi-telegram-...`. These are transient IPC endpoints, not durable routing state.

If an unclean host shutdown damages `state.json`, the next non-election leader start replaces it with an empty envelope and continues (see [Damaged-State Reset](./architecture.md#damaged-state-reset-operator-approved)). Every profile's runtime continuity is deliberately lost; `telegram.json`, released `tmp/telegram` and unrelated extension data remain untouched. No section-scoped recovery is planned.

The bridge must not keep a separate durable `telegram-targets.json` history. Profile-specific `state.json` retains exact-`cwd` Workspace bindings as restart hints, but they never authorize routing without a matching authenticated process claim and role-appropriate liveness/visibility proof. Stale/offline/failed observations are not reusable delivery authority. `sync` remains event-driven assumption reconciliation rather than a full Telegram bot-state mirror because Bot API exposes no complete thread listing surface. Non-current routeable thread bindings are pruned during load/persist; old session records must not be retained merely to drive allocation. Workspace allocation uses retained binding/claim reservations; `bot.lastSlot` remains a compatibility cursor only for provisioning without Workspace identity. Previous-process leader bindings are treated as occupied TTL-bounded reservations until Telegram confirms deletion: reload/startup may close/delete/probe the old thread, known reservations are retried proactively on leader startup, and if Telegram still accepts the old thread id, the new leader should provision the next free slot (`B`, `C`, …) rather than creating a duplicate same-letter tab or blocking startup on Telegram UI convergence. Routing must use live current threads/follower registry, never reservations. The bus leader provisions its own thread during bus startup/connect and provisions follower threads on `follower.register`; registered followers also live in the leader's in-memory registry and communicate over the local bus socket. The live follower registry can resolve a follower by exact `{ chatId, threadId? }`; the leader uses that target ownership to forward message and edited-message updates to followers, and the follower receiver accepts those updates in addition to callbacks and reactions. Terminal status and `[telegram|thread:name]` resolve the matching current-instance identity through the same target-aware path, preferring registered local metadata over stale shared bindings. Media album grouping and split-text coalescing keys include the thread target, queue reaction mutations can scope by chat/thread to avoid cross-target message-id collisions, active-turn target is exposed for lifecycle cleanup and local direct-tool defaults, transport reply dedup is chat/thread-scoped, stored menu state is keyed by chat/message so callback state lookup cannot collide across chats, and generated button turns plus section prompt/open actions preserve the callback thread target. `telegram_message` and immediate `telegram_attach` delivery can also carry an explicit `thread_id` with `chat_id`; when a follower is registered, their default direct-tool target is the assigned thread target and the bus-aware API runtime routes the send through the leader instead of calling Bot API transport locally.

All files containing routing, chat ids, thread ids, or process details use private permissions and represent current state rather than historical target caches.

## Failure Modes

### Leader exits cleanly

- Leader stops polling and marks itself offline.
- Follower heartbeats go unanswered and the released or dead owner is stale.
- One follower promotes itself after jitter/tie-break.
- New leader resumes `getUpdates` from the persisted offset if safe.

### Leader crashes

- Followers see a dead PID, or prove a hung leader unresponsive over the bus.
- One follower promotes itself.
- Some updates may be delayed or skipped depending on offset persistence; dispatcher design must define this explicitly.

### Follower heartbeat is missed

- Leader tolerates up to 15 seconds without a follower heartbeat before pruning it from the live registry, so short local event-loop or IPC stalls do not create false routing gaps. Follower heartbeat requests use a dedicated eight-second response deadline aligned with leader stale-liveness policy rather than the generic one-second local-RPC default; leader-to-follower Workspace Restore and live-rebind control requests share that window, so a follower busy with journal work or a slower Windows named pipe is not a lost recipient. The deadline also defers its final timeout through one socket poll phase: an acknowledgement already buffered while the Pi/TUI event loop was blocked wins, while a genuinely silent peer still fails in the same event-loop turn. Heartbeat pruning remains liveness bookkeeping. One leader generation owns at most one prune operation; stop makes late endpoint, policy, and cleanup settlement inert, while durable-profile mutation serialization prevents replacement registration from crossing confirmed-dead cleanup.
- A missed heartbeat does not delete, close, mark offline, or send a disconnected notice for the follower's Telegram thread binding because the common cause may be leader reload, IPC handoff, or transient reconnect rather than a dead follower.
- After removal from live routing, the current leader retains at most 26 volatile preservation observations with exact registered PID, generation, target, profile and leader epoch. The existing prune loop rechecks alive/unverifiable PIDs and retries non-destructive publication only after fresh explicit absence and a known disabled-cleanup policy. Missing identity or overflow stays protected. Observations never answer heartbeat/API/forwarding requests, launch processes, or authorize deletion. Enabling cleanup cancels deferred preservation rather than promoting it into destructive work; the existing first-prune cleanup path is not retried by this mechanism.
- Registry registration invalidates overlapping instance/profile-owner/target observations, even if that replacement is removed before the next prune. Admitted same-instance provisioning cancels the old observation before its store writes can precede live registration. Profile/epoch drift, registry clear and leader stop invalidate it; late old completion cannot erase a replacement runtime's observation. No observation survives leader restart, reconstructs a PID from an opaque instance ID, or invents legacy inactivity.
- Each unfinished preservation retains one admission operation ID across sequential attempts, so a lost acquisition acknowledgement reuses and releases the exact lease instead of leaking another. A rejected pre-rename write leaves the owner intact; a committed write with lost acknowledgement is recognized without rewriting first inactivity. A liveness interruption is retryable, not successful settlement. Every attempt reacquires profile admission and the shared mutation gate, rechecks authority at rename, and preserves accepted work. Cancellation or scope loss never guesses away an unresolved admission lease; existing exact lease/dead-owner recovery remains authoritative.
- Followers treat rejected/missing heartbeat acknowledgements as registration loss: retain the last known target locally, clear registered truth, show `reconnecting` instead of a healthy `follower` status, try to re-register with the current leader, wait a short leader-reload grace window, and retry. They promote only after the exact leader lease becomes stale or inactive; a live owner with an unreachable endpoint leaves the follower disconnected/retrying rather than creating a competing poller.
- Persisted current manual-follower bindings survive abrupt process absence as restoration hints when Thread cleanup is disabled. When enabled, graceful Pi quit requests exact-generation teardown before lifecycle suspension; if that envelope is missed, stale pruning may delete only after the leader's OS confirms the exact registered PID has exited.
- Fresh registration sends one compact connected notice in the assigned thread. An exact immediate session handoff uses a target-scoped `sendChatAction` as its synchronous visibility probe, avoiding a duplicate notice while retaining stale/ambiguous recovery; other cross-session restoration keeps the connected notice as its probe.
- Registration requires a present generation, and explicit disconnect requires that same exact live generation. Leader-side registration and disconnect mutations serialize per durable follower profile across old and replacement runtime instance IDs, so a replacement registration cannot overtake awaited destructive cleanup and an old disconnect cannot remove its successor's routing authority.
- Successful forwarded updates and follower-originated API calls refresh liveness, so active followers are not pruned only because the interval heartbeat tick lagged. Each follower lifecycle owns at most one in-flight heartbeat for its exact registration generation; stop/replacement makes late settlement inert, and recovery or diagnostic failure cannot escape as an unhandled interval Promise.
- Destructive follower thread teardown belongs to confirmed `/telegram-disconnect`, graceful Pi quit, or confirmed reconciliation actions, not generic heartbeat pruning. Manual disconnect retains its destructive confirmation and clears restart ownership; quit deletes the tab without prompting when Thread cleanup is enabled (default) but preserves the owner slot independently so a same-directory restart can reclaim leadership. Confirmed leader/follower teardown first persists an exact target/runtime-generation cleanup intent. The active leader attempts deletion under its current epoch; interruption preserves the intent so that leader or a successor can replay it under current authority, and confirmed deletion removes the binding plus intent in the same persisted state transition. If the graceful request is missed, stale heartbeat plus OS-confirmed absence of the exact registered PID may authorize the same cleanup while enabled; this action serializes ahead of replacement registration. Disabled cleanup, silence, heartbeat expiry alone, IPC/auth failure, and live or unknown process liveness remain non-destructive. Incomplete cleanup preserves durable intent for retry. A promoted leader uses its current owned leader epoch even when the inherited record still carries a historical `manual-follower` owner label.
- Explicit stale/deleted/offline observations invalidate reuse. Process absence affects reuse only through the enabled, exact-PID confirmed-dead cleanup path.

### Stale thread delivery

Local regression evidence covers continuity and cleanup authority; operator-coordinated live Restore verification passed in the operator's 0.52.0 live smoke.

- Direct replies, menus, activity, target-aware edits, and multipart transport capture the request target and local authority before sending. Exact typed HTTP 400 stale-thread evidence stages invalidation of only the matching unchanged binding. The thread store rechecks leader/session/profile authority, binding identity, snapshot revisions, and destination path at the synchronous owner-fenced rename; no staged invalidation enters the live projection before durable commit, and a rejected commit preserves newer state.
- Shared recovery marks topic, target-binding, and transport freshness suspect and schedules a diagnostic snapshot. Accepted local work and its active-turn target remain unchanged; a failed send is not replayed or redirected, and recovery does not create a replacement thread or probe on every send. Errors without a proven request target do not authorize invalidation.
- Reconnect and explicit Restore remain separate authority-bearing operations. A stale API response proves target failure, not who deleted or closed the thread.

### Restore controls and cleanup

This section records the conservative pre-0.53.0 runtime that Workspace Restore keeps for continuity with older instances. The 0.53.0 [live Thread rebinding channel](#live-rebind-channel) replaces native-reader parity research with one same-session workflow. Its explicit historical-protection reduction is limited to the new live cleanup origin, not temporary-tab cleanup, retirement, session adoption or legacy journal proofs.

Operator-confirmed client flow: send ordinary text from the **All** tab; Telegram creates a new thread containing that text. Choose **Replace/restore thread…** and the existing Pi instance. Successful leader Restore binds and renames the new thread, dispatches the original message to the same Pi, and removes the old thread. The resulting prompt's thread-name label reflects the restored destination, not proof that the user typed in the old thread. A threadless `/start` is not equivalent to this ordinary-text flow.

Unselected All-tab command choosers expire 60 minutes after the original Telegram message timestamp. Expiry reports terminal settlement only for the exact still-deferred update and makes its callback inert; it does not execute the command or discard an accepted queue receipt. A valid destination selection pauses expiry during dispatch and suppresses concurrent repeat clicks. Failed attempts return to the original deadline without extending it, matching the replay expiry policy after restart. Successful local command dispatch settles a still-deferred source without waiting for background menu delivery; an accepted Pi queue receipt retains its own settlement contract. Follower command forwarding retires a still-deferred source only after confirmed acceptance; thrown or retryable transfer outcomes retain it. Expired replay completes without recreating the chooser. Worker stop invalidates pending chooser authority even without a usable timestamp. The expiry timer rechecks and rearms against its original deadline after backward wall-clock changes. Missing/invalid timestamps are not guessed, and `/thread`, bound-thread messages, ordinary prompts, and classic mode do not use this policy. Journal-write failure preserves the source for recovery; an expired visible chooser may remain, but its button cannot route the command. At pending capacity, new chooser admission fails retryably rather than silently evicting unresolved All commands.

For repeated unselected All-tab `/start`, a newer durable source supersedes older exact-text equivalents only after its chooser message ID is returned. Coalescing requires the same chat, user, and active admission worker; different arguments, other commands, and previously selected intents are not combined. Superseded chooser messages may remain visible, but their callbacks are inert. Storage failure retains the source according to the worker's fail-closed settlement contract. Restart may recreate a chooser for a still-unexpired source using currently routable targets; it does not automatically forward that command into a restored thread. This does not purge historical Telegram messages or promise that no fresh chooser appears after restart.

The bounded pending reroute owns its original source target and the returned chooser message ID independently from remaining messages. Authenticated callbacks must match the stored chat/chooser and any supplied thread field. `InaccessibleMessage` may omit the thread field; a missing callback message, unknown chooser, conflicting identity, threadless source, or already-owned Restore source fails closed rather than forwarding to the old target. Restart or expiry without that pending identity does not authorize reconstruction from callback data alone. When the original input itself has no thread ID (including an All-tab `/start`), the chooser offers routing only, not Restore; old Restore callbacks explain that a plain message must first be sent in the destination thread. Client tab selection is not inferred from recent topic creation or from the chosen Pi instance.

After dispatch, cleanup retries retain source identity but never redispatch accepted messages. Confirmed typed HTTP 400 `message to delete not found` completes message deletion idempotently; permission and transient failures remain errors. Reroute cleanup rechecks current bindings, live targets, reservations, and pending provisions before close, before delete, and on retry. Other destructive cleanup origins use the shared synchronous target-protection policy: explicit retirement permits only its unchanged departing binding, persisted shutdown intent permits only its original pre-intent binding, and reservation/provision cleanup permits only the corresponding unchanged claim. Protection checks also guard post-API local invalidation, reservation, and disconnect completion. A newly protected target cancels remaining cleanup. Already-issued remote operations cannot be undone by a later local ownership change; checks prevent subsequent effects, not retroactive cancellation.

Follower Restore uses only the source-bound `leader.workspaceRestore` protocol described under [protocol identity and compatibility](#protocol-identity-and-compatibility). The follower validates the retained operation and exact registration/session authority under read-only canonical snapshot protection; it never persists leader-owned state. Lost apply replies require inspection, not target replacement or another apply grant. Legacy target-replacement requests are rejected before recipient effects.

### Restore settlement ordering

**Approved ordering and conservative 0.52.0 policy; acceptance, scoped removal ACK, cold hint continuation and queued readiness composed, retention/interruption/startup acceptance still open:** Separate acceptance-proof publication from asynchronous cleanup. The worker still commits a queue receipt or removes a completed source before notifying routing of source disposition; routing then re-enters Workspace admission to publish the Restore settlement. Follower Restore now publishes positive forwarding acceptance before reporting source completion, then co-publishes an immutable acceptance-scoped ACK with hash-guarded journal removal. Failed acceptance publication retains the original; later disposition-publication failure leaves a queued receipt or retained forwarding acceptance and scoped removal ACK. Cold canonical-settlement recovery now handles exact forwarded, completed-command and queued acceptance scopes; queued readiness remains separate from receipt-owned terminal disposition. Native full-capacity `settlement-publication-interrupted` fixtures exercise both cases: cold journal/snapshot reads preserve the relocated binding and issued grant, worker-cache restart can republish its own queued receipt, and unrelated callback completion cannot turn retained evidence into settlement when the strict scope-reader port is absent. Those fixtures keep recovery unwired and prove conservative protection. Separate cold-store/native-journal/IPC fixtures below exercise the new scoped-ACK continuation.

The native Restore store now exposes `recordSourceAcceptance` on an already-issued routing grant. Its optional, nonempty `routing.acceptances` records one proof per original: exact journal binding/update ID, SHA-256 of the worker's captured journal entry, accepted recipient identity, and either positive local completion, authenticated forwarding delivery ID/binding, or committed queue receipt/kind plus SHA-256 of its exact owner. Runtime publishers must validate the positive result and compute these hashes from journal-owner evidence, never a routed message projection. Storage validates scope, hash/receipt shape, current recipient and full executor/operator CAS; it cannot establish the truth of a caller-supplied execution result. The sorted source-unique collection rejects duplicate delivery identities and conflicting same-receipt queue owners/kinds/recipients. An exact duplicate is read-only, including revision/timestamps. Before/after-rename fault fixtures prove retained sources and exact lost-reply observation. Executor adoption preserves proofs. Cold parsing rejects malformed or contradictory acceptance/settlement evidence without repair; the existing byte bounds cover this optional field.

Acceptance alone never counts as terminal source settlement, grants cleanup, removes journal input, publishes a follower-owned snapshot, or permits dispatch replay. Canonical `queued` settlement facts retain admission only and cannot grant cleanup or retirement, even after source absence or a clearance hint. Only `queue-completed` records positive receipt-owned disposition; it requires matching retained queued acceptance and exact receipt/kind. It may upgrade matching admission IDs while preserving unproven siblings, but cannot duplicate terminal facts, change receipts or downgrade completion. Cold parsing rejects queued admission with cleanup and terminal receipt facts without acceptance, retaining the file without repair. Older writers reject the new terminal tag; every possible peer/successor must upgrade before live Restore. `recordSourceSettlement` rejects outcome/receipt mismatches; legacy admission without acceptance remains nonterminal. A read-only follower view may observe an exact duplicate proof but cannot publish a new proof or source disposition. Follower Restore composes acceptance before its completion report and publishes terminal settlement only after its journal ACK. Leader command completion now uses the same pre-disposition publisher through a private routed carrier; receipt-owned terminal disposition acceptance remains open; the approved terminal proof lifetime is immutable bounded retention below. Exact forwarded/completed/queued source-disposition recovery is composed below.

The implementation must use this order:

1. Validate the worker's exact original, journal binding, current execution claim and positive outcome. Retain the source while publishing a bounded acceptance proof into its already-issued Restore operation. Distinguish recipient acceptance or committed queue admission from acknowledgement of source removal; neither readiness nor a report alone is a removal ACK. No second sidecar or general custody migration is required.
2. Dispose of the exact completed source through the journal owner only after that proof is durable. A queued source keeps its existing owner/receipt lifecycle. An interrupted publication leaves the original protected by the issued Restore grant; a lost publication reply reconciles only the exact retained proof. Do not repeat dispatch to obtain another result.
3. Recover interrupted source disposal only from the exact retained proof plus strict source identity/transaction checks, without invoking the message handler. An absent source without that proof remains unknown. A changed source, foreign receipt owner, conflicting result or expired runtime authority must not be silently removed or marked complete.
4. Continue cleanup separately, under fresh profile admission and existing accepted-work protection, after the journal owner acknowledges source disposition. The synchronous completion-report path must not await the existing cleanup observer while holding the dispatch Workspace gate: that observer reacquires the same gate. A queued receipt must not start downstream dispatch before its required Restore proof is published.

Follower forwarding now uses `updates.inspectTelegramDeferredSource` through the original source's hidden admission carrier. The live worker requires its exact owner/signal, journal binding, deferred claim, context and process/session identity, reads the journal afresh, compares the full captured entry, then returns its hash. A changed, queued, missing, reported or stopped source cannot supply proof; a routed message projection is never hashed. Routing validates the positive delivery ACK against that original ID and the exact current recipient binding/generation. It publishes acceptance under the still-held Restore dispatch admission, without reacquiring the gate, before calling `reportTelegramUpdateCompleted(original, expectedSource)`. Admission preserves a detached guard through immediate and late completion; a duplicate ordinary report cannot downgrade it. The worker rechecks journal binding, context and captured process/session identity, then calls the separate `journal.removeCompletedExact` capability. There is no ID-only fallback when that capability is unavailable. Under the existing source serialization and journal transaction, every supplied hash must match the full current parsed entry; missing/changed sources reject the entire batch before publication. Even an exactly hashed queued or failed entry retains its existing receipt/operator-disposition protection. Ordinary non-Restore completion still uses its existing path. A failed publication or mismatched response leaves the original pending and accepted recipient work intact. A lost rename reply reconciles only the identical retained proof under current authority. Source-removal/observer failure retains that proof; neither a re-click nor unrelated completion repeats forwarding or invents a removal ACK. Native full-capacity fixtures cover these publication/removal boundaries, including an unqueued original changed after durable acceptance: hash-CAS retains it without a settlement ACK, repeated forwarding or cleanup. Native source-family tests verify lock ordering, whole-batch rejection, cold reads and absence-as-conflict. This proves composed exact-source disposal and proof-before-report timing; successor registration and operator acceptance remain separate gates.

The prepared journal primitive now accepts an optional third `removeCompletedExact` argument: source completions `{updateId, sourceSha256, completionSha256}`. Each completion requires a matching exact source guard and is co-published with removal in the same journal revision; it is not a pre-removal intent. The opaque completion hash must bind the caller's immutable Restore request/operator/acceptance and exact source scope, not a mutable executor lease; journal storage cannot prove the truth of supplied execution acceptance. `sourceCompletions` is retained in the existing v1 journal snapshot/segments, without a sidecar. Cold segment replay validates that new ACKs match the removed predecessor entry in that revision, and retained ACKs cannot change or disappear. Compaction and ordinary publishers preserve them. Active/discarded-source contradictions, duplicate IDs/scopes and foreign hashes fail closed. Retained markers prevent admission replay and empty-entry identity rebinding, including same-bot token rotation. Count, byte and actual source-work bounds apply; capacity never evicts older ACKs or removes the new source.

The prepared `completeQueuedExact(receipts, completions)` is a separate strict v1 capability, not an overload silently ignored by ordinary/v3 adapters. It reuses whole-receipt owner/process validation, requires unoffered complete receipt groups and matches each requested marker against the current full **queued** entry hash before disposal. Matching scopes co-publish with removal in the same revision; any source/owner/group/hash/capacity conflict retains the entire batch. A scope subset does not permit partial receipt removal or grant evidence to unscoped siblings, whose ordinary semantics stay unchanged. Cold segment continuity permits queued ACKs only when every predecessor group member is removed without reintroduction, with matching owner/kind and no offer; grouped validation is cached once per receipt. Strict binding composition supplies this capability from the private serialized sibling, while the raw custody surface omits it. Lost replies are observed with `inspectSourceCompletion`, never by a second disposal. Ordinary `completeQueued` still publishes no scoped ACK. The worker's explicit `completeQueueReceipts(..., sourceCompletions)` API still requires every source in the requested batch. Cached prepared subsets may now cover fewer sources only after a captured strict private v1 inspector confirms the complete queued receipt/full owner and each scoped queued-entry digest before readiness. Receipt membership is immutable for that owner/acquisition, and native ACK publication/cold continuity requires complete group removal. Each requested scoped receipt needs an origin witness: matching retained queued-source proofs then acknowledge whole-receipt disposition, never source absence or a marker for an ordinary sibling. Its captured exact disposer and strict reader verify both the returned ACK collection and retained evidence, with binding/context/process/session checks before memory acknowledgement. Required scopes remain sticky after failure; an ordinary retry cannot downgrade them. Issuance is retained in that worker before the disposal call, so an uncertain reply permits only exact read-only reconciliation, not another disposal. Missing proof protects all local claims; changed batch authority and worker stop/start cannot borrow that attempt. Native grouped/multi-receipt fixtures cover these boundaries without handler replay. Production queued acceptance now returns those immutable scopes to the barrier, which detaches and retains them before readiness and rejects missing terminal capabilities. Lifecycle callers use the retained scopes without minting their own. A captured post-ACK receipt observer emits only a routing hint, never Pi dispatch; canonical terminal publication re-enters admission and rereads exact evidence. Completion-only mux owner selection permits an issued attempt's read-only reconciliation after readiness is lost, while foreign context/reason/owner/binding stays blocked. Subset scopes require confirmed whole-receipt queued origin before readiness; missing inspection, partial/foreign origin or changed authority holds all sources. Any issued scoped disposition, including a pre-write exception, closes execution readiness and permits only completion-only exact proof reconciliation. Mixed batches of independent whole receipts now settle a scoped group first and an ordinary group separately, rechecking current owner/context/process/session/binding between them. Positive component ACKs survive another component's failure; mux/runtime retries reuse exact acknowledged receipt objects for local cleanup only, without restoring readiness or repeating disposal. Ordinary sources gain no scoped marker and retain ordinary unknown-disposition protection. Scoped post-ACK authority loss suppresses a stale worker wake. Native grouped/discard/lost-ACK/readback/publication fixtures prove this composed boundary, with Pi lifecycle handoff supplied rather than executed. Native subset-origin/multi-receipt/mixed/captured-reader and uncertainty fixtures prove this boundary without replay; actual interrupted startup was accepted in the operator's 0.52.0 live smoke; proof consumption is outside 0.52.0.

`inspectSourceCompletion(expected)` is a read-only exact observation under existing serialization/transaction and strict private source-handle validation. It returns only matching retained evidence, throws on another scope, and never repairs, quarantines or treats source absence as completion. New stores can observe a lost-removal reply without invoking disposal again. Neither marker publication nor inspection is exposed on the prepared v3 custody surface. Follower Restore now derives `completionSha256` from a domain-tagged, recursively key-sorted immutable request/operator/retained acceptance frame; executor, revision, timestamps and mutable progress are excluded. Admission validates and detaches the optional scope hash, preserves it across ordinary duplicate reports, and rejects conflicting scopes. The worker snapshots exact disposal/inspection capabilities, requires a reader before issuance, passes detached markers as the third removal argument, validates the returned collection and strictly reads each retained ACK before notification. Binding/context/process/session authority is checked again after publication and inspection. Missing, altered or foreign replies/reads produce no observer ACK; lost replies never retry disposal. Production v1 binding composition serializes ordinary access and exposes a strict private completion sibling, preserving ordinary recovery semantics. Cold continuation now uses this same exact evidence under fresh admission; the approved terminal lifetime is bounded immutable retention, with no marker expiry or release.

Completion and authenticated recipient-heartbeat observations now select ready, unissued-cleanup follower Restores with retained forwarding acceptances. The hint itself never settles a source. Under fresh profile admission, the controller derives each immutable scope and queries the exact active journal through a held `operator-disposition` reference. Missing or foreign proof cannot even adopt the executor. Positive evidence permits exact executor adoption plus authenticated `inspect` on the current same-session/CWD/slot/target follower; no handler, forwarding, apply or disposal runs. A changed registration generation is recorded only after that read-only canonical observation. After the await, each proof is read again before publishing only its matching unsettled IDs. Partial evidence cannot release missing originals. The captured live-recipient fence remains active through cleanup awaits and publication; unknown/issued cleanup never replays. Interrupted canonical settlement publication resumes on a new hint from the same retained ACK, without source mutation. Native cases cover missing/conflicting/unreadable/foreign-binding evidence, ended authority, post-await read/registration changes and cleanup uncertainty. They supply successor registration and clear accepted-work protection as explicit preconditions, not actual startup or recipient-work clearance proof.

Leader command Restore now binds `updates.bindTelegramUpdateCompletionAcceptance` only to its routed messages, preserving their execution fence without mutating the original shared worker binding. Positive complete reports publish source-hashed `completed` acceptance under the held dispatch admission before the original completion report can reach disposal. The shared publisher rechecks current leader/session/target/source and exact retained proof; lost rename replies reconcile only that proof. The carrier detaches and memoizes its scoped evidence, rejects conflicts or missing scope, and does not run for deferred/queued outcomes. Native `/start` temporary-tab Restore proves once-only local execution, acceptance-before-removal, scoped ACK, retained source on failed publication, and unchanged tab/slot fate. Production All-command routing supplies the prepared stores and owned leader epoch; local fixtures do not establish actual Pi startup or Telegram-client acceptance. Ordinary commands including `/new` still use their existing post-removal lifecycle observers.

A source-journal completion hint now also selects a ready leader Restore with retained completed or queued acceptance and unissued cleanup. Fresh admission and strict scoped ACK queries precede executor adoption. A read-only canonical observation requires one exact active binding/session/CWD/slot/target and one current leader owner with matching instance, CWD and profile key; local target equality alone cannot re-key the operation. The existing inspect-only controller preserves the original recipient/grants and observes the same-session current leader without applying identity, invoking a handler or sending follower RPC. After its await, immutable proofs are read again before settling only their proven originals. Live fences remain in storage authority callbacks; canonical observations stay outside the snapshot transaction and are repeated through cleanup awaits and before retirement. An owner change after close retains issued cleanup rather than deleting or retiring. Exact already-retained local readiness needs no republication. Native cold cases cover same-instance/successor continuation, partial/missing/foreign evidence, wrong local session/CWD/target/owner, post-await proof/recipient changes, both canonical publication rename boundaries and unknown/issued cleanup, preserving source bytes throughout. Queued ACK continuation uses the same pre-adoption proof and canonical-owner fences. After recipient inspection it rereads unproven scopes and publishes `queue-completed`, grouping only matching receipts/kinds; admission-only, missing or partial proof cannot release another original. Native single-/multi-receipt cases preserve issued grants and source bytes across canonical publication faults, with or without a prior admission fact. Producer execution, successor startup/ownership and accepted-work clearance are distinct preconditions; these fixtures do not prove actual interrupted Pi startup. Scoped ACKs still never expire, evict or release.

The worker now has a prepared optional `beforeQueueReceiptPublished(receipt, owner, ctx, isCurrent)` barrier. Exact durable queue admission retains the original, but neither cached readiness nor its downstream observer publishes until the asynchronous acceptance publisher succeeds. Publication requires a captured exact receipt-inspection capability before and after the await, with current process/session/context/binding checks. Receipt and owner arguments are detached; grouped reports and concurrent snapshot refresh share one pending publication. Same-process cold receipt reconstruction uses that barrier without running the original handler. Callback failures hold queued work; readiness observers remain diagnostic after successful publication. Ordinary commit failures still release deferred claims synchronously. `waitForDrain` covers in-flight publication. Serialized production journal bindings now compose `inspectQueuedReceipt(expected)` with their existing strict private sibling and derive `isQueueReceiptCurrent` from the exact active recovery key. The read-only v1 observer requires every canonical ordered source ID, receipt/kind, full owner/acquisition and absence of a handoff offer. It returns detached source digests of the retained **queued** originals plus normalized-owner SHA-256; those digests are not pre-queue pending hashes or removal ACKs. Query mismatch yields no proof; malformed, corrupt, foreign or unprepared evidence throws without repair, rebinding or source mutation. Inspection uses lock-only journal source serialization, not writer admission, so observing exact accepted work does not borrow persistence permission. Native fixtures cover whole groups, owner/acquisition mismatch, offers, retained byte continuity, cold/detached proof, post-await completion/corruption and read-only authority. The production worker now wires routing's `beforeQueueReceiptPublished` publisher. It selects only retained operations overlapping that exact active-journal receipt, then reacquires fresh profile Workspace admission; ordinary receipts remain a no-op for Restore policy. A selected operation must be ready with its issued routing, exact current executor/operator and leader recipient. Canonical read-only binding/owner checks and live session/CWD/slot/target establish recipient authority before each `queued` acceptance. Exact receipt/source and owner-hash evidence is retained in the existing operation before readiness. Storage's authority predicate checks only live fences to avoid reacquiring its held snapshot transaction; canonical CAS plus pre/post snapshot reads preserve binding protection. Lost publication reply reconciles only an identical retained request/executor/operator/proof. After publication the publisher re-reads the whole receipt, and after admission returns it rechecks captured recipient guards; changed proof or authority suppresses readiness without rolling back accepted local queue work. No apply, prompt enqueue, deletion or cleanup is repeated by this publisher. Same-process worker reconstruction can republish only acceptance from its still-exact receipt; it is not actual interrupted Pi startup or a foreign-session successor grant.

Native full-capacity leader Restore cases exercise positive queued publication, both rename sides, a forged returned intent with no proof, missing/unreadable receipt or a changed source digest on proof reread, authority loss, recipient change before publication/after the proof reread/after admission, and same-process worker reconstruction. Each original stays queued under its durable owner, exactly one prompt is accepted, and uncertain acceptance never publishes readiness or triggers cleanup. A native two-original media-group producer now exercises the same contract through its real unbound-group debounce, shared chooser/Restore selection, journal and worker. At full A–Z capacity, it relocates the same binding/slot and enqueues one combined prompt with one full receipt. A failed or unproven second acceptance retains the first proof but holds the entire receipt; lost post-rename reply reconciles both. A changed second digest on reread or an actual native whole-group handoff offer withholds readiness without undoing accepted work. Same-process worker reconstruction completes partial proof, preserves the first acceptance byte-for-byte, and emits one readiness observation without journal mutation or another enqueue/apply. Duplicate acceptance publication leaves canonical bytes unchanged. These are grouped prompt fixtures, not all remaining control/local outcomes or actual Pi startup acceptance.

The private local-command carrier distinguishes queued admission from completed handling. Once it reports a valid queued outcome—even if its durable commit later fails—a trailing implicit command completion neither invokes the completed acceptance publisher nor reports source disposal. An explicit source-disposal guard in that state throws rather than downgrading receipt authority. The shared original binding and ordinary non-Restore `/new` lifecycle stay untouched. Native temporary-tab `/continue` tests use real Workspace admission, strict serialized journal observation and the prepared queue barrier: one control-lane continuation prompt remains receipt-owned, publication faults hold readiness and same-process reconstruction resumes only acceptance. Its tab remains bound and temporary source custody is retained until actual queue terminal settlement, not merely readiness. `/compact` is deliberately different: it opens the existing confirmation dialog, publishes completed acceptance before scoped removal, and cannot open another dialog on Restore re-click. The confirmation's later callback is an independent input, not the original Restore source.

#### Terminal Proof Lifetime Boundary

A scoped journal completion ACK has two independent consumers: exact Restore disposition readback and journal admission anti-replay. Canonical operation retirement does not consume either journal authority. The current lifetime is bounded, immutable retention: segment continuity rejects erasure/rewrite, compaction carries ACKs into the snapshot, matching old input remains duplicate, and even an empty executable journal cannot rebind its bot/profile identity while proof remains. Count, byte and source-work limits refuse new publication while preserving the original and older ACKs; they never expire or evict evidence. An accepted polling cursor is not a recipient replay barrier. Metadata-only address pruning does not alter source files, and independent Restore/retirement/temporary snapshots are not its consumption roots. Approved optimistic session sweeping and guarded damaged-state deletion are separate accepted storage-loss policies, not completion or reusable proof-release grants. The operator approved this existing bounded immutable retention policy for 0.52.0. Capacity exhaustion is an accepted availability limit: preserve the new original and older ACKs, refuse publication, and never reset proof or replay accepted work to regain capacity. No terminal-proof consumption API is composed or required for 0.52.0; a future release protocol must separately prove retained anti-replay protection. Actual startup was accepted in the operator's 0.52.0 live smoke. Existing journal tests cover compaction, replay, identity and capacity boundaries; supplied host/registration fixtures do not establish actual Pi startup.

#### Protected Historical Originals And Cleanup

The operator-approved 0.52.0 policy for protected unsupported/non-text historical originals is retain-only: keep exact bytes, do not invoke their handlers again, and do not dispose of them through generic Cancel or raw journal removal. A clean pending record or missing local receipt is not positive non-admission proof; recipient admission may precede a lost ACK. Classification uncertainty must not fall through to ordinary dispatch or grant source exemptions. These unresolved sources continue protecting affected cleanup. The removed Historical inputs UI stays removed; unsupported-source cancellation/release is deferred until a design proves the exact source, whole owner/group membership and a positive permissible disposition. The operator also approved retaining every eligible raw unsupported historical private-Thread original on bound Threads, not only sources with surviving Restore/temporary membership. New-world forgetting therefore cannot release those originals to startup dispatch: each worker generation installs a new retain-only claim without reconstructing forgotten intent or adding durable witnesses. Supported cold spending, live arrivals, business/forwarded and known owner-bearing paths, queued ownership and follower custody remain unchanged. Native regressions cover both membership paths after relocation/forgetting and ordinary bound unsupported startup originals; writer closure is not claimed, while actual startup and live cleanup were accepted in the operator's 0.52.0 live smoke.

Keep fresh canonical binding and all required predecessor/current journal references for cleanup; nonempty unclassified/shared, incomplete or unreadable evidence remains protective. The old-target observer unions both legacy keys and exact session/recipient address pairs from the immutable request and fresh canonical binding; equal recipient keys in different sessions are not duplicates. Strict journal reads carry physical presence separately from entry count: an absent required binding family is unknown, not present complete-empty proof, even when a fresh namespace census succeeds. Native follower fixtures recheck this union after close-await metadata pruning and preserve relocation, recipient custody and issued cleanup when predecessor evidence disappears or becomes nonempty/unreadable. Queue-owner witnesses publish real prompt/control receipts and whole-group offers in disposable native journals: binding-owned groups remain protected irrespective of source target, while nonempty shared/discovered groups remain unknown rather than exempt. Predecessor pruning and a new canonical successor cannot hide those groups; original bytes, owner/acquisition, offered custody, relocated binding/slot and independently accepted recipient work stay exact after later hints. These are local journal/router/worker and strict resolver/reference composition checks, not actual Pi startup, writer closure or follower-custody activation; actual-host behavior was accepted in the operator's 0.52.0 live smoke. The temporary-tab adapter uses the journal owner's bounded strict complete-empty namespace guard under source serialization and per-family references. It requires present exact current/historical source keys, inspects the owners-named polling source plus retained flat/session families, and grants no target-based exemption to nonempty shared or unclassified work. Native new-world and last-Cancel fixtures prove missing/corrupt/unclassified/reference-refused sources block cleanup, while present complete-empty evidence permits the existing one-shot path; cancellation keeps exact private originals and forgetting does not reconstruct intent. These fixtures and composition inspection do not prove writer closure, actual Pi startup or live deletion. Acceptance, exact journal-owner terminal disposition (whole receipt for queued work), and old-tab cleanup are separate results. A successful relocation can retain its binding while cleanup remains blocked; do not roll back or redeliver to force deletion. Warm continuation uses only the same retained intent, exact ACK and fresh canonical recipient identity: lost apply replies permit inspection, lost disposal replies permit exact readback, and issued/unknown cleanup never repeats. Recheck authority and evidence after every await. Cold restart, including reload that creates a new runtime instance, follows new-world forgetting without rollback, not warm intent reconstruction.

Native both-role/full-slot warm close-await checks now change context, session generation, epoch or canonical binding while `closeForumTopic` is awaited. A separate follower registration replacement exposed a source-hint gap: source settlement now also checks the acknowledged ready follower's exact generation/session/CWD/slot/target and negotiated capabilities before storage publication and after awaits, without borrowing heartbeat authority or reacquiring a snapshot lock inside storage CAS. The close may already be issued, but the next delete is refused; cleanup remains issued, accepted originals and an independent receipt stay exact, and regained authority, duplicate hints or re-clicks cannot repeat apply, delivery or cleanup. These are native journal/worker/router/IPC fixtures with supplied host and cleanup-clearance ports, not actual Pi startup, writer closure or live transport acceptance. The native both-role/full-capacity fixture now also interrupts disposal before publication or loses its post-publication reply, then continues with the same retained intent and a fresh same-runtime recipient generation. Leader whole-receipt completion and follower forwarded-source removal both preserve exact immutable scoped ACKs; continuation holds a source reference and fresh profile admission, inspects canonical successor identity and rereads proof after the await. Missing ACK or canonical identity blocks continuation; missing post-await proof or lost context withholds terminal settlement, and a later valid hint reads the same ACK without source mutation or another disposal. Re-click may independently inspect readiness without an ACK, but cannot turn readiness into disposition. Native independent whole receipts/originals keep cleanup protected, recipient custody and all other bindings remain exact, and apply/delivery/disposal never repeat. These fixture generations are not actual Pi startup/adoption or interrupted Restore host sequencing. Ordinary production follower-registration owner/epoch and context/generation races are locally proven separately; they do not close Restore's actual-host gate.

Current direct prompt Restore always passes through prompt queue admission; it has no separate unqueued completion producer to wrap. The local queued-owner protection checkpoint is verified for prompt/control/group/offered custody; native warm disposal/readback and close-await boundaries are locally proven, while actual interrupted Pi startup/registration and Restore host sequencing remain separately gated. Test interruptions before proof publication, after publication but before source disposition, and after source disposition but before cleanup; preserve independently accepted recipient work at every boundary. `/new` and ordinary non-Restore worker completion must retain their existing post-removal semantics. Live delivery was accepted in the operator's 0.52.0 live smoke.

#### Remaining Host Acceptance Boundary

Inspection of `lib/extension.ts` confirms that admission completion and receipt observers feed the existing routing settlement owner, follower registration/heartbeat observations supply recipient hints, and scoped proof reads hold `operator-disposition` references. The follower Restore receiver captures authenticated leader/profile and actual context/session-generation authority through `lib/bus-follower.ts`; it rechecks after admission and snapshot loading. `lib/lifecycle.ts` publishes the new context generation before composed queue/delivery/polling startup and follower refresh. Historical worker preparation separately invokes `forgetPreviousWorld` under captured transport authority. This is wiring evidence, not an executed host-ordering witness or a new runtime correction.

The production cold-start integration fixture seeds a previous-instance relocated intent, invokes supplied Pi hooks and proves forgetting without rollback. The both-role/full-capacity routing fixtures instead keep the same intent and supply warm generation/recipient changes around real journal/IPC effects. Ordinary production registration races hold creation, not Restore settlement. Combining those results does not establish actual Pi shutdown/startup ordering, successor preparation or inbound readiness. No additional supplied-hook permutation is selected as a substitute.

Close this remaining gate only with a separately authorized actual Pi host run on isolated storage; real followers are operator-started, never hidden replacement processes:

- `Identity and ordering`: Record exact build, platform, role/profile, runtime instance, Pi session/context generation, registration acknowledgement and journal binding. Correlate actual host lifecycle events with source preparation and the first ready recipient observation; a successful connect/send alone does not prove inbound readiness or Restore continuation.
- `Warm continuation`: Within the same retained runtime/intent, observe interruption and fresh successor readiness at the acceptance/disposition/cleanup boundary. Correlate immutable acceptance, exact scoped ACK readback, whole-receipt protection and fresh canonical references with no repeated apply/delivery/disposal or issued cleanup. Preserve independent accepted work; do not manufacture another effect to recover missing proof.
- `Cold replacement`: A new runtime instance must forget unfinished intent without rollback and retain protected originals/bindings; do not require adoption of the forgotten operation. Record this separately from warm continuation and from actual `newSession` storage succession. Both roles/all occupied slots, real multi-process/profile continuity and native Windows/macOS remain distinct acceptance dimensions; 0.52.0 covered them through the operator's live smoke and the release CI matrix.

This inspection authorizes no reload/restart, live-state mutation, fault injection or release. Before any separately approved live restart, let the queue drain or knowingly accept losing waiting work. Missing host access or approval leaves the gate open; existing native evidence remains valid within its stated scope.

### Split brain

- Two leaders calling `getUpdates` is the main safety failure.
- Lock takeover must be atomic enough to prevent this under normal local concurrency.
- Persistent competing `getUpdates` clients trigger a full transport stand-down even when the local lock still appears owned; the stopped runtime preserves accepted local work and releases only its exact ownership. See [Runtime Ownership](./architecture.md#runtime-ownership) for the threshold, diagnostic, and reconnect contract.

## Security Boundaries

- Messages, edits, callbacks, and reactions check user authorization, not only chat/thread membership.
- Followers authenticate to the local leader IPC with a leader-minted capability secret carried in the active lock entry; registration, heartbeat, forwarded updates, and follower API calls without the secret are rejected. Registration rejections are surfaced verbatim in the follower `/telegram-connect` result, registration waits through leader-side Telegram thread provisioning, and successful registrations send an immediate heartbeat before the interval ticker so the leader does not prune a live follower before its first scheduled heartbeat. The local bus socket is also created under a private `0700` directory with `0600` socket permissions as a first local-only boundary.
- Follower Bot API proxying is allowlisted and target-scoped where applicable. Ordinary sends remain confined to the follower's assigned thread; trusted runtime-marked `telegram_message` cross-target sends may address only a different thread inside the same paired chat, and the leader strips the internal marker before calling Telegram. This preserves requested inter-thread delivery without granting arbitrary bot or cross-chat control.
- Button and section callbacks verify authorized `from.id` and owning target/instance.
- Generated artifacts stay scoped to the owning thread after leader failover.
- Diagnostics redact bot tokens, large prompts, attachment paths, and handler output.

## Acceptance Criteria

- [x] The lock semantics are redesigned as Telegram bus leadership with liveness proof and stale takeover rules.
- [x] A first-class `TelegramTarget` can represent classic private chats and thread destinations.
- [x] The bridge can run in classic mode with unchanged private-chat behavior.
- [x] A live Pi instance can register as a follower when another live instance is leader.
- [x] Followers never call `getUpdates` for the shared bot token.
- [x] Followers can send replies, previews, voice, attachments, menus, and chat actions through the leader transport.
- [x] The leader can route inbound messages, edits, callbacks, reactions, media groups, and split text to the owning instance by target. Message/edit and callback/reaction routing is authorized by user id; media and split-text coalescing are target-keyed locally.
- [x] Telegram UI thread targets can be provisioned as current state bindings; stable manual-follower identities reclaim current bindings across process restart, authenticated registration generation gates routing, and stale/deleted observations or explicit disconnect/reconciliation remove unusable bindings.
- [x] Leader failover promotes one remaining follower without creating competing pollers.
- [x] Queue, active turn, preview, reply deduplication, menu, section, button, reaction, and attachment state are scoped by instance/target. Queue reaction mutations and transport reply dedup are chat/thread-scoped; active-turn target is available to lifecycle cleanup; stored menu state is chat/message-keyed; generated button turns and section prompt/open actions preserve callback targets; preview and attachment delivery already carry targets.
- [x] Authorization prevents arbitrary Telegram users or local processes from controlling agents or receiving artifacts.

Open live client and native Windows evidence gates for future behavior belong in `BACKLOG.md`; this architecture document records the implemented contract, not the active smoke queue.

## Implemented Shape

- Bus semantics are the feature frame: Telegram threads are one Telegram UI substrate for a local multi-instance bus.
- `TelegramTarget` and target-key helpers represent classic private chats and thread destinations.
- Outbound ports, previews, replies, voice, attachments, chat actions, menus, sections, buttons, queue mutations, and direct local delivery carry target metadata where needed.
- The transport lock distinguishes live bus leadership from ordinary classic ownership through leader epoch, liveness proof, and stale takeover rules.
- The leader records live follower registration, heartbeat, thread identity, slot, and target mapping; followers do not poll `getUpdates`.
- Local IPC is the default internal bus. Registered followers receive normalized inbound updates and send allowlisted, target-scoped Bot API calls through the leader.
- Thread targets are current-state bindings, not historical delivery addresses. Stable restart hints require a fresh authenticated follower registration before they become live routing authority; stale/offline/failed entries remain reconciliation evidence only.
- Failover promotes a remaining follower after dead or clean-disconnected leaders without creating competing pollers; follower heartbeat recovery owns re-register → grace → promotion while preserving thread bindings across transient leader reload gaps.
- Thread cleanup is centralized in `thread-reconciler`, fails closed without a leader epoch while leadership exists, revalidates that epoch immediately before every close/delete call and local cleanup-state mutation, and requires confirmed delete/stale evidence before state is marked deleted. Manual leader and promoted-leader disconnect persist cleanup intent first but always stop polling and release the owner lock even when Telegram cleanup is incomplete; the successor replays the retained intent, so a Bot API or epoch failure cannot pin leadership. Manual followers still require a live leader acknowledgement for destructive teardown.
- Stable docs/UI now describe classic mode, opt-in Threaded Mode, manual follower registration, status/diagnostics, unbound-thread reroute/restore UX, and operator recovery boundaries.

## Evidence Gates

Open live/client questions belong in `BACKLOG.md` until confirmed. Capture confirmed quirks as focused regressions or documented caveats, not broad speculative matrices.
