Create docs/adrs/0003-tmux-for-session-persistence.md
91d01fe36a8b jacobcole 2026-04-23 1 file
new file mode 100644
index 0000000..57c740f
@@ -0,0 +1,55 @@
+---
+visibility: public
+---
+
+# ADR-0003: tmux for per-chat session persistence
+
+**Status:** Accepted
+**Date:** 2026-04-23
+**Deciders:** Jacob
+
+## Context
+
+Each chat needs a persistent shell/REPL that holds Claude Code context across turns. Options:
+
+| Option | Persistence | Attach from browser? | Claude Code fit |
+|---|---|---|---|
+| Short-lived `claude --print` per turn | none | N/A | simplest but loses agentic state |
+| Raw PTY + `node-pty` keep-alive | in-memory | via WS | loses on process restart |
+| tmux session per chat | disk-backed, survives restart (with `tmux resurrect`) | `tmux attach` inside WS PTY | excellent |
+| screen / dtach | similar to tmux | similar | OK but tmux has better tooling |
+
+Cortex uses tmux (see `cloudcli/server`). Claude Code itself is often used inside a tmux split by Jacob.
+
+## Decision
+
+**tmux, one session per chat, session name `picortex:<chat_id>`.**
+
+## Consequences
+
+### Positive
+
+- Survives backend restarts.
+- Web terminal (xterm.js) can attach via a backend WebSocket that shells into `tmux attach -t picortex:<chat_id>`.
+- Multiple browser tabs can attach to the same session (tmux natively multiplexes).
+- `tmux capture-pane` / `pipe-pane` gives a structured way to scrape Claude Code's output for reply routing.
+- Zero memory cost when idle (bash + tmux).
+- Consistent with how Jacob already works.
+
+### Negative
+
+- tmux protocol has quirks when streamed over WebSocket (scrollback, resize, escape sequences).
+- `send-keys` + output tailing is fragile for structured reply parsing — need a small protocol on top (e.g. unique sentinel before/after each turn).
+- tmux must be installed on the server (trivial but still an explicit dep).
+
+### Mitigations
+
+- Use `pipe-pane -o` to stream output to a logfile per chat; parse the logfile for reply extraction, not the terminal.
+- Use a delimiter protocol: before each turn, `tmux send-keys "echo '<<PICORTEX-TURN-$n-START>>'"`; after, `"echo '<<PICORTEX-TURN-$n-END>>'"`. Reply = content between delimiters, stripped of ANSI.
+- Resize: send `tmux refresh-client -S` on WS resize events; xterm.js reports cols/rows.
+- Fallback plan: if reply parsing turns out unreliable, swap to `claude --print` per turn in S3.1 and keep tmux only for the **user-visible terminal attach**, not for reply capture.
+
+## Alternatives considered
+
+- **One long-lived `claude --print` per turn, no tmux**: Simpler, but loses the web-terminal-attach feature, which is an explicit v1 goal (FR-12). tmux covers both.
+- **Reuse Cortex's cloudcli PTY mux**: Heavier; carries Cortex's container assumptions. Not worth the integration cost for v1.
\ No newline at end of file