# PAI Upgrade: v3 to v4
> **LEGACY MODULE (July 2026). Do NOT run this on PAI 5.** This walkthrough is for the PAI v3 → v4 era and is kept for historical installs only. Upstream has since renamed to LifeOS and the repo layout referenced below no longer matches upstream main. If a clone is needed, pin it to the old layout: `git clone --branch v4.0.3 --depth 1 https://github.com/danielmiessler/LifeOS.git ~/upstream/pai`. New setups should skip this module entirely and install pinned v5.0.0 per the [setup guide](https://chriscantey.com/posts/2026-05-12-setting-up-your-personal-ai-assistant-pai-5/).
> **For AI Assistants.** If you were directed here by your user to upgrade their PAI installation, follow the phases below. Each phase is independently verifiable. If interrupted, you can resume from any phase.
**Prerequisites (verify before starting):** See [PREREQUISITES.md](PREREQUISITES.md)
- PAI v3 currently installed in `~/.claude/`
- Claude Code installed and authenticated
- Bun runtime installed
- Git installed
- At least 500MB free disk space
- No other Claude Code sessions running
**Reference material:** See [reference/v3-to-v4-diff.md](reference/v3-to-v4-diff.md) for the complete structural diff between v3 and v4.
**Upstream release files:** The upgrade copies files from the PAI v4 release. These should be available at either:
- Local: `~/upstream/pai/Releases/v4.*/.claude/` (if the upstream repo is cloned; uses the latest v4.x release)
- Remote: The PAI GitHub releases page
If the local path doesn't exist, clone the upstream repo first, pinned to the last v4-era tag (upstream main has moved on and no longer contains this layout):
```bash
git clone --branch v4.0.3 --depth 1 https://github.com/danielmiessler/LifeOS.git ~/upstream/pai
```
Throughout this document, `V4_SOURCE` and `V3_SOURCE` refer to the release directories:
```bash
V3_SOURCE=~/upstream/pai/Releases/v3.0/.claude
V4_SOURCE=$(ls -d ~/upstream/pai/Releases/v4.*/.claude 2>/dev/null | sort -V | tail -1)
```
---
### Phase 0: Version Detection and Pre-flight
Detect the current PAI version and validate that this upgrade path applies.
**Steps:**
1. Detect the current version:
```bash
# v4+ has the PAI/ directory
if [ -d ~/.claude/PAI ]; then
echo "DETECTED: v4+ (PAI/ directory exists)"
echo "This upgrade is for v3 → v4. You may already be on v4."
cat ~/.claude/PAI/Algorithm/LATEST 2>/dev/null
# v3 has flat skills and no PAI/ directory
elif [ -d ~/.claude/skills ] && [ ! -d ~/.claude/PAI ]; then
# Check for v3 indicators: flat skill structure, specific hooks
if [ -d ~/.claude/skills/CORE ] || [ -d ~/.claude/skills/BeCreative ]; then
echo "DETECTED: v3 (flat skills structure, no PAI/ directory)"
else
echo "DETECTED: Unknown version (has skills/ but not v3 structure)"
fi
else
echo "DETECTED: No PAI installation found"
echo "This upgrade requires an existing v3 installation."
echo "For a fresh install, use the PAI installer instead."
fi
```
2. Verify the v4 source is available:
```bash
V4_SOURCE=$(ls -d ~/upstream/pai/Releases/v4.*/.claude 2>/dev/null | sort -V | tail -1)
test -d "$V4_SOURCE/skills" && echo "v4 source: READY" || echo "v4 source: NOT FOUND"
```
3. Check disk space:
```bash
df -h ~ | tail -1 | awk '{print "Available: " $4}'
```
4. Inventory the current installation:
```bash
echo "=== Current Installation ==="
echo "Skills: $(ls ~/.claude/skills/ 2>/dev/null | wc -l) directories"
echo "Hooks: $(ls ~/.claude/hooks/handlers/ 2>/dev/null | wc -l) handler files"
echo "Memory: $(du -sh ~/.claude/MEMORY/ 2>/dev/null | cut -f1)"
echo "Git tracked: $(git -C ~/.claude rev-parse --git-dir 2>/dev/null && echo 'yes' || echo 'no')"
```
**Verification:** Version is detected as v3. The v4 source directory exists. Disk space is sufficient.
**STOP if:** Version is detected as v4+ (already upgraded) or no PAI installation found.
---
### Phase 1: Full Backup
Create a complete backup before making any changes. This is your safety net.
**Steps:**
1. If `~/.claude/` is not git-tracked, initialize it:
```bash
cd ~/.claude
if ! git rev-parse --git-dir >/dev/null 2>&1; then
git init
git config user.email "local@vm"
git config user.name "PAI User"
fi
```
2. Commit current state:
```bash
cd ~/.claude && git add -A && git commit -m "Pre-upgrade snapshot: PAI v3 state before v4 upgrade" --allow-empty
```
3. Create a timestamped backup:
```bash
BACKUP_DIR=~/pai-v3-backup-$(date +%Y%m%d-%H%M%S)
cp -r ~/.claude "$BACKUP_DIR"
echo "Backup created at: $BACKUP_DIR"
echo "Size: $(du -sh "$BACKUP_DIR" | cut -f1)"
```
4. Record the backup location:
```bash
echo "$BACKUP_DIR" > ~/.claude/.upgrade-backup-path
```
**Verification:**
- `git -C ~/.claude log --oneline -1` shows the pre-upgrade commit
- The backup directory exists and is non-empty: `test -d "$BACKUP_DIR/skills" && echo "PASS" || echo "FAIL"`
- Backup path is recorded: `cat ~/.claude/.upgrade-backup-path`
---
### Phase 2: Identify and Protect User State
Scan the installation to find user customizations that must be preserved.
**Steps:**
1. Identify custom skills (skills not in v3 upstream):
```bash
# Dynamic detection: compare against the v3.0 release directory
V3_SOURCE=~/upstream/pai/Releases/v3.0/.claude
if [ -d "$V3_SOURCE/skills" ]; then
V3_UPSTREAM_SKILLS=$(ls "$V3_SOURCE/skills/" 2>/dev/null | tr '\n' ' ')
echo "Detection method: dynamic (from upstream v3 release)"
else
# Fallback: hardcoded v3 upstream list (38 skills)
V3_UPSTREAM_SKILLS="Agents AnnualReports Aphorisms Apify Art BeCreative BrightData Browser Cloudflare CORE Council CreateCLI CreateSkill Documents Evals ExtractWisdom Fabric FirstPrinciples IterativeDepth OSINT PAI PAIUpgrade Parser PrivateInvestigator Prompting PromptInjection Recon RedTeam Remotion Research Sales Science SECUpdates Telos USMetrics WebAssessment WorldThreatModelHarness WriteStory"
echo "Detection method: fallback (hardcoded v3 list)"
fi
echo "=== Custom Skills (user-created, will be preserved) ==="
CUSTOM_COUNT=0
for skill in ~/.claude/skills/*/; do
skill_name=$(basename "$skill")
[ "$skill_name" = "Custom" ] && continue
if ! echo "$V3_UPSTREAM_SKILLS" | grep -qw "$skill_name"; then
echo " CUSTOM: $skill_name"
CUSTOM_COUNT=$((CUSTOM_COUNT+1))
fi
done
echo "Found $CUSTOM_COUNT custom skill(s)"
```
2. Identify user context files:
```bash
echo "=== User Context Files ==="
# v3 user files live in skills/PAI/USER/ or similar
find ~/.claude/skills/PAI/USER/ -type f 2>/dev/null | while read f; do
echo " USER: $f"
done
# Also check for user files at other common locations
for f in ~/.claude/CLAUDE.md ~/.claude/.env ~/.claude/settings.json; do
test -f "$f" && echo " CONFIG: $f"
done
```
3. Detect PAI Companion additions:
```bash
echo "=== PAI Companion Detection ==="
COMPANION_DETECTED=false
# Check for companion marker in identity or steering rules
if grep -q "PAI Companion setup" ~/.claude/skills/PAI/USER/IDENTITY.md 2>/dev/null || \
grep -q "PAI Companion setup" ~/.claude/PAI/USER/IDENTITY.md 2>/dev/null; then
echo " FOUND: Companion identity additions (portal, exchange, clipboard config)"
COMPANION_DETECTED=true
fi
if grep -q "Visual-first\|Visual-First" ~/.claude/skills/PAI/USER/AISTEERINGRULES.md 2>/dev/null || \
grep -q "Visual-first\|Visual-First" ~/.claude/PAI/USER/AISTEERINGRULES.md 2>/dev/null; then
echo " FOUND: Companion steering rules (visual-first, portal-first, dark theme)"
COMPANION_DETECTED=true
fi
if [ -f ~/.claude/PAI/USER/DESIGN.md ] || [ -f ~/.claude/skills/PAI/USER/DESIGN.md ]; then
echo " FOUND: Companion design system (DESIGN.md)"
COMPANION_DETECTED=true
fi
# Check for companion Docker services
if docker ps 2>/dev/null | grep -q "portal\|pai-portal"; then
echo " FOUND: Companion portal server (Docker)"
COMPANION_DETECTED=true
fi
if [ "$COMPANION_DETECTED" = true ]; then
echo " STATUS: PAI Companion detected. Additions will be preserved during migration."
else
echo " STATUS: No PAI Companion detected. Standard upgrade."
fi
```
4. Identify custom hooks (hooks not in the v3 upstream list):
```bash
V3_UPSTREAM_HOOKS="AlgorithmEnrichment.ts DocCrossRefIntegrity.ts RebuildSkill.ts SystemIntegrity.ts TabState.ts UpdateCounts.ts VoiceNotification.ts"
echo "=== Custom Hooks ==="
for hook in ~/.claude/hooks/handlers/*; do
hook_name=$(basename "$hook")
if ! echo "$V3_UPSTREAM_HOOKS" | grep -qw "$hook_name"; then
echo " CUSTOM HOOK: $hook_name"
fi
done
```
5. Save the inventory:
```bash
# Create a manifest of everything custom
{
echo "# PAI Upgrade Manifest - $(date -Iseconds)"
echo "# Custom items to preserve during v3 → v4 upgrade"
echo ""
echo "## Custom Skills"
for skill in ~/.claude/skills/*/; do
skill_name=$(basename "$skill")
[ "$skill_name" = "Custom" ] && continue
if ! echo "$V3_UPSTREAM_SKILLS" | grep -qw "$skill_name"; then
echo "- $skill_name"
fi
done
echo ""
echo "## User Context Files"
find ~/.claude/skills/PAI/USER/ -type f 2>/dev/null | sed 's/^/- /'
echo ""
echo "## PAI Companion"
echo "- Detected: $COMPANION_DETECTED"
echo ""
echo "## Custom Hooks"
for hook in ~/.claude/hooks/handlers/*; do
hook_name=$(basename "$hook")
if ! echo "$V3_UPSTREAM_HOOKS" | grep -qw "$hook_name"; then
echo "- $hook_name"
fi
done
} > ~/.claude/.upgrade-manifest.md
```
6. **Report the inventory to the user.** Show what custom items were found. Ask the user to confirm before proceeding. If there are unexpected items, discuss before continuing.
**Verification:**
- Manifest file exists: `test -f ~/.claude/.upgrade-manifest.md && echo "PASS" || echo "FAIL"`
- Review the manifest: `cat ~/.claude/.upgrade-manifest.md`
- User has confirmed the inventory is correct
---
### Phase 3: Migrate Custom Skills
Move user-created skills to the v4 `skills/Custom/` directory before replacing upstream skills.
**Steps:**
1. Create the Custom skills directory:
```bash
mkdir -p ~/.claude/skills/Custom
```
2. Move each custom skill:
```bash
# Reuse the detection method from Phase 2
V3_SOURCE=~/upstream/pai/Releases/v3.0/.claude
if [ -d "$V3_SOURCE/skills" ]; then
V3_UPSTREAM_SKILLS=$(ls "$V3_SOURCE/skills/" 2>/dev/null | tr '\n' ' ')
else
V3_UPSTREAM_SKILLS="Agents AnnualReports Aphorisms Apify Art BeCreative BrightData Browser Cloudflare CORE Council CreateCLI CreateSkill Documents Evals ExtractWisdom Fabric FirstPrinciples IterativeDepth OSINT PAI PAIUpgrade Parser PrivateInvestigator Prompting PromptInjection Recon RedTeam Remotion Research Sales Science SECUpdates Telos USMetrics WebAssessment WorldThreatModelHarness WriteStory"
fi
for skill in ~/.claude/skills/*/; do
skill_name=$(basename "$skill")
[ "$skill_name" = "Custom" ] && continue
if ! echo "$V3_UPSTREAM_SKILLS" | grep -qw "$skill_name"; then
echo "Moving custom skill: $skill_name → skills/Custom/$skill_name"
mv "$skill" ~/.claude/skills/Custom/
fi
done
```
3. Verify custom skills have valid SKILL.md files:
```bash
echo "=== Custom Skill Validation ==="
for skill in ~/.claude/skills/Custom/*/; do
skill_name=$(basename "$skill")
if [ -f "$skill/SKILL.md" ]; then
echo " $skill_name: SKILL.md present"
else
echo " $skill_name: WARNING - no SKILL.md found"
fi
done
```
**Verification:**
- `skills/Custom/` directory exists: `test -d ~/.claude/skills/Custom && echo "PASS"`
- Custom skills are in `skills/Custom/` (or directory is empty if user had no custom skills, which is fine): `ls ~/.claude/skills/Custom/ 2>/dev/null || echo "(empty - no custom skills)"`
- No custom skills remain in the flat `skills/` root (only upstream v3 skills remain)
- Each custom skill (if any) has a SKILL.md
---
### Phase 4: Replace Upstream Skills
Remove v3 upstream skills and install v4 hierarchical skill structure.
**Steps:**
1. Back up user context from skills/PAI/USER/ before removing v3 skills:
```bash
# v3 stored user context in skills/PAI/USER/
# This must happen BEFORE we remove v3 skills below
if [ -d ~/.claude/skills/PAI/USER ]; then
mkdir -p ~/.claude/.upgrade-user-context-backup
cp -r ~/.claude/skills/PAI/USER/* ~/.claude/.upgrade-user-context-backup/
echo "Backed up v3 user context from skills/PAI/USER/"
ls ~/.claude/.upgrade-user-context-backup/
fi
```
2. Remove v3 upstream skills (Custom/ is preserved):
```bash
# Remove everything in skills/ EXCEPT Custom/
# At this point, Phase 3 already moved all custom skills to Custom/
# So everything remaining at the root level is v3 upstream
for skill in ~/.claude/skills/*/; do
skill_name=$(basename "$skill")
[ "$skill_name" = "Custom" ] && continue
rm -rf "$skill"
echo "Removed v3 skill: $skill_name"
done
```
3. Copy v4 skill categories from the release:
```bash
V4_SOURCE=$(ls -d ~/upstream/pai/Releases/v4.*/.claude 2>/dev/null | sort -V | tail -1)
for category in "$V4_SOURCE"/skills/*/; do
cat_name=$(basename "$category")
[ "$cat_name" = "Custom" ] && continue
echo "Installing v4 skill category: $cat_name"
cp -r "$category" ~/.claude/skills/
done
```
4. Verify the new structure:
```bash
echo "=== v4 Skill Categories ==="
for cat in ~/.claude/skills/*/; do
cat_name=$(basename "$cat")
skill_count=$(find "$cat" -name "SKILL.md" | wc -l)
echo " $cat_name: $skill_count skill(s)"
done
```
**Verification:**
- No v3 flat skills remain (except Custom/): `ls ~/.claude/skills/ | sort`
- v4 categories are present: should see Agents, ContentAnalysis, Investigation, Media, Research, Scraping, Security, Telos, Thinking, USMetrics, Utilities, Custom
- Custom skills are still intact: `ls ~/.claude/skills/Custom/`
---
### Phase 5: Hook Migration
Update all hook files: both the root `.hook.ts` files (called by settings.json) and the `handlers/` directory.
**Steps:**
1. Back up the entire hooks directory:
```bash
cp -r ~/.claude/hooks ~/.claude/hooks.v3-backup
echo "Backed up hooks directory"
```
2. Install all v4 root hook files (`.hook.ts`):
```bash
V4_SOURCE=$(ls -d ~/upstream/pai/Releases/v4.*/.claude 2>/dev/null | sort -V | tail -1)
echo "=== Installing v4 hook files ==="
for f in "$V4_SOURCE"/hooks/*.hook.ts; do
fname=$(basename "$f")
cp "$f" ~/.claude/hooks/
echo " Installed: $fname"
done
```
3. Install v4 hook handlers:
```bash
V4_SOURCE=$(ls -d ~/upstream/pai/Releases/v4.*/.claude 2>/dev/null | sort -V | tail -1)
for handler in "$V4_SOURCE"/hooks/handlers/*; do
handler_name=$(basename "$handler")
cp "$handler" ~/.claude/hooks/handlers/
echo " Installed handler: $handler_name"
done
```
4. Install v4 hook lib files:
```bash
V4_SOURCE=$(ls -d ~/upstream/pai/Releases/v4.*/.claude 2>/dev/null | sort -V | tail -1)
if [ -d "$V4_SOURCE/hooks/lib" ]; then
cp -r "$V4_SOURCE/hooks/lib/"* ~/.claude/hooks/lib/ 2>/dev/null
echo " Updated hooks/lib"
fi
```
5. Remove v3-only hook files that no longer exist in v4:
```bash
V3_ONLY_HOOKS="AlgorithmTracker.hook.ts AutoWorkCreation.hook.ts CheckVersion.hook.ts SessionSummary.hook.ts StartupGreeting.hook.ts StopOrchestrator.hook.ts VoiceGate.hook.ts"
V3_ONLY_HANDLERS="AlgorithmEnrichment.ts RebuildSkill.ts"
echo "=== Removing v3-only hooks ==="
for f in $V3_ONLY_HOOKS; do
rm -f ~/.claude/hooks/"$f"
echo " Removed: $f"
done
for f in $V3_ONLY_HANDLERS; do
rm -f ~/.claude/hooks/handlers/"$f"
echo " Removed handler: $f"
done
```
6. The settings.json hook configuration will be updated in Phase 7 (Settings Migration).
**Verification:**
- V4 hooks present: `ls ~/.claude/hooks/*.hook.ts | wc -l` (should be ~20)
- V3-only hooks gone: `test -f ~/.claude/hooks/AlgorithmTracker.hook.ts && echo "FAIL" || echo "PASS"`
- `BuildCLAUDE.ts` exists in handlers/: `test -f ~/.claude/hooks/handlers/BuildCLAUDE.ts && echo "PASS"`
- Backup exists: `test -d ~/.claude/hooks.v3-backup && echo "PASS"`
---
### Phase 6: Install PAI System Directory
Create the new PAI/ directory with documentation, tools, and user context structure.
**Steps:**
1. Copy the PAI/ directory from v4 release (user context was already backed up in Phase 4):
```bash
V4_SOURCE=$(ls -d ~/upstream/pai/Releases/v4.*/.claude 2>/dev/null | sort -V | tail -1)
cp -r "$V4_SOURCE/PAI" ~/.claude/PAI
echo "Installed PAI/ directory"
```
2. Restore user context files to the new PAI/USER/ location:
```bash
if [ -d ~/.claude/.upgrade-user-context-backup ]; then
echo "=== Restoring User Context ==="
for f in ~/.claude/.upgrade-user-context-backup/*; do
fname=$(basename "$f")
if [ -f ~/.claude/PAI/USER/"$fname" ]; then
# User file exists in v4 template too. Append user content to v4 version
echo "Merging: $fname (v4 base + your customizations)"
echo "" >> ~/.claude/PAI/USER/"$fname"
echo "---" >> ~/.claude/PAI/USER/"$fname"
echo "" >> ~/.claude/PAI/USER/"$fname"
cat "$f" >> ~/.claude/PAI/USER/"$fname"
else
# User file doesn't exist in v4. Copy it directly
echo "Restoring: $fname"
cp "$f" ~/.claude/PAI/USER/
fi
done
fi
```
3. Create required subdirectories if they don't exist:
```bash
mkdir -p ~/.claude/PAI/USER/SKILLCUSTOMIZATIONS
mkdir -p ~/.claude/PAI/USER/TELOS
mkdir -p ~/.claude/PAI/USER/PROJECTS
mkdir -p ~/.claude/PAI/USER/WORK
```
**Verification:**
- PAI/ directory exists with documentation: `ls ~/.claude/PAI/*.md | head -5`
- Algorithm is present: `cat ~/.claude/PAI/Algorithm/LATEST`
- Tools are present: `ls ~/.claude/PAI/Tools/`
- User context restored: `ls ~/.claude/PAI/USER/`
- User steering rules preserved (if they existed): `test -f ~/.claude/PAI/USER/AISTEERINGRULES.md && echo "PASS" || echo "SKIP (no v3 rules found)"`
---
### Phase 7: Settings.json and CLAUDE.md Migration
Update settings.json to v4 structure and install the CLAUDE.md template system.
**Steps:**
1. Extract user identity from current settings.json:
```bash
# Save user-specific fields
jq '{
principal: .principal,
daidentity: .daidentity,
env: .env
}' ~/.claude/settings.json > ~/.claude/.upgrade-user-settings.json
echo "Extracted user identity fields"
cat ~/.claude/.upgrade-user-settings.json | jq '.principal.name, .daidentity.name'
```
2. Copy v4 settings.json template:
```bash
V4_SOURCE=$(ls -d ~/upstream/pai/Releases/v4.*/.claude 2>/dev/null | sort -V | tail -1)
cp ~/.claude/settings.json ~/.claude/settings.json.v3-backup
cp "$V4_SOURCE/settings.json" ~/.claude/settings.json
echo "Installed v4 settings.json template"
```
3. Merge user identity back into v4 settings:
```bash
# Merge user fields into the v4 template
MERGED=$(jq -s '
.[0] as $v4 |
.[1] as $user |
$v4 * {
principal: ($v4.principal * ($user.principal // {})),
daidentity: ($v4.daidentity * ($user.daidentity // {})),
env: ($v4.env * ($user.env // {}))
}
' ~/.claude/settings.json ~/.claude/.upgrade-user-settings.json)
echo "$MERGED" > ~/.claude/settings.json
echo "Merged user identity into v4 settings"
```
4. Set version fields (the v4 template has these as null):
```bash
ALGO_VER=$(cat ~/.claude/PAI/Algorithm/LATEST 2>/dev/null | tr -d '[:space:]')
jq --arg alg "$ALGO_VER" '.paiVersion = "4.0" | .algorithmVersion = $alg' \
~/.claude/settings.json > /tmp/settings-versions.json
mv /tmp/settings-versions.json ~/.claude/settings.json
echo "Set paiVersion=4.0, algorithmVersion=$ALGO_VER"
```
5. Validate the merged settings:
```bash
# Check it's valid JSON
jq empty ~/.claude/settings.json 2>/dev/null && echo "JSON: valid" || echo "JSON: INVALID"
# Check identity is preserved
echo "Principal: $(jq -r '.principal.name' ~/.claude/settings.json)"
echo "AI Name: $(jq -r '.daidentity.name' ~/.claude/settings.json)"
echo "Timezone: $(jq -r '.principal.timezone' ~/.claude/settings.json)"
echo "PAI Version: $(jq -r '.paiVersion' ~/.claude/settings.json)"
echo "Algorithm: $(jq -r '.algorithmVersion' ~/.claude/settings.json)"
```
6. Install CLAUDE.md template system:
```bash
V4_SOURCE=$(ls -d ~/upstream/pai/Releases/v4.*/.claude 2>/dev/null | sort -V | tail -1)
# Back up existing CLAUDE.md
cp ~/.claude/CLAUDE.md ~/.claude/CLAUDE.md.v3-backup 2>/dev/null
# Install template
cp "$V4_SOURCE/CLAUDE.md.template" ~/.claude/CLAUDE.md.template 2>/dev/null
# Generate CLAUDE.md from template (if BuildCLAUDE.ts exists)
if [ -f ~/.claude/PAI/Tools/BuildCLAUDE.ts ]; then
bun ~/.claude/PAI/Tools/BuildCLAUDE.ts
echo "CLAUDE.md generated from template"
else
# Fallback: copy the static CLAUDE.md from the release
cp "$V4_SOURCE/CLAUDE.md" ~/.claude/CLAUDE.md
echo "CLAUDE.md copied from release (no BuildCLAUDE.ts found)"
fi
```
7. Verify CLAUDE.md has content (not a stub):
```bash
LINES=$(wc -l < ~/.claude/CLAUDE.md)
if [ "$LINES" -gt 10 ]; then
echo "CLAUDE.md: $LINES lines (populated)"
else
echo "CLAUDE.md: $LINES lines (WARNING: may still be a stub)"
fi
```
8. Fix statusline paths (v3 statusline reads from old locations):
```bash
if [ -f ~/.claude/statusline-command.sh ]; then
# Fix Algorithm LATEST path: skills/PAI/Components/Algorithm/ → PAI/Algorithm/
sed -i 's|skills/PAI/Components/Algorithm/LATEST|PAI/Algorithm/LATEST|g' ~/.claude/statusline-command.sh
# Fix PAI version field: .pai.version → .paiVersion
sed -i 's|\.pai\.version|.paiVersion|g' ~/.claude/statusline-command.sh
echo "Statusline paths updated for v4"
fi
```
9. Fix the `pai` shell alias (v3 path no longer exists in v4):
```bash
# The v3 alias points to skills/PAI/Tools/pai.ts, which moved to PAI/Tools/pai.ts in v4
sed -i 's|skills/PAI/Tools/pai.ts|PAI/Tools/pai.ts|g' ~/.bashrc
[ -f ~/.zshrc ] && sed -i 's|skills/PAI/Tools/pai.ts|PAI/Tools/pai.ts|g' ~/.zshrc
echo "Shell alias path updated for v4"
```
**Verification:**
- Settings.json is valid JSON: `jq empty ~/.claude/settings.json && echo "PASS"`
- User identity preserved: `jq '.principal.name' ~/.claude/settings.json` returns the user's name
- Version fields set: `jq '{paiVersion, algorithmVersion}' ~/.claude/settings.json`
- v3 backup exists: `test -f ~/.claude/settings.json.v3-backup && echo "PASS"`
- CLAUDE.md is populated (not a stub): more than 10 lines
- CLAUDE.md.template exists: `test -f ~/.claude/CLAUDE.md.template && echo "PASS" || echo "SKIP"`
- Statusline reads correct paths: `grep 'PAI/Algorithm/LATEST' ~/.claude/statusline-command.sh && echo "PASS"`
- Shell alias points to correct path: `grep 'PAI/Tools/pai.ts' ~/.bashrc | grep -v 'skills/' && echo "PASS"`
---
### Phase 8: Agent and Memory Migration
Update agent configurations and ensure memory directories are correct.
**Steps:**
1. Install v4 agent configurations:
```bash
V4_SOURCE=$(ls -d ~/upstream/pai/Releases/v4.*/.claude 2>/dev/null | sort -V | tail -1)
# Back up current agents
cp -r ~/.claude/agents ~/.claude/agents.v3-backup 2>/dev/null
# Install v4 agents
cp -r "$V4_SOURCE/agents" ~/.claude/
echo "Installed v4 agent configurations"
ls ~/.claude/agents/
```
2. Ensure memory directories exist:
```bash
mkdir -p ~/.claude/MEMORY/{WORK,LEARNING,STATE,RELATIONSHIP,VOICE}
echo "Memory directories verified"
```
3. Verify memory data is intact:
```bash
echo "=== Memory Data ==="
for dir in WORK LEARNING STATE RELATIONSHIP VOICE; do
count=$(find ~/.claude/MEMORY/$dir -type f 2>/dev/null | wc -l)
echo " $dir: $count files"
done
```
4. Install other v4 infrastructure if present:
```bash
V4_SOURCE=$(ls -d ~/upstream/pai/Releases/v4.*/.claude 2>/dev/null | sort -V | tail -1)
# Install lib/ directory (migration utilities)
[ -d "$V4_SOURCE/lib" ] && cp -r "$V4_SOURCE/lib" ~/.claude/
# Install PAI-Install/ (for future reference)
[ -d "$V4_SOURCE/PAI-Install" ] && cp -r "$V4_SOURCE/PAI-Install" ~/.claude/
```
**Verification:**
- Agents directory has v4 agents: `ls ~/.claude/agents/`
- Memory directories all exist: `ls -d ~/.claude/MEMORY/*/`
- No memory data was lost: file counts match or exceed pre-upgrade inventory
- v3 agent backup exists: `test -d ~/.claude/agents.v3-backup && echo "PASS"`
---
### Phase 9: Voice Server Decision
Handle the voice server based on the user's current setup.
**Steps:**
1. Detect current voice setup:
```bash
echo "=== Voice Server Detection ==="
# Check for PAI Companion's Kokoro voice server (Docker)
if docker ps 2>/dev/null | grep -q "kokoro\|chatterbox\|voice"; then
echo "DETECTED: PAI Companion voice server (Docker/Kokoro)"
echo "ACTION: Preserve current setup. Do not install upstream VoiceServer."
# Check for upstream ElevenLabs voice server
elif [ -f ~/.claude/VoiceServer/server.ts ]; then
echo "DETECTED: Upstream ElevenLabs voice server"
echo "ACTION: Update to v4 VoiceServer."
# Check for systemd voice service
elif systemctl --user is-active pai-voice >/dev/null 2>&1; then
echo "DETECTED: Voice server running via systemd"
echo "ACTION: Preserve current setup."
else
echo "DETECTED: No voice server"
echo "ACTION: Skip voice server installation."
fi
```
2. **If upstream ElevenLabs (not Companion):** Update the VoiceServer:
```bash
V4_SOURCE=$(ls -d ~/upstream/pai/Releases/v4.*/.claude 2>/dev/null | sort -V | tail -1)
if [ -d "$V4_SOURCE/VoiceServer" ]; then
cp -r ~/.claude/VoiceServer ~/.claude/VoiceServer.v3-backup 2>/dev/null
cp -r "$V4_SOURCE/VoiceServer" ~/.claude/
echo "Updated VoiceServer to v4"
fi
```
3. **If PAI Companion (Kokoro):** Skip upstream VoiceServer entirely. The Companion's voice system is independent and should not be overwritten.
4. **If no voice server:** Skip this phase. Voice is optional.
**Verification:**
- Voice server state matches detection (running or not, correct type)
- If updated: `test -f ~/.claude/VoiceServer/server.ts && echo "PASS"`
- If Companion: Docker voice containers still running: `docker ps | grep -i voice`
- If skipped: confirm no VoiceServer was accidentally installed
---
### Phase 9b: PAI Companion Update
If the user has PAI Companion installed, offer to update the companion's system portal pages to the latest version. This phase only updates system-managed pages and never touches user-created content.
**Steps:**
1. Detect PAI Companion installation:
```bash
echo "=== PAI Companion Detection ==="
COMPANION_DETECTED=false
# Check for portal directory with companion system pages
if [ -d ~/portal/skills ] || [ -d ~/portal/exchange ] || [ -d ~/portal/clipboard ]; then
echo " FOUND: Portal system pages"
COMPANION_DETECTED=true
fi
# Check for companion Docker container
if docker ps 2>/dev/null | grep -q "pai-portal"; then
echo " FOUND: pai-portal Docker container"
COMPANION_DETECTED=true
fi
# Check for companion marker in identity
if grep -q "PAI Companion setup" ~/.claude/PAI/USER/IDENTITY.md 2>/dev/null; then
echo " FOUND: Companion identity context"
COMPANION_DETECTED=true
fi
# Check for version marker
if [ -f ~/portal/.companion-version ]; then
echo " FOUND: Version marker ($(cat ~/portal/.companion-version))"
COMPANION_DETECTED=true
fi
echo " Companion detected: $COMPANION_DETECTED"
```
2. **If not detected:** Skip this phase. Inform the user:
> PAI Companion is not installed. It's an optional add-on that gives your assistant a web portal for visual output, a file exchange, and a clipboard. You can install it later from [the companion repo](https://github.com/chriscantey/pai-companion).
3. **If detected:** Ask the user with AskUserQuestion (3 options):
- **"Yes, update companion"** — Pull latest source, update system pages, rebuild Docker
- **"No, skip companion update"** — Leave companion as-is
- **"What is PAI Companion?"** — Explain what it is, then re-ask
4. **If user chooses yes:** Get the latest companion source:
```bash
if [ -d ~/pai-companion ]; then
echo "Updating existing companion repo..."
git -C ~/pai-companion pull
else
echo "Cloning companion repo..."
git clone https://github.com/chriscantey/pai-companion.git ~/pai-companion
fi
```
5. Update system portal pages only (never touch user-created pages):
```bash
echo "=== Updating System Portal Pages ==="
# System directories to update (these ship with companion)
SYSTEM_DIRS="skills agents context system clipboard exchange shared"
for dir in $SYSTEM_DIRS; do
if [ -d ~/pai-companion/companion/portal/public/$dir ]; then
rm -rf ~/portal/$dir
cp -r ~/pai-companion/companion/portal/public/$dir ~/portal/
echo " Updated: $dir/"
fi
done
# Welcome page (stored separately in repo)
if [ -d ~/pai-companion/companion/welcome ]; then
rm -rf ~/portal/welcome
cp -r ~/pai-companion/companion/welcome ~/portal/welcome
echo " Updated: welcome/"
fi
# Server infrastructure files
for f in server.ts Dockerfile docker-compose.yml; do
if [ -f ~/pai-companion/companion/portal/$f ]; then
cp ~/pai-companion/companion/portal/$f ~/portal/$f
echo " Updated: $f"
fi
done
# Patch the portal homepage if it still has the standard quick-links.
# Read ~/portal/index.html. If it contains quick-links for /skills/, /agents/,
# and /system/ but NOT /context/, add a context link before the system link:
#
#
# 🧭 Context
#
#
# Also add 'context' to the skip Set in the JavaScript if it's missing.
#
# If the homepage doesn't have the standard quick-links structure,
# skip this step — the user has customized their homepage.
# Update shared assets
cp -r ~/pai-companion/companion/portal/public/shared/* ~/portal/shared/ 2>/dev/null
echo " Updated: shared/"
# Write version marker
echo "companion-$(date +%Y%m%d)" > ~/portal/.companion-version
echo " Version marker: $(cat ~/portal/.companion-version)"
```
6. Rebuild the Docker container:
```bash
cd ~/portal && docker compose up -d --build
```
7. Wait for the container to start, then verify:
```bash
sleep 3
VM_IP=$(cat ~/.vm-ip 2>/dev/null || hostname -I | awk '{print $1}')
PORT=$(grep PORTAL_PORT ~/.claude/.env 2>/dev/null | cut -d= -f2 || echo 8080)
echo "=== Companion Update Verification ==="
echo -n "Portal responds: "
curl -sf http://$VM_IP:$PORT/ >/dev/null && echo "PASS" || echo "FAIL"
echo -n "Skills page: "
curl -sf http://$VM_IP:$PORT/skills/ >/dev/null && echo "PASS" || echo "FAIL"
echo -n "Exchange page: "
curl -sf http://$VM_IP:$PORT/exchange/ >/dev/null && echo "PASS" || echo "FAIL"
echo -n "Docker container: "
docker ps | grep -q pai-portal && echo "PASS" || echo "FAIL"
```
**Verification:**
- System pages are updated (skills, agents, exchange, clipboard all respond)
- User-created portal pages are untouched (any pages the user built are still there)
- Docker container `pai-portal` is running
- Version marker exists: `cat ~/portal/.companion-version`
---
### Phase 10: Post-Upgrade Verification
Run comprehensive checks to confirm the upgrade succeeded.
**Steps:**
1. Run the full verification:
```bash
echo "=== PAI v3 → v4 Upgrade Verification ==="
echo ""
PASS=0
FAIL=0
WARN=0
# Structure checks
echo "--- Structure ---"
echo -n "PAI/ directory exists: "
test -d ~/.claude/PAI && { echo "PASS"; PASS=$((PASS+1)); } || { echo "FAIL"; FAIL=$((FAIL+1)); }
echo -n "Algorithm installed: "
test -f ~/.claude/PAI/Algorithm/LATEST && { echo "PASS ($(cat ~/.claude/PAI/Algorithm/LATEST))"; PASS=$((PASS+1)); } || { echo "FAIL"; FAIL=$((FAIL+1)); }
echo -n "Skills hierarchical: "
test -d ~/.claude/skills/Thinking && test -d ~/.claude/skills/Security && { echo "PASS"; PASS=$((PASS+1)); } || { echo "FAIL"; FAIL=$((FAIL+1)); }
echo -n "Custom skills preserved: "
test -d ~/.claude/skills/Custom && { echo "PASS ($(ls ~/.claude/skills/Custom/ 2>/dev/null | wc -l) skills)"; PASS=$((PASS+1)); } || { echo "WARN (no Custom/ dir)"; WARN=$((WARN+1)); }
echo -n "No v3 flat skills remain: "
test -d ~/.claude/skills/CORE 2>/dev/null && { echo "FAIL (CORE still exists)"; FAIL=$((FAIL+1)); } || { echo "PASS"; PASS=$((PASS+1)); }
# Config checks
echo ""
echo "--- Configuration ---"
echo -n "settings.json valid: "
jq empty ~/.claude/settings.json 2>/dev/null && { echo "PASS"; PASS=$((PASS+1)); } || { echo "FAIL"; FAIL=$((FAIL+1)); }
echo -n "Principal name set: "
NAME=$(jq -r '.principal.name // empty' ~/.claude/settings.json)
[ -n "$NAME" ] && { echo "PASS ($NAME)"; PASS=$((PASS+1)); } || { echo "FAIL"; FAIL=$((FAIL+1)); }
echo -n "CLAUDE.md populated: "
LINES=$(wc -l < ~/.claude/CLAUDE.md 2>/dev/null || echo 0)
[ "$LINES" -gt 10 ] && { echo "PASS ($LINES lines)"; PASS=$((PASS+1)); } || { echo "FAIL ($LINES lines)"; FAIL=$((FAIL+1)); }
# Hook checks
echo ""
echo "--- Hooks ---"
echo -n "BuildCLAUDE.ts present: "
test -f ~/.claude/hooks/handlers/BuildCLAUDE.ts && { echo "PASS"; PASS=$((PASS+1)); } || { echo "FAIL"; FAIL=$((FAIL+1)); }
echo -n "v3-only hooks removed: "
if [ -f ~/.claude/hooks/handlers/AlgorithmEnrichment.ts ] || [ -f ~/.claude/hooks/handlers/RebuildSkill.ts ]; then
echo "FAIL (v3 hooks still present)"; FAIL=$((FAIL+1))
else
echo "PASS"; PASS=$((PASS+1))
fi
# Data checks
echo ""
echo "--- Data ---"
echo -n "Memory intact: "
test -d ~/.claude/MEMORY/WORK && test -d ~/.claude/MEMORY/LEARNING && { echo "PASS"; PASS=$((PASS+1)); } || { echo "FAIL"; FAIL=$((FAIL+1)); }
echo -n "User context in PAI/USER/: "
test -d ~/.claude/PAI/USER && { echo "PASS ($(ls ~/.claude/PAI/USER/ | wc -l) files)"; PASS=$((PASS+1)); } || { echo "FAIL"; FAIL=$((FAIL+1)); }
echo -n "Backup exists: "
BACKUP=$(cat ~/.claude/.upgrade-backup-path 2>/dev/null)
[ -n "$BACKUP" ] && test -d "$BACKUP" && { echo "PASS ($BACKUP)"; PASS=$((PASS+1)); } || { echo "WARN"; WARN=$((WARN+1)); }
# Safety checks
echo ""
echo "--- Safety ---"
echo -n ".env preserved: "
test -f ~/.claude/.env && { echo "PASS"; PASS=$((PASS+1)); } || { echo "WARN (no .env found)"; WARN=$((WARN+1)); }
echo -n "Shell alias (no duplicates): "
SHELL_RC="$HOME/.$(basename $SHELL 2>/dev/null || echo bash)rc"
ALIAS_COUNT=$(grep -c "^alias pai=" "$SHELL_RC" 2>/dev/null || echo 0)
if [ "$ALIAS_COUNT" -eq 1 ]; then
echo "PASS (1 alias in $(basename $SHELL_RC))"
PASS=$((PASS+1))
elif [ "$ALIAS_COUNT" -gt 1 ]; then
echo "WARN ($ALIAS_COUNT duplicate aliases in $(basename $SHELL_RC))"
WARN=$((WARN+1))
else
echo "WARN (no alias found in $(basename $SHELL_RC))"
WARN=$((WARN+1))
fi
echo ""
echo "=== Results: $PASS passed, $FAIL failed, $WARN warnings ==="
if [ "$FAIL" -eq 0 ]; then
echo "Upgrade verification: SUCCESS"
else
echo "Upgrade verification: ISSUES FOUND - review failures above"
fi
```
2. If any checks fail, **report them to the user** and suggest remediation. For each failure, check the backup and restore the specific component if needed.
**Verification:** All checks pass (0 failures). Warnings are acceptable (some items may not exist in every installation).
---
### Phase 11: Cleanup and Commit
Clean up temporary files and commit the upgraded state.
**Steps:**
1. Clean up shell aliases:
The PAI installer writes a `pai` alias to `.zshrc` (macOS default shell). On Linux, some setup guides merge `.zshrc` into `.bashrc`, which can leave duplicate or stale aliases after upgrading. This step cleans up all shell configs to have one correct alias.
```bash
echo "=== Shell Alias Cleanup ==="
# Determine the correct alias path for v4
# v3 used skills/PAI/Tools/pai.ts, v4 uses PAI/Tools/pai.ts
PAI_TOOL="$HOME/.claude/PAI/Tools/pai.ts"
CORRECT_ALIAS="alias pai='bun $PAI_TOOL'"
for RC_FILE in ~/.bashrc ~/.zshrc; do
[ -f "$RC_FILE" ] || continue
# Count existing pai aliases
ALIAS_COUNT=$(grep -c "^alias pai=" "$RC_FILE" 2>/dev/null || echo 0)
MARKER_COUNT=$(grep -c "^#.*PAI.*alias" "$RC_FILE" 2>/dev/null || echo 0)
if [ "$ALIAS_COUNT" -gt 1 ] || [ "$MARKER_COUNT" -gt 1 ]; then
echo " $(basename $RC_FILE): Found $ALIAS_COUNT alias(es), $MARKER_COUNT marker(s) - cleaning duplicates"
# Remove all pai alias lines and PAI alias markers
grep -v "^alias pai=" "$RC_FILE" | grep -v "^#.*PAI.*alias" > "${RC_FILE}.upgrade-tmp"
mv "${RC_FILE}.upgrade-tmp" "$RC_FILE"
# Add one clean alias
echo "" >> "$RC_FILE"
echo "# PAI alias" >> "$RC_FILE"
echo "$CORRECT_ALIAS" >> "$RC_FILE"
echo " $(basename $RC_FILE): Cleaned and set single alias"
elif [ "$ALIAS_COUNT" -eq 1 ]; then
# One alias exists. Verify it points to the right path
CURRENT=$(grep "^alias pai=" "$RC_FILE")
if [ "$CURRENT" != "$CORRECT_ALIAS" ]; then
grep -v "^alias pai=" "$RC_FILE" | grep -v "^#.*PAI.*alias" > "${RC_FILE}.upgrade-tmp"
mv "${RC_FILE}.upgrade-tmp" "$RC_FILE"
echo "" >> "$RC_FILE"
echo "# PAI alias" >> "$RC_FILE"
echo "$CORRECT_ALIAS" >> "$RC_FILE"
echo " $(basename $RC_FILE): Updated alias path"
else
echo " $(basename $RC_FILE): Alias correct, no changes needed"
fi
elif [ "$ALIAS_COUNT" -eq 0 ]; then
# No alias. Add one if this is the user's active shell config
CURRENT_SHELL=$(basename "$SHELL" 2>/dev/null || echo "bash")
if [ "$RC_FILE" = "$HOME/.${CURRENT_SHELL}rc" ]; then
echo "" >> "$RC_FILE"
echo "# PAI alias" >> "$RC_FILE"
echo "$CORRECT_ALIAS" >> "$RC_FILE"
echo " $(basename $RC_FILE): Added alias (active shell)"
else
echo " $(basename $RC_FILE): No alias, skipping (not active shell)"
fi
fi
done
# Clean up fish config if it exists (v3 wrote fish functions, v4 doesn't)
FISH_CONFIG=~/.config/fish/config.fish
if [ -f "$FISH_CONFIG" ] && grep -q "function pai" "$FISH_CONFIG"; then
echo " config.fish: Found v3 fish function - updating"
# Remove old function block
sed -i '/^#.*PAI.*alias/,/^end$/d' "$FISH_CONFIG"
sed -i '/^function pai/,/^end$/d' "$FISH_CONFIG"
fi
```
2. Remove v3 artifacts that are now consolidated:
```bash
# The skills/PAI/ directory is replaced by the top-level PAI/ directory
rm -rf ~/.claude/skills/PAI 2>/dev/null
# Remove upgrade temp files (keep backups)
rm -f ~/.claude/.upgrade-user-settings.json
rm -f ~/.claude/.upgrade-manifest.md
rm -f ~/.claude/.upgrade-backup-path
```
3. Clean up handler backups (optional, ask user):
```bash
echo "Backup directories created during upgrade:"
ls -d ~/.claude/*.v3-backup ~/.claude/hooks/handlers.v3-backup 2>/dev/null
echo ""
echo "These can be removed once you've verified the upgrade is working correctly."
echo "The full backup at ~/pai-v3-backup-* is your safety net."
```
3. Commit the upgraded state:
```bash
cd ~/.claude && git add -A && git commit -m "Upgrade complete: PAI v3 → v4"
```
4. Report to the user:
```
Upgrade complete! Here's what changed:
- Skills: Reorganized from 38 flat directories to 11 hierarchical categories
- Your custom skills are preserved in skills/Custom/
- Hooks: Updated to v4 set (BuildCLAUDE.ts added, v3-only hooks removed)
- PAI/ directory: New system documentation and tools installed
- CLAUDE.md: Now generated from template (auto-rebuilds on session start)
- Settings: Your identity, timezone, and API keys are preserved
- Memory: All session history, learning, and relationship data intact
- Algorithm: Updated to latest version
Full backup at: ~/pai-v3-backup-[timestamp]/
**Important:** Exit Claude Code and start a new session. The v4 hooks
and configuration won't fully load until you restart. You may see hook
errors on exit — that's expected, since the current session is still
running with v3 hook references. A fresh session will load cleanly.
```
**Verification:**
- Git commit exists: `git -C ~/.claude log --oneline -1` shows the upgrade commit
- No temp files remain: `ls ~/.claude/.upgrade-* 2>/dev/null` returns nothing
- skills/PAI/ is gone (replaced by top-level PAI/): `test -d ~/.claude/skills/PAI && echo "FAIL" || echo "PASS"`
---
### Rollback
If something goes wrong, you can restore from the backup:
```bash
# Find your backup
BACKUP=$(ls -d ~/pai-v3-backup-* | tail -1)
echo "Restoring from: $BACKUP"
# Restore (preserves the git history)
cd ~/.claude && git stash
rm -rf skills/ hooks/ PAI/ CLAUDE.md CLAUDE.md.template settings.json agents/ VoiceServer/ lib/ PAI-Install/
cp -r "$BACKUP"/* ~/.claude/
cd ~/.claude && git add -A && git commit -m "Rollback: restored v3 from backup"
```
Or use git:
```bash
# Find the pre-upgrade commit
git -C ~/.claude log --oneline | head -5
# Reset to the pre-upgrade state
git -C ~/.claude checkout -- .
git -C ~/.claude commit -m "Rollback: reverted to v3 via git"
```