[[
wikihub
]]
Search
⌘K
Explore
People
For Agents
Sign in
Explore
People
For Agents
Sign in
@jacobcole / picortex — planning / docs/wiki/piyush-era-design.md
Suggest edit
Cancel
Submit suggestion
Title
Name
Note
--- visibility: public --- # Piyush-era Cortex design (2026-01-20 → 2026-01-23) A study, not an inheritance source. Nine commits under **Piyush Jha** that preceded Tejas's containerized rewrite. Worth reading in full because the shape is closer to what picortex-as-a-personal-tool wants than the current Cortex is. **Final SHA:** `d2d6a534` (2026-01-23 03:45 PST). Replaced wholesale starting `af3a76f5` (Tejas, 2026-01-26). ## Architecture ``` ┌───────────────────────────────────────────────┐ │ Frontend (Vercel) │ │ React 19 + Vite + Socket.IO + xterm.js │ │ Auth │ File Browser │ Chat │ Terminal │ └──────────────┬────────────────────────────────┘ │ REST + WebSocket ┌──────────────┴────────────────────────────────┐ │ Backend (separate server) │ │ Node.js + Express + Prisma + Socket.IO │ │ • auth • files • claudeService • ssh │ └──────────────┬────────────────────────────────┘ │ SSH (ed25519, keyed) ┌──────────────┴────────────────────────────────┐ │ EC2 workspace host (single shared box) │ │ cortex_admin service user │ │ └─ sudoers drop-in: useradd/userdel/chmod │ │ cortex_user_<id> per signup │ │ ├─ ~/workspace/ │ │ ├─ ~/.claude/ (API key via apiKeyHelper) │ │ └─ claude CLI pre-installed │ └───────────────────────────────────────────────┘ ``` Three machines, three roles. **This separation is what picortex has been missing** — Jacob's question "could the workspace just live on a remote box?" is the Piyush-era answer. ## The turn loop (the important bit) `backend/src/websocket/handlers/chatHandler.ts` + `services/claudeService.ts`: ```ts // Per inbound message: socket.on('chat:message', async ({ content }) => { const workspace = await prisma.workspace.findUnique({ where: { userId } }); const username = workspace.linuxUsername; await prisma.message.create({ data: { userId, role: 'user', content } }); // The actual Claude call: const cmd = `claude -c --dangerously-skip-permissions -p "${escaped}"`; const result = await sshService.execAsUser(username, cmd); await prisma.message.create({ data: { userId, role: 'assistant', content: result.stdout } }); socket.emit('chat:response', { content: result.stdout }); }); ``` No tmux. No sentinel protocol. No long-lived REPL to babysit. Each turn: 1. Backend picks the chat's Linux username from Prisma. 2. Backend opens an SSH session as that user on the EC2 host. 3. Runs `claude -c --dangerously-skip-permissions -p "<prompt>"`. 4. `-c` is Claude CLI's **"continue the previous conversation"** flag — session memory is managed by Claude itself in `~/.claude/`, not by the backend. 5. stdout is the reply. Close SSH. Done. This is the design codex told picortex to try (the `claude --print` spike — `picortex-b92`, `picortex-3vj`). Piyush already ran the experiment and it worked. ## What else Piyush built From `backend/src/` at `d2d6a534`: - **`services/sshService.ts`** — pooled SSH connections to the workspace host, one keyed connection per chat-user. Mock mode for dev. - **`services/workspaceService.ts`** — 4-step provisioning with WebSocket progress events: `create_user` → `install_cli` → `configure_api` → `complete`. Each user gets their Anthropic key stored encrypted server-side; written to `~/.claude/config.json` via `apiKeyHelper`. - **`services/fileService.ts`** / **`controllers/fileController.ts`** — read/list/write over SSH. - **`services/voiceService.ts`** — OpenAI-backed voice chat. - **`websocket/handlers/terminalHandler.ts`** — xterm.js ↔ SSH PTY bridge. - **`websocket/handlers/provisioningHandler.ts`** — emits provisioning progress. - **`routes/authRoutes.ts`** + **`middleware/auth.ts`** — JWT auth with user-supplied Anthropic API key at signup. - **`prisma/schema.prisma`** — `User`, `Workspace`, `Message`. SQLite-first. Frontend `frontend/src/components/`: - `chat/{ChatContainer, MessageInput, MessageList, VoiceButton}.tsx` - `files/{FileBrowser, FilePreview, FileTree}.tsx` - `terminal/WebTerminal.tsx` - `provisioning/ProvisioningScreen.tsx` - `auth/{LoginForm, SignupForm, ProtectedRoute}.tsx` - `sidebar/Sidebar.tsx` (chat list) ## What Piyush got right that picortex should steal 1. **Three-tier physical layout.** Frontend (Vercel) ↔ backend (whatever) ↔ workspace host (separate). The bot box and the workspace box are different machines. This is the natural home for Jacob's "remote box for privacy" instinct. 2. **`claude -c -p` per turn, no tmux.** Session continuity is Claude's own feature. Replace the sentinel-protocol design in `spec/002-tmux-session-spawning.md` with this. Simpler, survives backend restarts, fewer moving parts. 3. **Per-user provisioning with real-time progress events.** `provisioning:status` WebSocket events with `{step, progress, message}` is a nice UX pattern; picortex's mobile UI should mirror it for cold-start chats. 4. **`apiKeyHelper` via Claude CLI config.** Lets the backend inject/rotate keys without writing them into workspace files. Decoupled secret management. 5. **Mock mode for SSH.** The `sshService.isMockMode()` check lets the whole frontend+chat flow run without real EC2. picortex needs the same — linq-sim for the Linq side, an `sshMock` or local-exec fallback for the workspace side. 6. **Backend's sudoers are scoped to user-management binaries only**: `useradd`, `userdel`, `chown`, `chmod`, `mkdir`, `sudo -u *`. No `bash`, no `tmux`. That's tighter than picortex's current spec and closer to what codex asked for (`picortex-5sc`). ## What Piyush got wrong (and why it was replaced) 1. **Per-user, not per-chat workspaces.** Fine for "I log in and chat with one Claude." Useless for group texts where each chat needs its own context and filesystem. Cortex pivoted on this. 2. **No attention gating / group model.** Piyush's chat is a single web interface, not a group-text agent. 3. **Single shared EC2 box** with Linux users for isolation — acceptable for a v0 but blocked the enterprise multi-tenant direction. Fly.io Docker containers replaced it. 4. **No Linq / iMessage / SMS.** Web-chat-only. Adding texting was a wholesale rewrite, not an add-on. 5. **No per-file ACL, no sharing bridge, no cross-chat context.** All deferred. 6. **SQLite-first schema** would have needed a migration to scale; Tejas moved to Postgres. 7. **Claude's `-c` continuation is global per user**, not per-topic — subtle UX problem: one user's chats all share the same Claude memory. Needs `--session-id` instead of `-c` to get per-chat isolation. ## Mapping Piyush's design onto the "awesome texting" framing Piyush's design was "web chat + dev surface for one user." Jacob wants "iMessage for Jacob + group texts with shared brain." The overlap: | Piyush element | picortex need | Fit | |---|---|---| | Separate workspace host | "remote box for privacy" | ✅ perfect | | `claude -c -p` per turn | replaces tmux sentinel fragility | ✅ perfect | | Per-user Linux isolation | generalize to per-chat | ⚠️ adapt | | apiKeyHelper | keeps keys out of workspace | ✅ steal | | Sudoers scoped to useradd etc. | replaces picortex's broad sudoers | ✅ steal | | Provisioning progress WS events | mobile UI UX | ✅ steal for web UI | | File browser / xterm terminal | may or may not be needed for texting-first | 🤷 defer, probably skip in v0.1 | | Web chat UI | redundant if Linq is the primary surface | ❌ skip | | User signup / own API keys | Jacob is the only user | ❌ skip | | Voice | covered by existing voice-assistant project | ❌ skip | ## Verbatim quotes worth remembering From the original README at `d2d6a534`: > Each user gets their own isolated Linux environment on EC2 > Messages sent via WebSocket, executed as `claude -p "..."` via SSH From `claudeService.ts`: > `-c` continues the previous conversation session > `--dangerously-skip-permissions` allows file operations without interactive prompts From `setup-ec2.sh` sudoers block: ``` cortex_admin ALL=(ALL) NOPASSWD: /usr/sbin/useradd cortex_admin ALL=(ALL) NOPASSWD: /usr/sbin/userdel cortex_admin ALL=(ALL) NOPASSWD: /bin/chown cortex_admin ALL=(ALL) NOPASSWD: /bin/chmod cortex_admin ALL=(ALL) NOPASSWD: /bin/mkdir cortex_admin ALL=(ALL) NOPASSWD: /usr/bin/sudo -u * ``` ## References - Piyush's commits: `238052c4` → `d2d6a534` in `IdeaFlowCo/cortex` - Tejas's replacement: starting `af3a76f5` (2026-01-26, "Implement Fly.io workspace infrastructure") - Related brainstorm: [docs/plans/2026-04-23-prototype-options.md](../plans/2026-04-23-prototype-options.md) - The old "skip Cortex pre-container history" rule in [cortex-inheritance.md](cortex-inheritance.md) is **softened** — pre-container is still not a source of patterns to inherit, but **is** a source of ideas to *study*.