Run Social Slate from your own code
Both metered lanes are available over HTTP and each returns one JSON object. The division of labour: the page confirms event-hospitality has decided each occasion's format and the alcohol posture (or routes there and stops), re-ranks the slate-depth menu with every deleted rung struck, finds the occasions sharing hours with each other or with the main programme, checks the inclusion floor per slot against the access commitments already published, protects the closing occasion, names an owner per occasion and checks the venue-hold and access-statement dates - all free, in socialkit.js. The run writes the slate for validation, or the published social programme page; the page then holds the reply to its own board (socialreply.js). It never decides a format, an alcohol posture or a commitment.
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":"social-slate"}), so no slug header is needed afterwards. Send it as Authorization: Bearer YOUR_TOKEN. 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, occasions, commitments, facts) is missing. notes may be an empty string. |
| 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, chosen by task:
slate- the slate for the organizer to validate, section by section: the verdict, the depth pick and what moved it, every overlap, every floor row (slot, commitment, status, clearing option), the closing occasion and its protection, the owner per occasion, the dates, the hand-offs and the caveats.announce- the published social programme page: a title, an intro, every occasion with its hours, venue, cost, contact and an access statement built only from the page's access facts, what to expect slot by slot, the code-of-conduct note and a note. The lane exists only when event-hospitality has decided.
A missing or unknown task is answered as slate. Both lanes send the same six fields; for the page, the facts carry each occasion's access facts and the slate's depth, floor statuses and closing protection (the page fills these from the slate). The notes field may be an empty string.
The input
| Field | Required | What |
|---|---|---|
task | yes | slate or announce. If it is missing or neither, do slate and set "task": "slate". |
event | yes | the event basics (name, dates, city, attendance, the as-of date), the published grid's hours per day, and the interview answers: Q1 what event-hospitality has decided; Q2 how many occasions are on the table and whether somebody asked for a second; Q3 who published the access set and at what depth, and whether one format can carry every commitment at once; Q4 whether two occasions share hours; Q5 owners (answered per occasion); Q6 the effort ceiling and what last edition left; Q7 one-off or recurring; Q8 when the social programme page goes public. |
occasions | yes | one line per occasion as entered (name, kind, when, venue, capacity, owner, alcohol, step-free, quiet option, cost, closing, and whether it is off the slate), then one line per occasion's venue-hold and access-statement dates. |
commitments | yes | the published access commitments, one per line, as published, with what the page checks each against. |
notes | no | pasted notes; may be empty. A request in the notes is never a commitment. |
facts | yes | the page's verdict, its self-set lines, the slate, the depth menu re-ranked, the overlaps, the floor rows, the closing occasion, the owners, the dates, the hand-offs, for the announce task each occasion's access facts, the options per slot and the slate it carries - each item with its own must line, each capped list saying how many rows it holds and how many were cut - 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
- This skill decides one thing: how many official social occasions the event runs, and how they sit against each other. What is already decided, and by whom: the format of any one occasion and the event's alcohol posture belong to samber/dev-event-organizer-skills@event-hospitality; if it has not decided them, route there and stop. Which access provisions exist at all belongs to samber/dev-event-organizer-skills@event-accessibility-inclusion - never invent that set, check the slate against whatever it published. The published grid belongs to samber/dev-event-organizer-skills@event-schedule-design - hand it a non-overlap requirement, never place or move a slot yourself. Third-party satellites belong to samber/dev-event-organizer-skills@event-side-event-coordination; invite-only gatherings to samber/dev-event-organizer-skills@event-vip-social-program; the scope clause to samber/dev-event-organizer-skills@event-code-of-conduct. Hand off - never decide for them.
- The depth menu, in efficiency order: a single social occasion (the default), two occasions on separate slots, a themed multi-activity track. Move up to two occasions only when Q2 reports that somebody actually asked for a second occasion and Q5 names its owner. Promote the track only when the published access set holds commitments no single format can carry at once, Q5 names an owner per activity, and Q7 says the edition recurs. When Q5 names no owner beyond the first occasion, delete both upper rungs - delete, not demote. A slate published as a list of options with no statement of which one clears the access floor is deleted too.
- The inclusion floor is a slate property, never an activity property: in every hour where the slate offers anything at all, at least one option clears the access set Q3 reported. A slot whose options nobody can clear is a slot the event excluded somebody from: add a clearing option, move one activity out of it, or narrow the slate - never note the gap and publish anyway. Publish which option clears the floor beside the slate itself.
- The closing occasion is programme, not leftover: the main programme ends early enough to leave it its reserved time; it is never cut first, never overlapped, and has a named owner. The cue that enforces it on the day is samber/dev-event-organizer-skills@event-run-of-show's.
- Every occasion beyond the first needs a named owner, per occasion, never for the slate as a whole and never a function. Publish each access statement before, never after, the slate goes public.
House rules
- Use only the input and the rules above. Copy the page's short facts exactly: the verdict, the depth pick, every occasion name, slot, commitment, status, option, owner, date and venue. Every number in your text must appear in the input.
- Every "must: [...]" line in the facts applies to the item directly above it - every occasion, every overlap, every slot, every owner, every date and every hand-off, not only the first. Do every item it lists.
- Copy the page's rows exactly as sent, in order - no more, no fewer. When the facts say rows were cut, do not invent the cut rows.
- Label anything that is not one of the rules above, and is not in the input, as your own design; label the page's self-set lines self-set.
- Never invent an occasion, a venue, an access feature, a commitment, a price, a time or an owner. Never state a percentage.
- Never state an alcohol posture or a format the page did not give - event-hospitality decides both. Never move a slot on the grid.
- Refer to people by name, as they; never use he, she, him, her, his or hers.
- 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
slate
summary- one sentence, at most 40 words: the verdict, the most important open item, and the next step.verdict- the page's verdict, exactly:decide the format with hospitality first,fix firstorready to publish.depth- one object:pick- the page's depth pick, exactly (the word "none" when the verdict isdecide the format with hospitality first);reason- one sentence that says what moved the pick, using the page's words from the depth must line: the question numbers (for example "Q2" and "Q5", or "Q3", "Q5" and "Q7") when interview answers moved it, the word "default" when nothing did, or the word "hospitality" when Q1 is open.overlaps- one object per overlap row the page sent, in its order, every row, not only the first:a- the page's occasion exactly;b- the page's other occasion exactly, or "main programme";kind- the page's kind exactly, "occasions" (two occasions in the same hours) or "programme" (an occasion in the main programme's hours).[]when the page sent none.floor- one object per floor row the page sent, in its order, every slot, not only the first:slot- the page's slot exactly;commitment- the page's commitment exactly, as published;status- the page's status exactly, "clears", "not cleared", "unknown" or "not checkable";option- the page's clearing option exactly, or the word "none".[]when the page sent none.closing- one object:occasion- the page's closing occasion exactly, or the word "none";protection- one sentence using the words "programme" and "ends": the main programme ends early enough to leave the closing occasion its reserved time, never cut first and never overlapped; when the page names no closing occasion, the words "no closing occasion".owners- one object per owner row the page sent, in its order, every occasion, not only the first:occasion- the page's occasion exactly;owner- the page's name exactly, or the words "not named".[]when the page sent none.dates- one object per date row the page sent, in its order, every row:occasion- the page's occasion exactly;hold_by- the page's date exactly, or the words "not set";publish_by- the page's date exactly, or the words "not set";status- the page's status exactly, "set", "missing", "passed" or "late".[]when the page sent none.handoffs- one string per hand-off the page sent, in its order, every one: says what is handed on and contains each sibling the row names (for example "event-schedule-design", "event-run-of-show", "event-side-event-coordination", "event-accessibility-inclusion", "event-code-of-conduct" or "event-hospitality").caveats- at most 5 short strings: the[fix]findings first in the page's order, then the most important[check]findings. Never empty when the page has a[fix]finding;[]otherwise when there is nothing to say.
{
"task": "slate",
"summary": "one sentence",
"verdict": "fix first",
"depth": { "pick": "the page's pick", "reason": "one sentence" },
"overlaps": [ { "a": "the page's occasion", "b": "the page's other occasion", "kind": "occasions" } ],
"floor": [ { "slot": "the page's slot", "commitment": "the page's commitment", "status": "clears", "option": "the page's option" } ],
"closing": { "occasion": "the page's closing occasion", "protection": "one sentence" },
"owners": [ { "occasion": "the page's occasion", "owner": "the page's name" } ],
"dates": [ { "occasion": "the page's occasion", "hold_by": "the page's date", "publish_by": "the page's date", "status": "set" } ],
"handoffs": [ "one sentence" ],
"caveats": [ "a caveat" ]
}
The page holds it to its own board: the verdict exactly; the depth pick exactly with the question that moved it, or "default"; every overlap's two sides and kind; every floor row's slot, commitment, status ("clears", "not cleared", "unknown" or "not checkable") and option; the closing occasion with "programme" and "ends" in its protection; every owner, or "not named"; every date row; every hand-off naming its sibling; caveats when the page has a fix; and no number, percentage, price, format, drink, quote or pronoun the input does not hold. Required words are read tolerantly: "covered" is clears, "somebody asked" is Q2.
announce
title- a short title that contains the event's name exactly as the page gives it.intro- one or two sentences: the official occasions open to every badge, and that each one has its access statement below.occasions- one object per occasion the page sent, in its order, every occasion, not only the first:name- the page's name exactly;when- the day and the hours, containing the page's start and end clock times (for example "18:00" and "21:00"), or the words "not set";where- contains the page's venue exactly;cost- contains the page's cost exactly, the words "no cost" when the page says no cost, or the words "not said";access- the access statement, built only from the page's access facts for that occasion: use each commitment's word from its must line ("step-free", "quiet", "alcohol-free", "no-cost" or "family-inclusive"), with the word "not" where the occasion does not clear it and the word "unknown" where the page says unknown - never add an access feature the page does not give;owner_contact- the page's owner exactly, or the words "not named".what_to_expect- one to three sentences, slot by slot: name every alcohol-free option and every quiet option the page lists, with the words "alcohol-free" and "quiet" - only the ones the page has; where the page lists none for a published commitment, say so with the word "no".code_of_conduct_note- one sentence using the words "code of conduct" and "every": the policy reaches every occasion on this page.note- one sentence: when the page's verdict is notready to publish, begin with "Not ready to publish:" and say what to fix first; otherwise use the word "publish" and say what happens next.
{
"task": "announce",
"title": "a short title",
"intro": "one or two sentences",
"occasions": [ { "name": "the page's name", "when": "the day and the hours", "where": "the page's venue", "cost": "the page's cost", "access": "the access statement", "owner_contact": "the page's owner" } ],
"what_to_expect": "one to three sentences",
"code_of_conduct_note": "one sentence",
"note": "one sentence"
}
The page requires the event's name in the title; every occasion the page sent, in order, with its name, its start and end clock times, its venue, its cost, an access statement using each published commitment's word (with "not" where the occasion does not clear it and "unknown" where nobody knows) and its contact; what to expect naming every alcohol-free and quiet option the page has; the words "code of conduct" and "every"; and a note beginning "Not ready to publish:" unless the page is ready.
Worked examples - both lanes
Two of the page's four free samples (fictional events, organizations and people). Each input is exactly what the page sends; each reply is the saved reply the sample replays, shipped unedited; the last line is the page's own check. The announce input carries its sample's saved slate, as the handoff does.
A 700-person, 2-day conference with 3 occasions - a bar with no step-free entrance, an overlap, a closing party with no owner - slate
Verdict: fix first; ready to publish: no; 3 occasions on the slate; depth: Two occasions on separate slots; 2 overlaps; 4 slots, 2 uncovered; closing: not protected; 8 fix-level findings.
Input:
{
"task": "slate",
"event": "Event: \"Harborlight Dev Summit 2027\" | dates: 2027-06-08 to 2027-06-09 | city: \"Port Avery\" | attendance: 700 | as of: 2027-04-01\nPublished grid hours (event-schedule-design's): [2027-06-08 09:00-17:30; 2027-06-09 09:00-17:45]\nQ1 event-hospitality has decided: \"both decided - the format of every occasion and the event's alcohol posture\"\nQ2 occasions on the table: 3 | a second occasion: \"somebody actually asked for a second occasion\"\nQ3 access set published by and at what depth: \"Ruth Okomberly, accessibility lead - published in the attendee guide\" | one format can carry every published commitment at once: \"no\"\nQ4 two occasions in the same hours: yes | answered from the occasions table\nQ5 owners: answered per occasion in the occasions table | occasions beyond the first with a named owner: 1\nQ6 effort ceiling: volunteer hours beyond the first occasion: 12 | a budget line beyond the first: \"yes\" | a second room at no extra ask: \"no\" | last edition left: \"the closing drink shrank to ten minutes when the talks ran long\"\nQ7: \"a recurring event\"\nQ8 the social programme page goes public: 2027-05-10 | each occasion's venue-hold and access-statement dates: in the occasions field",
"occasions": "Occasions - 3, one per line, as entered (format, alcohol, venue and hours as event-hospitality decided them):\n- occasion 1: name: \"Welcome reception\" | kind: \"venue-based mixer\" | when: 2027-06-08 18:00-21:00 | venue: \"The Brassmoor Tap, upstairs bar\" | capacity: 250 | owner: \"Dana Quillmere\" | alcohol: \"central\" | step-free: \"no\" | quiet option: \"no\" | cost: \"no cost\" | closing: no\n- occasion 2: name: \"Board-game night\" | kind: \"games table\" | when: 2027-06-08 20:00-22:30 | venue: \"Harbormoor Hall foyer lounge\" | capacity: 60 | owner: \"Priya Thandrevel\" | alcohol: \"none\" | step-free: \"yes\" | quiet option: \"yes\" | cost: \"no cost\" | closing: no\n- occasion 3: name: \"Closing party\" | kind: \"venue-based mixer\" | when: 2027-06-09 17:30-20:00 | venue: \"Harbormoor Hall rooftop\" | capacity: 400 | owner: \"not named\" | alcohol: \"available, never central\" | step-free: \"unknown\" | quiet option: \"no\" | cost: \"no cost\" | closing: yes\nQ8 dates - 3:\n- dates: occasion: \"Welcome reception\" | venue held by: 2027-04-15 | access statement published by: 2027-05-01\n- dates: occasion: \"Board-game night\" | venue held by: 2027-04-20 | access statement published by: 2027-05-01\n- dates: occasion: \"Closing party\" | venue held by: 2027-04-15 | access statement published by: 2027-05-20",
"commitments": "Published access commitments (Q3) - 4, as published - never invented, never added to:\n- commitment 1: \"Step-free access to every venue\" | the page checks it as: \"step-free\" | cleared by: an occasion whose step-free column is yes\n- commitment 2: \"An alcohol-free option\" | the page checks it as: \"alcohol-free\" | cleared by: an occasion whose alcohol, as hospitality decided it, is none or available but never central\n- commitment 3: \"A quiet space\" | the page checks it as: \"quiet\" | cleared by: an occasion whose quiet-option column is yes\n- commitment 4: \"Live captions for any spoken remarks\" | the page checks it as: \"not checkable - no column\" | cleared by: nothing the page can read",
"notes": "Two sponsors asked whether the welcome reception could be a pub crawl instead. Last edition the closing drink shrank to ten minutes.",
"facts": "Verdict: \"fix first\" | ready to publish: no | fix-level findings: 8\nSelf-set lines: [the closing occasion wants at least 15 min after the main programme ends; a date under 14 days away is soon; at most 20 occasions, 20 overlaps, 40 floor rows and 60 findings sent; a summary cap of 40 words; at most 5 caveats; each rung holds one occasion, two on separate slots, or a track of any count; an alcohol-free option is an occasion whose alcohol is none or available but never central; a slot is a stretch of time in which the same occasions run]\nNever stated: [an occasion, a venue, an access feature, a commitment, a price, a time or an owner the input does not hold; an alcohol posture or a format the page did not give; a moved slot on the grid; a percentage]\nGate (Q1): \"both decided - the format of every occasion and the event's alcohol posture\" | stop: no\nSlate - official occasions open to every badge: 3 | rows below: 3 of 3 | cut: 0 (at most 20 sent, self-set) - in this order:\n- slate 1: name: \"Welcome reception\" | when: 2027-06-08 18:00-21:00 | venue: \"The Brassmoor Tap, upstairs bar\" | owner: \"Dana Quillmere\" | closing: no\n- slate 2: name: \"Board-game night\" | when: 2027-06-08 20:00-22:30 | venue: \"Harbormoor Hall foyer lounge\" | owner: \"Priya Thandrevel\" | closing: no\n- slate 3: name: \"Closing party\" | when: 2027-06-09 17:30-20:00 | venue: \"Harbormoor Hall rooftop\" | owner: \"not named\" | closing: yes\nOff the slate - permitted or invite-only, never provided: none\nSlate depth menu, re-ranked - pick: \"Two occasions on separate slots\"\n- rung 1: \"Two occasions on separate slots\" | moved: made the pick by Q2 and Q5: somebody actually asked for a second occasion and Q5 names its owner - both halves of the promotion condition hold\n- rung 2: \"A single social occasion\" | the skill's default\n- rung 3: \"Themed multi-activity track\"\n- deleted, not demoted: \"A slate published as a list of options with no statement of which one clears the access floor\" | reason: it looks like the generous rung, but the choice is fake for exactly the people the floor exists for - a slate is not an access provision because it is long\n- notes: Dominance check: 3 rungs, 3 pairs, zero strict-dominance relations - clean only by construction, which is never a pass; each rung is the one below plus another occasion, so effort and coverage march together. Concentration - one shared moment everybody was in - is a real property of a slate, named here and not ranked.\nDepth: pick: \"Two occasions on separate slots\" | moved by: \"Q2 and Q5\" | occasions on the slate: 3 | the pick holds: 2\n depth must: [pick \"Two occasions on separate slots\" exactly; reason uses the words \"Q2\" and \"Q5\"]\nOverlaps (Q4) - occasions sharing hours with each other or with the main programme: 2 | rows below: 2 of 2 | cut: 0 (at most 20 sent, self-set) - in this order:\n- overlap 1: a: \"Welcome reception\" | b: \"Board-game night\" | kind: \"occasions\" | shared hours: 2027-06-08 20:00-21:00\n must: [a \"Welcome reception\" exactly; b \"Board-game night\" exactly; kind \"occasions\" exactly]\n- overlap 2: a: \"Closing party\" | b: \"main programme\" | kind: \"programme\" | shared hours: 2027-06-09 17:30-17:45\n must: [a \"Closing party\" exactly; b \"main programme\" exactly; kind \"programme\" exactly]\nInclusion floor, per slot - one row per slot and published commitment: 16 | rows below: 16 of 16 | cut: 0 (at most 40 sent, self-set) - in this order:\n- floor 1: slot: \"2027-06-08 18:00-20:00\" | commitment: \"Step-free access to every venue\" | status: \"not cleared\" | option: \"none\" | options in the slot: [\"Welcome reception\"]\n must: [slot \"2027-06-08 18:00-20:00\" exactly; commitment \"Step-free access to every venue\" exactly; status \"not cleared\" exactly; option \"none\" exactly]\n- floor 2: slot: \"2027-06-08 18:00-20:00\" | commitment: \"An alcohol-free option\" | status: \"not cleared\" | option: \"none\" | options in the slot: [\"Welcome reception\"]\n must: [slot \"2027-06-08 18:00-20:00\" exactly; commitment \"An alcohol-free option\" exactly; status \"not cleared\" exactly; option \"none\" exactly]\n- floor 3: slot: \"2027-06-08 18:00-20:00\" | commitment: \"A quiet space\" | status: \"not cleared\" | option: \"none\" | options in the slot: [\"Welcome reception\"]\n must: [slot \"2027-06-08 18:00-20:00\" exactly; commitment \"A quiet space\" exactly; status \"not cleared\" exactly; option \"none\" exactly]\n- floor 4: slot: \"2027-06-08 18:00-20:00\" | commitment: \"Live captions for any spoken remarks\" | status: \"not checkable\" | option: \"none\" | options in the slot: [\"Welcome reception\"]\n must: [slot \"2027-06-08 18:00-20:00\" exactly; commitment \"Live captions for any spoken remarks\" exactly; status \"not checkable\" exactly; option \"none\" exactly]\n- floor 5: slot: \"2027-06-08 20:00-21:00\" | commitment: \"Step-free access to every venue\" | status: \"clears\" | option: \"Board-game night\" | options in the slot: [\"Welcome reception\"; \"Board-game night\"]\n must: [slot \"2027-06-08 20:00-21:00\" exactly; commitment \"Step-free access to every venue\" exactly; status \"clears\" exactly; option \"Board-game night\" exactly]\n- floor 6: slot: \"2027-06-08 20:00-21:00\" | commitment: \"An alcohol-free option\" | status: \"clears\" | option: \"Board-game night\" | options in the slot: [\"Welcome reception\"; \"Board-game night\"]\n must: [slot \"2027-06-08 20:00-21:00\" exactly; commitment \"An alcohol-free option\" exactly; status \"clears\" exactly; option \"Board-game night\" exactly]\n- floor 7: slot: \"2027-06-08 20:00-21:00\" | commitment: \"A quiet space\" | status: \"clears\" | option: \"Board-game night\" | options in the slot: [\"Welcome reception\"; \"Board-game night\"]\n must: [slot \"2027-06-08 20:00-21:00\" exactly; commitment \"A quiet space\" exactly; status \"clears\" exactly; option \"Board-game night\" exactly]\n- floor 8: slot: \"2027-06-08 20:00-21:00\" | commitment: \"Live captions for any spoken remarks\" | status: \"not checkable\" | option: \"none\" | options in the slot: [\"Welcome reception\"; \"Board-game night\"]\n must: [slot \"2027-06-08 20:00-21:00\" exactly; commitment \"Live captions for any spoken remarks\" exactly; status \"not checkable\" exactly; option \"none\" exactly]\n- floor 9: slot: \"2027-06-08 21:00-22:30\" | commitment: \"Step-free access to every venue\" | status: \"clears\" | option: \"Board-game night\" | options in the slot: [\"Board-game night\"]\n must: [slot \"2027-06-08 21:00-22:30\" exactly; commitment \"Step-free access to every venue\" exactly; status \"clears\" exactly; option \"Board-game night\" exactly]\n- floor 10: slot: \"2027-06-08 21:00-22:30\" | commitment: \"An alcohol-free option\" | status: \"clears\" | option: \"Board-game night\" | options in the slot: [\"Board-game night\"]\n must: [slot \"2027-06-08 21:00-22:30\" exactly; commitment \"An alcohol-free option\" exactly; status \"clears\" exactly; option \"Board-game night\" exactly]\n- floor 11: slot: \"2027-06-08 21:00-22:30\" | commitment: \"A quiet space\" | status: \"clears\" | option: \"Board-game night\" | options in the slot: [\"Board-game night\"]\n must: [slot \"2027-06-08 21:00-22:30\" exactly; commitment \"A quiet space\" exactly; status \"clears\" exactly; option \"Board-game night\" exactly]\n- floor 12: slot: \"2027-06-08 21:00-22:30\" | commitment: \"Live captions for any spoken remarks\" | status: \"not checkable\" | option: \"none\" | options in the slot: [\"Board-game night\"]\n must: [slot \"2027-06-08 21:00-22:30\" exactly; commitment \"Live captions for any spoken remarks\" exactly; status \"not checkable\" exactly; option \"none\" exactly]\n- floor 13: slot: \"2027-06-09 17:30-20:00\" | commitment: \"Step-free access to every venue\" | status: \"unknown\" | option: \"none\" | options in the slot: [\"Closing party\"]\n must: [slot \"2027-06-09 17:30-20:00\" exactly; commitment \"Step-free access to every venue\" exactly; status \"unknown\" exactly; option \"none\" exactly]\n- floor 14: slot: \"2027-06-09 17:30-20:00\" | commitment: \"An alcohol-free option\" | status: \"clears\" | option: \"Closing party\" | options in the slot: [\"Closing party\"]\n must: [slot \"2027-06-09 17:30-20:00\" exactly; commitment \"An alcohol-free option\" exactly; status \"clears\" exactly; option \"Closing party\" exactly]\n- floor 15: slot: \"2027-06-09 17:30-20:00\" | commitment: \"A quiet space\" | status: \"not cleared\" | option: \"none\" | options in the slot: [\"Closing party\"]\n must: [slot \"2027-06-09 17:30-20:00\" exactly; commitment \"A quiet space\" exactly; status \"not cleared\" exactly; option \"none\" exactly]\n- floor 16: slot: \"2027-06-09 17:30-20:00\" | commitment: \"Live captions for any spoken remarks\" | status: \"not checkable\" | option: \"none\" | options in the slot: [\"Closing party\"]\n must: [slot \"2027-06-09 17:30-20:00\" exactly; commitment \"Live captions for any spoken remarks\" exactly; status \"not checkable\" exactly; option \"none\" exactly]\nUncovered slots: 2 | [\"2027-06-08 18:00-20:00\"; \"2027-06-09 17:30-20:00\"]\nClosing occasion: \"Closing party\" | status: \"not protected\" | starts: 2027-06-09 17:30 | the main programme ends that day: 17:45 | owner: \"not named\" | protection: \"The main programme ends by 17:30 on 2027-06-09, leaving Closing party its reserved time - never cut first, never sharing its hours, with a named owner, event-run-of-show holds the cue on the day.\"\n closing must: [occasion \"Closing party\" exactly; protection uses the words \"programme\" and \"ends\"]\nOwners (Q5) - one per occasion, never a function: 3 | rows below: 3 of 3 | cut: 0 (at most 20 sent, self-set) - in this order:\n- owner 1: occasion: \"Welcome reception\" | owner: \"Dana Quillmere\" | the first occasion\n must: [occasion \"Welcome reception\" exactly; owner \"Dana Quillmere\" exactly]\n- owner 2: occasion: \"Board-game night\" | owner: \"Priya Thandrevel\" | beyond the first\n must: [occasion \"Board-game night\" exactly; owner \"Priya Thandrevel\" exactly]\n- owner 3: occasion: \"Closing party\" | owner: \"not named\" | beyond the first\n must: [occasion \"Closing party\" exactly; owner \"not named\" exactly]\nDates (Q8) - the venue hold and the access statement, per occasion: 3 | rows below: 3 of 3 | cut: 0 (at most 20 sent, self-set) - in this order:\n- date 1: occasion: \"Welcome reception\" | venue held by: \"2027-04-15\" | access statement by: \"2027-05-01\" | status: \"set\"\n must: [occasion \"Welcome reception\" exactly; hold_by \"2027-04-15\" exactly; publish_by \"2027-05-01\" exactly; status \"set\" exactly]\n- date 2: occasion: \"Board-game night\" | venue held by: \"2027-04-20\" | access statement by: \"2027-05-01\" | status: \"set\"\n must: [occasion \"Board-game night\" exactly; hold_by \"2027-04-20\" exactly; publish_by \"2027-05-01\" exactly; status \"set\" exactly]\n- date 3: occasion: \"Closing party\" | venue held by: \"2027-04-15\" | access statement by: \"2027-05-20\" | status: \"late\"\n must: [occasion \"Closing party\" exactly; hold_by \"2027-04-15\" exactly; publish_by \"2027-05-20\" exactly; status \"late\" exactly]\nHand-offs - 4 - in this order:\n- handoff 1: \"The non-overlap requirement: Welcome reception and Board-game night must not share hours, and no occasion shares hours with the main programme, and the closing protection - the main programme ends by 17:30 on 2027-06-09 so Closing party keeps its reserved time\" | to: [event-schedule-design; event-run-of-show]\n must: [contains \"event-schedule-design\" and \"event-run-of-show\"]\n- handoff 2: \"The confirmed slate - 3 occasions and the hours they hold - as the calendar third-party satellites are deconflicted against\" | to: [event-side-event-coordination]\n must: [contains \"event-side-event-coordination\"]\n- handoff 3: \"The per-slot floor check - 4 slots, 2 with a commitment no option clears (2027-06-08 18:00-20:00 and 2027-06-09 17:30-20:00)\" | to: [event-accessibility-inclusion]\n must: [contains \"event-accessibility-inclusion\"]\n- handoff 4: \"The count and the places - 3 occasions at The Brassmoor Tap, upstairs bar, Harbormoor Hall foyer lounge and Harbormoor Hall rooftop - so the scope clause reaches every occasion, not only the one everybody remembers\" | to: [event-code-of-conduct]\n must: [contains \"event-code-of-conduct\"]\nSlate must: [summary at most 40 words; verdict \"fix first\" exactly; overlaps copies the 2 rows above, in this order; floor copies the 16 rows above, in this order; owners copies the 3 rows above, in this order; dates copies the 3 rows above, in this order; handoffs copies the 4 rows above, in this order; caveats carry the fix findings first, at most 5]\nFindings (16):\n[fix] depth_count (depth): The depth is \"Two occasions on separate slots\", but 3 occasions are on the table - narrow the slate to it (never the closing occasion first), or settle Q2 and Q5 so the promotion condition holds. Present the slate for approval before holding any second venue.\n[fix] overlap_1 (overlap): \"Welcome reception\" and \"Board-game night\" share 20:00-21:00 - concurrency is the track's property, and the depth is \"Two occasions on separate slots\". Hand event-schedule-design the requirement that they must not share hours, or narrow the slate.\n[fix] grid_overlap_2 (overlap): \"Closing party\" shares 17:30-17:45 on 2027-06-09 with the main programme - hand samber/dev-event-organizer-skills@event-schedule-design the requirement that it must not share hours with the main programme; never move a slot yourself.\n[fix] floor_1 (floor): The slot 2027-06-08 18:00-20:00 (\"Welcome reception\") has no option that clears \"Step-free access to every venue\", \"An alcohol-free option\" and \"A quiet space\" - a slot the event excluded somebody from. Add a clearing option to it, move one activity out of it, or narrow the slate; never note the gap and publish anyway.\n[fix] floor_4 (floor): The slot 2027-06-09 17:30-20:00 (\"Closing party\") has no option that clears \"A quiet space\" - a slot the event excluded somebody from. Add a clearing option to it, move one activity out of it, or narrow the slate; never note the gap and publish anyway.\n[fix] closing_owner (closing): Q5: the closing occasion \"Closing party\" has no named owner - a closing occasion nobody owns is the leftover this protection exists to prevent.\n[fix] closing_programme (closing): The main programme runs to 17:45 on 2027-06-09, past \"Closing party\"'s start at 17:30 - hand event-schedule-design the protection requirement: the main programme ends by 17:30.\n[fix] date_late_3 (dates): Q8: \"Closing party\" - the access statement is due after the slate goes public on 2027-05-10. Publishing the slate before the access statement reaches only whoever is still deciding.\n[check] commit_other_4 (floor): Q3: \"Live captions for any spoken remarks\" - the page has no column to check it against; say in each access statement which option clears it. The page never guesses.\n[check] floor_unknown_4 (floor): The slot 2027-06-09 17:30-20:00 (\"Closing party\"): \"Step-free access to every venue\" is not known for any option in it - confirm before the access statement goes out.\n[info] hospitality_done (gate): Q1: event-hospitality decided the format of each occasion and the alcohol posture, set once for the event - take its answer as the shape of every occasion; never re-rank its menu.\n[info] notes (context): The pasted notes are context: a request in them counts for Q2 only when somebody actually asked, and never as a commitment.\n[info] notes_hospitality (context): The notes name a format or a drink - event-hospitality's ground; the page never re-decides a format or the alcohol posture.\n[info] q6_last (context): Q6: last edition left \"the closing drink shrank to ten minutes when the talks ran long\" - next edition ranks against that record instead of this default.\n[info] approval (depth): Present the slate for approval before holding any second venue - a slate published and then shortened reads as a cancellation.\n[info] scope (scope): This skill decides one thing - how many official occasions and how they sit against each other. Each occasion's format, food, drink and alcohol posture are event-hospitality's; the access set is event-accessibility-inclusion's; the grid is event-schedule-design's; satellites are event-side-event-coordination's; the scope clause is event-code-of-conduct's."
}
Reply:
{
"task": "slate",
"summary": "Fix first: the welcome reception's first two hours have no step-free, alcohol-free or quiet option, the board-game night overlaps it, and the closing party has no owner - settle these before the slate goes public.",
"verdict": "fix first",
"depth": {
"pick": "Two occasions on separate slots",
"reason": "Q2 and Q5: somebody actually asked for a second occasion and Q5 names its owner - both halves of the promotion condition hold."
},
"overlaps": [
{
"a": "Welcome reception",
"b": "Board-game night",
"kind": "occasions"
},
{
"a": "Closing party",
"b": "main programme",
"kind": "programme"
}
],
"floor": [
{
"slot": "2027-06-08 18:00-20:00",
"commitment": "Step-free access to every venue",
"status": "not cleared",
"option": "none"
},
{
"slot": "2027-06-08 18:00-20:00",
"commitment": "An alcohol-free option",
"status": "not cleared",
"option": "none"
},
{
"slot": "2027-06-08 18:00-20:00",
"commitment": "A quiet space",
"status": "not cleared",
"option": "none"
},
{
"slot": "2027-06-08 18:00-20:00",
"commitment": "Live captions for any spoken remarks",
"status": "not checkable",
"option": "none"
},
{
"slot": "2027-06-08 20:00-21:00",
"commitment": "Step-free access to every venue",
"status": "clears",
"option": "Board-game night"
},
{
"slot": "2027-06-08 20:00-21:00",
"commitment": "An alcohol-free option",
"status": "clears",
"option": "Board-game night"
},
{
"slot": "2027-06-08 20:00-21:00",
"commitment": "A quiet space",
"status": "clears",
"option": "Board-game night"
},
{
"slot": "2027-06-08 20:00-21:00",
"commitment": "Live captions for any spoken remarks",
"status": "not checkable",
"option": "none"
},
{
"slot": "2027-06-08 21:00-22:30",
"commitment": "Step-free access to every venue",
"status": "clears",
"option": "Board-game night"
},
{
"slot": "2027-06-08 21:00-22:30",
"commitment": "An alcohol-free option",
"status": "clears",
"option": "Board-game night"
},
{
"slot": "2027-06-08 21:00-22:30",
"commitment": "A quiet space",
"status": "clears",
"option": "Board-game night"
},
{
"slot": "2027-06-08 21:00-22:30",
"commitment": "Live captions for any spoken remarks",
"status": "not checkable",
"option": "none"
},
{
"slot": "2027-06-09 17:30-20:00",
"commitment": "Step-free access to every venue",
"status": "unknown",
"option": "none"
},
{
"slot": "2027-06-09 17:30-20:00",
"commitment": "An alcohol-free option",
"status": "clears",
"option": "Closing party"
},
{
"slot": "2027-06-09 17:30-20:00",
"commitment": "A quiet space",
"status": "not cleared",
"option": "none"
},
{
"slot": "2027-06-09 17:30-20:00",
"commitment": "Live captions for any spoken remarks",
"status": "not checkable",
"option": "none"
}
],
"closing": {
"occasion": "Closing party",
"protection": "The main programme ends by 17:30 on 2027-06-09, leaving Closing party its reserved time - never cut first, never sharing its hours, with a named owner; event-run-of-show holds the cue on the day."
},
"owners": [
{
"occasion": "Welcome reception",
"owner": "Dana Quillmere"
},
{
"occasion": "Board-game night",
"owner": "Priya Thandrevel"
},
{
"occasion": "Closing party",
"owner": "not named"
}
],
"dates": [
{
"occasion": "Welcome reception",
"hold_by": "2027-04-15",
"publish_by": "2027-05-01",
"status": "set"
},
{
"occasion": "Board-game night",
"hold_by": "2027-04-20",
"publish_by": "2027-05-01",
"status": "set"
},
{
"occasion": "Closing party",
"hold_by": "2027-04-15",
"publish_by": "2027-05-20",
"status": "late"
}
],
"handoffs": [
"The non-overlap requirement: Welcome reception and Board-game night must not share hours, and no occasion shares hours with the main programme; and the closing protection - the main programme ends by 17:30 on 2027-06-09 so Closing party keeps its reserved time - event-schedule-design and event-run-of-show",
"The confirmed slate - 3 occasions and the hours they hold - as the calendar third-party satellites are deconflicted against - event-side-event-coordination",
"The per-slot floor check - 4 slots, 2 with a commitment no option clears (2027-06-08 18:00-20:00 and 2027-06-09 17:30-20:00) - event-accessibility-inclusion",
"The count and the places - 3 occasions at The Brassmoor Tap, upstairs bar, Harbormoor Hall foyer lounge and Harbormoor Hall rooftop - so the scope clause reaches every occasion, not only the one everybody remembers - event-code-of-conduct"
],
"caveats": [
"The depth is \"Two occasions on separate slots\", but 3 occasions are on the table - narrow the slate to it (never the closing occasion first), or settle Q2 and Q5 so the promotion condition holds.",
"\"Welcome reception\" and \"Board-game night\" share 20:00-21:00 - concurrency is the track's property, and the depth is \"Two occasions on separate slots\".",
"\"Closing party\" shares 17:30-17:45 on 2027-06-09 with the main programme - hand samber/dev-event-organizer-skills@event-schedule-design the requirement that it must not share hours with the main programme; never move a slot yourself.",
"The slot 2027-06-08 18:00-20:00 (\"Welcome reception\") has no option that clears \"Step-free access to every venue\", \"An alcohol-free option\" and \"A quiet space\" - a slot the event excluded somebody from.",
"The slot 2027-06-09 17:30-20:00 (\"Closing party\") has no option that clears \"A quiet space\" - a slot the event excluded somebody from."
]
}
The page's check: kept - nothing to flag
The same, resolved - an alcohol-free, step-free option in every slot, owners named, the overlap removed - announce
Verdict: ready to publish; ready to publish: yes; 3 occasions on the slate; depth: Themed multi-activity track; 0 overlaps; 3 slots, 0 uncovered; closing: protected; 0 fix-level findings.
Input:
{
"task": "announce",
"event": "Event: \"Harborlight Dev Summit 2027\" | dates: 2027-06-08 to 2027-06-09 | city: \"Port Avery\" | attendance: 700 | as of: 2027-04-01\nPublished grid hours (event-schedule-design's): [2027-06-08 09:00-17:30; 2027-06-09 09:00-17:30]\nQ1 event-hospitality has decided: \"both decided - the format of every occasion and the event's alcohol posture\"\nQ2 occasions on the table: 3 | a second occasion: \"somebody actually asked for a second occasion\"\nQ3 access set published by and at what depth: \"Ruth Okomberly, accessibility lead - published in the attendee guide\" | one format can carry every published commitment at once: \"no\"\nQ4 two occasions in the same hours: no | answered from the occasions table\nQ5 owners: answered per occasion in the occasions table | occasions beyond the first with a named owner: 2\nQ6 effort ceiling: volunteer hours beyond the first occasion: 12 | a budget line beyond the first: \"yes\" | a second room at no extra ask: \"no\" | last edition left: \"the closing drink shrank to ten minutes when the talks ran long\"\nQ7: \"a recurring event\"\nQ8 the social programme page goes public: 2027-05-10 | each occasion's venue-hold and access-statement dates: in the occasions field",
"occasions": "Occasions - 3, one per line, as entered (format, alcohol, venue and hours as event-hospitality decided them):\n- occasion 1: name: \"Welcome reception\" | kind: \"venue-based mixer\" | when: 2027-06-08 18:00-20:00 | venue: \"Harbormoor Hall atrium\" | capacity: 250 | owner: \"Dana Quillmere\" | alcohol: \"available, never central\" | step-free: \"yes\" | quiet option: \"yes\" | cost: \"no cost\" | closing: no\n- occasion 2: name: \"Board-game night\" | kind: \"games table\" | when: 2027-06-08 20:30-22:30 | venue: \"Harbormoor Hall foyer lounge\" | capacity: 60 | owner: \"Priya Thandrevel\" | alcohol: \"none\" | step-free: \"yes\" | quiet option: \"yes\" | cost: \"no cost\" | closing: no\n- occasion 3: name: \"Closing party\" | kind: \"venue-based mixer\" | when: 2027-06-09 18:00-20:30 | venue: \"Harbormoor Hall rooftop\" | capacity: 400 | owner: \"Marco Fennaruzzi\" | alcohol: \"available, never central\" | step-free: \"yes\" | quiet option: \"yes\" | cost: \"no cost\" | closing: yes\nQ8 dates - 3:\n- dates: occasion: \"Welcome reception\" | venue held by: 2027-04-15 | access statement published by: 2027-05-01\n- dates: occasion: \"Board-game night\" | venue held by: 2027-04-20 | access statement published by: 2027-05-01\n- dates: occasion: \"Closing party\" | venue held by: 2027-04-15 | access statement published by: 2027-05-05",
"commitments": "Published access commitments (Q3) - 4, as published - never invented, never added to:\n- commitment 1: \"Step-free access to every venue\" | the page checks it as: \"step-free\" | cleared by: an occasion whose step-free column is yes\n- commitment 2: \"An alcohol-free option\" | the page checks it as: \"alcohol-free\" | cleared by: an occasion whose alcohol, as hospitality decided it, is none or available but never central\n- commitment 3: \"A quiet space\" | the page checks it as: \"quiet\" | cleared by: an occasion whose quiet-option column is yes\n- commitment 4: \"Live captions for any spoken remarks\" | the page checks it as: \"not checkable - no column\" | cleared by: nothing the page can read",
"notes": "",
"facts": "Verdict: \"ready to publish\" | ready to publish: yes | fix-level findings: 0\nSelf-set lines: [the closing occasion wants at least 15 min after the main programme ends; a date under 14 days away is soon; at most 20 occasions, 20 overlaps, 40 floor rows and 60 findings sent; a summary cap of 40 words; at most 5 caveats; each rung holds one occasion, two on separate slots, or a track of any count; an alcohol-free option is an occasion whose alcohol is none or available but never central; a slot is a stretch of time in which the same occasions run]\nNever stated: [an occasion, a venue, an access feature, a commitment, a price, a time or an owner the input does not hold; an alcohol posture or a format the page did not give; a moved slot on the grid; a percentage]\nGate (Q1): \"both decided - the format of every occasion and the event's alcohol posture\" | stop: no\nSocial programme page: event: \"Harborlight Dev Summit 2027\" | occasions on the slate: 3 | depth: \"Themed multi-activity track\"\n title must: [contains \"Harborlight Dev Summit 2027\"]\nOccasions - one access statement each, from the page's facts only: 3 | rows below: 3 of 3 | cut: 0 (at most 20 sent, self-set) - in this order:\n- occasion 1: name: \"Welcome reception\" | when: \"2027-06-08 18:00-20:00\" | where: \"Harbormoor Hall atrium\" | cost: \"no cost\" | owner: \"Dana Quillmere\" | access facts: [step-free \"yes\"; alcohol-free \"yes\"; quiet \"yes\"]\n must: [name \"Welcome reception\" exactly; when contains \"18:00\" and \"20:00\"; where contains \"Harbormoor Hall atrium\"; cost uses the words \"no cost\"; access \"step-free\"; access \"alcohol-free\"; access \"quiet\"; owner_contact \"Dana Quillmere\" exactly]\n- occasion 2: name: \"Board-game night\" | when: \"2027-06-08 20:30-22:30\" | where: \"Harbormoor Hall foyer lounge\" | cost: \"no cost\" | owner: \"Priya Thandrevel\" | access facts: [step-free \"yes\"; alcohol-free \"yes\"; quiet \"yes\"]\n must: [name \"Board-game night\" exactly; when contains \"20:30\" and \"22:30\"; where contains \"Harbormoor Hall foyer lounge\"; cost uses the words \"no cost\"; access \"step-free\"; access \"alcohol-free\"; access \"quiet\"; owner_contact \"Priya Thandrevel\" exactly]\n- occasion 3: name: \"Closing party\" | when: \"2027-06-09 18:00-20:30\" | where: \"Harbormoor Hall rooftop\" | cost: \"no cost\" | owner: \"Marco Fennaruzzi\" | access facts: [step-free \"yes\"; alcohol-free \"yes\"; quiet \"yes\"]\n must: [name \"Closing party\" exactly; when contains \"18:00\" and \"20:30\"; where contains \"Harbormoor Hall rooftop\"; cost uses the words \"no cost\"; access \"step-free\"; access \"alcohol-free\"; access \"quiet\"; owner_contact \"Marco Fennaruzzi\" exactly]\nSlots - alcohol-free and quiet options per slot, where the page has them: 3\n- slot 1: \"2027-06-08 18:00-20:00\" | alcohol-free options: [\"Welcome reception\"] | quiet options: [\"Welcome reception\"]\n- slot 2: \"2027-06-08 20:30-22:30\" | alcohol-free options: [\"Board-game night\"] | quiet options: [\"Board-game night\"]\n- slot 3: \"2027-06-09 18:00-20:30\" | alcohol-free options: [\"Closing party\"] | quiet options: [\"Closing party\"]\n what_to_expect must: [names \"Welcome reception\", \"Board-game night\" and \"Closing party\" with \"alcohol-free\"; names \"Welcome reception\", \"Board-game night\" and \"Closing party\" with \"quiet\"]\nCode of conduct: the scope clause reaches every occasion on the slate - \"event-code-of-conduct\" owns it\n code_of_conduct_note must: [uses the words \"code of conduct\" and \"every\"]\nAnnounce must: [title contains \"Harborlight Dev Summit 2027\"; occasions copies the 3 rows above, in this order; intro one or two sentences] | announce note must: [use the word \"publish\" and say what happens next]\nCarried from the slate: summary: \"Ready to publish: every slot has a step-free, alcohol-free and quiet option, every occasion has a named owner, nothing shares hours, and the closing party keeps its reserved time.\" | parts matching the page: 3 of 3\nFindings (6):\n[check] commit_other_4 (floor): Q3: \"Live captions for any spoken remarks\" - the page has no column to check it against; say in each access statement which option clears it. The page never guesses.\n[info] hospitality_done (gate): Q1: event-hospitality decided the format of each occasion and the alcohol posture, set once for the event - take its answer as the shape of every occasion; never re-rank its menu.\n[info] floor_sequence (floor): No two occasions share an hour, so each slot holds one option - the floor can still fail, the ordinary per-occasion way the hospitality reference already covers.\n[info] q6_last (context): Q6: last edition left \"the closing drink shrank to ten minutes when the talks ran long\" - next edition ranks against that record instead of this default.\n[info] approval (depth): Present the slate for approval before holding any second venue - a slate published and then shortened reads as a cancellation.\n[info] scope (scope): This skill decides one thing - how many official occasions and how they sit against each other. Each occasion's format, food, drink and alcohol posture are event-hospitality's; the access set is event-accessibility-inclusion's; the grid is event-schedule-design's; satellites are event-side-event-coordination's; the scope clause is event-code-of-conduct's."
}
Reply:
{
"task": "announce",
"title": "Harborlight Dev Summit 2027 - the official social programme",
"intro": "Three official occasions are open to every badge, one after another, and each one has its access statement below.",
"occasions": [
{
"name": "Welcome reception",
"when": "2027-06-08, 18:00 to 20:00",
"where": "Harbormoor Hall atrium",
"cost": "no cost",
"access": "Step-free access. An alcohol-free option. A quiet option.",
"owner_contact": "Dana Quillmere"
},
{
"name": "Board-game night",
"when": "2027-06-08, 20:30 to 22:30",
"where": "Harbormoor Hall foyer lounge",
"cost": "no cost",
"access": "Step-free access. An alcohol-free option. A quiet option.",
"owner_contact": "Priya Thandrevel"
},
{
"name": "Closing party",
"when": "2027-06-09, 18:00 to 20:30",
"where": "Harbormoor Hall rooftop",
"cost": "no cost",
"access": "Step-free access. An alcohol-free option. A quiet option.",
"owner_contact": "Marco Fennaruzzi"
}
],
"what_to_expect": "Slot by slot, the alcohol-free options are Welcome reception, Board-game night and Closing party; the quiet options are Welcome reception, Board-game night and Closing party.",
"code_of_conduct_note": "The event's code of conduct reaches every occasion on this page, wherever it is held.",
"note": "Publish this page with the slate on 2027-05-10, after every access statement is out."
}
The page's check: kept - nothing to flag
The free social programme page skeleton for the resolved sample
# Harborlight Dev Summit 2027 - the official social programme (the page's skeleton)
_Verdict: ready to publish._
## The occasions
### Welcome reception
- When: 2027-06-08, 18:00-20:00
- Where: Harbormoor Hall atrium
- Cost: no cost
- Access: Step-free access to every venue: yes; An alcohol-free option: yes; A quiet space: yes
- Contact: Dana Quillmere
### Board-game night
- When: 2027-06-08, 20:30-22:30
- Where: Harbormoor Hall foyer lounge
- Cost: no cost
- Access: Step-free access to every venue: yes; An alcohol-free option: yes; A quiet space: yes
- Contact: Priya Thandrevel
### Closing party (closing)
- When: 2027-06-09, 18:00-20:30
- Where: Harbormoor Hall rooftop
- Cost: no cost
- Access: Step-free access to every venue: yes; An alcohol-free option: yes; A quiet space: yes
- Contact: Marco Fennaruzzi
## What to expect, slot by slot
- 2027-06-08 18:00-20:00: alcohol-free - Welcome reception; quiet - Welcome reception
- 2027-06-08 20:30-22:30: alcohol-free - Board-game night; quiet - Board-game night
- 2027-06-09 18:00-20:30: alcohol-free - Closing party; quiet - Closing party
## Code of conduct
The event's code of conduct covers every occasion on this page.
Made with Social Slate - https://social-slate.skillsafe.ai/
Build the input yourself
Load the engine in node (global.window = global; require("./socialkit.js"); require("./socialreply.js")), then:
const K = SocialKit, R = SocialReply;
const f = K.form({ name: "My Meetup 12", start: "2027-09-14", q1: "decided", q2: "one", grid: "2027-09-14 | 18:30 | 20:00",
occasions: "After-talks social | venue-based mixer | 2027-09-14 | 20:15 | 22:00 | Hall | 60 | Ana Silva | none | yes | yes | no cost | yes",
commitments: "Step-free entrance\nAn alcohol-free option", dates: "After-talks social | 2027-08-20 | 2027-09-01", slatePublish: "2027-09-02" });
// f.menu, f.depthLabel, f.overlaps, f.slots, f.floor, f.uncovered, f.closing, f.dates, f.handoffs, f.verdict, f.findings
K.slateCsv(f); K.floorCsv(f); K.slateIcs(f); K.pageMd(f); K.board(f) // the free exports
const b = K.buildInput(f, "slate"); // or "announce"
const wire = K.toWire(b.input); // POST this to /estimate and /run
// then: const reply = R.normalize(R.parseReply(job.output.output), "slate");
// R.check(reply, b) // {status: kept or flagged, notes}
// R.carryText(reply) // the lines the announce lane carries - set them as the carry field
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="YOUR_TOKEN" # from https://social-slate.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 = "YOUR_TOKEN" # from https://social-slate.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"]
// social-slate.mjs -- Node 18+ (global fetch)
const BASE = "https://api.skillsafe.ai/v1/app-api";
let TOKEN = "YOUR_TOKEN"; // from https://social-slate.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 = "YOUR_TOKEN" // from https://social-slate.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
}
// SocialSlate.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 SocialSlate {
static final String BASE = "https://api.skillsafe.ai/v1/app-api";
static String token = "YOUR_TOKEN"; // from https://social-slate.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 = "YOUR_TOKEN" # from https://social-slate.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 = "YOUR_TOKEN"; // from https://social-slate.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 = "YOUR_TOKEN"; // from https://social-slate.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":"social-slate"}' | 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": "social-slate"})["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: "social-slate" })).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": "social-slate"}, 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", "social-slate"), 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: "social-slate" })["token"])
// a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
$TOKEN = "";
$TOKEN = api("POST", "/guest", ["slug" => "social-slate"])["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 = "social-slate" }))!["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": "slate", "event": "Event: ... | dates: ... | attendance: ...", "occasions": "Occasions - 3 ...", "commitments": "Published access commitments (Q3) - 4 ...", "notes": "", "facts": "Verdict: ..."}
# - 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": "slate", "event": "Event: ... | dates: ... | attendance: ...", "occasions": "Occasions - 3 ...", "commitments": "Published access commitments (Q3) - 4 ...", "notes": "", "facts": "Verdict: ..."}
# - 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: "slate" or "announce", event, occasions, commitments, notes, 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 social-slate:<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 slate carries summary, verdict, depth, overlaps, floor, closing, owners, dates, handoffs and caveats; a social programme page carries title, intro, occasions, what_to_expect, code_of_conduct_note and note. The code below prints the headline of either.
KEY="social-slate:slate:$(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 '.verdict // .note, .summary // .title' reply.json
jq -r '.floor[]? | "\(.slot) \(.status)"' reply.json # a slate; a page has .occasions
import hashlib, time
key = "social-slate:" + 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("verdict") or reply.get("note"))
for row in reply.get("floor") or reply.get("occasions") or []:
print("-", row.get("slot") or row.get("name"), row.get("status") or row.get("access"))
import { createHash } from "node:crypto";
const key = `social-slate:${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.verdict ?? reply.note);
for (const row of reply.floor || reply.occasions || []) console.log("-", row.slot ?? row.name, row.status ?? row.access);
sum := sha256.Sum256(raw) // add "crypto/sha256", "encoding/hex", "time" to the imports
key := "social-slate:" + 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)
fmt.Println(j["charged_credits"], j["truncated"], reply["verdict"], reply["note"])
rows, _ := reply["floor"].([]any) // a slate's floor; a page has occasions
for _, row := range rows {
fmt.Println("-", row.(map[string]any)["slot"], row.(map[string]any)["status"])
}
var digest = java.security.MessageDigest.getInstance("SHA-256").digest(M.writeValueAsBytes(input));
String key = "social-slate:" + 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.path("verdict").asText(reply.path("note").asText()));
for (JsonNode row : reply.path("floor")) System.out.println("- " + row.path("slot").asText() + " " + row.path("status").asText());
require "digest"
key = "social-slate:#{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["verdict"] || reply["note"]].inspect
(reply["floor"] || []).each { |row| puts "- #{row["slot"]} #{row["status"]}" }
$key = "social-slate:" . $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["verdict"] ?? $reply["note"] ?? ""), "\n";
foreach ($reply["floor"] ?? [] as $row) echo "- ", $row["slot"], " ", $row["status"], "\n";
var digest = System.Security.Cryptography.SHA256.HashData(System.Text.Encoding.UTF8.GetBytes(input.ToJsonString()));
var key = $"social-slate:{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!["verdict"] ?? reply["note"]}");
foreach (var row in reply["floor"] as JsonArray ?? new JsonArray()) Console.WriteLine("- " + row!["slot"] + " " + row["status"]);
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: social-slate:slate:$(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": "slate", "event": "Event: ... | dates: ... | attendance: ...", "occasions": "Occasions - 3 ...", "commitments": "Published access commitments (Q3) - 4 ...", "notes": "", "facts": "Verdict: ..."}
# - 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": "social-slate:slate:" + 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": `social-slate:slate:${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", "social-slate:slate:"+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", "social-slate:slate:" + 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" => "social-slate:slate:#{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: social-slate:slate:" . 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", $"social-slate:slate:{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);