{"slug": "wanderlog-an-offline-field-journal-that-answers-where-did-i-see-that-kingfisher", "title": "WanderLog: an offline field journal that answers \"where did I see that kingfisher last time?\"", "summary": "A developer built WanderLog, a local-first field journal implemented as a Model Context Protocol (MCP) server that turns 30-second voice memos into structured wildlife sightings and walks stored in SQLite. The system runs entirely offline using open-weight embeddings (all-MiniLM-L6-v2) via ONNX Runtime and Transformers.js, with a local gemma3:4b model through Ollama answering natural-language queries about past entries. In a field test, the journal correctly retrieved and cited a prior kingfisher sighting at Maota Lake, Amber, from a semantic rather than keyword search.", "body_md": "I have a habit of seeing things outside and then losing them to my own memory. The peacock that called from the fog. The kingfisher that dove twice off the jetty and came up with a fish. The lizard big enough to hiss like a bicycle pump.\n\nSo I built **WanderLog** — a local-first field journal that turns my coding agent into a naturalist's notebook. It's a Model Context Protocol (MCP) server. I go outside; when I get back I record a thirty-second voice memo of what I saw, and one command turns it into structured sightings and a walk. Everything else — remembering, finding, connecting — happens on my own machine, with open-source AI, with no network.\n\n**Stack:** TypeScript · MCP (Model Context Protocol) · SQLite · ONNX Runtime · [Transformers.js](https://huggingface.co/docs/transformers.js) · open-weight embeddings (`all-MiniLM-L6-v2`) · local `gemma3:4b` via Ollama · ElevenLabs Scribe (optional) · Sentry `gen_ai` tracing\n\n**Code:** [github.com/praneshnikhar/wanderlog](https://github.com/praneshnikhar/wanderlog)\n\nWanderLog is a field journal for the person who notices things outside and forgets them: the birder without a checklist, the hiker who wants to remember which trail had fog on the lake, the person who sees a peacock on the commute and wants to know whether it's the same one from last month.\n\nIt's built around one rule: **the screen should be the shortest part of the experience.** There's no dashboard to maintain, no logbook to transcribe, no \"curated profile of your nature observations.\" There's a walk, a thirty-second voice memo, and a question you can answer a month later.\n\nOn the trail, you just talk to your phone like you're telling a friend what you saw. At home, one command:\n\n``` bash\n$ npm run log-voice -- ~/walk.m4a\ntranscribing walk.m4a with ElevenLabs...\ntranscript (scribe_v2, eng):\n  \"Saw a white-breasted kingfisher at the lake jetty. It dove twice and got a fish.\n   Also spotted a rain lily on the path side, fresh after last night's rain.\n   Walked the Central Park loop, three point two kilometers in fifty minutes. Fog on the lake.\"\nlogged walk #1\na walk on Central Park loop, 3.2 km, 50 minutes, fog on the lake. Walked on 2026-10-08.\nlogged sighting #1\nbird white-breasted kingfisher, at lake jetty, dove twice and got a fish. Spotted on 2026-10-08.\nlogged sighting #2\nplant rain lily, at path side, fresh after last night's rain. Spotted on 2026-10-08.\n```\n\nNo typing, no forms. The memo became a walk, two sightings linked to that walk, and three searchable journal entries — and the spoken \"three point two kilometers\" became a real number.\n\nThen the journal answers questions about itself, from the same local model:\n\n``` bash\n$ WANDERLOG_OLLAMA_MODEL=gemma3:4b npm run demo\n\n  \"where did I see the kingfisher last time?\"\n\n  1. [2026-10-03] sighting #10 — white-breasted kingfisher at Maota Lake, Amber\n     score=0.465 semantic=0.500 keyword=0.186\n\n  Answer from local gemma3:4b:\n\n  According to entry [1] (2026-10-03), the white-breasted kingfisher was last\n  sighted at Maota Lake, Amber. The note specifically states \"bird\n  white-breasted kingfisher, at Maota Lake, Amber\".\n```\n\nIt doesn't search for the word \"kingfisher\" — it searches for what the entry *means*. \"Where did I see it last time\" and \"bird with the blue flash near the lake at golden hour\" both land on the same entries, because the journal is understood by the same open-weight model that understands you. And the answer above was written by a 4B model on the same laptop: it cited the entry number, parsed the date, and got the place right.\n\nThe full loop on a real walk — memo recorded on the trail, logged at home, question answered — was field-tested on Friday, October 9, and the video is below:\n\nEverything in the terminal sessions above is real output from the actual project, not mock-ups: the voice run is a real transcription of a real audio file, and the search output comes from the seeded journal.\n\nA **local-first field journal** for people who spend time outside.\n\nWanderLog is a Model Context Protocol (MCP) server that turns your coding\nagent into a naturalist's notebook. Log the birds, plants, and trails you see\non a walk; later ask in plain language *\"where did I see the kingfisher last\ntime?\"* — and get an answer computed **entirely on your machine**.\n\n`Xenova/all-MiniLM-L6-v2`, Apache-2.0) executing\nin-process via `gemma3:4b`) through\nMIT licensed, one `npm test` away from reproducible: a hermetic smoke test drives every tool over real MCP stdio, plus unit tests for the voice pipeline that stub both the ElevenLabs and Ollama calls.\n\n```\nflowchart LR\n    subgraph trail [On the trail]\n        A[Voice memo<br/>no typing]\n    end\n    subgraph home [At home, on-device]\n        B[Transcribe<br/>ElevenLabs Scribe · optional]\n        C[Parse<br/>gemma3:4b via Ollama]\n        D[(SQLite journal<br/>sightings · walks · vectors)]\n        E[Semantic search + answers<br/>MiniLM embeddings · gemma3:4b]\n    end\n    A --> B --> C --> D --> E\n    A -.->|--transcript: no network at all| C\n```\n\n`~/.wanderlog/journal.db`): sightings, walks, and a small `embeddings` table that stores each entry's vector as a blob. At field-journal scale (hundreds to low thousands of entries) a vector database would be ceremony, not engineering — cosine similarity over the table runs in milliseconds.`@huggingface/transformers` loads an open-weight ONNX sentence-transformer (`all-MiniLM-L6-v2`, Apache-2.0) and runs it `other` rather than dropped. Pass `--transcript` and the entire pipeline runs offline; no bytes leave the machine. One honest wrinkle: Scribe runs zero-retention by default (`enable_logging=false`), which is an enterprise feature on ElevenLabs' side — on smaller plans the CLI says so up front and offers `WANDERLOG_STT_LOGGING=1` (explicit retention) or the fully local path instead of silently downgrading your privacy.`answer_question` retrieves the top evidence locally, then asks `llama3.2:3b` and Google's open-weight `gen_ai` semantic attributes: tool calls are `gen_ai.execute_tool` spans, transcription is `gen_ai.transcription`, parsing and Q&A are `gen_ai.chat` with token counts, and search and embeddings are spans too. Set `SENTRY_DSN` and you can watch the agent think.\nMeasured on my MacBook (no GPU fan spin-up, no cloud): cold model load **244 ms**, one embedding **3 ms**, full semantic search over a 16-entry journal **9 ms**. The only thing slower than that is me, on the trail.\n\nThe closed version of this product would be an app with a cloud account. And the cloud version of a *field journal* is exactly wrong, for four reasons:\n\n`cat`, back up, or delete with my own hands.` WANDERLOG_EMBED_MODEL` and `WANDERLOG_OLLAMA_MODEL` are environment variables. Don't like MiniLM? Point it at another open-weight embedder and the journal starts understanding you differently — no vendor change, no data migration, no \"new API version.\" That's what open weights buy you: The one cloud call in the whole system — transcription — is opt-in in both directions: you can use it, skip it (`--transcript`), or swap it, and everything downstream of the transcript is open weights on your disk. The open approach won precisely where it matters: **everywhere the network ends.** The trail is the last place on earth that has birds but no bandwidth, and WanderLog was designed for exactly that boundary — the model is already on the disk, the data is already on the disk, and the two of them never need to meet a server.\n\n| Tool | What it does | \n|---|---|\n| `log_sighting` | Record a species, category, place, weather, field notes | \n| `log_walk` | Record an outing: trail, distance, duration, notes | \n| `log_voice_note` | Voice memo (or raw transcript) → transcription + structured entries | \n| `search_journal` | Hybrid semantic + keyword search, computed on-device | \n| `answer_question` | Local-Gemma answer with citations; evidence fallback | \n| `journal_overview` | Totals, category counts, last outing | \n\nPlus MCP resources (`journal://stats`, `journal://day/{date}`, `journal://category/{category}`, `journal://walk/{id}`), so any client can browse the notebook like files.\n\nThe build — including verifying the voice pipeline with a real memo, and discovering the zero-retention tier limit the hard way — is saved on DEV as an agent session:\n\nWanderLog genuinely uses three of this week's partner technologies, so I'm entering all three:\n\n`gen_ai.*` traces to Sentry when I'm taking WanderLog to Central Park, Jaipur this week for a real field test — logging actual sightings from a voice memo, and asking the journal questions at the end. This post will be updated with the field report and the video.\n\nBuilt in three days, entirely inside the Hacktoberfest Week 1 window, MIT licensed, with a hermetic test suite that exercises every tool over real MCP stdio.\n\nOpen source made this project *possible* — the model is open, the runtime is open, and the notebook is yours. Go outside. Take notes. Ask it questions later.\n\n*Built for [Hacktoberfest 2026 Open-Source AI Challenge: Week 1](https://dev.to/challenges/hacktoberfest-week1-2026-10-05) — Touch Grass. Built on open-source AI: Transformers.js + open-weight embeddings, Gemma 3 via Ollama, MCP, SQLite — all open, all local, all yours.*", "url": "https://wpnews.pro/news/wanderlog-an-offline-field-journal-that-answers-where-did-i-see-that-kingfisher", "canonical_source": "https://dev.to/praneshnikhar/wanderlog-an-offline-field-journal-that-answers-where-did-i-see-that-kingfisher-last-time-fg7", "published_at": "2026-10-08 17:15:26+00:00", "updated_at": "2026-10-08 17:20:36.447067+00:00", "lang": "en", "topics": ["ai-agents", "agent-protocols", "ai-tools", "natural-language-processing", "developer-tools"], "entities": ["WanderLog", "Model Context Protocol", "Ollama", "SQLite", "ONNX Runtime", "Transformers.js", "all-MiniLM-L6-v2", "gemma3:4b"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/wanderlog-an-offline-field-journal-that-answers-where-did-i-see-that-kingfisher", "markdown": "https://wpnews.pro/news/wanderlog-an-offline-field-journal-that-answers-where-did-i-see-that-kingfisher.md", "text": "https://wpnews.pro/news/wanderlog-an-offline-field-journal-that-answers-where-did-i-see-that-kingfisher.txt", "jsonld": "https://wpnews.pro/news/wanderlog-an-offline-field-journal-that-answers-where-did-i-see-that-kingfisher.jsonld"}}