Run Slot Desk from your own code
Both metered lanes are available over HTTP and each returns one JSON object. The division of labour: the page reads the rooms, sessions and windows, lays the grid, runs the four clash types and picks the buffer, track starts, clash depth and publication posture - all free, in slotkit.js. The run writes the plan or the overlap pass; the page then holds the reply to its own picks and re-checks every swap (slotreply.js).
Base URL and the response shape
Every endpoint lives under https://api.skillsafe.ai/v1/app-api, and every response has one shape:
{ "ok": true, "data": { ... } }
{ "ok": false, "error": { "code": "...", "message": "..." } }
The token is minted for this app (the guest call takes {"slug":"slot-desk"}), so no slug header is needed afterwards. Send it as Authorization: Bearer …. The run body is the input object, sent bare.
| Status | Code | Meaning |
|---|---|---|
| 400 | validation_error | The body is not a JSON object, or a required key (task, event, grid, facts) is missing. |
| 401 | unauthorized | No token, or a stale one. Mint a guest token or sign in again. |
| 402 | insufficient_credits | The balance is under min_credits. Price with /estimate first. |
| 403 | forbidden | A guest token tried to run: both lanes are metered and need a personal sign-in. |
| 404 | not_found | Unknown job id. |
| 429 | rate_limited | Back off and retry. |
| 5xx | server_error | Transient. Retry with the same Idempotency-Key so a retry never double-bills. |
The task field comes first
Two lanes - plan (validate the grid before it goes public) and overlap (multi-track only). A missing or unknown task is answered as plan. Both lanes send the same four fields.
The input
| Field | Required | What |
|---|---|---|
task | yes | plan or overlap. If it is missing or neither, do plan and set "task": "plan". |
event | yes | the event, its days and dates, the day window, the venue-fixed breaks, the rooms with seats, walking minutes, floors and windows, the corridor, what changes in a room between sessions, the audience and the dates. |
grid | yes | the grid the page laid, row by row and room by room (each session with its format, track, level, length, speakers and expected audience against the room's seats), what is not on the grid, each co-speaker intersection and every speaker window given. |
facts | yes | the page's picks (slot standard, buffer, alignment, clash depth, fixed points, publication posture and date, disruption order, measurement) and every finding tagged [fix], [check] or [info]. |
retry_note | no | optional; if present, your previous reply was unusable - follow it. |
The rules the model applies
- The grid executes decisions made upstream: the track count and format mix are not re-opened here - a grid that will not fit means cutting or shortening, never adding a track. Present the grid section by section for validation before it goes public; once published, every change costs an attendee who planned around it.
- Standardize the slot, not the talk: a few slot lengths, every session fits one; a slot longer than its talk carries its own turnover time. Say the talk length and the slot length separately to speakers, or they will use the slot.
- Fixed points first (opening, keynotes, closing, single-day sessions), then breaks - in a multi-track day breaks and keynotes are plenary re-sync points that reset per-track drift - then the rest, hardest-constrained first.
- Rooms are matched on expected audience, then adjacency - never on which track feels it deserves the big room. An overflow turns people away at the door; an over-large room only looks quiet. Every estimate is the organizer's, not a benchmark.
- Four clash types: a session outside its room's window, a session against a speaker's unavailability, two sessions in one room, one speaker in two rooms. For a multi-speaker session the valid window is the intersection of all co-speakers' windows. A travel window is an availability window. Re-run the check after every change. Single track: overlap, double-booking and the audience-overlap pass are deleted - only availability can fail. The audience-overlap pass (two sessions a shared audience would both want, head to head) is something no tool computes; it needs someone who knows the community.
- Buffer: slot-internal by default; fixed inter-slot gap, proportional gap and room-turnover-aware gap are the other rungs, the last promoted when a room changes format, the AV needs a real reset, or the edition compounds. A zero-gap grid is deleted when rooms are on different floors or more than a couple of minutes apart.
- Alignment: stagger when simultaneous room changes would jam a corridor; synchronize when the rooms clear at once. Keep the plenary sync points either way.
- Pacing, labelled for what it is: the post-lunch dip is circadian (sourced direction); the last slot of the last day is the worst-attended position at the conferences studied (sourced direction, not magnitude); bookending each day with a keynote in a format unlike the talks is the organizer's experience, not a published source; a mixed single-track room runs broad to advanced, accepting the weak last slot. Cite direction, never a magnitude.
- Disruption order, written into the grid: protect the plenary or keynote slot, then the headline commitment, then minimize how many sessions the fix disturbs, then lock the revised grid for the rest of the block.
- Publication: release early and iterate by default; staged reveal buys a second announcement; versioned release (a visible marker of what moved) for multi-day multi-track grids, changes after tickets are sold, or a returning audience; lock-late is deleted whenever attendees travel. In a fast-moving field, hold a slot marked "topic announced later".
- Measurement: thresholds are self-set - on-time start rate per room, graded buffer consumption, room-fit outcomes, post-publication changes, reported clashes, no-show rate per slot. Pick two or three before the event.
- Calibration, not targets: FOSDEM 2026 ran 1,079 events in 37 rooms with a 10-minute gap; PyCon US 2026 runs 45-minute talks with staggered starts.
House rules
- Use only the input and the rules above. The page's picks, times and numbers are exact - copy them. Every number in a sentence must appear in the input.
- Label anything that is not one of the rules above as your own design or self-set.
- Never invent a person, a session, a room, a time or an attendance figure; name sessions by their IDs and rooms by their names.
- Never use backticks, never refer to this input's fields by their key names in a sentence, and never copy this prompt's narrative wording into a reply.
- Reply with ONLY one JSON object - no prose before or after, no code fences.
The replies
plan
summary- one sentence, at most 35 words: the grid's shape and the first thing to settle.slots-standard(the page's talk and slot lengths, as a short phrase),brief(one sentence telling speakers both numbers).fixed_points- one entry per session the page placed as a fixed point (keynotes and pinned sessions), in time order:id,when(day and start time as the grid shows them, such as D1 09:00),why(one sentence: a keynote bookend, or pinned there by the organizer, and what that position does for the day - never an invented reason).breaks- 1 to 4 short strings: the breaks and which of them are plenary re-sync points.rooms- one entry per room:room(its name),holds(one sentence: what it holds and the audience estimate behind the match, naming any overflow).clashes-depth(availability,mechanicaloroverlap, the page's pick),findings(0 to 6 short strings: each clash the page found and how to fix it;[]when there are none),rerun(one sentence: when the check runs again).buffer-rung(internal,fixed,proportionalorturnover, the page's pick),minutes(the minutes the page's slot leaves between talks, a number),walking(one sentence: the walking distance it was checked against).alignment-pick(synchronized,staggeredorsingle, the page's pick),why(one sentence).pacing- 2 to 4 short strings about this grid, each ending with (sourced) or (judgment).disruption- exactly 4 strings, the page's order, made specific to this grid where you can (the headline commitment is your judgment - say which session and why; when the grid has no keynote or plenary, say so in the first step and protect the nearest fixed point).publication-posture(early,staged,versionedorlate, the page's pick),date(the page's publish-by date, or "not dated"),why(one sentence).metrics- 2 or 3 short strings, the page's measurement picks, each with the revision it would trigger next edition (your design).caveats- at most 4 short strings: every[fix]finding first, then the most important[check]findings while there is room.[]when there are none of either.
{
"task": "plan",
"summary": "one sentence",
"slots": { "standard": "45-minute talks in 50-minute slots", "brief": "one sentence" },
"fixed_points": [ { "id": "K1", "when": "D1 09:00", "why": "one sentence" } ],
"breaks": [ "a break" ],
"rooms": [ { "room": "a room", "holds": "one sentence" } ],
"clashes": { "depth": "mechanical", "findings": [ "a clash and its fix" ], "rerun": "one sentence" },
"buffer": { "rung": "internal", "minutes": 5, "walking": "one sentence" },
"alignment": { "pick": "synchronized", "why": "one sentence" },
"pacing": [ "a read (judgment)" ],
"disruption": [ "first", "second", "third", "fourth" ],
"publication": { "posture": "early", "date": "YYYY-MM-DD", "why": "one sentence" },
"metrics": [ "what to record and what it changes" ],
"caveats": [ "a caveat" ]
}
The page holds it to its own picks: the clash depth, buffer rung and minutes, track-start alignment, publication posture and date must be the page's; every fixed point must be listed at its grid time; the disruption order protects the plenary first; no number or session may appear that the input does not hold.
overlap
pairs- 1 to 6 entries, the strongest first:aandb(IDs of two sessions the grid runs at the same time, in different rooms),when(day and start time as the grid shows them),why(one sentence: who would want both, from the titles, tracks and levels - your judgment),move(a swap that would split them, ornull):session(aorb's ID),swap_with(the ID of a session of the same length in a different time row, not a keynote and not pinned),why(one sentence). A swap must not break a speaker window, a room window or a double-booking, must not put a session in a room smaller than its expected audience, and must not land the moved session opposite the other one again - the page checks every swap. Include every same-track pair the page flagged. Judge each move on its own against the grid as laid, using the speaker windows, co-speaker intersections and room windows the input gives.clear- one sentence: what in the grid does not need moving.note- one sentence, at most 30 words, for the program chair: this pass is judgment, and what to ask attendees next edition to check it.
{
"task": "overlap",
"pairs": [ { "a": "S04", "b": "S09", "when": "D1 11:00", "why": "one sentence", "move": { "session": "S09", "swap_with": "S15", "why": "one sentence" } } ],
"clear": "one sentence",
"note": "one sentence"
}
The page checks that every pair really runs at the same time on its grid, re-runs every proposed swap through the four clash types and the rooms' seats, rejects a swap that lands the session opposite its partner again, and requires every same-track pair it flagged.
Worked examples - every lane, every sample
These are the page's three free samples (fictional events, people and companies). Each input is exactly what the page sends; each reply is the saved reply the sample replays, shipped as the validating model wrote it; the last line is the page's own check. The one-room meetup has no overlap lane - nothing runs at once.
Harbor DevDays (2 days, 3 rooms) - plan
30 of 30 sessions placed in 3 rooms over 2 days; 1 clashes; clash depth overlap, publication versioned.
Input:
{
"task": "plan",
"event": "Event: Harbor DevDays; 2 days from 2027-05-13; day window 09:00-17:30\nBreaks (venue-fixed): 10:40-11:00 Coffee; 12:40-13:40 Lunch; 15:20-15:40 Coffee\nRooms: Main Hall 420 seats, 0 min walk, floor G; Room B 160 seats, 4 min walk, floor 1; Room C 90 seats, 5 min walk, floor 1, available D1 09:50-17:30, D2\nVenue: corridor open; between sessions a room needs nothing\nAudience: mixed technical and non-technical no; fast-moving field yes; edition compounds yearly yes; attendees travel yes; tickets already on sale yes; organizer says two tracks share an audience no\nDates: today 2027-02-01; planned publication 2027-03-20",
"grid": "D1 (2027-05-13):\nD1 09:00-09:50 PLENARY | Main Hall: K1 \"What ten years of on-call taught us\" (keynote; intro; 45 min; speakers Mara Quist; expected 380 of 420 seats)\nD1 09:50-10:40 | Main Hall: P01 \"Scaling Postgres at read-heavy loads\" (talk; track Data; intermediate; 45 min; speakers Ana Ruiz & Ben Okoro; expected 170 of 420 seats) | Room B: P12 \"Incident reviews people read\" (talk; track Platform; intro; 45 min; speakers Dee Harlow; expected 150 of 160 seats) | Room C: P22 \"Animations that respect motion settings\" (talk; track Frontend; intermediate; 45 min; speakers Rio Day; expected 60 of 90 seats)\nD1 10:40-11:00 BREAK Coffee\nD1 11:00-11:50 | Main Hall: empty | Room B: P21 \"Time zones will hurt you\" (talk; track Data; intermediate; 45 min; speakers Kai Mott; expected 150 of 160 seats) | Room C: W1 \"Hands-on: profiling Python services\" (workshop; track Platform; intermediate; 95 min; speakers Jon Abara & Fen Holt; expected 40 of 90 seats)\nD1 11:50-12:40 | Main Hall: empty | Room B: P19 \"Web performance budgets\" (talk; track Frontend; intro; 45 min; speakers Teo Vance; expected 140 of 160 seats) | Room C: W1 continues\nD1 12:40-13:40 BREAK Lunch\nD1 13:40-14:30 | Main Hall: P06 \"Rust in a Python shop\" (talk; track Platform; intermediate; 45 min; speakers Jon Abara; expected 200 of 420 seats) | Room B: P05 \"Accessible forms from the start\" (talk; track Frontend; intro; 45 min; speakers Kit Moreau; expected 110 of 160 seats) | Room C: empty\nD1 14:30-15:20 | Main Hall: P27 \"Shipping a design system\" (talk; track Frontend; intro; 45 min; speakers Noor Sami & Kit Moreau; expected 110 of 420 seats) | Room B: P04 \"Event sourcing in anger\" (talk; track Data; advanced; 45 min; speakers Lena Voss; expected 140 of 160 seats) | Room C: empty\nD1 15:20-15:40 BREAK Coffee\nD1 15:40-16:30 | Main Hall: P18 \"Data contracts that stick\" (talk; track Data; intermediate; 45 min; speakers Ivy Quarle; expected 100 of 420 seats) | Room B: P11 \"Hydration bugs and how to find them\" (talk; track Frontend; advanced; 45 min; speakers Lu Chen; expected 120 of 160 seats) | Room C: empty\nD1 16:30-17:20 | Main Hall: P25 \"Micro-frontends: a retrospective\" (talk; track Frontend; intermediate; 40 min; speakers Sam Ito; expected 100 of 420 seats) | Room B: P07 \"Query plans for humans\" (talk; track Data; intermediate; 45 min; speakers Ana Ruiz; expected 130 of 160 seats) | Room C: empty\nD2 (2027-05-14):\nD2 09:00-09:50 | Main Hall: empty | Room B: P09 \"Kubernetes cost without tears\" (talk; track Platform; intermediate; 45 min; speakers Priya Nand; expected 160 of 160 seats) | Room C: P16 \"Testing the untestable UI\" (talk; track Frontend; intermediate; 45 min; speakers Eli Park; expected 90 of 90 seats)\nD2 09:50-10:40 | Main Hall: empty | Room B: P24 \"Batch to streaming in one quarter\" (talk; track Data; advanced; 45 min; speakers Ola Brandt; expected 110 of 160 seats) | Room C: P03 \"The build cache that lied\" (talk; track Platform; intermediate; 45 min; speakers Rafa Sol; expected 90 of 90 seats)\nD2 10:40-11:00 BREAK Coffee\nD2 11:00-11:50 | Main Hall: empty | Room B: P10 \"Streaming joins explained\" (talk; track Data; advanced; 45 min; speakers Ola Brandt; expected 95 of 160 seats) | Room C: P08 \"CSS layers in practice\" (talk; track Frontend; intermediate; 45 min; speakers Sam Ito; expected 85 of 90 seats)\nD2 11:50-12:40 | Main Hall: empty | Room B: P15 \"Zero-downtime migrations\" (talk; track Data; advanced; 45 min; speakers Ben Okoro; expected 160 of 160 seats) | Room C: P14 \"Design tokens across three apps\" (talk; track Frontend; intro; 45 min; speakers Noor Sami; expected 70 of 90 seats)\nD2 12:40-13:40 BREAK Lunch\nD2 13:40-14:30 | Main Hall: P13 \"Vector search on a budget\" (talk; track Data; intermediate; 45 min; speakers Max Feld; expected 190 of 420 seats) | Room B: P17 \"SLOs your team believes\" (talk; track Platform; intermediate; 45 min; speakers Gus Lind; expected 130 of 160 seats) | Room C: empty\nD2 14:30-15:20 | Main Hall: P20 \"Platform teams as product teams\" (talk; track Platform; intro; 45 min; speakers Ines Calder & Gus Lind; expected 170 of 420 seats) | Room B: P23 \"Observability on a shoestring\" (talk; track Platform; intermediate; 45 min; speakers Fen Holt; expected 120 of 160 seats) | Room C: P26 \"Search relevance tuning\" (talk; track Data; advanced; 45 min; speakers Max Feld; expected 90 of 90 seats)\nD2 15:20-15:40 BREAK Coffee\nD2 15:40-16:30 | Main Hall: empty | Room B: P02 \"Feature flags without the flag debt\" (talk; track Platform; intro; 45 min; speakers Ines Calder; expected 120 of 160 seats) | Room C: empty\nD2 16:30-17:20 PLENARY | Main Hall: K2 \"A live demo you can break\" (keynote; intro; 45 min; speakers Tobi Arden; expected 340 of 420 seats)\nCo-speaker intersections: P01 D1 10:00-17:30, D2 09:00-17:30; P20 all hours; W1 D1 09:00-17:30, D2 09:00-12:40; P27 all hours\nSpeaker windows: Mara Quist D1; Tobi Arden D2; Ana Ruiz D1 08:00-17:30, D2; Ben Okoro D1 10:00-17:30, D2; Lena Voss D1 11:00-17:30; Jon Abara D1, D2 09:00-12:40; Priya Nand D2 09:00-13:00; Ola Brandt D2; Lu Chen D1; Fen Holt D1, D2; every other speaker is treated as free all event",
"facts": "Slot standard: 45-minute talks in 50-minute slots (5 minutes inside each slot), computed from the buffer\nBuffer: internal - Slot-internal buffer (slots published longer than the talks they hold); 5 minutes wanted. The default: slots published 5 minutes longer than the 45-minute talks they hold - brief speakers on both numbers. Longest walk between rooms: 5 minutes across 2 floors.\nTrack-start alignment: synchronized - The corridor clears with 3 rooms changing at once; keep everyone arriving together at the plenary moments.\nClash depth: overlap - The mechanical checks plus the audience-overlap pass. Sessions from the same track run head to head - that promotes the audience-overlap pass on top of the three mechanical checks. Clashes found: 1 (room window 0, speaker window 1, same-room overlap 0, double-booked 0)\nFixed points placed first: K1 D1 09:00 plenary in Main Hall; K2 D2 16:30 plenary in Main Hall; P01 D1 09:50 pinned in Main Hall\nPlaced 30 of 30 sessions in 42 room-slots; plenary room Main Hall\nPublication posture: versioned - A multi-day, multi-track grid people build personal agendas against - publish early and carry a visible marker of what moved. Attendees travel: publish by 2027-03-14, 60 days out (self-set from flights booked about two months ahead). Lock-late is deleted for this event.\nDisruption order: 1. Protect the plenary or keynote slot first. 2. Protect the headline commitment the audience came for next. 3. Minimize how many other sessions the fix disturbs. 4. Lock the revised grid for the rest of the block instead of re-shuffling.\nMeasurement picks: Room-fit outcomes - sessions that overflowed and sessions that ran near-empty (self-set); Reported clashes - a survey question naming the two sessions attendees were torn between; Post-publication change count and last-change date (self-set)\nLate-held topical slots: 2 (one a day, self-set)\nCalibration (not targets): FOSDEM 2026 - 1,079 events, 37 rooms, 71 tracks; ~50-minute talks, lightning capped at 15; a 10-minute visible gap; PyCon US 2026 - 45-minute talks across five main rooms, staggered starts (11:00, 11:45, 12:30, 1:45...); keynotes 9:00-9:45, lunch about 12:30-1:15\nFindings (10):\n[fix] speaker_window (clash - speaker window): P01 sits D1 09:50-10:35 in Main Hall, but Ben Okoro is not available then (Ben Okoro: D1 10:00-17:30, D2) - the check on Ana Ruiz alone passes; the co-speaker intersection is D1 10:00-17:30, D2 09:00-17:30.\n[check] bespoke (slots): 1 session (P25 40 min) does not fit the 45-minute standard - make every session fit one slot length.\n[check] avail_missing (speakers): 13 speakers without a window (Ines Calder, Rafa Sol, Kit Moreau, Sam Ito, Dee Harlow, Max Feld...) - treated as free all event; ask before publishing.\n[info] intersection (speakers): Co-speaker intersections: P01 D1 10:00-17:30, D2 09:00-17:30; P20 all hours; W1 D1 09:00-17:30, D2 09:00-12:40; P27 all hours.\n[check] same_track (audience): P20 and P23 are both Platform and run head to head on D2 at 14:30.\n[info] bookend (pacing): D1 has no closing keynote - bookending each day in a format unlike the talks is the organizer's own experience, not a published source.\n[info] bookend (pacing): D2 has no opening keynote - bookending each day in a format unlike the talks is the organizer's own experience, not a published source.\n[fix] late_publish (publication): Planned publication 2027-03-20 is after 2027-03-14 - attendees travel, and a late grid cannot inform a flight already booked.\n[info] topical (publication): Fast-moving field: hold 2 slots (one a day, self-set) marked 'topic announced later' - slot, room and time fixed, the talk filled close to the event.\n[info] idle (grid): Minutes no slot fits: 17:20-17:30 at the end of the day."
}
Reply:
{
"task": "plan",
"summary": "Harbor DevDays lays 30 sessions across two days and three rooms between two keynote bookends; the first thing to settle is Ben Okoro's unavailability during P01's pinned slot in Main Hall.",
"slots": {
"standard": "45-minute talks in 50-minute slots",
"brief": "Tell speakers their talk runs 45 minutes inside a 50-minute slot, or they will run long into the 5-minute buffer the slot carries."
},
"fixed_points": [
{
"id": "K1",
"when": "D1 09:00",
"why": "This is the keynote that opens D1, the first bookend of the day before the grid's talks begin."
},
{
"id": "P01",
"when": "D1 09:50",
"why": "This is pinned by the organizer directly after the opening keynote in Main Hall, carrying the day's largest non-keynote expected audience, 170 of 420, into the first standard slot."
},
{
"id": "K2",
"when": "D2 16:30",
"why": "This is the keynote that closes D2, the final bookend placed in the day's very last slot."
}
],
"breaks": [
"10:40-11:00 Coffee, a plenary re-sync point that resets per-track drift across Main Hall, Room B and Room C.",
"12:40-13:40 Lunch, a plenary re-sync point before the afternoon rooms resume together.",
"15:20-15:40 Coffee, a plenary re-sync point ahead of the final slots of the day."
],
"rooms": [
{
"room": "Main Hall",
"holds": "Main Hall (420 seats) holds both keynotes, K1 at 380 of 420 and K2 at 340 of 420, along with each day's highest-draw talks such as P01 at 170 and P13 at 190 of 420, all comfortably under capacity with no overflow."
},
{
"room": "Room B",
"holds": "Room B (160 seats) holds mid-sized talks across all three tracks, several running at full capacity such as P09 and P15 at 160 of 160, with no overflow beyond its seats."
},
{
"room": "Room C",
"holds": "Room C (90 seats) holds the smaller talks and the two-part workshop W1, with several sessions filling it exactly such as P16, P03 and P26 at 90 of 90, and no overflow."
}
],
"clashes": {
"depth": "overlap",
"findings": [
"P01's pinned 09:50 start on D1 falls before Ben Okoro's own window opens at 10:00, even though Ana Ruiz alone is free then; the fix is to start P01 within the co-speaker intersection of D1 10:00-17:30, D2 09:00-17:30."
],
"rerun": "Re-run all four clash checks after every change to the grid, including after any fix to P01's pinned start time."
},
"buffer": {
"rung": "internal",
"minutes": 5,
"walking": "The 5-minute internal buffer was checked against the longest walk between rooms, 5 minutes across 2 floors, to Room C."
},
"alignment": {
"pick": "synchronized",
"why": "All 3 rooms change at once and the corridor clears, so starts synchronize to keep arrivals together ahead of the plenary moments."
},
"pacing": [
"The slot right after Lunch, 13:40 on both days, falls into the post-lunch dip in attention (sourced).",
"D2's last slot of the event, 16:30, is normally the worst-attended position studied at other conferences, though no magnitude is given here (sourced).",
"D1 has no closing keynote, so its day ends on a regular talk slot rather than a bookending keynote format (judgment).",
"D2 has no opening keynote, so only its close with K2 bookends the day, unlike D1 which bookends only its open with K1 (judgment)."
],
"disruption": [
"Protect the plenary slots first: keep K1 at D1 09:00 and K2 at D2 16:30 exactly where they sit, since both are the fixed keynote bookends.",
"Protect the headline commitment next: P01 at D1 09:50 in Main Hall, the pinned session right after the opening keynote with the day's largest non-keynote expected audience at 170 of 420 - our call on what counts as the headline commitment here.",
"Minimize how many other sessions the fix disturbs: when resolving the P01 clash with Ben Okoro's availability, move only P01 rather than reshuffling Room B or Room C in the same window.",
"Lock the revised grid for the rest of the block once the fix is set, rather than continuing to re-shuffle D1's morning sessions."
],
"publication": {
"posture": "versioned",
"date": "2027-03-14",
"why": "A multi-day, multi-track grid that attendees build personal agendas against should publish early with a visible marker of what moved, going out by 2027-03-14, 60 days before the event, since attendees travel and typically book flights around then."
},
"metrics": [
"Room-fit outcomes - which sessions overflowed and which ran near-empty; repeated overflow next edition would trigger moving that session to a bigger room, repeated near-empty would trigger a smaller one (our design).",
"Reported clashes - a survey question naming the two sessions attendees were torn between; a same-track pair named repeatedly would trigger moving one of them to a different time row next edition (our design).",
"Post-publication change count and last-change date - a high count or a late last change would trigger moving the publish-by date earlier next edition (our design)."
],
"caveats": [
"P01 is pinned at D1 09:50 in Main Hall but Ben Okoro is only free from D1 10:00 onward (co-speaker intersection D1 10:00-17:30, D2 09:00-17:30); move its start to or after 10:00 to clear the speaker-window clash.",
"Planned publication is set for 2027-03-20, after the 2027-03-14 publish-by date the 60-day travel window calls for; move the publication date earlier.",
"13 speakers, including Ines Calder, Rafa Sol and Max Feld, have no stated window and are treated as free all event; confirm before publishing.",
"P20 and P23 are both Platform-track talks running head to head on D2 at 14:30; this same-track pair needs the audience-overlap pass before publication."
]
}
The page's check: kept - nothing to flag
Harbor DevDays (2 days, 3 rooms) - overlap
30 of 30 sessions placed in 3 rooms over 2 days; 1 clashes; clash depth overlap, publication versioned.
Input:
{
"task": "overlap",
"event": "Event: Harbor DevDays; 2 days from 2027-05-13; day window 09:00-17:30\nBreaks (venue-fixed): 10:40-11:00 Coffee; 12:40-13:40 Lunch; 15:20-15:40 Coffee\nRooms: Main Hall 420 seats, 0 min walk, floor G; Room B 160 seats, 4 min walk, floor 1; Room C 90 seats, 5 min walk, floor 1, available D1 09:50-17:30, D2\nVenue: corridor open; between sessions a room needs nothing\nAudience: mixed technical and non-technical no; fast-moving field yes; edition compounds yearly yes; attendees travel yes; tickets already on sale yes; organizer says two tracks share an audience no\nDates: today 2027-02-01; planned publication 2027-03-20",
"grid": "D1 (2027-05-13):\nD1 09:00-09:50 PLENARY | Main Hall: K1 \"What ten years of on-call taught us\" (keynote; intro; 45 min; speakers Mara Quist; expected 380 of 420 seats)\nD1 09:50-10:40 | Main Hall: P01 \"Scaling Postgres at read-heavy loads\" (talk; track Data; intermediate; 45 min; speakers Ana Ruiz & Ben Okoro; expected 170 of 420 seats) | Room B: P12 \"Incident reviews people read\" (talk; track Platform; intro; 45 min; speakers Dee Harlow; expected 150 of 160 seats) | Room C: P22 \"Animations that respect motion settings\" (talk; track Frontend; intermediate; 45 min; speakers Rio Day; expected 60 of 90 seats)\nD1 10:40-11:00 BREAK Coffee\nD1 11:00-11:50 | Main Hall: empty | Room B: P21 \"Time zones will hurt you\" (talk; track Data; intermediate; 45 min; speakers Kai Mott; expected 150 of 160 seats) | Room C: W1 \"Hands-on: profiling Python services\" (workshop; track Platform; intermediate; 95 min; speakers Jon Abara & Fen Holt; expected 40 of 90 seats)\nD1 11:50-12:40 | Main Hall: empty | Room B: P19 \"Web performance budgets\" (talk; track Frontend; intro; 45 min; speakers Teo Vance; expected 140 of 160 seats) | Room C: W1 continues\nD1 12:40-13:40 BREAK Lunch\nD1 13:40-14:30 | Main Hall: P06 \"Rust in a Python shop\" (talk; track Platform; intermediate; 45 min; speakers Jon Abara; expected 200 of 420 seats) | Room B: P05 \"Accessible forms from the start\" (talk; track Frontend; intro; 45 min; speakers Kit Moreau; expected 110 of 160 seats) | Room C: empty\nD1 14:30-15:20 | Main Hall: P27 \"Shipping a design system\" (talk; track Frontend; intro; 45 min; speakers Noor Sami & Kit Moreau; expected 110 of 420 seats) | Room B: P04 \"Event sourcing in anger\" (talk; track Data; advanced; 45 min; speakers Lena Voss; expected 140 of 160 seats) | Room C: empty\nD1 15:20-15:40 BREAK Coffee\nD1 15:40-16:30 | Main Hall: P18 \"Data contracts that stick\" (talk; track Data; intermediate; 45 min; speakers Ivy Quarle; expected 100 of 420 seats) | Room B: P11 \"Hydration bugs and how to find them\" (talk; track Frontend; advanced; 45 min; speakers Lu Chen; expected 120 of 160 seats) | Room C: empty\nD1 16:30-17:20 | Main Hall: P25 \"Micro-frontends: a retrospective\" (talk; track Frontend; intermediate; 40 min; speakers Sam Ito; expected 100 of 420 seats) | Room B: P07 \"Query plans for humans\" (talk; track Data; intermediate; 45 min; speakers Ana Ruiz; expected 130 of 160 seats) | Room C: empty\nD2 (2027-05-14):\nD2 09:00-09:50 | Main Hall: empty | Room B: P09 \"Kubernetes cost without tears\" (talk; track Platform; intermediate; 45 min; speakers Priya Nand; expected 160 of 160 seats) | Room C: P16 \"Testing the untestable UI\" (talk; track Frontend; intermediate; 45 min; speakers Eli Park; expected 90 of 90 seats)\nD2 09:50-10:40 | Main Hall: empty | Room B: P24 \"Batch to streaming in one quarter\" (talk; track Data; advanced; 45 min; speakers Ola Brandt; expected 110 of 160 seats) | Room C: P03 \"The build cache that lied\" (talk; track Platform; intermediate; 45 min; speakers Rafa Sol; expected 90 of 90 seats)\nD2 10:40-11:00 BREAK Coffee\nD2 11:00-11:50 | Main Hall: empty | Room B: P10 \"Streaming joins explained\" (talk; track Data; advanced; 45 min; speakers Ola Brandt; expected 95 of 160 seats) | Room C: P08 \"CSS layers in practice\" (talk; track Frontend; intermediate; 45 min; speakers Sam Ito; expected 85 of 90 seats)\nD2 11:50-12:40 | Main Hall: empty | Room B: P15 \"Zero-downtime migrations\" (talk; track Data; advanced; 45 min; speakers Ben Okoro; expected 160 of 160 seats) | Room C: P14 \"Design tokens across three apps\" (talk; track Frontend; intro; 45 min; speakers Noor Sami; expected 70 of 90 seats)\nD2 12:40-13:40 BREAK Lunch\nD2 13:40-14:30 | Main Hall: P13 \"Vector search on a budget\" (talk; track Data; intermediate; 45 min; speakers Max Feld; expected 190 of 420 seats) | Room B: P17 \"SLOs your team believes\" (talk; track Platform; intermediate; 45 min; speakers Gus Lind; expected 130 of 160 seats) | Room C: empty\nD2 14:30-15:20 | Main Hall: P20 \"Platform teams as product teams\" (talk; track Platform; intro; 45 min; speakers Ines Calder & Gus Lind; expected 170 of 420 seats) | Room B: P23 \"Observability on a shoestring\" (talk; track Platform; intermediate; 45 min; speakers Fen Holt; expected 120 of 160 seats) | Room C: P26 \"Search relevance tuning\" (talk; track Data; advanced; 45 min; speakers Max Feld; expected 90 of 90 seats)\nD2 15:20-15:40 BREAK Coffee\nD2 15:40-16:30 | Main Hall: empty | Room B: P02 \"Feature flags without the flag debt\" (talk; track Platform; intro; 45 min; speakers Ines Calder; expected 120 of 160 seats) | Room C: empty\nD2 16:30-17:20 PLENARY | Main Hall: K2 \"A live demo you can break\" (keynote; intro; 45 min; speakers Tobi Arden; expected 340 of 420 seats)\nCo-speaker intersections: P01 D1 10:00-17:30, D2 09:00-17:30; P20 all hours; W1 D1 09:00-17:30, D2 09:00-12:40; P27 all hours\nSpeaker windows: Mara Quist D1; Tobi Arden D2; Ana Ruiz D1 08:00-17:30, D2; Ben Okoro D1 10:00-17:30, D2; Lena Voss D1 11:00-17:30; Jon Abara D1, D2 09:00-12:40; Priya Nand D2 09:00-13:00; Ola Brandt D2; Lu Chen D1; Fen Holt D1, D2; every other speaker is treated as free all event",
"facts": "Slot standard: 45-minute talks in 50-minute slots (5 minutes inside each slot), computed from the buffer\nBuffer: internal - Slot-internal buffer (slots published longer than the talks they hold); 5 minutes wanted. The default: slots published 5 minutes longer than the 45-minute talks they hold - brief speakers on both numbers. Longest walk between rooms: 5 minutes across 2 floors.\nTrack-start alignment: synchronized - The corridor clears with 3 rooms changing at once; keep everyone arriving together at the plenary moments.\nClash depth: overlap - The mechanical checks plus the audience-overlap pass. Sessions from the same track run head to head - that promotes the audience-overlap pass on top of the three mechanical checks. Clashes found: 1 (room window 0, speaker window 1, same-room overlap 0, double-booked 0)\nFixed points placed first: K1 D1 09:00 plenary in Main Hall; K2 D2 16:30 plenary in Main Hall; P01 D1 09:50 pinned in Main Hall\nPlaced 30 of 30 sessions in 42 room-slots; plenary room Main Hall\nPublication posture: versioned - A multi-day, multi-track grid people build personal agendas against - publish early and carry a visible marker of what moved. Attendees travel: publish by 2027-03-14, 60 days out (self-set from flights booked about two months ahead). Lock-late is deleted for this event.\nDisruption order: 1. Protect the plenary or keynote slot first. 2. Protect the headline commitment the audience came for next. 3. Minimize how many other sessions the fix disturbs. 4. Lock the revised grid for the rest of the block instead of re-shuffling.\nMeasurement picks: Room-fit outcomes - sessions that overflowed and sessions that ran near-empty (self-set); Reported clashes - a survey question naming the two sessions attendees were torn between; Post-publication change count and last-change date (self-set)\nLate-held topical slots: 2 (one a day, self-set)\nCalibration (not targets): FOSDEM 2026 - 1,079 events, 37 rooms, 71 tracks; ~50-minute talks, lightning capped at 15; a 10-minute visible gap; PyCon US 2026 - 45-minute talks across five main rooms, staggered starts (11:00, 11:45, 12:30, 1:45...); keynotes 9:00-9:45, lunch about 12:30-1:15\nFindings (10):\n[fix] speaker_window (clash - speaker window): P01 sits D1 09:50-10:35 in Main Hall, but Ben Okoro is not available then (Ben Okoro: D1 10:00-17:30, D2) - the check on Ana Ruiz alone passes; the co-speaker intersection is D1 10:00-17:30, D2 09:00-17:30.\n[check] bespoke (slots): 1 session (P25 40 min) does not fit the 45-minute standard - make every session fit one slot length.\n[check] avail_missing (speakers): 13 speakers without a window (Ines Calder, Rafa Sol, Kit Moreau, Sam Ito, Dee Harlow, Max Feld...) - treated as free all event; ask before publishing.\n[info] intersection (speakers): Co-speaker intersections: P01 D1 10:00-17:30, D2 09:00-17:30; P20 all hours; W1 D1 09:00-17:30, D2 09:00-12:40; P27 all hours.\n[check] same_track (audience): P20 and P23 are both Platform and run head to head on D2 at 14:30.\n[info] bookend (pacing): D1 has no closing keynote - bookending each day in a format unlike the talks is the organizer's own experience, not a published source.\n[info] bookend (pacing): D2 has no opening keynote - bookending each day in a format unlike the talks is the organizer's own experience, not a published source.\n[fix] late_publish (publication): Planned publication 2027-03-20 is after 2027-03-14 - attendees travel, and a late grid cannot inform a flight already booked.\n[info] topical (publication): Fast-moving field: hold 2 slots (one a day, self-set) marked 'topic announced later' - slot, room and time fixed, the talk filled close to the event.\n[info] idle (grid): Minutes no slot fits: 17:20-17:30 at the end of the day."
}
Reply:
{
"task": "overlap",
"pairs": [
{
"a": "P20",
"b": "P23",
"when": "D2 14:30",
"why": "Both are Platform-track sessions that would draw the same platform-engineering crowd, Platform teams as product teams at intro level and Observability on a shoestring at intermediate level, but the grid runs them at the same D2 14:30 start.",
"move": {
"session": "P20",
"swap_with": "P27",
"why": "P20 and P27 are both 45-minute Main Hall talks with unrestricted speaker windows, so trading D1 14:30 for D2 14:30 pulls Platform away from opposite P23 without changing any room assignment."
}
}
],
"clear": "Every other time row seats sessions from different tracks, so only the D2 14:30 Platform pair needs attention and the rest of the grid can stand as laid out.",
"note": "This pairing is judgment, not a computed check; ask attendees next edition which two sessions they were torn between to confirm or correct it."
}
The page's check: kept - nothing to flag
Open Data Summit (4 rooms, narrow corridor) - plan
16 of 16 sessions placed in 4 rooms over 1 days; 1 clashes; clash depth overlap, publication versioned.
Input:
{
"task": "plan",
"event": "Event: Open Data Summit; 1 day from 2027-09-18; day window 09:00-17:00\nBreaks (venue-fixed): 10:30-10:45 Coffee; 12:15-13:00 Lunch; 15:00-15:15 Coffee\nRooms: Auditorium 320 seats, 0 min walk, floor 2; Hall East 150 seats, 3 min walk, floor 2; Studio 60 seats, 3 min walk, floor 2, available D1 10:00-17:00; Lab 40 seats, 4 min walk, floor 2\nVenue: corridor narrow; between sessions a room needs furniture moved\nAudience: mixed technical and non-technical yes; fast-moving field no; edition compounds yearly yes; attendees travel no; tickets already on sale yes; organizer says two tracks share an audience yes\nDates: today 2027-06-01; planned publication not set",
"grid": "D1 (2027-09-18):\nD1 09:00-09:45 PLENARY | Auditorium: K1 \"Opening: the state of open data\" (keynote; intro; 30 min; speakers Rhea Moss; expected 300 of 320 seats)\nD1 09:45-10:30 | Auditorium: C2 \"Notebooks to pipelines\" (talk; track Tools; intermediate; 30 min; speakers Ida Crane; expected 90 of 320 seats) | Hall East: B1 \"Privacy-preserving statistics\" (talk; track Privacy; advanced; 30 min; speakers Ada Fenn; expected 120 of 150 seats) | Studio: empty | Lab: empty\nD1 10:30-10:45 BREAK Coffee\nD1 10:45-11:30 | Auditorium: A4 \"Data portals that do not rot\" (talk; track Open data; intermediate; 30 min; speakers Cy Brook; expected 80 of 320 seats) | Hall East: B2 \"Differential privacy in production\" (talk; track Privacy; advanced; 30 min; speakers Ada Fenn; expected 100 of 150 seats) | Studio: empty | Lab: C1 \"Workshop: cleaning messy CSVs\" (workshop; track Tools; intro; 60 min; speakers Lee Tan; expected 35 of 40 seats)\nD1 11:30-12:15 | Auditorium: A3 \"Open data at city scale\" (panel; track Open data; intro; 30 min; speakers Ugo Lam & Pia Rask; expected 160 of 320 seats) | Hall East: B4 \"Synthetic data: when it helps\" (talk; track Privacy; intermediate; 30 min; speakers Mo Reyes; expected 110 of 150 seats) | Studio: empty | Lab: C1 continues\nD1 12:15-13:00 BREAK Lunch\nD1 13:00-13:45 | Auditorium: empty | Hall East: A2 \"Licensing for data releases\" (talk; track Open data; intermediate; 30 min; speakers Vera Holm; expected 90 of 150 seats) | Studio: M3 \"Documentation sprints\" (talk; track Community; intro; 30 min; speakers Ray Quill; expected 50 of 60 seats) | Lab: C3 \"Workshop: dashboards in an afternoon\" (workshop; track Tools; intro; 60 min; speakers Lee Tan & Ida Crane; expected 35 of 40 seats)\nD1 13:45-14:30 | Auditorium: empty | Hall East: M1 \"Civic tech funding panel\" (panel; track Community; intro; 30 min; speakers Pia Rask & Jo Wilde & Cy Brook; expected 130 of 150 seats) | Studio: M2 \"Running a volunteer data team\" (talk; track Community; intro; 30 min; speakers Nell Ober; expected 60 of 60 seats) | Lab: C3 continues\nD1 15:00-15:15 BREAK Coffee\nD1 15:15-16:00 | Auditorium: B3 \"Consent records done right\" (talk; track Privacy; intro; 30 min; speakers Jo Wilde; expected 70 of 320 seats) | Hall East: A1 \"Publishing open datasets people reuse\" (talk; track Open data; intro; 30 min; speakers Ugo Lam; expected 140 of 150 seats) | Studio: empty | Lab: empty\nD1 16:00-16:45 PLENARY | Auditorium: K2 \"Closing: what we keep\" (keynote; intro; 30 min; speakers Rhea Moss & Nell Ober; expected 280 of 320 seats)\nCo-speaker intersections: A3 D1 11:00-17:00; C3 D1 09:00-17:00; M1 D1 11:00-17:00; K2 all hours\nSpeaker windows: Pia Rask D1 11:00-17:00; Ada Fenn D1 09:00-12:30; Lee Tan D1 09:00-17:00; Ray Quill D1 14:00-17:00; every other speaker is treated as free all event",
"facts": "Slot standard: 30-minute talks in 45-minute slots (15 minutes inside each slot), computed from the buffer\nBuffer: turnover - Room-turnover-aware gap (sized to what changes in the room); 15 minutes wanted. The room has to change between sessions (furniture moved), which promotes the room-turnover-aware gap: 15 minutes (self-set), and same-format sessions are kept together in a room. Longest walk between rooms: 4 minutes.\nTrack-start alignment: staggered - Simultaneous room changes would jam the narrow corridor, so offset every other room by 15 minutes (self-set) and move its breaks with it; the grid below is drawn aligned and the clash check holds within each room.\nClash depth: overlap - The mechanical checks plus the audience-overlap pass. You said two tracks share an audience - that promotes the audience-overlap pass on top of the three mechanical checks. Clashes found: 1 (room window 0, speaker window 1, same-room overlap 0, double-booked 0)\nFixed points placed first: K1 D1 09:00 plenary in Auditorium; M3 D1 13:00 pinned in Studio; K2 D1 16:00 plenary in Auditorium\nPlaced 16 of 16 sessions in 24 room-slots; plenary room Auditorium\nPublication posture: versioned - A change will land after tickets are sold, at a compounding annual event - publish early and carry a visible marker of what moved. No one travels: publish by 2027-09-04, 14 days out (self-set). Lock-late is deleted for this event.\nDisruption order: 1. Protect the plenary or keynote slot first. 2. Protect the headline commitment the audience came for next. 3. Minimize how many other sessions the fix disturbs. 4. Lock the revised grid for the rest of the block instead of re-shuffling.\nMeasurement picks: Room-fit outcomes - sessions that overflowed and sessions that ran near-empty (self-set); Reported clashes - a survey question naming the two sessions attendees were torn between; Post-publication change count and last-change date (self-set)\nCalibration (not targets): FOSDEM 2026 - 1,079 events, 37 rooms, 71 tracks; ~50-minute talks, lightning capped at 15; a 10-minute visible gap; PyCon US 2026 - 45-minute talks across five main rooms, staggered starts (11:00, 11:45, 12:30, 1:45...); keynotes 9:00-9:45, lunch about 12:30-1:15\nFindings (6):\n[fix] speaker_window (clash - speaker window): M3 sits D1 13:00-13:30 in Studio, but Ray Quill is not available then (Ray Quill: D1 14:00-17:00).\n[check] avail_missing (speakers): 8 speakers without a window (Rhea Moss, Ugo Lam, Vera Holm, Cy Brook, Jo Wilde, Mo Reyes...) - treated as free all event; ask before publishing.\n[info] intersection (speakers): Co-speaker intersections: A3 D1 11:00-17:00; C3 D1 09:00-17:00; M1 D1 11:00-17:00; K2 all hours.\n[check] same_track (audience): M1 and M2 are both Community and run head to head on D1 at 13:45.\n[info] weekend (pacing): The event starts on a weekend day - DevOpsDays recommends weekdays only, since a weekend competes with time people protect.\n[info] idle (grid): Minutes no slot fits: 14:30-15:00 before coffee; 16:45-17:00 at the end of the day."
}
Reply:
{
"task": "plan",
"summary": "One day across the Auditorium, Hall East, Studio and Lab, 16 sessions placed between keynotes K1 and K2; the first thing to settle is M3's speaker-window clash, since Ray Quill is free only from 14:00.",
"slots": {
"standard": "30-minute talks in 45-minute slots",
"brief": "Your talk is 30 minutes inside a 45-minute slot, so do not fill the whole slot."
},
"fixed_points": [
{
"id": "K1",
"when": "D1 09:00",
"why": "K1 is the opening keynote, a bookend in a different format from the talks that gathers everyone together before the rooms split into tracks."
},
{
"id": "M3",
"when": "D1 13:00",
"why": "M3 is pinned here by the organizer, opening the Studio room's use of the day right after the lunch break."
},
{
"id": "K2",
"when": "D1 16:00",
"why": "K2 is the closing keynote, a bookend in a different format from the talks that gathers everyone together again before the day ends."
}
],
"breaks": [
"10:30-10:45 Coffee - brings all tracks back together before the next slot.",
"12:15-13:00 Lunch - brings all tracks back together before the afternoon sessions.",
"15:00-15:15 Coffee - brings all tracks back together before the closing keynote."
],
"rooms": [
{
"room": "Auditorium",
"holds": "Auditorium seats 320 and holds K1, K2, C2, A4, A3 and B3, matched up to the largest expected audience of 300 for K1, well under capacity throughout."
},
{
"room": "Hall East",
"holds": "Hall East seats 150 and holds B1, B2, B4, A2, M1 and A1, matched up to the largest expected audience of 140 of 150 for A1, without overflow."
},
{
"room": "Studio",
"holds": "Studio seats 60, available D1 10:00-17:00, and holds M3 and M2, filling exactly to 60 of 60 for M2."
},
{
"room": "Lab",
"holds": "Lab seats 40 and holds C1 and C3, both expected at 35 of 40 seats, with room to spare."
}
],
"clashes": {
"depth": "overlap",
"findings": [
"Speaker-window clash: M3 runs D1 13:00-13:45 in Studio, but Ray Quill is not available until 14:00; move M3 to any Studio slot at or after 14:00.",
"Audience-overlap: M1 and M2 are both Community sessions running head to head D1 13:45; separate them by moving one to a different time row."
],
"rerun": "Re-run all four clash checks, including the audience-overlap pass, after any change to the grid."
},
"buffer": {
"rung": "turnover",
"minutes": 15,
"walking": "Checked against the longest walk between rooms, 4 minutes, which fits comfortably inside the 15-minute gap."
},
"alignment": {
"pick": "staggered",
"why": "Simultaneous room changes would jam the narrow corridor, so every other room is offset by 15 minutes and its breaks move with it."
},
"pacing": [
"The 13:00 slot right after the lunch break sits in the circadian post-lunch dip (sourced).",
"The 15:15 slot before the closing keynote is this conference's weakest-attended position, matching the pattern seen elsewhere (sourced).",
"Starting and ending the day with keynotes, a different format from the talks around them, reflects the organizers' own experience rather than a published source (judgment)."
],
"disruption": [
"Protect the plenary keynote slots first: keep K1 fixed at D1 09:00 and K2 fixed at D1 16:00 whatever else moves.",
"Protect the headline commitment next: A3, the panel drawing the largest non-keynote crowd at 160 expected, is the session the audience most organized their day around (judgment call).",
"Minimize disturbance: when fixing the Ray Quill availability clash on M3, move only M3 rather than reshuffling its Studio neighbors.",
"Lock the revised grid for the rest of the day once changed, instead of continuing to re-shuffle other sessions."
],
"publication": {
"posture": "versioned",
"date": "2027-09-04",
"why": "Tickets are already on sale and the edition compounds yearly, so release early but carry a visible marker of anything that moves afterward."
},
"metrics": [
"Room-fit outcomes, sessions that overflowed or ran near-empty: a recurring mismatch next edition would mean resizing that session's room.",
"Reported clashes, a survey question naming the two sessions attendees were torn between: a repeated pair would move one of them to a different time row next edition.",
"Post-publication change count and last-change date: a high count or a late last change would tighten the publish-by date next edition."
],
"caveats": [
"M3 is scheduled D1 13:00-13:45 in Studio, but Ray Quill is not available until 14:00 - move M3 or swap it before publishing.",
"8 speakers have no stated availability window and are currently treated as free all event - confirm with them before publishing.",
"M1 and M2 are both Community sessions running head to head D1 13:45 - check whether that is acceptable for this shared audience."
]
}
The page's check: kept - nothing to flag
Open Data Summit (4 rooms, narrow corridor) - overlap
16 of 16 sessions placed in 4 rooms over 1 days; 1 clashes; clash depth overlap, publication versioned.
Input:
{
"task": "overlap",
"event": "Event: Open Data Summit; 1 day from 2027-09-18; day window 09:00-17:00\nBreaks (venue-fixed): 10:30-10:45 Coffee; 12:15-13:00 Lunch; 15:00-15:15 Coffee\nRooms: Auditorium 320 seats, 0 min walk, floor 2; Hall East 150 seats, 3 min walk, floor 2; Studio 60 seats, 3 min walk, floor 2, available D1 10:00-17:00; Lab 40 seats, 4 min walk, floor 2\nVenue: corridor narrow; between sessions a room needs furniture moved\nAudience: mixed technical and non-technical yes; fast-moving field no; edition compounds yearly yes; attendees travel no; tickets already on sale yes; organizer says two tracks share an audience yes\nDates: today 2027-06-01; planned publication not set",
"grid": "D1 (2027-09-18):\nD1 09:00-09:45 PLENARY | Auditorium: K1 \"Opening: the state of open data\" (keynote; intro; 30 min; speakers Rhea Moss; expected 300 of 320 seats)\nD1 09:45-10:30 | Auditorium: C2 \"Notebooks to pipelines\" (talk; track Tools; intermediate; 30 min; speakers Ida Crane; expected 90 of 320 seats) | Hall East: B1 \"Privacy-preserving statistics\" (talk; track Privacy; advanced; 30 min; speakers Ada Fenn; expected 120 of 150 seats) | Studio: empty | Lab: empty\nD1 10:30-10:45 BREAK Coffee\nD1 10:45-11:30 | Auditorium: A4 \"Data portals that do not rot\" (talk; track Open data; intermediate; 30 min; speakers Cy Brook; expected 80 of 320 seats) | Hall East: B2 \"Differential privacy in production\" (talk; track Privacy; advanced; 30 min; speakers Ada Fenn; expected 100 of 150 seats) | Studio: empty | Lab: C1 \"Workshop: cleaning messy CSVs\" (workshop; track Tools; intro; 60 min; speakers Lee Tan; expected 35 of 40 seats)\nD1 11:30-12:15 | Auditorium: A3 \"Open data at city scale\" (panel; track Open data; intro; 30 min; speakers Ugo Lam & Pia Rask; expected 160 of 320 seats) | Hall East: B4 \"Synthetic data: when it helps\" (talk; track Privacy; intermediate; 30 min; speakers Mo Reyes; expected 110 of 150 seats) | Studio: empty | Lab: C1 continues\nD1 12:15-13:00 BREAK Lunch\nD1 13:00-13:45 | Auditorium: empty | Hall East: A2 \"Licensing for data releases\" (talk; track Open data; intermediate; 30 min; speakers Vera Holm; expected 90 of 150 seats) | Studio: M3 \"Documentation sprints\" (talk; track Community; intro; 30 min; speakers Ray Quill; expected 50 of 60 seats) | Lab: C3 \"Workshop: dashboards in an afternoon\" (workshop; track Tools; intro; 60 min; speakers Lee Tan & Ida Crane; expected 35 of 40 seats)\nD1 13:45-14:30 | Auditorium: empty | Hall East: M1 \"Civic tech funding panel\" (panel; track Community; intro; 30 min; speakers Pia Rask & Jo Wilde & Cy Brook; expected 130 of 150 seats) | Studio: M2 \"Running a volunteer data team\" (talk; track Community; intro; 30 min; speakers Nell Ober; expected 60 of 60 seats) | Lab: C3 continues\nD1 15:00-15:15 BREAK Coffee\nD1 15:15-16:00 | Auditorium: B3 \"Consent records done right\" (talk; track Privacy; intro; 30 min; speakers Jo Wilde; expected 70 of 320 seats) | Hall East: A1 \"Publishing open datasets people reuse\" (talk; track Open data; intro; 30 min; speakers Ugo Lam; expected 140 of 150 seats) | Studio: empty | Lab: empty\nD1 16:00-16:45 PLENARY | Auditorium: K2 \"Closing: what we keep\" (keynote; intro; 30 min; speakers Rhea Moss & Nell Ober; expected 280 of 320 seats)\nCo-speaker intersections: A3 D1 11:00-17:00; C3 D1 09:00-17:00; M1 D1 11:00-17:00; K2 all hours\nSpeaker windows: Pia Rask D1 11:00-17:00; Ada Fenn D1 09:00-12:30; Lee Tan D1 09:00-17:00; Ray Quill D1 14:00-17:00; every other speaker is treated as free all event",
"facts": "Slot standard: 30-minute talks in 45-minute slots (15 minutes inside each slot), computed from the buffer\nBuffer: turnover - Room-turnover-aware gap (sized to what changes in the room); 15 minutes wanted. The room has to change between sessions (furniture moved), which promotes the room-turnover-aware gap: 15 minutes (self-set), and same-format sessions are kept together in a room. Longest walk between rooms: 4 minutes.\nTrack-start alignment: staggered - Simultaneous room changes would jam the narrow corridor, so offset every other room by 15 minutes (self-set) and move its breaks with it; the grid below is drawn aligned and the clash check holds within each room.\nClash depth: overlap - The mechanical checks plus the audience-overlap pass. You said two tracks share an audience - that promotes the audience-overlap pass on top of the three mechanical checks. Clashes found: 1 (room window 0, speaker window 1, same-room overlap 0, double-booked 0)\nFixed points placed first: K1 D1 09:00 plenary in Auditorium; M3 D1 13:00 pinned in Studio; K2 D1 16:00 plenary in Auditorium\nPlaced 16 of 16 sessions in 24 room-slots; plenary room Auditorium\nPublication posture: versioned - A change will land after tickets are sold, at a compounding annual event - publish early and carry a visible marker of what moved. No one travels: publish by 2027-09-04, 14 days out (self-set). Lock-late is deleted for this event.\nDisruption order: 1. Protect the plenary or keynote slot first. 2. Protect the headline commitment the audience came for next. 3. Minimize how many other sessions the fix disturbs. 4. Lock the revised grid for the rest of the block instead of re-shuffling.\nMeasurement picks: Room-fit outcomes - sessions that overflowed and sessions that ran near-empty (self-set); Reported clashes - a survey question naming the two sessions attendees were torn between; Post-publication change count and last-change date (self-set)\nCalibration (not targets): FOSDEM 2026 - 1,079 events, 37 rooms, 71 tracks; ~50-minute talks, lightning capped at 15; a 10-minute visible gap; PyCon US 2026 - 45-minute talks across five main rooms, staggered starts (11:00, 11:45, 12:30, 1:45...); keynotes 9:00-9:45, lunch about 12:30-1:15\nFindings (6):\n[fix] speaker_window (clash - speaker window): M3 sits D1 13:00-13:30 in Studio, but Ray Quill is not available then (Ray Quill: D1 14:00-17:00).\n[check] avail_missing (speakers): 8 speakers without a window (Rhea Moss, Ugo Lam, Vera Holm, Cy Brook, Jo Wilde, Mo Reyes...) - treated as free all event; ask before publishing.\n[info] intersection (speakers): Co-speaker intersections: A3 D1 11:00-17:00; C3 D1 09:00-17:00; M1 D1 11:00-17:00; K2 all hours.\n[check] same_track (audience): M1 and M2 are both Community and run head to head on D1 at 13:45.\n[info] weekend (pacing): The event starts on a weekend day - DevOpsDays recommends weekdays only, since a weekend competes with time people protect.\n[info] idle (grid): Minutes no slot fits: 14:30-15:00 before coffee; 16:45-17:00 at the end of the day."
}
Reply:
{
"task": "overlap",
"pairs": [
{
"a": "M1",
"b": "M2",
"when": "D1 13:45",
"why": "M1 and M2 are both introductory Community sessions - Civic tech funding panel and Running a volunteer data team - so civic-tech organizers and volunteer coordinators would want to be in both rooms at once.",
"move": {
"session": "M1",
"swap_with": "A2",
"why": "Swapping M1 into A2's 13:00 slot puts it in Hall East (150 seats, enough for its 130), stays inside Pia Rask's D1 11:00-17:00 window with Jo Wilde and Cy Brook free then, and sends A2 to 13:45 in Hall East (150 seats, enough for its 90), so M1 no longer runs against M2."
}
},
{
"a": "B3",
"b": "A1",
"when": "D1 15:15",
"why": "B3 and A1 are both introductory sessions - Consent records done right in Privacy and Publishing open datasets people reuse in Open data - so attendees weighing how to release data responsibly would want both.",
"move": {
"session": "B3",
"swap_with": "M1",
"why": "Swapping B3 into M1's 13:45 slot puts it in Hall East (150 seats, enough for its 70) where Jo Wilde is free all event, and sends M1 to the Auditorium at 15:15 (320 seats, enough for its 130) inside Pia Rask's D1 11:00-17:00 window, so B3 no longer runs against A1."
}
}
],
"clear": "Keynotes K1 and K2, the Tools workshops C1 and C3 running beside differently themed tracks, and the 13:00 grouping of A2 and M3 draw clearly separate audiences and do not need moving.",
"note": "This overlap read is judgment, not measurement; next edition ask attendees which concurrent sessions they most wanted to attend together, to confirm or correct these picks."
}
The page's check: kept - nothing to flag
Backend Evening (one room, mixed) - plan
5 of 5 sessions placed in 1 rooms over 1 days; 0 clashes; clash depth availability, publication early.
Input:
{
"task": "plan",
"event": "Event: Backend Evening #14; 1 day from 2027-04-07; day window 18:30-21:00\nBreaks (venue-fixed): 19:30-19:45 Pizza\nRooms: Main room 80 seats\nVenue: corridor open; between sessions a room needs nothing\nAudience: mixed technical and non-technical yes; fast-moving field no; edition compounds yearly no; attendees travel no; tickets already on sale no; organizer says two tracks share an audience no\nDates: today 2027-03-20; planned publication not set",
"grid": "D1 (2027-04-07):\nD1 18:30-19:00 | Main room: T2 \"What a database index actually is\" (talk; intro; 25 min; speakers Bea Lund; expected 70 of 80 seats)\nD1 19:00-19:30 | Main room: T3 \"Caching layers we regret\" (talk; intermediate; 25 min; speakers Sid Okafor; expected 70 of 80 seats)\nD1 19:30-19:45 BREAK Pizza\nD1 19:45-20:15 | Main room: L1 \"Lightning: my first open-source PR\" (lightning; intro; 5 min; speakers Ami Kaur; expected 70 of 80 seats) then L2 \"Lightning: a terminal trick a day\" (lightning; intro; 5 min; speakers Ty Sorel; expected 70 of 80 seats)\nD1 20:15-20:45 | Main room: T1 \"Inside the garbage collector\" (talk; advanced; 25 min; speakers Oren Vale; expected 70 of 80 seats)\nSpeaker windows: none given; every other speaker is treated as free all event",
"facts": "Slot standard: 25-minute talks in 30-minute slots (5 minutes inside each slot), as entered\nBuffer: internal - Slot-internal buffer (slots published longer than the talks they hold); 5 minutes wanted. The default: slots published 5 minutes longer than the 25-minute talks they hold - brief speakers on both numbers. No walking times given.\nTrack-start alignment: single - One room - every session starts when the one before it ends.\nClash depth: availability - Speaker availability only (single track - nothing runs at once). One room: nothing runs at once, so the overlap and double-booking checks and the audience-overlap pass are deleted; the live check is whether each speaker is in the building. Clashes found: 0 (room window 0, speaker window 0, same-room overlap 0, double-booked 0)\nFixed points placed first: none\nPlaced 5 of 5 sessions in 4 room-slots; plenary room Main room\nPublication posture: early - Release early and iterate: the published grid is a living document. Lock-late survives only for a same-city meetup nobody travels to. No one travels: publish by 2027-03-24, 14 days out (self-set).\nDisruption order: 1. Protect the plenary or keynote slot first. 2. Protect the headline commitment the audience came for next. 3. Minimize how many other sessions the fix disturbs. 4. Lock the revised grid for the rest of the block instead of re-shuffling.\nMeasurement picks: Room-fit outcomes - sessions that overflowed and sessions that ran near-empty (self-set); No-show rate per slot, recorded by slot position; On-time start rate per room (self-set threshold)\nCalibration (not targets): FOSDEM 2026 - 1,079 events, 37 rooms, 71 tracks; ~50-minute talks, lightning capped at 15; a 10-minute visible gap; PyCon US 2026 - 45-minute talks across five main rooms, staggered starts (11:00, 11:45, 12:30, 1:45...); keynotes 9:00-9:45, lunch about 12:30-1:15\nFindings (5):\n[check] no_avail (speakers): No speaker availability pasted - every speaker is treated as free all event. Collect the windows before laying anything; a conflict found after publication costs a public correction.\n[info] single_deletes (clashes): Single track: same-room overlap, speaker double-booking and the audience-overlap pass are deleted - only availability can fail. One overrun moves the whole day.\n[info] bookend (pacing): D1 has no opening or closing keynote - bookending each day in a format unlike the talks is the organizer's own experience, not a published source.\n[info] graveyard (pacing): The last slot of the last day holds T1 - the worst-attended position at the conferences studied (direction, not magnitude); the broad-to-advanced order puts the deepest talk there on purpose - accept that trade for an evening where the specialists stay, and watch the no-show rate per slot.\n[info] idle (grid): Minutes no slot fits: 20:45-21:00 at the end of the day."
}
Reply:
{
"task": "plan",
"summary": "One room, one evening, 4 slots holding 5 sessions around a fixed pizza break; the first thing to settle is collecting real speaker availability windows before the grid locks.",
"slots": {
"standard": "25-minute talks in 30-minute slots",
"brief": "Your talk should run 25 minutes; the slot on the grid is 30 minutes, so the extra time is for changeover, not more talking."
},
"fixed_points": [],
"breaks": [
"19:30-19:45 Pizza, the evening's one venue-fixed break; with only one track running, there is nothing in parallel for it to realign."
],
"rooms": [
{
"room": "Main room",
"holds": "Main room holds all 5 sessions against an audience estimated at 70 of its 80 seats each time, with no overflow."
}
],
"clashes": {
"depth": "availability",
"findings": [],
"rerun": "Check again whenever anything in this grid changes, since availability is the only thing that can break here."
},
"buffer": {
"rung": "internal",
"minutes": 5,
"walking": "No walking distance was given to check the buffer against, since every session runs in the same Main room."
},
"alignment": {
"pick": "single",
"why": "With only one room, each session simply starts when the one before it ends, so there is no corridor to stagger or synchronize."
},
"pacing": [
"This evening's last slot holds T1, its advanced talk - direction that lines up with weaker attendance in that position at other conferences studied (sourced).",
"The order runs intro (T2), intermediate (T3), intro lightning talks (L1, L2), then advanced (T1) - a deliberate broad-to-advanced climb that accepts a quieter finish (judgment).",
"Nothing in a different format bookends this evening at open or close, since no keynote was placed (judgment)."
],
"disruption": [
"Protect the 19:30-19:45 Pizza break first - it is the evening's one venue-fixed point, so any fix works around it, not through it.",
"Protect T1, 'Inside the garbage collector,' as the headline commitment next - it is the advanced talk placed last on purpose, for the specialists who stay to the end.",
"Then favor the fix that disturbs the fewest of the other sessions in Main room.",
"Finally, lock the revised running order for the rest of this one-day evening instead of changing it again."
],
"publication": {
"posture": "early",
"date": "2027-03-24",
"why": "Since nobody is traveling to this same-city meetup, the grid can go out well ahead of the event and still be adjusted afterward, publishing 14 days before the night itself."
},
"metrics": [
"Room-fit outcomes: track which sessions overflow Main room's 80 seats or run far under the expected 70, and resize the room pick next edition.",
"No-show rate per slot, recorded by slot position: a high rate in the last slot would move the advanced talk out of the closing spot next edition.",
"On-time start rate per room: persistent lateness in Main room would add visible minutes to the 5-minute buffer already built into each slot."
],
"caveats": [
"No speaker availability was pasted - every speaker is treated as free all event; collect real windows before the grid locks, since a conflict found after publication costs a public correction."
]
}
The page's check: kept - nothing to flag
The grid CSV for the first sample
day,date,start,end,room,id,title,speakers,format,track,level,expected,capacity
D1,2027-05-13,09:00,09:45,Main Hall,K1,What ten years of on-call taught us,Mara Quist,keynote,,intro,380,420
D1,2027-05-13,09:50,10:35,Main Hall,P01,Scaling Postgres at read-heavy loads,Ana Ruiz & Ben Okoro,talk,Data,intermediate,170,420
D1,2027-05-13,09:50,10:35,Room B,P12,Incident reviews people read,Dee Harlow,talk,Platform,intro,150,160
D1,2027-05-13,09:50,10:35,Room C,P22,Animations that respect motion settings,Rio Day,talk,Frontend,intermediate,60,90
D1,2027-05-13,11:00,11:45,Room B,P21,Time zones will hurt you,Kai Mott,talk,Data,intermediate,150,160
D1,2027-05-13,11:00,12:35,Room C,W1,Hands-on: profiling Python services,Jon Abara & Fen Holt,workshop,Platform,intermediate,40,90
D1,2027-05-13,11:50,12:35,Room B,P19,Web performance budgets,Teo Vance,talk,Frontend,intro,140,160
D1,2027-05-13,13:40,14:25,Main Hall,P06,Rust in a Python shop,Jon Abara,talk,Platform,intermediate,200,420
D1,2027-05-13,13:40,14:25,Room B,P05,Accessible forms from the start,Kit Moreau,talk,Frontend,intro,110,160
D1,2027-05-13,14:30,15:15,Main Hall,P27,Shipping a design system,Noor Sami & Kit Moreau,talk,Frontend,intro,110,420
D1,2027-05-13,14:30,15:15,Room B,P04,Event sourcing in anger,Lena Voss,talk,Data,advanced,140,160
D1,2027-05-13,15:40,16:25,Main Hall,P18,Data contracts that stick,Ivy Quarle,talk,Data,intermediate,100,420
D1,2027-05-13,15:40,16:25,Room B,P11,Hydration bugs and how to find them,Lu Chen,talk,Frontend,advanced,120,160
D1,2027-05-13,16:30,17:10,Main Hall,P25,Micro-frontends: a retrospective,Sam Ito,talk,Frontend,intermediate,100,420
D1,2027-05-13,16:30,17:15,Room B,P07,Query plans for humans,Ana Ruiz,talk,Data,intermediate,130,160
D2,2027-05-14,09:00,09:45,Room B,P09,Kubernetes cost without tears,Priya Nand,talk,Platform,intermediate,160,160
D2,2027-05-14,09:00,09:45,Room C,P16,Testing the untestable UI,Eli Park,talk,Frontend,intermediate,90,90
D2,2027-05-14,09:50,10:35,Room B,P24,Batch to streaming in one quarter,Ola Brandt,talk,Data,advanced,110,160
D2,2027-05-14,09:50,10:35,Room C,P03,The build cache that lied,Rafa Sol,talk,Platform,intermediate,90,90
D2,2027-05-14,11:00,11:45,Room B,P10,Streaming joins explained,Ola Brandt,talk,Data,advanced,95,160
D2,2027-05-14,11:00,11:45,Room C,P08,CSS layers in practice,Sam Ito,talk,Frontend,intermediate,85,90
D2,2027-05-14,11:50,12:35,Room B,P15,Zero-downtime migrations,Ben Okoro,talk,Data,advanced,160,160
D2,2027-05-14,11:50,12:35,Room C,P14,Design tokens across three apps,Noor Sami,talk,Frontend,intro,70,90
D2,2027-05-14,13:40,14:25,Main Hall,P13,Vector search on a budget,Max Feld,talk,Data,intermediate,190,420
D2,2027-05-14,13:40,14:25,Room B,P17,SLOs your team believes,Gus Lind,talk,Platform,intermediate,130,160
D2,2027-05-14,14:30,15:15,Main Hall,P20,Platform teams as product teams,Ines Calder & Gus Lind,talk,Platform,intro,170,420
D2,2027-05-14,14:30,15:15,Room B,P23,Observability on a shoestring,Fen Holt,talk,Platform,intermediate,120,160
D2,2027-05-14,14:30,15:15,Room C,P26,Search relevance tuning,Max Feld,talk,Data,advanced,90,90
D2,2027-05-14,15:40,16:25,Room B,P02,Feature flags without the flag debt,Ines Calder,talk,Platform,intro,120,160
D2,2027-05-14,16:30,17:15,Main Hall,K2,A live demo you can break,Tobi Arden,keynote,,intro,340,420
Build the input yourself
Load the engine in node (global.window = global; require("./slotkit.js"); require("./slotreply.js")), then:
const K = SlotKit, R = SlotReply;
const f = K.form({ name: "My Conf", days: "2", first: "2027-05-13", start: "09:00", end: "17:30", talk: "45", breaks: "10:40 20 Coffee\n12:40 60 Lunch",
rooms: fs.readFileSync("rooms.csv", "utf8"), sessions: fs.readFileSync("sessions.csv", "utf8"), avail: "Ben Okoro: D1 10:00-17:30; D2" });
// f.grid: [{day, rows: [{kind: slot|break|plenary, a, b, cells: {room: [ids]}}]}] f.placed, f.unplaced
// f.clash: {room_window, speaker_window, room_overlap, double_booked} f.buffer, f.gap, f.align, f.depth, f.posture, f.publishBy, f.findings
K.gridCSV(f) // day, date, start, end, room, id, title, speakers, ...
K.trySwap(f, "P23", "P11") // {ok, why} - the same check the page runs on every proposed swap
const b = K.buildInput(f, "plan"); // or "overlap" (multi-track grids only)
const wire = K.toWire(b.input); // POST this to /estimate and /run
// then: const reply = R.normalize(R.parseReply(job.output.output), "plan");
// R.check(reply, b) // {status: kept or flagged, notes}
Get a token for the steps below from /tokens.html.
Step 1 — a tiny client helper
One function that sends JSON and unwraps the response. Paste a personal token from /tokens.html, or leave it empty and mint a guest token in step 2.
BASE="https://api.skillsafe.ai/v1/app-api"
TOKEN="paste-your-token-here" # from https://slot-desk.skillsafe.ai/tokens.html
api() { # api METHOD PATH [JSON-BODY]
curl -s -X "$1" "$BASE$2" -H "Content-Type: application/json" \
${TOKEN:+-H "Authorization: Bearer $TOKEN"} ${3:+--data "$3"}
}
import json, urllib.request
BASE = "https://api.skillsafe.ai/v1/app-api"
TOKEN = "paste-your-token-here" # from https://slot-desk.skillsafe.ai/tokens.html
def api(method, path, body=None, headers=None):
req = urllib.request.Request(BASE + path, method=method,
data=None if body is None else json.dumps(body).encode(),
headers={"Content-Type": "application/json", **({"Authorization": "Bearer " + TOKEN} if TOKEN else {}), **(headers or {})})
with urllib.request.urlopen(req) as r:
env = json.load(r)
if env.get("error"):
raise RuntimeError(env["error"])
return env["data"]
// slot-desk.mjs -- Node 18+ (global fetch)
const BASE = "https://api.skillsafe.ai/v1/app-api";
let TOKEN = "paste-your-token-here"; // from https://slot-desk.skillsafe.ai/tokens.html
async function api(method, path, body, headers = {}) {
const r = await fetch(BASE + path, { method, headers: { "Content-Type": "application/json",
...(TOKEN ? { Authorization: "Bearer " + TOKEN } : {}), ...headers },
body: body === undefined ? undefined : JSON.stringify(body) });
const env = await r.json();
if (env.error) throw new Error(env.error.code + ": " + env.error.message);
return env.data;
}
package main
import ("bytes"; "encoding/json"; "fmt"; "net/http")
const base = "https://api.skillsafe.ai/v1/app-api"
var token = "paste-your-token-here" // from https://slot-desk.skillsafe.ai/tokens.html
func api(method, path string, body any, headers map[string]string) (map[string]any, error) {
var buf bytes.Buffer
if body != nil { json.NewEncoder(&buf).Encode(body) }
req, _ := http.NewRequest(method, base+path, &buf)
req.Header.Set("Content-Type", "application/json")
if token != "" { req.Header.Set("Authorization", "Bearer "+token) }
for k, v := range headers { req.Header.Set(k, v) }
res, err := http.DefaultClient.Do(req)
if err != nil { return nil, err }
defer res.Body.Close()
var env struct { Data map[string]any; Error map[string]any }
json.NewDecoder(res.Body).Decode(&env)
if env.Error != nil { return nil, fmt.Errorf("%v", env.Error) }
return env.Data, nil
}
// SlotDesk.java -- Java 17+, with com.fasterxml.jackson.core:jackson-databind
import com.fasterxml.jackson.databind.*;
import java.net.URI; import java.net.http.*; import java.util.*;
public class SlotDesk {
static final String BASE = "https://api.skillsafe.ai/v1/app-api";
static String token = "paste-your-token-here"; // from https://slot-desk.skillsafe.ai/tokens.html
static final ObjectMapper M = new ObjectMapper();
static final HttpClient C = HttpClient.newHttpClient();
static JsonNode api(String method, String path, Object body, Map<String, String> headers) throws Exception {
var b = HttpRequest.newBuilder(URI.create(BASE + path)).header("Content-Type", "application/json");
if (!token.isEmpty()) b.header("Authorization", "Bearer " + token);
if (headers != null) headers.forEach(b::header);
b.method(method, body == null ? HttpRequest.BodyPublishers.noBody() : HttpRequest.BodyPublishers.ofString(M.writeValueAsString(body)));
JsonNode env = M.readTree(C.send(b.build(), HttpResponse.BodyHandlers.ofString()).body());
if (env.hasNonNull("error")) throw new RuntimeException(env.get("error").toString());
return env.get("data");
}
}
require "json"
require "net/http"
BASE = "https://api.skillsafe.ai/v1/app-api"
TOKEN = "paste-your-token-here" # from https://slot-desk.skillsafe.ai/tokens.html
def api(method, path, body = nil, headers = {})
uri = URI(BASE + path)
req = Net::HTTPGenericRequest.new(method, !body.nil?, true, uri.request_uri,
{ "Content-Type" => "application/json" }.merge(TOKEN.empty? ? {} : { "Authorization" => "Bearer #{TOKEN}" }).merge(headers))
req.body = JSON.generate(body) unless body.nil?
env = JSON.parse(Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) }.body)
raise env["error"].to_s if env["error"]
env["data"]
end
<?php
const BASE = "https://api.skillsafe.ai/v1/app-api";
$TOKEN = "paste-your-token-here"; // from https://slot-desk.skillsafe.ai/tokens.html
function api(string $method, string $path, $body = null, array $headers = []) {
global $TOKEN;
$h = ["Content-Type: application/json"];
if ($TOKEN) $h[] = "Authorization: Bearer $TOKEN";
foreach ($headers as $k => $v) $h[] = "$k: $v";
$ch = curl_init(BASE . $path);
curl_setopt_array($ch, [CURLOPT_CUSTOMREQUEST => $method, CURLOPT_HTTPHEADER => $h, CURLOPT_RETURNTRANSFER => true]);
if ($body !== null) curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
$env = json_decode(curl_exec($ch), true);
if (!empty($env["error"])) throw new Exception(json_encode($env["error"]));
return $env["data"];
}
// Program.cs -- .NET 6+ console app
using System.Net.Http.Json; using System.Text.Json; using System.Text.Json.Nodes;
static class Api {
public const string Base = "https://api.skillsafe.ai/v1/app-api";
public static string Token = "paste-your-token-here"; // from https://slot-desk.skillsafe.ai/tokens.html
static readonly HttpClient C = new();
public static async Task<JsonNode?> Call(string method, string path, object? body = null, Dictionary<string, string>? headers = null) {
var req = new HttpRequestMessage(new HttpMethod(method), Base + path);
if (Token != "") req.Headers.Add("Authorization", "Bearer " + Token);
if (headers != null) foreach (var h in headers) req.Headers.Add(h.Key, h.Value);
if (body != null) req.Content = JsonContent.Create(body);
var env = JsonNode.Parse(await (await C.SendAsync(req)).Content.ReadAsStringAsync())!;
if (env["error"] is JsonNode e && e.GetValueKind() != JsonValueKind.Null) throw new Exception(e.ToJsonString());
return env["data"];
}
}
Step 2 — a token
A guest token is enough for /me and /estimate. A run is metered, so it needs a personal token: sign in on /tokens.html and copy it from there.
# a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
TOKEN=$(curl -s -X POST "$BASE/guest" -H "Content-Type: application/json" -d '{"slug":"slot-desk"}' | jq -r '.data.token')
# a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
TOKEN = ""
TOKEN = api("POST", "/guest", {"slug": "slot-desk"})["token"]
// a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
TOKEN = "";
TOKEN = (await api("POST", "/guest", { slug: "slot-desk" })).token;
// inside main: a guest token (enough for /me and /estimate); a run needs your personal token
token = ""
g, err := api("POST", "/guest", map[string]string{"slug": "slot-desk"}, nil)
if err != nil { panic(err) }
token = g["token"].(string)
// inside main: a guest token (enough for /me and /estimate); a run needs your personal token
token = "";
token = api("POST", "/guest", Map.of("slug", "slot-desk"), null).get("token").asText();
# a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
TOKEN.replace("")
TOKEN.replace(api("POST", "/guest", { slug: "slot-desk" })["token"])
// a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
$TOKEN = "";
$TOKEN = api("POST", "/guest", ["slug" => "slot-desk"])["token"];
// a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
Api.Token = "";
Api.Token = (await Api.Call("POST", "/guest", new { slug = "slot-desk" }))!["token"]!.GetValue<string>();
Step 3 — GET /me
Who the token is (subject_type: user or guest) and the credit balance.
api GET /me | jq '.data | {subject_type, credits}'
me = api("GET", "/me")
print(me["subject_type"], me.get("credits"))
const me = await api("GET", "/me");
console.log(me.subject_type, me.credits);
me, _ := api("GET", "/me", nil, nil)
fmt.Println(me["subject_type"], me["credits"])
JsonNode me = api("GET", "/me", null, null);
System.out.println(me.get("subject_type") + " " + me.get("credits"));
me = api("GET", "/me")
puts [me["subject_type"], me["credits"]].inspect
$me = api("GET", "/me");
echo $me["subject_type"], " ", $me["credits"] ?? "", "\n";
var me = await Api.Call("GET", "/me");
Console.WriteLine($"{me!["subject_type"]} {me["credits"]}");
Step 4 — POST /estimate
Free. The body is the run input itself, the bare object (never wrapped in {"input": ...}). hold_credits is a reservation, not the price; min_credits is what the balance must cover; model_alias reads gpt-terra.
# input.json: the run input object - {"task": "plan", "event": "Event: ...; 2 days from 2027-05-13; day window 09:00-17:30 ...", "grid": "D1 09:00-09:50 PLENARY | Main Hall: K1 ...", "facts": "Slot standard: 45-minute talks in 50-minute slots ..."}
# - see the input table
api POST /estimate "$(cat input.json)" | jq '.data | {hold_credits, min_credits, model, model_alias, markup_bps, warnings}'
inp = json.load(open("input.json")) # {"task": "plan", "event": "Event: ...; 2 days from 2027-05-13; day window 09:00-17:30 ...", "grid": "D1 09:00-09:50 PLENARY | Main Hall: K1 ...", "facts": "Slot standard: 45-minute talks in 50-minute slots ..."}
# - see the input table
est = api("POST", "/estimate", inp)
print(est["hold_credits"], est["min_credits"], est["model"], est.get("warnings"))
import { readFileSync } from "node:fs";
const input = JSON.parse(readFileSync("input.json", "utf8")); // {task: "plan" or "overlap", event, grid, facts} (see the input table)
const est = await api("POST", "/estimate", input);
console.log(est.hold_credits, est.min_credits, est.model, est.warnings);
raw, _ := os.ReadFile("input.json") // add "os" to the imports
var input map[string]any
json.Unmarshal(raw, &input)
est, _ := api("POST", "/estimate", input, nil)
fmt.Println(est["hold_credits"], est["min_credits"], est["model"])
Map<String, Object> input = M.readValue(new java.io.File("input.json"), Map.class);
JsonNode est = api("POST", "/estimate", input, null);
System.out.println(est.get("hold_credits") + " " + est.get("min_credits") + " " + est.get("model"));
input = JSON.parse(File.read("input.json"))
est = api("POST", "/estimate", input)
puts [est["hold_credits"], est["min_credits"], est["model"]].inspect
$input = json_decode(file_get_contents("input.json"), true);
$est = api("POST", "/estimate", $input);
echo $est["hold_credits"], " ", $est["min_credits"], " ", $est["model"], "\n";
var input = JsonNode.Parse(File.ReadAllText("input.json"))!;
var est = await Api.Call("POST", "/estimate", input);
Console.WriteLine($"{est!["hold_credits"]} {est["min_credits"]} {est["model"]}");
Step 5 — POST /run, then poll the job
Send an Idempotency-Key built from the lane and the input: a retry with the same key never bills twice. The page uses slot-desk:<task>:<inputHash>:a<attempt> - the lane is part of the key - and its one automatic reformat retry is a new attempt number, named in the page as one extra run. A plan carries summary, fixed_points and caveats among its sections; an overlap pass carries pairs, clear and note. The code below prints either.
KEY="slot-desk:plan:$(shasum -a 256 input.json | cut -c1-16):a1"
JOB=$(curl -s -X POST "$BASE/run" -H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" \
-H "Idempotency-Key: $KEY" --data @input.json | jq -r '.data.job_id')
while :; do
J=$(api GET "/jobs/$JOB"); S=$(echo "$J" | jq -r '.data.status')
case "$S" in succeeded|failed|cancelled) break;; esac; sleep 2
done
echo "$J" | jq -r '.data.output.output' > reply.json # one JSON object, the reply contract
echo "$J" | jq '.data | {charged_credits, truncated}'
jq -r '.summary // .format, .note // .summary, ((.fixed_points // .pairs)[] | "\(.id // .a): \(.when // .why)")' reply.json
jq -r '.caveats // .note // empty' reply.json # caveats (plan) or the note for the program chair (overlap)
import hashlib, time
key = "slot-desk:" + inp["task"] + ":" + hashlib.sha256(json.dumps(inp).encode()).hexdigest()[:16] + ":a1"
job = api("POST", "/run", inp, {"Idempotency-Key": key})
while True:
j = api("GET", "/jobs/" + job["job_id"])
if j["status"] in ("succeeded", "failed", "cancelled"):
break
time.sleep(2)
reply = json.loads(j["output"]["output"]) # the reply as one JSON object
print(j["charged_credits"], j.get("truncated"), reply.get("summary"))
print(reply.get("note") or reply.get("summary", ""))
for s in reply.get("fixed_points") or reply.get("pairs") or []:
print(s.get("id", s.get("a")), "-", s.get("when", s.get("why")))
if reply.get("caveats"):
print("caveats:", reply["caveats"])
import { createHash } from "node:crypto";
const key = `slot-desk:${input.task}:${createHash("sha256").update(JSON.stringify(input)).digest("hex").slice(0, 16)}:a1`;
const job = await api("POST", "/run", input, { "Idempotency-Key": key });
let j;
do { await new Promise((r) => setTimeout(r, 2000)); j = await api("GET", "/jobs/" + job.job_id); }
while (!["succeeded", "failed", "cancelled"].includes(j.status));
const reply = JSON.parse(j.output.output);
console.log(j.charged_credits, j.truncated, reply.summary);
console.log(reply.note ?? reply.summary);
for (const s of reply.fixed_points || reply.pairs || []) console.log(s.id ?? s.a, "-", s.when ?? s.why);
if (reply.caveats) console.log("caveats:", reply.caveats);
sum := sha256.Sum256(raw) // add "crypto/sha256", "encoding/hex", "time" to the imports
key := "slot-desk:" + input["task"].(string) + ":" + hex.EncodeToString(sum[:8]) + ":a1"
job, _ := api("POST", "/run", input, map[string]string{"Idempotency-Key": key})
var j map[string]any
for {
time.Sleep(2 * time.Second)
j, _ = api("GET", "/jobs/"+job["job_id"].(string), nil, nil)
if s := j["status"]; s == "succeeded" || s == "failed" || s == "cancelled" { break }
}
var reply map[string]any
json.Unmarshal([]byte(j["output"].(map[string]any)["output"].(string)), &reply)
items, _ := reply["fixed_points"].([]any) // an overlap pass lists "pairs" instead
fmt.Println(j["charged_credits"], j["truncated"], reply["summary"])
fmt.Println(reply["note"], reply["summary"])
for _, x := range items {
c := x.(map[string]any)
fmt.Println(c["id"], "-", c["status"])
}
if fix, ok := reply["caveats"].(string); ok && fix != "" {
fmt.Println("caveats:", fix)
}
var digest = java.security.MessageDigest.getInstance("SHA-256").digest(M.writeValueAsBytes(input));
String key = "slot-desk:" + input.get("task") + ":" + HexFormat.of().formatHex(digest, 0, 8) + ":a1";
JsonNode job = api("POST", "/run", input, Map.of("Idempotency-Key", key)), j;
do { Thread.sleep(2000); j = api("GET", "/jobs/" + job.get("job_id").asText(), null, null); }
while (!Set.of("succeeded", "failed", "cancelled").contains(j.get("status").asText()));
JsonNode reply = M.readTree(j.get("output").get("output").asText());
System.out.println(j.get("charged_credits") + " " + reply.get("summary"));
System.out.println(reply.path("note").asText(reply.path("summary").asText()));
for (JsonNode s : reply.has("fixed_points") ? reply.path("fixed_points") : reply.path("pairs")) System.out.println(s.path("id").asText(s.path("a").asText()) + " - " + s.path("status").asText(s.path("why").asText()));
if (!reply.path("caveats").asText().isEmpty()) System.out.println("caveats: " + reply.get("caveats").asText());
require "digest"
key = "slot-desk:#{input["task"]}:#{Digest::SHA256.hexdigest(JSON.generate(input))[0, 16]}:a1"
job = api("POST", "/run", input, { "Idempotency-Key" => key })
j = nil
loop do
sleep 2
j = api("GET", "/jobs/#{job["job_id"]}")
break if %w[succeeded failed cancelled].include?(j["status"])
end
reply = JSON.parse(j["output"]["output"])
puts [j["charged_credits"], j["truncated"], reply["summary"]].inspect
puts reply["note"] || reply["summary"]
(reply["fixed_points"] || reply["pairs"] || []).each { |s| puts "#{s["id"] || s["a"]} - #{s["when"] || s["why"]}" }
puts "caveats: #{reply["caveats"]}" unless reply["caveats"].to_s.empty?
$key = "slot-desk:" . $input["task"] . ":" . substr(hash("sha256", json_encode($input)), 0, 16) . ":a1";
$job = api("POST", "/run", $input, ["Idempotency-Key" => $key]);
do { sleep(2); $j = api("GET", "/jobs/" . $job["job_id"]); }
while (!in_array($j["status"], ["succeeded", "failed", "cancelled"]));
$reply = json_decode($j["output"]["output"], true);
echo $j["charged_credits"], " ", ($reply["summary"] ?? ""), "\n";
echo $reply["note"] ?? $reply["summary"] ?? "", "\n";
foreach ($reply["fixed_points"] ?? $reply["pairs"] ?? [] as $s) echo $s["id"] ?? $s["a"], " - ", $s["when"] ?? $s["why"], "\n";
if (!empty($reply["caveats"])) echo "caveats: ", $reply["caveats"], "\n";
var digest = System.Security.Cryptography.SHA256.HashData(System.Text.Encoding.UTF8.GetBytes(input.ToJsonString()));
var key = $"slot-desk:{input["task"]}:{Convert.ToHexString(digest, 0, 8).ToLowerInvariant()}:a1";
var job = await Api.Call("POST", "/run", input, new() { ["Idempotency-Key"] = key });
JsonNode j;
do { await Task.Delay(2000); j = (await Api.Call("GET", "/jobs/" + job!["job_id"]))!; }
while (!new[] { "succeeded", "failed", "cancelled" }.Contains(j["status"]!.GetValue<string>()));
var reply = JsonNode.Parse(j["output"]!["output"]!.GetValue<string>());
Console.WriteLine($"{j["charged_credits"]} {reply!["summary"]}");
Console.WriteLine($"{reply["note"] ?? reply["summary"]}");
foreach (var s in (reply["fixed_points"] ?? reply["pairs"]) as JsonArray ?? new JsonArray()) Console.WriteLine($"{s!["id"] ?? s["a"]} - {s["when"] ?? s["why"]}");
if (reply["caveats"] is JsonNode fix && fix.GetValue<string>() != "") Console.WriteLine("caveats: " + fix);
Step 6 — POST /run-stream
The same run as Server-Sent Events, with the same input.json. Browsers receive progress ticks rather than text deltas; the final done event carries the reply.
# Server-Sent Events: tick/delta events while the job runs, then a final event with the job
curl -N -s -X POST "$BASE/run-stream" -H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" \
-H "Idempotency-Key: slot-desk:plan:$(shasum -a 256 input.json | cut -c1-16):a1" --data @input.json
rev = json.load(open("input.json")) # the same run input as input.json: {"task": "plan", "event": "Event: ...; 2 days from 2027-05-13; day window 09:00-17:30 ...", "grid": "D1 09:00-09:50 PLENARY | Main Hall: K1 ...", "facts": "Slot standard: 45-minute talks in 50-minute slots ..."}
# - see the input table
req = urllib.request.Request(BASE + "/run-stream", method="POST", data=json.dumps(rev).encode(),
headers={"Content-Type": "application/json", "Authorization": "Bearer " + TOKEN,
"Idempotency-Key": "slot-desk:plan:" + hashlib.sha256(json.dumps(rev).encode()).hexdigest()[:16] + ":a1"})
with urllib.request.urlopen(req) as r:
for line in r:
line = line.decode().strip()
if line.startswith("data:"):
print(line[5:].strip()[:200])
const rev = JSON.parse(readFileSync("input.json", "utf8"));
const r = await fetch(BASE + "/run-stream", { method: "POST", body: JSON.stringify(rev), headers: {
"Content-Type": "application/json", Authorization: "Bearer " + TOKEN,
"Idempotency-Key": `slot-desk:plan:${createHash("sha256").update(JSON.stringify(rev)).digest("hex").slice(0, 16)}:a1` } });
const dec = new TextDecoder();
for await (const chunk of r.body) for (const line of dec.decode(chunk).split("\n"))
if (line.startsWith("data:")) console.log(line.slice(5).trim().slice(0, 200));
rraw, _ := os.ReadFile("input.json") // add "bufio", "strings" to the imports
rs := sha256.Sum256(rraw)
req, _ := http.NewRequest("POST", base+"/run-stream", bytes.NewReader(rraw))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Idempotency-Key", "slot-desk:plan:"+hex.EncodeToString(rs[:8])+":a1")
res, _ := http.DefaultClient.Do(req)
sc := bufio.NewScanner(res.Body)
for sc.Scan() { if l := sc.Text(); strings.HasPrefix(l, "data:") { fmt.Println(l[5:]) } }
String rev = java.nio.file.Files.readString(java.nio.file.Path.of("input.json"));
var rd = java.security.MessageDigest.getInstance("SHA-256").digest(rev.getBytes());
var sreq = HttpRequest.newBuilder(URI.create(BASE + "/run-stream")).header("Content-Type", "application/json")
.header("Authorization", "Bearer " + token).header("Idempotency-Key", "slot-desk:plan:" + HexFormat.of().formatHex(rd, 0, 8) + ":a1")
.POST(HttpRequest.BodyPublishers.ofString(rev)).build();
C.send(sreq, HttpResponse.BodyHandlers.ofLines()).body().filter(l -> l.startsWith("data:")).forEach(System.out::println);
rev = File.read("input.json")
uri = URI(BASE + "/run-stream")
req = Net::HTTP::Post.new(uri, "Content-Type" => "application/json", "Authorization" => "Bearer #{TOKEN}",
"Idempotency-Key" => "slot-desk:plan:#{Digest::SHA256.hexdigest(rev)[0, 16]}:a1")
req.body = rev
Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |h|
h.request(req) { |res| res.read_body { |chunk| chunk.each_line { |l| puts l if l.start_with?("data:") } } }
end
$rev = file_get_contents("input.json");
$ch = curl_init(BASE . "/run-stream");
curl_setopt_array($ch, [CURLOPT_POST => true, CURLOPT_POSTFIELDS => $rev, CURLOPT_HTTPHEADER => [
"Content-Type: application/json", "Authorization: Bearer $TOKEN",
"Idempotency-Key: slot-desk:plan:" . substr(hash("sha256", $rev), 0, 16) . ":a1"],
CURLOPT_WRITEFUNCTION => function ($ch, $chunk) { foreach (explode("\n", $chunk) as $l) if (str_starts_with($l, "data:")) echo $l, "\n"; return strlen($chunk); }]);
curl_exec($ch);
var rev = File.ReadAllText("input.json");
var rd = System.Security.Cryptography.SHA256.HashData(System.Text.Encoding.UTF8.GetBytes(rev));
var sreq = new HttpRequestMessage(HttpMethod.Post, Api.Base + "/run-stream") { Content = new StringContent(rev, System.Text.Encoding.UTF8, "application/json") };
sreq.Headers.Add("Authorization", "Bearer " + Api.Token);
sreq.Headers.Add("Idempotency-Key", $"slot-desk:plan:{Convert.ToHexString(rd, 0, 8).ToLowerInvariant()}:a1");
using var http = new HttpClient();
using var res = await http.SendAsync(sreq, HttpCompletionOption.ResponseHeadersRead);
using var rdr = new StreamReader(await res.Content.ReadAsStreamAsync());
for (string? l; (l = await rdr.ReadLineAsync()) != null;) if (l.StartsWith("data:")) Console.WriteLine(l);