Run Hybrid Grid from your own code
Both metered lanes are available over HTTP and each returns one JSON object. The division of labour: the page confirms the format decision is closed (or routes to event-format-selection and stops), writes the promise in one sentence, marks remote eligibility on the published grid as a column (rooms that cannot carry a feed, consent, the join/leave deadline, the remote clock), re-ranks the three menus (eligibility, parity depth, crew split) with every deleted rung struck, names the remote person per streamed session and flags every double-booking, lays out the stream's cue track as offsets and clock times, and finds the two-audience gap in the disruption order - all free, in hybridkit.js. The run writes the design for validation, or the cue column and the remote advocate brief; the page then holds the reply to its own board (hybridreply.js). It never names a streaming platform as a recommendation or a price, and never specifies equipment.
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":"hybrid-grid"}), 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, grid, crew, 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:
design- the hybrid design for the organizer to validate, section by section: the verdict, the promise in one sentence, every session's remote eligibility with its reason, the parity depth and what moved it, the remote person per streamed session (or closed), the two-audience decision and who made it, the hand-offs and the caveats.cues- the stream's cue column for the run of show (every cue row copied: session, cue, offset, clock, who), the remote advocate brief (who, what to watch, the relay rule, the holding card, the escalation), what remote attendees are told, and a note. The lane exists only when the format is closed and a session streams.
A missing or unknown task is answered as design. Both lanes send the same six fields; for the cue track, the facts carry the cue rows and the design's eligibility, parity and crew (the page fills these from the design). The notes field may be an empty string.
The input
| Field | Required | What |
|---|---|---|
task | yes | design or cues. If it is missing or neither, do design and set "task": "design". |
event | yes | the event basics (name, dates, city, the venue's time zone as a UTC offset, the as-of date), the format decision, and the interview answers: Q1 the promise in its own words, to whom, how it was written, what it covers, what it was sold on and the sessions or rooms chosen for remote; Q3 where the remote audience sits and its time zones; Q4 who holds the consent record; Q5 any platform or data constraint; Q6 the disruption priority order; Q7 the join/leave deadline and whether the grid is published; Q8 one-off or recurring; Q9 the effort ceiling; Q10 what already exists; and the two written two-audience decisions. |
grid | yes | one line per session as entered (id, title, room, when, format, recording consent, in-room audience on camera), then one line per room (can carry a feed, already wired). |
crew | yes | one line per person (works the remote side alone, other duties during sessions, the sessions they are down for), then the watcher off the floor, the host on the feed and the senior organizer the feed call escalates to. |
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 promise, the rooms, the three menus re-ranked, the eligibility rows, the parity pick and its mechanics, the crew rows, the double-bookings, the two-audience gap, the hand-offs, for the cues task the streamed sessions, the cue rows and the design 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 is deliberately narrow. The format decision arrives made: if hybrid is still being weighed, route to samber/dev-event-organizer-skills@event-format-selection and stop. Production coverage tiers, the signal path and every piece of equipment belong to samber/dev-event-organizer-skills@event-production; consent records to samber/dev-event-organizer-skills@event-speaker-experience; the run of show to samber/dev-event-organizer-skills@event-run-of-show; the budget to samber/dev-event-organizer-skills@event-budget. Hand off - never decide for them.
- Write down what the remote audience was promised, in one sentence. Every later choice is measured against that sentence, not against what is technically possible.
- Mark remote eligibility on the published grid as a column, never as a separate list, and never move a session. A room that cannot carry a feed deletes rungs rather than reordering them. Publishing the recordings afterwards is not a remote programme.
- Parity by goodwill loses to a room of raised hands: either a named person owns the remote questions or a rule reserves them. An open question channel nobody reads is worse than none; drop to feed only for a session where nobody can be named.
- Staff the remote side by name per streamed session, never by function. The room lead never doubles as the remote advocate. Nobody standing in one room can see another room's output.
- The stream's cue track is one more column on the run of show's rows: what the stream shows, who owns it, and what the remote audience is told. Every gap gets a holding card.
- The disruption priority order's audience is singular and a hybrid has two. Close the gap in writing before the day - who gets a shared technician, and whether a moved session takes the remote programme with it - and say who decided.
- Never name a streaming platform as a recommendation or a price, never specify equipment, and never invent a parity threshold, a staffing ratio, a share of Q&A owed to remote or a tolerable stream delay.
House rules
- Use only the input and the rules above. Copy the page's short facts exactly: the verdict, session ids, statuses, the parity pick, every name, every cue, offset and clock time. Every number in your text must appear in the input.
- Every "must: [...]" line in the facts applies to the item directly above it - every session, every crew row, every cue 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 a session, a room, a time, a person, a consent or a platform. Never state a percentage.
- Never name a streaming platform the input does not name, never as a recommendation, and never state a price. Never name equipment (an encoder, an uplink, a microphone, a mixer) - state the requirement; event-production states the kit.
- 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
design
summary- one sentence, at most 40 words: the verdict, the most important open item, and the next step.verdict- the page's verdict, exactly:close the format decision first,fix firstorready to hand off.promise- one sentence: when the page gives the promise's own words, it contains those words exactly; when nothing was written down, it uses the words "nothing written"; when Q1 was not answered, it uses the words "not answered".eligibility- one object per eligibility row the page sent, in its order, every row, not only the first:session- the page's session id exactly;status- the page's status exactly, "eligible" or "not eligible";reason- one short sentence using the row's word: "programme" (in or out of the remote programme), "feed" (the room cannot carry a feed), "consent" (consent is no, not recorded or unknown) or "deadline" (the join/leave deadline has passed).[]when the page sent none.parity- one object:pick- the page's parity depth, exactly (the word "none" when the verdict isclose the format decision first);reason- one sentence that says what moved the pick, using the page's word from the parity must line: the question number (for example "Q1", "Q3", "Q9" or "Q10") when an interview answer moved it, the word "default" when nothing did, or the word "format" when the format decision is open.crew- one object per crew row the page sent, in its order, every row:session- the page's session id exactly;who- the page's name exactly, or the word "closed" when nobody is named.[]when the page sent none.gap- one object:decision- when the page says the gap is closed in writing, one sentence using the words "technician" and the page's word for a moved session ("follows" or "loses"); otherwise the words "not decided";who- the page's name for who decided, exactly, or the words "not named".handoffs- one string per hand-off the page sent, in its order, every one: says what is handed off and contains each sibling the row names (for example "event-production", "event-speaker-experience", "event-run-of-show" or "event-budget").[]when the page sent none.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": "design",
"summary": "one sentence",
"verdict": "fix first",
"promise": "one sentence",
"eligibility": [ { "session": "the page's id", "status": "eligible", "reason": "one sentence" } ],
"parity": { "pick": "the page's pick", "reason": "one sentence" },
"crew": [ { "session": "the page's id", "who": "the page's name" } ],
"gap": { "decision": "one sentence", "who": "the page's name" },
"handoffs": [ "one sentence" ],
"caveats": [ "a caveat" ]
}
The page holds it to its own board: the verdict exactly; the promise's own words (or "nothing written" / "not answered"); every eligibility row's session id and status exactly, with the reason's word ("programme", "feed", "consent" or "deadline"); the parity pick exactly with the question that moved it, or "default"; every crew row's name, or "closed"; the two-audience decision with "technician" and "follows" or "loses" when it is decided, "not decided" otherwise, and who decided; every hand-off naming its sibling; caveats when the page has a fix; and no number, percentage, platform, price, equipment, quote or pronoun the input does not hold. Required words are read tolerantly: "the room cannot carry a feed" is feed, "a paid virtual ticket" is Q1.
cues
cue_track- one object per cue row the page sent, in its order, every cue, not only the first:session,cue,offset,clockandwho, each copied exactly as the page gives it (whois the page's name, or the words "not named").advocate_brief- one object:who- one line naming every advocate the page lists, exactly (or the words "not named" when the page lists none).what_to_watch- one or two sentences using the words "feed" and "channel": the outgoing feed as a remote attendee receives it, and the remote channel.relay_rule- one or two sentences following the parity depth, using the page's relay rule word: "relay" for a live relay by a named person, "first" for reserved floor time (remote takes the first question), "queue" for remote participants who appear in the room, "not taken live" for feed only, "in writing" for an asynchronous written return path. For "relay", "first" and "queue" also use the word "confirm": the asker is told in the channel that the question was asked.holding- one or two sentences using the words "holding card" and "next": what the stream shows in every gap and when the next session starts.escalation- one sentence using the word "feed" and naming the page's senior organizer exactly (or the words "not named").remote_notice- a short paragraph for remote attendees that names every streamed session the page lists by its id, and, when the page lists sessions that are not streamed, uses the word "not" to say which are not streamed.note- one sentence: when the page's verdict is notready to hand off, begin with "Not ready to hand off:" and say what to fix first; otherwise use the word "hand" and say what happens next.
{
"task": "cues",
"cue_track": [ { "session": "the page's id", "cue": "pre-roll", "offset": "-5 min", "clock": "08:55", "who": "the page's name" } ],
"advocate_brief": {
"who": "the page's names",
"what_to_watch": "one or two sentences",
"relay_rule": "one or two sentences",
"holding": "one or two sentences",
"escalation": "one sentence"
},
"remote_notice": "a short paragraph",
"note": "one sentence"
}
The page requires every cue row the page sent, in order, with its session, cue, offset, clock and owner exactly; every advocate named; the feed and the channel in what to watch; the parity's relay word (and "confirm" where a question is relayed); the holding card and what is next; the senior organizer and the feed in the escalation; every streamed session's id in the remote notice and the word "not" for what is not streamed; and a note beginning "Not ready to hand off:" 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 cues input carries its sample's saved design, as the handoff does.
A 2-day, 3-track conference with a paid virtual ticket - a room with no feed, two consents unknown, one person double-booked - design
Verdict: fix first; ready to hand off: no; 18 sessions, 8 eligible; remote programme: Curated remote programme; parity: Reserved floor time for remote questions; crew split: A watcher off the floor and an advocate in each room; 48 cue rows; 1 double-bookings; 4 fix-level findings.
Input:
{
"task": "design",
"event": "Event: \"Northfield DevConf 2027\" | dates: 2027-05-12 to 2027-05-13 | city: \"Northfield\" | time zone: \"UTC+1\" | as of: 2027-03-15\nFormat decision (step 1): \"closed - hybrid was chosen\"\nQ1 promised: \"all main-stage talks live with Q&A\" | to whom: \"virtual ticket holders\" | written as: \"a paid virtual ticket\" | covers: \"named sessions or rooms\" | sold on: \"live Q&A - remote questions answered\" | chosen for remote: [\"Main Hall\"; \"B2\"; \"B5\"]\nQ3 remote audience: \"a spread wide enough that part of the programme lands in their night\" | remote time zones: [\"UTC-5\"; \"UTC+5:30\"]\nQ4 consent record held by: \"Ruth Adeyemi, speaker team\" | per-speaker opt-out genuinely offered: \"no\"\nQ5 platform or data constraint: \"The sponsor contract keeps the stream on the conference's own site.\"\nQ6 disruption priority order: \"Protect the plenary or keynote first, protect the headline commitment the audience showed up for next, minimize how many other sessions the fix disturbs, then lock the revised grid for the rest of the block.\"\nQ7 last date a session can join or leave: 2027-04-20 | days left: 36 | the grid: \"not yet published\"\nQ8: \"a recurring event\"\nQ9 people who can work the remote side alone: 3\nQ10 what already exists: [\"an organizer who has streamed before\"; \"last edition's remote questions went unanswered\"; \"a community that already lives in a chat space\"; \"a room already wired: Main Hall\"]\nTwo-audience decisions: technician: \"not decided\" | a moved session: \"not decided\" | decided by: \"not named\"",
"grid": "Published grid - 18 sessions, one per line, as entered:\n- session 1: id: \"M1\" | title: \"Opening keynote: the next ten years of the edge\" | room: \"Main Hall\" | when: 2027-05-12 09:00-09:45 | format: \"keynote\" | consent: \"yes\" | in-room audience on camera: \"yes\"\n- session 2: id: \"M2\" | title: \"Observability without the bill\" | room: \"Main Hall\" | when: 2027-05-12 10:00-10:45 | format: \"talk\" | consent: \"yes\" | in-room audience on camera: \"no\"\n- session 3: id: \"B1\" | title: \"Hands-on: tracing a slow request\" | room: \"Room B\" | when: 2027-05-12 10:00-10:45 | format: \"workshop\" | consent: \"yes\" | in-room audience on camera: \"unknown\"\n- session 4: id: \"C1\" | title: \"Writing your first operator\" | room: \"Room C\" | when: 2027-05-12 10:00-11:30 | format: \"workshop\" | consent: \"yes\" | in-room audience on camera: \"yes\"\n- session 5: id: \"M3\" | title: \"Postgres at the edge\" | room: \"Main Hall\" | when: 2027-05-12 11:00-11:45 | format: \"talk\" | consent: \"unknown\" | in-room audience on camera: \"yes\"\n- session 6: id: \"B2\" | title: \"Panel: running a platform team\" | room: \"Room B\" | when: 2027-05-12 11:00-11:45 | format: \"panel\" | consent: \"yes\" | in-room audience on camera: \"yes\"\n- session 7: id: \"M4\" | title: \"Shipping on Fridays, safely\" | room: \"Main Hall\" | when: 2027-05-12 14:00-14:45 | format: \"talk\" | consent: \"yes\" | in-room audience on camera: \"unknown\"\n- session 8: id: \"B3\" | title: \"Chaos drills for small teams\" | room: \"Room B\" | when: 2027-05-12 14:00-15:30 | format: \"workshop\" | consent: \"no\" | in-room audience on camera: \"yes\"\n- session 9: id: \"C2\" | title: \"Lightning talks\" | room: \"Room C\" | when: 2027-05-12 14:00-14:45 | format: \"talk\" | consent: \"yes\" | in-room audience on camera: \"yes\"\n- session 10: id: \"M5\" | title: \"Day-two keynote: open source as infrastructure\" | room: \"Main Hall\" | when: 2027-05-13 09:30-10:15 | format: \"keynote\" | consent: \"yes\" | in-room audience on camera: \"yes\"\n- session 11: id: \"M6\" | title: \"Rust in the request path\" | room: \"Main Hall\" | when: 2027-05-13 10:30-11:15 | format: \"talk\" | consent: \"unknown\" | in-room audience on camera: \"yes\"\n- session 12: id: \"B4\" | title: \"Accessibility audits in CI\" | room: \"Room B\" | when: 2027-05-13 10:30-11:15 | format: \"talk\" | consent: \"yes\" | in-room audience on camera: \"yes\"\n- session 13: id: \"C3\" | title: \"Build a feature flag service\" | room: \"Room C\" | when: 2027-05-13 10:30-12:00 | format: \"workshop\" | consent: \"not recorded\" | in-room audience on camera: \"yes\"\n- session 14: id: \"M7\" | title: \"The cost of a millisecond\" | room: \"Main Hall\" | when: 2027-05-13 13:30-14:15 | format: \"talk\" | consent: \"yes\" | in-room audience on camera: \"yes\"\n- session 15: id: \"B5\" | title: \"Panel: hiring for on-call\" | room: \"Room B\" | when: 2027-05-13 13:30-14:15 | format: \"panel\" | consent: \"yes\" | in-room audience on camera: \"no\"\n- session 16: id: \"C4\" | title: \"Incident reviews that teach\" | room: \"Room C\" | when: 2027-05-13 13:30-14:15 | format: \"talk\" | consent: \"yes\" | in-room audience on camera: \"yes\"\n- session 17: id: \"M8\" | title: \"Closing keynote\" | room: \"Main Hall\" | when: 2027-05-13 16:00-16:45 | format: \"keynote\" | consent: \"yes\" | in-room audience on camera: \"yes\"\n- session 18: id: \"S1\" | title: \"Community social\" | room: \"Room C\" | when: 2027-05-13 17:00-18:30 | format: \"social\" | consent: \"not recorded\" | in-room audience on camera: \"no\"\nRooms - 3:\n- room 1: \"Main Hall\" | can carry a feed: yes | already wired: yes\n- room 2: \"Room B\" | can carry a feed: yes | already wired: no\n- room 3: \"Room C\" | can carry a feed: no | already wired: no",
"crew": "Crew - 4, one per line, as entered:\n- crew 1: name: \"Lena Park\" | works the remote side alone: yes | other duties during sessions: \"none\" | remote side for: [\"M1\"; \"M2\"; \"M4\"; \"M7\"; \"B5\"]\n- crew 2: name: \"Omar Diaz\" | works the remote side alone: yes | other duties during sessions: \"none\" | remote side for: [\"M5\"; \"M8\"; \"B2\"]\n- crew 3: name: \"Sana Okafor\" | works the remote side alone: yes | other duties during sessions: \"none\" | remote side for: [\"M3\"; \"M6\"]\n- crew 4: name: \"Tom Reyes\" | works the remote side alone: no | other duties during sessions: \"room lead in Room B\" | remote side for: none\nWatcher off the floor: \"Ines Brandt\" | host on the feed: \"not named\" | senior organizer the feed call escalates to: \"Maya Chen\"",
"notes": "Last edition the remote channel asked twice whether the afternoon was delayed and nobody answered. The virtual ticket page says: all main-stage talks live with Q&A.",
"facts": "Verdict: \"fix first\" | ready to hand off: no | fix-level findings: 4\nSelf-set lines: [the pre-roll cue at -5 min; the Q&A handover and the remote question relay at 10 min before the end; the remote night is 00:00 to 06:00; a join/leave deadline under 14 days is soon; at most 40 sessions and 60 cues sent; a summary cap of 40 words; at most 5 caveats; a room missing from the rooms table cannot carry a feed; audience questions off camera until settled; the promise sentence is the page's template]\nNever stated: [a session, a room, a time, a person, a consent or a platform the input does not hold; a streaming platform as a recommendation or a price; equipment - cameras, encoders, uplinks or return audio; a parity threshold, a staffing ratio, a share of Q&A owed to remote or a tolerable stream delay; a moved session on the grid]\nFormat (step 1): \"closed - hybrid was chosen\" | stop: no\nPromise (step 2): \"Promised to virtual ticket holders: 'all main-stage talks live with Q&A' - a paid virtual ticket, covering named sessions or rooms, sold on live Q&A.\" | promise word: \"all main-stage talks live with Q&A\"\n promise must: [promise contains \"all main-stage talks live with Q&A\"]\nRooms (Q2): on the grid: [\"Main Hall\"; \"Room B\"; \"Room C\"] | can carry a feed: [\"Main Hall\"; \"Room B\"] | cannot: [\"Room C\"] | rooms running at once: 3 | streamed sessions at once: 2\nSession remote eligibility menu (step 3), re-ranked - pick: \"Curated remote programme\"\n- rung 1: \"Curated remote programme\" | moved: made the pick by Q1: the remote audience paid - a room chosen for its cabling is not a programme anyone was promised\n- rung 2: \"One designated streamed room\" | the skill's default | moved: promoted by Q10: a room is already wired for capture - it is the natural remote room\n- rung 3: \"Plenary only\"\n- deleted, not demoted: \"Every session in every room\" | reason: Q2: Room C cannot carry a feed - a room the production sibling cannot instrument deletes this rung rather than reordering it\n- deleted, not demoted: \"Per-session speaker opt-in\" | reason: Q2: an opt-in can land in any room, and Room C cannot carry a feed\n- deleted, not demoted: \"Publishing the recordings afterwards and calling that the remote programme\" | reason: it is not a hybrid at all - a remote audience that watches next week is the in-person-with-record-and-post mode this event decided against; post-event publication belongs to event-content-repurposing\n- notes: Dominance check: five rungs, ten pairs, one strict relation - the curated programme dominates per-session opt-in; five pairs are blocked by breadth alone and four by cost running opposite to both value axes.\nRemote programme: pick: \"Curated remote programme\" | moved by: \"Q1\" | remote room: \"Main Hall\" | sessions eligible: 8 of 18\nEligibility column (step 3) - every session on the grid, marked: 18 | rows below: 18 of 18 | cut: 0 (at most 40 sent, self-set) - in this order:\n- session 1: id: \"M1\" | title: \"Opening keynote: the next ten years of the edge\" | room: \"Main Hall\" | when: 2027-05-12 09:00-09:45 | status: \"eligible\" | reason: \"in the curated remote programme - promised to remote\" | return path: \"Reserved floor time for remote questions\" | remote person: \"Lena Park\" | audience: \"in-room questioners may be on camera\" | night for: [\"UTC-5\"]\n must: [session \"M1\" exactly; status \"eligible\" exactly; reason uses the word \"programme\"]\n- session 2: id: \"M2\" | title: \"Observability without the bill\" | room: \"Main Hall\" | when: 2027-05-12 10:00-10:45 | status: \"eligible\" | reason: \"in the curated remote programme - promised to remote\" | return path: \"Reserved floor time for remote questions\" | remote person: \"Lena Park\" | audience: \"in-room questioners off camera - audio only\" | night for: [\"UTC-5\"]\n must: [session \"M2\" exactly; status \"eligible\" exactly; reason uses the word \"programme\"]\n- session 3: id: \"B1\" | title: \"Hands-on: tracing a slow request\" | room: \"Room B\" | when: 2027-05-12 10:00-10:45 | status: \"not eligible\" | reason: \"not in the curated remote programme\"\n must: [session \"B1\" exactly; status \"not eligible\" exactly; reason uses the word \"programme\"]\n- session 4: id: \"C1\" | title: \"Writing your first operator\" | room: \"Room C\" | when: 2027-05-12 10:00-11:30 | status: \"not eligible\" | reason: \"not in the curated remote programme\"\n must: [session \"C1\" exactly; status \"not eligible\" exactly; reason uses the word \"programme\"]\n- session 5: id: \"M3\" | title: \"Postgres at the edge\" | room: \"Main Hall\" | when: 2027-05-12 11:00-11:45 | status: \"not eligible\" | reason: \"consent not known (Q4) - settle it with event-speaker-experience before the grid is marked\"\n must: [session \"M3\" exactly; status \"not eligible\" exactly; reason uses the word \"consent\"]\n- session 6: id: \"B2\" | title: \"Panel: running a platform team\" | room: \"Room B\" | when: 2027-05-12 11:00-11:45 | status: \"eligible\" | reason: \"in the curated remote programme - promised to remote\" | return path: \"Reserved floor time for remote questions\" | remote person: \"Omar Diaz\" | audience: \"in-room questioners may be on camera\" | night for: [\"UTC-5\"]\n must: [session \"B2\" exactly; status \"eligible\" exactly; reason uses the word \"programme\"]\n- session 7: id: \"M4\" | title: \"Shipping on Fridays, safely\" | room: \"Main Hall\" | when: 2027-05-12 14:00-14:45 | status: \"eligible\" | reason: \"in the curated remote programme - promised to remote\" | return path: \"Reserved floor time for remote questions\" | remote person: \"Lena Park\" | audience: \"off camera until settled (the page's default)\" | night for: none\n must: [session \"M4\" exactly; status \"eligible\" exactly; reason uses the word \"programme\"]\n- session 8: id: \"B3\" | title: \"Chaos drills for small teams\" | room: \"Room B\" | when: 2027-05-12 14:00-15:30 | status: \"not eligible\" | reason: \"not in the curated remote programme\"\n must: [session \"B3\" exactly; status \"not eligible\" exactly; reason uses the word \"programme\"]\n- session 9: id: \"C2\" | title: \"Lightning talks\" | room: \"Room C\" | when: 2027-05-12 14:00-14:45 | status: \"not eligible\" | reason: \"not in the curated remote programme\"\n must: [session \"C2\" exactly; status \"not eligible\" exactly; reason uses the word \"programme\"]\n- session 10: id: \"M5\" | title: \"Day-two keynote: open source as infrastructure\" | room: \"Main Hall\" | when: 2027-05-13 09:30-10:15 | status: \"eligible\" | reason: \"in the curated remote programme - promised to remote\" | return path: \"Reserved floor time for remote questions\" | remote person: \"Omar Diaz\" | audience: \"in-room questioners may be on camera\" | night for: [\"UTC-5\"]\n must: [session \"M5\" exactly; status \"eligible\" exactly; reason uses the word \"programme\"]\n- session 11: id: \"M6\" | title: \"Rust in the request path\" | room: \"Main Hall\" | when: 2027-05-13 10:30-11:15 | status: \"not eligible\" | reason: \"consent not known (Q4) - settle it with event-speaker-experience before the grid is marked\"\n must: [session \"M6\" exactly; status \"not eligible\" exactly; reason uses the word \"consent\"]\n- session 12: id: \"B4\" | title: \"Accessibility audits in CI\" | room: \"Room B\" | when: 2027-05-13 10:30-11:15 | status: \"not eligible\" | reason: \"not in the curated remote programme\"\n must: [session \"B4\" exactly; status \"not eligible\" exactly; reason uses the word \"programme\"]\n- session 13: id: \"C3\" | title: \"Build a feature flag service\" | room: \"Room C\" | when: 2027-05-13 10:30-12:00 | status: \"not eligible\" | reason: \"not in the curated remote programme\"\n must: [session \"C3\" exactly; status \"not eligible\" exactly; reason uses the word \"programme\"]\n- session 14: id: \"M7\" | title: \"The cost of a millisecond\" | room: \"Main Hall\" | when: 2027-05-13 13:30-14:15 | status: \"eligible\" | reason: \"in the curated remote programme - promised to remote\" | return path: \"Reserved floor time for remote questions\" | remote person: \"Lena Park\" | audience: \"in-room questioners may be on camera\" | night for: none\n must: [session \"M7\" exactly; status \"eligible\" exactly; reason uses the word \"programme\"]\n- session 15: id: \"B5\" | title: \"Panel: hiring for on-call\" | room: \"Room B\" | when: 2027-05-13 13:30-14:15 | status: \"eligible\" | reason: \"in the curated remote programme - promised to remote\" | return path: \"Reserved floor time for remote questions\" | remote person: \"Lena Park\" | audience: \"in-room questioners off camera - audio only\" | night for: none\n must: [session \"B5\" exactly; status \"eligible\" exactly; reason uses the word \"programme\"]\n- session 16: id: \"C4\" | title: \"Incident reviews that teach\" | room: \"Room C\" | when: 2027-05-13 13:30-14:15 | status: \"not eligible\" | reason: \"not in the curated remote programme\"\n must: [session \"C4\" exactly; status \"not eligible\" exactly; reason uses the word \"programme\"]\n- session 17: id: \"M8\" | title: \"Closing keynote\" | room: \"Main Hall\" | when: 2027-05-13 16:00-16:45 | status: \"eligible\" | reason: \"in the curated remote programme - promised to remote\" | return path: \"Reserved floor time for remote questions\" | remote person: \"Omar Diaz\" | audience: \"in-room questioners may be on camera\" | night for: none\n must: [session \"M8\" exactly; status \"eligible\" exactly; reason uses the word \"programme\"]\n- session 18: id: \"S1\" | title: \"Community social\" | room: \"Room C\" | when: 2027-05-13 17:00-18:30 | status: \"not eligible\" | reason: \"not in the curated remote programme\"\n must: [session \"S1\" exactly; status \"not eligible\" exactly; reason uses the word \"programme\"]\nParity depth menu (step 4), re-ranked - pick: \"Reserved floor time for remote questions\"\n- rung 1: \"Reserved floor time for remote questions\" | moved: made the pick by Q1: live Q&A was promised - a promise is kept by a rule, never by goodwill (the page's own reading of the skill's parity-by-goodwill failure); promoted by Q10: last edition's remote questions went unanswered - a rule the moderator holds, not goodwill\n- rung 2: \"Live relay by a named person\" | the skill's default\n- rung 3: \"Feed only\"\n- rung 4: \"An asynchronous written return path\" | moved: promoted by Q3: part of the programme lands in the remote audience's night - a written answer reaches people who were asleep (the page's own reading)\n- rung 5: \"Remote participants appear in the room\"\n- deleted, not demoted: \"An open remote question channel with nobody reading it\" | reason: the one rung worse than the floor - it invites a question inside the session and drops it inside the same session, in front of the person who asked\n- notes: Dominance check: five rungs, ten pairs, zero strict relations - reaching the stage runs opposite to effort and to the room's own session on every pair; clean only by construction. The room's-own-session axis names who pays (the in-room audience), never a reason to pick a rung.\nParity: pick: \"Reserved floor time for remote questions\" | moved by: \"Q1\"\n parity must: [pick \"Reserved floor time for remote questions\" exactly; reason uses the word \"Q1\"]\nInside a session, minute to minute (the reference's mechanics): The relay mechanic plus a sequence rule the moderator holds: remote takes the first question of every Q&A (a sequence, never a share - it survives a Q&A cut short). Brief the moderator before the day and put the rule on the cue sheet row.\nCrew split menu (step 7), re-ranked - pick: \"A watcher off the floor and an advocate in each room\"\n- rung 1: \"A watcher off the floor and an advocate in each room\" | moved: made the pick by Q2: more than one room carries a feed at once - nobody standing in one room can see another room's output\n- rung 2: \"An advocate inside each streamed room\" | the skill's default\n- rung 3: \"A host on the feed as well\" | moved: promoted by Q1: a paid virtual ticket; promoted by Q8: a recurring event - the same person can hold the role again and the briefing is written once\n- rung 4: \"One watcher off the floor, event-wide\"\n- rung 5: \"No split\"\n- deleted, not demoted: \"The room lead doubling as the remote advocate\" | reason: the room lead owns that room's clock - holds a start, cuts a Q&A, gets the tech owner - the job competing with relaying a remote question at the moment both come due\n- notes: Dominance check: five rungs, ten pairs, zero strict relations - eight pairs blocked by cost running opposite to both value axes, one by the two value axes crossing (the lone watcher against the in-room advocate), one by a tie on in-session service.\nCrew split: pick: \"A watcher off the floor and an advocate in each room\" | moved by: \"Q2\" | watcher off the floor: \"Ines Brandt\" | host on the feed: \"not named\" | people needed at the busiest moment: 3 | Q9 effort ceiling: 3\nCrew (step 7) - a named person per streamed session, or closed: 8 | rows below: 8 of 8 | cut: 0 (at most 40 sent, self-set) - in this order:\n- crew 1: session: \"M1\" | who: \"Lena Park\" | concurrent with: none\n must: [session \"M1\" exactly; who \"Lena Park\" exactly]\n- crew 2: session: \"M2\" | who: \"Lena Park\" | concurrent with: none\n must: [session \"M2\" exactly; who \"Lena Park\" exactly]\n- crew 3: session: \"B2\" | who: \"Omar Diaz\" | concurrent with: none\n must: [session \"B2\" exactly; who \"Omar Diaz\" exactly]\n- crew 4: session: \"M4\" | who: \"Lena Park\" | concurrent with: none\n must: [session \"M4\" exactly; who \"Lena Park\" exactly]\n- crew 5: session: \"M5\" | who: \"Omar Diaz\" | concurrent with: none\n must: [session \"M5\" exactly; who \"Omar Diaz\" exactly]\n- crew 6: session: \"M7\" | who: \"Lena Park\" | concurrent with: [\"B5\"]\n must: [session \"M7\" exactly; who \"Lena Park\" exactly]\n- crew 7: session: \"B5\" | who: \"Lena Park\" | concurrent with: [\"M7\"]\n must: [session \"B5\" exactly; who \"Lena Park\" exactly]\n- crew 8: session: \"M8\" | who: \"Omar Diaz\" | concurrent with: none\n must: [session \"M8\" exactly; who \"Omar Diaz\" exactly]\nDouble-booked across concurrent streamed sessions: 1:\n- double 1: who: \"Lena Park\" | sessions: [\"M7\"; \"B5\"]\nTwo-audience gap (step 9): disruption order: \"as entered in Q6\" | says anything about the remote side: no | closed in writing: no | decision: \"not decided\" | who decided: \"not named\"\n gap must: [decision uses the words \"not decided\"; who \"not named\" exactly]\nDocumented defaults: [keep the room going and fix a dead feed in parallel, rather than pausing on reflex; put the authority to give up on a feed inside the technical chain, escalated to a senior organizer - never a volunteer alone with the call; give the remote side a named live-chat contact, not an intermittently checked inbox]\nOpen questions, decided in writing: [who wins when the remote-side person and the room lead need the same technician at once; whether a session moved to fix an in-room problem takes the remote programme with it]\nHand-offs - 4 - in this order:\n- handoff 1: \"The eligible-session list per room (8 sessions in 2 rooms), each session's return path and its audience-on-camera rule - an input to the coverage tier, it names the equipment\" | to: [event-production]\n must: [contains \"event-production\"]\n- handoff 2: \"The consent check - 5 sessions whose consent is no, not recorded or unknown, and whether each yes covers a live feed\" | to: [event-speaker-experience]\n must: [contains \"event-speaker-experience\"]\n- handoff 3: \"The stream's cue column - 48 rows on the same rows as the room's, never a second document\" | to: [event-run-of-show]\n must: [contains \"event-run-of-show\"]\n- handoff 4: \"The staffing consequence - the feed for 8 sessions survives every tier, a lean tier cuts the return path and the 3 people watching it first\" | to: [event-budget]\n must: [contains \"event-budget\"]\nStaffing for the budget: the feed survives every tier: 8 sessions | a lean tier cuts first: the return path and the people watching it: [\"Lena Park\"; \"Omar Diaz\"; \"Ines Brandt\"]\nDesign must: [summary at most 40 words; verdict \"fix first\" exactly; promise contains \"all main-stage talks live with Q&A\"; eligibility copies the 18 rows above, in this order; crew copies the 8 rows above, in this order; handoffs copies the 4 rows above, in this order; caveats carry the fix findings first, at most 5]\nCue track: 48 rows - written in the cues lane\nFindings (14):\n[fix] promise_5 (promise): \"M3\" was promised to remote but is not eligible - consent not known (Q4) - settle it with event-speaker-experience before the grid is marked. Settle it before the grid is published, or tell the remote audience in writing.\n[fix] promise_11 (promise): \"M6\" was promised to remote but is not eligible - consent not known (Q4) - settle it with event-speaker-experience before the grid is marked. Settle it before the grid is published, or tell the remote audience in writing.\n[fix] crew_double_14_15 (crew): \"Lena Park\" is the remote person for \"M7\" and \"B5\", which run at the same time in \"Main Hall\" and \"Room B\" - nobody standing in one room can see another room's output; name a second person.\n[fix] gap_open (gap): Q6: the disruption priority order is silent on the remote side - the two-audience gap. Its audience is singular and a hybrid has two. Decide in writing before the day who wins when the remote-side person and the room lead need the same technician at once and whether a session moved to fix an in-room problem takes the remote programme with it, and say who decided.\n[check] audience_unknown (consent): Q4: whether in-room questioners appear on the outgoing feed is not settled for \"M4\" - off camera until it is (the page's default); settle it before the grid is marked.\n[check] cue_conflict (cues): The cue track has 12 cues owned by a person who is due in a concurrent streamed session at that minute: \"Lena Park\" in \"B5\" and \"M7\" (12).\n[info] format_closed (format): Step 1: the format decision is closed - consume its verdict, never reopen it.\n[info] consent_live (consent): Q4: a recording consent and a live-feed consent are different permissions - confirm with Ruth Adeyemi, speaker team that each yes covers a live feed. \"A published stream cannot be recalled from the people already watching it.\"\n[info] crew_off_3 (crew): \"Sana Okafor\" is down for \"M3\" and \"M6\", which are not eligible - free for another streamed session.\n[info] night (time): Q3: \"M1\", \"M2\", \"B2\" and \"M5\" land in the night (00:00 to 06:00, self-set) for UTC-5 - a live relay reaches the people awake; say what the others get.\n[info] defaults (gap): Documented defaults, written beside the decisions: keep the room going and fix a dead feed in parallel, rather than pausing on reflex; put the authority to give up on a feed inside the technical chain, escalated to a senior organizer - never a volunteer alone with the call; give the remote side a named live-chat contact, not an intermittently checked inbox.\n[info] notes (context): The pasted notes are context: a request in them is answered in writing, never a silent commitment.\n[info] q5 (context): Q5: the constraint goes to event-production with the eligible-session list - the page recommends no platform and prices none.\n[info] scope (scope): This skill is deliberately narrow: format to event-format-selection, coverage tiers to event-production, consent records to event-speaker-experience, the run of show to event-run-of-show, the budget to event-budget. The page never moves a session on the grid."
}
Reply:
{
"task": "design",
"summary": "Fix first: two promised main-stage talks have no settled consent, Lena Park is double-booked at 13:30 and the disruption order is silent on the remote side - settle all three before the grid is published.",
"verdict": "fix first",
"promise": "Promised to virtual ticket holders: \"all main-stage talks live with Q&A\" - a paid virtual ticket, covering named sessions or rooms, sold on live Q&A.",
"eligibility": [
{
"session": "M1",
"status": "eligible",
"reason": "In the curated remote programme - promised to remote."
},
{
"session": "M2",
"status": "eligible",
"reason": "In the curated remote programme - promised to remote."
},
{
"session": "B1",
"status": "not eligible",
"reason": "Not in the curated remote programme."
},
{
"session": "C1",
"status": "not eligible",
"reason": "Not in the curated remote programme."
},
{
"session": "M3",
"status": "not eligible",
"reason": "Consent not known (Q4) - settle it with event-speaker-experience before the grid is marked."
},
{
"session": "B2",
"status": "eligible",
"reason": "In the curated remote programme - promised to remote."
},
{
"session": "M4",
"status": "eligible",
"reason": "In the curated remote programme - promised to remote."
},
{
"session": "B3",
"status": "not eligible",
"reason": "Not in the curated remote programme."
},
{
"session": "C2",
"status": "not eligible",
"reason": "Not in the curated remote programme."
},
{
"session": "M5",
"status": "eligible",
"reason": "In the curated remote programme - promised to remote."
},
{
"session": "M6",
"status": "not eligible",
"reason": "Consent not known (Q4) - settle it with event-speaker-experience before the grid is marked."
},
{
"session": "B4",
"status": "not eligible",
"reason": "Not in the curated remote programme."
},
{
"session": "C3",
"status": "not eligible",
"reason": "Not in the curated remote programme."
},
{
"session": "M7",
"status": "eligible",
"reason": "In the curated remote programme - promised to remote."
},
{
"session": "B5",
"status": "eligible",
"reason": "In the curated remote programme - promised to remote."
},
{
"session": "C4",
"status": "not eligible",
"reason": "Not in the curated remote programme."
},
{
"session": "M8",
"status": "eligible",
"reason": "In the curated remote programme - promised to remote."
},
{
"session": "S1",
"status": "not eligible",
"reason": "Not in the curated remote programme."
}
],
"parity": {
"pick": "Reserved floor time for remote questions",
"reason": "Q1: live Q&A was promised - a promise is kept by a rule, never by goodwill (the page's own reading of the skill's parity-by-goodwill failure)."
},
"crew": [
{
"session": "M1",
"who": "Lena Park"
},
{
"session": "M2",
"who": "Lena Park"
},
{
"session": "B2",
"who": "Omar Diaz"
},
{
"session": "M4",
"who": "Lena Park"
},
{
"session": "M5",
"who": "Omar Diaz"
},
{
"session": "M7",
"who": "Lena Park"
},
{
"session": "B5",
"who": "Lena Park"
},
{
"session": "M8",
"who": "Omar Diaz"
}
],
"gap": {
"decision": "not decided",
"who": "not named"
},
"handoffs": [
"The eligible-session list per room (8 sessions in 2 rooms), each session's return path and its audience-on-camera rule - an input to the coverage tier; it names the equipment - event-production",
"The consent check - 5 sessions whose consent is no, not recorded or unknown, and whether each yes covers a live feed - event-speaker-experience",
"The stream's cue column - 48 rows on the same rows as the room's, never a second document - event-run-of-show",
"The staffing consequence - the feed for 8 sessions survives every tier; a lean tier cuts the return path and the 3 people watching it first - event-budget"
],
"caveats": [
"\"M3\" was promised to remote but is not eligible - consent not known (Q4) - settle it with event-speaker-experience before the grid is marked.",
"\"M6\" was promised to remote but is not eligible - consent not known (Q4) - settle it with event-speaker-experience before the grid is marked.",
"\"Lena Park\" is the remote person for \"M7\" and \"B5\", which run at the same time in \"Main Hall\" and \"Room B\" - nobody standing in one room can see another room's output; name a second person.",
"The disruption priority order is silent on the remote side - the two-audience gap.",
"Whether in-room questioners appear on the outgoing feed is not settled for \"M4\" - off camera until it is (the page's default); settle it before the grid is marked."
]
}
The page's check: kept - nothing to flag
The same, consents settled, a second person named, the gap decided - ready to hand off - cues
Verdict: ready to hand off; ready to hand off: yes; 18 sessions, 10 eligible; remote programme: Curated remote programme; parity: Reserved floor time for remote questions; crew split: A watcher off the floor and an advocate in each room; 60 cue rows; 0 double-bookings; 0 fix-level findings.
Input:
{
"task": "cues",
"event": "Event: \"Northfield DevConf 2027\" | dates: 2027-05-12 to 2027-05-13 | city: \"Northfield\" | time zone: \"UTC+1\" | as of: 2027-03-15\nFormat decision (step 1): \"closed - hybrid was chosen\"\nQ1 promised: \"all main-stage talks live with Q&A\" | to whom: \"virtual ticket holders\" | written as: \"a paid virtual ticket\" | covers: \"named sessions or rooms\" | sold on: \"live Q&A - remote questions answered\" | chosen for remote: [\"Main Hall\"; \"B2\"; \"B5\"]\nQ3 remote audience: \"a spread wide enough that part of the programme lands in their night\" | remote time zones: [\"UTC-5\"; \"UTC+5:30\"]\nQ4 consent record held by: \"Ruth Adeyemi, speaker team\" | per-speaker opt-out genuinely offered: \"no\"\nQ5 platform or data constraint: \"The sponsor contract keeps the stream on the conference's own site.\"\nQ6 disruption priority order: \"Protect the plenary or keynote first, protect the headline commitment the audience showed up for next, minimize how many other sessions the fix disturbs, then lock the revised grid for the rest of the block.\"\nQ7 last date a session can join or leave: 2027-04-20 | days left: 36 | the grid: \"not yet published\"\nQ8: \"a recurring event\"\nQ9 people who can work the remote side alone: 3\nQ10 what already exists: [\"an organizer who has streamed before\"; \"last edition's remote questions went unanswered\"; \"a community that already lives in a chat space\"; \"a room already wired: Main Hall\"]\nTwo-audience decisions: technician: \"the room lead gets the technician first, unless the feed on a keynote is dead\" | a moved session: \"the remote programme follows the moved session\" | decided by: \"Maya Chen\"",
"grid": "Published grid - 18 sessions, one per line, as entered:\n- session 1: id: \"M1\" | title: \"Opening keynote: the next ten years of the edge\" | room: \"Main Hall\" | when: 2027-05-12 09:00-09:45 | format: \"keynote\" | consent: \"yes\" | in-room audience on camera: \"yes\"\n- session 2: id: \"M2\" | title: \"Observability without the bill\" | room: \"Main Hall\" | when: 2027-05-12 10:00-10:45 | format: \"talk\" | consent: \"yes\" | in-room audience on camera: \"no\"\n- session 3: id: \"B1\" | title: \"Hands-on: tracing a slow request\" | room: \"Room B\" | when: 2027-05-12 10:00-10:45 | format: \"workshop\" | consent: \"yes\" | in-room audience on camera: \"unknown\"\n- session 4: id: \"C1\" | title: \"Writing your first operator\" | room: \"Room C\" | when: 2027-05-12 10:00-11:30 | format: \"workshop\" | consent: \"yes\" | in-room audience on camera: \"yes\"\n- session 5: id: \"M3\" | title: \"Postgres at the edge\" | room: \"Main Hall\" | when: 2027-05-12 11:00-11:45 | format: \"talk\" | consent: \"yes\" | in-room audience on camera: \"yes\"\n- session 6: id: \"B2\" | title: \"Panel: running a platform team\" | room: \"Room B\" | when: 2027-05-12 11:00-11:45 | format: \"panel\" | consent: \"yes\" | in-room audience on camera: \"yes\"\n- session 7: id: \"M4\" | title: \"Shipping on Fridays, safely\" | room: \"Main Hall\" | when: 2027-05-12 14:00-14:45 | format: \"talk\" | consent: \"yes\" | in-room audience on camera: \"unknown\"\n- session 8: id: \"B3\" | title: \"Chaos drills for small teams\" | room: \"Room B\" | when: 2027-05-12 14:00-15:30 | format: \"workshop\" | consent: \"no\" | in-room audience on camera: \"yes\"\n- session 9: id: \"C2\" | title: \"Lightning talks\" | room: \"Room C\" | when: 2027-05-12 14:00-14:45 | format: \"talk\" | consent: \"yes\" | in-room audience on camera: \"yes\"\n- session 10: id: \"M5\" | title: \"Day-two keynote: open source as infrastructure\" | room: \"Main Hall\" | when: 2027-05-13 09:30-10:15 | format: \"keynote\" | consent: \"yes\" | in-room audience on camera: \"yes\"\n- session 11: id: \"M6\" | title: \"Rust in the request path\" | room: \"Main Hall\" | when: 2027-05-13 10:30-11:15 | format: \"talk\" | consent: \"yes\" | in-room audience on camera: \"yes\"\n- session 12: id: \"B4\" | title: \"Accessibility audits in CI\" | room: \"Room B\" | when: 2027-05-13 10:30-11:15 | format: \"talk\" | consent: \"yes\" | in-room audience on camera: \"yes\"\n- session 13: id: \"C3\" | title: \"Build a feature flag service\" | room: \"Room C\" | when: 2027-05-13 10:30-12:00 | format: \"workshop\" | consent: \"not recorded\" | in-room audience on camera: \"yes\"\n- session 14: id: \"M7\" | title: \"The cost of a millisecond\" | room: \"Main Hall\" | when: 2027-05-13 13:30-14:15 | format: \"talk\" | consent: \"yes\" | in-room audience on camera: \"yes\"\n- session 15: id: \"B5\" | title: \"Panel: hiring for on-call\" | room: \"Room B\" | when: 2027-05-13 13:30-14:15 | format: \"panel\" | consent: \"yes\" | in-room audience on camera: \"no\"\n- session 16: id: \"C4\" | title: \"Incident reviews that teach\" | room: \"Room C\" | when: 2027-05-13 13:30-14:15 | format: \"talk\" | consent: \"yes\" | in-room audience on camera: \"yes\"\n- session 17: id: \"M8\" | title: \"Closing keynote\" | room: \"Main Hall\" | when: 2027-05-13 16:00-16:45 | format: \"keynote\" | consent: \"yes\" | in-room audience on camera: \"yes\"\n- session 18: id: \"S1\" | title: \"Community social\" | room: \"Room C\" | when: 2027-05-13 17:00-18:30 | format: \"social\" | consent: \"not recorded\" | in-room audience on camera: \"no\"\nRooms - 3:\n- room 1: \"Main Hall\" | can carry a feed: yes | already wired: yes\n- room 2: \"Room B\" | can carry a feed: yes | already wired: no\n- room 3: \"Room C\" | can carry a feed: no | already wired: no",
"crew": "Crew - 4, one per line, as entered:\n- crew 1: name: \"Lena Park\" | works the remote side alone: yes | other duties during sessions: \"none\" | remote side for: [\"M1\"; \"M2\"; \"M4\"; \"M7\"]\n- crew 2: name: \"Omar Diaz\" | works the remote side alone: yes | other duties during sessions: \"none\" | remote side for: [\"M5\"; \"M8\"; \"B2\"]\n- crew 3: name: \"Sana Okafor\" | works the remote side alone: yes | other duties during sessions: \"none\" | remote side for: [\"M3\"; \"M6\"; \"B5\"]\n- crew 4: name: \"Tom Reyes\" | works the remote side alone: no | other duties during sessions: \"room lead in Room B\" | remote side for: none\nWatcher off the floor: \"Ines Brandt\" | host on the feed: \"not named\" | senior organizer the feed call escalates to: \"Maya Chen\"",
"notes": "Last edition the remote channel asked twice whether the afternoon was delayed and nobody answered. The virtual ticket page says: all main-stage talks live with Q&A.",
"facts": "Verdict: \"ready to hand off\" | ready to hand off: yes | fix-level findings: 0\nSelf-set lines: [the pre-roll cue at -5 min; the Q&A handover and the remote question relay at 10 min before the end; the remote night is 00:00 to 06:00; a join/leave deadline under 14 days is soon; at most 40 sessions and 60 cues sent; a summary cap of 40 words; at most 5 caveats; a room missing from the rooms table cannot carry a feed; audience questions off camera until settled; the promise sentence is the page's template]\nNever stated: [a session, a room, a time, a person, a consent or a platform the input does not hold; a streaming platform as a recommendation or a price; equipment - cameras, encoders, uplinks or return audio; a parity threshold, a staffing ratio, a share of Q&A owed to remote or a tolerable stream delay; a moved session on the grid]\nFormat (step 1): \"closed - hybrid was chosen\" | stop: no\nPromise (step 2): \"Promised to virtual ticket holders: 'all main-stage talks live with Q&A' - a paid virtual ticket, covering named sessions or rooms, sold on live Q&A.\" | promise word: \"all main-stage talks live with Q&A\"\nStreamed sessions: 10 | ids: [\"M1\"; \"M2\"; \"M3\"; \"B2\"; \"M4\"; \"M5\"; \"M6\"; \"M7\"; \"B5\"; \"M8\"] | not streamed: [\"B1\"; \"C1\"; \"B3\"; \"C2\"; \"B4\"; \"C3\"; \"C4\"; \"S1\"]\n- streamed 1: id: \"M1\" | title: \"Opening keynote: the next ten years of the edge\" | room: \"Main Hall\" | when: 2027-05-12 09:00-09:45 | return path: \"Reserved floor time for remote questions\" | remote person: \"Lena Park\" | night for: [\"UTC-5\"]\n- streamed 2: id: \"M2\" | title: \"Observability without the bill\" | room: \"Main Hall\" | when: 2027-05-12 10:00-10:45 | return path: \"Reserved floor time for remote questions\" | remote person: \"Lena Park\" | night for: [\"UTC-5\"]\n- streamed 3: id: \"M3\" | title: \"Postgres at the edge\" | room: \"Main Hall\" | when: 2027-05-12 11:00-11:45 | return path: \"Reserved floor time for remote questions\" | remote person: \"Sana Okafor\" | night for: [\"UTC-5\"]\n- streamed 4: id: \"B2\" | title: \"Panel: running a platform team\" | room: \"Room B\" | when: 2027-05-12 11:00-11:45 | return path: \"Reserved floor time for remote questions\" | remote person: \"Omar Diaz\" | night for: [\"UTC-5\"]\n- streamed 5: id: \"M4\" | title: \"Shipping on Fridays, safely\" | room: \"Main Hall\" | when: 2027-05-12 14:00-14:45 | return path: \"Reserved floor time for remote questions\" | remote person: \"Lena Park\" | night for: none\n- streamed 6: id: \"M5\" | title: \"Day-two keynote: open source as infrastructure\" | room: \"Main Hall\" | when: 2027-05-13 09:30-10:15 | return path: \"Reserved floor time for remote questions\" | remote person: \"Omar Diaz\" | night for: [\"UTC-5\"]\n- streamed 7: id: \"M6\" | title: \"Rust in the request path\" | room: \"Main Hall\" | when: 2027-05-13 10:30-11:15 | return path: \"Reserved floor time for remote questions\" | remote person: \"Sana Okafor\" | night for: [\"UTC-5\"]\n- streamed 8: id: \"M7\" | title: \"The cost of a millisecond\" | room: \"Main Hall\" | when: 2027-05-13 13:30-14:15 | return path: \"Reserved floor time for remote questions\" | remote person: \"Lena Park\" | night for: none\n- streamed 9: id: \"B5\" | title: \"Panel: hiring for on-call\" | room: \"Room B\" | when: 2027-05-13 13:30-14:15 | return path: \"Reserved floor time for remote questions\" | remote person: \"Sana Okafor\" | night for: none\n- streamed 10: id: \"M8\" | title: \"Closing keynote\" | room: \"Main Hall\" | when: 2027-05-13 16:00-16:45 | return path: \"Reserved floor time for remote questions\" | remote person: \"Omar Diaz\" | night for: none\nParity: pick: \"Reserved floor time for remote questions\" | relay rule word: \"first\" | inside a session: The relay mechanic plus a sequence rule the moderator holds: remote takes the first question of every Q&A (a sequence, never a share - it survives a Q&A cut short). Brief the moderator before the day and put the rule on the cue sheet row.\nCrew split: pick: \"A watcher off the floor and an advocate in each room\" | watcher off the floor: \"Ines Brandt\" | host on the feed: \"not named\"\nAdvocates - the named remote people: [\"Lena Park\"; \"Sana Okafor\"; \"Omar Diaz\"] | senior organizer the feed call escalates to: \"Maya Chen\"\nTwo-audience decision: \"technician: the room lead gets the technician first, unless the feed on a keynote is dead - moved session: the remote programme follows the moved session\" | who decided: \"Maya Chen\"\nDocumented defaults: [keep the room going and fix a dead feed in parallel, rather than pausing on reflex; put the authority to give up on a feed inside the technical chain, escalated to a senior organizer - never a volunteer alone with the call; give the remote side a named live-chat contact, not an intermittently checked inbox]\nCue track (step 8) - one more column on the run of show's rows; offsets from each session's start: 60 | rows below: 60 of 60 | cut: 0 (at most 60 sent, self-set) - in this order:\n- cue 1: session: \"M1\" | cue: \"pre-roll\" | offset: \"-5 min\" | clock: \"08:55\" | who: \"Lena Park\"\n must: [session \"M1\" exactly; cue \"pre-roll\" exactly; offset \"-5 min\" exactly; clock \"08:55\" exactly; who \"Lena Park\" exactly]\n- cue 2: session: \"M1\" | cue: \"start\" | offset: \"+0 min\" | clock: \"09:00\" | who: \"Lena Park\"\n must: [session \"M1\" exactly; cue \"start\" exactly; offset \"+0 min\" exactly; clock \"09:00\" exactly; who \"Lena Park\" exactly]\n- cue 3: session: \"M1\" | cue: \"Q&A handover\" | offset: \"+35 min\" | clock: \"09:35\" | who: \"Lena Park\"\n must: [session \"M1\" exactly; cue \"Q&A handover\" exactly; offset \"+35 min\" exactly; clock \"09:35\" exactly; who \"Lena Park\" exactly]\n- cue 4: session: \"M1\" | cue: \"remote question relay\" | offset: \"+35 min\" | clock: \"09:35\" | who: \"Lena Park\"\n must: [session \"M1\" exactly; cue \"remote question relay\" exactly; offset \"+35 min\" exactly; clock \"09:35\" exactly; who \"Lena Park\" exactly]\n- cue 5: session: \"M1\" | cue: \"holding slate\" | offset: \"+45 min\" | clock: \"09:45\" | who: \"Lena Park\"\n must: [session \"M1\" exactly; cue \"holding slate\" exactly; offset \"+45 min\" exactly; clock \"09:45\" exactly; who \"Lena Park\" exactly]\n- cue 6: session: \"M1\" | cue: \"end\" | offset: \"+45 min\" | clock: \"09:45\" | who: \"Lena Park\"\n must: [session \"M1\" exactly; cue \"end\" exactly; offset \"+45 min\" exactly; clock \"09:45\" exactly; who \"Lena Park\" exactly]\n- cue 7: session: \"M2\" | cue: \"pre-roll\" | offset: \"-5 min\" | clock: \"09:55\" | who: \"Lena Park\"\n must: [session \"M2\" exactly; cue \"pre-roll\" exactly; offset \"-5 min\" exactly; clock \"09:55\" exactly; who \"Lena Park\" exactly]\n- cue 8: session: \"M2\" | cue: \"start\" | offset: \"+0 min\" | clock: \"10:00\" | who: \"Lena Park\"\n must: [session \"M2\" exactly; cue \"start\" exactly; offset \"+0 min\" exactly; clock \"10:00\" exactly; who \"Lena Park\" exactly]\n- cue 9: session: \"M2\" | cue: \"Q&A handover\" | offset: \"+35 min\" | clock: \"10:35\" | who: \"Lena Park\"\n must: [session \"M2\" exactly; cue \"Q&A handover\" exactly; offset \"+35 min\" exactly; clock \"10:35\" exactly; who \"Lena Park\" exactly]\n- cue 10: session: \"M2\" | cue: \"remote question relay\" | offset: \"+35 min\" | clock: \"10:35\" | who: \"Lena Park\"\n must: [session \"M2\" exactly; cue \"remote question relay\" exactly; offset \"+35 min\" exactly; clock \"10:35\" exactly; who \"Lena Park\" exactly]\n- cue 11: session: \"M2\" | cue: \"holding slate\" | offset: \"+45 min\" | clock: \"10:45\" | who: \"Lena Park\"\n must: [session \"M2\" exactly; cue \"holding slate\" exactly; offset \"+45 min\" exactly; clock \"10:45\" exactly; who \"Lena Park\" exactly]\n- cue 12: session: \"M2\" | cue: \"end\" | offset: \"+45 min\" | clock: \"10:45\" | who: \"Lena Park\"\n must: [session \"M2\" exactly; cue \"end\" exactly; offset \"+45 min\" exactly; clock \"10:45\" exactly; who \"Lena Park\" exactly]\n- cue 13: session: \"M3\" | cue: \"pre-roll\" | offset: \"-5 min\" | clock: \"10:55\" | who: \"Sana Okafor\"\n must: [session \"M3\" exactly; cue \"pre-roll\" exactly; offset \"-5 min\" exactly; clock \"10:55\" exactly; who \"Sana Okafor\" exactly]\n- cue 14: session: \"M3\" | cue: \"start\" | offset: \"+0 min\" | clock: \"11:00\" | who: \"Sana Okafor\"\n must: [session \"M3\" exactly; cue \"start\" exactly; offset \"+0 min\" exactly; clock \"11:00\" exactly; who \"Sana Okafor\" exactly]\n- cue 15: session: \"M3\" | cue: \"Q&A handover\" | offset: \"+35 min\" | clock: \"11:35\" | who: \"Sana Okafor\"\n must: [session \"M3\" exactly; cue \"Q&A handover\" exactly; offset \"+35 min\" exactly; clock \"11:35\" exactly; who \"Sana Okafor\" exactly]\n- cue 16: session: \"M3\" | cue: \"remote question relay\" | offset: \"+35 min\" | clock: \"11:35\" | who: \"Sana Okafor\"\n must: [session \"M3\" exactly; cue \"remote question relay\" exactly; offset \"+35 min\" exactly; clock \"11:35\" exactly; who \"Sana Okafor\" exactly]\n- cue 17: session: \"M3\" | cue: \"holding slate\" | offset: \"+45 min\" | clock: \"11:45\" | who: \"Sana Okafor\"\n must: [session \"M3\" exactly; cue \"holding slate\" exactly; offset \"+45 min\" exactly; clock \"11:45\" exactly; who \"Sana Okafor\" exactly]\n- cue 18: session: \"M3\" | cue: \"end\" | offset: \"+45 min\" | clock: \"11:45\" | who: \"Sana Okafor\"\n must: [session \"M3\" exactly; cue \"end\" exactly; offset \"+45 min\" exactly; clock \"11:45\" exactly; who \"Sana Okafor\" exactly]\n- cue 19: session: \"B2\" | cue: \"pre-roll\" | offset: \"-5 min\" | clock: \"10:55\" | who: \"Omar Diaz\"\n must: [session \"B2\" exactly; cue \"pre-roll\" exactly; offset \"-5 min\" exactly; clock \"10:55\" exactly; who \"Omar Diaz\" exactly]\n- cue 20: session: \"B2\" | cue: \"start\" | offset: \"+0 min\" | clock: \"11:00\" | who: \"Omar Diaz\"\n must: [session \"B2\" exactly; cue \"start\" exactly; offset \"+0 min\" exactly; clock \"11:00\" exactly; who \"Omar Diaz\" exactly]\n- cue 21: session: \"B2\" | cue: \"Q&A handover\" | offset: \"+35 min\" | clock: \"11:35\" | who: \"Omar Diaz\"\n must: [session \"B2\" exactly; cue \"Q&A handover\" exactly; offset \"+35 min\" exactly; clock \"11:35\" exactly; who \"Omar Diaz\" exactly]\n- cue 22: session: \"B2\" | cue: \"remote question relay\" | offset: \"+35 min\" | clock: \"11:35\" | who: \"Omar Diaz\"\n must: [session \"B2\" exactly; cue \"remote question relay\" exactly; offset \"+35 min\" exactly; clock \"11:35\" exactly; who \"Omar Diaz\" exactly]\n- cue 23: session: \"B2\" | cue: \"holding slate\" | offset: \"+45 min\" | clock: \"11:45\" | who: \"Omar Diaz\"\n must: [session \"B2\" exactly; cue \"holding slate\" exactly; offset \"+45 min\" exactly; clock \"11:45\" exactly; who \"Omar Diaz\" exactly]\n- cue 24: session: \"B2\" | cue: \"end\" | offset: \"+45 min\" | clock: \"11:45\" | who: \"Omar Diaz\"\n must: [session \"B2\" exactly; cue \"end\" exactly; offset \"+45 min\" exactly; clock \"11:45\" exactly; who \"Omar Diaz\" exactly]\n- cue 25: session: \"M4\" | cue: \"pre-roll\" | offset: \"-5 min\" | clock: \"13:55\" | who: \"Lena Park\"\n must: [session \"M4\" exactly; cue \"pre-roll\" exactly; offset \"-5 min\" exactly; clock \"13:55\" exactly; who \"Lena Park\" exactly]\n- cue 26: session: \"M4\" | cue: \"start\" | offset: \"+0 min\" | clock: \"14:00\" | who: \"Lena Park\"\n must: [session \"M4\" exactly; cue \"start\" exactly; offset \"+0 min\" exactly; clock \"14:00\" exactly; who \"Lena Park\" exactly]\n- cue 27: session: \"M4\" | cue: \"Q&A handover\" | offset: \"+35 min\" | clock: \"14:35\" | who: \"Lena Park\"\n must: [session \"M4\" exactly; cue \"Q&A handover\" exactly; offset \"+35 min\" exactly; clock \"14:35\" exactly; who \"Lena Park\" exactly]\n- cue 28: session: \"M4\" | cue: \"remote question relay\" | offset: \"+35 min\" | clock: \"14:35\" | who: \"Lena Park\"\n must: [session \"M4\" exactly; cue \"remote question relay\" exactly; offset \"+35 min\" exactly; clock \"14:35\" exactly; who \"Lena Park\" exactly]\n- cue 29: session: \"M4\" | cue: \"holding slate\" | offset: \"+45 min\" | clock: \"14:45\" | who: \"Lena Park\"\n must: [session \"M4\" exactly; cue \"holding slate\" exactly; offset \"+45 min\" exactly; clock \"14:45\" exactly; who \"Lena Park\" exactly]\n- cue 30: session: \"M4\" | cue: \"end\" | offset: \"+45 min\" | clock: \"14:45\" | who: \"Lena Park\"\n must: [session \"M4\" exactly; cue \"end\" exactly; offset \"+45 min\" exactly; clock \"14:45\" exactly; who \"Lena Park\" exactly]\n- cue 31: session: \"M5\" | cue: \"pre-roll\" | offset: \"-5 min\" | clock: \"09:25\" | who: \"Omar Diaz\"\n must: [session \"M5\" exactly; cue \"pre-roll\" exactly; offset \"-5 min\" exactly; clock \"09:25\" exactly; who \"Omar Diaz\" exactly]\n- cue 32: session: \"M5\" | cue: \"start\" | offset: \"+0 min\" | clock: \"09:30\" | who: \"Omar Diaz\"\n must: [session \"M5\" exactly; cue \"start\" exactly; offset \"+0 min\" exactly; clock \"09:30\" exactly; who \"Omar Diaz\" exactly]\n- cue 33: session: \"M5\" | cue: \"Q&A handover\" | offset: \"+35 min\" | clock: \"10:05\" | who: \"Omar Diaz\"\n must: [session \"M5\" exactly; cue \"Q&A handover\" exactly; offset \"+35 min\" exactly; clock \"10:05\" exactly; who \"Omar Diaz\" exactly]\n- cue 34: session: \"M5\" | cue: \"remote question relay\" | offset: \"+35 min\" | clock: \"10:05\" | who: \"Omar Diaz\"\n must: [session \"M5\" exactly; cue \"remote question relay\" exactly; offset \"+35 min\" exactly; clock \"10:05\" exactly; who \"Omar Diaz\" exactly]\n- cue 35: session: \"M5\" | cue: \"holding slate\" | offset: \"+45 min\" | clock: \"10:15\" | who: \"Omar Diaz\"\n must: [session \"M5\" exactly; cue \"holding slate\" exactly; offset \"+45 min\" exactly; clock \"10:15\" exactly; who \"Omar Diaz\" exactly]\n- cue 36: session: \"M5\" | cue: \"end\" | offset: \"+45 min\" | clock: \"10:15\" | who: \"Omar Diaz\"\n must: [session \"M5\" exactly; cue \"end\" exactly; offset \"+45 min\" exactly; clock \"10:15\" exactly; who \"Omar Diaz\" exactly]\n- cue 37: session: \"M6\" | cue: \"pre-roll\" | offset: \"-5 min\" | clock: \"10:25\" | who: \"Sana Okafor\"\n must: [session \"M6\" exactly; cue \"pre-roll\" exactly; offset \"-5 min\" exactly; clock \"10:25\" exactly; who \"Sana Okafor\" exactly]\n- cue 38: session: \"M6\" | cue: \"start\" | offset: \"+0 min\" | clock: \"10:30\" | who: \"Sana Okafor\"\n must: [session \"M6\" exactly; cue \"start\" exactly; offset \"+0 min\" exactly; clock \"10:30\" exactly; who \"Sana Okafor\" exactly]\n- cue 39: session: \"M6\" | cue: \"Q&A handover\" | offset: \"+35 min\" | clock: \"11:05\" | who: \"Sana Okafor\"\n must: [session \"M6\" exactly; cue \"Q&A handover\" exactly; offset \"+35 min\" exactly; clock \"11:05\" exactly; who \"Sana Okafor\" exactly]\n- cue 40: session: \"M6\" | cue: \"remote question relay\" | offset: \"+35 min\" | clock: \"11:05\" | who: \"Sana Okafor\"\n must: [session \"M6\" exactly; cue \"remote question relay\" exactly; offset \"+35 min\" exactly; clock \"11:05\" exactly; who \"Sana Okafor\" exactly]\n- cue 41: session: \"M6\" | cue: \"holding slate\" | offset: \"+45 min\" | clock: \"11:15\" | who: \"Sana Okafor\"\n must: [session \"M6\" exactly; cue \"holding slate\" exactly; offset \"+45 min\" exactly; clock \"11:15\" exactly; who \"Sana Okafor\" exactly]\n- cue 42: session: \"M6\" | cue: \"end\" | offset: \"+45 min\" | clock: \"11:15\" | who: \"Sana Okafor\"\n must: [session \"M6\" exactly; cue \"end\" exactly; offset \"+45 min\" exactly; clock \"11:15\" exactly; who \"Sana Okafor\" exactly]\n- cue 43: session: \"M7\" | cue: \"pre-roll\" | offset: \"-5 min\" | clock: \"13:25\" | who: \"Lena Park\"\n must: [session \"M7\" exactly; cue \"pre-roll\" exactly; offset \"-5 min\" exactly; clock \"13:25\" exactly; who \"Lena Park\" exactly]\n- cue 44: session: \"M7\" | cue: \"start\" | offset: \"+0 min\" | clock: \"13:30\" | who: \"Lena Park\"\n must: [session \"M7\" exactly; cue \"start\" exactly; offset \"+0 min\" exactly; clock \"13:30\" exactly; who \"Lena Park\" exactly]\n- cue 45: session: \"M7\" | cue: \"Q&A handover\" | offset: \"+35 min\" | clock: \"14:05\" | who: \"Lena Park\"\n must: [session \"M7\" exactly; cue \"Q&A handover\" exactly; offset \"+35 min\" exactly; clock \"14:05\" exactly; who \"Lena Park\" exactly]\n- cue 46: session: \"M7\" | cue: \"remote question relay\" | offset: \"+35 min\" | clock: \"14:05\" | who: \"Lena Park\"\n must: [session \"M7\" exactly; cue \"remote question relay\" exactly; offset \"+35 min\" exactly; clock \"14:05\" exactly; who \"Lena Park\" exactly]\n- cue 47: session: \"M7\" | cue: \"holding slate\" | offset: \"+45 min\" | clock: \"14:15\" | who: \"Lena Park\"\n must: [session \"M7\" exactly; cue \"holding slate\" exactly; offset \"+45 min\" exactly; clock \"14:15\" exactly; who \"Lena Park\" exactly]\n- cue 48: session: \"M7\" | cue: \"end\" | offset: \"+45 min\" | clock: \"14:15\" | who: \"Lena Park\"\n must: [session \"M7\" exactly; cue \"end\" exactly; offset \"+45 min\" exactly; clock \"14:15\" exactly; who \"Lena Park\" exactly]\n- cue 49: session: \"B5\" | cue: \"pre-roll\" | offset: \"-5 min\" | clock: \"13:25\" | who: \"Sana Okafor\"\n must: [session \"B5\" exactly; cue \"pre-roll\" exactly; offset \"-5 min\" exactly; clock \"13:25\" exactly; who \"Sana Okafor\" exactly]\n- cue 50: session: \"B5\" | cue: \"start\" | offset: \"+0 min\" | clock: \"13:30\" | who: \"Sana Okafor\"\n must: [session \"B5\" exactly; cue \"start\" exactly; offset \"+0 min\" exactly; clock \"13:30\" exactly; who \"Sana Okafor\" exactly]\n- cue 51: session: \"B5\" | cue: \"Q&A handover\" | offset: \"+35 min\" | clock: \"14:05\" | who: \"Sana Okafor\"\n must: [session \"B5\" exactly; cue \"Q&A handover\" exactly; offset \"+35 min\" exactly; clock \"14:05\" exactly; who \"Sana Okafor\" exactly]\n- cue 52: session: \"B5\" | cue: \"remote question relay\" | offset: \"+35 min\" | clock: \"14:05\" | who: \"Sana Okafor\"\n must: [session \"B5\" exactly; cue \"remote question relay\" exactly; offset \"+35 min\" exactly; clock \"14:05\" exactly; who \"Sana Okafor\" exactly]\n- cue 53: session: \"B5\" | cue: \"holding slate\" | offset: \"+45 min\" | clock: \"14:15\" | who: \"Sana Okafor\"\n must: [session \"B5\" exactly; cue \"holding slate\" exactly; offset \"+45 min\" exactly; clock \"14:15\" exactly; who \"Sana Okafor\" exactly]\n- cue 54: session: \"B5\" | cue: \"end\" | offset: \"+45 min\" | clock: \"14:15\" | who: \"Sana Okafor\"\n must: [session \"B5\" exactly; cue \"end\" exactly; offset \"+45 min\" exactly; clock \"14:15\" exactly; who \"Sana Okafor\" exactly]\n- cue 55: session: \"M8\" | cue: \"pre-roll\" | offset: \"-5 min\" | clock: \"15:55\" | who: \"Omar Diaz\"\n must: [session \"M8\" exactly; cue \"pre-roll\" exactly; offset \"-5 min\" exactly; clock \"15:55\" exactly; who \"Omar Diaz\" exactly]\n- cue 56: session: \"M8\" | cue: \"start\" | offset: \"+0 min\" | clock: \"16:00\" | who: \"Omar Diaz\"\n must: [session \"M8\" exactly; cue \"start\" exactly; offset \"+0 min\" exactly; clock \"16:00\" exactly; who \"Omar Diaz\" exactly]\n- cue 57: session: \"M8\" | cue: \"Q&A handover\" | offset: \"+35 min\" | clock: \"16:35\" | who: \"Omar Diaz\"\n must: [session \"M8\" exactly; cue \"Q&A handover\" exactly; offset \"+35 min\" exactly; clock \"16:35\" exactly; who \"Omar Diaz\" exactly]\n- cue 58: session: \"M8\" | cue: \"remote question relay\" | offset: \"+35 min\" | clock: \"16:35\" | who: \"Omar Diaz\"\n must: [session \"M8\" exactly; cue \"remote question relay\" exactly; offset \"+35 min\" exactly; clock \"16:35\" exactly; who \"Omar Diaz\" exactly]\n- cue 59: session: \"M8\" | cue: \"holding slate\" | offset: \"+45 min\" | clock: \"16:45\" | who: \"Omar Diaz\"\n must: [session \"M8\" exactly; cue \"holding slate\" exactly; offset \"+45 min\" exactly; clock \"16:45\" exactly; who \"Omar Diaz\" exactly]\n- cue 60: session: \"M8\" | cue: \"end\" | offset: \"+45 min\" | clock: \"16:45\" | who: \"Omar Diaz\"\n must: [session \"M8\" exactly; cue \"end\" exactly; offset \"+45 min\" exactly; clock \"16:45\" exactly; who \"Omar Diaz\" exactly]\nCue conflicts - a person due in two concurrent streamed sessions: 0\nCues must: [cue_track copies the 60 rows above, in this order; advocate_brief who contains \"Lena Park\", \"Sana Okafor\" and \"Omar Diaz\"; what_to_watch uses the words \"feed\" and \"channel\"; relay_rule uses the words \"first\" and \"confirm\"; holding uses the words \"holding card\" and \"next\"; escalation contains \"Maya Chen\" and the word \"feed\"; remote_notice contains \"M1\", \"M2\", \"M3\", \"B2\", \"M4\", \"M5\", \"M6\", \"M7\", \"B5\" and \"M8\" and the word \"not\"] | cues note must: [use the word \"hand\" and say what happens next]\nCarried from the design: summary: \"Ready to hand off: every promised session is eligible with a named remote person, remote takes the first question at every Q&A, and Maya Chen decided the two-audience gap in writing.\" | parts matching the page: 3 of 3\nFindings (9):\n[check] audience_unknown (consent): Q4: whether in-room questioners appear on the outgoing feed is not settled for \"M4\" - off camera until it is (the page's default); settle it before the grid is marked.\n[info] format_closed (format): Step 1: the format decision is closed - consume its verdict, never reopen it.\n[info] consent_live (consent): Q4: a recording consent and a live-feed consent are different permissions - confirm with Ruth Adeyemi, speaker team that each yes covers a live feed. \"A published stream cannot be recalled from the people already watching it.\"\n[info] night (time): Q3: \"M1\", \"M2\", \"M3\", \"B2\", \"M5\" and \"M6\" land in the night (00:00 to 06:00, self-set) for UTC-5 - a live relay reaches the people awake; say what the others get.\n[info] gap_closed (gap): Q6: the two-audience gap is closed in writing - technician: the room lead gets the technician first, unless the feed on a keynote is dead - moved session: the remote programme follows the moved session; decided by \"Maya Chen\". Neither answer is inherited practice: write it into the run of show beside the documented defaults.\n[info] defaults (gap): Documented defaults, written beside the decisions: keep the room going and fix a dead feed in parallel, rather than pausing on reflex; put the authority to give up on a feed inside the technical chain, escalated to a senior organizer - never a volunteer alone with the call; give the remote side a named live-chat contact, not an intermittently checked inbox.\n[info] notes (context): The pasted notes are context: a request in them is answered in writing, never a silent commitment.\n[info] q5 (context): Q5: the constraint goes to event-production with the eligible-session list - the page recommends no platform and prices none.\n[info] scope (scope): This skill is deliberately narrow: format to event-format-selection, coverage tiers to event-production, consent records to event-speaker-experience, the run of show to event-run-of-show, the budget to event-budget. The page never moves a session on the grid."
}
Reply:
{
"task": "cues",
"cue_track": [
{
"session": "M1",
"cue": "pre-roll",
"offset": "-5 min",
"clock": "08:55",
"who": "Lena Park"
},
{
"session": "M1",
"cue": "start",
"offset": "+0 min",
"clock": "09:00",
"who": "Lena Park"
},
{
"session": "M1",
"cue": "Q&A handover",
"offset": "+35 min",
"clock": "09:35",
"who": "Lena Park"
},
{
"session": "M1",
"cue": "remote question relay",
"offset": "+35 min",
"clock": "09:35",
"who": "Lena Park"
},
{
"session": "M1",
"cue": "holding slate",
"offset": "+45 min",
"clock": "09:45",
"who": "Lena Park"
},
{
"session": "M1",
"cue": "end",
"offset": "+45 min",
"clock": "09:45",
"who": "Lena Park"
},
{
"session": "M2",
"cue": "pre-roll",
"offset": "-5 min",
"clock": "09:55",
"who": "Lena Park"
},
{
"session": "M2",
"cue": "start",
"offset": "+0 min",
"clock": "10:00",
"who": "Lena Park"
},
{
"session": "M2",
"cue": "Q&A handover",
"offset": "+35 min",
"clock": "10:35",
"who": "Lena Park"
},
{
"session": "M2",
"cue": "remote question relay",
"offset": "+35 min",
"clock": "10:35",
"who": "Lena Park"
},
{
"session": "M2",
"cue": "holding slate",
"offset": "+45 min",
"clock": "10:45",
"who": "Lena Park"
},
{
"session": "M2",
"cue": "end",
"offset": "+45 min",
"clock": "10:45",
"who": "Lena Park"
},
{
"session": "M3",
"cue": "pre-roll",
"offset": "-5 min",
"clock": "10:55",
"who": "Sana Okafor"
},
{
"session": "M3",
"cue": "start",
"offset": "+0 min",
"clock": "11:00",
"who": "Sana Okafor"
},
{
"session": "M3",
"cue": "Q&A handover",
"offset": "+35 min",
"clock": "11:35",
"who": "Sana Okafor"
},
{
"session": "M3",
"cue": "remote question relay",
"offset": "+35 min",
"clock": "11:35",
"who": "Sana Okafor"
},
{
"session": "M3",
"cue": "holding slate",
"offset": "+45 min",
"clock": "11:45",
"who": "Sana Okafor"
},
{
"session": "M3",
"cue": "end",
"offset": "+45 min",
"clock": "11:45",
"who": "Sana Okafor"
},
{
"session": "B2",
"cue": "pre-roll",
"offset": "-5 min",
"clock": "10:55",
"who": "Omar Diaz"
},
{
"session": "B2",
"cue": "start",
"offset": "+0 min",
"clock": "11:00",
"who": "Omar Diaz"
},
{
"session": "B2",
"cue": "Q&A handover",
"offset": "+35 min",
"clock": "11:35",
"who": "Omar Diaz"
},
{
"session": "B2",
"cue": "remote question relay",
"offset": "+35 min",
"clock": "11:35",
"who": "Omar Diaz"
},
{
"session": "B2",
"cue": "holding slate",
"offset": "+45 min",
"clock": "11:45",
"who": "Omar Diaz"
},
{
"session": "B2",
"cue": "end",
"offset": "+45 min",
"clock": "11:45",
"who": "Omar Diaz"
},
{
"session": "M4",
"cue": "pre-roll",
"offset": "-5 min",
"clock": "13:55",
"who": "Lena Park"
},
{
"session": "M4",
"cue": "start",
"offset": "+0 min",
"clock": "14:00",
"who": "Lena Park"
},
{
"session": "M4",
"cue": "Q&A handover",
"offset": "+35 min",
"clock": "14:35",
"who": "Lena Park"
},
{
"session": "M4",
"cue": "remote question relay",
"offset": "+35 min",
"clock": "14:35",
"who": "Lena Park"
},
{
"session": "M4",
"cue": "holding slate",
"offset": "+45 min",
"clock": "14:45",
"who": "Lena Park"
},
{
"session": "M4",
"cue": "end",
"offset": "+45 min",
"clock": "14:45",
"who": "Lena Park"
},
{
"session": "M5",
"cue": "pre-roll",
"offset": "-5 min",
"clock": "09:25",
"who": "Omar Diaz"
},
{
"session": "M5",
"cue": "start",
"offset": "+0 min",
"clock": "09:30",
"who": "Omar Diaz"
},
{
"session": "M5",
"cue": "Q&A handover",
"offset": "+35 min",
"clock": "10:05",
"who": "Omar Diaz"
},
{
"session": "M5",
"cue": "remote question relay",
"offset": "+35 min",
"clock": "10:05",
"who": "Omar Diaz"
},
{
"session": "M5",
"cue": "holding slate",
"offset": "+45 min",
"clock": "10:15",
"who": "Omar Diaz"
},
{
"session": "M5",
"cue": "end",
"offset": "+45 min",
"clock": "10:15",
"who": "Omar Diaz"
},
{
"session": "M6",
"cue": "pre-roll",
"offset": "-5 min",
"clock": "10:25",
"who": "Sana Okafor"
},
{
"session": "M6",
"cue": "start",
"offset": "+0 min",
"clock": "10:30",
"who": "Sana Okafor"
},
{
"session": "M6",
"cue": "Q&A handover",
"offset": "+35 min",
"clock": "11:05",
"who": "Sana Okafor"
},
{
"session": "M6",
"cue": "remote question relay",
"offset": "+35 min",
"clock": "11:05",
"who": "Sana Okafor"
},
{
"session": "M6",
"cue": "holding slate",
"offset": "+45 min",
"clock": "11:15",
"who": "Sana Okafor"
},
{
"session": "M6",
"cue": "end",
"offset": "+45 min",
"clock": "11:15",
"who": "Sana Okafor"
},
{
"session": "M7",
"cue": "pre-roll",
"offset": "-5 min",
"clock": "13:25",
"who": "Lena Park"
},
{
"session": "M7",
"cue": "start",
"offset": "+0 min",
"clock": "13:30",
"who": "Lena Park"
},
{
"session": "M7",
"cue": "Q&A handover",
"offset": "+35 min",
"clock": "14:05",
"who": "Lena Park"
},
{
"session": "M7",
"cue": "remote question relay",
"offset": "+35 min",
"clock": "14:05",
"who": "Lena Park"
},
{
"session": "M7",
"cue": "holding slate",
"offset": "+45 min",
"clock": "14:15",
"who": "Lena Park"
},
{
"session": "M7",
"cue": "end",
"offset": "+45 min",
"clock": "14:15",
"who": "Lena Park"
},
{
"session": "B5",
"cue": "pre-roll",
"offset": "-5 min",
"clock": "13:25",
"who": "Sana Okafor"
},
{
"session": "B5",
"cue": "start",
"offset": "+0 min",
"clock": "13:30",
"who": "Sana Okafor"
},
{
"session": "B5",
"cue": "Q&A handover",
"offset": "+35 min",
"clock": "14:05",
"who": "Sana Okafor"
},
{
"session": "B5",
"cue": "remote question relay",
"offset": "+35 min",
"clock": "14:05",
"who": "Sana Okafor"
},
{
"session": "B5",
"cue": "holding slate",
"offset": "+45 min",
"clock": "14:15",
"who": "Sana Okafor"
},
{
"session": "B5",
"cue": "end",
"offset": "+45 min",
"clock": "14:15",
"who": "Sana Okafor"
},
{
"session": "M8",
"cue": "pre-roll",
"offset": "-5 min",
"clock": "15:55",
"who": "Omar Diaz"
},
{
"session": "M8",
"cue": "start",
"offset": "+0 min",
"clock": "16:00",
"who": "Omar Diaz"
},
{
"session": "M8",
"cue": "Q&A handover",
"offset": "+35 min",
"clock": "16:35",
"who": "Omar Diaz"
},
{
"session": "M8",
"cue": "remote question relay",
"offset": "+35 min",
"clock": "16:35",
"who": "Omar Diaz"
},
{
"session": "M8",
"cue": "holding slate",
"offset": "+45 min",
"clock": "16:45",
"who": "Omar Diaz"
},
{
"session": "M8",
"cue": "end",
"offset": "+45 min",
"clock": "16:45",
"who": "Omar Diaz"
}
],
"advocate_brief": {
"who": "Lena Park, Sana Okafor and Omar Diaz",
"what_to_watch": "Watch the outgoing feed as a remote attendee receives it - live, audible, not frozen - and keep the remote channel open for the whole session; Ines Brandt watches the feed off the floor.",
"relay_rule": "Remote takes the first question of every Q&A: read the selected question aloud with the asker's name and where they are, then confirm in the channel that it was asked.",
"holding": "Between sessions the stream shows a holding card with what is happening and when the next session starts, and the same line goes into the remote channel at the same moment.",
"escalation": "If the feed fails, keep the room going and fix it in parallel; the technician escalates the call to give up on a feed to Maya Chen, and the remote channel hears what broke and where to watch."
},
"remote_notice": "Streamed live: M1, M2, M3, B2, M4, M5, M6, M7, B5 and M8, with remote questions taken first at every Q&A. Not streamed: B1, C1, B3, C2, B4, C3, C4 and S1. Between sessions the stream shows a holding card with the next start time.",
"note": "Hand the cue column to the run of show and the eligible-session list to production now, then brief each advocate before doors."
}
The page's check: kept - nothing to flag
The free remote notice skeleton for the resolved sample
# Northfield DevConf 2027 - what the remote audience is told (the page's skeleton)
_Verdict: ready to hand off._
## Streamed live
- M1 - Opening keynote: the next ten years of the edge - 2027-05-12 09:00-09:45 (Main Hall) - questions: reserved floor time for remote questions
- M2 - Observability without the bill - 2027-05-12 10:00-10:45 (Main Hall) - questions: reserved floor time for remote questions
- M3 - Postgres at the edge - 2027-05-12 11:00-11:45 (Main Hall) - questions: reserved floor time for remote questions
- B2 - Panel: running a platform team - 2027-05-12 11:00-11:45 (Room B) - questions: reserved floor time for remote questions
- M4 - Shipping on Fridays, safely - 2027-05-12 14:00-14:45 (Main Hall) - questions: reserved floor time for remote questions
- M5 - Day-two keynote: open source as infrastructure - 2027-05-13 09:30-10:15 (Main Hall) - questions: reserved floor time for remote questions
- M6 - Rust in the request path - 2027-05-13 10:30-11:15 (Main Hall) - questions: reserved floor time for remote questions
- M7 - The cost of a millisecond - 2027-05-13 13:30-14:15 (Main Hall) - questions: reserved floor time for remote questions
- B5 - Panel: hiring for on-call - 2027-05-13 13:30-14:15 (Room B) - questions: reserved floor time for remote questions
- M8 - Closing keynote - 2027-05-13 16:00-16:45 (Main Hall) - questions: reserved floor time for remote questions
## Not streamed
- B1 - Hands-on: tracing a slow request
- C1 - Writing your first operator
- B3 - Chaos drills for small teams
- C2 - Lightning talks
- B4 - Accessibility audits in CI
- C3 - Build a feature flag service
- C4 - Incident reviews that teach
- S1 - Community social
## Questions and the gaps
- The relay mechanic plus a sequence rule the moderator holds: remote takes the first question of every Q&A (a sequence, never a share - it survives a Q&A cut short). Brief the moderator before the day and put the rule on the cue sheet row.
- Between sessions the stream shows a holding card with what is happening and when the next session starts.
- If the feed fails, the room keeps going and the feed is fixed in parallel; the remote channel hears what broke, what still works and where to watch.
Made with Hybrid Grid - https://hybrid-grid.skillsafe.ai/
Build the input yourself
Load the engine in node (global.window = global; require("./hybridkit.js"); require("./hybridreply.js")), then:
const K = HybridKit, R = HybridReply;
const f = K.form({ name: "My Meetup 12", start: "2027-09-14", tz: "UTC+2", format: "decided", q1Type: "access", q1Words: "every talk streamed live", q1Covers: "whole",
grid: "T1 | Opening talk | Hall | 19:00 | 19:30 | talk | yes | yes", rooms: "Hall | yes | yes", crew: "Ana Silva | yes | none | T1",
escalate: "Ben Ito", techRule: "the room keeps the technician", movedRule: "loses", decidedBy: "Ana Silva" });
// f.promise, f.elig, f.menu1..3, f.parityLabel, f.streamed (each with .who), f.doubles, f.cues, f.gap, f.handoffs, f.verdict, f.findings
K.gridCsv(f); K.cueCsv(f); K.cueIcs(f); K.crewCsv(f); K.noticeMd(f); K.board(f) // the free exports
const b = K.buildInput(f, "design"); // or "cues"
const wire = K.toWire(b.input); // POST this to /estimate and /run
// then: const reply = R.normalize(R.parseReply(job.output.output), "design");
// R.check(reply, b) // {status: kept or flagged, notes}
// R.carryText(reply) // the lines the cues 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://hybrid-grid.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://hybrid-grid.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"]
// hybrid-grid.mjs -- Node 18+ (global fetch)
const BASE = "https://api.skillsafe.ai/v1/app-api";
let TOKEN = "YOUR_TOKEN"; // from https://hybrid-grid.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://hybrid-grid.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
}
// HybridGrid.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 HybridGrid {
static final String BASE = "https://api.skillsafe.ai/v1/app-api";
static String token = "YOUR_TOKEN"; // from https://hybrid-grid.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://hybrid-grid.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://hybrid-grid.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://hybrid-grid.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":"hybrid-grid"}' | 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": "hybrid-grid"})["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: "hybrid-grid" })).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": "hybrid-grid"}, 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", "hybrid-grid"), 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: "hybrid-grid" })["token"])
// a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
$TOKEN = "";
$TOKEN = api("POST", "/guest", ["slug" => "hybrid-grid"])["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 = "hybrid-grid" }))!["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": "design", "event": "Event: ... | dates: ... | time zone: ...", "grid": "Published grid - 18 sessions ...", "crew": "Crew - 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": "design", "event": "Event: ... | dates: ... | time zone: ...", "grid": "Published grid - 18 sessions ...", "crew": "Crew - 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: "design" or "cues", event, grid, crew, 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 hybrid-grid:<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 design carries summary, verdict, promise, eligibility, parity, crew, gap, handoffs and caveats; a cue track carries cue_track, advocate_brief, remote_notice and note. The code below prints the headline of either.
KEY="hybrid-grid:design:$(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 // .remote_notice' reply.json
jq -r '.eligibility[]? | "\(.session) \(.status)"' reply.json # a design; a cue track has .cue_track
import hashlib, time
key = "hybrid-grid:" + 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("eligibility") or reply.get("cue_track") or []:
print("-", row["session"], row.get("status") or row.get("clock"))
import { createHash } from "node:crypto";
const key = `hybrid-grid:${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.eligibility || reply.cue_track || []) console.log("-", row.session, row.status ?? row.clock);
sum := sha256.Sum256(raw) // add "crypto/sha256", "encoding/hex", "time" to the imports
key := "hybrid-grid:" + 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["eligibility"].([]any) // a design's column; a cue track has cue_track
for _, row := range rows {
fmt.Println("-", row.(map[string]any)["session"], row.(map[string]any)["status"])
}
var digest = java.security.MessageDigest.getInstance("SHA-256").digest(M.writeValueAsBytes(input));
String key = "hybrid-grid:" + 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("eligibility")) System.out.println("- " + row.path("session").asText() + " " + row.path("status").asText());
require "digest"
key = "hybrid-grid:#{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["eligibility"] || []).each { |row| puts "- #{row["session"]} #{row["status"]}" }
$key = "hybrid-grid:" . $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["eligibility"] ?? [] as $row) echo "- ", $row["session"], " ", $row["status"], "\n";
var digest = System.Security.Cryptography.SHA256.HashData(System.Text.Encoding.UTF8.GetBytes(input.ToJsonString()));
var key = $"hybrid-grid:{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["eligibility"] as JsonArray ?? new JsonArray()) Console.WriteLine("- " + row!["session"] + " " + 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: hybrid-grid:design:$(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": "design", "event": "Event: ... | dates: ... | time zone: ...", "grid": "Published grid - 18 sessions ...", "crew": "Crew - 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": "hybrid-grid:design:" + 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": `hybrid-grid:design:${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", "hybrid-grid:design:"+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", "hybrid-grid:design:" + 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" => "hybrid-grid:design:#{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: hybrid-grid:design:" . 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", $"hybrid-grid:design:{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);