skills/conf-schedule-optimization/SKILL.md
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.
npx skillsauth add lyndonkl/claude conf-schedule-optimizationInstall 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.
Choosing what to attend is a constrained optimization problem, and treating it as one is what separates a real schedule from a wishlist. Events are variables; time slots are their domain; hard constraints (you cannot be in two rooms at once, you need minutes to walk between them, a capped workshop may be full, you said you must catch the opening keynote) define what is feasible; and a weighted objective over the feasible plans defines what is good. The output is the best feasible plan plus an honest account of what it could not decide and what it gave up to score well.
The objective is genuinely multi-term and the terms are in real conflict: maximizing interest fights breadth (seeing across themes), both fight pacing (a day of six back-to-back talks with fifteen-minute gaps is worse than five with a real break), and all three fight serendipity (deliberately leaving room for the unplanned good thing). You do not resolve this by pretending one term dominates. You jointly optimize a weighted sum whose weights are the attendee's — handed over from the preference profile, never invented here.
Two disciplines keep it honest. Conflict-surfacing: when two talks overlap and both score high and the profile cannot break the tie, the system flags it for the person rather than quietly choosing — that flag is the natural output of a model that tracks its own uncertainty. And the Goodhart caution: any score you optimize produces a substitution effect somewhere the score did not name (optimize raw interest and you quietly sacrifice breaks; optimize breadth and you skip the one talk they cared about most). So the weights stay user-owned, the conflicts get surfaced, and the schedule reports what it traded away.
{
"generated_on": "YYYY-MM-DD",
"method": "greedy-local-search | ilp",
"selections": [
{
"day": "YYYY-MM-DD", "start": "HH:MM", "end": "HH:MM",
"event_id": "...", "room": "...",
"score": 0.0, "why": "why this won its slot",
"alternatives": [ { "event_id": "...", "score": 0.0, "why_not": "why it lost / is recorded / lower affinity" } ]
}
],
"free_blocks": [ { "day": "YYYY-MM-DD", "start": "HH:MM", "end": "HH:MM", "purpose": "break | buffer | travel | meal" } ],
"objective_breakdown": {
"interest": 0.0, "breadth": 0.0, "pacing": 0.0, "serendipity": 0.0,
"total": 0.0, "weights": { "interest": 0.0, "breadth": 0.0, "pacing": 0.0, "serendipity": 0.0 }
},
"unresolved_conflicts": [
{
"day": "YYYY-MM-DD", "slot": "HH:MM-HH:MM",
"candidates": ["event_id_a", "event_id_b"],
"why_unbreakable": "both score within epsilon; profile does not rank them",
"needs": "your decision"
}
],
"constraints_applied": { "travel_time": "...", "capacity": "...", "hard_constraints": ["..."] },
"tradeoffs_note": "what the chosen weighting sacrificed (the Goodhart honesty line)"
}
Separate the two kinds of rule cleanly:
capacity_constrained session counts only if the attendee can realistically get a seat; and the attendee's own hard_constraints from the profile (must-attends are forced in; blackouts/kept-free blocks are forced out).A plan that violates a hard constraint is not a worse plan, it is not a plan. Filter first, optimize second.
Score a candidate plan as w_interest·Interest + w_breadth·Breadth + w_pacing·Pacing + w_serendipity·Serendipity, with the ws from the profile:
Anti-fragmentation deserves its own emphasis because it is the easiest thing to get wrong. Six non-adjacent talks with fifteen-minute gaps is a worse day than the same six clustered with a genuine break — the human needs contiguous recovery, hallway conversations, and food. Model free time as blocks, reward the longest contiguous block, and treat a real break as a first-class good in the objective, not as leftover space.
When two (or more) talks overlap, both score within an epsilon of each other, and the profile gives no basis to rank them, flag it in unresolved_conflicts with both candidates and why the model cannot break the tie. Do not flip a coin and hide it. This is the honest output of a system that knows both talks score high and that its model of the person cannot settle it alone — exactly the moment to hand the decision back. (When the profile does break the tie, pick, and record the loser in alternatives with a why_not.)
resources/schedule.py implements with numpy/pandas/networkx only.pulp/ortools when installed and an optimum is wanted. Document the upgrade; do not require it.Record which ran in method.
Whatever the weights, the plan sacrificed something the weights under-counted. State it in tradeoffs_note: "Weighted toward interest, so day 2 is dense and breaks are short" or "Paced/breadth weighting means your single highest-interest talk was dropped for coverage." Naming the substitution is the antidote to optimizing a proxy and pretending it was the goal.
□ Step 1: Load events.json, affinities.json, profile.json; load rooms + travel matrix + day bounds from config.
□ Step 2: Force in the profile's must-attends; force out blackouts/kept-free blocks.
□ Step 3: Build feasibility: per slot, the set of events that do not overlap, respect travel time,
and are seatable (capacity).
□ Step 4: Score each event for the attendee (affinity × region interest, depth/format fit).
□ Step 5: Greedy-seed a feasible plan; run local search to maximize the user-weighted objective.
□ Step 6: Detect overlaps where top candidates tie within epsilon and the profile can't rank them ->
unresolved_conflicts (do not auto-pick).
□ Step 7: Compute free_blocks (reward the longest contiguous block) and objective_breakdown.
□ Step 8: Write schedule.json + schedule.md (day-by-day) + conflicts.md (decisions) + rationale.md
(picks, objective_breakdown, tradeoffs_note). Return the schedule.json path.
Danger: A hard-coded weighting silently sets the schedule's whole character.
Guardrail: Read objective_weights from the profile. Never invent or override them.
Red flag: A default weight vector compiled into the optimizer.
Danger: A silent coin-flip on two high-value talks looks like a confident decision it isn't.
Guardrail: Tie within epsilon + profile can't rank ⇒ unresolved_conflicts, not a pick.
Danger: A maximally-packed day reads as "optimal" and burns the human out. Guardrail: Reward the longest contiguous free block; treat a real break as a first-class objective term.
Danger: Optimizing one proxy quietly sacrifices an unnamed good.
Guardrail: State what the weighting traded away in tradeoffs_note.
Danger: A high-scoring plan that double-books a room or skips a must-attend. Guardrail: Filter to feasible before optimizing; force must-attends in, blackouts out.
Danger: Capping candidates or writing to a calendar without saying so. Guardrail: Log any caps. Do not change the profile. Calendar export only on explicit instruction.
| Constraint | Source | Effect |
|---|---|---|
| No time overlap | event times | one selection per instant |
| Travel time | config room matrix | min gap between different-room picks |
| Capacity / registration | axes.capacity_constrained | a capped session may be infeasible |
| Must-attend / blackout | profile hard_constraints | forced in / forced out |
| Term | Rewards | Typical signal | |---|---|---| | interest | talks the attendee cares about | affinity × region interest | | breadth | coverage across themes | distinct themes (diminishing returns) | | pacing | contiguous free time, low fragmentation, preferred times | longest free block; gap penalty | | serendipity | slack + occasional outlier pick | deliberate unscheduled space |
| Situation | Method |
|---|---|
| No solver deps (default) | greedy + local search (resources/schedule.py) |
| pulp/ortools present and an optimum wanted | ILP |
tradeoffs_note.Day 2, 11:10–11:30 has two talks scoring 0.81 and 0.79 — one on Agents, one on Retrieval, both close to the attendee's interests, and the profile does not rank Agents over Retrieval. The optimizer does not pick. It writes an unresolved_conflicts entry naming both and "both within epsilon; profile does not rank these themes," needs: your decision. The person settles it in one line; the schedule never pretended it had.
Two feasible plans tie on interest. Plan A: six talks, gaps of 15–20 minutes all afternoon. Plan B: five talks plus one contiguous 80-minute afternoon block. With the attendee's pacing-leaning weights, Plan B scores higher — the long break is a modeled good, not wasted space — and tradeoffs_note records that one lower-interest talk was dropped to buy the break, which is exactly the trade the person asked for.
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
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.
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.