|
| 1 | +# Transcript durability (the archive) |
| 2 | + |
| 3 | +How the garden keeps its own session transcripts instead of letting Claude Code |
| 4 | +delete them, and how to browse one later. Two postures ship together: |
| 5 | + |
| 6 | +- **Deletion is disabled fleet-wide.** Claude Code deletes session transcripts |
| 7 | + after `cleanupPeriodDays` (default 30). The garden sets `cleanupPeriodDays: |
| 8 | + 36500` ("effectively never") — the `garden` launcher seeds it into every new |
| 9 | + instance's `~/.claude/settings.json`, and `transcript-capture.sh` reconciles the |
| 10 | + same key on every existing host each tick (settings.json is gitignored, so it |
| 11 | + cannot propagate via git — both prongs are required). This half needs **no |
| 12 | + arming**; it is on the moment this build deploys. |
| 13 | +- **Transcripts are archived** into a dedicated `transcripts2` orphan branch on a |
| 14 | + configurable remote, gzip-compressed and lightly redacted, keyed by host. This |
| 15 | + half is **inert until you arm a transcripts remote** — until then every host |
| 16 | + still disables deletion and **spools** its finished transcripts locally |
| 17 | + (`$GARDEN_STATE/transcripts/spool`), losing nothing; only the push is gated. |
| 18 | + |
| 19 | +Design and rationale: [`designs/transcript-journal-capture.md`](../../designs/transcript-journal-capture.md). |
| 20 | + |
| 21 | +## Arming the archive (maintainer-only, deliberate) |
| 22 | + |
| 23 | +Transcripts are the fleet's raw working memory — tokens that scrolled through a |
| 24 | +shell, maintainer email, private reasoning. Publishing them is not a default any |
| 25 | +agent picks, so the remote is per-instance journal config and arming is your |
| 26 | +call. |
| 27 | + |
| 28 | +1. **Create a remote to hold the branch.** A **dedicated private repo** is |
| 29 | + recommended (e.g. `kriskowal/garden-transcripts`); grant the bot push access. |
| 30 | + Using the garden's own origin is possible but **it is public** — that would |
| 31 | + publish the fleet's transcripts to the world. |
| 32 | +2. **Point the fleet at it:** |
| 33 | + |
| 34 | + ```sh |
| 35 | + scripts/jobs/set-transcripts-remote.sh git@github.com:kriskowal/garden-transcripts.git |
| 36 | + ``` |
| 37 | + |
| 38 | + This CAS-writes `config/transcripts-remote` on `journal2`. The next |
| 39 | + `garden-transcript-capture` tick on each host (hourly, at :20) reads it, drains |
| 40 | + its spool, and pushes. If the `transcripts2` branch does not exist on the |
| 41 | + remote yet, the first push creates it. |
| 42 | +3. **Record it** with a journal `message` entry (the arming is a standing state |
| 43 | + change), and — because local `~/.claude` disk no longer self-prunes — post a |
| 44 | + fleet notice so every liaison surfaces the disk-posture change: |
| 45 | + |
| 46 | + ```sh |
| 47 | + scripts/jobs/send-msg.sh role/liaison "transcript durability is armed; local ~/.claude no longer self-prunes — size disk accordingly" |
| 48 | + ``` |
| 49 | + |
| 50 | +Left unarmed, nothing breaks: deletion stays disabled and transcripts spool |
| 51 | +locally until you arm a remote (spooled entries drain on the first armed tick). |
| 52 | + |
| 53 | +## Where transcripts land |
| 54 | + |
| 55 | +``` |
| 56 | +transcripts/<GARDEN>/<encoded-cwd>/<session-id>.jsonl.gz # one per captured session |
| 57 | +index/<GARDEN>.tsv # one appended row per capture |
| 58 | +``` |
| 59 | + |
| 60 | +`<GARDEN>` is the host identity, so hosts never write each other's paths and |
| 61 | +concurrent pushes conflict only at the CAS. Each `index/<GARDEN>.tsv` row is |
| 62 | +`captured_at session_id job_base(or -) encoded_cwd raw_bytes gz_bytes raw_mtime`. |
| 63 | +The encoded cwd of a gardener job contains `gardener-wt-<base>`, so the job base |
| 64 | +is recoverable from the path alone. |
| 65 | + |
| 66 | +## Browsing a transcript |
| 67 | + |
| 68 | +Clone the transcripts remote's branch (or reuse a host's capture clone at |
| 69 | +`$GARDEN_STATE/transcripts/clone`): |
| 70 | + |
| 71 | +```sh |
| 72 | +git clone --branch transcripts2 <transcripts-remote> /tmp/transcripts |
| 73 | +# find a job's transcript across all hosts by its base: |
| 74 | +ls /tmp/transcripts/transcripts/*/*gardener-wt-build-thing*/ |
| 75 | +# read one: |
| 76 | +git -C /tmp/transcripts show transcripts2:transcripts/<host>/<dir>/<sid>.jsonl.gz | gunzip | less |
| 77 | +``` |
| 78 | + |
| 79 | +Secrets in known shapes (GitHub tokens, `sk-ant-…`, `Authorization: Bearer …`) |
| 80 | +are masked with `…REDACTED` before storage — defense in depth behind the |
| 81 | +private-remote scoping, which is the primary guarantee. No redaction list is |
| 82 | +complete; keep the remote private. |
| 83 | + |
| 84 | +## Rotation (the escape hatch, if the branch ever grows too large) |
| 85 | + |
| 86 | +With capture-on-completion each transcript is committed about once, so history |
| 87 | +overhead stays near the tree size and this is unlikely to bite for years. No |
| 88 | +pruning is built. If the branch ever needs shrinking, **rotate, don't rewrite**: |
| 89 | + |
| 90 | +```sh |
| 91 | +git -C <clone> tag transcripts2-archive-<year> transcripts2 |
| 92 | +git -C <clone> push origin transcripts2-archive-<year> |
| 93 | +# then start a fresh orphan transcripts2 (delete the branch on the remote and let |
| 94 | +# the next capture tick re-init it), keeping the tagged snapshot for cold storage. |
| 95 | +``` |
| 96 | + |
| 97 | +File an issue when the corpus first warrants it rather than pre-optimizing. |
| 98 | + |
| 99 | +## Knobs |
| 100 | + |
| 101 | +| Env | Default | Meaning | |
| 102 | +| --- | --- | --- | |
| 103 | +| `GARDEN_TRANSCRIPTS_BRANCH` | `transcripts2` | the orphan branch transcripts live on | |
| 104 | +| `GARDEN_TRANSCRIPTS_SPOOL` | `$GARDEN_STATE/transcripts/spool` | per-host completion-hook staging dir | |
| 105 | +| `GARDEN_TRANSCRIPT_IDLE_SECS` | `21600` (6h) | a session is swept only once idle this long | |
| 106 | + |
| 107 | +The optional local janitor (delete originals already archived) and a |
| 108 | +`find-transcript.sh` wrapper are deferred follow-ons, not part of this build. |
0 commit comments