My projects span machine learning, programming languages, personal analytics, browser extensions, code editors, and personal tools to support my workflows. I work on projects for fun, to learn, or to solve a specific problem I have. More recent projects are listed first.

Click on a project’s heading to go to its website/repo.



A smarter search engine for a local (South African) fashion and homeware store.

I have no affiliation with said store. I built this for myself, because I was frustrated at how difficult it was to find what I wanted with the existing search engine + I was curious how well CLIP (a relatively new AI technique with open source code and models) would work here.

I think it works quite well! It’s much more forgiving than the original search engine. I don’t have to guess what exactly they decided to label a particular item. But what I like even more is that it works quite well for abstract things like “colourful shoes”.

Here’s the full stack:

  • Hardware: Deployed on a 2CPU 4GB RAM VPS with docker
  • Storage: SQLite + object storage (for images)
  • Search: CLIP text/image neural networks + faiss similarity search index
  • Pipeline: Python scripts + cron
  • Web: Django serving HTML/Tailwind/daisyUI
  • IDE: Developed in notebooks with nbdev

I tried to keep the implementation as simple as possible and I’m happy with the result! It took ~2 weeks to build and has been running seamlessly without my input ever since.

Check out this Twitter thread for more details.


I’m a core developer of nbdev, an open source notebook-driven software development platform.

nbdev let’s me use exploratory programming (related to REPL-driven development and literate programming) for all of my software development.

I also came up with the idea of hooking into the Jupyter file save API and using a custom git merge driver to improve Jupyter/git integration. All of the core functionality used by these hooks was already implemented by other nbdev core developers. You can find out more in the write-up.

Plum dispatch for fastcore (experimental)

An experimental PR to fastcore (the core library powering the fastai deep learning framework), that replaces its custom type dispatch system with plum-dispatch. Both bring Julia’s multiple dispatch into Python using type annotations and decorators.


EasyEquities browser extension

A browser extension for the EasyEquities investment platform built with TypeScript, React, React Router, and Mock Service Worker (to develop against a mocked version of their API).

This started with frustration that EasyEquities didn’t provide a single view of my holdings across all of my investment accounts, nor of my time-weighted returns. I made good progress but stopped building this since there’s no public EasyEquities API, and sending handcrafted requests to the internal API of my investment platform seems like a bad idea!

MyFitnessPal to SQLite

Save your personal data from MyFitnessPal to a SQLite database. I occasionally use MyFitnessPal to track my weight and calories. Inspired by the dogsheep movement, I built this to afford personal analytics on my own data.

CircleCI to SQLite

Save your personal data from CircleCI to a SQLite database. I threw this together for a quick analysis on build times at a former workplace, which we could then follow up with easy build pipeline optimisations.

Reverse engineering ncode

An attempt at reverse engineering ncode paper technology. I got all the way down from what looked like dots on paper to a matrix which I needed to decode. Perhaps I’ll get back to it some day!


An experimental framework for creating structure-aware editors. The idea was that actions (e.g. move left, insert character) would be aware of the context surrounding the cursor location within the (tree-)structured document, thus would have slightly more intelligent behaviour.

For example, given a document ((foo)|, where | represents the cursor, and where ((foo)) is a known symbol represented some BlockRef object, inserting ) would know to create a BlockRef object in the internal tree structure. Like a hackier version of tree sitter. The vision was to use this framework to create a Roam clone using an enhanced markdown-like syntax for personal use.


Functional hierarchical zipper (tree cursor), with navigation, editing, and enumeration. A port of to JavaScript that I intended to use in Romulus.

Roam tools

Tiny command-line tools for working with Roam graphs.

Roam parser

A tiny Roam parser built with Clojure and instaparse. I also live tweeted the entire development process.

Image alignment

Keypoint-based alignment of two grayscale images using ORB and RANSAC via skimage. This was completed as a take-home assignment for a job application, so I limited the implementation to a max of 8 hours.



Minimal terminal text editor written in Python and curses. I wrote a corresponding step-by-step tutorial as well.

Advent of Code

I enjoyed taking part in AoC for years 2017, 2018, 2019, and 2020.

Alfred Github local

An Alfred workflow to open GitHub repo URLs via a local workspace directory. No GitHub API access needed!


Seamless logging for pandas dataframe operations, inspired by tidylog. We used pandas in production extensively at a former workplace, and our code often ended up overwhelmed with logging logic. With pdlog it’s simple: instead of calling, say, df.dropna(), call df.log.dropna() and it’ll log <pdlog> dropna: dropped 1 row (17%), 5 rows remaining.


Neural networks from scratch

A basic implementation of neural networks from scratch. Shortly after my encounter with reinforcement learning (see below), I realised that deep learning was an important precursor and shifted my studies there.

Reinforcement learning from scratch

Re-implementing sections of Sutton and Barto’s Reinforcement Learning: An Introduction. My first inspiration in AI was the possibility that a computer could play games better than the best humans! I was determined to build one of these AIs myself.