Noeis is a four-room knowledge workspace for turning reading into durable understanding and better judgment. Library grounds what you know, Think is the native notebook, Wiki maintains durable synthesis, and Judgment compounds decisions, outcomes, and lessons.
Core product flow:
- Login or register (
/login,/register) with optional Chrome extension setup - Capture material (manual notes, paste, markdown, Readwise CSV, or extension clipping)
- Open on the Wiki return surface (
/wiki), then move through Library (/library), Think (/think), Wiki, and Judgment (/judgment) without losing source context - Ask the persistent thought partner to retrieve owned material, then insert grounded objects directly into the Think notebook
- Configure appearance, onboarding, integrations, and export controls in Settings (
/settings)
Agentic capabilities (optional, human-in-the-loop):
- Thought Partner in-context chat for concept/notebook/question/handoff workflows
- Concept suggestions and synthesis helpers sourced from the user's own library
- Handoff queue shared between user, native agents, and personal BYO agents
- Orchestration policy + external bridge tokens for advanced A2A/MCP-compatible runtimes
Release design and acceptance details: Noeis four-room implementation plan.
Vectors live in the vectoritems collection on the same Atlas cluster as
everything else — no separate vector database to run or provision. Embeddings
come from OpenRouter (OPENROUTER_API_KEY), the same account as the text model.
First-time setup (idempotent, safe to re-run):
node scripts/create_vector_index.js # builds the Atlas index, polls to READY
node scripts/backfill_embeddings.js --user <id> # or --all-usersGET /health reports vectorIndex.status and vectorIndex.itemCount. A READY
index with zero items means the backfill has not run — it is called out
explicitly there because two previous vector stores emptied themselves in
production without anything noticing.
Endpoints:
GET /api/semantic/related?sourceType=highlight&sourceId=...→{ results: [...] }GET /api/highlights/:id/related→{ results: [...] }POST /api/semantic/margins{ paragraphs, articleId?, excludeHighlightIds? }→{ notes: [{ paragraph, relation: 'close', highlight }] }— the closest highlight from another source per paragraph (at most five); empty when nothing is close or no embedding provider is configured
The backend now records critical product events as JSON lines (default) with pseudonymous user IDs. No highlight text, note content, usernames, or raw queries are logged.
Tracked events:
user_signuphighlight_capturedworkspace_createdcapture_completedconcept_createdrevisit_scheduledsemantic_search_performedai_draft_generatedai_draft_acceptedrelated_highlight_clicked
Environment variables:
ANALYTICS_ENABLED(default:true)ANALYTICS_LOG_PATH(default:server/logs/product-events.jsonl)ANALYTICS_HASH_SALT(recommended, used to hash user/object ids)POSTHOG_HOSTandPOSTHOG_PROJECT_API_KEY(optional; if set, events are also mirrored to self-hosted PostHog)POSTHOG_TIMEOUT_MS(default:3000)
The Node API calls OpenRouter directly for both text generation and embeddings.
Without OPENROUTER_API_KEY (or HF_TOKEN for text), AI features stay quiet and
the rest of the app works. Model routes and the remaining knobs are documented in
.env.example.
Agent eval:
npm run agent:evalseeds a small reading library (server/agentEval/library.js), asks the thought partner the questions inserver/agentEval/cases.jsfrom the surfaces readers use, and scores each answer. It checks that the right sources were found, that the answer carries their actual words, that it cites only what it used, that it never invents a quotation or answers with a template, and that it says so when the library has nothing.- With
HF_TOKENorOPENROUTER_API_KEYin.envit measures the model path; without one it measures what readers get when the model is unavailable.modelAnsweredin the summary says which. npm run agent:eval:checkexits non-zero when any rate falls belowserver/agentEval/baseline.json. Raise the baseline with-- --update-baselineafter an improvement lands.-- --case=summary,hiring --verboseruns selected cases and prints the replies. Reports are written totmp/agent-eval/.- The eval starts its own in-memory MongoDB. Set
AGENT_EVAL_MONGODB_URIto use a throwaway server instead; it never readsMONGODB_URI. npm run agent:approval-smoke -- --base-url=https://your-api --token=JWT --action=rejectcreates a real pendingmemory.commitapproval, verifies it is listable, rejects it with an audit note, and writes a JSON report totmp/agent-approval-smoke-runs. Use--action=approveonly when you intentionally want the smoke test to commit working-memory rows.POST /api/agent/memory-approvalsstages working-memory updates asmemory.commitprotocol approvals. Approving the protocol approval executes the commit intoWorkingMemoryItem; rejecting it stores the user decision note for audit review.GET /api/agent/write-boundarysummarizes the safety split between approved memory commits, pending memory approvals, and staged structure proposals. The Thought Partner panel renders this as a write-boundary card plus a separate Memory approvals review queue.
A publish-ready HTML draft for docs.noeis.io lives at:
docs/docs.noeis.io/index.html
It documents the current marketing narrative across:
- how the product works end to end
- Think main screen behavior
- settings and integrations
- login/signup messaging
- agentic capabilities and safety controls
Concepts now persist a document-outline workspace in concept.workspace:
{
"version": 1,
"groups": [
{
"id": "uuid",
"title": "Workspace",
"description": "",
"collapsed": false,
"order": 0
}
],
"items": [
{
"id": "uuid",
"type": "highlight",
"refId": "mongo-object-id",
"groupId": "group-uuid",
"parentId": "",
"order": 0
}
],
"updatedAt": "2026-02-21T00:00:00.000Z"
}Workspace endpoints (JWT required):
GET /api/concepts/:conceptId/workspace-> returns workspace and lazily initializes default group if missingPUT /api/concepts/:conceptId/workspace-> replaces workspace with validated payloadPATCH /api/concepts/:conceptId/workspace-> applies operation payload{ op, payload }POST /api/concepts/:conceptId/agent/build-> runs the library-only concept agent loop. Supportspreview: truefor confirm-before-apply, andapplyPreview: trueto apply the latest stored preview.POST /api/concepts/:conceptId/agent/suggest-> generates AI draft suggestions from your library for the concept (library_only)GET /api/concepts/:conceptId/agent/suggestions-> returns active AI-generated draft suggestions (pending + accepted, excluding discarded)POST /api/concepts/:conceptId/agent/suggestions/:draftId/accept-> accepts selected or all pending AI draft suggestions; accepted items are added to workspace InboxPOST /api/concepts/:conceptId/agent/suggestions/:draftId/discard-> discards selected or all pending AI draft suggestionsGET /api/debug/agent-metrics-> returns in-memory observability counters for scout/build and AI upstream outcomes
AI draft suggestions are explicitly marked as AI-generated, require user acceptance to enter Inbox, and persist until discarded.
Patch operations:
addGroup,updateGroup,deleteGroupaddItem,moveItem,updateItem,deleteItem
The UI shell now uses a shared token-driven layer across the four primary rooms:
Wikiis the post-login return route (/wiki); Library, Think, Wiki, and Judgment remain persistent primary navigation.- Global chrome is composed from reusable primitives:
AppShell(note-taker-ui/src/layout/AppShell.jsx)TopBar(note-taker-ui/src/layout/TopBar.jsx)RightDrawer(note-taker-ui/src/layout/RightDrawer.jsx)SurfaceCard,PillButton,Chip(note-taker-ui/src/components/ui.js)
- Think remains a native document-first notebook. Judgment alone offers a board posture for moving through one case without turning the product into a generic dashboard.
- Right column context/working-memory is user-collapsible and persists per route via localStorage keys.
- Primary UI token overrides:
note-taker-ui/src/styles/dashboard-refresh.css - Existing base theme variables/components:
note-taker-ui/src/styles/theme.css - Cross-room semantic roles and motion rules:
note-taker-ui/src/styles/semantic-theme.css
- Tokens:
- spacing / radii / surface aliases live in
note-taker-ui/src/styles/dashboard-refresh.css(--space-*,--radius-*,--surface-*,--text-*)
- spacing / radii / surface aliases live in
- Shell components:
note-taker-ui/src/layout/AppShell.jsxnote-taker-ui/src/layout/LeftNav.jsxnote-taker-ui/src/layout/TopBar.jsxnote-taker-ui/src/layout/RightDrawer.jsx
- Think page composition:
note-taker-ui/src/pages/ThinkMode.jsxnote-taker-ui/src/components/think/ThinkHome.jsx
- Verify after style tweaks:
- light + dark mode both preserve contrast hierarchy
- right drawer collapses/expands cleanly and main panel reflows
- Think Home “Continue” module remains the primary visual CTA
- no overflow in nav/topbar/right drawer on smaller widths
- Screenshot matrix + capture checklist:
docs/ui-regression/think-home-baseline.md
- Think-specific polish overrides (split out for maintainability):
note-taker-ui/src/styles/think-home-polish.css