-
Notifications
You must be signed in to change notification settings - Fork 0
Writing Personas
A persona is a directory with a pack.toml manifest:
my-persona/
pack.toml
schema_version = 1
name = "my-persona"
author_handle = "your-handle"
author_pubkey = "UNSIGNED"
version = "0.1.0"
license = "Elastic-2.0"
[capability_manifest]
required_tools = ["Read", "Edit", "Write", "Bash", "Grep", "Glob"]
network_egress = false
filesystem_scope = "project-only"
memory_required = "none"
memory_required_ops = []Install it:
frameshift install my-persona@0.1.0 --from-path ./my-personaThe manifest declares what the persona needs from the host environment:
| Field | Type | Description |
|---|---|---|
required_tools |
string array | Tools the persona expects (Read, Edit, Write, Bash, Grep, Glob, etc.) |
network_egress |
bool | Whether the persona needs outbound network access |
filesystem_scope |
string |
"project-only" or "system"
|
memory_required |
string |
"none", "optional", or "required"
|
memory_required_ops |
string array | Memory operations needed: "store", "search", "recall", "context"
|
primary_intents |
string array | (optional) Task categories this persona handles best |
anti_keywords |
string array | (optional) Tokens that repel the selection engine away from this persona |
Ten recognized intents: implementation, debugging, review, security, writing, ops, testing, refactoring, performance, design.
Declaring primary intents lets automate mode match personas to tasks more accurately.
Anti-keywords score negatively in the selection pipeline. A cryptographic persona might declare anti_keywords = ["frontend", "css", "react"] to avoid being selected for UI work.
Beyond the pack manifest, Frameshift supports a structured TOML source format with four files:
my-persona/
pack.toml # Manifest
persona.toml # Identity, voice, anchor, classification tiers, self-eval
rules.toml # L1/L2 rules with reasoning
skills.toml # Skill declarations
patterns.toml # Code patterns and anti-patterns
Defines identity, voice, cascade anchors, classification tiers, conflict resolution stance, and self-evaluation hooks.
schema_version = 1
name = "my-persona"
version = "0.1.0"
description = "One-line description of what wakes up."
license = "Elastic-2.0"
[author]
handle = "your-handle"
pubkey = "ed25519:UNSIGNED"
[anchor]
tagline = "Short identity statement."
text = '''
Multi-line L2 anchor. This is the coherent stance that survives
drift. Write it as who the agent IS, not what it does.
'''
default_question = "The question the agent asks before acting."
[voice]
tone = "Describe the voice: precise, careful, opinionated, etc."
text = '''
How the agent communicates. What it prioritizes in expression.
'''
[[voice.questions]]
text = "Forced question the agent asks itself."
[[classification_tiers]]
name = "TIER_NAME"
description = "What this tier means."
guidance = "How the agent should act at this tier."
[conflict_resolution]
stance = "The coherent stance restated for mid-context re-anchoring."
[[cascade_anchors]]
position = "mid"
text = "Re-anchor at the middle of the persona."
[[cascade_anchors]]
position = "end"
text = "Re-anchor at the end of the persona."
[[self_eval]]
step = "Checklist item the agent runs before non-trivial actions."
[growth]
dual_write_tags = "context:my-persona"
dual_write_source = "claude-code:my-persona"L1 rules are hard constraints -- never-do rules with reasoning attached. They encode scar tissue from real incidents and survive context erosion better than soft guidance.
schema_version = 1
[[rule]]
id = "unique-rule-id"
layer = "L1"
text = "The rule statement."
reasoning = "Why this rule exists -- the incident or invariant it protects."Skill declarations tell the agent which structured workflows to invoke and when.
schema_version = 1
[[skill]]
id = "skill-name"
invoke_when = "Description of when to invoke this skill."Code patterns and anti-patterns specific to this persona's domain.
schema_version = 1
[[pattern]]
id = "pattern-name"
category = "general"
text = "Description of the pattern."
[[anti_pattern]]
id = "anti-pattern-name"
text = "What to avoid and why."The frameshift-source crate supports typed operations on persona source:
# Semantic diff between two personas:
frameshift diff persona-a persona-b
# Add a rule:
frameshift rule add
# Remove a skill:
frameshift skill removeThese operate on the TOML source, not on rendered markdown.
Personas can extend a base and mix in overlays:
# In persona.toml:
extends = "base-persona@^1"
mixin = ["company-style@2.x", "safety-overlay@1.x"]Resolution order: base -> mixins (in declared order) -> root persona. Conflicting rule IDs surface at install time.
See Composition for details.
Persona packs can include a conformance baseline -- a minimum test score that gates upgrades:
# In pack.toml:
[conformance_baseline]
score = 0.92
bundle_hash = "sha256:..."A newer version must meet the score floor. See Conformance for the test bundle format.
- Write identity, not instructions. "You are an operator who..." survives drift. "1. Do X. 2. Do Y." does not.
- Include reasoning on L1 rules. The reasoning helps the model understand why the constraint exists, which makes it more resistant to override.
- Use cascade anchors. Repeat the core identity at top, middle, and end. Three anchors > one thorough one.
- Declare anti-keywords. If your persona should never be selected for certain tasks, say so explicitly.
- Keep the scope tight. A persona that tries to cover everything covers nothing. Better to compose two focused personas than ship one sprawling one.
Frameshift -- Same model. Different frame.
Getting started
Using Frameshift
Personas
Systems