One sentence. A team-facing web app that exposes the NOTQIN vault as a filtered knowledge base + AI assistant + lightweight editor that commits back via PR.
Relationship to the existing Quartz portal. The portal is read-only static and excellent for casual browsing on phones. The Workbench is interactive, AI-augmented, and write-capable. Same audience model, same vault, different surface. Both stay live.
Status. Spec v1 locked from 4 design decisions (2026-05-24). Phase 0 scaffolding in progress. Build target: 3 weeks.
0. Goals & non-goals
Goals (v1)
Every teammate can authenticate via Cloudflare Access (no new auth code) and see only the notes their audience tag permits
Every teammate can chat with Codex, with Codex grounded in their filtered view of the vault (RAG over the audience-filtered corpus)
Every teammate can view a knowledge graph of their accessible notes (nodes = notes, edges = wikilinks)
Every teammate can create + edit notes in-app
Every edit auto-commits to a per-session git branch; “Push” creates a PR back to main for Ahmed’s review
Ahmed sees PRs in his usual GitHub flow; merging is the canonical write to the vault
Non-goals (v1)
Real-time multi-user co-editing (no CRDTs / OT — single user per session per branch)
Mobile-optimized editing (browse + AI works on mobile; editing is desktop-class)
Plugin system / extensibility
Public publishing (Quartz portal is for that)
Replacement of Obsidian for Ahmed’s personal workflow (Ahmed keeps Obsidian + git locally)
Uses Ahmed’s existing ChatGPT subscription auth on the droplet — no separate OPENAI_API_KEY to manage
Per codex skill conventions in ~/.claude/skills/codex/
--sandbox read-only enforces filesystem read-only at the OS level — even if the prompt asks Codex to “write to <sensitive-file>”, the sandbox refuses
The invocation pattern
codex exec \ --sandbox read-only \ --cwd /srv/vault-views/<audience>/ \ --skip-git-repo-check \ --output-format=plain \ "<user prompt with system framing>"
System framing prefix (prepended to every prompt)
You are NOTQIN's AI assistant, helping {user_name} (audience: {audiences})
work with their accessible vault. The current working directory contains
ONLY the notes this user can see. Cite source files when you make claims.
Today's date is YYYY-MM-DD.
User question:
{user_prompt}
Conversation memory
Each conversation gets a uuid. Backend persists conversations/<uuid>.jsonl with appended turns. Codex doesn’t have built-in session memory across exec calls — we re-inject the last N turns as context on each invocation.
Streaming
codex exec writes to stdout. FastAPI StreamingResponse pipes it to the browser as SSE.
For a 5-person team with moderate use, no incremental cost
If usage spikes: switch to OpenAI API with rate-limiting per user
5. Per-audience filtered views (storage)
The vault is git cloned once at /srv/vault/. Filtered views live alongside:
/srv/
├── vault/ ← live git repo
│ ├── main (default checkout)
│ └── .git/
├── vault-views/ ← rebuilt on every git pull
│ ├── academic/
│ ├── ai/
│ ├── ot/
│ └── gtm/
└── notqin-workbench/ ← the FastAPI app
├── config/users.yaml
├── app/
└── conversations/
Rebuild trigger
FastAPI startup → pulls latest, rebuilds all 4 views
After any PR merges to main (GitHub webhook → POST /api/internal/refresh → pull + rebuild)
On a 15-min cron as fallback
Rebuild mechanism
Extract the pure filtering core from portal/scripts/sync-content.mjs into a reusable function. Call it from a small Python wrapper (vault_views.rebuild()) that invokes node with the per-audience target.
6. Branch & PR flow
Session start
User clicks “Start writing” → POST /api/session/start
Backend: git checkout main && git pull origin main && git checkout -b elwalid-2026-05-26-1430
Branch name pattern: <user-slug>-<YYYY-MM-DD>-<HHMM> — unique per session, human-readable
All subsequent writes commit to this branch
Per-edit commits
Every PUT /api/notes and POST /api/notes auto-commits with message "<user>: <commit_message or default>"
Author = the user’s GitHub handle from users.yaml, falling back to Ahmed’s account if not set
Commits are squash-friendly (PR review can squash on merge)
Push & PR
User clicks “Push for review” → POST /api/session/push
Backend: git push origin <branch> then gh pr create --base main --head <branch> --title "<pr_title or auto>" --body "<pr_body or auto>"
Default PR body: lists all commits + diff summary + the user’s name
Returns pr_url → frontend shows “PR created → “
Ahmed’s review side
Standard GitHub PR review (using existing flow)
Merge → CF Pages portal auto-rebuilds (existing pipeline) AND the workbench gets a webhook → pulls fresh
Workbench’s session-branch handling is at-most-once: after PR merge, the user’s session ends; new session starts fresh from updated main
Conflict handling
Push fails on conflict (branch is stale vs current main)
Frontend shows: “Main has new commits since you started. Discard session and start fresh? [Discard + restart] [Cancel]”
v1 does NOT attempt automatic rebase / merge resolution — too risky for non-engineers
7. Editor UX (CodeMirror 6)
Markdown source mode (no WYSIWYG in v1 — keeps git diffs clean)
Frontmatter shown in a structured panel at the top (audience picker, tags, type, owner)
Body in CodeMirror with: syntax highlighting · wikilink autocomplete (from accessible note paths) · live preview pane (toggleable)
Save button or auto-save every 5s while focused → debounced PUT
Visual indicator: ”● Unsaved” / ”✓ Saved to “
8. Knowledge graph UX (Cytoscape.js)
Default view: all accessible notes as nodes, wikilinks as edges
Node color by type: (concept-reference, strategy, person, etc.)
Node size by in-degree (most-linked-to = biggest)
Click node → open in editor
Filter sidebar: by type:, by folder, by tag
Search box → highlight matching node + neighbors
9. AI drawer UX
Right-side slide-in drawer (toggleable with Ctrl/Cmd+K or button)
Conversation list at top
New message → streamed Codex response with file citations
Click any citation → opens that note in the main pane
“Insert as new note” button — turns the AI response into a draft note in the editor (with default [ai, ot, gtm] audience)
“Quote in current note” — inserts citation block at cursor in active editor
10. Infrastructure & deploy
The droplet
Existing DO box with Codex installed (per user)
Ubuntu 22.04 / 24.04
Add: Python 3.12, nginx, node 20+, gh CLI authenticated to Ahmed’s GitHub, gpg for signed commits (optional)
Document re-auth flow; alert on /api/ai/chat failures; fallback to OPENAI_API_KEY if catastrophic
Audience filter bug leaks a founder-only doc
Triple defense (tree + read + AI cwd); test suite covering each layer; security audit before opening to team
Concurrent writes corrupt the vault
Single user per branch = no concurrent writes within a branch. Cross-branch conflicts surface at push, handled.
Droplet goes down
Standard DO uptime; consider snapshot backups weekly
gh pr create rate limits or token expires
Standard GitHub limits are generous; monitor; rotate token annually
Editor session abandoned mid-write
Auto-save covers most cases; orphan branches cleaned up by weekly cron (git branch -D <name> if older than 14 days and not pushed)
LLM hallucinates an audience: value or path
System prompt explicitly forbids; even if it does, the next save’s audience filter will reject
Open questions for Phase 0
GitHub author of commits — Ahmed’s account (simpler, all PRs look like Ahmed’s) or per-user via git commit --author? Per-user is honest, requires real email per teammate.
Should the workbench show a “live audience preview” — toggle between “view as me” and “view as “? (Founder-only feature, useful for sanity-checking.)
WebSocket vs SSE for chat streaming. SSE simpler, works fine for one-direction streaming. Lean SSE for v1.
How to handle binary attachments (images embedded in notes) in v1? Skip → v2 problem.
Should PRs auto-tag Ahmed for review? Yes — gh pr create --reviewer Ahmed-BenAhmed.
13. Success criteria (how we know v1 works)
All 4 teammates log in and see only their audience-filtered notes ✅
Each teammate has a successful Codex conversation grounded in their corpus ✅
Each teammate creates one new note + edits one existing note + opens a PR ✅
Ahmed reviews + merges at least 4 PRs (one per teammate) ✅
Knowledge graph renders for each audience without performance issues (<100 ms for <500 nodes) ✅
Zero audience-leak incidents reported in first 2 weeks of use ✅