PRD — picortex v1

Status: Draft — provisional pending Q0-Q4 closure Owner: Jacob Date: 2026-04-23 Last reviewed: 2026-04-23

⚠️ This PRD describes Option 1 (tmux-centered single-host with web terminal + file browser) from the prototype options brainstorm. Late in the planning session, the product was reframed as "awesome texting experience" with dev surface optional — that may demote several functional requirements here (especially FR-9..FR-12, FR-18..FR-20) to "one branch of the decision tree." Treat this PRD as the maximal version until Q0 (picortex-adb) defines measurable success criteria and Q2 (picortex-2b4) decides whether the dev surface is in v0.1. Reconciliation tracked in picortex-357.

1. Problem

Jacob wants to chat with Claude Code from his phone — via real iMessage, including group texts — the same way Cortex enables, but with a lighter-weight personal stack. The existing Cortex deployment is a shared business product carrying design constraints (Docker-container-per-workspace, session dashboard, multi-tenant billing) that aren't needed for a single user. Meanwhile, Cortex has just spent a month solving the actual hard problems (attention gating, per-chat isolation, sharing bridge, HMAC webhook ingestion, linq-sim harness) and all of those are directly reusable.

There is no tool today that gives Jacob:

  1. A persistent Claude Code tmux session per iMessage conversation,
  2. Per-chat Unix-user isolation so group-chat compromise doesn't leak across,
  3. A mobile-first web UI to see the file tree and attach a live terminal mid-conversation,
  4. The lightest-weight spin-up possible so provisioning a new chat costs cents not dollars.

2. Users & personas

v1 has one user: Jacob. Everything else is future.

Scenarios:

  • Jacob in an Uber, wants to check whether the Cortex deploy succeeded. Opens iMessage, texts the bot. Bot replies with status. No computer needed.
  • Jacob in a group text with two collaborators, asks the bot to summarize the thread and file a ticket. Bot responds inside the group thread (reply, not new message).
  • Jacob at desk, wants to see what Claude Code is doing in his chat with "research-bot". Opens web UI on laptop, clicks that chat, sees split view: messages on left, file tree + file viewer in middle, live terminal attached to the chat's tmux session on the right.
  • Jacob wants to import a file from his personal chat into a group chat. Group agent proposes the import; picortex DMs Jacob a challenge; Jacob replies "yes" from the DM; the file is copied with a BridgeEvent audit row.

3. Goals

G1. Text the bot from iMessage and get a useful Claude Code response within 10 seconds (warm chat) / 60 seconds (cold provision). G2. Every chat has its own Unix user + home dir; compromising a group chat's workspace cannot read Jacob's personal workspace. G3. Mobile Safari experience: a user on iPhone viewing any chat sees a usable, touch-first UI that does not require pinch-zoom. G4. Web terminal attaches to any chat's tmux session in < 2 s and displays the live Claude Code output. G5. linq-sim E2E suite covers at minimum: send/receive 1:1, send/receive group, reaction add/remove, reply-in-thread, attention-gating switch. G6. Operating cost of an idle chat is effectively zero (tmux session + a small home dir); an active chat's cost is dominated by the Anthropic API.

4. Non-goals

  • NG1. No Docker / docker-compose / Kubernetes. ADR-0002
  • NG2. No Cortex-style session-management dashboard. A web terminal attach to the current chat is the entire session UI. [Constraint from user]
  • NG3. No billing, multi-tenant, or team accounts.
  • NG4. No native mobile apps. Mobile-first web UI only.
  • NG5. No group-chat-specific mobile UI in v1 — 1:1 threading works; groups render on desktop first.
  • NG6. No MCP tools for cross-chat queries in v1 (Cortex R6 deferred; sharing bridge R7 is enough).
  • NG7. No OpenClaw. No OpenClaw. No OpenClaw. Softened 2026-04-23: OpenClaw was initially ruled out, but docs/plans/2026-04-23-prototype-options.md Option 5 reopened it on merit. Current status: not chosen, but not ruled out. Final decision is Q4 (picortex-3mk).
  • NG8. No shared knowledge graph backend yet. Canonical log is SQLite on the picortex server. Integration with noos deferred.

5. User stories

  • US-1. As Jacob, I text my iMessage-only bot "What's the status of the ListHub deploy?" and get a coherent answer that used run_command on the server.
  • US-2. As Jacob in a group text with two friends, I @mention the bot ("@picortex summarize this"). The bot replies in the thread (not a new message) with a 3-bullet summary.
  • US-3. As Jacob, I change a group's attention mode to "discriminate" and the bot stops responding to off-topic messages but still speaks up when the conversation is about something it can help with.
  • US-4. As Jacob on mobile Safari, I tap a chat and see messages, swipe-right gets file browser, swipe-further-right gets live terminal.
  • US-5. As Jacob, I ask in a group chat "bring in the refs/2026-plan.md from my DM workspace." The bot DMs me a challenge; I reply "yes"; the file appears in the group chat's workspace and a BridgeEvent is logged.
  • US-6. As Jacob deploying picortex, I run ./deploy.sh and the latest version appears in the UI's footer within seconds, with an update-available badge cleared.

6. Functional requirements

Numbered for citation. Every one is pass/fail testable.

Linq integration

  • FR-1. Bot receives inbound iMessage via POST /api/linq/inbound, signature verified with HMAC-SHA256("{timestamp}.{raw_body}", LINQ_WEBHOOK_SECRET). Reject unsigned / skew > 5 min / replay.
  • FR-2. Bot sends replies via POST $LINQ_BASE_URL/api/partner/v3/sendMessage.
  • FR-3. Bot supports all event types the linq-sim publishes: message.{received, delivered, read, edited, failed}, reaction.{added, removed}, chat.typing_indicator.{started, stopped}, chat.{created, updated, group_name_updated}, participant.{added, removed}.
  • FR-4. Bot supports in-thread replies (data.reply_to_message_id). Requires linq-sim to be upgraded — see Stage S2.

Workspace isolation

  • FR-5. Every durable chat ID maps to exactly one Unix user (chat-<8-hex-of-sha256>) and one home dir ($CHAT_WORKSPACE_ROOT/<chat_id>).
  • FR-6. Home dirs are chmod 0700, owned by the chat user. No other chat user can read them.
  • FR-7. The picortex backend runs as a privileged service user; shells into each chat's user via sudo -u chat-<hex> -H … (no shared filesystem write path).
  • FR-8. Backend SQLite is the canonical message log. The workspace FS is a cache only and is never used for authorization.

Sessions

  • FR-9. Each chat has a tmux session named picortex:<chat_id>. If missing, backend creates it on first inbound message.
  • FR-10. The tmux session runs claude --dangerously-skip-permissions (or equivalent) with cwd = chat home dir. Flag choice TBD in S3.
  • FR-11. Idle tmux sessions are killed after 7 days of zero activity; home dir preserved for 30 days, then archived.
  • FR-12. Web terminal (xterm.js) attaches to a chat's tmux session via tmux attach -t picortex:<chat_id> through a backend WebSocket PTY bridge.

Attention gating

  • FR-13. Each chat has an attention mode: always | mentions-only | discriminate | discriminate-quiet | silent. Default for 1:1: always; default for groups: mentions-only.
  • FR-14. Mode discriminate runs an LLM over the message with a git-versioned prompt at $CHAT_HOME/.picortex/prompts/discriminator.md and only proceeds if the model scores ≥ threshold.
  • FR-15. Mode changes are visible in the mobile UI's chat-settings panel.

Sharing bridge

  • FR-16. When chat A asks to import a file from chat B, backend DMs the user-intersection (same Jacob here) a challenge; only a reply from that DM approves the import.
  • FR-17. Every bridge op logs a BridgeEvent row (source chat, dest chat, file path, sha256, approver user, timestamp).

Mobile-first web UI

  • FR-18. Mobile Safari: messages pane is usable without pinch-zoom; tap-targets ≥ 44pt; <meta viewport> correct.
  • FR-19. Tabbed / swipe-panel layout on mobile: [Messages | Files | Terminal].
  • FR-20. In-thread replies render as a "replying to …" pill above the message.
  • FR-21. Footer shows picortex vX.Y.Z from package.json. When HEAD on main is newer than running commit, a dot badge appears next to the version.

Observability

  • FR-22. All server logs are structured JSON via pino.
  • FR-23. Every HTTP request has an X-Request-ID header, echoed to the response.
  • FR-24. POST /api/frontend-log accepts browser error reports and writes them to server logs with the originating X-Request-ID.
  • FR-25. /health returns 200 + {version, uptime, db_ok}.

7. Non-functional requirements

  • NFR-1. Cold-start new chat: provision Unix user, home dir, tmux session, first Claude turn — < 60 s.
  • NFR-2. Warm response (existing tmux, cached Anthropic session): < 10 s first token.
  • NFR-3. Idle cost: < 50 MB RAM per paused chat (tmux + bash, no Claude process).
  • NFR-4. Server handles ≥ 50 simultaneous chats on a 4 GB VPS.
  • NFR-5. Accessibility: WCAG AA contrast, keyboard nav for desktop web UI.
  • NFR-6. Secrets never written to disk in plaintext outside .env.

8. Open questions

# Question Owner Status
Q1 What Claude Code command-line flag set do we run tmux sessions with? --dangerously-skip-permissions inside the sandboxed Unix user? Jacob Open — revisit in S3
Q2 Do we need a backend read model (search) for message history or is grep-over-SQLite enough? Jacob Open — revisit post-S5
Q3 What's the deployment target: Hetzner (jcortex sibling), Fly.io, or Mac Mini (HMA)? Jacob Open — revisit in S7
Q4 OpenChat: upgrade to linq-adapter as a separate project (OpenChat-yg8 continues), or roll into picortex repo? Jacob Open — see wiki/openchat-adapter.md
Q5 Does picortex eventually federate with noos (knowledge graph backend)? Jacob Open — see wiki/relationship-to-noos.md
Q6 What's the real project name? picortex is provisional. Jacob Open — rename before v0.1.0

9. Success metrics

  • M1. Jacob uses picortex for ≥ 1 productive conversation per day for 2 consecutive weeks.
  • M2. Zero isolation breaches over the first 3 months (measured by: no chat user accessing another chat's home dir).
  • M3. Median cold-start time < 60 s (p95 < 120 s).
  • M4. Median warm-reply time < 10 s (p95 < 30 s).
  • M5. Uptime ≥ 99 % over a 30-day window.

10. Out of scope (reiterated)

Docker, Kubernetes, dashboard, billing, multi-tenant, native apps, group-chat mobile UI, cross-chat MCP, knowledge-graph integration, native iOS push.

[[curator]]
I'm the Curator. I can help you navigate, organize, and curate this wiki. What would you like to do?