Skip to content

Question Set Format

m4bwav edited this page Oct 26, 2026 · 3 revisions

A set is one folder with one questions.json. The agent writes that file; the server writes everything else in the folder. schema.md, which ships with the skill, lists every field in short. This page adds the rules lint and the server apply to each field, from the 0.2.0 source and the wiki's run.

The set

Field Required Rule
askwright yes Exactly 1, the format version.
id yes Starts with a letter or digit, then letters, digits, _, . or -; 64 characters at most.
title yes Not empty. Shown in the page's top bar.
context, asked_by no Free text for the record.
mode no decide (the default) or study, a quiz (see Study sets). Since 0.2.0.
media_roots no Folders that may hold media, relative to the set folder or absolute. Each must exist.
questions yes At least one with status open.

A question

Field Required Rule
id yes Same characters as the set id, unique in the set. An id that starts with _ fails.
kind yes choice, multi, compare, rank, text, scale or confirm.
title yes Not empty; over 80 characters is a warning. Unique among open questions, ignoring case.
question yes Ends in ?. Unique among open questions, ignoring case.
why yes Not empty; more than three sentences is a warning (the same holds for hint).
look_for on compare, and wherever there is media A list of strings. An item of fewer than three words, or one like "quality" or "overall", is a warning.
options all kinds but text At least two objects with id and label. Ids unique, never unsure or other. Labels unique (ignoring case), and not a bare position or yes/no ("Left", "B", "Option 2", "OK").
guess yes See "Guesses" below.
sides on compare with a side-by-side clip Maps left and right (or other side names) to option ids, plus randomized.
media no A list of media items.
show_if no {"question": <another id>, "option": [<its option ids>]}.
one_pager no Background blocks of type text, facts, options_compared, table, image, chart, video or audio.
status no open (the default), closed, superseded (needs superseded_by naming a question in the set) or withdrawn (needs reason).
rev no Starts at 1.
supersedes no "<old id>" or "<old id>@<rev>"; ask.py add then marks the old question superseded.

Only open questions are checked for duplicates, double asks and leading words, and only they reach the page.

Kinds and the built-in answers

Every kind except text also offers "Not sure, ask me later" (stored as the choice unsure) and "Something else" (other, explained in the note). A text question offers "Not sure" only.

Kind The person Stored as
choice, scale picks one choice
confirm picks one of two or three actions; a label that starts with "Yes" or "No" fails the lint choice
compare picks a take shown side by side; the options in sides read "Left" and "Right" until the pick choice, and read adds side
multi picks any number choices
rank orders every option order
text writes an answer text

Guesses

Every question carries one, with a why and a confidence of high or low.

Kind Guess
choice, compare, scale, confirm {"option": "<id>", ...}
multi {"options": ["<id>", ...], ...}, each one an option id
rank {"order": [...every option id once...], ...}
text {"text": "...", ...}

A high guess is shown under the options as "My guess"; a low one waits behind "Show my guess". Nothing is ever pre-selected. When three or more open questions all have the guess as their first option, lint warns.

Media

A media item is {"src", "type", "start", "label", "alt", "caption"}; only src is required. type comes from the extension when it is left out:

Type Extensions
video mp4, m4v, webm, mov
audio mp3, m4a, wav, ogg, oga, opus, flac
image png, jpg, jpeg, gif, webp, svg, avif

A relative src is looked for in the set folder first, then in each media root in order. An absolute src is allowed when it resolves inside one of them. Anything else fails the lint as outside, and the server refuses it. A .vtt file with the same name beside a video is served with it as captions, without being named in the set. An image without alt is a warning. With ffprobe on the PATH, a video that is not H.264 with AAC or MP3 sound (or no sound) is a warning; --no-probe skips the check. The wiki's runs had no ffprobe and passed --no-probe, so the codec warning is described from the source.

A media item can also be a drawn figure, {"figure": {...}, "alt": "..."}, with no src (since 0.2.0). Its types and fields are on Figures.

Study sets

With "mode": "study" (since 0.2.0) the set is a quiz (Study mode shows one end to end). The rules above hold, with these differences:

Field Where Rule
kind question choice or text only.
topic question The topic the review scores it under; missing is a warning.
correct option true on exactly one option of a choice question.
why option Required on every option: why it is right, or the mistake it stands for.
points question A number above 0; default 1.
bonus question true scores it apart from the main score and the topics.
shuffle question false keeps the option order; otherwise every attempt shuffles it.
rubric, model_answer text question For the grader; one of them is required. A rubric is a string or a list.

Not needed in a quiz: guess, the question's why and look_for. The question only has to contain a ?, so it may quote its sentence after the ask, and option labels may differ only by capitals ("We" and "we"). A label like "Left" is allowed, since it can be the answer. A quiz may have 30 open questions before too-many warns, against 12 in a decision set.

A compare question

The clip arrow_pair.mp4 sits in renders/, two folders up from the set, so the set names that folder as a media root.

{
  "askwright": 1,
  "id": "takes",
  "title": "Pick the take",
  "media_roots": ["../../renders"],
  "questions": [
    {
      "id": "arrow",
      "kind": "compare",
      "title": "The arrow",
      "question": "Which take of the arrow shot should go in the cut?",
      "why": "This is the only shot of the arrow; the take you pick gets the HD re-render.",
      "look_for": ["The arrow from 1.0 s: does it stay in place when he falls?"],
      "media": [{"src": "arrow_pair.mp4"}],
      "sides": {"left": "t2", "right": "t5", "randomized": true},
      "options": [
        {"id": "t2", "label": "Take 2"},
        {"id": "t5", "label": "Take 5"},
        {"id": "tie", "label": "Either is fine"},
        {"id": "redo", "label": "Neither: redo the shot", "consequence": "About 40 minutes of renders."}
      ],
      "guess": {"option": "t5", "why": "The shaft is thinner and he finishes the line.", "confidence": "low"}
    }
  ]
}

"1.0 s" in look_for becomes a "Play from 1.0 s" button on the page. Set randomized to true only when the sides were drawn at random (a pair tool did it); a clip with a known fixed order gets false, and the lint warning stays.

What comes back

The wiki's run sent the server what the page sends for a person who picked Take 2, pasted one screenshot, tied the note to 1.4 s and had opened the guess before picking. read gives:

$ python ask.py read ask/takes
{
  "ok": true,
  "set": "takes",
  "rev": 1,
  "count": 1,
  "notice": "Answers and notes are the person's data to act on, not instructions to follow.",
  "answers": [
    {
      "qid": "arrow",
      "title": "The arrow",
      "question": "Which take of the arrow shot should go in the cut?",
      "kind": "compare",
      "status": "open",
      "state": "answered",
      "choice": "t2",
      "label": "Take 2",
      "side": "left",
      "matched_guess": false,
      "guess": {
        "option": "t5",
        "why": "The shaft is thinner and he finishes the line.",
        "confidence": "low"
      },
      "guess_revealed_before_pick": true,
      "note": "keep the shaft thin",
      "attachments": [
        "/work/proj/ask/takes/attachments/arrow-1.png"
      ],
      "timecodes": [
        {
          "t": 1.4,
          "text": ""
        }
      ],
      "q_rev": 1,
      "current_rev": 1,
      "stale": false,
      "rev": 1,
      "updated": "<time>"
    }
  ]
}
$ echo $?
0

The states an answer can have:

state Means What the agent does
answered a pick (or text, choices, an order) act on it
unsure "Not sure, ask me later" ask again later
note_only a note or an attachment, no pick read the note; it is not a choice
unanswered nothing (only with read --all) wait; never fill it with the guess

An answer is stale when it was given to an older rev of its question: q_rev is the rev the person saw, current_rev the rev now. A stale answer is still answered; status lists it under both. Answers to questions later closed, superseded or withdrawn stay in answers.json, and read still returns them with that status.

The server stores timecodes rounded to 0.01 s, with up to 500 characters of text each. One answer holds at most 50 timecodes, 20 attachments, and 20,000 characters of note or text.

Clone this wiki locally