Projects
Side work and experiments.
Side work, experiments, and finished things worth showing.
Recognito is a local-first knowledge base built around a single insight: if the SQLite database is the only source of truth, the sync layer that broke Total Wikcall just goes away. Same SQLite + sqlite-vec + Ollama stack underneath. No more vault, no more file watchers, no more reconciling two stores that can never quite see each other.
Why this exists
Total Wikcall had a sync problem I never liked. Obsidian was the primary store; SQLite was a downstream index. To keep them in agreement I needed file watchers, debounce logic, content hashing, and a periodic re-indexer. Each of those was tractable on its own, and together they were always one edge case away from drifting out of sync. The deeper issue was that Obsidian cannot read SQLite natively, so the two stores could never truly converge. One of them had to be authoritative; the other had to apologize.
Recognito flips it. The database is authoritative. The UI reads from and writes to the database directly. Plain old REST plus React, served by a Fastify app on localhost:7180. There is no markdown vault. There is no sync. The thing that was hardest to get right in Total Wikcall is structurally absent.
A few choices
Web-first UI, not desktop. Recognito’s UI runs in a browser, which means it works the same on my laptop and my phone. That last part is the unlock. With Tailscale running on both devices, my phone reaches the laptop over a private mesh network and gets the full UI; the data never leaves the machine. This was the original reason Obsidian was attractive in Total Wikcall (cross-device access via iCloud sync). Recognito gets the same outcome without the sync.
Two-axis taxonomy: intent and domain. Earlier projects had a single category field that always felt overloaded. Recognito splits it: an entry has both an intent (what the entry is for: capture, reference, decision, plan) and a domain (what area of life it belongs to: developer, finance, health, etc.). Two orthogonal dimensions classify cleanly. One does not.
Recurring plans with completion history. Total Wikcall had plans, but they were one-shot: complete a plan and it gets archived. Recognito has plans that recur (weekly review, monthly bills, quarterly tax check) and tracks completion history per occurrence. This is one of those features that sounds boring on paper but changes how I actually use the system day to day.
Claude Code lifecycle hooks. Recognito ships four skills that integrate at specific points in the Claude Code session lifecycle:
recognito-queryruns at PreToolUse and reminds the agent to query the knowledge base before edits.recognito-captureruns at Stop and reminds the agent to capture meaningful learnings.recognito-planruns at PostToolUse onExitPlanModeand writes approved plans to the database.recognito-protocolruns at UserPromptSubmit and injects active system entries as[RECOGNITO-PROTOCOL]based on the current{domain, scope}.
Together they give the agent persistent searchable memory of prior context before implementation. Sessions stop being amnesic; the assistant carries forward what I already wrote, what I already decided, what I already failed at last time.
What is in it
- Single-store SQLite + sqlite-vec backend with Ollama embeddings.
- Fastify-served REST API with a React UI accessible on desktop and phone over Tailscale.
- Two-axis intent and domain taxonomy.
- Recurring plan model with per-occurrence completion history.
- MCP server exposing 22 tools to Claude Code and other clients.
- Four Claude Code lifecycle skills (
recognito-query,recognito-capture,recognito-plan,recognito-protocol).
Status
Initial implementation. The architecture is settled, the lifecycle skills work, the UI is functional. Tests, polish, and the inevitable v0.2 are on the way.
- TypeScript
- SQLite
- sqlite-vec
- Ollama
- Fastify
- React
- Tailscale
- Model Context Protocol
A local-first personal knowledge engine. SQLite plus sqlite-vec for vector search, Ollama for embeddings, and a Model Context Protocol server that exposes the knowledge base to Claude Code or any MCP client. The name is a portmanteau: “wiki” plus “recall,” with a nod to Total Recall. The database is the search engine; an Obsidian vault is the human interface; they stay in sync.
Why I built it
I keep most of what I think about in markdown notes. That worked when I wrote three notes a week. It stopped working when I started writing five a day across finance, health, side projects, and whatever Claude Code and I were working on that afternoon. Asking “what did I figure out about 401k Roth conversions?” would turn into ten minutes of grep, and half the time the answer was in a note whose filename had nothing to do with the question.
I also wanted Claude Code to be able to ask the same kind of question without me copy-pasting context into the prompt. If I am in a session about portfolio rebalancing, the assistant should be able to look up what I already wrote about it last quarter, instead of starting from zero every time.
That suggested two things glued together: an embedding-based search index over my notes, and a way for an LLM agent to query that index during a session. Model Context Protocol made the second part tractable. sqlite-vec made the first part tractable without spinning up a dedicated vector database.
A few choices
Local-first, not hosted. Pinecone, Weaviate, and the rest are better at scale, but my whole vault is a few thousand notes and I would rather not ship them to a third party. sqlite-vec keeps everything in a single brain.db file. Backups are cp. The cost is worse recall than a production vector DB on a billion-document corpus, which I do not have.
Ollama, not the OpenAI embeddings API. Running locally meant I could index aggressively without watching a meter, and I could keep notes about things I would rather not put through someone else’s API. I use nomic-embed-text (768 dimensions, truncated to 256 via Matryoshka, L2 normalized). The truncation cuts storage by a third and the recall hit is small enough that I have not noticed it for personal-vault sized data.
Pair it with an MCP server. The engine on its own is just total-wikcall search "..." from a terminal. The real unlock is letting Claude Code call search, findConnections, writeNote, and friends as tools during a conversation. It changes the assistant from “reads what you paste” to “reads what you have already written,” which compounds over months.
What I learned
The database has to live outside the vault. My Obsidian vault is in iCloud Drive, which corrupted the SQLite WAL file the first time I tried colocating them. The fix was two characters in a path constant, but the bug took an afternoon to track down.
Two-way sync against a markdown file with checkboxes is harder than it looks. A user editing a plan note in Obsidian will trigger several rapid change events, sometimes with partial content. The current debounce plus content-hash plus task-deduplication setup is the third version. The first two had race conditions where a checked box would flip back to unchecked because Obsidian saved a stale buffer.
These two lessons are the reason Recognito exists. The sync layer was always going to be the thing that gave out first, and trying to keep two stores in agreement when only one of them can natively read SQLite turned out to be a fight not worth winning.
What is in it
- Engine: add, update, delete knowledge entries with embeddings; cosine-similarity vector search with optional filters; cross-reference discovery via
findConnections; vault-wide connections report; plan and task tracking with two-way sync to markdown checkboxes. - CLI:
reindex,search,watch,connections-report,health,stats. - MCP server: 18 tools across read, write, and plan/task management, used by Claude Code and other clients.
Status
Predecessor to Recognito. Still works for daily use, but no longer actively developed.
- TypeScript
- SQLite
- sqlite-vec
- Ollama
- Model Context Protocol
- Obsidian
The site you are currently reading. A portfolio built on Astro, designed and written from scratch rather than dropped onto a template. The visual language is an homage to a hand-designed resume I made in 2015 that leaned hard into typographic blocks and a strong personal aesthetic.
Why I built it
I wanted a single place that does three things: tells my story end to end (work, projects, skills, education, the rest), serves as the canonical version of my resume, and gives the people I link it to (recruiters, hiring managers, the occasional friend) a useful artifact rather than another LinkedIn page.
LinkedIn does the social-graph job well enough. It does not let me write the way I want to write, organize the way I want to organize, or remove the things I do not want to compete with for attention. A personal site does.
A few choices
Astro, not Next.js. Most of this site is content. Static generation with islands of interactivity where I want them is the right shape; I do not need server-side rendering, an API layer, or React state on every page. Astro keeps the bundle small, the build fast, and the markdown-first content flow clean.
Content collections, not hard-coded pages. Each project, role, and skill section is a markdown file in src/content/. Adding a project is one new file, not a code change. Same for editing one.
Custom design, not a template. Templates are fine, but a portfolio with someone else’s visual fingerprint is a missed opportunity. The typography, spacing, color palette, and section layout are mine, not a download. I leaned on the muscle memory of a hand-designed resume I built in 2015, which used heavy black geometric blocks, sectioned typographic hierarchy, and a willingness to commit to a strong point of view. The web version softens the edges but keeps the spirit.
View transitions enabled. Navigating between pages should feel like one continuous surface, not a slideshow of full reloads. Astro’s view transitions get most of the way there for free.
What is on it
- A short pitch on the home page that doubles as the site’s elevator brief.
- A Work page with role-by-role context.
- A Projects page (you are on it) with longer write-ups of side work.
- A Skills page that is honest about depth versus surface familiarity.
- An Education page.
- An About page.
- A downloadable resume linked from the nav.
What is rough
The Writing section is empty for now. I will fill it once I have something worth saying that has not already been said in a hundred other posts.
- Astro
- TypeScript
- Markdown content collections
- View transitions