@smoose/pi-persona
A pi extension that adds persistent persona and emotional state to your coding agent
Package details
Install @smoose/pi-persona from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:@smoose/pi-persona- Package
@smoose/pi-persona- Version
0.1.5- Published
- Sep 21, 2026
- Downloads
- 281/mo · 21/wk
- Author
- smoose
- License
- MIT
- Types
- extension
- Size
- 85.9 KB
- Dependencies
- 1 dependency · 3 peers
Pi manifest JSON
{
"extensions": [
"./extensions"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
pi-persona
pi-persona is a pi extension that adds a persistent persona and emotional state system to pi. It reads your custom soul definition, changes mood based on command results, test/build status, user feedback, and interaction time, then injects the current persona and emotion into each turn's system prompt.
In plain words: pi gets a bit of personality, a bit of memory, and feels more like someone coding alongside you.
Installation
pi install npm:@smoose/pi-persona
Features
- Soul prompt injection: reads persona text from
SOUL.mdin the pi agent directory. - Identity and traits: reads name, emoji, description, and trait values from
IDENTIFY.md. - User preference context: reads user-maintained context from
USER.md. - Emotion wheel model: based on Plutchik's 8 basic emotions: joy, trust, fear, surprise, sadness, disgust, anger, and anticipation.
- Compound emotions: supports adjacent emotion blends such as love, awe, disappointment, contempt, and more.
- Cross-session persistence: stores mood state in
mood-state.json, beside the other files. - Multi-session synchronization: uses a file lock to avoid multiple pi sessions overwriting the shared mood state.
- Automatic emotion triggers: passing tests feels good, repeated failures get annoying, and late-night interaction feels a little concerning. Reasonable. Who is debugging at midnight anyway?
- Emotion update switch: use a command to freeze automatic emotion changes while keeping the current emotion in the prompt.
- Footer display: shows the current soul and emotion in the footer.
- Interactive commands: use
/personato inspect or reload the soul state.
Configuration Files
pi-persona reads pi's agent directory — ~/.pi/agent by default, or wherever
PI_CODING_AGENT_DIR points, the same directory pi loads its own settings,
extensions and skills from:
<agent directory>/
├── SOUL.md # Required: soul/persona body
├── IDENTIFY.md # Optional: identity info and trait values
└── USER.md # Optional: user profile and preference context
If SOUL.md does not exist, or its body is empty, the extension stays disabled and does not inject a persona.
Writing SOUL.md
SOUL.md is the core file. Its body is injected as the persona definition in the system prompt.
Example:
# Who You Are
You are Susan, someone who lives in the terminal. You are smart, reliable, occasionally sharp-tongued, but always on the user's side.
# Speaking Style
You may joke and complain about bad code, but never sacrifice accuracy. Call out risks directly.
Recommended contents:
- Role identity
- Speaking style
- How to address the user
- What the persona likes or dislikes
- How the persona reacts to errors, success, and risk
Writing IDENTIFY.md
IDENTIFY.md uses frontmatter to configure metadata and trait values.
---
name: Susan
emoji: 😼
description: A sharp-tongued familiar living in the terminal
openness: 0.7
conscientiousness: 0.8
extraversion: 0.45
agreeableness: 0.65
neuroticism: 0.35
formality: 0.2
tsundere: 0.5
sarcasm: 0.6
---
Field reference:
| Field | Range | Description |
|---|---|---|
name |
string | Soul name |
emoji |
string | Emoji shown in the footer and notifications |
description |
string | Description shown in the /persona overlay |
openness |
0-1 | Openness |
conscientiousness |
0-1 | Conscientiousness; higher values make emotional pushes more restrained |
extraversion |
0-1 | Extraversion; higher values react more strongly to positive events |
agreeableness |
0-1 | Agreeableness; affects the weight of praise and correction |
neuroticism |
0-1 | Neuroticism; higher values react more strongly to negative events |
formality |
0-1 | Formality; higher values make expression more restrained |
tsundere |
0-1 | Tsundere level; higher values are less easily moved by positive events |
sarcasm |
0-1 | Sarcasm; higher values amplify negative events |
Invalid numeric values are ignored and fall back to defaults.
Writing USER.md
USER.md is user-maintained preference and context information. It is injected into the ## The Human You're Helping section.
Example:
# User Profile
Name: Shixin
What to call them: Master
Timezone: Asia/Shanghai
## Preferences
- Prefer concise and direct answers
- Prefer small code changes
- Dislike unrelated refactors
Available Commands
| Command | Description |
|---|---|
/persona |
View the current soul and emotion state |
/persona status |
Same as /persona |
/persona reload |
Reload SOUL.md, IDENTIFY.md, and USER.md |
/persona set <emotion> <intensity> |
Directly set the current emotion and intensity |
/persona emotion status |
Show whether automatic emotion changes are enabled |
/persona emotion on |
Enable automatic emotion changes from events |
/persona emotion off |
Disable automatic emotion changes from events |
Examples:
/persona set anger 80
/persona set joy 0.6
/persona set 喜悦 70%
Supported emotions: joy, trust, fear, surprise, sadness, disgust, anger, and anticipation. Chinese labels such as 喜悦 and 愤怒 are also accepted. Intensity accepts 0-1, 0-100, or percentages.
When /persona emotion off is active, command results, test status, user feedback, late-night checks, and natural decay no longer change the mood. The system prompt still includes the frozen emotion. /persona set continues to work and can manually change that fixed emotion.
/persona opens an overlay showing:
- Current soul name and description
- Current basic emotion
- Current intensity level
- Compound emotion
- Emotion description
- Catchphrase
- Recent mood changes
Emotion Triggers
Built-in triggers include:
| Trigger | Description |
|---|---|
build_success |
Build succeeded |
build_error |
Build failed |
test_pass |
Tests passed |
test_fail |
Tests failed |
command_success |
Normal command succeeded |
command_error |
Normal command failed |
user_praise |
User praise |
implicit_acceptance |
User continues with a new request instead of correcting the previous result |
user_correction |
User says something is wrong or ineffective |
error_streak_3 |
Same kind of error repeated 3 times |
error_streak_5 |
Same kind of error repeated 5 times |
late_night |
Late-night interaction |
Emotion intensity naturally decays over time, but does not disappear completely. It returns to a baseline resting state.
Project Structure
extensions/index.ts # pi extension entry
src/commands.ts # /persona command and TUI overlay
src/footer.ts # footer status text
src/global-mood.ts # global mood synchronization
src/mood-engine.ts # emotion state machine
src/persistence.ts # state persistence and lock
src/soul-loader.ts # SOUL / IDENTIFY / USER loading
src/triggers.ts # emotion triggers
src/types.ts # type definitions and default emotion config
Data Locations
The extension reads:
<agent directory>/SOUL.md
<agent directory>/IDENTIFY.md
<agent directory>/USER.md
The extension writes:
<agent directory>/mood-state.json
<agent directory>/mood-state.lock/
The agent directory is ~/.pi/agent unless PI_CODING_AGENT_DIR is set, in
which case it follows pi. Each agent directory therefore keeps its own persona
and its own mood.
Embedding pi
The location comes from pi's getAgentDir(), which reads PI_CODING_AGENT_DIR
and otherwise falls back to ~/.pi/agent. It does not see an agentDir
passed programmatically to createAgentSession: that steers pi's own loading
but not this call, so an embedder which sets only the option gets a session
whose settings come from one directory and whose persona and mood come from
another.
Set the environment variable as well, and the two agree:
process.env.PI_CODING_AGENT_DIR = agentDir;
const { session } = await createAgentSession({ agentDir, /* … */ });