Anthropic (Claude)
Native integration via @anthropic-ai/sdk. The only preset that accepts PDF attachments; fixed endpoint.
Local embeddings, a semantic graph, hybrid retrieval and a native agent — every prompt and threshold documented, straight from the source.
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.
Native integration via @anthropic-ai/sdk. The only preset that accepts PDF attachments; fixed endpoint.
The GPT family over the standard chat-completions API.
deepseek-chat / deepseek-reasoner. Text-only — the API rejects image input.
MiniMax-Text-01. Edit the base URL to use the China endpoint. Text-only.
Kimi K2 and the moonshot-v1 models. Text-only.
One key, hundreds of models — pick any model id in the selector.
The OpenCode gateway, fully OpenAI-compatible.
100% local inference — no key, no account, nothing ever leaves your machine.
Any OpenAI-compatible server: LM Studio, vLLM, llama.cpp, a corporate gateway…
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.
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.
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.
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.
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.
When you ask the chat something, your vault is not pasted into the prompt. A retrieval pipeline picks the few sections that matter:
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.
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.
The matched sections are re-read from the markdown on disk (never from the index), capped at 1,500 characters per block.
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.
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. 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.
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. 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:
| Tool | What 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. |
Every AI surface — index, retrieval, tools — respects the same boundaries, enforced in the main process, not in the UI:
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.
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.
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.
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.