Skip to content

SOPs

Multi-Session Orchestration — Sessions Talking to Sessions

I run many Claude Code panels in parallel. Each one is its own conversation, its own context, its own tool history. Until recently the only way to know what was happening in another panel was to switch to it and read.

That is not true anymore. Every Claude Code session writes a complete JSONL transcript to disk in real time. Any other session can read that file. Combined with the inbox routing already in place, this gives one master session the ability to watch, query, and eventually clean up all the others.

This is the unlock. The page below is the mechanism plus where I think it goes.

Every session writes its transcript to:

~/.claude/projects/<encoded-cwd>/<session-uuid>.jsonl

The <encoded-cwd> is the working directory with slashes replaced by dashes. For my main workspace it is -Users-ojhurst-apps. So the transcript files for everything I do under ~/apps/ live in ~/.claude/projects/-Users-ojhurst-apps/.

Each .jsonl is append-only. One JSON object per line. Every user prompt, every tool call, every tool result, every assistant message. Hooks output is in there too. The whole session, end to end.

The session UUIDs are not memorable. The session-namer skill (Haiku) writes a human-readable title into the transcript itself as a special line:

{"type":"ai-title","sessionId":"5743cd7b-...","aiTitle":"Add word count tracking to FreeFlow app"}

So the lookup goes: title → grep across *.jsonl for "aiTitle":"<title>" → session UUID → full transcript.

~/.claude/sessions/<pid>.json has one file per running Claude Code process, keyed by PID. Each file is small JSON with pid, sessionId, cwd, startedAt, version, entrypoint. Reading this directory tells me which sessions are actively running. Files for closed sessions are cleaned up; transcripts in projects/ are kept indefinitely.

So:

  • All sessions ever~/.claude/projects/<workspace>/*.jsonl
  • Live sessions right now~/.claude/sessions/*.json
  • This session’s UUID$CLAUDE_SESSION_ID in the env, or the most recently modified *.jsonl for this cwd.

The Read and Bash tools work on these files like any other. Concrete patterns:

Find a session by name:

Terminal window
grep -l '"aiTitle":"Review smarter review requests"' \
~/.claude/projects/-Users-ojhurst-apps/*.jsonl

See what another session is currently working on (last user prompt and assistant response):

Terminal window
tail -50 ~/.claude/projects/-Users-ojhurst-apps/<uuid>.jsonl | \
python3 -c "
import json, sys
for line in sys.stdin:
try:
d = json.loads(line)
if d.get('type') in ('user', 'assistant'):
content = d.get('message', {}).get('content', '')
if isinstance(content, list):
content = ' '.join(c.get('text','') for c in content if isinstance(c, dict))
print(d['type'].upper(), str(content)[:300])
except Exception:
pass
"

List every live session with its title:

Terminal window
for f in ~/.claude/sessions/*.json; do
sid=$(python3 -c "import json; print(json.load(open('$f'))['sessionId'])" 2>/dev/null)
[ -z "$sid" ] && continue
cwd=$(python3 -c "import json; print(json.load(open('$f'))['cwd'])" 2>/dev/null)
enc=$(echo "$cwd" | sed 's|/|-|g')
title=$(grep -h '"aiTitle"' ~/.claude/projects/${enc}/${sid}.jsonl 2>/dev/null | \
tail -1 | python3 -c "import json,sys; print(json.loads(sys.stdin.readline()).get('aiTitle','(untitled)'))" 2>/dev/null)
echo "$sid $title"
done

That last block is the seed of a “what is everybody working on right now” sweep.

When I want one session to look into another, I just say it: “Check the session called ‘Review smarter review requests’ — what is it working on?” and the session in front of me grep-finds the UUID, reads the transcript tail, and tells me. No special wiring. The unlock is realizing the data is already there to be read.

Reading transcripts is one direction. The other direction is sending — one session pushes a message to another. That layer was built 2026-04-29 and lives under ~/apps/cc/:

  • cc/inbox/PRIMARY_SESSION_ID — single line, UUID of the current primary session.
  • cc/inbox/inbox.jsonl — pending messages from secondaries to primary.
  • cc/inbox/replies/<secondary-uuid>.jsonl — primary’s replies to a specific secondary.
  • cc/bin/claim-primary-session.sh — claim caller as primary.
  • cc/bin/report-to-primary-claude.sh — secondary → primary sender (--from NAME --type update|question|done|error MESSAGE).
  • cc/bin/reply-to-secondary-claude.sh — primary → secondary (--to <uuid> or --to-recent).
  • cc/hooks/inbox-inject.sh — UserPromptSubmit hook. Drains the primary inbox if this session is primary, drains replies/<this-session-id>.jsonl if a reply is waiting.
  • /be-primary slash command — claims the current session as primary.

Known limit: hooks fire only on UserPromptSubmit, so a secondary does not see a reply until I prompt it. Zero-touch wake-up is not built yet.

NeedTool
See what another session is doingRead its .jsonl directly
Send a message to another sessionreport-to-primary-claude.sh / reply-to-secondary-claude.sh
Find a session by human namegrep '"aiTitle"' projects/*/*.jsonl
List live sessionsls ~/.claude/sessions/*.json
Coordinate “everybody report in”Master session loops over live sessions, reads each tail, summarizes

Reading is passive — no impact on the other session. Sending is active — surfaces in the other session next time I prompt it. Most of the orchestration value is in reading. The sending half is for handoffs that need a response.

What makes this an unlock and not just a trick: the master session does not need to be told what every other session is doing. It can find out.

A few patterns I want to build:

  1. Stand-up sweep. One session, one prompt: “What is every live session working on right now?” → reads each tail → returns one paragraph per session. Replaces panel-switching as the way I keep my head around ten parallel threads.

  2. Loose-ends close-out. “Sweep all my other sessions for anything unfinished.” Each transcript gets read for the last user request, the last assistant response, and any commit-check Stop-hook blocks. The master session returns a punch list: Session X has an uncommitted edit to repo Y. Session Z asked you a question 40 minutes ago. Session W finished its task and is idle. I decide what to do; the master session can prompt any of them via the inbox to act on it.

  3. Cross-session learning. When one session figures out a tricky thing — a working API call, a config that finally took, a non-obvious workaround — the next session does not need to rediscover it from scratch. The transcript is already on disk. “How did I get the GHL invoice send working last Tuesday? Read that session’s transcript.” This is what loose-ends.md does on a 24-hour cadence with Haiku; the in-session version is faster and more targeted.

  4. Session reaping. A daemon, or a /closeout extension, that identifies sessions which have been idle for hours with no loose ends and closes them. The chaos of nine open panels is real; auto-cleanup of finished work would compound.

  5. One-prompt ops. “Send the same instruction to all four sessions working on FreeFlow.” The inbox can already do this; the listing-by-content piece is what makes the ergonomic version possible.

The deeper thing: the file system already holds a complete record of every conversation I have ever had with Claude Code. Any session, current or new, can read any of it. That is a memory layer that does not depend on the user retelling what happened. The first applications are coordination across panels. The bigger applications are about treating the JSONL archive as a queryable knowledge base in its own right.

  • Loose Ends Harvester — the nightly version of pattern (3) above, on the VPS.
  • Working Over SSH — the routing layer, since some sessions run on Studio while I sit at the MacBook Pro.
  • ~/apps/cc/memory/project_multi_session_routing.md — the inbox project memory.

When a new pattern proves itself in real use, add it to “The Vision” with a one-line example. When a script gets built that automates one of the patterns, move it from “Vision” to “The Mechanism” with the path. The unlock keeps growing as long as I keep finding new things the JSONL archive lets me do.