Run Jury Room from your own code
Both metered lanes are available over HTTP and each returns one JSON object. The division of labour: the page computes the allocation band and the map, picks the rubric shape, the combination and conflict rules and the tie-break ladder, and ranks a score sheet - all free, in jurykit.js. The run writes the plan or the brief; the page then holds the reply to its own picks and to the map (juryreply.js).
Base URL and the response shape
Every endpoint lives under https://api.skillsafe.ai/v1/app-api, and every response has one shape:
{ "ok": true, "data": { ... } }
{ "ok": false, "error": { "code": "...", "message": "..." } }
The token is minted for this app (the guest call takes {"slug":"jury-room"}), so no slug header is needed afterwards. Send it as Authorization: Bearer …. The run body is the input object, sent bare.
| Status | Code | Meaning |
|---|---|---|
| 400 | validation_error | The body is not a JSON object, or a required key (task, event, allocation, facts) is missing. |
| 401 | unauthorized | No token, or a stale one. Mint a guest token or sign in again. |
| 402 | insufficient_credits | The balance is under min_credits. Price with /estimate first. |
| 403 | forbidden | A guest token tried to run: both lanes are metered and need a personal sign-in. |
| 404 | not_found | Unknown job id. |
| 429 | rate_limited | Back off and retry. |
| 5xx | server_error | Transient. Retry with the same Idempotency-Key so a retry never double-bills. |
The task field comes first
Two lanes - plan (validate the judging before it opens) and brief (sent to judges with the calendar invite; needs the brief's categories). A missing or unknown task is answered as plan. Both lanes send the same four fields.
The input
| Field | Required | What |
|---|---|---|
task | yes | plan or brief. If it is missing or neither, do plan and set "task": "plan". |
event | yes | the event, its format, team count, window and minutes per team, the judges with their types and ties, the tracks and sponsors, the brief's categories and scale, the room and feedback facts, and who holds the tie-break and receives challenges. |
allocation | yes | the page's arithmetic: the band and why, the ceilings, judges per team, the panels, the allocation map (judge, panel, times, teams in visiting order), the sponsor-track panel and the recusals. |
facts | yes | the page's picks (rubric shape, combination rule, conflict rule, tie-break ladder), the readiness gate, the signals to record and every finding tagged [fix], [check] or [info]. |
retry_note | no | optional; if present, your previous reply was unusable - follow it. |
The rules the model applies
- The demo format and the brief's categories arrive decided: the format fixes the minutes of judge time per team, and the categories are weighted, never renamed or replaced. Never set a prize amount. Judges are not mentors.
- Allocation is arithmetic, computed before the rubric: full coverage only while teams N <= window W / minutes per team t - no judge count appears in that cap, so recruiting judges never extends full coverage; a sampled panel gives k = floor(J x W / (N x t)) judges per team; past J x W / t not even one judge reaches every team and only a pass/fail pre-screen then finals is left (or a format change, decided upstream). Expo and stage minutes leave out setup and walking, so every ceiling is an optimistic upper bound. Mix judge types within each panel. An unassigned judge wanders; an unvisited team is a dispute.
- Rubric: weighted criteria by default, integer weights summing to 100, at most four criteria, a 1-5 (or similar) integer scale per criterion; a written per-criterion comment only when judging is async or feedback is promised with a window to write it; a single overall score is deleted once more than one track awards a prize. Publishing the category names and the elimination list satisfies disclosure - the numeric weights may stay internal. A rubric changed mid-round re-scores whoever was seen first.
- Combining: under full coverage a harsh judge's constant lands on every team alike, so raw averages keep the order; under a sampled panel normalize each judge's scores within their own slice; synchronous consensus needs every judge in one room with slack and is deleted when judging is async or judges leave after their slot. An organizer override of the judges' combined result is deleted outright.
- Conflicts: self-declared recusal by default (abstain rather than score and disclose, with a named recusal fallback); declared recusal plus assigned replacement when a sponsor funds a track and a judge is tied to it; a written policy with a registry and an escalation path for a recurring event that promised a dispute process or a prize pool large enough to be challenged; no formal process is deleted once a sponsor funds a track prize. A contract that requires a sponsor's representative to judge its own track: confine them to that track and off the overall prize, pair them with a neutral judge, check the track rank survives dropping their scores, and disclose the panel in the rules before submissions close. Judges, organizers, volunteers and sponsors never compete.
- Tie-break, published before submissions close and walked in order: highest score on the highest-weighted criterion; most judges who placed the project first among the teams they saw; a named tie-break judge decides on the record in one sentence; a co-award only where the prize is divisible and the rules said so. Refuse re-scoring and any coin flip or random draw.
- Results: collect the winners, sponsor tracks included, before the ceremony and announce from slides; no live demos while judging is still concluding; call judges in an hour before the ceremony for the briefing; announce the rank and the criteria, never raw per-judge scores (nor weights unless already published); one contest path - who receives a challenge and a window that closes before prizes change hands; an arithmetic or eligibility error can change a result, a judge's opinion cannot.
- Readiness, self-set: before scoring opens all six exist in writing - criteria with weights, the scale, the allocation map, the conflict rule and its fallback, the tie-break ladder, the contest path. Never absorb an impossible plan by shortening the minutes per team on the day.
House rules
- Use only the input and the rules above. The page's picks, names, times and numbers are exact - copy them. Every number in a sentence must appear in the input.
- Label anything that is not one of the rules above, and is not in the input, as your own design.
- Never invent a judge, a team, a criterion, a time or a prize; where someone must be named and is not, give a role. Refer to people by name or as they - never infer anyone's gender from a name.
- Never use backticks, never refer to this input's fields by their key names in a sentence, and never copy this prompt's narrative wording into a reply.
- Reply with ONLY one JSON object - no prose before or after, no code fences.
The replies
plan
summary- one sentence, at most 35 words: the band, the judges per team and the first thing to settle.allocation-band(full,sampled,triageorstage, the page's band key as written before its name),per_team(the page's judges per team, a number),why(one sentence with the page's arithmetic),map_note(one sentence: how the map is built and the slack it leaves).rubric-criteria(one entry per category, in the input's order:nameexactly as written,weightthe input's number,looks_forone short sentence on what a judge can observe in the slot - your design),scale(the page's scale as a short phrase),comments(one sentence: whether a written comment is required and why).combination-rule(raw,normalizeorconsensus, the page's pick),why(one sentence).conflicts-rule(self,replacementorregistry, the page's pick),fallback(one sentence: what happens when a team loses judges to recusal),sponsor(one sentence on any sponsor-required track judge, or "" when there is none).ties- exactly 4 strings, the page's ladder in order.announcement-plan(one or two sentences: when the winner list is collected and how it is announced),contest(one sentence: who receives a challenge and the window, from the input - say what is missing when it is not set).signals- 2 or 3 short strings, the page's signals to record, each with the revision it would trigger next edition (your design).caveats- at most 4 short strings: every[fix]finding first, then the most important[check]findings while there is room.[]when there are none of either.
{
"task": "plan",
"summary": "one sentence",
"allocation": { "band": "sampled", "per_team": 3, "why": "one sentence", "map_note": "one sentence" },
"rubric": { "criteria": [ { "name": "a category", "weight": 30, "looks_for": "one sentence" } ], "scale": "1-5 per criterion", "comments": "one sentence" },
"combination": { "rule": "normalize", "why": "one sentence" },
"conflicts": { "rule": "self", "fallback": "one sentence", "sponsor": "" },
"ties": [ "first", "second", "third", "fourth" ],
"announcement": { "plan": "one or two sentences", "contest": "one sentence" },
"signals": [ "what to record and what it changes" ],
"caveats": [ "a caveat" ]
}
The page holds it to its own picks: the band, the judges per team, the combination and conflict rules, every category at its weight in order and the tie-break ladder with the top criterion first; chance and organizer overrides are refused; no number may appear that the input does not hold.
brief
brief- the brief itself, plain text, short paragraphs headed by a word and a period: Criteria (every category with its weight, and what to look for), Scale (the page's scale and how to score inside the slot - evidence in one clause, not prose), Conflicts (the recusal wording: abstain rather than score a team you are close to, tell the judging lead, and you judge for the event, not for your employer), Your slice (read your line below; what to do if you fall behind - tell the judging lead, never shorten the minutes per team), Timing (arrive an hour before the ceremony for the briefing - a fixed rule, not a clock time; the minutes per team), Confidentiality (scores and the result stay in the room until the announcement), Ties and challenges (who breaks a tie and who receives a challenge, from the input). Where the input leaves something unset (a tie-break judge, a contest path), write [to be decided: the item] - never fill it in.per_judge- one entry per judge in the allocation map and the sponsor-track panel, in the map's order:judge(the name only, as written),line(one sentence: their panel, start time, first team and how many teams they see, from the map, and any team they abstain on).note- one sentence, at most 30 words, for the judging lead: what to check before the invite goes out.
{
"task": "brief",
"brief": "Criteria. ...\n\nScale. ...",
"per_judge": [ { "judge": "a judge", "line": "one sentence" } ],
"note": "one sentence"
}
The page requires every category and its weight in the brief, the abstain rule, and one line per judge on the map with their real start time and first team; it refuses an instruction to shorten the minutes per team.
Worked examples - every lane, every sample
These are the page's three free samples (fictional events, people and companies). Each input is exactly what the page sends; each reply is the saved reply the sample replays, shipped as the validating model wrote it; the last line is the page's own check. Every sample has both lanes.
Riverside Campus Hack (10 teams, scores in) - plan
Full coverage: 10 teams, 4 judges, 4 per team; combination consensus, conflicts self; 6 of 6 ready.
Input:
{
"task": "plan",
"event": "Event: Riverside Campus Hack; format Expo Table; 10 teams expected; judging window 90 min from 15:00; 5 min of judge time per team\nJudges (4): Ana Ruvelle (technical); Ben Okwill (business); Cass Wenmoor (technical); Dara Millvane (domain) tied to Kettle\nTracks: General\nBrief's categories: Working demo 35%; Technical execution 25%; Fit to the brief 25%; Presentation 15%; scale 1-5\nJudges can all meet in one room yes; judges leave straight after their slot no; per-team feedback promised yes; post-event window to write it yes\nRecurring event yes; published rules promise a dispute process no; prize pool large enough to be challenged no; volunteer crew for a pre-screen no\nTie-break judge: Head judge Ana Ruvelle; co-award promised for a divisible prize no; challenges go to the organizing lead within until the end of the ceremony",
"allocation": "Band: full (Full coverage) - N 10 <= W/t = 90/5 = 18: every judge sees every team, k = J = 4.\nFull-coverage ceiling W/t = 18 teams; triage threshold J x W / t = 72 teams; judges per team k = 4; slack after the visits 40 min\nAllocation map (judge | panel | start-end | teams in visiting order):\nAna Ruvelle (technical) | All | 15:00-15:50 | Byte Club, Null Pointers, Green Grid, Quiet Hours, Lab Notes, Pathfinders, Open Desk, Tiny Tides, Room 404, Kettle\nBen Okwill (business) | All | 15:00-15:50 | Quiet Hours, Lab Notes, Pathfinders, Open Desk, Tiny Tides, Room 404, Kettle, Byte Club, Null Pointers, Green Grid\nCass Wenmoor (technical) | All | 15:00-15:50 | Pathfinders, Open Desk, Tiny Tides, Room 404, Kettle, Byte Club, Null Pointers, Green Grid, Quiet Hours, Lab Notes\nDara Millvane (domain) | All | 15:00-15:50 | Room 404, Kettle, Byte Club, Null Pointers, Green Grid, Quiet Hours, Lab Notes, Pathfinders, Open Desk, Tiny Tides\nRecusals: Dara Millvane abstains on Kettle (every other judge still sees it, so it is averaged over one fewer judge)",
"facts": "Rubric: weighted criteria plus a written per-criterion comment; scale 1-5 per criterion; weights add to 100\nCombination rule: consensus - Synchronous consensus. Every judge fits one room and the visits leave 40 minutes of slack (the 20-minute line and the 7-judge room are self-set) - one timed discussion round.\nConflict rule: self - Self-declared recusal. One line in the judge brief: abstain from scoring a team you are close to, rather than score it and disclose; the judging lead routes another judge to that table.\nTie-break ladder: 1. Highest score on Working demo, the highest-weighted criterion 2. Highest count of judges who placed the project first among the teams they personally saw 3. Head judge Ana Ruvelle decides, on the record, in one sentence 4. No co-award - the published rules do not promise a divisible prize\nRefused: re-scoring after the demos, and any coin flip or random draw\nReadiness gate (6 of 6): Criteria with weights written; The scoring scale written; The allocation map written; The conflict rule and its recusal fallback written; The tie-break ladder written; The contest path written\nSignals to record: Judges who finished their slice inside the window (self-set); Teams that got fewer judges than the map promised (self-set); Recusals that fired (self-set)\nFindings (4):\n[info] t_default (allocation): t = 5 min from the format: 3-5 minutes per team at an Expo Table (no setup or swap allowance - an optimistic upper bound).\n[info] optimistic (allocation): Expo and science-fair minutes allow little or nothing for walking and notes - every ceiling here is an optimistic upper bound, not a capacity promise.\n[check] recusal (conflict): Dara Millvane is tied to Kettle, which is on their list - they abstain; every other judge already sees that team, so it is averaged over one fewer judge.\n[info] gate (readiness): Readiness: 6 of 6 written before scoring opens."
}
Reply:
{
"task": "plan",
"summary": "Full coverage means all 4 judges see every team at Riverside Campus Hack; the first thing to settle is how Kettle is covered now that Dara Millvane abstains from it.",
"allocation": {
"band": "full",
"per_team": 4,
"why": "With 10 teams at or under 90 minutes divided by 5 minutes per team, which is 18, every one of the 4 judges sees every team, so judges per team equals the judge count, 4.",
"map_note": "Each of the 4 judges visits all 10 teams between 15:00 and 15:50 in a different order, leaving 40 minutes of slack inside the 90-minute window."
},
"rubric": {
"criteria": [
{
"name": "Working demo",
"weight": 35,
"looks_for": "Whether the product runs live and completes its core flow without a crash in the slot."
},
{
"name": "Technical execution",
"weight": 25,
"looks_for": "Whether the build shows real engineering depth behind the demo, not just a claim of it."
},
{
"name": "Fit to the brief",
"weight": 25,
"looks_for": "Whether the project visibly solves the problem the brief set for this event."
},
{
"name": "Presentation",
"weight": 15,
"looks_for": "Whether the team explains the demo clearly and handles a quick follow-up question."
}
],
"scale": "1-5 per criterion",
"comments": "A written comment is required for each criterion because per-team feedback is promised and there is a post-event window to write it."
},
"combination": {
"rule": "consensus",
"why": "Scores are combined by synchronous consensus because every judge can meet in one room and the 40 minutes of slack after the visits leaves room for one timed discussion round."
},
"conflicts": {
"rule": "self",
"fallback": "A judge abstains rather than score a team they are close to and tells the judging lead, who can route another judge to that table, though for Kettle every other judge already sees it so it is simply averaged over one fewer judge.",
"sponsor": ""
},
"ties": [
"Highest score on Working demo, the highest-weighted criterion.",
"Highest count of judges who placed the project first among the teams they personally saw.",
"Head judge Ana Ruvelle decides, on the record, in one sentence.",
"No co-award: the published rules do not promise a divisible prize."
],
"announcement": {
"plan": "Winners for the General track are collected before the ceremony and announced from slides, with no live demos while judging is still concluding.",
"contest": "Challenges go to the organizing lead, in a window that closes at the end of the ceremony."
},
"signals": [
"Judges who finished their visits inside the window: if several run long, lengthen the minutes per team next edition.",
"Teams that got fewer judges than the map promised: if this happens, re-check the allocation map before the next event.",
"Recusals that fired: if recusals are frequent, build out the conflict registry for next edition."
],
"caveats": [
"Dara Millvane is tied to Kettle and abstains from it; every other judge already sees Kettle, so it is averaged over one fewer judge."
]
}
The page's check: kept - nothing to flag
Riverside Campus Hack (10 teams, scores in) - brief
Full coverage: 10 teams, 4 judges, 4 per team; combination consensus, conflicts self; 6 of 6 ready.
Input:
{
"task": "brief",
"event": "Event: Riverside Campus Hack; format Expo Table; 10 teams expected; judging window 90 min from 15:00; 5 min of judge time per team\nJudges (4): Ana Ruvelle (technical); Ben Okwill (business); Cass Wenmoor (technical); Dara Millvane (domain) tied to Kettle\nTracks: General\nBrief's categories: Working demo 35%; Technical execution 25%; Fit to the brief 25%; Presentation 15%; scale 1-5\nJudges can all meet in one room yes; judges leave straight after their slot no; per-team feedback promised yes; post-event window to write it yes\nRecurring event yes; published rules promise a dispute process no; prize pool large enough to be challenged no; volunteer crew for a pre-screen no\nTie-break judge: Head judge Ana Ruvelle; co-award promised for a divisible prize no; challenges go to the organizing lead within until the end of the ceremony",
"allocation": "Band: full (Full coverage) - N 10 <= W/t = 90/5 = 18: every judge sees every team, k = J = 4.\nFull-coverage ceiling W/t = 18 teams; triage threshold J x W / t = 72 teams; judges per team k = 4; slack after the visits 40 min\nAllocation map (judge | panel | start-end | teams in visiting order):\nAna Ruvelle (technical) | All | 15:00-15:50 | Byte Club, Null Pointers, Green Grid, Quiet Hours, Lab Notes, Pathfinders, Open Desk, Tiny Tides, Room 404, Kettle\nBen Okwill (business) | All | 15:00-15:50 | Quiet Hours, Lab Notes, Pathfinders, Open Desk, Tiny Tides, Room 404, Kettle, Byte Club, Null Pointers, Green Grid\nCass Wenmoor (technical) | All | 15:00-15:50 | Pathfinders, Open Desk, Tiny Tides, Room 404, Kettle, Byte Club, Null Pointers, Green Grid, Quiet Hours, Lab Notes\nDara Millvane (domain) | All | 15:00-15:50 | Room 404, Kettle, Byte Club, Null Pointers, Green Grid, Quiet Hours, Lab Notes, Pathfinders, Open Desk, Tiny Tides\nRecusals: Dara Millvane abstains on Kettle (every other judge still sees it, so it is averaged over one fewer judge)",
"facts": "Rubric: weighted criteria plus a written per-criterion comment; scale 1-5 per criterion; weights add to 100\nCombination rule: consensus - Synchronous consensus. Every judge fits one room and the visits leave 40 minutes of slack (the 20-minute line and the 7-judge room are self-set) - one timed discussion round.\nConflict rule: self - Self-declared recusal. One line in the judge brief: abstain from scoring a team you are close to, rather than score it and disclose; the judging lead routes another judge to that table.\nTie-break ladder: 1. Highest score on Working demo, the highest-weighted criterion 2. Highest count of judges who placed the project first among the teams they personally saw 3. Head judge Ana Ruvelle decides, on the record, in one sentence 4. No co-award - the published rules do not promise a divisible prize\nRefused: re-scoring after the demos, and any coin flip or random draw\nReadiness gate (6 of 6): Criteria with weights written; The scoring scale written; The allocation map written; The conflict rule and its recusal fallback written; The tie-break ladder written; The contest path written\nSignals to record: Judges who finished their slice inside the window (self-set); Teams that got fewer judges than the map promised (self-set); Recusals that fired (self-set)\nFindings (4):\n[info] t_default (allocation): t = 5 min from the format: 3-5 minutes per team at an Expo Table (no setup or swap allowance - an optimistic upper bound).\n[info] optimistic (allocation): Expo and science-fair minutes allow little or nothing for walking and notes - every ceiling here is an optimistic upper bound, not a capacity promise.\n[check] recusal (conflict): Dara Millvane is tied to Kettle, which is on their list - they abstain; every other judge already sees that team, so it is averaged over one fewer judge.\n[info] gate (readiness): Readiness: 6 of 6 written before scoring opens."
}
Reply:
{
"task": "brief",
"brief": "Criteria. Working demo, 35%: does the demo actually run live at the table. Technical execution, 25%: how solid the build is under the hood. Fit to the brief, 25%: how closely the project answers the stated challenge. Presentation, 15%: how clearly the team explains the problem and the solution in the slot.\n\nScale. Score each criterion 1 to 5 as you watch, each score backed by one clause of what you saw, not a paragraph; since per-team feedback is promised with a window to write it afterward, add a short written comment for each criterion.\n\nConflicts. If you are close to a team, abstain from scoring it rather than score it and disclose; tell the judging lead, who will route another judge to the table. You judge for the event, not for your employer.\n\nYour slice. Read your line below for your panel, start time, and visiting order. If you fall behind, tell the judging lead - never shorten the 5 minutes per team to catch up.\n\nTiming. Arrive one hour before the ceremony for the judges' briefing - a standing rule, not a scheduled clock time. Each team gets 5 minutes of your time.\n\nConfidentiality. Scores and the result stay in the room until the announcement.\n\nTies and challenges. Head judge Ana Ruvelle breaks any tie on the record, in one sentence. Challenges go to the organizing lead, open until the end of the ceremony.",
"per_judge": [
{
"judge": "Ana Ruvelle",
"line": "Ana Ruvelle judges the All panel, starting at 15:00 with Byte Club first, and sees all 10 teams."
},
{
"judge": "Ben Okwill",
"line": "Ben Okwill judges the All panel, starting at 15:00 with Quiet Hours first, and sees all 10 teams."
},
{
"judge": "Cass Wenmoor",
"line": "Cass Wenmoor judges the All panel, starting at 15:00 with Pathfinders first, and sees all 10 teams."
},
{
"judge": "Dara Millvane",
"line": "Dara Millvane judges the All panel, starting at 15:00 with Room 404 first, sees all 10 teams, and abstains on Kettle."
}
],
"note": "Confirm the invite notes Dara Millvane abstains on Kettle, since the other three judges already cover that team without a replacement."
}
The page's check: kept - nothing to flag
Harbor City Hack (60 teams, sponsor track) - plan
Sampled panel: 60 teams, 12 judges, 4 per team; combination normalize, conflicts registry; 6 of 6 ready.
Input:
{
"task": "plan",
"event": "Event: Harbor City Hack Weekend; format Expo Table; 60 teams expected; judging window 120 min from 13:00; 5 min of judge time per team\nJudges (13): Ines Calder (technical); Jon Abara (technical); Kit Moreau (business); Lena Voss (domain); Mara Quist (technical); Ola Brandt (business); Priya Nand (technical); Rafa Sol (domain); Sana Iqbal (technical); Teo Vance (business); Vera Holm (technical); Wes Hart (domain); Yara Cole (sponsor) tied to sponsor Nimbus\nTracks: General; Best use of Nimbus API funded by Nimbus, contract requires the sponsor to judge it\nBrief's categories: Working demo 30%; Technical execution 30%; Fit to the brief 25%; Presentation 15%; scale 1-5\nJudges can all meet in one room no; judges leave straight after their slot no; per-team feedback promised no; post-event window to write it no\nRecurring event yes; published rules promise a dispute process yes; prize pool large enough to be challenged no; volunteer crew for a pre-screen yes\nTie-break judge: Jury chair Kit Moreau; co-award promised for a divisible prize no; challenges go to the judging lead within 24 hours after the ceremony, before prizes are paid",
"allocation": "Band: sampled (Sampled panel) - W/t = 24 < N 60 <= J x W / t = 288: full coverage is impossible, not merely expensive; k = floor(12 x 120 / (60 x 5)) = 4 judges per team.\nFull-coverage ceiling W/t = 24 teams; triage threshold J x W / t = 288 teams; judges per team k = 4; slack after the visits 20 min\nPanels: 3, slices of 20/20/20 teams\nAllocation map (judge | panel | start-end | teams in visiting order):\nInes Calder (technical) | Panel 1 | 13:00-14:40 | Team 01, Team 02, Team 03, Team 04, Team 05, Team 06, Team 07, Team 08, Team 09, Team 10, Team 11, Team 12, Team 13, Team 14, Team 15, Team 16, Team 17, Team 18, Team 19, Team 20\nPriya Nand (technical) | Panel 1 | 13:00-14:40 | Team 06, Team 07, Team 08, Team 09, Team 10, Team 11, Team 12, Team 13, Team 14, Team 15, Team 16, Team 17, Team 18, Team 19, Team 20, Team 01, Team 02, Team 03, Team 04, Team 05\nKit Moreau (business) | Panel 1 | 13:00-14:40 | Team 11, Team 12, Team 13, Team 14, Team 15, Team 16, Team 17, Team 18, Team 19, Team 20, Team 01, Team 02, Team 03, Team 04, Team 05, Team 06, Team 07, Team 08, Team 09, Team 10\nLena Voss (domain) | Panel 1 | 13:00-14:40 | Team 16, Team 17, Team 18, Team 19, Team 20, Team 01, Team 02, Team 03, Team 04, Team 05, Team 06, Team 07, Team 08, Team 09, Team 10, Team 11, Team 12, Team 13, Team 14, Team 15\nJon Abara (technical) | Panel 2 | 13:00-14:40 | Team 21, Team 22, Team 23, Team 24, Team 25, Team 26, Team 27, Team 28, Team 29, Team 30, Team 31, Team 32, Team 33, Team 34, Team 35, Team 36, Team 37, Team 38, Team 39, Team 40\nSana Iqbal (technical) | Panel 2 | 13:00-14:40 | Team 26, Team 27, Team 28, Team 29, Team 30, Team 31, Team 32, Team 33, Team 34, Team 35, Team 36, Team 37, Team 38, Team 39, Team 40, Team 21, Team 22, Team 23, Team 24, Team 25\nOla Brandt (business) | Panel 2 | 13:00-14:40 | Team 31, Team 32, Team 33, Team 34, Team 35, Team 36, Team 37, Team 38, Team 39, Team 40, Team 21, Team 22, Team 23, Team 24, Team 25, Team 26, Team 27, Team 28, Team 29, Team 30\nRafa Sol (domain) | Panel 2 | 13:00-14:40 | Team 36, Team 37, Team 38, Team 39, Team 40, Team 21, Team 22, Team 23, Team 24, Team 25, Team 26, Team 27, Team 28, Team 29, Team 30, Team 31, Team 32, Team 33, Team 34, Team 35\nMara Quist (technical) | Panel 3 | 13:00-14:40 | Team 41, Team 42, Team 43, Team 44, Team 45, Team 46, Team 47, Team 48, Team 49, Team 50, Team 51, Team 52, Team 53, Team 54, Team 55, Team 56, Team 57, Team 58, Team 59, Team 60\nVera Holm (technical) | Panel 3 | 13:00-14:40 | Team 46, Team 47, Team 48, Team 49, Team 50, Team 51, Team 52, Team 53, Team 54, Team 55, Team 56, Team 57, Team 58, Team 59, Team 60, Team 41, Team 42, Team 43, Team 44, Team 45\nTeo Vance (business) | Panel 3 | 13:00-14:40 | Team 51, Team 52, Team 53, Team 54, Team 55, Team 56, Team 57, Team 58, Team 59, Team 60, Team 41, Team 42, Team 43, Team 44, Team 45, Team 46, Team 47, Team 48, Team 49, Team 50\nWes Hart (domain) | Panel 3 | 13:00-14:40 | Team 56, Team 57, Team 58, Team 59, Team 60, Team 41, Team 42, Team 43, Team 44, Team 45, Team 46, Team 47, Team 48, Team 49, Team 50, Team 51, Team 52, Team 53, Team 54, Team 55\nSponsor track Best use of Nimbus API: Yara Cole | sponsor panel | 13:00-14:15 | 15 teams, only these: Team 04, Team 08, Team 12, Team 16, Team 20, Team 24, Team 28, Team 32, Team 36, Team 40, Team 44, Team 48, Team 52, Team 56, Team 60",
"facts": "Rubric: weighted criteria; scale 1-5 per criterion; weights add to 100\nCombination rule: normalize - Per-judge normalization. Each judge scores a slice of the field, so a harsh judge's constant lands on that slice only - convert each judge's weighted totals to z-scores within their own slice before averaging.\nConflict rule: registry - Written policy with disclosure registry and escalation path. A recurring event whose published rules already promise a dispute process - ties declared in writing, a register, a named arbiter.\nTie-break ladder: 1. Highest score on Working demo, the highest-weighted criterion 2. Highest count of judges who placed the project first among the teams they personally saw 3. Jury chair Kit Moreau decides, on the record, in one sentence 4. No co-award - the published rules do not promise a divisible prize\nRefused: re-scoring after the demos, and any coin flip or random draw\nReadiness gate (6 of 6): Criteria with weights written; The scoring scale written; The allocation map written; The conflict rule and its recusal fallback written; The tie-break ladder written; The contest path written\nSignals to record: Judges who finished their slice inside the window (self-set); Teams that got fewer judges than the map promised (self-set); Recusals that fired (self-set)\nFindings (5):\n[info] optimistic (allocation): Expo and science-fair minutes allow little or nothing for walking and notes - every ceiling here is an optimistic upper bound, not a capacity promise.\n[check] sponsor_judge (conflict): The signed agreement for Best use of Nimbus API requires Yara Cole to judge it: keep them on that track only and off the overall prize, pair them with at least one neutral judge, check the track rank survives dropping their scores, and disclose the panel in the published rules before submissions close.\n[check] top_tie (ties): Two criteria share the top weight, so the first tie-break rung cannot separate on one criterion - weights that cannot break a tie were the wrong weights.\n[info] coi_floor (conflict): A sponsor funds a track prize, so 'no formal process' is off the menu.\n[info] gate (readiness): Readiness: 6 of 6 written before scoring opens."
}
Reply:
{
"task": "plan",
"summary": "The plan uses a sampled panel with 4 judges per team across 60 teams; first settle how Yara Cole's sponsor-track role stays separate from the overall prize.",
"allocation": {
"band": "sampled",
"per_team": 4,
"why": "Full coverage tops out at 24 teams and triage begins past 288, so with 60 teams the panel is sampled at floor(12 x 120 / (60 x 5)), which equals 4 judges per team.",
"map_note": "Each of the 3 panels covers a slice of 20 teams with judges visiting in a staggered rotation so every team is seen by multiple judges, leaving 20 minutes of slack inside the 120-minute window."
},
"rubric": {
"criteria": [
{
"name": "Working demo",
"weight": 30,
"looks_for": "Whether the product actually runs live for the judge rather than being described secondhand."
},
{
"name": "Technical execution",
"weight": 30,
"looks_for": "How sound the technical build is for what could reasonably be built in a hackathon."
},
{
"name": "Fit to the brief",
"weight": 25,
"looks_for": "How closely the project matches the stated brief and track."
},
{
"name": "Presentation",
"weight": 15,
"looks_for": "How clearly the team explains the project in the time they have."
}
],
"scale": "1-5 per criterion",
"comments": "No written comment is required, since judging happens live during the expo and neither per-team feedback nor a writing window has been promised."
},
"combination": {
"rule": "normalize",
"why": "Each judge only scores a slice of the field under the sampled panel, so their weighted totals are converted to z-scores within their own slice before averaging, keeping a harsh or lenient judge's bias from skewing comparisons across slices."
},
"conflicts": {
"rule": "registry",
"fallback": "When a judge recuses from a team, the recusal is logged in the written disclosure register and escalated to the judging lead, and the team is scored by its remaining assigned judges rather than the recusing judge.",
"sponsor": "Yara Cole, tied to sponsor Nimbus, judges the Best use of Nimbus API track only, under the sponsor's contract requirement, and does not score the overall prize."
},
"ties": [
"Highest score on Working demo, the highest-weighted criterion.",
"Most judges who placed the project first among the teams they personally saw.",
"Jury chair Kit Moreau decides, on the record, in one sentence.",
"No co-award, since the published rules do not promise a divisible prize."
],
"announcement": {
"plan": "Winners across all tracks, including the sponsor track, are collected before the ceremony and announced from slides, with no live demos while scoring is still being finalized.",
"contest": "Challenges go to the judging lead within 24 hours after the ceremony and must be resolved before prizes are paid."
},
"signals": [
"Judges who finished their slice inside the 120-minute window - if this slips often, add judges or panels next time rather than shortening the slot.",
"Teams that got fewer judges than the map promised - if this recurs, add a buffer judge to each panel next time.",
"Recusals that fired - if sponsor ties cause frequent recusals, recruit sponsor-neutral judges earlier next time."
],
"caveats": [
"Yara Cole must judge the Nimbus track only, under contract: keep them off the overall prize, pair a neutral judge with them, verify the track rank without their scores, and disclose the panel before submissions close.",
"Working demo and Technical execution are tied at the top weight of 30, so the first tie-break rung alone cannot separate them."
]
}
The page's check: kept - nothing to flag
Harbor City Hack (60 teams, sponsor track) - brief
Sampled panel: 60 teams, 12 judges, 4 per team; combination normalize, conflicts registry; 6 of 6 ready.
Input:
{
"task": "brief",
"event": "Event: Harbor City Hack Weekend; format Expo Table; 60 teams expected; judging window 120 min from 13:00; 5 min of judge time per team\nJudges (13): Ines Calder (technical); Jon Abara (technical); Kit Moreau (business); Lena Voss (domain); Mara Quist (technical); Ola Brandt (business); Priya Nand (technical); Rafa Sol (domain); Sana Iqbal (technical); Teo Vance (business); Vera Holm (technical); Wes Hart (domain); Yara Cole (sponsor) tied to sponsor Nimbus\nTracks: General; Best use of Nimbus API funded by Nimbus, contract requires the sponsor to judge it\nBrief's categories: Working demo 30%; Technical execution 30%; Fit to the brief 25%; Presentation 15%; scale 1-5\nJudges can all meet in one room no; judges leave straight after their slot no; per-team feedback promised no; post-event window to write it no\nRecurring event yes; published rules promise a dispute process yes; prize pool large enough to be challenged no; volunteer crew for a pre-screen yes\nTie-break judge: Jury chair Kit Moreau; co-award promised for a divisible prize no; challenges go to the judging lead within 24 hours after the ceremony, before prizes are paid",
"allocation": "Band: sampled (Sampled panel) - W/t = 24 < N 60 <= J x W / t = 288: full coverage is impossible, not merely expensive; k = floor(12 x 120 / (60 x 5)) = 4 judges per team.\nFull-coverage ceiling W/t = 24 teams; triage threshold J x W / t = 288 teams; judges per team k = 4; slack after the visits 20 min\nPanels: 3, slices of 20/20/20 teams\nAllocation map (judge | panel | start-end | teams in visiting order):\nInes Calder (technical) | Panel 1 | 13:00-14:40 | Team 01, Team 02, Team 03, Team 04, Team 05, Team 06, Team 07, Team 08, Team 09, Team 10, Team 11, Team 12, Team 13, Team 14, Team 15, Team 16, Team 17, Team 18, Team 19, Team 20\nPriya Nand (technical) | Panel 1 | 13:00-14:40 | Team 06, Team 07, Team 08, Team 09, Team 10, Team 11, Team 12, Team 13, Team 14, Team 15, Team 16, Team 17, Team 18, Team 19, Team 20, Team 01, Team 02, Team 03, Team 04, Team 05\nKit Moreau (business) | Panel 1 | 13:00-14:40 | Team 11, Team 12, Team 13, Team 14, Team 15, Team 16, Team 17, Team 18, Team 19, Team 20, Team 01, Team 02, Team 03, Team 04, Team 05, Team 06, Team 07, Team 08, Team 09, Team 10\nLena Voss (domain) | Panel 1 | 13:00-14:40 | Team 16, Team 17, Team 18, Team 19, Team 20, Team 01, Team 02, Team 03, Team 04, Team 05, Team 06, Team 07, Team 08, Team 09, Team 10, Team 11, Team 12, Team 13, Team 14, Team 15\nJon Abara (technical) | Panel 2 | 13:00-14:40 | Team 21, Team 22, Team 23, Team 24, Team 25, Team 26, Team 27, Team 28, Team 29, Team 30, Team 31, Team 32, Team 33, Team 34, Team 35, Team 36, Team 37, Team 38, Team 39, Team 40\nSana Iqbal (technical) | Panel 2 | 13:00-14:40 | Team 26, Team 27, Team 28, Team 29, Team 30, Team 31, Team 32, Team 33, Team 34, Team 35, Team 36, Team 37, Team 38, Team 39, Team 40, Team 21, Team 22, Team 23, Team 24, Team 25\nOla Brandt (business) | Panel 2 | 13:00-14:40 | Team 31, Team 32, Team 33, Team 34, Team 35, Team 36, Team 37, Team 38, Team 39, Team 40, Team 21, Team 22, Team 23, Team 24, Team 25, Team 26, Team 27, Team 28, Team 29, Team 30\nRafa Sol (domain) | Panel 2 | 13:00-14:40 | Team 36, Team 37, Team 38, Team 39, Team 40, Team 21, Team 22, Team 23, Team 24, Team 25, Team 26, Team 27, Team 28, Team 29, Team 30, Team 31, Team 32, Team 33, Team 34, Team 35\nMara Quist (technical) | Panel 3 | 13:00-14:40 | Team 41, Team 42, Team 43, Team 44, Team 45, Team 46, Team 47, Team 48, Team 49, Team 50, Team 51, Team 52, Team 53, Team 54, Team 55, Team 56, Team 57, Team 58, Team 59, Team 60\nVera Holm (technical) | Panel 3 | 13:00-14:40 | Team 46, Team 47, Team 48, Team 49, Team 50, Team 51, Team 52, Team 53, Team 54, Team 55, Team 56, Team 57, Team 58, Team 59, Team 60, Team 41, Team 42, Team 43, Team 44, Team 45\nTeo Vance (business) | Panel 3 | 13:00-14:40 | Team 51, Team 52, Team 53, Team 54, Team 55, Team 56, Team 57, Team 58, Team 59, Team 60, Team 41, Team 42, Team 43, Team 44, Team 45, Team 46, Team 47, Team 48, Team 49, Team 50\nWes Hart (domain) | Panel 3 | 13:00-14:40 | Team 56, Team 57, Team 58, Team 59, Team 60, Team 41, Team 42, Team 43, Team 44, Team 45, Team 46, Team 47, Team 48, Team 49, Team 50, Team 51, Team 52, Team 53, Team 54, Team 55\nSponsor track Best use of Nimbus API: Yara Cole | sponsor panel | 13:00-14:15 | 15 teams, only these: Team 04, Team 08, Team 12, Team 16, Team 20, Team 24, Team 28, Team 32, Team 36, Team 40, Team 44, Team 48, Team 52, Team 56, Team 60",
"facts": "Rubric: weighted criteria; scale 1-5 per criterion; weights add to 100\nCombination rule: normalize - Per-judge normalization. Each judge scores a slice of the field, so a harsh judge's constant lands on that slice only - convert each judge's weighted totals to z-scores within their own slice before averaging.\nConflict rule: registry - Written policy with disclosure registry and escalation path. A recurring event whose published rules already promise a dispute process - ties declared in writing, a register, a named arbiter.\nTie-break ladder: 1. Highest score on Working demo, the highest-weighted criterion 2. Highest count of judges who placed the project first among the teams they personally saw 3. Jury chair Kit Moreau decides, on the record, in one sentence 4. No co-award - the published rules do not promise a divisible prize\nRefused: re-scoring after the demos, and any coin flip or random draw\nReadiness gate (6 of 6): Criteria with weights written; The scoring scale written; The allocation map written; The conflict rule and its recusal fallback written; The tie-break ladder written; The contest path written\nSignals to record: Judges who finished their slice inside the window (self-set); Teams that got fewer judges than the map promised (self-set); Recusals that fired (self-set)\nFindings (5):\n[info] optimistic (allocation): Expo and science-fair minutes allow little or nothing for walking and notes - every ceiling here is an optimistic upper bound, not a capacity promise.\n[check] sponsor_judge (conflict): The signed agreement for Best use of Nimbus API requires Yara Cole to judge it: keep them on that track only and off the overall prize, pair them with at least one neutral judge, check the track rank survives dropping their scores, and disclose the panel in the published rules before submissions close.\n[check] top_tie (ties): Two criteria share the top weight, so the first tie-break rung cannot separate on one criterion - weights that cannot break a tie were the wrong weights.\n[info] coi_floor (conflict): A sponsor funds a track prize, so 'no formal process' is off the menu.\n[info] gate (readiness): Readiness: 6 of 6 written before scoring opens."
}
Reply:
{
"task": "brief",
"brief": "Criteria. Working demo is 30 percent of the score - look for the product actually running in the slot. Technical execution is 30 percent - look for the engineering behind what you see running. Fit to the brief is 25 percent - look for how closely the project answers the stated challenge. Presentation is 15 percent - look for a clear, focused pitch that makes good use of the slot.\n\nScale. Score each criterion from 1 to 5. For every score, note the one piece of evidence behind it in a short clause, not a paragraph.\n\nConflicts. Abstain rather than score a team you are close to, and tell the judging lead. You judge for the event, not for your employer.\n\nYour slice. Read your line below for your panel, start time, first team and how many teams you see. If you fall behind, tell the judging lead - never shorten the five minutes per team to catch up.\n\nTiming. Arrive an hour before the ceremony for the briefing, and keep each team to five minutes of judge time.\n\nConfidentiality. Scores and the result stay in the room until the announcement.\n\nTies and challenges. Jury chair Kit Moreau breaks any tie the criteria cannot settle, on the record, in one sentence. Any challenge goes to the judging lead within 24 hours after the ceremony, before prizes are paid.",
"per_judge": [
{
"judge": "Ines Calder",
"line": "Ines Calder sits on Panel 1 from 13:00 to 14:40, starts with Team 01, and sees 20 teams in total."
},
{
"judge": "Priya Nand",
"line": "Priya Nand sits on Panel 1 from 13:00 to 14:40, starts with Team 06, and sees 20 teams in total."
},
{
"judge": "Kit Moreau",
"line": "Kit Moreau sits on Panel 1 from 13:00 to 14:40, starts with Team 11, and sees 20 teams in total."
},
{
"judge": "Lena Voss",
"line": "Lena Voss sits on Panel 1 from 13:00 to 14:40, starts with Team 16, and sees 20 teams in total."
},
{
"judge": "Jon Abara",
"line": "Jon Abara sits on Panel 2 from 13:00 to 14:40, starts with Team 21, and sees 20 teams in total."
},
{
"judge": "Sana Iqbal",
"line": "Sana Iqbal sits on Panel 2 from 13:00 to 14:40, starts with Team 26, and sees 20 teams in total."
},
{
"judge": "Ola Brandt",
"line": "Ola Brandt sits on Panel 2 from 13:00 to 14:40, starts with Team 31, and sees 20 teams in total."
},
{
"judge": "Rafa Sol",
"line": "Rafa Sol sits on Panel 2 from 13:00 to 14:40, starts with Team 36, and sees 20 teams in total."
},
{
"judge": "Mara Quist",
"line": "Mara Quist sits on Panel 3 from 13:00 to 14:40, starts with Team 41, and sees 20 teams in total."
},
{
"judge": "Vera Holm",
"line": "Vera Holm sits on Panel 3 from 13:00 to 14:40, starts with Team 46, and sees 20 teams in total."
},
{
"judge": "Teo Vance",
"line": "Teo Vance sits on Panel 3 from 13:00 to 14:40, starts with Team 51, and sees 20 teams in total."
},
{
"judge": "Wes Hart",
"line": "Wes Hart sits on Panel 3 from 13:00 to 14:40, starts with Team 56, and sees 20 teams in total."
},
{
"judge": "Yara Cole",
"line": "Yara Cole judges the sponsor panel from 13:00 to 14:15, starts with Team 04, and sees 15 teams in total."
}
],
"note": "Before sending, confirm Yara Cole's panel is disclosed as Nimbus-only and off the overall prize, with a neutral judge paired alongside them on that track."
}
The page's check: kept - nothing to flag
Northgate AI Summit (30 pitches, 5 judges) - plan
Triage then finals: 30 teams, 5 judges, 5 per team; combination raw, conflicts registry; 4 of 6 ready.
Input:
{
"task": "plan",
"event": "Event: Northgate AI Summit Hackathon; format Stage Pitch; 30 teams expected; judging window 120 min from 14:00; 10 min of judge time per team\nJudges (5): Ada Fenn (technical); Cy Brook (business); Jo Wilde (domain); Mo Reyes (technical); Nell Ober (business)\nTracks: Grand prize; Best agent funded by Orbit Labs\nBrief's categories: Impact 40%; Technical depth 40%; Presentation 20%; scale 1-10\nJudges can all meet in one room yes; judges leave straight after their slot yes; per-team feedback promised no; post-event window to write it no\nRecurring event no; published rules promise a dispute process no; prize pool large enough to be challenged yes; volunteer crew for a pre-screen no\nTie-break judge: not named; co-award promised for a divisible prize no; challenges go to not named within no window set",
"allocation": "Band: triage (Triage then finals) - A stage pitch at 10 min per team reaches 12 teams in 120 min, fewer than 30 - a pass/fail pre-screen (30 min) cuts to 9 finalists, judged with full coverage in the remaining 90 min.\nFull-coverage ceiling W/t = 12 teams; triage threshold J x W / t = 60 teams; judges per team k = 5; slack after the visits 0 min\nAllocation map (judge | panel | start-end | teams in visiting order):\nAda Fenn (technical) | Stage panel | 14:30-16:00 | Finalist 1, Finalist 2, Finalist 3, Finalist 4, Finalist 5, Finalist 6, Finalist 7, Finalist 8, Finalist 9\nCy Brook (business) | Stage panel | 14:30-16:00 | Finalist 1, Finalist 2, Finalist 3, Finalist 4, Finalist 5, Finalist 6, Finalist 7, Finalist 8, Finalist 9\nJo Wilde (domain) | Stage panel | 14:30-16:00 | Finalist 1, Finalist 2, Finalist 3, Finalist 4, Finalist 5, Finalist 6, Finalist 7, Finalist 8, Finalist 9\nMo Reyes (technical) | Stage panel | 14:30-16:00 | Finalist 1, Finalist 2, Finalist 3, Finalist 4, Finalist 5, Finalist 6, Finalist 7, Finalist 8, Finalist 9\nNell Ober (business) | Stage panel | 14:30-16:00 | Finalist 1, Finalist 2, Finalist 3, Finalist 4, Finalist 5, Finalist 6, Finalist 7, Finalist 8, Finalist 9",
"facts": "Rubric: weighted criteria; scale 1-10 per criterion; weights add to 100\nCombination rule: raw - Raw average. Every judge scores every team in the finals, so a harsh judge's constant lands on every total alike and leaves the order untouched - normalizing would be pure overhead.\nConflict rule: registry - Written policy with disclosure registry and escalation path. A prize pool large enough that a losing team will ask who decided - ties declared in writing, a register, a named arbiter.\nTie-break ladder: 1. Highest score on Impact, the highest-weighted criterion 2. Highest count of judges who placed the project first among the teams they personally saw 3. A named tie-break judge decides, on the record, in one sentence 4. No co-award - the published rules do not promise a divisible prize\nRefused: re-scoring after the demos, and any coin flip or random draw\nReadiness gate (4 of 6): Criteria with weights written; The scoring scale written; The allocation map written; The conflict rule and its recusal fallback written; The tie-break ladder MISSING; The contest path MISSING\nSignals to record: Judges who finished their slice inside the window (self-set); Teams that got fewer judges than the map promised (self-set); Recusals that fired (self-set)\nFindings (9):\n[info] triage (allocation): Triage then finals: publish the pre-screen's pass/fail criteria (does it run, does it meet the deliverable format, is it eligible) in the brief before submissions close; record which teams were cut and on which requirement.\n[fix] no_crew (allocation): Triage needs a pass/fail pre-screen and no volunteer crew is confirmed - staff it, lengthen the window, or move judging out of the live window (a format decision upstream).\n[check] no_buffer (allocation): The map spends the whole window - no room for the first team that is not at its table.\n[check] top_tie (ties): Two criteria share the top weight, so the first tie-break rung cannot separate on one criterion - weights that cannot break a tie were the wrong weights.\n[check] no_tie_judge (ties): Name the tie-break judge who decides on the record if the score cascade still ties.\n[check] no_contest (results): Write the contest path: who receives a challenge and the window, closing before prizes change hands. An arithmetic or eligibility error can change a result; a judge's opinion cannot.\n[info] no_consensus (combination): Synchronous consensus is deleted - judges leave straight after their slot.\n[info] coi_floor (conflict): A sponsor funds a track prize, so 'no formal process' is off the menu.\n[check] gate (readiness): Readiness: 4 of 6 written before scoring opens - missing: The tie-break ladder, The contest path."
}
Reply:
{
"task": "plan",
"summary": "This is a triage plan: a 30-minute pre-screen, then full coverage of the 9 finalists by all 5 judges; first, settle the tie-break judge and the contest path.",
"allocation": {
"band": "triage",
"per_team": 5,
"why": "The stage format reaches only 12 of the 30 expected teams in the 120-minute window at 10 minutes per team, so a 30-minute pass/fail pre-screen cuts the field to 9 finalists, who get full coverage from all 5 judges in the remaining 90 minutes.",
"map_note": "The map seats all 5 judges on the Stage panel, each visiting all 9 finalists from 14:30 to 16:00, leaving 0 minutes of slack after the visits."
},
"rubric": {
"criteria": [
{
"name": "Impact",
"weight": 40,
"looks_for": "Whether the problem and audience are real and the demo shows a plausible path to adoption."
},
{
"name": "Technical depth",
"weight": 40,
"looks_for": "Whether the pitch shows what was actually built and how, not just a claim on a slide."
},
{
"name": "Presentation",
"weight": 20,
"looks_for": "Whether the team uses its time on stage clearly and stays inside the slot."
}
],
"scale": "1-10 per criterion",
"comments": "No written per-criterion comment is required, since per-team feedback was not promised and no window to write one was set."
},
"combination": {
"rule": "raw",
"why": "Every judge scores every team in the finals, so a harsh judge's constant lands on every total alike and leaves the order untouched."
},
"conflicts": {
"rule": "registry",
"fallback": "A team that loses judges to recusal is handled under the written policy's registry and escalation path, with the conflict declared in writing and a named arbiter ruling on it.",
"sponsor": ""
},
"ties": [
"Highest score on Impact, the highest-weighted criterion.",
"Highest count of judges who placed the project first among the teams they personally saw.",
"A named tie-break judge decides, on the record, in one sentence.",
"No co-award, since the published rules do not promise a divisible prize."
],
"announcement": {
"plan": "The winners for the Grand prize and the Best agent track funded by Orbit Labs are collected before the ceremony and announced from slides, with no live demos while judging is still concluding.",
"contest": "The contest path is not set: no one is named to receive a challenge and no window is set to close before prizes change hands."
},
"signals": [
"Judges who finished their slice inside the window - if several run over, next edition should add scheduled slack between finalists.",
"Teams that got fewer judges than the map promised - if this happens, next edition should build buffer time into the allocation map.",
"Recusals that fired - if they recur, next edition should assign replacement judges up front for sponsor-tied tracks."
],
"caveats": [
"No volunteer crew is confirmed for the pass/fail pre-screen - staff it, lengthen the window, or move judging out of the live window.",
"Impact and Technical depth share the top weight at 40%, so the first tie-break rung cannot separate on one criterion.",
"No tie-break judge is named to decide on the record if the cascade still ties.",
"No contest path is set - no one is named to receive a challenge and no window closes before prizes change hands."
]
}
The page's check: kept - nothing to flag
Northgate AI Summit (30 pitches, 5 judges) - brief
Triage then finals: 30 teams, 5 judges, 5 per team; combination raw, conflicts registry; 4 of 6 ready.
Input:
{
"task": "brief",
"event": "Event: Northgate AI Summit Hackathon; format Stage Pitch; 30 teams expected; judging window 120 min from 14:00; 10 min of judge time per team\nJudges (5): Ada Fenn (technical); Cy Brook (business); Jo Wilde (domain); Mo Reyes (technical); Nell Ober (business)\nTracks: Grand prize; Best agent funded by Orbit Labs\nBrief's categories: Impact 40%; Technical depth 40%; Presentation 20%; scale 1-10\nJudges can all meet in one room yes; judges leave straight after their slot yes; per-team feedback promised no; post-event window to write it no\nRecurring event no; published rules promise a dispute process no; prize pool large enough to be challenged yes; volunteer crew for a pre-screen no\nTie-break judge: not named; co-award promised for a divisible prize no; challenges go to not named within no window set",
"allocation": "Band: triage (Triage then finals) - A stage pitch at 10 min per team reaches 12 teams in 120 min, fewer than 30 - a pass/fail pre-screen (30 min) cuts to 9 finalists, judged with full coverage in the remaining 90 min.\nFull-coverage ceiling W/t = 12 teams; triage threshold J x W / t = 60 teams; judges per team k = 5; slack after the visits 0 min\nAllocation map (judge | panel | start-end | teams in visiting order):\nAda Fenn (technical) | Stage panel | 14:30-16:00 | Finalist 1, Finalist 2, Finalist 3, Finalist 4, Finalist 5, Finalist 6, Finalist 7, Finalist 8, Finalist 9\nCy Brook (business) | Stage panel | 14:30-16:00 | Finalist 1, Finalist 2, Finalist 3, Finalist 4, Finalist 5, Finalist 6, Finalist 7, Finalist 8, Finalist 9\nJo Wilde (domain) | Stage panel | 14:30-16:00 | Finalist 1, Finalist 2, Finalist 3, Finalist 4, Finalist 5, Finalist 6, Finalist 7, Finalist 8, Finalist 9\nMo Reyes (technical) | Stage panel | 14:30-16:00 | Finalist 1, Finalist 2, Finalist 3, Finalist 4, Finalist 5, Finalist 6, Finalist 7, Finalist 8, Finalist 9\nNell Ober (business) | Stage panel | 14:30-16:00 | Finalist 1, Finalist 2, Finalist 3, Finalist 4, Finalist 5, Finalist 6, Finalist 7, Finalist 8, Finalist 9",
"facts": "Rubric: weighted criteria; scale 1-10 per criterion; weights add to 100\nCombination rule: raw - Raw average. Every judge scores every team in the finals, so a harsh judge's constant lands on every total alike and leaves the order untouched - normalizing would be pure overhead.\nConflict rule: registry - Written policy with disclosure registry and escalation path. A prize pool large enough that a losing team will ask who decided - ties declared in writing, a register, a named arbiter.\nTie-break ladder: 1. Highest score on Impact, the highest-weighted criterion 2. Highest count of judges who placed the project first among the teams they personally saw 3. A named tie-break judge decides, on the record, in one sentence 4. No co-award - the published rules do not promise a divisible prize\nRefused: re-scoring after the demos, and any coin flip or random draw\nReadiness gate (4 of 6): Criteria with weights written; The scoring scale written; The allocation map written; The conflict rule and its recusal fallback written; The tie-break ladder MISSING; The contest path MISSING\nSignals to record: Judges who finished their slice inside the window (self-set); Teams that got fewer judges than the map promised (self-set); Recusals that fired (self-set)\nFindings (9):\n[info] triage (allocation): Triage then finals: publish the pre-screen's pass/fail criteria (does it run, does it meet the deliverable format, is it eligible) in the brief before submissions close; record which teams were cut and on which requirement.\n[fix] no_crew (allocation): Triage needs a pass/fail pre-screen and no volunteer crew is confirmed - staff it, lengthen the window, or move judging out of the live window (a format decision upstream).\n[check] no_buffer (allocation): The map spends the whole window - no room for the first team that is not at its table.\n[check] top_tie (ties): Two criteria share the top weight, so the first tie-break rung cannot separate on one criterion - weights that cannot break a tie were the wrong weights.\n[check] no_tie_judge (ties): Name the tie-break judge who decides on the record if the score cascade still ties.\n[check] no_contest (results): Write the contest path: who receives a challenge and the window, closing before prizes change hands. An arithmetic or eligibility error can change a result; a judge's opinion cannot.\n[info] no_consensus (combination): Synchronous consensus is deleted - judges leave straight after their slot.\n[info] coi_floor (conflict): A sponsor funds a track prize, so 'no formal process' is off the menu.\n[check] gate (readiness): Readiness: 4 of 6 written before scoring opens - missing: The tie-break ladder, The contest path."
}
Reply:
{
"task": "brief",
"brief": "Criteria. Impact is weighted 40 percent - look for whether the problem is real and sized correctly. Technical depth is weighted 40 percent - look for what was actually built in the time given, not just claimed. Presentation is weighted 20 percent - look for whether the pitch makes the work clear inside the slot.\n\nScale. Score each criterion from 1 to 10. Attach one short clause of evidence to each score - not a paragraph of prose.\n\nConflicts. If you are close to a team, abstain rather than score it, and tell the judging lead. You are judging for the event, not for your employer.\n\nYour slice. Read your line below for your panel, start time, first team and team count. If you start falling behind, tell the judging lead - never shorten the 10 minutes per team to catch up.\n\nTiming. Arrive an hour before the ceremony for the briefing - that is a lead time, not a clock time. Each team gets 10 minutes of your time.\n\nConfidentiality. Scores and the result stay inside the room until the organizers announce them.\n\nTies and challenges. If the score cascade still ties, [to be decided: a tie-break judge] decides on the record in one sentence. Challenges go to [to be decided: a contest path].",
"per_judge": [
{
"judge": "Ada Fenn",
"line": "You are on the Stage panel, starting at 14:30, opening with Finalist 1 and seeing all nine finalists, with no recusals on record for you."
},
{
"judge": "Cy Brook",
"line": "You are on the Stage panel, starting at 14:30, opening with Finalist 1 and seeing all nine finalists, with no recusals on record for you."
},
{
"judge": "Jo Wilde",
"line": "You are on the Stage panel, starting at 14:30, opening with Finalist 1 and seeing all nine finalists, with no recusals on record for you."
},
{
"judge": "Mo Reyes",
"line": "You are on the Stage panel, starting at 14:30, opening with Finalist 1 and seeing all nine finalists, with no recusals on record for you."
},
{
"judge": "Nell Ober",
"line": "You are on the Stage panel, starting at 14:30, opening with Finalist 1 and seeing all nine finalists, with no recusals on record for you."
}
],
"note": "Confirm pre-screen staffing before sending the invite - no volunteer crew is confirmed for the triage round, and the judging plan depends on it."
}
The page's check: kept - nothing to flag
The allocation-map CSV for the first sample
judge,type,panel,order,team,start,end
Ana Ruvelle,technical,All,1,Byte Club,15:00,15:05
Ana Ruvelle,technical,All,2,Null Pointers,15:05,15:10
Ana Ruvelle,technical,All,3,Green Grid,15:10,15:15
Ana Ruvelle,technical,All,4,Quiet Hours,15:15,15:20
Ana Ruvelle,technical,All,5,Lab Notes,15:20,15:25
Ana Ruvelle,technical,All,6,Pathfinders,15:25,15:30
Ana Ruvelle,technical,All,7,Open Desk,15:30,15:35
Ana Ruvelle,technical,All,8,Tiny Tides,15:35,15:40
Ana Ruvelle,technical,All,9,Room 404,15:40,15:45
Ana Ruvelle,technical,All,10,Kettle,15:45,15:50
Ben Okwill,business,All,1,Quiet Hours,15:00,15:05
Ben Okwill,business,All,2,Lab Notes,15:05,15:10
Ben Okwill,business,All,3,Pathfinders,15:10,15:15
Ben Okwill,business,All,4,Open Desk,15:15,15:20
Ben Okwill,business,All,5,Tiny Tides,15:20,15:25
Ben Okwill,business,All,6,Room 404,15:25,15:30
Ben Okwill,business,All,7,Kettle,15:30,15:35
Ben Okwill,business,All,8,Byte Club,15:35,15:40
Ben Okwill,business,All,9,Null Pointers,15:40,15:45
Ben Okwill,business,All,10,Green Grid,15:45,15:50
Cass Wenmoor,technical,All,1,Pathfinders,15:00,15:05
Cass Wenmoor,technical,All,2,Open Desk,15:05,15:10
Cass Wenmoor,technical,All,3,Tiny Tides,15:10,15:15
Cass Wenmoor,technical,All,4,Room 404,15:15,15:20
Cass Wenmoor,technical,All,5,Kettle,15:20,15:25
Cass Wenmoor,technical,All,6,Byte Club,15:25,15:30
Cass Wenmoor,technical,All,7,Null Pointers,15:30,15:35
Cass Wenmoor,technical,All,8,Green Grid,15:35,15:40
Cass Wenmoor,technical,All,9,Quiet Hours,15:40,15:45
Cass Wenmoor,technical,All,10,Lab Notes,15:45,15:50
Dara Millvane,domain,All,1,Room 404,15:00,15:05
Dara Millvane,domain,All,2,Kettle,15:05,15:10
Dara Millvane,domain,All,3,Byte Club,15:10,15:15
Dara Millvane,domain,All,4,Null Pointers,15:15,15:20
Dara Millvane,domain,All,5,Green Grid,15:20,15:25
Dara Millvane,domain,All,6,Quiet Hours,15:25,15:30
Dara Millvane,domain,All,7,Lab Notes,15:30,15:35
Dara Millvane,domain,All,8,Pathfinders,15:35,15:40
Dara Millvane,domain,All,9,Open Desk,15:40,15:45
Dara Millvane,domain,All,10,Tiny Tides,15:45,15:50
Build the input yourself
Load the engine in node (global.window = global; require("./jurykit.js"); require("./juryreply.js")), then:
const K = JuryKit, R = JuryReply;
const f = K.form({ name: "My Hack", format: "expo", teams: "60", window: "120", start: "13:00",
judges: fs.readFileSync("judges.txt", "utf8"), categories: "Working demo | 35\nTechnical execution | 25\nFit to the brief | 25\nPresentation | 15" });
// f.band (1 | 2 | 3 | "stage"), f.k, f.fullCap, f.triageCap, f.map: [{judge, panel, start, end, teams}], f.trackMaps, f.recusals
// f.combo (raw | normalize | consensus), f.coi (self | replacement | registry), f.ladder, f.gate, f.findings, f.result (after scores)
K.mapCSV(f) // judge, type, panel, order, team, start, end
const b = K.buildInput(f, "plan"); // or "brief"
const wire = K.toWire(b.input); // POST this to /estimate and /run
// then: const reply = R.normalize(R.parseReply(job.output.output), "plan");
// R.check(reply, b) // {status: kept or flagged, notes}
Get a token for the steps below from /tokens.html.
Step 1 — a tiny client helper
One function that sends JSON and unwraps the response. Paste a personal token from /tokens.html, or leave it empty and mint a guest token in step 2.
BASE="https://api.skillsafe.ai/v1/app-api"
TOKEN="paste-your-token-here" # from https://jury-room.skillsafe.ai/tokens.html
api() { # api METHOD PATH [JSON-BODY]
curl -s -X "$1" "$BASE$2" -H "Content-Type: application/json" \
${TOKEN:+-H "Authorization: Bearer $TOKEN"} ${3:+--data "$3"}
}
import json, urllib.request
BASE = "https://api.skillsafe.ai/v1/app-api"
TOKEN = "paste-your-token-here" # from https://jury-room.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"]
// jury-room.mjs -- Node 18+ (global fetch)
const BASE = "https://api.skillsafe.ai/v1/app-api";
let TOKEN = "paste-your-token-here"; // from https://jury-room.skillsafe.ai/tokens.html
async function api(method, path, body, headers = {}) {
const r = await fetch(BASE + path, { method, headers: { "Content-Type": "application/json",
...(TOKEN ? { Authorization: "Bearer " + TOKEN } : {}), ...headers },
body: body === undefined ? undefined : JSON.stringify(body) });
const env = await r.json();
if (env.error) throw new Error(env.error.code + ": " + env.error.message);
return env.data;
}
package main
import ("bytes"; "encoding/json"; "fmt"; "net/http")
const base = "https://api.skillsafe.ai/v1/app-api"
var token = "paste-your-token-here" // from https://jury-room.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
}
// JuryRoom.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 JuryRoom {
static final String BASE = "https://api.skillsafe.ai/v1/app-api";
static String token = "paste-your-token-here"; // from https://jury-room.skillsafe.ai/tokens.html
static final ObjectMapper M = new ObjectMapper();
static final HttpClient C = HttpClient.newHttpClient();
static JsonNode api(String method, String path, Object body, Map<String, String> headers) throws Exception {
var b = HttpRequest.newBuilder(URI.create(BASE + path)).header("Content-Type", "application/json");
if (!token.isEmpty()) b.header("Authorization", "Bearer " + token);
if (headers != null) headers.forEach(b::header);
b.method(method, body == null ? HttpRequest.BodyPublishers.noBody() : HttpRequest.BodyPublishers.ofString(M.writeValueAsString(body)));
JsonNode env = M.readTree(C.send(b.build(), HttpResponse.BodyHandlers.ofString()).body());
if (env.hasNonNull("error")) throw new RuntimeException(env.get("error").toString());
return env.get("data");
}
}
require "json"
require "net/http"
BASE = "https://api.skillsafe.ai/v1/app-api"
TOKEN = "paste-your-token-here" # from https://jury-room.skillsafe.ai/tokens.html
def api(method, path, body = nil, headers = {})
uri = URI(BASE + path)
req = Net::HTTPGenericRequest.new(method, !body.nil?, true, uri.request_uri,
{ "Content-Type" => "application/json" }.merge(TOKEN.empty? ? {} : { "Authorization" => "Bearer #{TOKEN}" }).merge(headers))
req.body = JSON.generate(body) unless body.nil?
env = JSON.parse(Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) }.body)
raise env["error"].to_s if env["error"]
env["data"]
end
<?php
const BASE = "https://api.skillsafe.ai/v1/app-api";
$TOKEN = "paste-your-token-here"; // from https://jury-room.skillsafe.ai/tokens.html
function api(string $method, string $path, $body = null, array $headers = []) {
global $TOKEN;
$h = ["Content-Type: application/json"];
if ($TOKEN) $h[] = "Authorization: Bearer $TOKEN";
foreach ($headers as $k => $v) $h[] = "$k: $v";
$ch = curl_init(BASE . $path);
curl_setopt_array($ch, [CURLOPT_CUSTOMREQUEST => $method, CURLOPT_HTTPHEADER => $h, CURLOPT_RETURNTRANSFER => true]);
if ($body !== null) curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
$env = json_decode(curl_exec($ch), true);
if (!empty($env["error"])) throw new Exception(json_encode($env["error"]));
return $env["data"];
}
// Program.cs -- .NET 6+ console app
using System.Net.Http.Json; using System.Text.Json; using System.Text.Json.Nodes;
static class Api {
public const string Base = "https://api.skillsafe.ai/v1/app-api";
public static string Token = "paste-your-token-here"; // from https://jury-room.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":"jury-room"}' | 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": "jury-room"})["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: "jury-room" })).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": "jury-room"}, 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", "jury-room"), 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: "jury-room" })["token"])
// a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
$TOKEN = "";
$TOKEN = api("POST", "/guest", ["slug" => "jury-room"])["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 = "jury-room" }))!["token"]!.GetValue<string>();
Step 3 — GET /me
Who the token is (subject_type: user or guest) and the credit balance.
api GET /me | jq '.data | {subject_type, credits}'
me = api("GET", "/me")
print(me["subject_type"], me.get("credits"))
const me = await api("GET", "/me");
console.log(me.subject_type, me.credits);
me, _ := api("GET", "/me", nil, nil)
fmt.Println(me["subject_type"], me["credits"])
JsonNode me = api("GET", "/me", null, null);
System.out.println(me.get("subject_type") + " " + me.get("credits"));
me = api("GET", "/me")
puts [me["subject_type"], me["credits"]].inspect
$me = api("GET", "/me");
echo $me["subject_type"], " ", $me["credits"] ?? "", "\n";
var me = await Api.Call("GET", "/me");
Console.WriteLine($"{me!["subject_type"]} {me["credits"]}");
Step 4 — POST /estimate
Free. The body is the run input itself, the bare object (never wrapped in {"input": ...}). hold_credits is a reservation, not the price; min_credits is what the balance must cover; model_alias reads gpt-terra.
# input.json: the run input object - {"task": "plan", "event": "Event: ...; format Expo Table; 60 teams expected ...", "allocation": "Band: sampled (Sampled panel) - ...", "facts": "Rubric: weighted criteria ..."}
# - see the input table
api POST /estimate "$(cat input.json)" | jq '.data | {hold_credits, min_credits, model, model_alias, markup_bps, warnings}'
inp = json.load(open("input.json")) # {"task": "plan", "event": "Event: ...; format Expo Table; 60 teams expected ...", "allocation": "Band: sampled (Sampled panel) - ...", "facts": "Rubric: weighted criteria ..."}
# - see the input table
est = api("POST", "/estimate", inp)
print(est["hold_credits"], est["min_credits"], est["model"], est.get("warnings"))
import { readFileSync } from "node:fs";
const input = JSON.parse(readFileSync("input.json", "utf8")); // {task: "plan" or "brief", event, allocation, 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 jury-room:<task>:<inputHash>:a<attempt> - the lane is part of the key - and its one automatic reformat retry is a new attempt number, named in the page as one extra run. A plan carries summary, allocation, ties and caveats among its sections; a brief carries brief, per_judge and note. The code below prints either (the plan's ties are plain strings).
KEY="jury-room:plan:$(shasum -a 256 input.json | cut -c1-16):a1"
JOB=$(curl -s -X POST "$BASE/run" -H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" \
-H "Idempotency-Key: $KEY" --data @input.json | jq -r '.data.job_id')
while :; do
J=$(api GET "/jobs/$JOB"); S=$(echo "$J" | jq -r '.data.status')
case "$S" in succeeded|failed|cancelled) break;; esac; sleep 2
done
echo "$J" | jq -r '.data.output.output' > reply.json # one JSON object, the reply contract
echo "$J" | jq '.data | {charged_credits, truncated}'
jq -r '.summary // .format, .note // .brief, ((.ties // .per_judge)[] | "\(.judge // .judge): \(.line // .line)")' reply.json
jq -r '.caveats // .note // empty' reply.json # caveats (plan) or the note for the judging lead (brief)
import hashlib, time
key = "jury-room:" + inp["task"] + ":" + hashlib.sha256(json.dumps(inp).encode()).hexdigest()[:16] + ":a1"
job = api("POST", "/run", inp, {"Idempotency-Key": key})
while True:
j = api("GET", "/jobs/" + job["job_id"])
if j["status"] in ("succeeded", "failed", "cancelled"):
break
time.sleep(2)
reply = json.loads(j["output"]["output"]) # the reply as one JSON object
print(j["charged_credits"], j.get("truncated"), reply.get("summary"))
print(reply.get("note") or reply.get("brief", ""))
for s in reply.get("ties") or reply.get("per_judge") or []:
print(s.get("judge", s.get("judge")), "-", s.get("line", s.get("line")))
if reply.get("caveats"):
print("caveats:", reply["caveats"])
import { createHash } from "node:crypto";
const key = `jury-room:${input.task}:${createHash("sha256").update(JSON.stringify(input)).digest("hex").slice(0, 16)}:a1`;
const job = await api("POST", "/run", input, { "Idempotency-Key": key });
let j;
do { await new Promise((r) => setTimeout(r, 2000)); j = await api("GET", "/jobs/" + job.job_id); }
while (!["succeeded", "failed", "cancelled"].includes(j.status));
const reply = JSON.parse(j.output.output);
console.log(j.charged_credits, j.truncated, reply.summary);
console.log(reply.note ?? reply.brief);
for (const s of reply.ties || reply.per_judge || []) console.log(s.judge ?? s.judge, "-", s.line ?? s.line);
if (reply.caveats) console.log("caveats:", reply.caveats);
sum := sha256.Sum256(raw) // add "crypto/sha256", "encoding/hex", "time" to the imports
key := "jury-room:" + input["task"].(string) + ":" + hex.EncodeToString(sum[:8]) + ":a1"
job, _ := api("POST", "/run", input, map[string]string{"Idempotency-Key": key})
var j map[string]any
for {
time.Sleep(2 * time.Second)
j, _ = api("GET", "/jobs/"+job["job_id"].(string), nil, nil)
if s := j["status"]; s == "succeeded" || s == "failed" || s == "cancelled" { break }
}
var reply map[string]any
json.Unmarshal([]byte(j["output"].(map[string]any)["output"].(string)), &reply)
items, _ := reply["ties"].([]any) // a brief lists "per_judge" instead
fmt.Println(j["charged_credits"], j["truncated"], reply["summary"])
fmt.Println(reply["note"], reply["brief"])
for _, x := range items {
c := x.(map[string]any)
fmt.Println(c["judge"], "-", c["status"])
}
if fix, ok := reply["caveats"].(string); ok && fix != "" {
fmt.Println("caveats:", fix)
}
var digest = java.security.MessageDigest.getInstance("SHA-256").digest(M.writeValueAsBytes(input));
String key = "jury-room:" + input.get("task") + ":" + HexFormat.of().formatHex(digest, 0, 8) + ":a1";
JsonNode job = api("POST", "/run", input, Map.of("Idempotency-Key", key)), j;
do { Thread.sleep(2000); j = api("GET", "/jobs/" + job.get("job_id").asText(), null, null); }
while (!Set.of("succeeded", "failed", "cancelled").contains(j.get("status").asText()));
JsonNode reply = M.readTree(j.get("output").get("output").asText());
System.out.println(j.get("charged_credits") + " " + reply.get("summary"));
System.out.println(reply.path("note").asText(reply.path("brief").asText()));
for (JsonNode s : reply.has("ties") ? reply.path("ties") : reply.path("per_judge")) System.out.println(s.path("judge").asText(s.path("judge").asText()) + " - " + s.path("status").asText(s.path("line").asText()));
if (!reply.path("caveats").asText().isEmpty()) System.out.println("caveats: " + reply.get("caveats").asText());
require "digest"
key = "jury-room:#{input["task"]}:#{Digest::SHA256.hexdigest(JSON.generate(input))[0, 16]}:a1"
job = api("POST", "/run", input, { "Idempotency-Key" => key })
j = nil
loop do
sleep 2
j = api("GET", "/jobs/#{job["job_id"]}")
break if %w[succeeded failed cancelled].include?(j["status"])
end
reply = JSON.parse(j["output"]["output"])
puts [j["charged_credits"], j["truncated"], reply["summary"]].inspect
puts reply["note"] || reply["brief"]
(reply["ties"] || reply["per_judge"] || []).each { |s| puts "#{s["judge"] || s["judge"]} - #{s["line"] || s["line"]}" }
puts "caveats: #{reply["caveats"]}" unless reply["caveats"].to_s.empty?
$key = "jury-room:" . $input["task"] . ":" . substr(hash("sha256", json_encode($input)), 0, 16) . ":a1";
$job = api("POST", "/run", $input, ["Idempotency-Key" => $key]);
do { sleep(2); $j = api("GET", "/jobs/" . $job["job_id"]); }
while (!in_array($j["status"], ["succeeded", "failed", "cancelled"]));
$reply = json_decode($j["output"]["output"], true);
echo $j["charged_credits"], " ", ($reply["summary"] ?? ""), "\n";
echo $reply["note"] ?? $reply["brief"] ?? "", "\n";
foreach ($reply["ties"] ?? $reply["per_judge"] ?? [] as $s) echo $s["judge"] ?? $s["judge"], " - ", $s["line"] ?? $s["line"], "\n";
if (!empty($reply["caveats"])) echo "caveats: ", $reply["caveats"], "\n";
var digest = System.Security.Cryptography.SHA256.HashData(System.Text.Encoding.UTF8.GetBytes(input.ToJsonString()));
var key = $"jury-room:{input["task"]}:{Convert.ToHexString(digest, 0, 8).ToLowerInvariant()}:a1";
var job = await Api.Call("POST", "/run", input, new() { ["Idempotency-Key"] = key });
JsonNode j;
do { await Task.Delay(2000); j = (await Api.Call("GET", "/jobs/" + job!["job_id"]))!; }
while (!new[] { "succeeded", "failed", "cancelled" }.Contains(j["status"]!.GetValue<string>()));
var reply = JsonNode.Parse(j["output"]!["output"]!.GetValue<string>());
Console.WriteLine($"{j["charged_credits"]} {reply!["summary"]}");
Console.WriteLine($"{reply["note"] ?? reply["brief"]}");
foreach (var s in (reply["ties"] ?? reply["per_judge"]) as JsonArray ?? new JsonArray()) Console.WriteLine($"{s!["judge"] ?? s["judge"]} - {s["line"] ?? s["line"]}");
if (reply["caveats"] is JsonNode fix && fix.GetValue<string>() != "") Console.WriteLine("caveats: " + fix);
Step 6 — POST /run-stream
The same run as Server-Sent Events, with the same input.json. Browsers receive progress ticks rather than text deltas; the final done event carries the reply.
# Server-Sent Events: tick/delta events while the job runs, then a final event with the job
curl -N -s -X POST "$BASE/run-stream" -H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" \
-H "Idempotency-Key: jury-room:plan:$(shasum -a 256 input.json | cut -c1-16):a1" --data @input.json
rev = json.load(open("input.json")) # the same run input as input.json: {"task": "plan", "event": "Event: ...; format Expo Table; 60 teams expected ...", "allocation": "Band: sampled (Sampled panel) - ...", "facts": "Rubric: weighted criteria ..."}
# - 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": "jury-room:plan:" + hashlib.sha256(json.dumps(rev).encode()).hexdigest()[:16] + ":a1"})
with urllib.request.urlopen(req) as r:
for line in r:
line = line.decode().strip()
if line.startswith("data:"):
print(line[5:].strip()[:200])
const rev = JSON.parse(readFileSync("input.json", "utf8"));
const r = await fetch(BASE + "/run-stream", { method: "POST", body: JSON.stringify(rev), headers: {
"Content-Type": "application/json", Authorization: "Bearer " + TOKEN,
"Idempotency-Key": `jury-room:plan:${createHash("sha256").update(JSON.stringify(rev)).digest("hex").slice(0, 16)}:a1` } });
const dec = new TextDecoder();
for await (const chunk of r.body) for (const line of dec.decode(chunk).split("\n"))
if (line.startsWith("data:")) console.log(line.slice(5).trim().slice(0, 200));
rraw, _ := os.ReadFile("input.json") // add "bufio", "strings" to the imports
rs := sha256.Sum256(rraw)
req, _ := http.NewRequest("POST", base+"/run-stream", bytes.NewReader(rraw))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Idempotency-Key", "jury-room:plan:"+hex.EncodeToString(rs[:8])+":a1")
res, _ := http.DefaultClient.Do(req)
sc := bufio.NewScanner(res.Body)
for sc.Scan() { if l := sc.Text(); strings.HasPrefix(l, "data:") { fmt.Println(l[5:]) } }
String rev = java.nio.file.Files.readString(java.nio.file.Path.of("input.json"));
var rd = java.security.MessageDigest.getInstance("SHA-256").digest(rev.getBytes());
var sreq = HttpRequest.newBuilder(URI.create(BASE + "/run-stream")).header("Content-Type", "application/json")
.header("Authorization", "Bearer " + token).header("Idempotency-Key", "jury-room:plan:" + HexFormat.of().formatHex(rd, 0, 8) + ":a1")
.POST(HttpRequest.BodyPublishers.ofString(rev)).build();
C.send(sreq, HttpResponse.BodyHandlers.ofLines()).body().filter(l -> l.startsWith("data:")).forEach(System.out::println);
rev = File.read("input.json")
uri = URI(BASE + "/run-stream")
req = Net::HTTP::Post.new(uri, "Content-Type" => "application/json", "Authorization" => "Bearer #{TOKEN}",
"Idempotency-Key" => "jury-room:plan:#{Digest::SHA256.hexdigest(rev)[0, 16]}:a1")
req.body = rev
Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |h|
h.request(req) { |res| res.read_body { |chunk| chunk.each_line { |l| puts l if l.start_with?("data:") } } }
end
$rev = file_get_contents("input.json");
$ch = curl_init(BASE . "/run-stream");
curl_setopt_array($ch, [CURLOPT_POST => true, CURLOPT_POSTFIELDS => $rev, CURLOPT_HTTPHEADER => [
"Content-Type: application/json", "Authorization: Bearer $TOKEN",
"Idempotency-Key: jury-room:plan:" . substr(hash("sha256", $rev), 0, 16) . ":a1"],
CURLOPT_WRITEFUNCTION => function ($ch, $chunk) { foreach (explode("\n", $chunk) as $l) if (str_starts_with($l, "data:")) echo $l, "\n"; return strlen($chunk); }]);
curl_exec($ch);
var rev = File.ReadAllText("input.json");
var rd = System.Security.Cryptography.SHA256.HashData(System.Text.Encoding.UTF8.GetBytes(rev));
var sreq = new HttpRequestMessage(HttpMethod.Post, Api.Base + "/run-stream") { Content = new StringContent(rev, System.Text.Encoding.UTF8, "application/json") };
sreq.Headers.Add("Authorization", "Bearer " + Api.Token);
sreq.Headers.Add("Idempotency-Key", $"jury-room:plan:{Convert.ToHexString(rd, 0, 8).ToLowerInvariant()}:a1");
using var http = new HttpClient();
using var res = await http.SendAsync(sreq, HttpCompletionOption.ResponseHeadersRead);
using var rdr = new StreamReader(await res.Content.ReadAsStreamAsync());
for (string? l; (l = await rdr.ReadLineAsync()) != null;) if (l.StartsWith("data:")) Console.WriteLine(l);