pi-extension-persona
v0.2.1
Published
Pi extension that loads persona (system prompt) into pi session with namespaced sources (hermes/hat/json)
Maintainers
Readme
pi-extension-persona
Pi extension that loads persona (system prompt) into pi sessions with namespaced sources (hermes/hat/json).
Features
- 3 namespaced sources:
hermes/,hat/,json/ - Session-scoped snapshots: persona frozen at fork-time, no cross-session collision
- Idempotent injection: no stacked duplicate blocks on re-switch
- Strict addressing:
source/namerequired (bare names rejected) - Source-grouped TUI picker with two-stage tab completion
- Backward compatible: old
personas.jsonconfig still works
Usage
/persona → TUI picker (grouped by source)
/persona hermes/<profile> → hermes source (SOUL.md + memories)
/persona hat/<role> → hat source (frontmatter .md files)
/persona json/<name> → legacy json source
/persona <bare-name> → ❌ REJECTED: use source/name
/persona --list → list all available personas
/persona --status → show current personaSources
hermes
Loads from ~/.hermes/profiles/<name>/:
SOUL.md— identity (required)memories/MEMORY.md,memories/USER.md— profile memory (allowlist default)- Config:
sources.hermes.root,sources.hermes.memoryGlob(opt-in all *.md)
hat
Loads from configured directories (default: ~/.agents/skills/wear-hats/references/_agent/):
<name>.md— filename is primary key- Optional YAML frontmatter (stripped from content)
- Works with files that have NO frontmatter
json
Legacy source from personas.json:
files[]+inline— same as before
Config
Extends personas.json with optional sources key:
{
"default": "hermes/coder",
"sources": {
"hermes": {
"root": "~/.hermes/profiles",
"memoryDir": "memories",
"soulFile": "SOUL.md",
"memoryAllowlist": ["MEMORY.md", "USER.md"],
"memoryGlob": false
},
"hat": {
"dirs": ["~/.agents/skills/wear-hats/references/_agent"]
}
},
"personas": {
"legacy": { "inline": "You are a legacy persona" }
}
}Old flat config (without sources) still works — sources is optional.
Architecture
Session-scoped snapshots (root-cause fix)
Persona state is stored per-session (keyed by session file path), not globally. Snapshot is frozen at fork-time and never re-read from disk. This fixes:
- G-A1: Cross-session persona collision
- G-A2: Stacked duplicate persona blocks
- G-A3: Mid-conversation memory drift
Idempotent injection
before_agent_start strips existing persona/memory markers before re-injecting from the frozen snapshot. Safe to call multiple times.
Locked decisions (LD1-LD8)
| # | Decision | |---|----------| | LD1 | Fork = inherit history + inject identity/memory | | LD2 | Memory scope = profile-local | | LD3 | SOUL.md only (config.yaml NOT loaded) | | LD4 | Namespaced addressing (source/name) | | LD5 | Load ALL memory files, no truncation | | LD6 | Bare names strict-rejected | | LD7 | Memory block separated from identity block | | LD8 | Config extends personas.json with sources key |
Development
npm test # 58 tests
npm run typecheck # TypeScript strict
bash e2e-smoke.sh # Real behavior smoke testLicense
MIT
