new file mode 100644
index 0000000..b8eaeac
@@ -0,0 +1,157 @@
+---
+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*.
\ No newline at end of file