Skip to content

Fields

A field declares one column. Input fields and output fields share one shape, so what you learn on one side applies to the other.

{
"name": "score", // the column header in the data file
"type": "number", // what the value is — decides which other keys are legal
"display": "Model score", // captions and layout (optional)
// …plus whatever this type carries: min, max, step
}

name is the column header in the data file, matched exactly. Permitted characters:

  • letters, digits, _ and -,
  • inner spaces (model score is fine),
  • but not leading or trailing whitespace,
  • and not . [ ] *.

The two exclusions are deliberate:

  • . [ ] * are reserved for a future rule path grammar (checks[*].toxic) and would be ambiguous in the dotted paths the loader reports errors against.
  • Edge whitespace is excluded because CSV headers are trimmed when read but written verbatim. A padded name would fail to round-trip through Prepare’s join.

Names must be unique within their side. An input id and an output id are fine and unrelated — the connection between them is only ever what fill states.

An input field declares a source column and how to display it. It takes name, type, display and the type’s own constraints — nothing else.

"input": {
"fields": [
{ "name": "id", "type": "text" },
{ "name": "prompt", "type": "text", "display": { "title": "Prompt", "titlePosition": "above" } },
{ "name": "score", "type": "number", "min": 0, "max": 1, "display": "Model score" },
],
}

Constraints on an input field are documentation and coercion guidance, not gates: a value outside min/max still displays. Only a value that cannot be coerced to the type at all is flagged, and even then the file still loads.

A declared input column must exist in the file. A missing one is a blocking error and the file is refused. Extra columns in the file are ignored with a warning.

An output field declares a column to write. It adds four keys:

Key Meaning
fill Where the value comes from. Default user.
widget How it renders. Type-specific default.
required Whether a record is complete without it. Defaults by fill.
shortcut A chord that focuses it.
"output": {
"fields": [
{ "name": "id", "type": "text", "fill": { "kind": "copy" } },
{
"name": "verdict",
"type": "enum",
"widget": "radio",
"shortcut": "mod+1",
"display": "Your verdict",
"choices": [{ "name": "pass" }, { "name": "fail" }],
},
{ "name": "notes", "type": "text", "widget": "textarea", "required": false },
],
}

Unlike input, output constraints are enforced: a value that violates maxLength, min/max or pattern keeps the record out of *-output. That asymmetry is the point — a labeler can fix their own answer, but cannot fix the source data.

Fields are written to the output file in the order they are declared. If a downstream consumer cares about column order, declare them in that order.

InputField · OutputField