Skip to main content
Glama
@littlebigbrain/mcp

@littlebigbrain/mcp

lbb_workflows_manage

Create, refresh, pause, resume, run, activate, and test knowledge-graph workflows from starter templates or developer definitions, including triggers, schedules, budgets, and pre-write validation.

Instructions

Create a workflow from a starter workflow, or refresh, pause or resume a triggered workflow. Run lbb_workflows action=preview first and show the user the watch and the instance count. use creates a workflow from a starter workflow of action=starters, under a name (default: the class's local name), with its watch (class, fields) and params; the workflow then reads every instance of the class and follows each published commit. A developer workflow (starter=workflow) also takes workflow (workflow_type and version of the developer's worker), owns and batch; it needs a server with the workflow engine. use again with the same starter and name changes only what you name, and the same request again changes nothing. For starter=agent, use takes owns.properties (1 to 64), params.agent={instructions,tools?,max_iterations?,max_cost_micro_usd?,max_wall_ms?,max_read_ms?,max_proposals?,max_review_items?,pin_wait_ms?}, top-level gate and optional budget_usd_per_month; ownership, input and authority do not go inside params.agent. Tools are graph.read, files.read and propose_write; finish is implicit. Models chooses use extract, not an agent use. Hosted content-trigger creation uses PUT /v1/workflows/named with the dedicated Agent request; define here remains a developer-workflow action. A workflow that calls models (agent, ontology.fit, documents.link, documents.map, values.extract) spends model budget as it reads. refresh asks the workflow to run now and returns at once with queued; poll lbb_workflows action=get for progress. pause stops a workflow (a developer workflow, documents.parse, documents.link, documents.map or values.extract) from reading and sending, and stops the claims of a developer workflow's shards; resume lets it catch up. Without starter, pause and resume name a workflow with triggers. search.embed and ontology.fit cannot be paused. define creates or changes a workflow with triggers of the developer's worker (/v1/workflows/named): name, workflow, one trigger (class and fields for a watch, media_types for a content trigger on uploads, schedule for one run per slot of a timer, or stream for the events a client sends, in batches), owns, batch, and shards (engine identities, 1 to 16), route_by (a field that keeps work in order) and window (messages queued per shard, default 4), and optionally budget_usd_per_month (the workflow's own monthly model budget), on_exhausted (block or skip) and fan_in (the id of a fan-in identity the shards send to); it needs a server with the workflow engine, and a change of shards waits for empty inboxes. run (name, optional inputs) with inputs sends 1 to 16 explicit {iri} or {sha256} inputs as new manual-origin messages, pinned to the current revision; use the original source IRI from run_results to run a failed or pin-wait input again, not a proposed value subject. Each new message gets a new work key. Without inputs, run runs a workflow with triggers now: its watch sends every instance again, a content trigger reads its log again, a schedule sends one run outside its slots, a stream sends its partial batches. activate (name, revision) makes an earlier revision of a workflow's settings (from lbb_workflows action=revisions) the one in use: it goes through the same checks as a change, and answers the workflow with its active_revision. Show the user what changes first (compare the settings of the two revisions), and when the revision's last_tested is null say that no test is recorded for those settings and ask before you activate (activation stays allowed), and pass expected_revision, the revision you read as bound, so a change by someone else since fails with workflow_revision_conflict. A revision whose settings the graph now reads differently fails with workflow_revision_stale; then change the workflow instead. Activating changes what the workflow does from then on and deletes no data, so it is not a destructive write; a change of the watch, the parameters or the gate makes the workflow read its class again, and work that waits or runs keeps the revision it started with. tests_create tests a values.extract workflow or an Agent workflow on a server that offers hosted agent tests on chosen inputs before it writes: name (a workflow of the graph; starter defaults to values.extract) or install (a workflow not created yet, as use takes it: starter, name, watch, params, gate), and iris (at most 20) or sample (1 to 20, default 6), and models to try other models before choosing one. A test reads each input as the workflow would and calls its models (the calls count in the monthly workflow budget); it writes nothing to the graph. Poll lbb_workflows action=tests_get. tests_answer records the person's answer to one value of a read input (id, iri, property, answer: accept, reject, correct with value, or cant_tell; null removes it); it writes nothing to the graph. Answer only what the user decided. tests_cancel stops a queued or running test (id). Save a test's answers as a dataset with lbb_evals action=datasets_create.

Tool call template

Replace each placeholder with a value matching the input schema. Only required parameters are included; add optional parameters as needed.

{
  "id": 1,
  "jsonrpc": "2.0",
  "method": "tools/call",
  "params": {
    "arguments": {
      "action": "<action>"
    },
    "name": "lbb_workflows_manage"
  }
}

Input Schema

NameRequiredDescriptionDefault
idNotests_answer, tests_cancel: the test's id.
iriNotests_answer: the input, by its instance IRI.
gateNouse: outputs below this confidence go to review instead of the graph, for a workflow with a review gate.
irisNotests_create: the instances to test, at most 20.
nameNoThe triggered workflow's name, [a-z0-9][a-z0-9-]{0,62} (refresh, pause, resume: required; use: omit for the class's local name; tests_create: the workflow of the graph to test).
ownsNopreview/use: agent requires properties (1 to 64) and no classes or relations; a developer workflow names what its code writes. classes it creates instances of, relations it writes as out-edges of the watched instances, properties it writes on them. Owned outputs never trigger the workflow itself.
batchNopreview/use and define, developer workflow only: instances per message, 1 to 64 (default 16); for a stream, events per message, 1 to 500 (default 100). The server checks each kind's bound.
classNopreview/use: the class whose instances the workflow watches (an IRI, or a name the ontology knows). Required unless the starter workflow's watch is fixed.
graphNoGraph to target; defaults to the connection's graph
actionYes
answerNotests_answer: { decision: accept | reject | correct | cant_tell, value (with correct), note }; null removes the answer. For a property without a value, accept says the text states none, and correct gives the value it states.
detailNoResponse detail level. Defaults to compact.
fan_inNodefine: the id of the workflow's fan-in identity (starter.<name>.<fan_in>) that the shards' turns send to, for example a catalogue. Default: none.
fieldsNopreview/use: the fields whose change triggers the workflow, as embedding paths (transcript, label, about/label, <https://…>). Omit for the starter workflow's own choice.
inputsNorun only: explicit original source inputs, one record IRI or uploaded-file SHA-256 per entry. New messages, new work keys; no whole-watch rescan.
modelsNotests_create: other models to run the test on, per use (extract, decide), each one of the use's options in lbb_model_choice action=choices. The test's models name the ones it ran.
paramsNopreview/use: agent takes {agent:{instructions,tools?,max_iterations?,max_cost_micro_usd?,max_wall_ms?,max_read_ms?,max_proposals?,max_review_items?,pin_wait_ms?}}. Other settings follow the catalog entry: search.embed takes model, dim and title; ontology.fit takes context (one sentence about the text, e.g. {"context": "interviews with employees"}); workflow takes none.
sampleNotests_create: the first instances of the watched class instead, 1 to 20 (default 6).
shardsNodefine: engine identities that receive the messages, 1 to 16 (default 1). A change needs every shard's inbox empty.
streamNodefine, in place of class: a stream of the events a client sends (POST /v1/workflows/named/events), in batches of batch events (1 to 500, default 100) or after max_wait; each event's key routes it to a shard, and shards stay as made.
windowNodefine: messages the watch keeps queued per shard, 1 to 128 (default 4).
excludeNopreview/use: fields to drop from the automatic choice.
installNotests_create: a workflow not created yet, as use takes it: starter (values.extract), name, watch ({class, fields, exclude}), params and gate ({below}).
starterNoThe starter workflow's catalog id from action=starters, e.g. search.embed (embeddings on a class), ontology.fit (fit from text) or workflow (a developer workflow: your worker's workflow gets the instances). get/preview/use/refresh: required. pause/resume: omit to name a workflow with triggers.
triggerNodefine: the trigger's id (default watch, content, schedule or stream). queue: the trigger whose queue to read.
propertyNotests_answer: the property of the value.
revisionNoactivate: the revision to use (wrev_ and 32 hex characters).
route_byNodefine: a field of the class whose value keeps work in order: the changes of one value go to one shard. Default: the instance.
scheduleNodefine, in place of class: a schedule. One message per slot, at least 5 minutes apart, to one shard; a slot runs even when nothing changed (the message's changed is false).
workflowNopreview/use, developer workflow (starter=workflow) only, required there: the workflow of your worker that receives the messages. Creating it creates the instance starter.<name>.
experimentNoactivate: an experiment (exp_…) whose B this activation applies; revision is B's revision. The workflow must still run the experiment's A, else experiment_outdated; when B's revision is active already, nothing changes.
media_typesNodefine, in place of class: a content trigger on the uploads of these media types (application/pdf, image/*, */*), one content per message, without a wait for a publication.
on_exhaustedNodefine: what a turn that failed for good does to its shard: block (the default) holds the inbox until a person retries or skips it; skip keeps the failed turn in the history and goes on.
expected_revisionNoactivate: the revision the workflow had bound when you read it; another one fails with workflow_revision_conflict.
budget_usd_per_monthNouse and define: a monthly model budget in USD for this workflow, below the stack's own. For define, its turns' activity calls stop at it (429 workflow_budget_exceeded) and the turn waits until the next month.

Schema Changelog

Changes observed during successful MCP inspections.

  1. Changed3 schema fields changedv0.18.1
    • addedInput schema / properties / inputs
      Added value: +{
      +  "description": "run only: explicit original source inputs, one record IRI or uploaded-file SHA-256 per entry. New messages, new work keys; no whole-watch rescan.",
      +  "items": {
      +    "anyOf": [
      +      {
      +        "additionalProperties": false,
      +        "properties": {
      +          "iri": {
      +            "maxLength": 4096,
      +            "minLength": 1,
      +            "type": "string"
      +          }
      +        },
      +        "required": [
      +          "iri"
      +        ],
      +        "type": "object"
      +      },
      +      {
      +        "additionalProperties": false,
      +        "properties": {
      +          "sha256": {
      +            "pattern": "^[0-9a-f]{64}$",
      +            "type": "string"
      +          }
      +        },
      +        "required": [
      +          "sha256"
      +        ],
      +        "type": "object"
      +      }
      +    ]
      +  },
      +  "maxItems": 16,
      +  "minItems": 1,
      +  "type": "array"
      +}
    • changedInput schema / properties / owns / description
      Previous value: -"preview/use, developer workflow only: what your code writes. classes it creates instances of, relations it writes as out-edges of the watched instances, properties it writes on them. Owned outputs never trigger the workflow itself."New value: +"preview/use: agent requires properties (1 to 64) and no classes or relations; a developer workflow names what its code writes. classes it creates instances of, relations it writes as out-edges of the watched instances, properties it writes on them. Owned outputs never trigger the workflow itself."
    • changedInput schema / properties / params / description
      Previous value: -"preview/use: the starter workflow's own settings, as listed in its catalog entry: search.embed takes model, dim and title; ontology.fit takes context (one sentence about the text, e.g. {\"context\": \"interviews with employees\"}); workflow takes none."New value: +"preview/use: agent takes {agent:{instructions,tools?,max_iterations?,max_cost_micro_usd?,max_wall_ms?,max_read_ms?,max_proposals?,max_review_items?,pin_wait_ms?}}. Other settings follow the catalog entry: search.embed takes model, dim and title; ontology.fit takes context (one sentence about the text, e.g. {\"context\": \"interviews with employees\"}); workflow takes none."
  2. Changed1 schema field changedv0.17.0
    • addedInput schema / properties / experiment
      Added value: +{
      +  "description": "activate: an experiment (exp_…) whose B this activation applies; revision is B's revision. The workflow must still run the experiment's A, else experiment_outdated; when B's revision is active already, nothing changes.",
      +  "type": "string"
      +}
  3. Changed23 schema fields changedv0.16.0
    • removedInput schema / additionalProperties
      Removed value: -false
    • changedInput schema / properties / action / enum
      Previous value: -[
      -  "use",
      -  "refresh",
      -  "pause",
      -  "resume",
      -  "define",
      -  "run"
      -]New value: +[
      +  "use",
      +  "refresh",
      +  "pause",
      +  "resume",
      +  "define",
      +  "run",
      +  "activate",
      +  "tests_create",
      +  "tests_answer",
      +  "tests_cancel"
      +]
    • addedInput schema / properties / answer
      Added value: +{
      +  "anyOf": [
      +    {
      +      "additionalProperties": false,
      +      "properties": {
      +        "decision": {
      +          "description": "accept, reject, correct (with value), choose (with option) or cant_tell",
      +          "enum": [
      +            "accept",
      +            "reject",
      +            "correct",
      +            "choose",
      +            "cant_tell"
      +          ],
      +          "type": "string"
      +        },
      +        "note": {
      +          "description": "Why, in a few words; for an answer item, the answer text. At most 1,000 characters",
      +          "maxLength": 1000,
      +          "type": "string"
      +        },
      +        "option": {
      +          "description": "choose: the id of the chosen option or grade",
      +          "type": "string"
      +        },
      +        "value": {
      +          "description": "correct: the right value, in the property's type. A number may name its unit, e.g. 450 mm",
      +          "type": [
      +            "number",
      +            "string",
      +            "boolean"
      +          ]
      +        }
      +      },
      +      "required": [
      +        "decision"
      +      ],
      +      "type": "object"
      +    },
      +    {
      +      "type": "null"
      +    }
      +  ],
      +  "description": "tests_answer: { decision: accept | reject | correct | cant_tell, value (with correct), note }; null removes the answer. For a property without a value, accept says the text states none, and correct gives the value it states."
      +}
    • changedInput schema / properties / batch / description
      Previous value: -"preview/use, developer workflow only: instances per message, 1 to 64 (default 16)."New value: +"preview/use and define, developer workflow only: instances per message, 1 to 64 (default 16); for a stream, events per message, 1 to 500 (default 100). The server checks each kind's bound."
    • changedInput schema / properties / batch / maximum
      Previous value: -64New value: +500
    • changedInput schema / properties / budget_usd_per_month / description
      Previous value: -"use: a monthly model budget in USD for this workflow, below the stack's own."New value: +"use and define: a monthly model budget in USD for this workflow, below the stack's own. For define, its turns' activity calls stop at it (429 workflow_budget_exceeded) and the turn waits until the next month."
    • addedInput schema / properties / expected_revision
      Added value: +{
      +  "description": "activate: the revision the workflow had bound when you read it; another one fails with workflow_revision_conflict.",
      +  "type": "string"
      +}
    • addedInput schema / properties / fan_in
      Added value: +{
      +  "description": "define: the id of the workflow's fan-in identity (starter.<name>.<fan_in>) that the shards' turns send to, for example a catalogue. Default: none.",
      +  "type": "string"
      +}
    • addedInput schema / properties / id
      Added value: +{
      +  "description": "tests_answer, tests_cancel: the test's id.",
      +  "type": "string"
      +}
    • addedInput schema / properties / install
      Added value: +{
      +  "additionalProperties": false,
      +  "description": "tests_create: a workflow not created yet, as use takes it: starter (values.extract), name, watch ({class, fields, exclude}), params and gate ({below}).",
      +  "properties": {
      +    "gate": {
      +      "additionalProperties": false,
      +      "properties": {
      +        "below": {
      +          "description": "Values below this support go to review",
      +          "maximum": 1,
      +          "minimum": 0,
      +          "type": "number"
      +        }
      +      },
      +      "required": [
      +        "below"
      +      ],
      +      "type": "object"
      +    },
      +    "name": {
      +      "description": "The name the workflow would get; default the class's local name",
      +      "type": "string"
      +    },
      +    "params": {
      +      "additionalProperties": {},
      +      "description": "The starter workflow's settings, as its catalog entry lists them (the properties to fill, the context)",
      +      "propertyNames": {
      +        "type": "string"
      +      },
      +      "type": "object"
      +    },
      +    "starter": {
      +      "description": "The starter workflow, values.extract",
      +      "type": "string"
      +    },
      +    "watch": {
      +      "additionalProperties": false,
      +      "properties": {
      +        "class": {
      +          "description": "The class whose instances it reads",
      +          "type": "string"
      +        },
      +        "exclude": {
      +          "items": {
      +            "type": "string"
      +          },
      +          "type": "array"
      +        },
      +        "fields": {
      +          "description": "The fields that hold the text, as embedding paths",
      +          "items": {
      +            "type": "string"
      +          },
      +          "type": "array"
      +        }
      +      },
      +      "required": [
      +        "class"
      +      ],
      +      "type": "object"
      +    }
      +  },
      +  "required": [
      +    "starter"
      +  ],
      +  "type": "object"
      +}
    • addedInput schema / properties / iri
      Added value: +{
      +  "description": "tests_answer: the input, by its instance IRI.",
      +  "type": "string"
      +}
    • addedInput schema / properties / iris
      Added value: +{
      +  "description": "tests_create: the instances to test, at most 20.",
      +  "items": {
      +    "type": "string"
      +  },
      +  "maxItems": 20,
      +  "type": "array"
      +}
    • addedInput schema / properties / media_types
      Added value: +{
      +  "description": "define, in place of class: a content trigger on the uploads of these media types (application/pdf, image/*, */*), one content per message, without a wait for a publication.",
      +  "items": {
      +    "type": "string"
      +  },
      +  "maxItems": 32,
      +  "minItems": 1,
      +  "type": "array"
      +}
    • addedInput schema / properties / models
      Added value: +{
      +  "description": "tests_create: other models to run the test on, per use (extract, decide), each one of the use's options in lbb_model_choice action=choices. The test's models name the ones it ran.",
      +  "properties": {
      +    "decide": {
      +      "properties": {
      +        "effort": {
      +          "enum": [
      +            "low",
      +            "medium",
      +            "high",
      +            "xhigh",
      +            "max"
      +          ],
      +          "type": "string"
      +        },
      +        "model": {
      +          "type": "string"
      +        },
      +        "provider": {
      +          "enum": [
      +            "anthropic",
      +            "typesafe",
      +            "mock"
      +          ],
      +          "type": "string"
      +        }
      +      },
      +      "required": [
      +        "provider",
      +        "model"
      +      ],
      +      "type": "object"
      +    },
      +    "extract": {
      +      "properties": {
      +        "effort": {
      +          "enum": [
      +            "low",
      +            "medium",
      +            "high",
      +            "xhigh",
      +            "max"
      +          ],
      +          "type": "string"
      +        },
      +        "model": {
      +          "type": "string"
      +        },
      +        "provider": {
      +          "enum": [
      +            "anthropic",
      +            "typesafe",
      +            "mock"
      +          ],
      +          "type": "string"
      +        }
      +      },
      +      "required": [
      +        "provider",
      +        "model"
      +      ],
      +      "type": "object"
      +    }
      +  },
      +  "type": "object"
      +}
    • changedInput schema / properties / name / description
      Previous value: -"The triggered workflow's name, [a-z0-9][a-z0-9-]{0,62} (refresh, pause, resume: required; use: omit for the class's local name)."New value: +"The triggered workflow's name, [a-z0-9][a-z0-9-]{0,62} (refresh, pause, resume: required; use: omit for the class's local name; tests_create: the workflow of the graph to test)."
    • addedInput schema / properties / on_exhausted
      Added value: +{
      +  "description": "define: what a turn that failed for good does to its shard: block (the default) holds the inbox until a person retries or skips it; skip keeps the failed turn in the history and goes on.",
      +  "enum": [
      +    "block",
      +    "skip"
      +  ],
      +  "type": "string"
      +}
    • addedInput schema / properties / params / propertyNames
      Added value: +{
      +  "type": "string"
      +}
    • addedInput schema / properties / property
      Added value: +{
      +  "description": "tests_answer: the property of the value.",
      +  "type": "string"
      +}
    • addedInput schema / properties / revision
      Added value: +{
      +  "description": "activate: the revision to use (wrev_ and 32 hex characters).",
      +  "type": "string"
      +}
    • addedInput schema / properties / sample
      Added value: +{
      +  "description": "tests_create: the first instances of the watched class instead, 1 to 20 (default 6).",
      +  "maximum": 20,
      +  "minimum": 1,
      +  "type": "integer"
      +}
    • addedInput schema / properties / schedule
      Added value: +{
      +  "description": "define, in place of class: a schedule. One message per slot, at least 5 minutes apart, to one shard; a slot runs even when nothing changed (the message's changed is false).",
      +  "properties": {
      +    "cron": {
      +      "description": "Five fields: minute, hour, day of month, month, day of week (0 3 * * 1-5), or @daily and the other macros.",
      +      "type": "string"
      +    },
      +    "every": {
      +      "description": "In place of cron: a period in UTC, 5m to 366d (15m, 6h, 1d).",
      +      "type": "string"
      +    },
      +    "jitter": {
      +      "description": "Each slot runs up to this much later, the same delay each time; at most 1h.",
      +      "type": "string"
      +    },
      +    "offset": {
      +      "description": "With every: moves each slot (every 1d, offset 3h runs at 03:00 UTC).",
      +      "type": "string"
      +    },
      +    "timezone": {
      +      "description": "With cron: an IANA time zone (Europe/Berlin). Default UTC.",
      +      "type": "string"
      +    }
      +  },
      +  "type": "object"
      +}
    • addedInput schema / properties / stream
      Added value: +{
      +  "description": "define, in place of class: a stream of the events a client sends (POST /v1/workflows/named/events), in batches of batch events (1 to 500, default 100) or after max_wait; each event's key routes it to a shard, and shards stay as made.",
      +  "properties": {
      +    "max_wait": {
      +      "description": "How long the first event of a partial batch waits, 1s to 15m (default 10s).",
      +      "type": "string"
      +    }
      +  },
      +  "type": "object"
      +}
    • changedInput schema / properties / trigger / description
      Previous value: -"define: the watch trigger's id (default watch). queue: the trigger whose queue to read."New value: +"define: the trigger's id (default watch, content, schedule or stream). queue: the trigger whose queue to read."
  4. Addedv0.14.0

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With annotations only saying readOnlyHint=false, destructiveHint=false, and openWorldHint=false, the description adds substantial behavioral context: model-budget spending, queued refresh and polling, paused claims and shard inbox waits, activation conflict and stale-revision errors, budget-exceeded 429s, and the fact that activation deletes no data. It is consistent with destructiveHint=false and explicitly explains why activation is not treated as destructive.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness2/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The purpose is front-loaded, but the description is an extremely long, dense paragraph covering ten actions and dozens of edge cases without sectioning or bullets. Much of the content is useful for such a broad tool, but the wall-of-text structure makes it hard for an agent to scan and find the relevant action-specific guidance.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

There is no output schema, and the description compensates well by explaining return behavior for key actions: refresh returns at once with queued, activate answers with active_revision, and tests are polled through lbb_workflows action=tests_get. It also covers error outcomes, budget effects, and the write/no-write distinction for tests, so the definition is complete enough for this complex multi-action tool.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 97%, so the schema already carries most parameter meaning and the baseline would be 3. The description still adds conditional, cross-parameter semantics beyond the schema, such as 'use again with the same starter and name changes only what you name,' the rule that ownership, input, and authority do not go inside params.agent, and the distinction that tests_create models choose use extract rather than an agent use.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The opening sentence states a specific verb and resource: create a workflow from a starter, or refresh, pause, or resume a triggered workflow. Across the description it also covers define, run, activate, and the test actions, and it distinguishes this manage tool from the read-side lbb_workflows actions (preview, get, revisions, starters) and from lbb_evals datasets_create.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives explicit sequencing ('Run lbb_workflows action=preview first and show the user the watch and the instance count'), names the correct alternative action for progress polling ('poll lbb_workflows action=get'), and states exclusions ('search.embed and ontology.fit cannot be paused'). It also explains when to ask the user before activation and when to use the original source IRI from run_results rather than a proposed value subject.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.