---
title: "Structured instructions, options, levels, criteria"
type: concept
tags: [advanced, structure, instructions, criteria, taxonomy]
created: 2026-09-17
updated: 2026-09-17
confidence: high
sources:
  - raw/docs/primitives__advanced.md
  - raw/docs/api.md
jev_version: "jev-1.13.0"
sdk_js: "0.6.0"
summary: "instructions, Choice option descriptions, Score levels and Noul true/false all accept JSON: string, object, array or null (EntryType)."
---

# Structured instructions, options, levels, criteria

> **TL;DR** Every prompt-bearing field of a question is an `EntryType`: `string`, `object`, `array`, or `null`. Use an object when one description carries several kinds of guidance (`what` / `not_for` / `examples`), an array when the instruction is a list of things to check, and a nested object when an option's value is a taxonomy subtree. The field names inside are yours — none are reserved, and the model sees the names along with the values.

## What it is

System One models are trained to understand structure. You do not have to flatten a schema, a taxonomy, or a database row into an English sentence before sending it — put the JSON in the field directly.

## Where structure is allowed

Every one of these fields is an `EntryType` (see [[reference/javascript-sdk-types]]).

| Field | Applies to | Accepted shape |
|---|---|---|
| `instructions` | Choice, Score, Noul | `string`, `object`, `array`, or `null` |
| `criteria` values (option descriptions) | Choice | `string`, `object`, `array`, or `null` |
| `criteria` entries (level descriptions) | Score | `string`, `object`, `array`, or `null` |
| `criteria.true` and `criteria.false` | Noul | `string`, `object`, `array`, or `null` |

Note that raw/docs/api.md types the Choice `criteria` map as `map<string, string | null>` while raw/docs/primitives__advanced.md and the JS SDK's `EntryType` allow objects and arrays as option values too. The advanced page and the SDK type are the broader, more recent statement; the worked examples below use objects and arrays as option values.

## When to structure a question

- **When it helps with clarity.** When a question has multiple parts, putting them in the form of JSON helps because the keys are labeled.
- **When the question needs supporting data.** A schema, a taxonomy, or a database row is already JSON. Use the JSON entirely, or pass in the relevant subfields, instead of serializing them into a string template.

Start with strings. Reach for structure when the model keeps confusing two options or scoring between two levels on inputs you think are clear.

## Structured instructions

One `field` object describes the field being checked, and each question refers to it by key. The same shape drives a Noul that verifies a value, a Choice that picks one from candidates, and two Scores that place a value on a scale.

```json
{
  "state": {
    "source_text": "Invoice #4471 issued March 3, 2026 to Beaver Dam Logistics for $12,840.00, net 30."
  },
  "model": "jev-latest",
  "questions": {
    "invoice_number_is_correct": {
      "type": "noul",
      "instructions": {
        "field": {
          "name": "invoice_number",
          "type": "string",
          "description": "The identifier printed on the invoice."
        },
        "extracted_value": "4471",
        "question": "Does `extracted_value` match the `field` as it appears in `source_text`?"
      }
    },
    "customer_name": {
      "type": "choice",
      "instructions": {
        "field": {
          "name": "customer_name",
          "type": "string",
          "description": "The organization the invoice was issued to."
        },
        "question": "Which option is the value of `field` in `source_text`?"
      },
      "criteria": {
        "Beaver Logistics": null,
        "Dam Logistics": null,
        "Beaver Dam Logistics": null,
        "Beaver": null,
        "Dam": null
      }
    },
    "amount_due": {
      "type": "score",
      "instructions": {
        "field": {
          "name": "amount_due",
          "type": "number",
          "unit": "USD",
          "description": "The total the invoice asks to be paid."
        },
        "question": "How large is the `field` value in `source_text`?"
      },
      "criteria": [
        "Under $1,000",
        "$1,000 to $10,000",
        "$10,000 to $100,000",
        "$100,000 to $1,000,000",
        "Over $1,000,000"
      ]
    },
    "payment_terms": {
      "type": "score",
      "instructions": {
        "field": {
          "name": "payment_terms",
          "type": "integer",
          "unit": "days",
          "description": "Days allowed for payment, from terms such as \"net 30\"."
        },
        "question": "How many days does the `field` in `source_text` allow for payment?"
      },
      "criteria": [
        "Due on receipt",
        "Net 10",
        "Net 30",
        "Net 60",
        "Net 90"
      ]
    }
  }
}
```

In code you could loop over the potential records and build one of these questions per field, all sent in a single call. [[cookbooks/sde-cascade]] does something similar.

Note the pattern: the *value* is extracted into bounded options (a Choice over candidate strings, a Score over bucket ranges) rather than generated — which is exactly the mitigation [[concepts/jaggedness-jev-1-13]] recommends for numbers and generation.

### Arrays in instructions

Use an array when the instruction is a list of things to check or to compare:

```json
"instructions": {
  "question": "Does the claimed sender identity conflict with the sending domain?",
  "compare": ["ticket.sender.display_name", "ticket.sender.email"],
  "focus": "Compare the named organization with the email domain."
}
```

## Structured Choice options

### JSON rubric for boundary clarification

```json
{
  "state": "I ordered the standing desk two weeks ago and tracking still says label created. Was I even charged?",
  "model": "jev-latest",
  "questions": {
    "department": {
      "type": "choice",
      "instructions": {
        "question": "Which team should handle this message?",
        "focus": "Classify the customer's primary request, not every topic mentioned."
      },
      "criteria": {
        "billing": {
          "what": "Charges, invoices, refunds, or subscriptions",
          "not_for": "Order tracking or account access",
          "examples": ["I was charged twice", "Where is my refund?"]
        },
        "orders": {
          "what": "Order status, delivery, cancellation, or returns",
          "not_for": "Charges or account access",
          "examples": ["Where is my package?", "Cancel my order"]
        },
        "account": {
          "what": "Login, password, profile, or security",
          "not_for": "Charges or delivery",
          "examples": ["I can't log in", "Change my email"]
        }
      }
    }
  }
}
```

The example tells the model what each option does and does *not* cover. It sharpens the boundary between options. `question`, `focus`, `what`, `not_for`, `examples` are names you invent — see [[concepts/choice]].

### Walking a taxonomy

To classify into a deep taxonomy, ask one Choice per level and walk the tree in code. At each step the options are the children of the current node, and **each option's value is the child's subtree**. That lets the model see what lives under a branch before committing to it, which matters when the item belongs to a leaf whose name is not obvious from the branch name alone.

```json
{
  "state": "32oz plastic bottle with a flip straw lid. Fits most bike cages.",
  "model": "jev-latest",
  "questions": {
    "department": {
      "type": "choice",
      "instructions": "Which top-level department does this product belong to?",
      "criteria": {
        "Sporting Goods": {
          "Cycling": ["Bike Bottles & Cages", "Bike Lights", "Helmets"],
          "Fitness": ["Yoga Mats", "Resistance Bands"],
          "Outdoor": ["Tents", "Sleeping Bags", "Hydration Packs"]
        },
        "Home & Kitchen": {
          "Drinkware": ["Water Bottles", "Travel Mugs", "Tumblers"],
          "Cookware": ["Pots & Pans", "Bakeware"]
        },
        "Baby & Toddler": ["Sippy Cups", "Bottle Warmers", "Bibs"]
      }
    }
  }
}
```

The bottle plausibly fits under two departments. Showing the subtrees lets the model see that both `Sporting Goods > Cycling > Bike Bottles & Cages` and `Home & Kitchen > Drinkware > Water Bottles` exist, and weigh the listing's emphasis on bike cages against everyday drinkware. The `probabilities` on this answer tell you whether the split is close enough to explore both branches.

Once a department is chosen, ask the next Choice with that department's children as the options and their subtrees as the values, and repeat until you reach a leaf. In code this could be a loop over a nested dict, where each question's `criteria` is simply the current node. [[cookbooks/hierarchical-classification]] shows a similar walk, including a beam search that keeps several candidate paths alive when the probabilities are close.

**Subtrees can get large.** If a branch is too large, trim the value to its direct children and a sample of leaves.

## Structured Score levels

Each entry in a Score `criteria` array can be an object. Use the same field names on every level.

```json
{
  "state": "Fixed the null check in the payment handler. Also refactored the retry loop while I was in there, and bumped the SDK version since the old one had that timeout bug.",
  "model": "jev-latest",
  "questions": {
    "pr_scope": {
      "type": "score",
      "instructions": {
        "question": "How focused is this pull request description on a single change?",
        "note": "Judge the number of independent changes, not the size of any one change."
      },
      "criteria": [
        {
          "summary": "One change, clearly stated",
          "signals": [
            "A single fix or feature",
            "Nothing described as \"also\" or \"while I was in there\""
          ]
        },
        {
          "summary": "One main change plus a small related tweak",
          "signals": [
            "A primary change and one minor adjacent edit",
            "The tweak supports the main change"
          ]
        },
        {
          "summary": "Several independent changes bundled together",
          "signals": [
            "Two or more unrelated fixes or features",
            "Changes that could each be their own PR"
          ]
        }
      ]
    }
  }
}
```

Note that the Score `legend` in the response echoes structured levels back verbatim — see the worked response in [[concepts/score]].

## Structured Noul criteria

Noul `criteria` is optional; when the yes/no boundary is subtle, structured `true` and `false` descriptions let you pin it down with a definition and examples on each side.

```json
{
  "state": {
    "sender": {
      "display_name": "Beaver Dam Builders Ltd.",
      "email": "donotreply@payroll.example"
    },
    "message": "Your Q3 bonus is ready. Reply with your login password so we can verify your identity and release the funds."
  },
  "model": "jev-latest",
  "questions": {
    "requests_credentials": {
      "type": "noul",
      "instructions": {
        "question": "Does the `message` ask the recipient to disclose a sensitive credential?",
        "inspect": "message",
        "focus": "Look for a request to send the credential itself, not a request to change or reset it."
      },
      "criteria": {
        "true": {
          "what": "Asks the recipient to reply with, type, or send a password, PIN, one-time code, or other security sensitive answer",
          "examples": [
            "Reply with your password",
            "Send us the 6-digit code you just received"
          ]
        },
        "false": {
          "what": "No sensitive credential is requested",
          "examples": [
            "Reset your password from the settings page",
            "Your statement is ready"
          ]
        }
      }
    }
  }
}
```

The `focus` line here does the work that [[concepts/jaggedness-jev-1-13]] calls "state the exact condition": it rules out the neighbouring case (reset requests) that a literal reading would otherwise sweep in.

## Gotchas

- **No field names are reserved.** `what`, `not_for`, `examples`, `question`, `focus`, `note`, `signals`, `field`, `inspect`, `compare` are all invented in the docs' examples. Use short names that label what follows, and keep them consistent across sibling options or levels.
- **The model sees the key names.** A badly named key is a badly worded prompt.
- **Structure is not a substitute for a clear instruction.** Adding JSON to a question that is really two questions still hides two judgments in one question.
- **Large subtrees cost tokens and add distractors** — the state and questions share one token budget, and [[concepts/jaggedness-jev-1-13]] warns that irrelevant detail costs accuracy.
- Choice option *names* remain plain strings and are what comes back in `choice` and in the `probabilities` keys, however structured the descriptions are.

## Related

- [[concepts/choice]], [[concepts/score]], [[concepts/noul]] — the per-type contracts
- [[guides/writing-instructions-and-criteria]] — when to escalate from a string to an object
- [[concepts/jaggedness-jev-1-13]] — literal reading, indirection, context rot
- [[reference/http-api]] — declared wire types
- [[reference/javascript-sdk-types]] — `EntryType`, `ChoiceCriteria`, `ScoreCriteria`
- [[cookbooks/hierarchical-classification]], [[cookbooks/sde-cascade]] — structure in production shapes

## Sources

- raw/docs/primitives__advanced.md (https://docs.typesafe.ai/primitives/advanced)
- raw/docs/api.md (https://docs.typesafe.ai/api)
