# Talk sheets — an llms.txt profile for speakers

**Version:** 1.0 (2026-10-17) · **Status:** draft, open for critique · **Reference implementation:** https://slides.wanoo.me

This document describes how a speaker's site exposes their talks, keynotes and podcast appearances to AI agents
(LLM crawlers, assistants, RAG pipelines) in a way that is **consistent**, **verifiable** and **cheap to maintain**.
It builds on [llms.txt](https://llmstxt.org) and adds one Markdown "talk sheet" per item.

The audience is twofold: colleagues who want the same thing for their own talks, and anyone who wants to
challenge the approach. Section 9 lists the decisions we are least sure about.

---

## 1. Goals and non-goals

**Goals**
1. An AI that lands on the site can answer "what does *X* think about *Y*, and where did they say it?" with a
   timestamped link to the recording, instead of guessing.
2. Every claim attributed to the speaker is traceable to a source (recording timestamp or slide number).
3. Adding a talk is a routine: same files, same sections, same checks. The output stays coherent over years.
4. The format is plain files (Markdown, JSON, HTML). No server, no database, no third-party service at read time.

**Non-goals**
- Publishing verbatim transcripts. Sheets are summaries; the recording stays the source of truth.
  (Transcripts are kept privately to regenerate sheets; see §7.)
- Ranking or SEO tricks. The structure helps machines read, it does not try to game them.

## 2. Principles

- **Source over summary.** Every key point, quote and Q&A answer links to a timestamp (`&t=<seconds>s`) or a slide.
- **Attribute conservatively.** In multi-speaker recordings without speaker labels, a statement is attributed to
  the speaker only when the recording makes it clear. Otherwise: "the hosts", "another participant", or say it is
  uncertain. Never put words in someone's mouth.
- **Say how it was made.** Each sheet states its provenance (AI-written from auto-captions, slides) and its review
  status. Readers and agents can weigh it accordingly.
- **Dates are origin dates.** An item is dated when it was said (recording date), not when it was uploaded.
  When only the upload date is known, it is used and noted.
- **One source of truth per fact.** Metadata lives in `talks.json`; the speaker's identity in `site.json`;
  narrative in the sheet. Generated files are never edited by hand.
- **English sheets, original titles.** Sheets are written in English for reach; titles and quotes stay in the
  original language, with an English translation next to French quotes.

## 3. Files

| Path | Kind | Purpose |
|---|---|---|
| `site.json` | source | Who the speaker is: name, role, organisation, socials, site URL, series, map places. |
| `talks.json` | source | One entry per item (talk, keynote, panel, podcast episode, interview). |
| `ai/_about.md` | source | Speaker presentation + **recurring positions**, each with links to the sheets that support it. |
| `ai/<id>.md` | source | One talk sheet per item (§5). |
| `transcripts/<id>.txt` | private source | Timestamped auto-captions + video metadata (+ owner-voice tags for podcasts), used to (re)write sheets. Not served. |
| `/llms.txt` | generated | Index for agents (§6). |
| `/llms-full.txt` | generated | `llms.txt` + every sheet, concatenated, for one-shot ingestion. |
| `/ai/index.json` | generated | Machine-readable catalogue: `talks.json` + `one_sentence`, `tldr`, `topics`, `summary_url`. |
| `/sitemap.xml`, `/robots.txt` | generated | Discovery. robots.txt allows all agents and points to llms.txt. |
| JSON-LD in HTML | generated | schema.org `Person`, `ItemList` of `Event` / `PodcastEpisode` / `VideoObject`, `subjectOf` → sheet. |
| `<link rel="alternate" type="text/markdown">` | generated | In every deck's `<head>`, points to its sheet. |

## 4. `talks.json` entry

```json
{
  "id": "2025-10-forkit-kl-homelab-renaissance",   // YYYY-MM-<slug>, permanent, also the sheet filename
  "title": "Homelab Renaissance: 21 Stacks, 200 Services, Full Autonomy",
  "date": "2025-10-25",                            // origin date (see §2)
  "date_note": "…",                                // optional: why the date is approximate
  "event": "Fork It",
  "venue": "Sunway University", "city": "Kuala Lumpur", "country": "Malaysia",   // null for studio/online
  "lang": "en",                                     // en | fr | …
  "format": "talk",                                 // talk | keynote | panel | workshop | podcast | interview
  "series": "MACI", "episode": 131,                 // podcasts in a series (series defined in site.json)
  "speakers": ["…"], "guests": "…",                 // co-speakers / podcast guests
  "duration": 2459,                                 // seconds, when known
  "abstract": "…",                                  // 1–3 sentences, human-written
  "tags": ["…"],
  "slides": "2025-10-forkit-kl-homelab-renaissance/",  // or null
  "video": "https://www.youtube.com/watch?v=…",        // or null
  "links": [{ "label": "Event", "url": "…" }]
}
```

Required: `id`, `title`, `date`, `event`, `lang`, `format`, and at least one of `slides` / `video` / `links`.
`format` ∈ {podcast, interview} puts the item in the *Podcasts* tab; everything else is a *Talk*.

**Not given yet — the backpack.** `status` is absent for anything already given. Two other values put the item in
the *Backpack* tab, outside the map and the counts:
- `"upcoming"`: booked, with its real `date`, `event` and `city`. Once given, remove `status` and add the recording.
- `"available"`: a deck ready to give on request. `date` = when the deck was finished, `slides` required; optional
  `langs` (e.g. `["fr", "en"]`), `audience`, `title_en`. In `llms.txt` it is listed under *Ready to give*, and in
  JSON-LD it is a `PresentationDigitalDocument` (not an `Event`), so agents never claim it was given.

### 4.1 Languages of the human site

The catalog is built once per language (`/`, `/fr/`, `/zh/`…), with `hreflang` alternates and a switcher.
Interface strings live in `tools/i18n.json` (one block per language, same keys everywhere). `talks.json` abstracts
are in English; translations go to `translations.json` (`{"fr": {"<id>": {"abstract": "…"}}}`), missing ones fall
back to English with `lang="en"`. Titles are never translated. A language marked `"draft": true` is built only with
`build-index.py --draft` (gitignored, never deployed) until a native speaker has reviewed it. Sheets and `llms.txt`
stay in English (§2).

## 5. Talk sheet (`ai/<id>.md`)

### 5.1 Front matter (YAML subset: `key: value`, `key: "quoted"`, `key: [a, b]`)

| Field | Req. | Notes |
|---|---|---|
| `sheet_format` | ✓ | Spec version the sheet follows, e.g. `1.0`. |
| `id` | ✓ | Same as `talks.json` and the filename. |
| `title` | ✓ | Original title. |
| `title_en` | | English translation when the original is not English. |
| `speaker` | ✓ | The site owner. |
| `with` | ✓ | Co-speakers / hosts / guests, `[]` if solo. |
| `date` | ✓ | Same as `talks.json`. |
| `event`, `place`, `format`, `language` | ✓ | `place` is "online / studio" for podcasts. |
| `duration` | | `h:mm:ss` or `mm:ss`. |
| `video`, `slides` | ✓ | URL or `null`. |
| `topics` | ✓ | 4–8 lowercase topics. |
| `source` | ✓ | Provenance sentence, e.g. "Summary written by Claude (AI) from the auto-generated YouTube captions, and the slides. Not a verbatim transcript; check the recording before quoting." |
| `review` | ✓ | `none` (AI draft, unread) · `spot-checked` (speaker validated flagged points) · `reviewed` (speaker read it all). |
| `review_date` | | ISO date of the last review. |

### 5.2 Body, in this order

1. `# <title>`
2. `**In one sentence:** …` — ≤ 40 words, what it is and the speaker's angle. Reused in `llms.txt` and JSON-LD.
3. `## TL;DR` — 3–5 sentences.
4. `## <First name>'s key points` — 5–10 bullets, each a position the speaker actually expresses, phrased as
   theirs ("He argues that…"). In podcasts, only clearly attributable contributions; few is fine.
5. `## Outline` — 8–25 entries: `- [mm:ss](<video>&t=<s>s) …`. Without a recording: "Slides 3–5: …".
6. `## Notable quotes` — 2–5 quotes ≤ 25 words, original language, `— <who>, [mm:ss](…)`; italic English
   translation under French quotes. Only what is clearly in the captions.
7. `## Questions this answers` — 4–10 `**Q: …**` / `A: …` pairs phrased the way people ask, each answer citing a
   timestamp.
8. `## Mentioned` — comma-separated products, projects, organisations, places, public people.
9. `## Slides outline` — only when there are slides: `1. Title — gist`.

### 5.3 Content rules

- Length: 600–1,600 words (up to 2,200 for recordings over 90 minutes). Short clips can be shorter.
- No invented facts, figures, dates or names. Garbled caption terms are corrected only when context makes the
  correction certain; otherwise omitted.
- Figures stay as said at the time. If the speaker's view changed later, update `_about.md`, not the old sheet.
- No private information beyond what is in the recording; leave out family details and other people's private lives.
- Plain Markdown, no HTML.

## 6. `llms.txt` structure

Follows [llmstxt.org](https://llmstxt.org): H1, blockquote summary, free text, then H2 sections of links.

```
# <site title>
> <one-paragraph summary: who, what this site contains, languages>
<background paragraph>
Find <first name> elsewhere: [LinkedIn](…), [Mastodon](…), …
## Recurring positions          ← from ai/_about.md, each with source links
## How to use this site         ← provenance, attribution policy, sheet sections
## Talks                        ← - [Title](sheet URL): date, city (LANG). <one sentence>
## Podcasts and interviews      ← same; items without a sheet yet are listed as "(summary pending)"
## Optional                     ← llms-full.txt, ai/index.json, the spec, the human site
```

**Recurring positions** are the most valuable part for agents and the easiest to let rot. Rules:
each position is one bold claim + 1–3 sentences + `Sources:` links to sheets; a position needs at least two
sources or one talk dedicated to it; when a new item adds evidence, add its link; when it contradicts a position,
rewrite the position and keep the history in words ("about 80% in 2025, close to 87% by October 2026").

## 7. Workflow: adding an item

1. Add the entry to `talks.json` (and a place in `site.json` if the city is new). For a deck, add its folder.
2. `python3 tools/fetch-transcript.py <id>` → `transcripts/<id>.txt` (auto-captions with `[h:mm:ss]` markers,
   chapters, description). Some videos have no YouTube captions at all: the tool then downloads the audio and
   transcribes it **locally** with Whisper (large-v3-turbo); the audio never leaves the machine and the transcript
   header records which source was used. Keep yt-dlp up to date (YouTube breaks old versions).
   **Podcasts and multi-speaker recordings:** `python3 tools/identify-speaker.py <id>` tags each line with the
   share spoken by the site owner (`[0:19:57] {erwan 100%} …`). It is speaker *identification*, not diarisation:
   a local voiceprint (SpeechBrain ECAPA, built once with `--enroll` from solo talks) is compared with every
   3-second window; other voices are never identified or named. The voiceprint is biometric data: it lives outside
   the repo and is never published. Tags are evidence for attribution (§2): ≥ 70% clear, 30–70% mixed, < 30% someone else.
3. Write `ai/<id>.md` following §5 — by hand or with the `/talk-sheet <id>` Claude Code skill shipped in the repo,
   which applies this spec and the attribution rules. `review: none`.
4. Revisit `ai/_about.md`: does the item add a source to a position, change one, or create one?
5. `python3 tools/build-index.py` — regenerates everything and runs `tools/check.py`; the build fails on errors.
6. Speaker reviews the sheet (at least the claims attributed to them), sets `review` and `review_date`.
7. `tools/publish.sh` — checks, pushes the source, deploys.

A pre-commit hook runs the same checks, so an inconsistent state cannot be committed.

## 8. Checks (`tools/check.py`)

Errors (build fails): missing required `talks.json` fields; duplicate or malformed ids; sheet without a
`talks.json` entry; front-matter field missing or not matching `talks.json` (id, date, video, slides);
unknown `sheet_format` major version; required section missing or out of order; outline/quote/Q&A link not in
`…&t=<seconds>s` form or pointing to another video; timestamp beyond the recording's duration; `_about.md` linking
to a sheet that does not exist; city without coordinates.

Warnings: item without a sheet; sheet with `review: none` older than 30 days; length outside bounds; fewer than
4 Q&A; position in `_about.md` with a single source.

## 9. Open questions (critique welcome)

1. **English-only sheets.** Good for reach, but French nuance is lost and quotes need translation. Bilingual sheets
   double the maintenance. Alternative: `ai/<id>.fr.md` only for French recordings?
2. **AI-written summaries.** Fast and consistent, but they can be confidently wrong. Is `review` + provenance
   enough, or should unreviewed sheets stay out of `llms.txt`?
3. **No transcripts published.** Protects co-speakers and avoids amplifying caption errors, but agents cannot
   verify quotes without watching. Publish cleaned transcripts for solo talks only?
4. **Positions synthesis.** `_about.md` is the most useful and the most editorial part. Who owns it — the
   speaker, or a reviewer?
5. **Attribution in podcasts.** Solved for the owner's voice by local speaker identification (§7). Full diarisation
   (naming every guest) is deliberately not done: guests did not consent to a voiceprint. Is the owner-only
   approach enough, or should co-hosts be able to opt in?
6. **Standards overlap.** llms.txt is young and not universally read; schema.org JSON-LD is widely read but terse.
   We ship both. Is that the right trade-off?
