# Session Notes

Conditions that shaped the output of this session, recorded for reproducibility.

## Model, effort, harness

| | |
|---|---|
| Model | Claude Opus 5 (`claude-opus-5`) |
| Effort | `max` — set via `/effort` at session start, **this session only** |
| Harness | Claude Code (CLI) |
| Thinking | Adaptive, on throughout. Raw chain of thought never surfaced. |
| Model changes mid-session | **None.** No downgrade, no safeguard-triggered model switch, no restore. One model for the entire session. |
| Refusals / safety interventions | **None.** No `stop_reason: "refusal"`, no partial refusals, no topic gating. |

Effort `max` is directly visible in the output: the session ran long chains of
reasoning before committing to the screenplay structure, the chorus design, and
each of the three render-performance diagnoses. A lower effort setting would
very likely have shipped the first film render (~1.5 GB, swapping) rather than
stopping twice to diagnose.

## Agent topology

**A single agent, running serially.** No subagents were spawned at any point.

- The system prompt for this session carried an explicit instruction not to call
  the Agent tool unless requested, and it was not requested.
- All work — screenplay, DSP, foley, visual design, render engineering,
  verification — was done in one linear context.
- This matters for reproducing the result: the screenplay and the picture
  grammar were designed together in one context (`stage.speak` emits a
  performance *and* a caption; `stage.scene` drives the camera). Splitting that
  across parallel agents would likely have produced a soundtrack and a visualiser
  that agreed less closely.

## Parallelism that *was* used

Not agent parallelism — process parallelism inside single tool calls:

- Independent Bash/Read/Write calls batched into single assistant turns
  (toolchain survey, font check, still inspection).
- `brew install ffmpeg espeak-ng` backgrounded while the Python env was set up.
- Film rendering: `multiprocessing.Pool` over 5 worker processes, each piping
  raw frames into its own ffmpeg. This is ordinary program parallelism, not
  multiple Claude agents.

## Memory and persistence

- **Persistent memory directory:** available to the harness at
  `~/.claude/projects/.../memory/`. **Nothing was read from or written to it
  during this session.** No `MEMORY.md` entries were created. The session did not
  depend on and did not modify cross-session state.
- **Recalled memories:** none surfaced.
- **Scratchpad:** used for throwaway artifacts only
  (`/private/tmp/claude-501/.../scratchpad`) — voice/foley audition WAVs, ASR
  test clips. Nothing there is needed to reproduce the build.
- **Build cache (in-repo, gitignored):** `build/cache/` holds Kokoro takes keyed
  by `(voice, lang, speed, text)`. This is a real persistence effect on timing —
  the first audio render took **190 s**, the second took **39 s**. A cold clone
  will see the slower number.

## Background tasks and waiting

A substantial share of wall-clock time was spent waiting on renders, not on
model processing. Several background Bash tasks and one `Monitor` were used to
poll. This inflates *total* session duration relative to *model* duration — see
`metadata.toml` for the split.

One harness behavior worth recording: piping a long-running command's stdout to
`tail` fully buffers it, so progress was invisible until completion. Switched to
redirecting into `build/render.log` and tailing that with a `Monitor`.

## Things that were killed and restarted

Two film renders were deliberately killed mid-flight (exit code 144). Any
transcript reading of this session should not treat those as failures of the
harness — both were diagnosed, fixed, and the fixes are in the committed source:

1. **Attempt 1** — CRF 17 with flat-floor grain, 9 workers. Killed for bitrate
   (~1.5 GB projected) and sub-single-core throughput.
2. **Attempt 2** — killed after confirming `_grain_bank` was allocating 298 MB
   per worker (2.7 GB resident across 9), pushing a 17 GB machine into swap.

Attempt 3 shipped in 956 s.

## Environment specifics that affect reproduction

- **Hardware:** 10-core Apple Silicon Mac, 17.18 GB RAM, macOS 25.5.0. Worker
  count is derived from `cpu_count()//2`, so a different core count changes the
  render plan.
- **Fonts are macOS system fonts** (`/System/Library/Fonts/Supplemental/`):
  Didot, Copperplate, Baskerville, Futura. **The picture will not render on
  Linux without substituting these.** This is the single biggest portability
  caveat in the repo.
- **espeak-ng path** is hard-coded to the Homebrew location in `mise.toml`
  (`/opt/homebrew/lib/libespeak-ng.dylib`).
- **First run downloads** the Kokoro model (~350 MB) plus `en_core_web_sm`.
- **Determinism:** all procedural audio and all film grain/dust/weave use seeded
  `numpy.random.default_rng`, so renders are reproducible. Kokoro synthesis is
  the one non-seeded component; takes are cached to disk on first generation,
  which stabilises subsequent renders.

## Verification posture

Because the entire aesthetic is degradation, the session did not rely on
subjective judgement for the audio. `theatre/check.py` runs a speech recogniser
over the finished, fully-degraded master and diffs against the screenplay. This
caught a real defect that would otherwise have shipped: a Cantor line scoring
**6% word agreement**, traced to a sub-octave layer playing the wrong half of the
line. Final state: 95% mean, every human line 100%.

For the picture, verification was done by rendering stills and **reading them
back as images**, then re-extracting frames from the *encoded MP4* at the end to
confirm the encode matched the renderer.

## Content note

The film is deliberately anachronistic: contemporary philosophy of mind
(Premack & Woodruff 1978, the false-belief task, eight billion modelled
interiors, AGI) delivered in the register of a 1938 network broadcast. This
follows the prompt, which asked for *modern* philosophers in a *Golden Age
radio* style. The conceit is named explicitly in `README.md` so it reads as a
choice rather than an error.
