Skip to main content
A fuzzy yes/no question is sometimes answered better as several narrow ones. A composite judgment asks the engine 2 to 8 narrow yes/no questions, called parts, and combines their answers with weights fitted on your labels. It helps some judgments and not others, and nobody can tell which in advance. So Vainona measures every composite on your labels, against a fairly tuned single question, before it answers anything.

When splitting helps

We tested splitting on two human-labelled datasets. Every number below is accuracy on held-out documents, half of them positive.
  • On clickbait, splitting helped. Every split set beat the tuned single question. The best sets asked whether the post states the news, where a yes means “not clickbait”, the half of the definition the single question left implicit. The one LLM-proposed set without that question scored lowest. With 50 labels, the combiner got within about 2 points of what 1,000 labels gave.
  • On argument quality, it did not. No split set did better than the tuned single question. Every part measured much the same thing as the question itself, so combining them added nothing. What helped was fitting the cut-off: it lifted the single question from 61.1% to 67.8%, a gain of 6.7 points. The split sets also needed about 300 labels just to match the single question.
Splitting pays when the question hides several distinct signals that the engine can see on their own. When it does not, a composite costs more per document and does no better.

Try the threshold recommender first

On argument quality, fitting the cut-off was worth more than any split, and it is cheaper. It costs nothing per document, needs no new version, and takes one call. If your judgment has outcomes, pick a threshold with the recommender first. Reach for a composite when a well-chosen threshold still gets too many documents wrong.

Define the parts

A composite is a bool judgment with parts. Post it like any other definition. On an existing name it is version n+1:
  • 2 to 8 parts, each a narrow yes/no question with a name unique within the judgment.
  • Parts share the judgment’s context recipe and engine. On Jev they go in one request with the judgment’s other questions, and each part counts toward the engine’s limit of questions per request.
  • question is not sent to the engine. It documents what the combination means. The engine is asked the parts.
  • Parts are part of the version. Changing one creates a new version, like any other change to the definition.
  • One level only. A part is a question, never another judgment, so composites never nest.
  • Only bool. There are no composite choice or score judgments, and all parts use the same context recipe.
Good parts are narrow and different from each other. Include one that points the other way, where a yes means the answer is no: on clickbait, “does it state a concrete news fact” was the strongest part in every set that had it.

Activate it, measured on your labels

A composite version is never active when it is created, not even as a judgment’s first version, and activate: true is refused with invalid_request. It cannot answer until its combiner is fitted on your labels. Post labelled examples for the judgment, then activate the version with POST /namespaces/{ns}/judgments/clickbait/activate:
Activation always returns 202 with a shadow job in awaiting_confirm, even with force: true: a composite cannot answer without a combiner, so there is nothing to skip to. The job:
  1. Judges up to 1,000 documents that have outcomes for this judgment, asking each part.
  2. Fits the combiner with 5-fold cross-validation, so every document is scored by a fit that never saw its label.
  3. Scores a baseline the same way: the active version, or for a first version the composite’s question asked on its own. The baseline’s cut-off is fitted by the same folds on the same labels, never left at 0.5, so splitting gets no credit for what a fitted threshold alone would give.
When it is done, report compares them. For the two-part first version of clickbait above:
  • composite and baseline give the cross-validated accuracy with its 95% interval, and the ROC AUC. baseline.threshold is the cut-off fitted for it.
  • difference is the composite’s accuracy minus the baseline’s, with its 95% interval over the same documents.
  • verdict is better only when the composite beats the baseline by more than that interval. Otherwise it is not_better, and the threshold recommender on the baseline is the cheaper fix.
  • parts gives each part’s weight in the combiner fitted on all the documents, on a common scale, so sizes compare. A negative weight means a yes points to false. A part with a weight near 0 adds cost and little else.
  • recompute estimates backfilling every document under the new version. Each part is billed as a judgment, so it is several times a single question’s estimate.
Only a composite report has type, so "type" in report tells it apart from the report of an ordinary version change:
Then decide. POST /jobs/{id}/confirm activates the version with the combiner fitted on all the labelled documents, whatever the verdict. POST /jobs/{id}/cancel leaves the active version as it is. The shadow job is free. Labels. The job needs at least 50 labelled documents, and at least 10 of each answer. With fewer it fails: status is failed, and error starts with insufficient_labels. Post more labels and activate again.

Answers

A composite’s answer has p, the combined probability, and parts, each part’s raw p from the engine:
  • There is no calibrated object. The combiner is already fitted to your outcomes, so there is nothing left to calibrate.
  • combiner says what p came from: outcomes, the labelled documents it was fitted on, and from_previous_epoch.
  • The combiner is fitted per version and per engine epoch, and refitted nightly along with calibration. After a Jev drift, answers under the new epoch use the previous epoch’s combiner, marked "from_previous_epoch": true, until the new epoch has 50 outcomes, at least 10 of each answer, and the nightly refit has run.
  • The calibration report measures the combined p per epoch. For a composite, method, fitted_at and calibrated are null, because there is nothing to calibrate.
The evaluation behind an answer stores each part’s raw p; the combined p is computed when the answer is read. To see what the engine returned, and how long it took, read the evaluation by its id:

Thresholds, filters and ranking

Everything that reads p reads the combined p: thresholds, filters such as ["answers.clickbait.p", "Gte", 0.8], rank_by, and threshold recommendations. A part’s p is in the answer for you to read, but you cannot filter or rank on it.

Suggested parts

Vainona can ask a general-purpose LLM to propose parts from your labelled examples. On clickbait, LLM-proposed sets scored between 75% and 81%, above the hand-written set’s 73.3%, but on argument quality they did no better than one question. A suggestion is a starting point for a composite, not a sign that one will help. It is off by default, because it sends your labelled examples to an LLM provider, which is then a subprocessor, listed in the data processing agreement. An org admin turns on Suggestions in the organization’s settings in the dashboard. Until then the call is refused with forbidden. POST /namespaces/{ns}/judgments/{name}/suggest_parts with the number of parts to propose, 2 to 8:
  • What the LLM sees: the judgment’s question and criteria from its newest version, and up to 30 of the namespace’s labelled examples for the judgment, balanced between true and false and compiled with the judgment’s context recipe. examples lists the documents it saw.
  • It never creates a version. Review the parts, edit or drop any, then post a new version with them. parts has the shape a definition’s parts takes.
  • It needs labels: at least 10 labelled examples of each answer, or it is refused with insufficient_labels.
  • It is free, and limited to 20 calls per judgment and 50 per organization a day (rate_limited, with details.limit, the limit reached, 20 or 50, and details.resets_at, the next midnight UTC). Each call shows the LLM at most 100,000 tokens of examples, so long documents are cut. When the LLM is unavailable it returns engine_unavailable with Retry-After, which the SDKs wait out and retry. A call that fails this way before the LLM ran does not count toward the day’s calls; one that timed out, or failed after the LLM replied, does.
In our tests, sets proposed from different examples were close paraphrases of each other. On clickbait, what separated the strong sets from the weak one was a single part, so read each set for the question it is missing.

Pricing

Each part is billed as a judgment, over the context and that part’s own question. A five-part composite costs five judgment units per document per started 1,000 tokens of compiled context and part question, where a single question costs one. Features cost nothing. The shadow job that measures it is free, like every shadow job, and suggestions are free. See pricing.