@pugi/plugin-personas
v0.1.0-alpha.2
Published
Pugi persona registry plugin - loads curated persona packs plus user overrides, classifies user messages, and injects layered system prompts into the Pugi chat surface.
Downloads
68
Maintainers
Readme
@pugi/plugin-personas
Persona registry + keyword classifier + system-prompt assembly for the Pugi runtime. Ships as part of the Pugi 1.0 soft fork sprint (see ADR-0081).
The npm name is
@pugi/plugin-personas. There is also a sibling package@pugi/personasin this monorepo that ships the Cyber-Zoo brand persona roster used by the landing and Console. The two coexist by intent. The brand roster does not import this plugin and this plugin does not import the brand roster.
What it does
- Loads a curated persona pack at startup (
pugi-default8 personas orpugi-extended16 personas), then optionally merges in*.persona.jsonfiles from a configurablepersonaDir. - Caches the last user-message text per session via the
chat.messagehook (bounded LRU, 256 sessions). - Resolves the active persona for each session: explicit override first, else keyword classifier, else the configured default.
- Pushes a layered system-prompt block to
experimental.chat.system.transform. Layered budget: Pugi global rules + persona prompt + few-shot examples, trimmed to a configurable per-persona token budget (default 1500 tokens). - Exposes
pugiPersonaPreferredModel,pugiPersonaFallbackModel, andpugiPersonaSlugon thechat.paramsoptionsbag so a future@pugi/plugin-anvil-providerpatch can route on persona. - Registers tools
pugi.persona.list,pugi.persona.get,pugi.persona.active,pugi.persona.switch. - Registers the slash command
/persona <slug> [reason]so the operator can pin the persona mid-session.
Install
pnpm add @pugi/plugin-personasConfiguration
// pugi.config.ts
import type { Config } from '@pugi-ai/plugin';
export default {
plugin: [
[
'@pugi/plugin-personas',
{
personaPack: 'pugi-default', // or 'pugi-extended' or 'custom'
personaDir: '~/.pugi/personas', // user-supplied overrides
defaultPersona: 'pugi', // coordinator (never 'mira', never 'oes-dev')
enableAnimalAvatars: false, // TUI text only; web avatars opt-in
personaSlugRoutingMap: { // exact substring overrides for the classifier
'design system review': 'marcus',
},
},
],
],
} satisfies Config;Persona schema
Each persona is a JSON object:
{
"slug": "marcus", // /^[a-z][a-z0-9-]*$/, never reserved
"name": "Marcus",
"role": "architect",
"expertise": ["system-design", "adr-writing"],
"systemPrompt": "You are Marcus, the system architect ...",
"fewShotExamples": [ // optional, capped at 3
{ "user": "...", "assistant": "..." }
],
"preferredModel": "qwen3-coder-480b",
"fallbackModel": "deepseek-coder-v2-16b",
"brand": {
"animal": "wolf", // informational; TUI never renders it
"accentColor": "oklch(0.45 0.10 260)",
"tagline": "System architect."
},
"voiceRules": ["pugi-voice-default"]
}Roles (capability scopes)
Roles and personas are orthogonal. A user-facing surface displays the persona name; the role is the SDLC capability scope it covers.
| Role | Persona examples |
| --- | --- |
| coordinator | Pugi |
| product-planner | Olivia, Sofia, Yuki |
| architect | Marcus, Sigma |
| dev-producer | Olivia, Daniel |
| builder | Hiroshi, Tom |
| reviewer | Priya, Omar, Anika |
| release | Diego |
| out-of-sdlc | Hannah, Mateo, Lena |
Bundled pack matrix
pugi-default (8): pugi, hiroshi, marcus, olivia, priya, omar, diego, sigma.
pugi-extended (16): the eight above plus sofia, yuki, daniel, tom,
anika, hannah, mateo, lena.
Routing logic
The classifier is keyword-based and deterministic (no LLM). Precedence:
- Explicit
personaSlugRoutingMapsubstring match (operator-set). - Ranked keyword rules (security, devops, architecture, review, product, QA, builder).
- Default fallback (
defaultPersona, defaultpugi).
If the classifier ever returns a reserved or unknown slug the
registry refuses it and the coordinator (pugi) takes over. The
oes-dev slug is never selected as a fallback, ever.
Safety rails
mira,codeforge,oes-devare reserved slugs. Custom files using them get rejected with a startup warning.- Any
Mirastring inside a personasystemPromptis rewritten toPugiwith amira-mention-rewrittenwarning. Codeforgestrings inside persona content are rewritten toPugi.
Token budget
Total prompt budget before the user message: 8000 tokens.
| Layer | Budget (approx) | | --- | --- | | Pugi global rules | ~80 tokens | | Active persona prompt + tagline | 1500 tokens (configurable) | | Few-shot examples (max 3) | sized to fit within persona budget | | Other plugins (codegraph, RAG) | up to the remaining ~6400 tokens |
Token approximation: Math.ceil(textLength / 4). Same convention as
@pugi/plugin-codegraph.
Console / Web surface
The plugin runs in the Pugi server, not in the Console web app.
The console-web app consumes persona data via the separate
@pugi/personas brand roster, which exports a curated subset
(THE_TEN). The plugin's persona registry is exclusively for
runtime system-prompt shaping.
Tools
| Tool | Args | Returns |
| --- | --- | --- |
| pugi.persona.list | {} | array of {slug,name,role,tagline} |
| pugi.persona.get | {slug} | full Persona JSON or {error:'not-found'} |
| pugi.persona.active | {} | {slug, sessionID} |
| pugi.persona.switch | {slug, reason?} | text confirmation |
Slash command
/persona <slug> [reason] switches the active persona for the
session. /persona with no args lists every loaded persona and the
matching role.
License
MIT. See LICENSE.
