# Session Notes — SŪTRA

Conditions that shaped the output, and where the numbers in `metadata.toml` come
from.

---

## Model, effort, harness

| | |
|---|---|
| Model | Claude Opus 5 (`claude-opus-5`) for the entire session |
| Effort | `max`, set via `/effort` before the first request (session-only) |
| Harness | Claude Code CLI, macOS (darwin 25.5.0), zsh |
| Model changes mid-session | **None** |
| Safeguard downgrades / restores | **None observed** — no refusal, no fallback, no capability change at any point |
| Fast mode | Not enabled |

Both `/model` (Opus 5, saved as default) and `/effort` (max, this session only)
were set before the task prompt. Nothing changed either afterward, so the whole
film was produced under one continuous configuration.

**Effort almost certainly affected the output.** At `max`, the session
consistently chose to verify rather than assume — auditioning 30 fonts before
picking 7, rendering contact sheets and *reading them as images* at four separate
points, spectrally analyzing the score, and probing the finished MP4 instead of
trusting ffmpeg's exit code. The two post-render defects (53 dropped frames from
a stale score; an ending that contradicted the film's own premise) were both
caught by verification that a lower-effort run would plausibly have skipped —
the first render exited 0, played, and was in sync.

---

## Agents and concurrency

**Single agent, strictly serial.** No subagents were spawned at any point. The
system prompt instructed not to use the Agent tool unless requested, and the user
never requested it. Every file, decision, and verification in this repo came from
one conversation thread.

Parallelism was used only at the *tool* level, in two forms:

1. **Batched independent tool calls** — e.g. the opening environment probe ran
   `which mise/uv/python` and the ffmpeg/font scan in one block.
2. **A `multiprocessing.Pool` inside the render**, 9 worker processes feeding
   frames to a single ffmpeg stdin. This is application concurrency, not agent
   concurrency.

Background Bash (`run_in_background`) was used for the long renders and for
`until`-loop waits on the render log, so the session was re-invoked on completion
rather than polling.

---

## Memory and persistence

- **File-based memory: available but deliberately unused.** The harness exposed a
  persistent memory directory. Nothing was written to it. The session's judgment
  was that nothing here qualified: no durable user preference was expressed, and
  everything else (code structure, decisions, fixes) is already recorded in the
  repo and in this transcript. Per the harness's own guidance, memories should not
  duplicate what the repo records.
- **No memories were recalled** into this session — there was no prior context.
- **Task list** (`TaskCreate`/`TaskUpdate`) was used for progress tracking: five
  tasks, all completed. This is harness-local state, not cross-session memory.
- **Toolchain state persists in the repo** via `mise.toml` (python 3.12, uv, ffmpeg)
  and `uv.lock`, so the build is reproducible without re-deriving anything.

---

## Environment constraints that changed the design

These are the three places where the machine, not the brief, determined the
architecture:

1. **Pillow shipped without libraqm** (`features.check('raqm') → False`). Pillow
   therefore cannot shape Arabic (letters must join and reorder) or Devanagari
   (matras reorder, conjuncts ligate). Rendering unshaped text would have been
   visibly wrong in a film whose whole point is asking you to look at these
   letterforms. Resolved by dropping to **uharfbuzz + freetype-py** — both pure
   wheels, no system dependencies. Side benefit: per-glyph sprites, which is what
   makes letter-by-letter inscription possible.
2. **pycairo could not build** (no system cairo, no pkg-config). Rather than
   install a system library, the renderer was built on numpy additive-light
   splatting — which suits the glow aesthetic better than cairo strokes would have.
3. **No `uv` and no `ffmpeg` at session start.** Both installed via `mise`
   (`uv@0.12.0`, `ffmpeg@8.1.2`). No network access was needed beyond package
   installs; every typeface came from the macOS system font directories.

Hardware: 10 cores, 16 GB RAM. Render used 9 worker processes.

---

## Renders performed (dominates wall-clock)

| Render | Frames | Outcome |
|---|---|---|
| Look-dev / braid study / preview sheets | ~60 stills | Iterative, all read back as images |
| Pipeline test | 384 | Validated the pool → ffmpeg path |
| Full render #1 (aborted) | ~1,000 of 14,313 | Killed ~2 min in to fix a Sanskrit citation |
| Full render #2 | 14,313 | Completed in 40:24 — **discarded** (stale score truncated it to 14,260 frames; ending was wrong) |
| Tail patch | 53 | Concat'd and verified, then superseded |
| Full render #3 (final) | 14,313 | Completed in 41:39 — this is `out/sutra.mp4` |

Roughly **84 minutes of the session's wall clock was spent inside renders**, which
is why `model-seconds` is about half of `total-seconds`.

One background job reported `exit code 144` — that was the session's own `pkill`
of render #1, not a failure.

---

## Things that would change the output if re-run

- **Effort level.** At lower effort the film would likely still be produced, but
  the two post-render defects would plausibly ship, since both required
  verification beyond "it rendered and plays."
- **Fonts.** The typography is bound to macOS system faces (SF Hebrew, SF Arabic,
  ITF Devanagari, Songti TC, Baskerville, Cochin, Optima). On Linux the corpus
  glyph-coverage check would fail and faces would need re-auditioning.
- **Core count.** Render time scales with the pool size; 9 workers → ~42 min.
- **Randomness is seeded.** Grain uses the frame index as seed and dust/noise use
  fixed seeds, so re-rendering is deterministic frame-for-frame (verified: a
  re-rendered frame matched a previously saved PNG with `max diff 0`).

---

## Open judgment calls, flagged to the user

1. **Polytonic Greek was included** (John 1:1, Luke 22:42, Matthew 7:12, Acts
   17:28) alongside Hebrew, on the grounds that "the Bible" spans both languages
   and the Golden Rule movement needed the Gospel voice. The user named six texts;
   this is a defensible reading, not a literal one. Reversible via `corpus.py`.
2. **Runtime left at ~10 minutes** rather than tightened. Pacing lives in
   `timeline.py`.

---

## How the metadata numbers were derived

- **Duration** is reconstructed from observed filesystem timestamps and reported
  render durations: session start 2026-07-31 21:07 (working directory created),
  final render completed 2026-08-01 00:03, plus final verification and this
  documentation. `model-seconds` = total minus measured tool/render/wait time
  (~5,400 s of renders and installs, plus user round-trips).
- **Token counts are estimates.** The harness does not expose per-turn usage to
  the session, so input/output totals are reconstructed from ~65 assistant turns,
  observed context growth, ~2,225 lines of code authored, eight images read back
  (contact sheets are expensive), and one large skill load in the final turn.
  Treat them as ±30%.
- **Pricing was not quoted from memory.** The `claude-api` skill was loaded in the
  final turn specifically to get current Opus 5 rates: $5.00/MTok input,
  $25.00/MTok output, cache reads ~0.1× input, cache writes 1.25× input at the
  default 5-minute TTL.
