On Monday you write a note about negative feedback in Control Systems. On Thursday it's TCP congestion control in Computer Networks. On Saturday you save a news snippet about the RBI raising the repo rate to curb inflation.
Three subjects and three notebooks, but it's the same idea every time: a feedback loop. You would probably never notice, because the notes share almost no words.
That gap is what our team, WInDaChat, built Connectore to close at Hacktoberfest Hack Day Coimbatore 2026 (INIT CLUB × iDEA CLUB × MLH).
🧠 Connectore is a local-first second brain. Gemma 4 links your notes across subjects, tells you the story behind them, and then checks its own story against your notes for facts it dropped. Everything runs on a laptop through Ollama.
The two problems we wanted to solve
1. Notes live in silos. Tools like Obsidian Smart Connections, Mem and Reflect show "related notes" using embedding similarity. That catches notes that use similar words. Our three notes above don't, but they describe the same mechanism.
2. AI summaries quietly drop things. A 2025 study in Royal Society Open Science found LLM summaries were about 5× more likely than human ones to overgeneralise. An EBU/BBC study found significant issues in 45% of AI-assistant answers about news. A fluent summary isn't necessarily a faithful one. If an app writes you a story from your notes, how do you know it didn't drop the "unless" that mattered?
What Connectore does
1. Connect: links with reasons, not similarity scores
You write a note in one box. There are no folders and no manual tagging. Behind the scenes, each note costs two Gemma calls:
-
Tag. Gemma 4 returns JSON with a summary, entities (
RBI,TCP), and abstract patterns likefeedback loop,trade-offoroscillation. The patterns are the trick that lets a biology note match a networking note. - Shortlist. A plain SQL query finds up to 8 older notes sharing an entity or pattern. No AI is involved, so it's fast and free.
-
Judge. One batched Gemma call scores every candidate. For each one it returns:
- a relation (
same_idea,causes,consequence_of,continuation,contradicts) - a strength from 1 to 5
- a reason that has to cite a fact from both notes
- a relation (
- Filter. We keep a link only if it has strength ≥ 4 and points to a note that was really in the shortlist, so the model can't invent connections.
The result is an Obsidian-style force graph. Links are coloured by relation type, and hovering one shows Gemma's reason. Click a note and its whole thread lights up. Press Tell the story and Gemma streams a short narrative tying the thread together.
You can also drag one note onto another. Gemma judges just that pair, then either draws the link or tells you what separates the two notes.
2. Verify: the AI audits its own story
This is the part we're proudest of. Under every story there's a faithfulness report built from two layers:
-
A deterministic cue diff (no AI). Regexes pull out numbers with units and currency (
₹2,14-day), dates, negations (not,never), conditions (unless,only if), bounds (at most,within) and obligations (must,may not). Anything in the source notes that's missing or changed in the story gets flagged. - A fact quiz, in the style of QAGS. Gemma pulls up to 8 must-keep facts from the notes, each with an exact quote. Then, in a separate call, it answers a question about each fact using only the story. If the answer is "not stated", the story probably dropped that fact.
Each fact gets one of three statuses: Appears preserved, Needs review or Possible mismatch. The source quote is shown right next to the status. We deliberately never show a "verified ✅" badge. The checker can miss things and raise false alarms, and the UI says so.
We also drop any "fact" whose quote doesn't actually appear in the notes. The verifier is never allowed to cite a source the model made up.
Real numbers (measured on our laptop, not estimated)
Setup: Windows 11, Node 24, Ollama 0.40.1, gemma4:e4b.
- About 48–50 tokens/s once the model is warm.
- Tagging takes 1.2–1.7 s per note. A full add (tag + link) takes 3–6 s.
- Seeding 10 demo notes produced a clean graph:
- control systems, blood sugar regulation, TCP, the RBI repo rate and supply/demand all linked as
same_idea - deadlock linked to two-phase locking
- gradient descent linked to control systems (oscillation!)
- our photosynthesis control note correctly got zero links
- control systems, blood sugar regulation, TCP, the RBI repo rate and supply/demand all linked as
- The story streams its first words in about 0.5 s. A 6-note story finishes in under 10 s.
-
The verifier on a deliberately sabotaged story (a library rule with "at most twice", "unless someone else has reserved the book" and "₹2 per day after the 14-day loan period", rewritten to drop them):
- It flagged all four cues.
- It marked 3 facts as possible mismatches and 1 as needs review, in about 7.7 s.
- The honest 6-note story scored all 7 facts as preserved, with 0 cue issues.
Stack
| Layer | Tech |
|---|---|
| Model |
Gemma 4 (gemma4:e4b) via Ollama, fully local |
| Backend | Node.js + Express, the official ollama JS client |
| Validation |
Zod: the schema goes to Ollama as format, and the reply is validated against it |
| DB | SQLite via Node's built-in node:sqlite
|
| Frontend | React + Vite + Tailwind, react-force-graph-2d
|
Things that bit us (and what we learned)
1. Pass num_ctx on every single call. Ollama's default context window depends on your VRAM and can be as small as 4K on a laptop. Long prompts get silently truncated, and you get no error, just worse answers.
2. Structured output needs a seatbelt.
- We send a JSON schema via
format, usethink: falseand temperature 0, validate every reply with Zod, and retry once. - The very first call while the model was loading came back empty. That's why the retry exists, and why the server now warms the model up on startup (the first call took about 32 s before that).
export async function callJson(schema, messages) {
const format = z.toJSONSchema(schema);
for (let attempt = 1; attempt <= 2; attempt++) {
try {
const res = await ollama.chat({ ...CALL_DEFAULTS, messages, format, stream: false });
return schema.parse(JSON.parse(res.message.content));
} catch (err) { /* log and retry once */ }
}
throw new LlmError('Gemma call failed');
}
3. Models love vague answers. Gemma first tagged unrelated notes with the pattern "cause and effect", which linked everything to everything. Two changes fixed it:
- a short list of preferred patterns
- an explicit "never use vague patterns like cause and effect" rule
4. Ask for the difference, not just the reason. When drag-to-connect rejected a pair, Gemma's reason field often argued for the link ("both describe feedback…"), which made the rejection message confusing. We added a separate difference field and show that instead.
5. Batch your LLM calls. Judging 8 candidates one at a time would mean 9+ calls per note, which is a minute or more on a laptop. One batched call keeps adding a note at a few seconds.
6. Test a fresh clone before the deadline. better-sqlite3 tried to compile from source on a clean Windows install and failed without the C++ build tools. Switching to Node's built-in node:sqlite removed the native dependency entirely.
7. Cut scope honestly. We originally planned a "KV Cache Lab" to measure how Gemma's facts survive when its KV cache is quantized to q8/q4. Research showed Gemma 4 is unusually sensitive to that, which makes it a fascinating question. But we couldn't run it properly in the time, so we cut it rather than ship made-up numbers. It's first on our "what's next" list.
What's next
- The KV Cache Lab: run the same story + fact quiz with Gemma's KV cache at f16 / q8_0 / q4_0, and measure memory against how many facts survive
- Import notes from a URL or PDF
- Weekly digests of new threads
- Quizzes generated from your own threads
- A small Gemma 4 running offline on Android
Try it
ollama pull gemma4:e4b
git clone https://github.com/chuckstone-cpu/hacktoberfest-hack-day-coimbatore-x-init-club-and-idea-club--Team_WInDaChat.git
cd hacktoberfest-hack-day-coimbatore-x-init-club-and-idea-club--Team_WInDaChat
npm install && cp .env.example .env
npm run seed # loads 10 demo notes through the real pipeline
npm run dev # UI at http://localhost:5173
It's MIT licensed, and PRs are welcome. It is Hacktoberfest, after all 🎃
Built by Team WInDaChat: V Aravindhan, Siva Krithick, Sahesh Karthikeyan and Sathyanarayanan.
If you've ever had two notes from completely different classes turn out to be the same idea, tell us about it in the comments 👇
Top comments (0)