name: add-persona description: Create reusable persona system prompts for Prompture. Covers Persona dataclass, template variables, composition, trait registry, global registry, serialization, and Conversation integration. Use when defining system prompts for extraction or agent behavior. metadata: author: prompture version: "1.0"
Add a Persona
Creates reusable, composable system prompt definitions with template variables and a global registry.
Before Starting
Ask the user for:
- Persona name (lowercase_with_underscores, used as registry key)
- Purpose — what the persona should do (extract data, analyze, summarize, etc.)
- System prompt — the core instruction text
- Template variables — any
{{variable}}placeholders and their defaults - Constraints — rules the persona must follow
- Model hint — suggested model string (optional, e.g.
"openai/gpt-4o") - Settings — default driver options (optional, e.g.
{"temperature": 0.0})
Key Concepts
Persona Dataclass (frozen/immutable)
from prompture.persona import Persona
persona = Persona(
name="invoice_extractor",
system_prompt=(
"You are a financial document processor. "
"Extract invoice data from the provided text and return structured JSON. "
"Use {{currency}} as the default currency when not specified."
),
description="Invoice data extraction with configurable currency.",
traits=("precise_output",), # Trait names from trait registry
variables={"currency": "USD"}, # Default template variable values
constraints=[ # Appended as ## Constraints section
"Output ONLY valid JSON.",
"Use null for unknown values.",
"Amounts must be numeric, not strings.",
],
model_hint="openai/gpt-4o-mini", # Suggested model
settings={"temperature": 0.0}, # Default driver options
)
Persona Fields
| Field | Type | Description |
|---|---|---|
name |
str |
Unique identifier (required) |
system_prompt |
str |
Template text with {{variable}} placeholders (required) |
description |
str |
Human-readable purpose description |
traits |
tuple[str, ...] |
Trait names resolved from trait registry during render() |
variables |
dict[str, Any] |
Default template variable values |
constraints |
list[str] |
Rules appended as a ## Constraints section |
model_hint |
str | None |
Suggested "provider/model" string |
settings |
dict[str, Any] |
Default driver options (temperature, etc.) |
Template Variables
Placeholders use {{variable}} syntax. Built-in variables are always available:
| Variable | Example Value |
|---|---|
{{current_year}} |
2026 |
{{current_date}} |
2026-02-01 |
{{current_month}} |
February |
{{current_day_of_week}} |
Sunday |
Custom variables are set via variables dict or at render time:
rendered = persona.render(currency="EUR", max_items="10")
Variable precedence (highest wins):
kwargspassed torender()self.variables(defaults)- Built-in template variables
Rendering
prompt = persona.render() # Uses defaults
prompt = persona.render(currency="EUR") # Override variable
prompt = persona.render(custom_var="custom_value") # Additional variable
Composition
Personas are immutable. Composition returns new instances:
# Extend — append additional instructions
specialized = persona.extend("Focus specifically on line items and tax calculations.")
# Add constraints
stricter = persona.with_constraints([
"All dates must be in ISO 8601 format.",
"Round amounts to 2 decimal places.",
])
# Merge two personas with + operator
# Right-side values win on variable/settings conflicts
combined = base_persona + domain_persona
# Result: name="base+domain", prompts concatenated, constraints merged,
# variables merged (right wins), traits deduped
Trait Registry
Reusable prompt fragments that can be shared across personas:
from prompture.persona import register_trait, get_trait, get_trait_names
# Register traits
register_trait("precise_output", "Always output precisely formatted data with no extraneous text.")
register_trait("multilingual", "Support input in any language. Always respond in {{output_language}}.")
# Use in persona
persona = Persona(
name="my_persona",
system_prompt="...",
traits=("precise_output", "multilingual"),
variables={"output_language": "English"},
)
# Traits are resolved and appended during render()
Global Persona Registry
from prompture.persona import (
register_persona, get_persona, get_persona_names,
clear_persona_registry, reset_persona_registry,
PERSONAS, # Dict-like proxy
)
# Register
register_persona(persona)
# Retrieve
p = get_persona("invoice_extractor") # Returns Persona or None
p = PERSONAS["invoice_extractor"] # Returns Persona or raises KeyError
# List
names = get_persona_names() # ["json_extractor", "data_analyst", ...]
# Dict-like access via PERSONAS proxy
PERSONAS["my_persona"] = persona # Register
del PERSONAS["my_persona"] # Unregister
"my_persona" in PERSONAS # Check existence
len(PERSONAS) # Count
# Reset to built-in only
reset_persona_registry()
Built-in Personas
5 personas are registered on import:
| Name | Description |
|---|---|
json_extractor |
Precise JSON extraction (temperature=0.0) |
data_analyst |
Quantitative analysis with citations |
text_summarizer |
Configurable summarization ({{max_sentences}} variable) |
code_reviewer |
Structured code review (Summary/Issues/Suggestions) |
concise_assistant |
Brief, no-elaboration responses |
Serialization
# JSON
persona.save_json("personas/invoice_extractor.json")
loaded = Persona.load_json("personas/invoice_extractor.json")
# YAML (requires pyyaml)
persona.save_yaml("personas/invoice_extractor.yaml")
loaded = Persona.load_yaml("personas/invoice_extractor.yaml")
# Dict round-trip
d = persona.to_dict() # {"version": 1, "name": ..., "system_prompt": ..., ...}
p = Persona.from_dict(d)
# Bulk load from directory (registers all loaded personas)
from prompture.persona import load_personas_from_directory
personas = load_personas_from_directory("personas/")
Conversation Integration
from prompture import Conversation
conv = Conversation(
driver=driver,
system_prompt=persona.render(),
options=persona.settings,
)
result = conv.ask("Extract data from this invoice: ...")
Or use model_hint to select the driver:
from prompture.drivers import get_driver_for_model
if persona.model_hint:
driver = get_driver_for_model(persona.model_hint)
Implementation Steps
1. Define the persona
Create in the appropriate module or a personas directory:
from prompture.persona import Persona, register_persona
my_persona = Persona(
name="my_persona",
system_prompt="...",
description="...",
constraints=["..."],
settings={"temperature": 0.0},
)
register_persona(my_persona)
2. Add traits if needed
from prompture.persona import register_trait
register_trait("my_trait", "Trait text with {{variables}}.")
3. Write tests
def test_persona_render():
p = Persona(name="test", system_prompt="Hello {{name}}")
assert "Hello World" == p.render(name="World")
def test_persona_constraints():
p = Persona(name="test", system_prompt="Base", constraints=["Rule 1"])
rendered = p.render()
assert "## Constraints" in rendered
assert "Rule 1" in rendered
def test_persona_composition():
a = Persona(name="a", system_prompt="A")
b = Persona(name="b", system_prompt="B")
c = a + b
assert c.name == "a+b"
assert "A" in c.render()
assert "B" in c.render()
Best Practices
- Use template variables for anything that might change between uses (language, limits, format preferences)
- Keep system prompts focused — one clear role per persona
- Use constraints for rules — they're appended as a structured section, easy to compose
- Use traits for shared fragments — DRY across personas (e.g., "output JSON only" trait)
- Set
temperature: 0.0for extraction personas — deterministic output is preferred - Use
model_hintto suggest the best model for the persona's task - Serialize to YAML for human-editable persona libraries
- Use
load_personas_from_directory()to load persona libraries at startup