Skip to content

feat(share): share Claude's memory and conversations between seats, opt-in - #21

Merged
gelzinn merged 3 commits into
mainfrom
feat/share-memory-history
Oct 28, 2026
Merged

gelzinn merged 3 commits into
mainfrom
feat/share-memory-history

Conversation

@gelzinn

@gelzinn gelzinn commented Oct 28, 2026 •

Copy link
Copy Markdown
Owner

Description

Each Claude seat keeps its own conversations and its own auto-memory, so claude --resume on one account doesn't show what you did on another, and what Claude learned about a project on one seat doesn't exist on the others. seats sessions / seats move and auto-switch only copy one conversation on demand.

  • seats share memory on|off|status. Each project's projects/<folder>/memory moves to ~/.config/seats/shared/claude/projects/<folder>/memory, and every seat links to it. Conversations stay per seat. Claude Code names that folder after the git repository's main checkout, so worktrees and subfolders share one (per its memory docs, and matching the seat layout on disk), else after the cwd. memoryProject(cwd) in src/providers/claude/history.ts works it out from .git files only: a worktree's gitdir: plus commondir, without running git.

  • seats share sessions on|off|status. projects/, file-history/ (used by /rewind), plans/ (a session's plan file) and todos/ (older versions) move to shared/claude/ and get linked into each seat. Memory comes with projects/. Left out on purpose: history.jsonl (up-arrow prompts), session-env/, shell-snapshots/, and sessions/ (the live process registry).

  • Merging on first enable. Files are compared byte for byte (a new opaque mode in shareItems): the newest copy wins, nothing is merged as JSON, and every losing copy gets its own *.seats-backup (numbered). The winner is renamed into place, not rewritten, so it keeps its mtime and resume order. A link that already leads to the shared copy is unlinked instead of being backed up, which is the "memory first, sessions later" case. shareItems also skips a path that is the shared copy itself, reached through a linked parent.

  • How the two switches interact. A seat that shares conversations sees the shared memory through projects/. Turning memory on also links memory into the seats that keep their own conversations. Turning memory off while sessions are shared only affects those seats, and status says so. exclude <seat> memory implies own conversations, since memory lives in their folders. exclude <seat> sessions alone still lets the seat share memory when memory is on.

  • Off / exclude replaces each link with a real copy (preserveTimestamps), so nothing is lost. The shared tree stays where it is and its path is printed. Excluding a seat after sharing leaves it holding everything it could see; the output and README say so plainly and recommend excluding client seats before on.

  • include follows the precedent for items: the seat's own copy moves aside as *.seats-backup instead of being merged.

  • New seats and projects. seats sync and activation link a seat added later. The session hook now parses cwd from its stdin JSON. On SessionStart and Stop it links that project's memory, creating it in shared/ if nobody has it yet, and absorbs memory a seat wrote on its own. supervise() does the same before each launch, next to pullTrust. All of these never throw.

  • Running sessions. share sessions on|off and exclude/include … sessions|memory refuse while claude runs on a seat whose conversations would move. Detection is by CLAUDE_CONFIG_DIR, or plain claude on ~/.claude. The refusal lists the PIDs and there is no --force. In the background (sync, hook, launch), a running seat with real conversations is skipped until it's closed. Every shared item is created up front, so seats only ever hold links. Memory is moved regardless, since it is written rarely.

  • Everything else.

    • seats sessions reads each real projects/ folder once and shows a shared conversation once, as "shared".
    • seats move to a seat that already sees it says so, with or without --force.
    • copyTranscript finds the path inside the seat even when the transcript really lives in shared/, and never copies a file onto itself, which auto-switch relied on.
    • seats remove deletes only the links, and its prompt says shared conversations stay.
    • rename and migrate keep both the top-level and the nested memory links working, since moveTree repoints them.
    • seats export leaves conversations and memory out: they aren't in sharedItems.
  • herdr resumes on the pane's own seat. seats hook herdr used to report the most recently written copy of a pane's conversation. With shared conversations every seat has the same file, and right after an auto-switch two seats can hold copies with the same mtime, so herdr could restore the pane on the wrong seat, such as the old one at its limit. On pane.agent_detected, or for a pane with no saved state, the hook now asks herdr for pane process-info and takes the seat from the agent process's CLAUDE_CONFIG_DIR / CODEX_HOME (else the active seat), or else from the seats run <tool>:<seat> that started it. That seat wins when it has the session; otherwise the newest copy still does. A pane only changes seat with a new agent process, which herdr detects, so status changes keep feat(doctor): check integrations with seats doctor, set up the herdr plugin in one step and tidy shared hooks #22's single pane get and reuse the seat saved in the pane's state. runningProcesses(pids) reads only those pids. Covered by 5 tests with a fake herdr: shared history picks the pane's seat, the auto-switched child's env wins over the seats run argv, the active seat for a claude without a config dir, the fallback when process-info fails or the seat lacks the session, and no process-info call on status changes after a detection.

  • New SeatSource.history field (projects, items, memory, memoryProject); generic code lives in src/history.ts and src/commands/share.ts; config gets shareMemory, shareSessions, ownMemory, ownSessions.

  • Verified with 27 new tests in tempHome():

    • on/off round trips, with the same session id in three seats and the same memory file with different content;
    • mtimes kept;
    • memory before sessions, then sessions off with memory still on;
    • exclusion before and after sharing, memory implying sessions, and include moving the seat's copy aside;
    • the hook as a subprocess, and memory keyed from a fake worktree;
    • the running-process refusal and the background skip;
    • seats sessions dedupe, move to a seat that already sees it, and copyTranscript from both paths;
    • remove, rename, migrate and export;
    • dry runs.

    Also tried by hand with a throwaway HOME: dry run, on, status, exclude, memory on and off, sessions off.

  • Not verified: whether transcripts depend on paste-cache/ (left per seat), and whether SessionStart fires before Claude loads memory. Launches through seats link memory before spawning either way. autoMemoryDirectory isn't handled; the README says so.

  • README (new section, exclude section, table notes, herdr paragraph), plugins/herdr/README.md and help text updated.

User-facing changes

BEGIN_COMMIT_OVERRIDE
feat(share): Claude seats can share their memory (seats share memory on) and their conversations, so --resume and /rewind work from any account (seats share sessions on), both off by default and with seats exclude <seat> memory|sessions for a seat that keeps its own
END_COMMIT_OVERRIDE

Type of change

  • Bug fix
  • New provider
  • New feature
  • Docs / metadata

Checklist

  • bun run check passes (Biome lint and format, typecheck, tests)
  • bun src/cli.ts help runs without errors
  • Changes to seat folders were tried with --dry-run or a throwaway HOME
  • A new provider (if added) lives in src/providers/<id>/ and is listed in src/providers/index.ts

@gelzinn
gelzinn force-pushed the feat/share-memory-history branch from 872fd5b to 47c694d Compare October 28, 2026 15:43
@gelzinn
gelzinn changed the base branch from feat/session-hook to main October 28, 2026 15:44
@gelzinn gelzinn self-assigned this Oct 28, 2026
…pt-in

seats share memory on gives every Claude seat the same per-project memory,
and seats share sessions on the same conversations (claude --resume, /rewind),
memory included. Both are off by default and have a dry run. The first time,
every seat's copy is merged file by file: the newest transcript or memory file
wins and the older ones are kept as *.seats-backup. Turning either off gives
each seat a real copy of what it saw.

New seats and new projects join by themselves: sync and switching link a seat
added later, and the session hook links a project's memory as a session opens
there. seats exclude <seat> memory|sessions keeps a client's account out of
it. seats sessions lists a shared conversation once, seats move and
auto-switch don't copy a conversation onto itself, and seats export leaves
conversations and memory out.
…ging history

Sync, launches and the session hook no longer move the conversations of a
seat whose Claude is running; it joins once it's closed. Every shared
history folder exists from the start, so seats only ever hold links to it.
A conversation that wins a merge keeps its own timestamp instead of jumping
to the top of claude --resume. seats exclude <seat> sessions now says the
seat keeps a copy of everything it could see, the other seats' too.
When several seats had a pane's conversation, seats hook herdr picked
the copy written to last. With seats share sessions every seat has the
same file, and after an auto-switch both seats can hold copies with the
same mtime, so herdr could restore the pane on the wrong seat, such as
the old one at its limit.

On a detection, or for a pane it has no state for yet, the hook now asks
herdr which processes run in the pane (pane process-info) and takes the
seat from the agent process's CLAUDE_CONFIG_DIR or CODEX_HOME (else the
active seat), which follows an auto-switch, or else from the seats run
<tool>:<seat> that started it. That seat wins when it has the session;
otherwise the newest copy still does. A pane only changes seat with a
new agent process, which herdr detects, so status changes keep to one
pane get and reuse the seat saved with the pane's state.
@gelzinn
gelzinn force-pushed the feat/share-memory-history branch from 47c694d to 2c6f4a1 Compare October 28, 2026 16:40
@gelzinn
gelzinn merged commit f4418f4 into main Oct 28, 2026
12 of 14 checks passed
@gelzinn
gelzinn deleted the feat/share-memory-history branch October 28, 2026 17:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant