# Session Manager — implementation plan (pre-code review) Status: **built and shipped as v0.1.0 on 2026-08-19.** Kept as the record of what was decided before implementation, and why. See README.md and CHANGELOG.md for what exists now. Reviewed against CodemanPanel and Codeman v1.18.4 on 2026-08-18. --- ## 1. Conflicts in the brief ### 1.1 CodemanPanel is not a Node app, and has no tab bar or terminal The brief asks to "reuse CodemanPanel's existing component structure" **and** to build on "Node.js + Fastify, matching what Codeman itself uses". Those are two different codebases. | | CodemanPanel | Codeman | |---|---|---| | Language | Python 3.12, **stdlib only** (no framework, on purpose) | TypeScript / Node 22 | | Server | `http.server.ThreadingHTTPServer` | Fastify 5 + `@fastify/websocket` | | Frontend | Vanilla JS + CSS, no build step (1,124 lines JS) | xterm.js 6, bundled | | Size | ~4,400 lines, zero dependencies | 34 MB src, **789 MB node_modules** | | Terminal | none | node-pty + xterm | | Tab bar | **none — explicitly refused** ("two strips for one set of tabs is redundant chrome") | yes | | tmux logic | none | `tmux-manager.ts`, 3,604 lines | So the screenshot is **two products stitched together**: the sidebar is CodemanPanel, the tab bar / terminal / input bar are Codeman. Roughly half of "reuse CodemanPanel" is not available because that half does not exist there. ### 1.2 What is genuinely reusable (and it is a lot) Lifted or adapted, not rebuilt: - `auth.py` (133 lines) — password + signed cookie + login page. Take almost verbatim. - `store.py` (158 lines) — folder store, colour palette, **auto-filing by directory prefix** (directory prefix → a named folder). Adapt. - `data/groups.json` — the folder schema he already uses, with his six real groups and their colours. Reuse as the seed file. - `prefix_proxy.py:143` — a working hand-rolled WebSocket relay over stdlib sockets. This is the piece that makes a Python backend viable for terminal streaming. - `web/app.css` (318 lines) + the sidebar half of `web/app.js` — search box, folder tree, drag-drop between groups, double-click inline rename, collapse-to-rail, theme tokens. Not reusable, must be new: tmux engine, PTY bridge, tab bar, terminal panel, input bar, CLI registry, stats bar. ### 1.3 The stack decision this forces — **recommendation: Python** Two coherent options; picking one is the only thing blocking the build. **A. Python, stdlib-only** (recommended). Same shape as CodemanPanel, so the sidebar, auth and WebSocket code come across as-is. `pty` and `tomllib` are in the standard library, so there are no compiled dependencies, no `node_modules`, no build step. Expected footprint **~30–50 MB RSS**. Cost: the Fastify patterns from Codeman don't transfer — but the parts worth transferring are `tmux send-keys` / `capture-pane` shell calls, which are shell, not framework. **B. Node + Fastify.** Matches Codeman, so its tmux and xterm patterns port directly. Cost: the sidebar is rewritten from zero, plus ~790 MB of `node_modules` and a build step. The running Codeman web process is **253 MB RSS** today; Fastify + node-pty lands in that range. Given "lightweight, so I can run many tmux sessions", A wins on his stated constraint. ### 1.4 The real resource ceiling is not the manager Measured on this box right now: - 4 CPUs, 16 GB RAM, **9 GB available**, 6 Claude sessions live. - Each `claude` process: **~480–550 MB RSS**. Six of them ≈ 3 GB. - Codeman's web server: 253 MB. So the session manager is rounding error; **Claude Code itself sets the ceiling at roughly 12–15 concurrent sessions** on this box regardless of what we build. What the manager *can* do is refuse to add per-session overhead — see §4. ### 1.5 Minor - No screenshot reached this session. The plan is built from the written description only; if the image has layout detail the text doesn't, it may change the frontend section. - Ports 3000/3001 are Codeman (claude/grok), 3100/3101 are CodemanPanel. Proposing **3200**. --- ## 2. Checklist answers | Item | Answer | |---|---| | Confirm existing frontend framework and reuse it | **No framework.** Vanilla JS + CSS, no build step. Reuse it as-is — introducing React here would be the regression. | | Any backend tmux/session logic worth keeping? | **None in CodemanPanel** — it shells out to Codeman's HTTP API (`codeman.py`) and never touches tmux. Codeman's `tmux-manager.ts` is 3,604 lines and worth reading for technique, not porting. | | What does the brief duplicate? | Search, folder tree, drag-drop, inline rename, create/delete folders, per-folder colour dot, session counts, elapsed time, password auth — **all already built and working** in CodemanPanel. Also already solved there and worth keeping: auto-filing new sessions by directory rule, and collapse-to-rail. | | CLI registry format | TOML config file — see §3. Adding Grok is a 5-line block, no code change. | | tmux name collision | **Safe.** Codeman uses `codeman-<8hex>`; we use `sm-<8hex>`. Verified Codeman only ever kills sessions present in its own `state.json` (`tmux-manager.ts:2431-2446`), so it will not touch ours, and we will only touch `sm-*`. Both can run side by side through the transition. | | Implementation plan / file structure | §4 and §5. | --- ## 3. CLI type registry `config/clis.toml`, read with stdlib `tomllib`. Hot-reloaded on change — no restart. ```toml [cli.claude] label = "Claude Code" command = "claude" args = ["--permission-mode", "{mode}", "--session-id", "{uuid}", "--name", "{name}"] resume = ["--permission-mode", "{mode}", "--resume", "{cli_session_id}"] color = "#c7915b" modes = ["default", "acceptEdits", "plan", "auto"] # drives the mode pill mode_key = "S-Tab" # what the pill sends into the pane mode_label = "{mode} mode on (shift+tab to cycle)" [cli.grok] label = "Grok CLI" command = "grok" args = [] color = "#6f42c1" # no `modes` key -> the mode pill is hidden for this CLI automatically [cli.shell] label = "Shell" command = "bash" args = ["-l"] color = "#8b8b8b" ``` **Adding a new CLI = adding a block.** Placeholders (`{uuid}`, `{name}`, `{mode}`, `{cwd}`, `{cli_session_id}`) are substituted at launch; unknown placeholders are an error at load time, not a mystery at runtime. Absence of `modes` is what hides the autonomy pill, so that requirement falls out of the config rather than needing a per-CLI code branch. --- ## 4. Architecture — the lightweight part - **One tmux session per CLI instance**, `sm-<8hex>`, full UUID kept in state. tmux is the only thing that persists; the manager holds no session state in memory it can't rebuild. - **PTY only while a browser is watching.** On WebSocket connect, fork a PTY running `tmux attach -t sm-xxxx`; on disconnect, kill it. An idle session costs the backend **zero** — cost scales with open tabs, not with session count. This is the single decision that makes "many sessions" cheap. - **Reattach shows scrollback** via one `capture-pane -p -e -J -S -5000` at connect, then live stream. No persistent `pipe-pane` log files, so nothing grows on disk. - **Sidebar polling is one subprocess total** — a single `tmux list-sessions -F '#{...}'` every 3s for every session's status, not one call per session. - **Input** goes back as `send-keys -l` + `Enter` (Codeman's approach, and the reliable one). - **State**: single `data/state.json`, atomic write + `.bak`, same as CodemanPanel. - **Auth**: password + signed cookie, Tailscale-only bind. No QR. --- ## 5. File structure ``` / ├── pyproject.toml ├── README.md ├── config/ │ └── clis.toml # CLI registry (§3) ├── data/ │ └── state.json # sessions + folders; seeded from CodemanPanel groups.json ├── deploy/ │ └── .service # systemd user unit, pattern from CodemanPanel deploy/ └── / ├── __main__.py # serve --host --port 3200 ├── app.py # routing, static, JSON API, WS upgrade [pattern: CodemanPanel app.py] ├── auth.py # password + cookie [LIFT ~as-is] ├── store.py # folders, sessions, auto-file rules [ADAPT] ├── registry.py # clis.toml loader + arg templating [NEW] ├── tmux.py # create / list / attach / kill / send / capture [NEW] ├── stream.py # PTY <-> WebSocket bridge [NEW, WS framing from prefix_proxy.py] └── web/ ├── index.html ├── app.css # sidebar [ADAPT] + tab bar / terminal [NEW] ├── app.js # sidebar [ADAPT] + tabs / term / input [NEW] └── vendor/ # xterm.js + fit addon, vendored (no CDN, no build step) ``` --- ## 6. Build order Unchanged from the brief; it is the right order. 1. `tmux.py` + `registry.py` — engine and CLI config, exercised from a CLI smoke test 2. `app.py` + `stream.py` — API and live streaming, proven end to end with Claude Code 3. Terminal panel + tab bar 4. Sidebar (ported from CodemanPanel) — folder tree, drag-drop, rename, search 5. Add Grok as a registry block only — if it needs a code change, the registry design failed 6. Polish: stats bar, run/shell split, new-session modal Phase 2, per the brief: repeat stepper, voice input.