# ADR-0007 — One Claude session per Recoil project, persisted in `~/.recoil/chat-sessions.json`

**Status:** Accepted
**Date:** 2026-05-05
**Deciders:** JT, Claude (in dialogue)
**Supersedes:** none
**Superseded by:** none

> *This ADR was drafted during the embedded-claude-terminal Phase 11 cleanup commit. It locks in the session-binding decision made in BUILD_SPEC §4 (item ADR-0004 in spec text; renumbered to 0007 because the repo already has ADRs 0001–0005).*

## Context

The right-column terminal embeds `claude --resume <session_id>`. Two binding strategies were available:

1. **Ephemeral sessions.** A fresh `claude` invocation per console open; `--resume` is unused.
2. **Persistent per-project sessions.** Every Recoil project gets exactly one durable session ID; the console reuses it across restarts.

Ephemeral sessions lose every conversation thread on console restart, browser refresh, ttyd port recycling, or laptop sleep. JT's working pattern — return to a project days later, pick up the thread — would silently degrade.

## Decision

One Claude session per Recoil project. Session IDs are stored in `~/.recoil/chat-sessions.json` keyed by project slug, with `schema_version: 1`. Writes are atomic (temp-file + rename) and fcntl-flocked. Reads are unlocked.

Schema:

```json
{
  "schema_version": 1,
  "sessions": {
    "tartarus": {
      "session_id": "<uuid>",
      "created_at": "<iso8601>",
      "last_used_at": "<iso8601>"
    }
  }
}
```

## Consequences

**Thread continuity.** A Claude conversation that started Tuesday is still alive Friday — the right column wakes back up where it left off. `claude --resume` reads from `~/.claude/projects/<encoded-cwd>/<session_id>.jsonl` directly; the recoil-side state file just records which session ID belongs to which project.

**Failure modes.** Two: (a) the JSONL file under `~/.claude/projects/...` gets pruned or corrupted — `claude --resume` then errors and the right column surfaces the failure; (b) `~/.recoil/chat-sessions.json` itself corrupts. Both are recoverable: delete the entry and ttyd_routes auto-creates a new session on next start.

**Local-machine state.** This file is per-machine, gitignored, and not synced via Dropbox. Studio and MacBook Pro have independent session histories — by design, since the underlying Claude Code session JSONLs live under `~/.claude/projects/` per machine anyway.

**Atomic writes are mandatory.** Multiple ttyd processes (one per project) plus the console UI all read/write this file. Without flock + temp-file + rename, concurrent project switches can produce a torn JSON.

## Alternatives considered

- **Ephemeral sessions.** Rejected — loses thread continuity; degrades JT's working pattern silently.
- **One global session for all projects.** Rejected — Recoil projects are sealed per `core/paths.py`; mixing tartarus context into an afterimage session is exactly the kind of cross-project leakage the project root boundaries exist to prevent.
- **SQLite for session state.** Rejected — premature; <50 projects, single-writer-with-flock JSON is sufficient and human-debuggable.

## Implementation

- `~/.recoil/chat-sessions.json` — atomic-write JSON file, schema_version: 1.
- `recoil/api/ttyd_routes.py` — reads/writes the file via fcntl.flock, supplies `--resume <session_id>` to the spawned `claude` invocation.

## Verification

```
$ cat ~/.recoil/chat-sessions.json | jq '.schema_version'
1
$ cat ~/.recoil/chat-sessions.json | jq '.sessions | keys'
["tartarus", "the-afterimage"]
```
