---
title: Prompt Variant Catalog
type: text
description: >-
  Per-role prompt-variant catalog above model-role dispatch, so one metadata role can carry several
  per-model calibrations and bench drivers can A/B them by flag.
base_uri: sys:libs-server/prompt/ABOUT.md
created_at: '2026-11-02T16:43:26.931Z'
entity_id: 7aa3a42a-9c8c-4526-98a2-52cc2baf0f06
owner_identity_uri: █████████████████████████
updated_at: '2026-11-02T16:43:27.117Z'
---

# prompt

Per-role prompt-variant catalog. Sits one layer above `model-roles/dispatch-role.mjs` so each metadata role can carry multiple per-model calibrations (e.g., `qwen3-thinkoff` for the thinking-off mode of `qwen3.6-35b-a3b-4bit`) without cross-contamination, and so bench drivers can A/B variants via a flag.

## Files

- `loader.mjs` — `load_prompt_variant({ role, variant })`. Static variant map; throws on unknown role or variant. Default fallback when `variant` is omitted. Compiled-binary safe (no `import.meta.url` path math).
- `parsers.mjs` — shape parsers: `parse_message_shape`, `parse_metadata_shape`, `parse_tag_shape`. Plus `truncate_with_ellipsis`/`truncate_hard` helpers. Internally consumes `extract_json_from_response` from `libs-server/metadata/parse-analysis-output.mjs`. Not a re-export barrel.
- `<role>/default.mjs` — one variant module per role. Five today: `commit-message-writer`, `title-generator`, `tag-classifier`, `content-classifier`, `content-review`.
- `tag-classifier/format-tags.mjs` — taxonomy helpers (`TAG_CONSTRAINTS`, `format_tags_for_prompt`, `load_tags_with_content`, `compute_taxonomy_hash`). Extracted from `default.mjs` to keep the variant body under the write-javascript 200-line soft cap.
- `tag-classifier/heuristics.mjs` — ranking rules and boundary exemplars. Same extraction rationale.
- `tag-classifier/heuristics-feed-item.mjs` and `heuristics-feed-item-combined.mjs` — the feed-item
  ranking rules, **one file per variant and they do NOT share content.** `feed-item.mjs` (two-pass)
  reads the first, `feed-item-combined.mjs` (single-call) reads the second, so a rule added to one
  variant is absent from the other. This is the trap worth knowing before you measure: production
  classification runs the TWO-PASS variant, while `benchmark-content-classifier.mjs --combined`
  exercises the other file — so benchmarking a two-pass edit with `--combined` scores a prompt that
  never received it and reports a clean no-change, which reads as "my edit did nothing" rather than
  as "I measured the wrong variant". Match the flag to the file you edited.
- The two-pass variant issues a SEPARATE request per axis (`feed_heuristics(axis)`), so a
  content-type rule cannot affect a domain-axis result. That separation is the discriminator when a
  run reports parse failures on both axes: the domain ones cannot be attributable to a content-type
  edit, and `feed_heuristics('domain')` can be printed to prove it rather than argued.

## Variant export contract

Every variant exports the same five names:

```js
export const VERSION = 1                  // bump invalidates per-variant version-gated state
export const THINKING = false             // forwarded to dispatch_role as `thinking`
export const OUTPUT_SCHEMA = { ... }      // role's response schema
export const generate = (input) => '...'  // builds prompt body from named-parameter input
export const parse = (output) => ...      // returns role-shaped object
```

`THINKING` lives on the variant, not the role, because the calibration exists for a specific thinking mode — re-enabling thinking would invalidate the variant's assumptions.

## Variant naming convention

`<model-family>[-<thinking-mode>]`. Quant size is intentionally excluded (same calibration applies whether 4-bit, 6-bit, or DWQ serves it). Examples: `default`, `qwen3-thinkoff`, `gemma4`.

## Adding a new variant

1. Create `libs-server/prompt/<role>/<variant>.mjs` exporting the five names.
2. Register it in `loader.mjs`'s static variants map: `<role>: { ..., <variant>: () => import('./<role>/<variant>.mjs') }`.
3. Set `model_roles.roles.<role>.prompt_variant = "<variant>"` in `config/config.json` to flip production routing, or pass `--variant <name>` to the bench driver for an A/B run.

See `text/base/model-dispatch-architecture.md` § Prompt Selection Layer for the full design.
