Open app
Moonborn — Developers

Fork brand variants

Take a canonical brand persona, fork it into formal / casual / locale-specific variants without losing the through-line. Walk through Refine, lineage, distinctiveness.

Brand teams typically maintain multiple voice variants — a serious B2B tone, a playful social tone, an EN and a DE version. Hand-curated that's six prompts to keep in sync. Forking turns it into a single canonical persona with a tracked tree of variants.

1. Build the canonical persona

Start with the most authoritative voice — the one others will fork from. Treat its Soul + Self as the "brand DNA"; downstream variants should preserve those layers.

const brand = await client.personas.createPersona({
  intent:
    'Brand voice for Moonborn — Character Consistency Engine. Scholarly, character-centric, technically precise. Editorial restraint. Lead with concrete detail, avoid sales fluff.',
  workspaceId: 'ws_...',
});

Audit and provocation tests run automatically; aim for ≥ 4.0/5 on the audit before forking. If lower, refine before adding variants.

2. Fork a formal variant

POST /v1/personas/{id}/fork copies the persona and writes the parent edge. Pair it with a refine argument to apply an axis transform in the same call:

const formal = await client.personas.fork({
  id: brand.id,
  refine: {
    mode: 'refine',
    layer: 'mask',
    axis: 'more-authoritative',
    amount: 0.4,
  },
  note: 'Sales materials variant',
});

The fork keeps Soul + Self locked (the brand DNA) and rewrites only the Mask. Audit re-runs against the new Mask; distinctiveness compares the formal variant against the canonical (custom baseline) to catch drift toward a generic corporate voice.

3. Fork a casual variant

const casual = await client.personas.fork({
  id: brand.id,
  refine: {
    mode: 'refine',
    layer: 'mask',
    axis: 'warmer',
    amount: 0.45,
  },
  note: 'Social media + community variant',
});

4. Fork a Turkish locale variant

For multi-locale, refine the Surface (language + cultural details) while keeping Soul / Self / Mask locked:

const tr = await client.personas.fork({
  id: brand.id,
  refine: {
    mode: 'lock',
    lockLayers: ['soul', 'self', 'mask'],
    edits: {
      'surface.language': 'tr',
      'surface.location': 'İstanbul, Türkiye',
    },
  },
  note: 'TR locale variant',
});

5. Audit the variant set

const lineage = await client.personas.getLineage({ id: brand.id });
console.log(lineage.children.map((c) => c.id));
 
// Are the variants drifting toward each other?
const compare = await client.consistency.compareWithOrgPersonas({
  personaId: formal.id,
  threshold: 0.30,
});
console.log(compare.closest);

The compareWithOrgPersonas query returns the closest persona by voice fingerprint distance. If your "formal" and "casual" variants score < 0.15 against each other, they collapsed during refine — the axis amount was too small, or the canonical Mask already encoded too much variability. Re-fork with a larger amount or refine Soul instead (a Cascade mode edit).

6. Deploy

Each variant exposes its own persona ID. Wire each ID into the appropriate channel:

  • formal.id → your sales email automation.
  • casual.id → your social inbox.
  • tr.id → your Turkish-locale support agent.

Through the OpenAI-compatible endpoint, the variant ID is the model field:

import OpenAI from 'openai';
const oai = new OpenAI({
  apiKey: process.env.MOONBORN_API_KEY,
  baseURL: 'https://api.moonborn.co/v1/openai',
});
const reply = await oai.chat.completions.create({
  model: `persona://${formal.id}`,
  messages: [{ role: 'user', content: 'Tell me about Moonborn.' }],
});

Next