Skip to main content
Glama
aliasunder

Vault Cortex Obsidian MCP Server

Update Task

vault_update_task
Destructive

Update an existing task in one call: change its status, priority, description, dates, dependencies, checklist items, or block ID, and move it between headings or Kanban lanes.

Instructions

Update a task's status, priority, description, dates, dependencies, block_id, checklist items, or heading placement in one call. Any combination of these can change together — every field passed is written in a single edit.

Example: vault_update_task({ path: "TASKS.md", block_id: "my-task", status: "done" }) — complete a task; on a Kanban board, auto-moves to the done lane; a recurring task (🔁) spawns its next occurrence Example: vault_update_task({ path: "TASKS.md", block_id: "my-task", recurrence: "every week" }) — make a task recurring (null removes the rule) Example: vault_update_task({ path: "TASKS.md", block_id: "my-task", heading: "Done" }) — move a task to a different heading (lands at the top of the lane by default) Example: vault_update_task({ path: "TASKS.md", block_id: "my-task", heading: "Done", position: "bottom" }) — move to the bottom of the lane Example: vault_update_task({ path: "TASKS.md", block_id: "my-task", description: "Updated task name", due: "2026-10-01" }) — change description and set due date Example: vault_update_task({ path: "TASKS.md", block_id: "my-task", due: null }) — clear a date field Example: vault_update_task({ path: "TASKS.md", block_id: "my-task", status: "in_progress", add_subtasks: ["Design", "Implement", "Test"] }) — start working and add checklist stages Example: vault_update_task({ path: "TASKS.md", line: 42, assign_block_id: "my-task" }) — add a block_id to a task that lacks one Example: vault_update_task({ path: "TASKS.md", block_id: "my-task", task_id: "abc123" }) — set a Tasks plugin 🆔 identifier Example: vault_update_task({ path: "TASKS.md", block_id: "my-task", on_completion: "delete" }) — set the task to be removed on completion (null clears the field) Example: vault_update_task({ path: "TASKS.md", block_id: "my-task", heading: "Active", position: 3 }) — move to the 3rd position in a lane (1-based; past the card count lands directly below the last card) Example: vault_update_task({ path: "TASKS.md", block_id: "my-task", position: 1 }) — same-lane reorder to the top without a heading move

When to use: Any change to an existing task — completing, starting, re-prioritizing, editing text, setting or clearing dates, adding checklist items, assigning block_ids, moving between headings, or reordering within a lane. Use vault_list_tasks first to get identification fields (path + block_id or line). For creating a new task, use vault_create_task instead.

Parameters:

  • path (required): vault-relative path to the note (must end in ".md").

  • Exactly one of block_id or line is required to identify the task.

  • At least one change is required. Every field passed is applied in the same single write:

    • status: "todo" | "in_progress" | "done" | "cancelled". Manages checkbox and done/cancelled dates. Kanban: "done" moves the card and its checklist sub-items to the done lane (sub-item checkboxes left as they are); a sub-task stays under its parent. Recurring (🔁): spawns the next occurrence above the completed one (below with the plugin's "next line" setting), dates advanced per the rule. The spawn stays in the source lane with no block_id, 🆔, or ⛔ — follow up with assign_block_id on next_occurrence.line. Completing by line is NOT idempotent for recurring tasks (the spawn occupies the old line); prefer block_id. Delete (🏁): removes the task line and children instead of moving to done. With 🔁 + 🏁, the spawn is created first, then the completed line is removed; with "next line", children transfer to the spawn. Result carries on_completion_applied: "delete".

    • priority: "highest" | "high" | "medium" | "low" | "lowest" sets the signifier; null removes it.

    • recurrence: sets the Tasks plugin 🔁 rule, in natural language ("every week", "every month on the 15th", "every 3 days when done" — "when done" bases the next occurrence on the completion day); null removes it. Passed together with status "done", the new rule governs the spawn (recurrence: null completes without spawning).

    • on_completion: "delete" or "keep" — sets the Tasks plugin 🏁 action applied when the task is completed; null removes it. When on_completion and status are passed in the same call, the submitted on_completion value governs the delete decision — setting "keep" while completing a "delete" task prevents the deletion. See the status bullet for delete behavior details.

    • description: replaces the task text. Metadata fields and block_id are preserved.

    • due / scheduled / start / created: YYYY-MM-DD sets the date; null clears it.

    • task_id: string sets the Tasks plugin 🆔; null clears it.

    • depends_on: non-empty string array sets the Tasks plugin ⛔; null clears it.

    • add_subtasks: non-empty string array — appends one indented todo checklist item per entry under the task; existing checklist items are kept. For full sub-tasks with their own metadata, use vault_create_task with parent_block_id.

    • assign_block_id: adds or replaces the ^block-id on the task line. Letters, digits, and hyphens only; must be unique within the note.

    • heading: target heading to move the task to. On Kanban boards this is a lane move; works on any note with headings. Not valid on sub-tasks.

    • position: "top", "bottom", or a 1-based integer — where within the target heading the task lands after a heading move or auto-done-lane move. Position 1 is the first card; past the card count lands directly below the last card. Defaults to "top" on heading moves. Without a heading, triggers a same-lane reorder to the given position; omitting position entirely performs no reorder. Ignored when the task is deleted on completion. Not valid on sub-tasks.

    • Clearing is always explicit null — omitting a field leaves it untouched.

  • format: "emoji" or "dataview" — overrides the auto-detected Tasks plugin format.

Errors:

  • "note not found" — path does not exist

  • "exactly one of blockId or line is required" / "blockId and line are mutually exclusive" — pass exactly one of block_id or line

  • "blockId ... not found" — no task line in the note ends with ^block_id

  • "blockId ... is inside a fenced code block or comment" — the block_id matches a line inside a fenced code block or %% %% comment; target a line outside the fence

  • "no task at line N" — line doesn't contain a task checkbox

  • "line N is inside a fenced code block or comment" — the line is inside a fenced code block or %% %% comment; target a line outside the fence

  • "checkbox '[c]' is a NON_TASK status" — the task's checkbox char is typed NON_TASK in the Tasks plugin's status registry; NON_TASK checkboxes are excluded from the task system and cannot be mutated

  • "no checkbox symbol for status ..." — the status registry has no symbol for the target status and the built-in default is retyped; update the plugin's status registry to include a symbol for this status

  • "at least one mutation" — no change params provided

  • "cannot move a sub-task to a heading" — explicit heading on a task nested under another task (depth > 0 in vault_list_tasks)

  • "cannot reposition a sub-task" — explicit position on a sub-task (sub-tasks move with their parent)

  • "cannot reorder a task that sits above the first heading" — position without a heading on a task before the first section heading

  • "cannot reorder within "X" — the heading appears N times" — same-lane reorder on a card whose heading name is duplicated in the note; rename one section to make it unique

  • "cannot place at position N under "X" — the heading appears N times" — cross-lane move with an integer position to a heading name that appears more than once; rename one section to make it unique

  • "heading "X" not found; available: ..." — target heading doesn't exist; the error lists the note's headings

  • "multiple done lanes detected" — status "done" on a Kanban board with more than one Complete-marked lane; pass heading to pick the lane

  • "no done lane detected" — status "done" on a Kanban board with no Complete marker and no "Done" heading; pass heading explicitly

  • "blockId ... already exists" / "blockId ... contains invalid characters" — assign_block_id must be unique in the note and match [a-zA-Z0-9-]+

  • "invalid date" — a date param fails calendar validation

  • "description cannot be empty" / "dependsOn cannot be empty" / "addSubtasks cannot be empty" / "addSubtasks cannot contain an empty item" — whitespace-only text or an empty array (use null to clear depends_on)

  • "description must be a single line" / "addSubtasks items must be a single line" — a task is one file line; a line break in the text would split its metadata onto a line the parser never reads

  • "taskId ... contains invalid characters" / "dependsOn entry ... contains invalid characters" — task_id and every depends_on entry must match [a-zA-Z0-9_-]+ (the Tasks plugin's id grammar)

  • "unrecognized recurrence rule ..." — the rule text is not Tasks-plugin natural language; written as-is it would silently never recur

  • A recurring task completed with a rule that yields no next occurrence (unreadable rule text already on the line, or a finite rule with no dates left) still completes — the result carries an advisory instead of an error

  • "concurrent write in progress" — another write to this note is in flight; retry

Obsidian syntax: The Tasks plugin reads metadata off the END of a task line, so a trailing run of signifier syntax inside description or add_subtasks text — an emoji field like "🔁 every week", or a Dataview [key:: value] field, followed only by other recognized task fields — is read back as task metadata rather than text. A signifier followed by ordinary prose stays description text unless the prose matches that field's value grammar — a 🔁 recurrence reads any trailing words as its rule, while a 📅 followed by ordinary words stays description text because the words are not a date. The same interference can change the value an adjacent field reads back with, or make a field appear that was never set, as the 🔁 example shows. The write still succeeds either way; when the stored line would read back differently than this call set, the result carries an advisories array naming each divergence. The dates a status change stamps or clears (the ✅/❌ dates) are expected and produce no advisories on their own — but a description signifier that changes what the stamped date parses back as is still reported.

Returns: JSON { path, line, description, block_id, heading, subtasks, next_occurrence, changes, advisories, on_completion_applied } — line is the final 1-based position (when on_completion_applied is "delete", it is the position the task occupied before removal); description is the current text; block_id and heading reflect the task after the update (block_id is omitted when the task has none, heading when the task sits above the first heading); subtasks lists each checklist item added by add_subtasks as { line, description } (omitted when none were added) — checklist items carry no block_id, so line is the handle for a follow-up update; next_occurrence is present only when a completion spawned a recurring task's next occurrence: { line, description, due?, scheduled?, start? } with only the dates the occurrence has — it carries no block_id, so line is its handle; changes lists every field applied as "field: before → after", with "(none)" for an absent value (for subtasks the two sides are checklist-item counts, and a spawn adds "next_occurrence: (none) → line N"); advisories (omitted when the line round-trips clean and no recurrence notice applies) lists one sentence per place the stored line parses back differently than this call set (see Obsidian syntax above) or per recurrence event that did not produce a next occurrence; on_completion_applied (present only when the effective on_completion was delete — pre-existing on the task or set in the same call — and it was transitioned to done) is always "delete".

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
dueNoDue date (YYYY-MM-DD) to set, or null to clear.
lineNo1-based line number from vault_list_tasks. Fragile if the file changed since the query.
pathYesVault-relative path to the note containing the task (must end in ".md")
startNoStart date (YYYY-MM-DD) to set, or null to clear.
formatNoField format for new metadata. Default: auto-detected from .obsidian/ config, falling back to emoji.
statusNoTarget status. "done" appends the ✅ date and, on a Kanban board, moves the card and its checklist sub-items to the done lane (sub-item checkboxes are left as they are); a task with 🏁 delete / [onCompletion:: delete] is removed from the file instead. "cancelled" appends the ❌ date.
createdNoCreated date (YYYY-MM-DD) to set or clear. Typically auto-stamped; use for corrections.
headingNoTarget heading to move the task to. On Kanban boards this is a lane move; works on any note with headings. Not valid on sub-tasks.
task_idNoTasks plugin 🆔 identifier to set, or null to clear.
block_idNoStable task identifier — the ^block-id at the end of the task line, without the ^. Preferred over line.
positionNoWhere within the target heading the task lands after a heading move or auto-done-lane move. "top" or "bottom" for the extremes; an integer (1-based) for an exact position among the lane's top-level cards (sub-tasks move with their parent and are not counted). Position 1 is the first card. A position past the card count lands directly below the last card (unlike "bottom", which appends after any trailing section content). Defaults to "top" on heading moves. Without a heading, triggers a same-lane reorder to the given position; omitting position entirely performs no reorder. Ignored when the task is deleted on completion. Not valid on sub-tasks.
priorityNoPriority signifier to set, or null to remove it.
scheduledNoScheduled date (YYYY-MM-DD) to set, or null to clear.
depends_onNoTasks plugin ⛔ dependency IDs to set (non-empty), or null to clear.
recurrenceNoTasks plugin 🔁 rule in natural language (e.g. "every week", "every 2 weeks when done") to set, or null to remove it. Completing the task spawns its next occurrence.
descriptionNoNew task description text. Replaces the existing description; metadata fields and block_id are preserved.
add_subtasksNoChecklist items to append, one indented todo line each, under the task's existing items — never replaces them. Can be combined with any other change; not appended when the same call removes the task (on_completion delete). For full sub-tasks with metadata, use vault_create_task with parent_block_id.
on_completionNoTasks plugin 🏁 onCompletion action to set, or null to remove it. "delete" removes the task line on completion; "keep" leaves it in place (which is also the behavior when no 🏁 field exists on the task). Omitting this parameter leaves the field unchanged.
assign_block_idNoAdd or replace the ^block-id on the task line. Letters, digits, and hyphens only; must be unique within the note.

Schema Changelog

Changes observed during successful MCP inspections.

  1. Changed1 schema field changedv0.54.0
    • changedInput schema / properties / add_subtasks / description
      Previous value: -"Checklist items to append, one indented [ ] line each, under the task's existing items — never replaces them. Can be combined with any other change; not appended when the same call removes the task (on_completion delete). For full sub-tasks with metadata, use vault_create_task with parent_block_id."New value: +"Checklist items to append, one indented todo line each, under the task's existing items — never replaces them. Can be combined with any other change; not appended when the same call removes the task (on_completion delete). For full sub-tasks with metadata, use vault_create_task with parent_block_id."
  2. Changed5 schema fields changedv0.53.0
    • addedInput schema / properties / position / anyOf
      Added value: +[
      +  {
      +    "enum": [
      +      "top",
      +      "bottom"
      +    ],
      +    "type": "string"
      +  },
      +  {
      +    "maximum": 9007199254740991,
      +    "minimum": 1,
      +    "type": "integer"
      +  }
      +]
    • removedInput schema / properties / position / default
      Removed value: -"top"
    • changedInput schema / properties / position / description
      Previous value: -"Where within the target heading the task lands after a heading move or auto-done-lane move. Defaults to \"top\". Ignored when no heading move occurs."New value: +"Where within the target heading the task lands after a heading move or auto-done-lane move. \"top\" or \"bottom\" for the extremes; an integer (1-based) for an exact position among the lane's top-level cards (sub-tasks move with their parent and are not counted). Position 1 is the first card. A position past the card count lands directly below the last card (unlike \"bottom\", which appends after any trailing section content). Defaults to \"top\" on heading moves. Without a heading, triggers a same-lane reorder to the given position; omitting position entirely performs no reorder. Ignored when the task is deleted on completion. Not valid on sub-tasks."
    • removedInput schema / properties / position / enum
      Removed value: -[
      -  "top",
      -  "bottom"
      -]
    • removedInput schema / properties / position / type
      Removed value: -"string"
  3. Changed3 schema fields changedv0.51.1
    • changedInput schema / properties / add_subtasks / description
      Previous value: -"Checklist items to append, one indented [ ] line each, under the task's existing items — never replaces them. Can be combined with any other change. For full sub-tasks with metadata, use vault_create_task with parent_block_id."New value: +"Checklist items to append, one indented [ ] line each, under the task's existing items — never replaces them. Can be combined with any other change; not appended when the same call removes the task (on_completion delete). For full sub-tasks with metadata, use vault_create_task with parent_block_id."
    • addedInput schema / properties / on_completion
      Added value: +{
      +  "anyOf": [
      +    {
      +      "enum": [
      +        "delete",
      +        "keep"
      +      ],
      +      "type": "string"
      +    },
      +    {
      +      "type": "null"
      +    }
      +  ],
      +  "description": "Tasks plugin 🏁 onCompletion action to set, or null to remove it. \"delete\" removes the task line on completion; \"keep\" leaves it in place (which is also the behavior when no 🏁 field exists on the task). Omitting this parameter leaves the field unchanged."
      +}
    • changedInput schema / properties / status / description
      Previous value: -"Target status. \"done\" appends the ✅ date and, on a Kanban board, moves the card and its checklist sub-items to the done lane (sub-item checkboxes are left as they are). \"cancelled\" appends the ❌ date."New value: +"Target status. \"done\" appends the ✅ date and, on a Kanban board, moves the card and its checklist sub-items to the done lane (sub-item checkboxes are left as they are); a task with 🏁 delete / [onCompletion:: delete] is removed from the file instead. \"cancelled\" appends the ❌ date."
  4. Changed1 schema field changedv0.51.0
    • addedInput schema / properties / recurrence
      Added value: +{
      +  "anyOf": [
      +    {
      +      "minLength": 1,
      +      "type": "string"
      +    },
      +    {
      +      "type": "null"
      +    }
      +  ],
      +  "description": "Tasks plugin 🔁 rule in natural language (e.g. \"every week\", \"every 2 weeks when done\") to set, or null to remove it. Completing the task spawns its next occurrence."
      +}
  5. Changed1 schema field changedv0.50.0
    • addedInput schema / properties / position / default
      Added value: +"top"
  6. Changed18 schema fields changedv0.41.2
    • addedInput schema / properties / add_subtasks
      Added value: +{
      +  "description": "Checklist items to append, one indented [ ] line each, under the task's existing items — never replaces them. Can be combined with any other change. For full sub-tasks with metadata, use vault_create_task with parent_block_id.",
      +  "items": {
      +    "minLength": 1,
      +    "type": "string"
      +  },
      +  "minItems": 1,
      +  "type": "array"
      +}
    • addedInput schema / properties / assign_block_id
      Added value: +{
      +  "description": "Add or replace the ^block-id on the task line. Letters, digits, and hyphens only; must be unique within the note.",
      +  "minLength": 1,
      +  "type": "string"
      +}
    • addedInput schema / properties / created
      Added value: +{
      +  "anyOf": [
      +    {
      +      "minLength": 1,
      +      "type": "string"
      +    },
      +    {
      +      "type": "null"
      +    }
      +  ],
      +  "description": "Created date (YYYY-MM-DD) to set or clear. Typically auto-stamped; use for corrections."
      +}
    • addedInput schema / properties / depends_on
      Added value: +{
      +  "anyOf": [
      +    {
      +      "items": {
      +        "minLength": 1,
      +        "type": "string"
      +      },
      +      "minItems": 1,
      +      "type": "array"
      +    },
      +    {
      +      "type": "null"
      +    }
      +  ],
      +  "description": "Tasks plugin ⛔ dependency IDs to set (non-empty), or null to clear."
      +}
    • addedInput schema / properties / description
      Added value: +{
      +  "description": "New task description text. Replaces the existing description; metadata fields and block_id are preserved.",
      +  "minLength": 1,
      +  "type": "string"
      +}
    • addedInput schema / properties / due
      Added value: +{
      +  "anyOf": [
      +    {
      +      "minLength": 1,
      +      "type": "string"
      +    },
      +    {
      +      "type": "null"
      +    }
      +  ],
      +  "description": "Due date (YYYY-MM-DD) to set, or null to clear."
      +}
    • changedInput schema / properties / format / description
      Previous value: -"Field format for new metadata (done dates, priority). Overrides the auto-detected Tasks plugin config. Default: auto-detected from .obsidian/ config, falling back to emoji."New value: +"Field format for new metadata. Default: auto-detected from .obsidian/ config, falling back to emoji."
    • addedInput schema / properties / heading
      Added value: +{
      +  "description": "Target heading to move the task to. On Kanban boards this is a lane move; works on any note with headings. Not valid on sub-tasks.",
      +  "minLength": 1,
      +  "type": "string"
      +}
    • removedInput schema / properties / lane
      Removed value: -{
      -  "description": "Target Kanban lane heading for a lane move. Only valid on Kanban boards.",
      -  "minLength": 1,
      -  "type": "string"
      -}
    • addedInput schema / properties / position
      Added value: +{
      +  "description": "Where within the target heading the task lands after a heading move or auto-done-lane move. Defaults to \"top\". Ignored when no heading move occurs.",
      +  "enum": [
      +    "top",
      +    "bottom"
      +  ],
      +  "type": "string"
      +}
    • addedInput schema / properties / priority / anyOf
      Added value: +[
      +  {
      +    "enum": [
      +      "highest",
      +      "high",
      +      "medium",
      +      "low",
      +      "lowest"
      +    ],
      +    "type": "string"
      +  },
      +  {
      +    "type": "null"
      +  }
      +]
    • changedInput schema / properties / priority / description
      Previous value: -"Target priority. \"none\" removes the priority emoji."New value: +"Priority signifier to set, or null to remove it."
    • removedInput schema / properties / priority / enum
      Removed value: -[
      -  "highest",
      -  "high",
      -  "medium",
      -  "low",
      -  "lowest",
      -  "none"
      -]
    • removedInput schema / properties / priority / type
      Removed value: -"string"
    • addedInput schema / properties / scheduled
      Added value: +{
      +  "anyOf": [
      +    {
      +      "minLength": 1,
      +      "type": "string"
      +    },
      +    {
      +      "type": "null"
      +    }
      +  ],
      +  "description": "Scheduled date (YYYY-MM-DD) to set, or null to clear."
      +}
    • addedInput schema / properties / start
      Added value: +{
      +  "anyOf": [
      +    {
      +      "minLength": 1,
      +      "type": "string"
      +    },
      +    {
      +      "type": "null"
      +    }
      +  ],
      +  "description": "Start date (YYYY-MM-DD) to set, or null to clear."
      +}
    • changedInput schema / properties / status / description
      Previous value: -"Target status. \"done\" appends ✅ date and auto-moves to done lane on Kanban boards. \"cancelled\" appends ❌ date."New value: +"Target status. \"done\" appends the ✅ date and, on a Kanban board, moves the card and its checklist sub-items to the done lane (sub-item checkboxes are left as they are). \"cancelled\" appends the ❌ date."
    • addedInput schema / properties / task_id
      Added value: +{
      +  "anyOf": [
      +    {
      +      "minLength": 1,
      +      "type": "string"
      +    },
      +    {
      +      "type": "null"
      +    }
      +  ],
      +  "description": "Tasks plugin 🆔 identifier to set, or null to clear."
      +}
  7. Addedv0.32.1
  8. Removedv0.32.0
  9. Addedv0.27.2

TDQS

A4.9/5.0
Behavior5/5

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

Beyond annotations that mark the tool as destructive and non-idempotent, the description discloses numerous behavioral traits: completion of recurring tasks spawns next occurrences, on_completion delete removes the line, position past the card count lands below the last card, explicit null is required to clear fields, and Obsidian syntax can cause advisories when metadata round-trips differently. These details give the agent a full picture of side effects.

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

Conciseness4/5

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

The description is extremely long but well-structured: it front-loads a one-line summary, followed by examples, 'When to use', parameter details, error list, Obsidian syntax caveats, and return format. Every sentence adds value for this complex tool, but the sheer length may be overwhelming; a slightly more compact organization would earn a 5.

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?

The description is comprehensive: it covers required identification (path + block_id or line), optional fields and clearing semantics, all error cases with exact strings, the return object structure, and Obsidian syntax pitfalls. Given the tool's complexity and the lack of an output schema, this description leaves no ambiguity about correct invocation or expected behavior.

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

Parameters5/5

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

Although the schema covers all parameters at 100%, the description adds substantial meaning beyond the schema: it explains the interaction between status and on_completion, the position semantics ('past the card count lands directly below the last card'), the recurrence rule natural-language syntax, and provides concrete examples for every parameter combination. This exceeds the schema's baseline descriptions.

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 description opens with a precise verb and resource: 'Update a task's status, priority, description, dates, dependencies, block_id, checklist items, or heading placement in one call.' It enumerates the exact fields and explicitly names the alternative tool for creation ('For creating a new task, use vault_create_task instead'), distinguishing it from siblings without requiring schema inspection.

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?

The 'When to use' section explicitly states the conditions for using this tool versus alternatives: any change to an existing task, and directs agents to vault_list_tasks first for identification and to vault_create_task for new tasks. It also clarifies when not to use it (creation).

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