Project Session Manager (PSM) Skill
Native Subagent Protocol (Codex)
Codex supports native subagents. Delegate with spawn_agent, coordinate with send_input, collect via wait_agent, and clean up with close_agent.
Execution preference:
- Use native subagents first for independent workstreams (parallel when possible).
- Merge results in main thread and run final verification.
- Fallback only when delegation is blocked: use the
[ANALYST]/[ARCHITECT]/[EXECUTOR]/[REVIEWER] structure in a single response.
Minimal orchestration pattern:
spawn_agent -> send_input (optional) -> wait_agent -> close_agent
Codex invocation: use $project-session-manager ... or project-session-manager: ...
Automate isolated development environments using git worktrees and tmux sessions with Codex. Enables parallel work across multiple tasks, projects, and repositories.
Commands
| Command |
Description |
Example |
review <ref> |
PR review session |
$psm review omc#123 |
fix <ref> |
Issue fix session |
$psm fix omc#42 |
feature <proj> <name> |
Feature development |
$psm feature omc add-webhooks |
list [project] |
List active sessions |
$psm list |
attach <session> |
Attach to session |
$psm attach omc:pr-123 |
kill <session> |
Kill session |
$psm kill omc:pr-123 |
cleanup |
Clean merged/closed |
$psm cleanup |
status |
Current session info |
$psm status |
Project References
Supported formats:
- Alias:
omc#123 (requires ~/.psm/projects.json)
- Full:
owner/repo#123
- URL:
https://github.com/owner/repo/pull/123
- Current:
#123 (uses current directory's repo)
Configuration
Project Aliases (~/.psm/projects.json)
{
"aliases": {
"omc": {
"repo": "Yeachan-Heo/oh-my-codex",
"local": "~/Workspace/oh-my-codex",
"default_base": "main"
}
},
"defaults": {
"worktree_root": "~/.psm/worktrees",
"cleanup_after_days": 14
}
}
Directory Structure
~/.psm/
├── projects.json # Project aliases
├── sessions.json # Active session registry
└── worktrees/ # Worktree storage
└── <project>/
└── <type>-<id>/
Session Naming
| Type |
Tmux Session |
Worktree Dir |
| PR Review |
psm:omc:pr-123 |
~/.psm/worktrees/omc/pr-123 |
| Issue Fix |
psm:omc:issue-42 |
~/.psm/worktrees/omc/issue-42 |
| Feature |
psm:omc:feat-auth |
~/.psm/worktrees/omc/feat-auth |
Implementation Protocol
When the user invokes a PSM command, follow this protocol:
Parse Arguments
Parse {{ARGUMENTS}} to determine:
- Subcommand: review, fix, feature, list, attach, kill, cleanup, status
- Reference: project#number, URL, or session ID
- Options: --branch, --base, --no-codex, --no-tmux, etc.
Subcommand: review <ref>
Purpose: Create PR review session
Steps:
Resolve reference:
# Read project aliases
cat ~/.psm/projects.json 2>/dev/null || echo '{"aliases":{}}'
# Parse ref format: alias#num, owner/repo#num, or URL
# Extract: project_alias, repo (owner/repo), pr_number, local_path
Fetch PR info:
gh pr view <pr_number> --repo <repo> --json number,title,author,headRefName,baseRefName,body,files,url
Ensure local repo exists:
# If local path doesn't exist, clone
if [[ ! -d "$local_path" ]]; then
git clone "https://github.com/$repo.git" "$local_path"
fi
Create worktree:
worktree_path="$HOME/.psm/worktrees/$project_alias/pr-$pr_number"
# Fetch PR branch
cd "$local_path"
git fetch origin "pull/$pr_number/head:pr-$pr_number-review"
# Create worktree
git worktree add "$worktree_path" "pr-$pr_number-review"
Create session metadata:
cat > "$worktree_path/.psm-session.json" << EOF
{
"id": "$project_alias:pr-$pr_number",
"type": "review",
"project": "$project_alias",
"ref": "pr-$pr_number",
"branch": "<head_branch>",
"base": "<base_branch>",
"created_at": "$(date -Iseconds)",
"tmux_session": "psm:$project_alias:pr-$pr_number",
"worktree_path": "$worktree_path",
"source_repo": "$local_path",
"github": {
"pr_number": $pr_number,
"pr_title": "<title>",
"pr_author": "<author>",
"pr_url": "<url>"
},
"state": "active"
}
EOF
Update sessions registry:
# Add to ~/.psm/sessions.json
Create tmux session:
tmux new-session -d -s "psm:$project_alias:pr-$pr_number" -c "$worktree_path"
Launch Codex (unless --no-codex):
tmux send-keys -t "psm:$project_alias:pr-$pr_number" "codex" Enter
Output session info:
Session ready!
ID: omc:pr-123
Worktree: ~/.psm/worktrees/omc/pr-123
Tmux: psm:omc:pr-123
To attach: tmux attach -t psm:omc:pr-123
Subcommand: fix <ref>
Purpose: Create issue fix session
Steps:
Resolve reference (same as review)
Fetch issue info:
gh issue view <issue_number> --repo <repo> --json number,title,body,labels,url
Create feature branch:
cd "$local_path"
git fetch origin main
branch_name="fix/$issue_number-$(echo "$title" | tr ' ' '-' | tr '[:upper:]' '[:lower:]' | head -c 30)"
git checkout -b "$branch_name" origin/main
Create worktree:
worktree_path="$HOME/.psm/worktrees/$project_alias/issue-$issue_number"
git worktree add "$worktree_path" "$branch_name"
Create session metadata (similar to review, type="fix")
Update registry, create tmux, launch codex (same as review)
Subcommand: feature <project> <name>
Purpose: Start feature development
Steps:
Resolve project (from alias or path)
Create feature branch:
cd "$local_path"
git fetch origin main
branch_name="feature/$feature_name"
git checkout -b "$branch_name" origin/main
Create worktree:
worktree_path="$HOME/.psm/worktrees/$project_alias/feat-$feature_name"
git worktree add "$worktree_path" "$branch_name"
Create session, tmux, launch codex (same pattern)
Subcommand: list [project]
Purpose: List active sessions
Steps:
Read sessions registry:
cat ~/.psm/sessions.json 2>/dev/null || echo '{"sessions":{}}'
Check tmux sessions:
tmux list-sessions -F "#{session_name}" 2>/dev/null | grep "^psm:"
Check worktrees:
ls -la ~/.psm/worktrees/*/ 2>/dev/null
Format output:
Active PSM Sessions:
ID | Type | Status | Worktree
-------------------|---------|----------|---------------------------
omc:pr-123 | review | active | ~/.psm/worktrees/omc/pr-123
omc:issue-42 | fix | detached | ~/.psm/worktrees/omc/issue-42
Subcommand: attach <session>
Purpose: Attach to existing session
Steps:
Parse session ID: project:type-number
Verify session exists:
tmux has-session -t "psm:$session_id" 2>/dev/null
Attach:
tmux attach -t "psm:$session_id"
Subcommand: kill <session>
Purpose: Kill session and cleanup
Steps:
Kill tmux session:
tmux kill-session -t "psm:$session_id" 2>/dev/null
Remove worktree:
worktree_path=$(jq -r ".sessions[\"$session_id\"].worktree" ~/.psm/sessions.json)
source_repo=$(jq -r ".sessions[\"$session_id\"].source_repo" ~/.psm/sessions.json)
cd "$source_repo"
git worktree remove "$worktree_path" --force
Update registry:
# Remove from sessions.json
Subcommand: cleanup
Purpose: Clean up merged PRs and closed issues
Steps:
Read all sessions
For each PR session, check if merged:
gh pr view <pr_number> --repo <repo> --json merged,state
For each issue session, check if closed:
gh issue view <issue_number> --repo <repo> --json closed,state
Clean up merged/closed sessions:
- Kill tmux session
- Remove worktree
- Update registry
Report:
Cleanup complete:
Removed: omc:pr-123 (merged)
Removed: omc:issue-42 (closed)
Kept: omc:feat-auth (active)
Subcommand: status
Purpose: Show current session info
Steps:
Detect current session from tmux or cwd:
tmux display-message -p "#{session_name}" 2>/dev/null
# or check if cwd is inside a worktree
Read session metadata:
cat .psm-session.json 2>/dev/null
Show status:
Current Session: omc:pr-123
Type: review
PR: #123 - Add webhook support
Branch: feature/webhooks
Created: 2 hours ago
Error Handling
| Error |
Resolution |
| Worktree exists |
Offer: attach, recreate, or abort |
| PR not found |
Verify URL/number, check permissions |
| No tmux |
Warn and skip session creation |
| No gh CLI |
Error with install instructions |
Requirements
git with worktree support (v2.5+)
gh CLI (authenticated)
tmux
jq for JSON parsing
Initialization
On first run, create default config:
mkdir -p ~/.psm/worktrees ~/.psm/logs
# Create default projects.json if not exists
if [[ ! -f ~/.psm/projects.json ]]; then
cat > ~/.psm/projects.json << 'EOF'
{
"aliases": {
"omc": {
"repo": "Yeachan-Heo/oh-my-codex",
"local": "~/Workspace/oh-my-codex",
"default_base": "main"
}
},
"defaults": {
"worktree_root": "~/.psm/worktrees",
"cleanup_after_days": 14,
"auto_cleanup_merged": true
}
}
EOF
fi
# Create sessions.json if not exists
if [[ ! -f ~/.psm/sessions.json ]]; then
echo '{"version":1,"sessions":{},"stats":{"total_created":0,"total_cleaned":0}}' > ~/.psm/sessions.json
fi
1---2name: project-session-manager3description: Manage isolated dev environments with git worktrees and tmux sessions4---567# Project Session Manager (PSM) Skill8910## Native Subagent Protocol (Codex)1112Codex supports native subagents. Delegate with `spawn_agent`, coordinate with `send_input`, collect via `wait_agent`, and clean up with `close_agent`.1314Execution preference:151. Use native subagents first for independent workstreams (parallel when possible).162. Merge results in main thread and run final verification.173. Fallback only when delegation is blocked: use the `[ANALYST]`/`[ARCHITECT]`/`[EXECUTOR]`/`[REVIEWER]` structure in a single response.1819Minimal orchestration pattern:20```text21spawn_agent -> send_input (optional) -> wait_agent -> close_agent22```2324> Codex invocation: use `$project-session-manager ...` or `project-session-manager: ...`252627Automate isolated development environments using git worktrees and tmux sessions with Codex. Enables parallel work across multiple tasks, projects, and repositories.2829## Commands3031| Command | Description | Example |32|---------|-------------|---------|33| `review <ref>` | PR review session | `$psm review omc#123` |34| `fix <ref>` | Issue fix session | `$psm fix omc#42` |35| `feature <proj> <name>` | Feature development | `$psm feature omc add-webhooks` |36| `list [project]` | List active sessions | `$psm list` |37| `attach <session>` | Attach to session | `$psm attach omc:pr-123` |38| `kill <session>` | Kill session | `$psm kill omc:pr-123` |39| `cleanup` | Clean merged/closed | `$psm cleanup` |40| `status` | Current session info | `$psm status` |4142## Project References4344Supported formats:45- **Alias**: `omc#123` (requires `~/.psm/projects.json`)46- **Full**: `owner/repo#123`47- **URL**: `https://github.com/owner/repo/pull/123`48- **Current**: `#123` (uses current directory's repo)4950## Configuration5152### Project Aliases (`~/.psm/projects.json`)5354```json55{56 "aliases": {57 "omc": {58 "repo": "Yeachan-Heo/oh-my-codex",59 "local": "~/Workspace/oh-my-codex",60 "default_base": "main"61 }62 },63 "defaults": {64 "worktree_root": "~/.psm/worktrees",65 "cleanup_after_days": 1466 }67}68```6970## Directory Structure7172```73~/.psm/74├── projects.json # Project aliases75├── sessions.json # Active session registry76└── worktrees/ # Worktree storage77 └── <project>/78 └── <type>-<id>/79```8081## Session Naming8283| Type | Tmux Session | Worktree Dir |84|------|--------------|--------------|85| PR Review | `psm:omc:pr-123` | `~/.psm/worktrees/omc/pr-123` |86| Issue Fix | `psm:omc:issue-42` | `~/.psm/worktrees/omc/issue-42` |87| Feature | `psm:omc:feat-auth` | `~/.psm/worktrees/omc/feat-auth` |8889---9091## Implementation Protocol9293When the user invokes a PSM command, follow this protocol:9495### Parse Arguments9697Parse `{{ARGUMENTS}}` to determine:981. **Subcommand**: review, fix, feature, list, attach, kill, cleanup, status992. **Reference**: project#number, URL, or session ID1003. **Options**: --branch, --base, --no-codex, --no-tmux, etc.101102### Subcommand: `review <ref>`103104**Purpose**: Create PR review session105106**Steps**:1071081. **Resolve reference**:109 ```bash110 # Read project aliases111 cat ~/.psm/projects.json 2>/dev/null || echo '{"aliases":{}}'112113 # Parse ref format: alias#num, owner/repo#num, or URL114 # Extract: project_alias, repo (owner/repo), pr_number, local_path115 ```1161172. **Fetch PR info**:118 ```bash119 gh pr view <pr_number> --repo <repo> --json number,title,author,headRefName,baseRefName,body,files,url120 ```1211223. **Ensure local repo exists**:123 ```bash124 # If local path doesn't exist, clone125 if [[ ! -d "$local_path" ]]; then126 git clone "https://github.com/$repo.git" "$local_path"127 fi128 ```1291304. **Create worktree**:131 ```bash132 worktree_path="$HOME/.psm/worktrees/$project_alias/pr-$pr_number"133134 # Fetch PR branch135 cd "$local_path"136 git fetch origin "pull/$pr_number/head:pr-$pr_number-review"137138 # Create worktree139 git worktree add "$worktree_path" "pr-$pr_number-review"140 ```1411425. **Create session metadata**:143 ```bash144 cat > "$worktree_path/.psm-session.json" << EOF145 {146 "id": "$project_alias:pr-$pr_number",147 "type": "review",148 "project": "$project_alias",149 "ref": "pr-$pr_number",150 "branch": "<head_branch>",151 "base": "<base_branch>",152 "created_at": "$(date -Iseconds)",153 "tmux_session": "psm:$project_alias:pr-$pr_number",154 "worktree_path": "$worktree_path",155 "source_repo": "$local_path",156 "github": {157 "pr_number": $pr_number,158 "pr_title": "<title>",159 "pr_author": "<author>",160 "pr_url": "<url>"161 },162 "state": "active"163 }164 EOF165 ```1661676. **Update sessions registry**:168 ```bash169 # Add to ~/.psm/sessions.json170 ```1711727. **Create tmux session**:173 ```bash174 tmux new-session -d -s "psm:$project_alias:pr-$pr_number" -c "$worktree_path"175 ```1761778. **Launch Codex** (unless --no-codex):178 ```bash179 tmux send-keys -t "psm:$project_alias:pr-$pr_number" "codex" Enter180 ```1811829. **Output session info**:183 ```184 Session ready!185186 ID: omc:pr-123187 Worktree: ~/.psm/worktrees/omc/pr-123188 Tmux: psm:omc:pr-123189190 To attach: tmux attach -t psm:omc:pr-123191 ```192193### Subcommand: `fix <ref>`194195**Purpose**: Create issue fix session196197**Steps**:1981991. **Resolve reference** (same as review)2002012. **Fetch issue info**:202 ```bash203 gh issue view <issue_number> --repo <repo> --json number,title,body,labels,url204 ```2052063. **Create feature branch**:207 ```bash208 cd "$local_path"209 git fetch origin main210 branch_name="fix/$issue_number-$(echo "$title" | tr ' ' '-' | tr '[:upper:]' '[:lower:]' | head -c 30)"211 git checkout -b "$branch_name" origin/main212 ```2132144. **Create worktree**:215 ```bash216 worktree_path="$HOME/.psm/worktrees/$project_alias/issue-$issue_number"217 git worktree add "$worktree_path" "$branch_name"218 ```2192205. **Create session metadata** (similar to review, type="fix")2212226. **Update registry, create tmux, launch codex** (same as review)223224### Subcommand: `feature <project> <name>`225226**Purpose**: Start feature development227228**Steps**:2292301. **Resolve project** (from alias or path)2312322. **Create feature branch**:233 ```bash234 cd "$local_path"235 git fetch origin main236 branch_name="feature/$feature_name"237 git checkout -b "$branch_name" origin/main238 ```2392403. **Create worktree**:241 ```bash242 worktree_path="$HOME/.psm/worktrees/$project_alias/feat-$feature_name"243 git worktree add "$worktree_path" "$branch_name"244 ```2452464. **Create session, tmux, launch codex** (same pattern)247248### Subcommand: `list [project]`249250**Purpose**: List active sessions251252**Steps**:2532541. **Read sessions registry**:255 ```bash256 cat ~/.psm/sessions.json 2>/dev/null || echo '{"sessions":{}}'257 ```2582592. **Check tmux sessions**:260 ```bash261 tmux list-sessions -F "#{session_name}" 2>/dev/null | grep "^psm:"262 ```2632643. **Check worktrees**:265 ```bash266 ls -la ~/.psm/worktrees/*/ 2>/dev/null267 ```2682694. **Format output**:270 ```271 Active PSM Sessions:272273 ID | Type | Status | Worktree274 -------------------|---------|----------|---------------------------275 omc:pr-123 | review | active | ~/.psm/worktrees/omc/pr-123276 omc:issue-42 | fix | detached | ~/.psm/worktrees/omc/issue-42277 ```278279### Subcommand: `attach <session>`280281**Purpose**: Attach to existing session282283**Steps**:2842851. **Parse session ID**: `project:type-number`2862872. **Verify session exists**:288 ```bash289 tmux has-session -t "psm:$session_id" 2>/dev/null290 ```2912923. **Attach**:293 ```bash294 tmux attach -t "psm:$session_id"295 ```296297### Subcommand: `kill <session>`298299**Purpose**: Kill session and cleanup300301**Steps**:3023031. **Kill tmux session**:304 ```bash305 tmux kill-session -t "psm:$session_id" 2>/dev/null306 ```3073082. **Remove worktree**:309 ```bash310 worktree_path=$(jq -r ".sessions[\"$session_id\"].worktree" ~/.psm/sessions.json)311 source_repo=$(jq -r ".sessions[\"$session_id\"].source_repo" ~/.psm/sessions.json)312313 cd "$source_repo"314 git worktree remove "$worktree_path" --force315 ```3163173. **Update registry**:318 ```bash319 # Remove from sessions.json320 ```321322### Subcommand: `cleanup`323324**Purpose**: Clean up merged PRs and closed issues325326**Steps**:3273281. **Read all sessions**3293302. **For each PR session, check if merged**:331 ```bash332 gh pr view <pr_number> --repo <repo> --json merged,state333 ```3343353. **For each issue session, check if closed**:336 ```bash337 gh issue view <issue_number> --repo <repo> --json closed,state338 ```3393404. **Clean up merged/closed sessions**:341 - Kill tmux session342 - Remove worktree343 - Update registry3443455. **Report**:346 ```347 Cleanup complete:348 Removed: omc:pr-123 (merged)349 Removed: omc:issue-42 (closed)350 Kept: omc:feat-auth (active)351 ```352353### Subcommand: `status`354355**Purpose**: Show current session info356357**Steps**:3583591. **Detect current session** from tmux or cwd:360 ```bash361 tmux display-message -p "#{session_name}" 2>/dev/null362 # or check if cwd is inside a worktree363 ```3643652. **Read session metadata**:366 ```bash367 cat .psm-session.json 2>/dev/null368 ```3693703. **Show status**:371 ```372 Current Session: omc:pr-123373 Type: review374 PR: #123 - Add webhook support375 Branch: feature/webhooks376 Created: 2 hours ago377 ```378379---380381## Error Handling382383| Error | Resolution |384|-------|------------|385| Worktree exists | Offer: attach, recreate, or abort |386| PR not found | Verify URL/number, check permissions |387| No tmux | Warn and skip session creation |388| No gh CLI | Error with install instructions |389390## Requirements391392- `git` with worktree support (v2.5+)393- `gh` CLI (authenticated)394- `tmux`395- `jq` for JSON parsing396397## Initialization398399On first run, create default config:400401```bash402mkdir -p ~/.psm/worktrees ~/.psm/logs403404# Create default projects.json if not exists405if [[ ! -f ~/.psm/projects.json ]]; then406 cat > ~/.psm/projects.json << 'EOF'407{408 "aliases": {409 "omc": {410 "repo": "Yeachan-Heo/oh-my-codex",411 "local": "~/Workspace/oh-my-codex",412 "default_base": "main"413 }414 },415 "defaults": {416 "worktree_root": "~/.psm/worktrees",417 "cleanup_after_days": 14,418 "auto_cleanup_merged": true419 }420}421EOF422fi423424# Create sessions.json if not exists425if [[ ! -f ~/.psm/sessions.json ]]; then426 echo '{"version":1,"sessions":{},"stats":{"total_created":0,"total_cleaned":0}}' > ~/.psm/sessions.json427fi428```