[[
wikihub
]]
Search
⌘K
Explore
People
For Agents
Sign in
Explore
People
For Agents
Sign in
@jacobcole / picortex — planning / docs/plans/2026-04-23-initial-roadmap.md
Suggest edit
Cancel
Submit suggestion
Title
Name
Note
--- visibility: public --- # picortex — Initial Roadmap **Date:** 2026-04-23 **Status:** Draft — Codex review in hand, revisions pending (see [codex-2026-04-23.md](../reviews/codex-2026-04-23.md)) > **Open follow-ups from Codex review (2026-04-23):** > - S3 may be an anti-pattern (shared tmux before per-chat isolation). Candidate: merge S3 into S4 and prefix with a `claude --print` spike. — `picortex-b92` > - Add S1.5: minimal SQLite schema, event normalization, replay/idempotency storage, outbound delivery records. — `picortex-ul4` > - Pick Linux-only or reduce v0.1 feature set for Mac Mini. — `picortex-mn3` > - Replace broad sudoers with narrow wrapper binary. — `picortex-5sc` > - Reply-capture proof stage — run real Claude CLI in tmux, verify sentinels in practice before committing the design. — `picortex-3vj` > - Move queueing/concurrency/retry rules into core plan. — `picortex-nk2` > - Egress-deny story must be a gate, not polish. — `picortex-xqx` > - Drop warm pool unless measurement proves need. — `picortex-j2p` A phased plan from "no code" to "Jacob uses it daily from his phone." Each stage ends in a demo that proves a property. No stage is allowed to merge without: (a) beads ticket closed, (b) tests green, (c) a short screen recording against linq-sim committed to `docs/demos/`. --- ## Phase A — Testing harness & fundamentals ### S0 · Planning & docs (done, 2026-04-23) - [x] PRD, ADRs 0001-0005, Specs 001-008, Wiki, LLM wiki, llms.txt, AGENTS.md - [x] Beads initialized (`bd init picortex`) - [x] Codex second opinion requested and filed at `docs/reviews/codex-2026-04-23.md` ### S1 · Minimal backend skeleton (1-2 days) **Goal:** `npm run dev` starts a Fastify server on :7823 that accepts signed linq-sim webhooks and echoes them back as outbound sendMessage calls. No Claude Code yet. - Fastify app, strict TS, pino logger, `X-Request-ID` - `POST /api/linq/inbound` with HMAC verification - Outbound Linq client (pointed at linq-sim by default) - `/health`, `/api/frontend-log` stubs - Vitest config, first smoke test - `picortex-S1-*` beads tickets **Demo:** Send a message in linq-sim's `/` admin UI → picortex logs it with request ID and replies with "echo: <text>". ### S2 · linq-sim: thread/reply support (2-3 hours, lives in `~/code/cortex`) **Goal:** linq-sim supports `data.reply_to_message_id` on `message.received` and outbound `sendMessage`. - Add `replyToMessageId` field to `buildInboundPayload()` in `src/server.js` - Mirror on outbound-capture `sendMessage` handler - Add `replyTo` composer input + "Reply" button in `ui.html` and `as-user.html` - Optional: validate parent exists in ring buffer - Contribute back to Cortex via PR **Demo:** linq-sim UI supports composing a reply; picortex echoes back preserving the reply pointer. ### S3 · Single-user tmux + Claude Code spawn (2-3 days) **Goal:** Backend on receive creates / reuses a single global tmux session `picortex:default`, sends the user's text to it, captures Claude Code's reply, sends back via Linq. Not isolated yet — one session for all chats. - `node-pty` + tmux wrapping - Input routing (`tmux send-keys`, then tail `pipe-pane` output) - Reply parsing (terminal → clean text) - Attention gating mode `always` only - `picortex-S3-*` tickets **Demo:** Text picortex from linq-sim, get a real Claude Code response grounded in files under `~/picortex-default/`. --- ## Phase B — Per-chat isolation & attention ### S4 · Linux-user provisioning per chat (3-4 days) **Goal:** Per-chat isolation working end-to-end. - `useradd --create-home --home-dir $CHAT_WORKSPACE_ROOT/<chat_id> --shell /bin/bash chat-<hex>` - sudoers drop-in granting the picortex service user `runuser -u chat-<hex>` - `chmod 0700` on each home dir - SQLite schema: `chats`, `chat_users`, `messages`, `events`, `bridge_events`, `chat_config` - Tmux-per-chat naming; on-demand creation - Warm-pool and idle-reap background job (stubbed — no actual reap until S6) **Demo:** Two linq-sim "users" send parallel conversations; `ls -la /srv/picortex/chats/` shows two distinct, 0700-locked dirs; cross-chat file access denied. ### S5 · Attention gating (2-3 days) **Goal:** Inbound messages to groups are filtered. LLM discriminator runs on `discriminate` mode. - Per-chat config in `chat_config` table - `@mention` detection for iMessage group markers - Mode `mentions-only`, `discriminate`, `discriminate-quiet`, `silent` - `.picortex/prompts/discriminator.md` default template, committed to the chat's home dir as a tiny git repo - Admin command: `/picortex attention <mode>` (message body parser) **Demo:** Same linq-sim group chat; off-topic messages don't trigger a response in `discriminate` mode; on-topic ones do. ### S6 · Lifecycle & warm pool (2 days) **Goal:** Sessions hibernate cleanly. - Idle-detector cron: kill tmux after 7 days inactive - Home-dir archive after 30 days - On inbound to archived chat: restore, cold-start path - Warm pool: keep N-1 idle tmux sessions pre-spawned for latency **Demo:** Force an idle kill; resend from linq-sim; see cold-start path fire within NFR-1 budget. --- ## Phase C — Mobile-first web UI ### S7 · Web UI: messages + file browser (3-4 days) **Goal:** Mobile Safari experience. A user authenticated with Noos OAuth can see all their chats, pick one, and view messages + files in a split / swipe layout. - Vite + React + Tailwind app on :7824 - Noos OAuth flow (follows voice-assistant pattern) - `/chats`, `/chats/:id`, `/chats/:id/files/*` routes - Mobile-first layout: `[Messages | Files | Terminal-stub]` swipe panels - Viewport meta, 44pt tap targets, keyboard focus rings - Version display in footer, update-available dot badge - Reply-to rendering (FR-20) **Demo:** Load picortex on phone; see most recent linq-sim conversation; swipe to see file tree; tap a file, view contents. ### S8 · Web terminal (2-3 days) **Goal:** Attach to any chat's tmux session from the browser. - xterm.js client - WebSocket PTY bridge: `/ws/terminal/:chat_id` - Backend spawns `tmux attach -t picortex:<chat_id>` inside `runuser -u chat-<hex>` - Read-only vs read-write modes (v1: read-write for Jacob; restrict later) - Resize propagation **Demo:** Open chat on phone; swipe to terminal; see Claude Code's live output; type `ls` and see it run as the chat user. ### S9 · Sharing bridge with audit (2-3 days) **Goal:** Cross-chat file import with out-of-band challenge. - `BridgeEvent` table - "Import from personal workspace" command parser - DM challenge loop (reuses Linq 1:1 path) - UI: bridge log viewer in chat settings **Demo:** From a linq-sim group chat, ask bot to import a file from DM workspace; challenge DM appears; reply "yes"; file copied; BridgeEvent row visible in UI. --- ## Phase D — Production hardening & ecosystem ### D1 · Deployment target & runbook (1 day) - Decide: Hetzner sibling of jcortex, Fly.io, or HMA (Q3) - Write `docs/runbooks/deploy.md` - Systemd unit, Caddy reverse proxy - `deploy.sh` one-liner - Closes Q3 ### D2 · Security-isolation report (research ticket `picortex-sec-1`) - Compare Docker vs Linux-user vs firejail/bubblewrap vs nsjail vs Landlock for Claude-Code-per-chat - Reference existing Jacob research: `~/memory/research/openclaw-group-chat-security.md`, `openclaw-security-audit-2026-02-20.md`, `openclaw-voice-call-tool-execution.md` - Decide if ADR-0002 holds or if we graduate to bubblewrap/Landlock - Deliverable: `docs/wiki/isolation-models.md` + revised ADR if needed ### D3 · OpenChat linq-adapter (optional; may live in `~/code/openchat` instead) - Add `reactions` table + REST + WS events (closes `OpenChat-yg8`-adjacent work) - Add outbound `/api/partner/v3/*` adapter emitting HMAC-signed webhooks matching Linq's 14 event types - picortex can now use OpenChat as an alternate channel without Linq - Estimated 1-2 weeks - Decision gate: is the effort worth it before picortex v0.1? Probably no — defer to v0.2. ### D4 · Cut v0.1.0 - Tag, push, fill in `CHANGELOG.md` - Update root `PROJECTS.md` entry to **ACTIVE** - Announce to self in daily note --- ## Explicit deferrals (v0.2+) - Cross-chat MCP (Cortex R6) - Noos knowledge-graph federation - Group-chat mobile-first UI - Native iOS / Android - OpenChat adapter productionization (D3) - Multi-user / shared access --- ## Dependency graph ``` S0 ─▶ S1 ─▶ S2 ─▶ S3 ─▶ S4 ─▶ S5 ─▶ S6 ─▶ S7 ─▶ S8 ─▶ S9 ─▶ D1 ─▶ D4 (v0.1.0) │ D2 (runs parallel to S4+) ─┘ D3 (deferred) ``` ## Estimated total effort - Planning (S0): done in this session - Phase A (S1-S3): ~1 week - Phase B (S4-S6): ~1.5 weeks - Phase C (S7-S9): ~1.5 weeks - Phase D (D1-D2, D4): ~3 days - **Total to v0.1.0: ~4 weeks of focused work**, 6-8 weeks elapsed at 50% focus. ## Risks | Risk | Mitigation | |---|---| | Linux-user isolation insufficient for adversarial workloads | D2 report; if fails, add bubblewrap/Landlock inside `runuser` | | Claude Code CLI doesn't behave well under long-lived tmux | Fall back to short-lived `claude --print` invocations per turn | | Linq API changes / not yet live for Jacob | linq-sim covers dev; real Linq onboarding can wait until S7+ | | tmux attach streaming performance via WebSocket | Precedent exists (Cortex cloudcli, Claude Code UI) | | Scope creep toward rebuilding Cortex | This plan is capped at v0.1; additional features must re-scope |