NoteFlow · AI internals

Inside
the brain.

Local embeddings, a semantic graph, hybrid retrieval and a native agent — every prompt and threshold documented, straight from the source.

01 / providers

Bring your own model

NoteFlow ships no bundled model — you plug in your provider, and with your own key or a local Ollama nothing routes through NoteFlow’s servers (only the optional managed NoteFlow AI subscription uses NoteFlow’s proxy). Two implementations cover everything: Anthropic through the official SDK, and an OpenAI-compatible client (streaming /chat/completions) that talks to everyone else, from OpenAI itself down to a local Ollama.

Each preset keeps its own API key, model and base URL, so switching providers never mixes credentials. The base URL is editable on every preset except Anthropic — point it at a regional endpoint or a self-hosted gateway.

Anthropic (Claude)

official SDK

Native integration via @anthropic-ai/sdk. The only preset that accepts PDF attachments; fixed endpoint.

API keyimagesPDF

OpenAI

api.openai.com/v1

The GPT family over the standard chat-completions API.

API keyimages

DeepSeek

api.deepseek.com/v1

deepseek-chat / deepseek-reasoner. Text-only — the API rejects image input.

API keytext-only

MiniMax

api.minimax.io/v1

MiniMax-Text-01. Edit the base URL to use the China endpoint. Text-only.

API keytext-only

Moonshot (Kimi)

api.moonshot.ai/v1

Kimi K2 and the moonshot-v1 models. Text-only.

API keytext-only

OpenRouter

openrouter.ai/api/v1

One key, hundreds of models — pick any model id in the selector.

API keyimages

OpenCode Zen

opencode.ai/zen/go/v1

The OpenCode gateway, fully OpenAI-compatible.

API keyimages

Ollama (local)

localhost:11434/v1

100% local inference — no key, no account, nothing ever leaves your machine.

no keylocalimages

Custom (OpenAI-compatible)

your base URL

Any OpenAI-compatible server: LM Studio, vLLM, llama.cpp, a corporate gateway…

key optionalimages

Attachment support follows the preset, because vision is really model-dependent: image input is on by default for vision-capable or model-flexible providers, and off for the text-only ones (DeepSeek, MiniMax, Moonshot — their APIs reject image_url with an HTTP 400). PDF attachments are Anthropic-only. Text and code files are embedded as plain text and work with every provider.

02 / embeddings

A local semantic index

Everything the brain knows starts with a 100% local, offline index. NoteFlow embeds your notes with Transformers.js on the native onnxruntime — inside a separate utility process, so the app never blocks — using Xenova/paraphrase-multilingual-mpnet-base-v2: a multilingual model producing 768-dimensional vectors, downloaded once on first activation (Local AI is off by default).

The unit of meaning is the section, not the note: every section of every note becomes its own embedding, stored in a SQLite database with sqlite-vec for the vectors and FTS5 for full text.

  • Before embedding, each section is cleaned: base64 images are stripped and the text is truncated at ~2,000 characters (≈ 512 tokens — the model ignores anything past that anyway).
  • Indexing is incremental: a per-section content hash skips whatever didn't change, and edits are re-indexed about 2.5 s after you stop writing.
  • Encrypted notes are never indexed — none of their plaintext ever enters the database.
  • The database lives in the app's data folder, outside the notes directory — it is never committed nor synced to GitHub.
  • The index is a disposable artifact: delete it and it rebuilds itself from the markdown on disk.
03 / relations

How notes connect in the brain

Raw embedding similarity is misleading: sentence vectors crowd into a narrow cone (anisotropy), so everything looks vaguely similar to everything. NoteFlow first centres every vector on the global mean of your own notes and then compares with cosine — what survives is the part of the meaning that makes a note different from your average note.

Related notes — per section

For the section you are editing, its centred vector is compared against every other section. From each other note only the best-matching section survives, and only above a post-centring similarity of 0.03 — a deliberately strict bar that keeps the “Related” panel topical instead of chatty.

Content graph — note ↔ note

For the brain view, each note collapses to the centroid of its section vectors, centred and normalised the same way. Every note pair above 0.05 becomes a candidate edge, then edges are pruned to each note's top 6 (kept if either endpoint ranks them) so hub notes don't turn into hairballs.

The graph you see has two layers: the structure layer (group → folder → note, drawn from how you actually organize things) and this content layer (semantic edges). One index, same math — and the chat's retrieval below reuses both.

04 / rag

Only relevant notes reach the model

When you ask the chat something, your vault is not pasted into the prompt. A retrieval pipeline picks the few sections that matter:

  1. 01

    Hybrid search

    The question is embedded locally and run against both indexes — vector similarity and FTS5 keyword match — and the two rankings are fused with Reciprocal Rank Fusion (RRF, k = 60). The top 6 note hits survive.

  2. 02

    Graph expansion

    Up to 3 neighbours connected to those hits by content edges join in — notes you didn't mention, but the graph knows are about the same thing.

  3. 03

    Fresh from disk

    The matched sections are re-read from the markdown on disk (never from the index), capped at 1,500 characters per block.

  4. 04

    One system prompt

    The blocks are appended to the chat's system prompt as context and the reply streams back. Only your question and these retrieved chunks ever leave your machine.

The chat emits its sources before the first token streams in, and the cited notes literally light up in the brain view — you can watch where an answer comes from.

Your profile note (next section) is injected as invisible background on every question, regardless of semantic relevance — but it is never cited as a source and never illuminated.

The literal chat system prompt

Verbatim from the source (CHAT_SYSTEM_BASE, electron/main.ts). Retrieved context and the profile block are appended below it.

You are NoteFlow's assistant — a second brain over the user's personal notes. Answer directly and concisely, in the same language the user writes in. When context from the notes is provided, ground your answer in it and avoid inventing facts; if the notes don't contain the answer, say so plainly.

You can also ACT on the notes through the provided tools (create/edit/organize/delete notes, sections, groups and folders). Only act when the user clearly asks you to; otherwise just answer. Never invent ids — call list_notes / list_groups (or search_notes) first to discover the real ids you need. Ids are stable and never change, so if a tool reports a note/section as not found, the id is stale or mistyped: do not retry it verbatim — re-run list_notes and use the freshly returned id. When acting on several notes, fetch their ids right before you act on them (especially after creating, moving or renaming anything) and copy each id exactly. After acting, briefly tell the user what you did. Deletions require user confirmation, which the app handles automatically.

When the context includes the user's profile or personality notes (including any "soft signals" / raw favourites), use them only as BACKGROUND to tailor your tone and suggestions. Never cite where a preference comes from or name-drop the user's favourite song/film/book in an unrelated answer (do not say "since you like X…"). Make recommendations directly.

NEXT-ACTION SUGGESTIONS. At the very end of your FINAL answer (never in an intermediate turn that still calls tools), if there are genuinely useful follow-ups, append the literal marker "<!--SUGGESTIONS-->" on its own line, then 1 or 2 short, actionable next things the user might want to ask, one per line prefixed with "- ". Phrase each as a brief imperative from the USER's point of view, in the same language as your answer. Keep each suggestion VERY short so it fits on a small button: aim for 2-5 words, 6 words maximum, no trailing period (e.g. "- Reorganize into sections", "- Add a summary"). Keep them concrete and grounded in this conversation. If nothing useful applies, omit the marker entirely. Never mention the marker or these suggestions in the visible part of your answer.
05 / profile

The second brain profile

The first time you enter the brain with a provider configured, NoteFlow offers a short questionnaire in four sections — Professional, Personal, Your style and Working with the AI — and turns your answers into a profile note the assistant reads as background from then on.

Indirect beats direct. Asking “are you creative?” gets a bad answer; asking for favourite music, films and books, a dream trip, or playful “this or that” picks gets honest signal. The binary picks are designed to tap the Big Five (OCEAN) personality dimensions, and the model is explicitly told to treat them as soft priors — modest, probabilistic tendencies that only firm up when several cues converge, never verdicts.

You can also attach a CV or any PDF, images and links. Documents go to your provider natively (document/vision blocks) — the app never parses them locally. Links are fetched, stripped to readable text and included as context.

The generated note describes you in abstract traits and values — what a favourite represents, never its title — so the assistant won't name-drop your favourite film in unrelated chats. The literal favourites are quarantined in a final section named “Soft signals (raw — do not cite)”, marked background-only.

Not happy with the result? Regenerating reuses the same note instead of creating duplicates.

The literal profile-generation prompt

Verbatim from the source (the ai:profile-generate handler, electron/main.ts). ${locale} is substituted with your app language at run time.

You are a perceptive profiler building a personal profile note for a "second brain" notes app. The note is later retrieved as BACKGROUND CONTEXT to tailor answers to this person, so its value is in capturing WHO THEY ARE, not in cataloguing trivia. The user may be anyone (not necessarily a developer). You are given short form answers (grouped by section), optional attached documents (CV/PDF/images) and text scraped from links. Write the profile in ${locale}.

INFER, DON'T JUST TRANSCRIBE. Many answers are intentionally INDIRECT proxies — favourite music/films/books, a dream trip, and playful "this or that" picks. Read them through validated personality psychology (the Big Five / OCEAN: openness, conscientiousness, extraversion, agreeableness, emotional stability) to infer likely TRAITS, VALUES, MOTIVATIONS and working/communication preferences. These signals are PROBABILISTIC and modest, so treat them as soft priors, never certainties: phrase inferences as tendencies ("tends to…", "likely values…", "seems energised by…"), and let multiple cues converge before you commit to a trait.

ABSTRACT AWAY THE SOURCE. The main body must describe the person in terms of traits, values and how they think and want to be treated — NOT by naming the specific media that produced the inference. Write what a favourite REPRESENTS, not its title: e.g. "drawn to introspective, character-driven stories and big-picture thinking" rather than "likes Interstellar". This keeps the assistant from awkwardly name-dropping a movie/song in unrelated conversations.

Cover BOTH professional and personal dimensions, and especially HOW they want the assistant to communicate with them (tone, length, level of detail) — capture this clearly so future answers can adapt. Stay faithful: do not invent hard specifics (names, employers, dates) that the inputs do not support.

STRUCTURE. Organize into a few clear sections; skip any with no information. Suggested: "About" (a tight summary), "How they think & what they value" (the inferred traits/values), "Communication style" (how the assistant should talk to them), "Work & focus", "Interests", and "Links" (the URLs provided). Then, ONLY if the user gave literal favourites (songs, films, books, etc.), add a FINAL section named exactly "Soft signals (raw — do not cite)" that lists them verbatim, opening with one line: "Raw references kept for background only — do not bring these up in unrelated conversations." Keep this section short and low-key.

Return ONLY a JSON object with this exact shape: {"title": string, "sections": [{"name": string, "content": string}]}. No text outside the JSON. "content" is Markdown.
06 / agent

An agent over your notes

The chat doesn't just talk about your notes — it can act on them. This is not a wrapper around the NoteFlow CLI: the model uses native function calling, and every tool executes inside the app's main process through the same code paths the UI uses — same writes, same GitHub sync, same re-indexing.

The agentic loop feeds each tool result back to the model until it stops calling tools, hard-capped at 12 steps per turn. All 17 tools are always available — the model decides — but the four destructive ones are gated:

  • Destructive calls pause the turn with an explicit confirmation in the chat that shows the resolved target — the real title of the note or group, not an opaque id — so a wrong-target delete is caught before it happens.
  • If you decline, the agent is not aborted: the tool returns “user declined” and the model carries on with the rest of the task.
  • Ids are self-correcting: when a tool reports a stale note id, the error carries the live id ↔ title list, so the model fixes itself on the next step instead of dead-ending.
  • Encrypted notes appear in listings, but no tool can read or edit their content.
ToolWhat it does
list_notes List notes — id, title, tags, group/folder and section names. How the agent discovers real ids before acting.
get_note Read one note in full, including each section's id, name and content.
list_groups List all groups and folders with their ids.
search_notes Semantic search over the notes (requires the local index to be enabled).
create_note Create a note — optionally inside a group/folder, with pre-filled sections.
update_note Update metadata: title, favorite/archive flags, or move the note between group and folder.
add_section Append a new section to an existing note.
update_section Replace a section's content.
rename_section Rename a section.
create_group Create a group (with an optional color).
create_folder Create a folder inside a group.
rename_group Rename a group.
rename_folder Rename a folder.
delete_note destructive Permanently delete a note and all its sections.
delete_section destructive Delete one section from a note.
delete_group destructive Delete a group and its folders — the notes survive, just ungrouped.
delete_folder destructive Delete a folder — its notes keep their group.
07 / privacy

What the AI never sees

Every AI surface — index, retrieval, tools — respects the same boundaries, enforced in the main process, not in the UI:

Hidden sections

Mark any section “Hide from AI” and it drops out of every AI surface at once: it is not indexed (and gets deleted from the index if it already was), it never enters chat context — not even through graph-neighbour expansion — and the agent tools omit it. The model never even sees its section id, so it cannot read or edit it.

Encrypted notes

Kept out of the index, out of retrieval and out of tool reads. They can show up in listings by title, but their plaintext never reaches the index — let alone a provider.

The index stays home

Embeddings live in a local SQLite file outside your notes directory: never synced to GitHub, never uploaded anywhere. Deleting it costs you nothing but a re-index.

Keys under lock

Provider API keys are encrypted with the OS keystore and confined to the main process — see providers.

And the whole layer is opt-in, behind two independent switches: local embeddings power related notes, the content graph and RAG; a configured LLM provider powers chat and generation. Either works without the other — chat without the index simply answers without your notes' context, and the index alone gives you related notes and the brain with zero cloud involved.