Note Taxonomy

Per-workspace tag vocabulary and folder routing rules for BlackOps notes. Defines the known tags, decides which folder a note's markdown lands in when written to your repo, and surfaces soft warnings when the AI uses a tag outside your vocabulary — without ever blocking a write.

Overview

Every note in BlackOps carries tags and a note_type. Note Taxonomy turns those into structured taxonomy you control: a named vocabulary of tags, and an ordered list of routing rules that decide where the markdown file lands when synced to your vault.

The system has three jobs: keep AI-generated tags consistent with your conventions, make folder layout deterministic and configurable, and make all of it discoverable by MCP-connected assistants before they call post_notes or post_notes_write.

Key Capabilities

Tag Vocabulary

Per-workspace list of known tags with name, description, and color. Drives soft warnings on writes — unknown tags get a Levenshtein fuzzy-match suggestion. Writes are never blocked.

Folder Routing

Ordered rules match a note's tags or note_type to a destination folder. First match wins. Used by post_notes_write when no explicit relative path is provided.

list_notes MCP Tool

Filtered, paginated note queries for AI assistants — by tags, note_type, status, or full-text search across title and excerpt. Mirrors list_reservoir_items.

Pre-flight Awareness

The get_note_taxonomy MCP tool lets an AI fetch your vocabulary + routing rules before writing, so it picks known tags and predicts where a file will land.

How It Works

1. Manage your vocabulary in Settings

Open Settings → Note Taxonomy to add, edit, reorder, or remove tags. Each tag has a name, description, and color chip used in admin views. Removing a tag only removes it from the vocabulary — existing notes that use it are not modified.

2. Define folder routing rules

On the same settings page, build the ordered list of routing rules. Each rule has a condition (tag_match or note_type_match), a value to match, and a destination folder. Drag-style up/down controls set priority — first match wins.

A live preview at the bottom of the page shows where common tag/type combinations would route, so you can sanity-check changes before saving.

3. AI writes a note via MCP

When an AI assistant calls post_notes or patch_notes with tags, the response includes a warnings array listing any tags outside your vocabulary, with a suggestion if a known tag is within Levenshtein distance ≤ 2:

"warnings": [
  "Unknown tag: 'featurez' — did you mean 'feature'?",
  "Unknown tag: 'totallyunknown'"
]

The note is saved either way — warnings are advisory.

4. AI writes the file via post_notes_write

When the AI calls post_notes_write with a note_id + target_id and no explicit relative_path, the server:

  • Loads the note's tags and note_type
  • Walks the routing rules in priority order
  • Picks the first matching rule's folder, or the default folder
  • Writes the file to {folder}{slug}.md
  • Returns routing_rule_applied in the response so the AI can confirm where it landed

Explicit relative_path always wins — auto-routing is opt-out.

Defaults

Every workspace is seeded with sensible defaults on creation (via a database trigger, so the seed never depends on app code paths). You can edit, reorder, or replace any of them at any time.

Default tags

  • inbox — unsorted notes
  • feature — feature requests
  • bug — bugs and defects
  • blackops — BlackOps-specific work
  • daily — daily journal
  • project — project notes
  • reference — long-lived reference
  • cap — capture session notes

Default routing rules

  • tag featurefeatures/
  • tag bugbugs/
  • type dailydaily/
  • type projectprojects/
  • type referencereference/

Default folder for unmatched notes: inbox/

MCP Tools

get_note_taxonomy

Returns the active site's tag vocabulary, folder routing rules, and default folder. Call this before post_notes to pick known tags, or before post_notes_write to predict the destination folder.

list_notes

Filtered, paginated note query. Use this when get_recent_notes isn't specific enough.

  • tags + tags_match (default all, also any)
  • note_type, status (default active)
  • search — title + excerpt ILIKE
  • limit (default 20, max 200), offset
  • sort (created_at | updated_at | title), order (asc | desc)

API Reference

GET /api/v2/notes/taxonomy

Returns { tags, folder_routing, default_folder }. Drives get_note_taxonomy.

GET / POST /api/v2/notes/list

Filtered note query with full pagination. Drives list_notes.

GET / PUT /api/admin/notes/taxonomy

Admin read of the full taxonomy and PUT for the default folder. Used by the settings page.

POST / PATCH / DELETE /api/admin/notes/taxonomy/tags

CRUD + reorder for tag vocabulary. POST with { order: [id, ...] } reorders, otherwise creates.

POST / PATCH / DELETE /api/admin/notes/taxonomy/folder-rules

CRUD + reorder for routing rules. Same order contract as tags.

Database Schema

note_tags

Per-workspace tag vocabulary. Columns: id, workspace_id, name, description, color, sort_order. Unique on (workspace_id, name).

note_folder_rules

Per-workspace ordered routing rules. Columns: id, workspace_id, condition_type (tag_match | note_type_match), condition_value, folder_path, priority. Unique on (workspace_id, condition_type, condition_value).

workspace_note_settings

Single-row config per workspace. Columns: workspace_id (PK), default_folder.

Auto-seed trigger

seed_note_taxonomy_for_new_site() fires AFTER INSERT on sites, populating defaults so new workspaces never need manual setup. Existing workspaces were backfilled at migration time.

Things to Know

  • Tag warnings are advisory only — writes always succeed. Do not gate workflows on the absence of warnings.
  • Removing a tag from the vocabulary doesn't strip it from existing notes — they keep the tag and just become "unknown" until you re-add it or rename to a known tag.
  • Auto-routing only fires when post_notes_write gets a note_id with no relative_path or template. Brain-scoped writes still require an explicit relative_path under the brain's source folder.
  • Notes have tags stored as jsonb, not text[] — direct DB queries should use jsonb containment (tags @> '["feature"]').

Related