Variants
A variant document is a change to a model, written down. It is the input
cwright propose takes beside a baseline: the proposal is applied to produce a
candidate, and the two are then compared exactly as compare compares any pair.
It is a separate document with its own root, not a part of the model. That is what makes a proposal replayable — the same variant can be applied to a baseline that has since moved, and the run record carries the question that was asked rather than only the answer.
name: "a second scanner"
rationale: "litho is the constraint at 94% utilisation"
changes:
- { kind: set_servers, resource: scanner, servers: 2 }
| field | required | what it is |
|---|---|---|
name | yes | What this change is, in the proposer's words. Carried into the run record. |
rationale | no, default "" | Why it was proposed. Free text, never interpreted. |
changes | yes | One or more changes, applied in order. |
changes list is refused, and so is a variant that changes nothingA proposal whose result is the baseline is not a proposal. apply refuses both — an empty list
by name, and a document whose changes all leave the model as it was by comparing the resulting
model_hash against the baseline's. ⭐ Otherwise a search loop spends a full paired comparison
discovering that two identical models are identical, and reports NoMeaningfulDifference about
a question nobody asked.
The changes
⚠️ The set is closed on purpose. An open-ended "set this JSON path" would let a proposal reach anywhere in the document, including fields whose invariants the compiler relies on. Every change below names the thing it touches and nothing else.
⭐ Scale changes are factors, not absolutes. "10% faster" survives a change to the underlying model and "600 seconds" does not, so a search that mixes the two cannot be replayed against a new baseline.
set_servers
How many servers a tool group has. The classic capital question.
kind: set_servers
resource: scanner
servers: 3
set_queue_capacity
How many jobs may wait at a resource, excluding the one in service.
kind: set_queue_capacity
resource: scanner
queue_capacity: 3
⭐ The capacity question with an exact answer behind it: Naor's welfare optimum is a statement about exactly this bound. At ρ = 4, choosing the bound selfishly rather than for welfare throws away 95.73% of the attainable welfare.
⚠️ A bound of 0 needs when_full: block on the resource — otherwise it is a loss system
with a different exact answer (Erlang B rather than Erlang C) rather than a smaller queue.
apply validates the result, so that refusal comes from resources and is
not restated here.
scale_process_time
Multiply every process time on a resource, including a wafer cascade's
piece_interval_seconds, so the whole cascade speeds up.
kind: scale_process_time
resource: bench
factor: 0.8
scale_release_interval
Multiply a release stream's interarrival time. Above 1.0 is less load.
kind: scale_release_interval
product: job
factor: 1.25
scale_maintenance_interval
Change how often a maintenance policy on a resource comes due.
kind: scale_maintenance_interval
resource: oven
factor: 2.0
set_batch
Change a batching policy, in pieces. See batching.
kind: set_batch
resource: furnace
min_pieces: 75
max_pieces: 150
set_entity_rule
Replace the rule that picks which waiting entity a free server takes. The rule is an ordinary
dispatch rule.
kind: set_entity_rule
resource: scanner
rule:
ranks:
- - priority
- 1.0
- - age
- 1.0
What is refused, and where
⭐ apply validates the result, not the change. A variant is legal when the model it
produces is legal, so every refusal on the model pages applies unchanged and there is
no second copy of any rule here. What the variant document adds on top:
| refusal | why |
|---|---|
| an unknown key anywhere in the document | ⚠️ the same rule as the model: a dropped key silently selects a default that looks plausible |
changes empty | a proposal with no changes is not a proposal |
the result's model_hash equals the baseline's | the proposal changes nothing, reported at the document rather than at one change |
| naming a resource, product or policy the model does not have | reported at /changes/{i}, so the message names which change is wrong |
| the same setting changed twice | reported at /changes/{i}: the second write would silently win |
Where the vocabulary comes from
This page is generated from nothing — but the set it describes is checked against
crates/clockwright-ir/src/variant.rs by the gate, in three places: the
schema an agent reads, the vocabulary the MCP server serves, and every
fence above. A change added to the engine and not documented here fails the build.