Generation pipeline
The six-step pipeline that turns an `intent` into a four-layer persona — Intent parse → Soul draft → Self+Mask enrich → Surface ground → Audit → Fingerprint + Test.
When you POST /v1/personas with an intent, Moonborn doesn't ask a
single LLM to invent a character. It runs a six-step pipeline whose
output is the persona's persistent state. Every step is configurable —
model, provider, temperature, fallback chain — via the config tree
under engine.pipeline.<step>.*.
The steps
| # | Step | Purpose | Default model |
|---|---|---|---|
| 1 | intent_parse | Read the raw brief; extract genre, locale, axis hints | claude-sonnet-4-6 |
| 2 | soul_draft | Write Soul (desire, fear, wound, growth arc) | claude-opus-4-7 |
| 3 | self_enrich | Big Five + archetype + values + attachment | claude-sonnet-4-6 |
| 4 | mask_build | Voice, tone, signature phrases, social role | claude-sonnet-4-6 |
| 5 | surface_ground | Name, age, location, occupation, appearance | claude-sonnet-4-6 |
| 6 | audit | Cross-layer coherence + LLM-as-judge | claude-opus-4-7 |
Note — these defaults are read from
engine.pipeline.<step>.modelconfig items. The values listed above are the system-scope defaults; org/workspace overrides apply.
After the six visible steps, the runtime triggers two post-generation jobs:
- Voice fingerprint — fifty short scenario completions embedded into a per-persona signature (Voice fingerprint).
- Test suite — the configurable provocation catalog runs against the new persona (Audit + provocation tests).
Why inside-out
Surface (name, age, job) is the last step on purpose. If we wrote Surface first, the model would anchor on the demographic cliché and back-fill Soul to match. By drafting Soul → Self → Mask → Surface, each layer constrains the next: the founder's restlessness comes from her wound, not from a stock template.
Streaming
Pass stream: true to POST /v1/personas and the response becomes
text/event-stream. Each event is one of:
event: step.started { step: "soul_draft" }
event: step.completed { step: "soul_draft", durationMs: 4210 }
event: step.failed { step: "audit", error: { code, message } }
event: pipeline.completed { id, status, pipelineRunId }
The pipeline run ID returned in the final event lets you fetch the full trace later (provider latencies, retries, cost breakdown) — see the audit log endpoints in API reference.
Retries + fallback
If a step fails (LLM provider error, audit below threshold), Moonborn
retries up to engine.pipeline.<step>.max_retries (default 3) using
the configured fallback chain (default: Anthropic → OpenAI → Google).
After the cap, the persona is delivered in flagged status — the user
sees it, but it's marked for review.
Honest scope
This is the server-side pipeline. There's no client-side step
implementation; you can't run an isolated step from the SDK. The
client surface is POST /v1/personas (full pipeline) and
POST /v1/personas/{id}/regenerate (re-run from a chosen step).