Skip to content

Project Folders

Multi-project support for ClawMux. Each project gets its own set of 7 agents, chat history, work directories, and context. Shipped in v0.5.x.

User Experience

  1. User clicks New Project in the ClawMux web UI
  2. Enters a project name (e.g. "zeuldocs", "clawmux-v2", "web-finder")
  3. A new project is created with all 7 default agents (Sky, Alloy, Sarah, Adam, Echo, Onyx, Fable)
  4. User can switch between projects — each project has independent agents, history, and state
  5. Projects can be created, renamed, archived, and deleted

Architecture

Previous State (pre-projects)

/tmp/clawmux-sessions/
├── af_sky/          ← one Sky, one Alloy, etc.
├── af_alloy/
├── af_sarah/
├── am_adam/
├── am_echo/
├── am_onyx/
└── bm_fable/
  • One flat set of 7 sessions
  • tmux sessions named voice-sky, voice-alloy, etc.
  • Session IDs are global: voice-{name}
  • No concept of projects

Current State (shipped)

/tmp/clawmux-sessions/
├── default/
│   ├── af_sky/
│   ├── af_alloy/
│   └── ...
├── zeuldocs/
│   ├── af_sky/
│   ├── af_alloy/
│   └── ...
└── clawmux-v2/
    ├── af_sky/
    ├── af_alloy/
    └── ...
  • Sessions nested under project directories
  • tmux sessions namespaced: default-sky, zeuldocs-sky, etc.
  • Session IDs include project: {project}-{name}
  • Each project has independent CLAUDE.md, .mcp.json, history

Data Model

Project

@dataclass
class Project:
    name: str                    # URL-safe slug (e.g. "zeuldocs")
    display_name: str            # Human name (e.g. "Zeul Docs")
    created_at: float
    work_dir: str                # /tmp/clawmux-sessions/{name}/
    active: bool = True          # False = archived

Session Changes

@dataclass
class Session:
    # Existing fields...
    project: str = "default"     # Which project this session belongs to

Session ID format changes from voice-{name} to {project}-{name}.

Storage

data/
├── settings.json                # Existing global settings
└── projects.json                # Project registry
{
  "projects": [
    {
      "name": "default",
      "display_name": "Default",
      "created_at": 1709500000,
      "active": true
    },
    {
      "name": "zeuldocs",
      "display_name": "Zeul Docs",
      "created_at": 1709500100,
      "active": true
    }
  ],
  "active_project": "default"
}

History Store

The existing HistoryStore needs project namespacing:

data/history/
├── default/
│   ├── af_sky.json
│   └── ...
├── zeuldocs/
│   ├── af_sky.json
│   └── ...

UI

Project Selector (Top Bar)

┌─────────────────────────────────────────────┐
│  ClawMux    [▼ Default Project]    [+] [⚙]  │
├─────────────────────────────────────────────┤
│  ┌─────────────────────────┐                │
│  │ ● Default Project    ✓  │                │
│  │ ○ Zeul Docs             │                │
│  │ ○ ClawMux v2            │                │
│  │ ─────────────────────── │                │
│  │ + New Project...        │                │
│  │ ⚙ Manage Projects       │                │
│  └─────────────────────────┘                │
│                                             │
│  ┌─────┐ ┌───────┐ ┌───────┐ ┌──────┐      │
│  │ Sky │ │ Alloy │ │ Sarah │ │ Adam │ ...  │
│  └─────┘ └───────┘ └───────┘ └──────┘      │
└─────────────────────────────────────────────┘
  • Dropdown in the top bar shows all projects
  • Active project has a checkmark
  • Quick-create button [+] next to the dropdown
  • Agent cards below show only the agents for the selected project

New Project Dialog

┌──────────────────────────────┐
│  New Project                 │
│                              │
│  Name: [________________]    │
│                              │
│  Work Dir (optional):        │
│  [________________________]  │
│                              │
│  [Cancel]        [Create]    │
└──────────────────────────────┘
  • Name is required, auto-slugified for directory names
  • Optional work directory lets the user point to an existing repo
  • If no work dir specified, uses /tmp/clawmux-sessions/{slug}/

Manage Projects

┌──────────────────────────────────────┐
│  Manage Projects                     │
│                                      │
│  Default Project          [Archive]  │
│    7 agents · active                 │
│                                      │
│  Zeul Docs                [Archive]  │
│    7 agents · active                 │
│                                      │
│  ClawMux v2               [Delete]   │
│    0 agents · archived               │
│                                      │
└──────────────────────────────────────┘

API Changes

New Endpoints

Method Path Description
GET /api/projects List all projects
POST /api/projects Create a new project
PUT /api/projects/{name} Update project (rename, archive)
DELETE /api/projects/{name} Delete project and all its sessions
POST /api/projects/{name}/activate Switch active project

Modified Endpoints

Method Path Change
GET /api/sessions Returns sessions for active project only
POST /api/sessions Creates session in active project
WebSocket /ws project field added to session state messages

WebSocket Protocol Addition

New message type for project switching:

{"type": "switch_project", "project": "zeuldocs"}

Hub responds with updated session list for the new project.

Resource Management

Scaling Concern

Each project = up to 7 tmux sessions = up to 7 Claude Code instances.

3 projects × 7 agents = 21 concurrent Claude processes. This is heavy.

Strategy: Lazy Spawning

Don't spawn all 7 agents when a project is created. Spawn on demand:

  1. Project created → 0 agents running. Just the project entry in projects.json.
  2. User clicks an agent card → That agent spawns for this project.
  3. Agent idles for N minutes → Session suspends (tmux stays, Claude exits). History preserved for --resume.
  4. User clicks suspended agent → Claude resumes in existing tmux.

This means a user with 5 projects but only actively using 1 will have ~7 processes, not 35.

Session Lifecycle

Created → Spawning → Ready → Active → Idle → Suspended
                                        (click to resume)
                                        Resuming → Ready

Suspended = tmux session alive but Claude process exited. Work dir and history intact. Resume is instant via claude --resume.

Limits

  • Configurable max concurrent sessions across all projects (default: 14 = 2 full projects)
  • When limit hit, oldest idle session auto-suspends
  • Warning shown in UI when approaching limit

Implementation Steps

Phase 1: Backend Data Model

  1. Create ProjectManager class in server/project_manager.py
  2. Add projects.json read/write
  3. Modify SessionManager to accept project context
  4. Namespace work dirs: /tmp/clawmux-sessions/{project}/{voice_id}/
  5. Namespace tmux sessions: {project}-{voice_name}
  6. Namespace history store paths

Phase 2: API Layer

  1. Add project CRUD endpoints to hub.py
  2. Modify session endpoints to filter by active project
  3. Add switch_project WebSocket message handler
  4. Add project field to session state broadcasts
  5. Migration: wrap existing sessions in a "default" project on first boot

Phase 3: Frontend — Project Selector

  1. Add project dropdown component to top bar
  2. Implement project switching (WebSocket message)
  3. Update agent grid to show only active project's agents
  4. Add "New Project" quick-create flow

Phase 4: Frontend — Project Management

  1. Full project management dialog
  2. Archive/unarchive projects
  3. Delete project (with confirmation — destroys all sessions and history)
  4. Rename project

Phase 5: Lazy Spawning & Suspension

  1. Implement session suspension (exit Claude, keep tmux)
  2. Implement resume-from-suspended
  3. Add concurrent session limit with auto-suspend
  4. UI indicators for suspended vs active vs spawning states

Phase 6: Per-Project CLAUDE.md

  1. Each project gets its own CLAUDE.md template
  2. Project-level context (repo path, instructions) injected into agent CLAUDE.md
  3. Users can customize per-project agent behavior

Migration

No auto-migration. The default project keeps the existing flat directory layout (/tmp/clawmux-sessions/{voice_id}/) forever. Named projects are opt-in and get their own subdirectories (/tmp/clawmux-sessions/{project}/{voice_id}/).

CRITICAL: Never move a live session directory. Claude Code sessions are tied to their exact working directory path. Moving a directory breaks bash, tools, and context permanently.

Recovery Plan

If deploying the project folders code breaks anything, run these exact commands:

1. Revert code

cd /home/zeul/GIT/clawmux
git checkout server/hub.py server/session_manager.py server/history_store.py
rm -f server/project_manager.py

2. Restart hub

clawmux reload

3. Verify agents reconnect

sleep 30
curl -s http://localhost:3460/api/sessions | python3 -c "
import json, sys
for s in json.load(sys.stdin):
    print(f'{s[\"voice\"]}: {s[\"status\"]}')
"

4. Verify agents can run commands

# Send a test message to any agent
curl -s -X POST http://localhost:3460/api/messages/send \
  -H "Content-Type: application/json" \
  -d '{"sender": "voice-sky", "to": "voice-onyx", "message": "Health check — can you run bash?"}'

5. If directories were moved

cd /tmp/clawmux-sessions/default && mv * ../ && cd .. && rmdir default

6. If projects.json is corrupted

rm /home/zeul/GIT/clawmux/data/projects.json
# Hub recreates it with just the default project on next start