Starter repo for a personal spoken-audio library: clone it, point an LLM agent at your archive, and get a searchable static site with transcripts, tags and trigger spoilers. Layout, tag vocabulary and working notes for Hypnotica and Inductor
Find a file
Naomi Persephone Amethyst bcbf82a86a
docs: what the instruments can settle, and what a move quietly breaks
A new section on measurement-derived tags, the third kind after the creator's
claims and a review model's. "Is there a binaural beat in this file" is
arithmetic, and a rule that can answer it skips the model and the adjudicator
both, because there is no opinion to rule on. What it must not skip is the
registry: a tag with no definition is a word nobody can browse by and nobody
can argue with.

Three properties keep that honest, each written down with the failure it
prevents. Every rule names the guard that makes its instrument trustworthy and
declines rather than guessing when the guard fails. The ruleset is versioned
and the version stamped beside the tags it wrote, because moving a threshold
leaves behind tags that were right under the old table and nothing else will
find them again. And a measurement that contradicts a claim is recorded as a
dispute rather than overwriting it, aggregated per creator -- one file is an
error and forty is a habit, and a creator who labels tone tracks "binaural"
with no measurable beat is telling you something about the rest of their
metadata that is worth more than the correction.

Three ways the pass fails while appearing to work:

- **Half the thresholds are judgements rather than instrument guards, and
  those do not travel.** "Markedly slower than conversation" is 70 words a
  minute against most speech; against a library of hypnosis whose median is 94
  it described 21% of the collection, which is not describing anything. At 55
  it picks out 909 recordings instead of 1,933. Neither number is wrong; only
  one of them is about that library. A threshold near the median of your
  material distinguishes nothing, and the way to find that out is to look
  before applying.
- **A claim is only ever disputed by a rule awarding the same string.** A
  ruleset spelling it `Whispered` cannot contradict one of the 1,930 entries
  claiming `Whispers`, so the mechanism ran only on the two rules whose names
  happened to match already: 499 disputes, all of them one of two tags.
  Mapping three names onto the library's own spellings took it to 1,849 and
  dropped the additions from 2,412 to 1,922, five hundred of them tags the
  entries already carried. `measured.tags` in `inductor.yaml` is where that
  mapping goes; an empty value switches a rule off.
- **A rule gated on a field most of the library lacks awards nothing and says
  so nowhere.** 79 transcripts out of 10,755 carried the `speech` verdict, so
  the rule for "no words in this at all" could never fire on a collection
  holding hundreds of wordless recordings, and the only sign was a tag missing
  from the tally. Count how many entries have a field before reading anything
  into a zero.

And a line on what such a rule may name at all: pitch is measurable and pitch
is still not gender.

`inductor.yaml` carries the whole `measured` block commented out -- the name
mapping, the cap, and every number the rules compare against -- and
`content/README.md` documents `provenance.measured_tags`, which records the
ruleset version and the thresholds the library moved. The version answers
"which code wrote this" and cannot answer "under which numbers" once the
numbers live in the config.

**A recording with no words in it cannot be keyed by its transcript.** Analysis
and review cache against a hash of the transcript, which is the right key while
there is one. Record that a file has no speech and its text is empty, so every
wordless recording hashes to the same string -- and keyed on that they do not
share a cache, they share an *answer*. One library had 124 entries keyed that
way holding 28 write-ups between them, 97 of them the same one: a description
of a fifteen-second clip of room tone and camera shutters. Nothing reports it,
and it bites hardest on the one prompt built entirely from what the audio was
measured and heard to be, which ran once for the whole collection.

**Moving a library breaks its symlinks, and only the site build notices.** The
relative-paths convention covered the references inside the documents and said
nothing about the links on disk, which is the half a move actually breaks:
`inductor` never reads them, so `check` and `orphans` are both satisfied, while
`hypnotica build` reports artwork it cannot find and carries on. `paths --write`
now relinks them too, matching the longest tail of each dead target against the
tree and reporting what it cannot account for rather than pointing it somewhere
new. `media/README.md` has the procedure.

`state/README.md` gains `sync/`, the sync endpoint's store where one is run. It
is the only directory in the library that nothing on the machine can read --
the blobs are encrypted in the browser under a key the server is never given --
which makes `hypnotica sync --rm` the only safe way to delete from it, and
makes it neither a backup of what the browsers hold nor a way back in. Losing
every device loses the key; the export file is the recovery path.

**The maintenance commands are opt-in through `--write`, and that is
load-bearing.** `run` and `ingest` write as their whole purpose and take
`--dry-run` to hold off; everything that sweeps the library to correct it
reports by default and changes nothing until asked. One of them shipped reading
`--dry-run` instead, so its plain form wrote, and somebody checking what it
would do added thirteen entries to a registry finding out. A single command
that inverts the convention is worse than no convention, because the value of
the rule is not having to check which kind you are holding.

A third worked parser, `example_author_pages.py`, reads a mirror for the
creator rather than for the recordings: their own bio, photograph and links, as
`kind: Author` records. Without it a creator page carries a synopsis a model
wrote and an avatar a renderer drew while their own words sit unread in the
mirror. `inductor authors --only-adopt` is how those reach a page that already
exists -- replacing a field the page marks `generated`, stopping at one it does
not, and taking the adopted fields back off the generated list, because they
were not.

Read it mostly for what it refuses, since all three traps produce a confident
wrong answer rather than an empty one. `og:image` is not necessarily the
creator: one site's is a stock photo from an image bank and the real avatar is
the site icon. A homepage is a shop window and not a biography -- four of five
read this way produced a catalogue, and a bare first-person test passes "I am
redefining you", a line from one recording's write-up that became somebody's
biography. And no heuristic separates a product photo from a portrait; one
candidate that survived every other check was a photograph of an envelope.

Five more war stories, all of them one recording arriving as several files or
several recordings arriving as one: a batch tagger that puts the artist in the
title field, so two files claim the creator's name as their title; a variant
marker in the filename and not in the title, which collides two dubs of one
session on a single id; a library that holds the preview and not the release,
where the envelope correlates 1.0000 precisely because the short one is inside
the long one; variants correlating as high as re-encodes, which no figure will
separate and only the catalogue will; and `inductor add`, which is for audio
with nothing beside it and hands a model the job of inventing descriptions a
mirror already had.
2026-09-21 06:12:28 +00:00
cache Skeleton: a Driftspace library an agent can work from 2026-09-15 07:39:48 +00:00
content docs: what the instruments can settle, and what a move quietly breaks 2026-09-21 06:12:28 +00:00
examples docs: record what audio with no words in it costs, and how a mirror arrives 2026-09-19 08:33:51 +00:00
media docs: what the instruments can settle, and what a move quietly breaks 2026-09-21 06:12:28 +00:00
sources docs: record what audio with no words in it costs, and how a mirror arrives 2026-09-19 08:33:51 +00:00
state docs: what the instruments can settle, and what a move quietly breaks 2026-09-21 06:12:28 +00:00
tools docs: what the instruments can settle, and what a move quietly breaks 2026-09-21 06:12:28 +00:00
www Skeleton: a Driftspace library an agent can work from 2026-09-15 07:39:48 +00:00
.gitignore Ignore cache, media and the built site 2026-09-15 07:39:57 +00:00
AGENTS.md Skeleton: a Driftspace library an agent can work from 2026-09-15 07:39:48 +00:00
CLAUDE.md docs: what the instruments can settle, and what a move quietly breaks 2026-09-21 06:12:28 +00:00
inductor.yaml docs: what the instruments can settle, and what a move quietly breaks 2026-09-21 06:12:28 +00:00
LICENSE Skeleton: a Driftspace library an agent can work from 2026-09-15 07:39:48 +00:00
README.md Skeleton: a Driftspace library an agent can work from 2026-09-15 07:39:48 +00:00

Driftspace

A working directory for building a spoken-audio library with Hypnotica and Inductor.

Clone it, point an LLM agent at it, and tell the agent what you have. It reads the notes in CLAUDE.md and in each directory, writes a parser for whatever shape your material arrived in, and runs the pipeline. The end of it is a static website: every recording with a transcript, a write-up, tags, acoustic measurements, and a spoiler list naming the triggers and compulsions it contains.

The spoiler list is the point. Hypnosis recordings try to do things to the listener, and a catalogue that says what lets you decide before you play something rather than after.

This template ships no recordings and no metadata about any. It is a layout, a tag vocabulary, and a set of working notes.

What it is made of

Three pieces, and keeping them separate is the point.

content/ the library itself: YAML, and the only thing that matters long-term
Hypnotica turns content/ into a website. Knows nothing about where it came from
Inductor fills content/ from audio. Knows nothing about websites

Each tool is usable alone. Hypnotica will build a site from YAML you wrote by hand. Inductor will enrich a folder of audio and hand you YAML to do what you like with. Together they are a pipeline.

Install the tools

Both are single Go binaries, and both want FFmpeg on PATH.

git clone https://github.com/NaomiAmethyst/inductor
cd inductor && go build -o ~/.local/bin/inductor ./cmd/inductor

git clone https://github.com/NaomiAmethyst/hypnotica
cd hypnotica && go build -o ~/.local/bin/hypnotica ./cmd/hypnotica
needed for
Go 1.26+ building either binary
FFmpeg and ffprobe durations, conversion, acoustic measurement
Python 3.10+ transcription and voiceprints — on the worker machine only
An NVIDIA GPU transcribing at a sensible speed. A CPU worker is the fallback
An OpenRouter key write-ups, tags and spoilers. OPENROUTER_API_KEY
ComfyUI cover art. Optional, and off by default

Nothing but Go and FFmpeg is needed to import material, tag it, and build a site. Transcription and enrichment are what pull in the rest.

Start

git clone https://github.com/NaomiAmethyst/driftspace-template my-library
cd my-library && rm -rf .git && git init

Then edit two files — inductor.yaml for models and paths, and content/hypnotica.yaml for the site's name and URL — and say something like:

I have a folder of mp3s at ~/audio/some-creator/ and a mirror of their site at ~/mirrors/some-creator/. Import them.

The agent's route from there is written down in CLAUDE.md. If you would rather drive it yourself:

inductor add ~/audio/some-creator/*.mp3 --author-name "Some Creator"
inductor check                      # before trusting anything a parser wrote
inductor tagmap --author some-creator
inductor run --author some-creator --no-covers
inductor adjudicate                 # rule on tags the run wants to add
hypnotica -s content check
hypnotica -s content build -o www --media link
hypnotica serve -o www -p 8080

--media link symlinks the audio into the site instead of copying it, which matters once the library is larger than the disk has room to duplicate.

The pipeline

  a site mirror, a folder of mp3s, a pack with a README
        │
        │  a parser you write for that one source        tools/mirrors/
        ▼
  sources/<creator>.yaml     kind: Source — audio, title, author, whatever else
        │
        │  inductor run                                  transcode · transcribe
        ▼                                                measure · enrich · tag
  content/<creator>/*.yaml   kind: Item, Author, Transcript
        │
        │  hypnotica build
        ▼
  www/

Each arrow is re-runnable and cached. Nothing later in the chain reaches back: Hypnotica never transcribes, Inductor never renders.

The layout

Every directory here has a README.md saying what belongs in it. Directories that are meant to start empty hold a .gitkeep and nothing else.

content/            the library. Permanent, and the only thing that matters
  tags.yaml           the tag registry: the only tags that may reach an item
  hypnotica.yaml      site configuration
media/              audio and artwork, placed where the site can serve them
sources/            source records, one YAML per creator

state/              produced by the pipeline. Expensive, and not disposable
  decisions/          tag maps and rulings: what a person decided, and why
  transcripts/        what was heard, keyed by the audio's fingerprint
  enrichment/         what the models made of it, keyed by the transcript's

cache/              indexes, measurements, staging. Safe to delete
tools/mirrors/      one parser per source site
tools/import/       one-off importers and migrations
www/                the built site. Generated; never edit it

cache/ may be deleted; state/ may not, and neither may content/. The line between them is not how the files were made but what it would take to make them again: a voiceprint is GPU-hours and a tag ruling is a judgement nobody can reconstruct.

Git, and the parts of this that git is wrong for

.gitignore excludes cache/, media/ and www/, and carries commented lines for state/ and content/ — see the comments there, because whether those belong in git depends on how private the library is and how large it has grown.

The audio does not belong in git, and neither does the artwork. A few thousand recordings is a few hundred gigabytes, git stores every version of every one of them forever, and no forge will take it. Options, roughly in order of how much trouble they are:

  • Leave it out. media/ is symlinks into wherever the audio already lives. Back that directory up the way you back up anything else — rsync, restic, borg, a second disk.
  • git-annex, which keeps the file contents outside git and the filenames inside it. The closest fit: it is built for exactly this, and it can track which drive holds which copy.
  • Git LFS, if you are pushing to a forge that offers it and can live with its quotas.
  • Syncthing or similar, alongside the git repo rather than inside it.

The YAML is what is worth versioning. It is small, it is the expensive part to rebuild, and its history is a record of what you decided about each recording.

Privacy

This is a personal library, and the default configuration treats it as one. base_url points at localhost, the site is static and needs no server-side anything, and nothing is published until you publish it.

What does leave the machine: transcript text and metadata go to whichever models inductor.yaml names, over OpenRouter. Transcription and acoustic measurement are local. If the material is sensitive enough that model providers are a problem, run the ingest stages without analyse and review and write the entries yourself — inductor ingest --stage media --stage transcribe --stage emit.

Licence

Copyright © 2026 Naomi Persephone Amethyst naomi@amethyst.name.

This template — the layout, the notes and the tag registry — is GNU GPL version 3 only, matching Hypnotica and Inductor, neither of which is included here. See LICENSE.

The library you build with it is yours. The licence covers this scaffolding, not your recordings, your metadata or anything you write into content/.