skills/conf-program-extraction/SKILL.md
Parse a heterogeneous conference program (markdown, HTML, PDF-derived text, or JSON) into normalized event records with per-field confidence scores and independent classification axes (topic, depth, format, prerequisites, recorded, capacity). Detects the program's format before extracting, treats every inferred field as uncertain (present vs inferred vs missing), and flags thin or missing abstracts so downstream enrichment can target them. Conference-agnostic. Use when ingesting a conference or event schedule into a structured store, normalizing a talk/session list, or extracting per-session metadata with calibrated confidence. Trigger keywords - program ingestion, parse schedule, session extraction, event records, conference program, talk metadata, per-field confidence.
npx skillsauth add lyndonkl/claude conf-program-extractionInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
A conference program is the noisiest structured document you will routinely parse. Formats differ between conferences and often between days of the same conference; abstracts are short, frequently missing, and written to sell rather than to classify; and the fields a downstream scheduler needs most — how advanced a talk is, whether it is recorded, whether it has a capacity cap — are almost never stated outright. They have to be inferred, and an inference you cannot tell apart from a fact is a liability.
This skill turns raw program text into normalized event records in which every field carries a calibrated confidence and a one-line basis, and in which the classification signal is split into independent axes rather than crushed into a single blob. The governing principle is honest uncertainty: a record should make it obvious to the next stage which fields are solid, which are guessed, and which are simply absent.
It does three jobs and stops: detect the format, extract into the schema, score the confidence. It does not enrich thin abstracts (that is conf-abstract-enrichment), cluster (that is conf-theme-clustering), or schedule. It only flags what is thin so the next agent knows where to spend effort.
The output is a single JSON file with a meta block (so an orchestrator can verify the stage gate without parsing every record) and an events array. This schema is canonical — reproduce it exactly; downstream skills read these field names.
{
"meta": {
"conference_id": "string",
"source_ref": "path or url of the parsed program",
"generated_on": "YYYY-MM-DD",
"counts": { "total": 0, "talk": 0, "workshop": 0, "keynote": 0, "panel": 0, "other": 0 },
"confidence_rollup": {
"mean_field_confidence": 0.0,
"low_confidence_field_fraction": 0.0,
"events_with_thin_abstract": 0
},
"status": "done | partial",
"format_detected": "short description of the delimiter/heading pattern used"
},
"events": [
{
"id": "kebab slug, day+time+speaker+title-stub, e.g. d2-0900-laurie-voss-vibes-to-production",
"title": "string",
"speakers": [ { "name": "string", "affiliation": "string | null", "affiliation_confidence": 0.0 } ],
"session_type": "talk | workshop | keynote | panel | sponsor | expo | unknown",
"track": "string | null",
"room": "string | null",
"day": "YYYY-MM-DD | null",
"start": "HH:MM | null",
"end": "HH:MM | null",
"abstract_raw": "string | null",
"abstract_enriched": null,
"axes": {
"topic": ["string"],
"depth": { "value": "intro | intermediate | advanced | unknown", "confidence": 0.0, "basis": "string" },
"format": { "value": "talk | workshop | keynote | panel | unknown", "confidence": 0.0, "basis": "string" },
"prerequisites": { "value": ["string"], "confidence": 0.0, "basis": "string" },
"recorded": { "value": true, "confidence": 0.0, "basis": "string" },
"capacity_constrained": { "value": true, "confidence": 0.0, "basis": "string" }
},
"enrichment": null,
"field_confidence": {
"title": 1.0, "speakers": 0.0, "track": 0.0, "room": 0.0, "time": 0.0, "abstract": 0.0
},
"source_ref": "the raw heading or line range this record came from"
}
]
}
abstract_enriched and enrichment are written as null here and left for conf-abstract-enrichment to fill. recorded and capacity_constrained use null for the value when there is genuinely no signal either way (and confidence 0.0).
Never assume a fixed format. Read a representative slice first and identify the recurring shape: the heading level that delimits a session, the field order, the inline metadata convention, the grouping (by day, by track, by time). Write what you found into meta.format_detected. Only then extract.
A typical session line looks like one of:
#### 9:00am-11:00am: From Vibes to Production — Laurie Voss
(sponsor) [Track 1] | Track: Track 1 | Status: tentative
<abstract paragraph or "TBA">
The reusable move: derive a small set of regexes/anchors from the detected pattern (time anchor, title/speaker split on the em dash, the (type) [room] | Track: x metadata line), not from a format you remember from another conference. When a later day breaks the pattern, re-detect rather than forcing the old anchors.
A talk is not a single label. Separate the signal into axes that vary independently, because the scheduler and the elicitor will query them independently:
["evaluation","agents","rag"]) — what it is about. Multi-valued by default.intro|intermediate|advanced) — how much prior knowledge it assumes.talk|workshop|keynote|panel) — how it is delivered.The point of separation: "advanced workshop on RAG eval, recorded, capacity-capped" is four orthogonal facts, each with its own confidence. A blob throws away the ability to say "I'm confident it's a workshop but only guessing it's advanced."
Every field lands in one of three states, and the confidence number must reflect which:
0.9–1.0.format=workshop (strong, ~0.85); "required prerequisites" in the abstract ⇒ depth=advanced (moderate, ~0.6); session in a "101" track ⇒ depth=intro (moderate). Confidence 0.3–0.8 with the basis naming the signal.value: null/unknown, confidence 0.0, basis "absent".The basis string is not decoration; it is what lets a human or the elicitor audit a low-confidence schedule decision later ("depth: advanced (0.6, inferred from 'assumes familiarity with embeddings')").
Mark an abstract thin when it is absent, "TBA", or too short/generic to classify from (a rough rule: under ~25 words, or no concrete nouns a topic could attach to). Record this in two places: a low field_confidence.abstract, and the meta.confidence_rollup.events_with_thin_abstract count. Do not web-search or invent text — that is conf-abstract-enrichment's job, and keeping the boundary clean is what lets enrichment carry its own provenance and confidence.
Conferences run many rooms at once, so the same start time appears repeatedly across the program. That is expected — do not dedupe by time. Each (time, room) pair is a distinct event. Capture room whenever present (it is what the scheduler turns into travel-time and overlap constraints). When a time is a range, fill both start and end; when only a start is given, leave end: null rather than guessing a duration.
□ Step 1: Read a representative slice of the program; identify the recurring session shape.
□ Step 2: Record the detected format in meta.format_detected; derive extraction anchors from it.
□ Step 3: Walk the program in document order, emitting one EventRecord per session.
□ Step 4: For each record, fill the present fields verbatim with high confidence + source_ref.
□ Step 5: Infer the axes (depth/format/prereqs/recorded/capacity/topic) from real signals;
attach confidence + a one-line basis to each; use null/unknown + 0.0 where absent.
□ Step 6: Score field_confidence per field; mark thin/missing abstracts.
□ Step 7: Mint a stable, collision-resistant id (day+time+speaker+title-stub, kebab-case).
□ Step 8: Compute meta.counts and meta.confidence_rollup; set status (done | partial).
□ Step 9: Write the {meta, events} JSON; write a human-readable index.md alongside it.
Danger: A null abstract or unknown room becomes a plausible-sounding guess that the scheduler then trusts.
Guardrail: Missing ⇒ null/unknown + confidence 0.0 + basis "absent". The only text in abstract_raw is text that was in the program.
Red flag: You wrote a sentence no source contained.
Danger: Everything gets 0.9, so the confidence signal carries no information and the elicitor/scheduler cannot tell solid from guessed.
Guardrail: Verbatim ⇒ 0.9–1.0; strong inference ⇒ 0.6–0.85; weak inference ⇒ 0.3–0.55; absent ⇒ 0.0. If you cannot name the basis, the confidence is low.
Red flag: A field marked 0.9 whose basis is "seemed right".
Danger: Collapsing the three states loses exactly the information downstream stages need.
Guardrail: Keep them distinct in both the value and the confidence. An inferred advanced and a stated Advanced must not look identical in the record.
Danger: A wrong extraction is unauditable.
Guardrail: Every record keeps source_ref (the raw heading or line range). A human can always trace a record back to the program.
Danger: "Evaluating agentic RAG" filed under one topic disappears from two of the three interests it actually serves.
Guardrail: axes.topic is a list; populate all genuine topics. (Soft cross-theme membership is conf-theme-clustering's job, but it can only do it if the topics survive extraction.)
Danger: Treating repeated start times as duplicates deletes real concurrent talks.
Guardrail: One record per (time, room). Same time, different room = two events.
| Axis | Values | Typical basis (when not stated) | Usual confidence | |---|---|---|---| | topic | list of strings | title + abstract nouns | 0.5–0.9 | | depth | intro / intermediate / advanced / unknown | "101"/"intro" wording, stated prereqs, jargon density | 0.3–0.7 | | format | talk / workshop / keynote / panel | session_type tag, "workshop"/"lab" in title, duration | 0.7–0.95 | | prerequisites | list of strings | explicit "prerequisites"/"assumes" phrasing | 0.3–0.8 | | recorded | true / false / null | conference-wide policy (from config), "not recorded" notes | 0.0–0.6 | | capacity_constrained | true / false / null | workshop/lab ⇒ likely true; main-stage talk ⇒ false | 0.3–0.8 |
| State | Definition | Confidence | Example |
|---|---|---|---|
| Present | Stated verbatim | 0.9–1.0 | Title; "Advanced" tag; explicit time |
| Strong inference | One clear signal | 0.6–0.85 | (workshop) ⇒ format=workshop |
| Weak inference | Indirect signal | 0.3–0.55 | dense jargon ⇒ depth=advanced |
| Absent | No signal | 0.0 | room not listed ⇒ room=null |
events_with_thin_abstract flag plus low field_confidence.abstract.axes.topic and abstracts to build the theme map; relies on multi-topic survival.capacity_constrained/recorded from a base rate for the session type rather than this specific listing.Input:
#### 9:00am-11:00am: From Vibes to Production: Evaluating and Shipping AI Agents That Work 101 — Laurie Voss
(sponsor) [Track 1] | Track: Track 1
Building an AI demo is easy. Knowing whether it works in production — and keeping it working — is the hard part. Hands-on: build an eval harness...
Record (abbreviated):
title: "From Vibes to Production: Evaluating and Shipping AI Agents That Work 101" — confidence 1.0speakers: [{name:"Laurie Voss", affiliation:null, affiliation_confidence:0.0}]session_type: "sponsor"; room: "Track 1"; track: "Track 1"; start:"09:00", end:"11:00" — all Presentaxes.depth: {value:"intro", confidence:0.6, basis:"'101' in title"}axes.format: {value:"workshop", confidence:0.75, basis:"'Hands-on: build...' + 2h block"}axes.topic: ["evaluation","agents","production","observability"]field_confidence.abstract: 0.9 (rich); not flagged thin.Input:
#### 2:30pm: Sponsor Lightning — TBA
(expo) [Expo Stage NE]
Record:
title: "Sponsor Lightning" — 1.0; speakers: []; session_type:"expo"; room:"Expo Stage NE"; start:"14:30", end:nullabstract_raw: null; field_confidence.abstract: 0.0 — flagged thin (counts toward events_with_thin_abstract)axes.depth: {value:"unknown", confidence:0.0, basis:"absent"}axes.capacity_constrained: {value:false, confidence:0.5, basis:"expo stage, open seating"}null/unknown at confidence 0.0 — the record advertises its own thinness rather than hiding it.testing
Cluster a conference's event records into a small set of coarse themes with finer sub-clusters, an explicit outlier bucket, and soft (multi-membership) affinities — using the hybrid embed-then-label pipeline (embed abstracts, reduce, density-cluster, then LLM-label the clusters) when embedding libraries are available, and an LLM-reasoned hierarchical fallback when they are not. Embeddings do the grouping; the LLM only names the groups. Conference-agnostic. Use when turning structured event records into a navigable theme map for preference elicitation and scheduling, when you need 6-8 reasonable themes rather than 20 muddy ones, or when overlapping talks must belong to more than one theme. Trigger keywords - theme clustering, cluster talks, embed then label, soft membership, outlier talks, conference themes, topic map.
development
Build a personal conference schedule as a constraint-optimization problem — hard constraints (no time overlap, room-to-room travel time, capacity/registration, the attendee's own must-attends and blackouts) plus a user-owned weighted objective trading interest against breadth, pacing (maximize contiguous free time), and serendipity. Surfaces unbreakable conflicts (two high-value overlapping talks the model cannot rank) as decisions for the human rather than silently picking, and reports what each choice traded away. Conference-agnostic. Use to turn a preference profile plus a theme map into a day-by-day plan, to resolve overlapping sessions, or to balance a packed vs paced schedule. Trigger keywords - schedule optimization, conference schedule, constraint optimization, overlapping talks, contiguous free time, conflict surfacing, packed vs paced.
development
Build a personalized preference profile from a small number of well-chosen, cluster-grounded questions instead of a long survey. Represents the person's interests as an uncertainty region over the theme map, picks the single highest-information-gain choice-based question (contrasting real talks from different clusters), balances exploiting known interests against exploring uncertain ones, deliberately injects outlier probes to fight selection bias, and stops as soon as the schedule would be stable. Also elicits the user-owned objective weights and hard constraints. Interactive — runs where it can actually ask the person. Conference-agnostic. Use to turn a theme map into a preference profile, to decide what to ask a conference attendee, or to elicit scheduling priorities. Trigger keywords - preference elicitation, ask few questions, information gain, choice-based questions, selection bias probe, objective weights, attendee preferences.
testing
The multi-agent communication and orchestration discipline for a staged pipeline whose stages each carry calibrated uncertainty. Agents talk through files with defined schemas and status fields (never free text); the orchestrator passes input paths plus an explicit output path plus a return contract, verifies each artifact's confidence before advancing a stage gate, holds and integrates worker outputs rather than passing them through, freezes its protected control logic against a checksum, maintains a diversity floor, hardens the fragile handoffs not the resilient hubs, and names the substitution effect every optimized score produces. Conference-agnostic; preloaded by a pipeline orchestrator. Use when coordinating a staged agent pipeline, designing the orchestrator-worker contract, or deciding when to advance, reconcile, or escalate. Trigger keywords - pipeline orchestration, stage gates, structured agent communication, invariant guard, confidence propagation, conflict surfacing.