# Thread Bookkeeping with Pin & Archive **Status:** runbook (2026-10-04) **Applies to:** muse.ai side-chat threads used by the fleet DM system ## Why bookkeeping matters Side-chat threads are addressed by UUID. UUIDs go stale (deletion, account-side cleanup, platform reissue — observed 2026-10-04 when `heartbeat-opm` changed UUIDs in a single day and when the hardcoded `1e75a740` "646 tasks" UUID died ~90 minutes after being committed). Stale UUIDs cause silent misdelivery: the SPA redirects to an arbitrary valid thread instead of 404ing, and the old placement-blind verify claimed success. Duplicates compound the problem. On 2026-10-04 the user observed two `BRAIN-INIT` messages, indicating two brain threads where one was expected. Fuzzy sidebar name search then matches the wrong thread. Pin and archive are the native muse.ai mechanisms for managing this. They are gateway API methods (`/api/session/pin`, `/api/session/unpin`, `/api/session/archive`, `/api/session/unarchive`, `/api/session/delete`, `/api/session/rename`), **not** CDP browser actions. Our `muse-chat-api.py` (CDP-based) does not implement them. ## Tooling: muse-cli The supported path is the third-party `muse-cli` tool (`nikships/muse-cli`), which speaks the gateway protocol (Noise XX + protobuf) directly. It is **not** installed on bl, the VM, or the operator container as of 2026-10-04. ### Install (on bl) ```bash # Node.js required npm install -g muse-cli # or per-repo install per muse-cli README ``` ### Auth muse-cli needs a `hatch_sess` cookie from a logged-in browser session. Obtain it from the operator's own browser profile — never from another agent's session. Treat the cookie as a credential: transient use, never logged, never in chat. ### Commands (per muse-cli PROTOCOL.md) ```bash muse-cli pin # pin a thread muse-cli unpin # unpin a thread muse-cli session-archive # archive a thread muse-cli session-unarchive # restore an archived thread muse-cli session-rename # rename a thread muse-cli session-delete <session_id> # delete a thread (destructive) ``` `<session_id>` is the thread UUID (the hex string in `https://muse.ai/thread/<uuid>`). ## When to pin a thread Pin threads that are **critical infrastructure** or **frequently accessed during triage**: | Thread | Alias / reuse_key | Why pinned | |---|---|---| | main-loop brain | `main-loop brain` | Fleet coordination point | | 646 tasks | `646 tasks` | 646's primary work thread | | heartbeat | `heartbeat-opm` | Pipeline health signal | | 646-pip-coord | `646-pip-coord` | Cross-operator coordination | | muse tasks | `muse tasks` | muse's primary work thread | Pinning keeps these at the top of the sidebar, which: - Prevents fuzzy-name-search confusion (searching "646" is less likely to match the wrong thread when the right one is pinned and visible). - Makes important threads easy to find during incident triage. - Does **not** change the UUID — pin is purely a display/ordering operation. `job-sidechats.json` mappings are unaffected. ## When to archive a thread Archive threads that are **stale, duplicate, test, or dead**: - **Duplicates:** the double `BRAIN-INIT` incident — archive the surplus thread, keep the registered one. - **Stale UUIDs:** threads whose UUID no longer resolves (e.g. the dead `1e75a740` if it still exists on 646's account). - **Test threads:** `test-auto-prov` (`b96dd020`), `pipe-7d2896`, `pipe-1b4579`, and other autoprovision experiments. - **Superseded threads:** old threads replaced by a renamed or re-provisioned canonical thread. Prefer **archive** over **delete**: archiving is reversible (`session-unarchive`), deleting is not. Delete only when certain the thread holds nothing worth recovering. ## Listing pinned vs archived threads muse-cli's session listing distinguishes state. Our CDP-based `sidechat list` scrapes visible sidebar text only and **cannot** distinguish pinned/archived/deleted threads — another reason to use muse-cli for bookkeeping operations. After any pin/archive operation, verify with the list command and confirm the expected state before updating mappings. ## Integration with job-sidechats.json `job-sidechats.json` (on bl) is the dynamic UUID registry: `{ "<alias>": { "thread_uuid": ..., "agent": ..., ... } }`. Rules: 1. **Pin changes nothing** in `job-sidechats.json`. The UUID is the same; only sidebar ordering changes. 2. **Archive the registered thread → update the mapping.** Either remove the entry (so the next send autoprovision a fresh thread) or point it at the surviving canonical thread's UUID. 3. **Archive a non-registered duplicate → no mapping change needed**, but note it in the commit message for traceability. 4. **Never hardcode UUIDs** in `SIDCHAT_ALIASES` or elsewhere (lesson from 2026-10-04). Use name-based autoprovision with the dynamic registry. A hardcoded UUID is a ticking time bomb: it *will* go stale, and there is no alert when it does. ## Case study: duplicate brain INIT (2026-10-04) **What happened.** The user observed two `BRAIN-INIT` messages in the UI, indicating two brain threads where the design calls for one. The brain thread (`5f18476d-8994-49e7-a9e0-4838732363fe`, created 19:40 UTC on opm's account with placement confirmed) was never registered in `job-sidechats.json`, so 646 reported it as "still pending" and "unwired" while a second INIT appeared. **Contributing factors.** - The brain thread was created but the registration step was skipped. - Without registration, later code paths could not find the canonical thread and created (or appeared to create) another. - No pin/archive pass had ever been run, so there was no canonical "this is the one true brain" signal in the sidebar. **How archive fixes it.** 1. Identify both thread UUIDs (from dm-log `alias_resolved` / `nav_ok` records or muse-cli session list). 2. Confirm which UUID is registered in `job-sidechats.json` under `"main-loop brain"` — that one is canonical. 3. `muse-cli session-archive <duplicate_uuid>` on the surplus thread. 4. Pin the canonical brain thread so it stays visible. 5. Commit the `job-sidechats.json` state with a note referencing the archived duplicate. **Prevention.** After creating any canonical thread: register it in `job-sidechats.json` immediately, pin it, and note the UUID in the commit message. Run a periodic (monthly) archive pass over test and stale threads. ## Quick reference ```bash # one-time: install + auth (hatch_sess cookie, transient) # pin the critical set (opm account, adjust per agent) muse-cli pin 5f18476d-8994-49e7-a9e0-4838732363fe # main-loop brain # archive a duplicate / stale thread muse-cli session-archive <duplicate_uuid> # verify, then update job-sidechats.json on bl if the archived # thread was the registered one (re-point or remove the entry) ``` ## Open items (2026-10-04) - muse-cli is not yet installed anywhere in the fleet. - `hatch_sess` cookie handling needs a defined transient-use procedure. - No scheduled archive pass exists yet; propose monthly. - CDP-based `sidechat list` cannot see archived state — bookkeeping must go through muse-cli, not dm.py.