Brand voice profile
A voice profile describes how a project's content should sound: its tone, its style rules, the vocabulary it prefers and forbids, and worked examples of the voice being got right and wrong. Brand checks score content against it, and generation steps are steered by it.
It is the one serialization in this family that is YAML rather than JSON.
Implemented in Go at
core/brand.
Why YAML
Every other format here is machine-produced — exported from a store, packed by a command, canonicalized for hashing. A voice profile is the opposite: it is authored and edited by a person, and it is mostly prose. Tone descriptions, style rules, and the worked examples are multi-line English text.
YAML carries multi-line prose without escaping it, allows comments explaining why a rule exists, and diffs a reworded paragraph legibly. JSON does none of those well. The type also carries JSON tags, so the same profile serializes to JSON when it moves over an API or into a store — the YAML is the authoring form, not the only form.
Resolution ladder
When a command needs a profile and no --profile, --profile-file, or
--pack flag is given, kapi resolves one in this order:
- The recipe binding —
defaults.brand_voiceinkapi.yaml, which may name aprofile_file, apack, or a profile in the local brand store. <project-root>/brand.yaml<project-root>/.kapi/brand.yaml
The binding wins. Rungs 2 and 3 are conventions that make
kapi brand check DRAFT.md work flag-free inside a project, not the primary
mechanism — a project that has bound a profile never reaches them.
--profile, --profile-file, and --pack are mutually exclusive; passing
more than one is an error rather than a silent precedence.
Fields
| Field | Type | Notes |
|---|---|---|
name | string | Required. |
id | string | Optional identity. |
description | string | Optional. |
tone | object | The tone profile. |
style | object | Style rules. |
vocabulary | object | Preferred, forbidden, and cautioned terms. |
examples | array | Worked examples of the voice. |
locales | map | Per-locale overrides, keyed by locale id. |
channels | map | Per-channel overrides. |
personas | map | Per-persona overrides. |
autonomy | object | How far automated steps may act on the profile. |
min_score | int | The on-brand bar, 0–100. |
version, version_note | int, string | Profile revision and a note describing it. |
created_at, updated_at, created_by | — | Provenance. |
workspace_id | string | Set when the profile came from a workspace. |
Overrides
locales, channels, and personas each hold partial overrides layered onto
the base profile at resolution time. A profile can therefore say "this is the
voice" once and then vary it — terser in a push notification, more formal in
de-DE, warmer for a particular persona — without duplicating the whole
document per variant.
Resolution applies the requested locale, channel, and persona to produce one effective profile; the file itself stays the single authored source.
The on-brand bar
min_score is the minimum brand-compliance score a block must reach to count
as on-brand in roll-ups. When a profile does not set one, the default applies:
a bar calibrated so that a single critical vocabulary hit already drops a block
below it, while a handful of minor issues does not.
A profile that cannot be read resolves to the default bar, so roll-ups over historical scores stay comparable.
What is deliberately not in it
- Scores and findings. Those are check results computed against the profile, stored with the content or the run — never written back here.
- The compiled brand store.
brand.dbis derived; the profile is the source. - Locale-specific content. Overrides adjust voice, not copy. Translated strings belong in the deliverable.