Staff Software Engineer

Clojure, architecture, and engineering decisions. Based in Singapore.

  • Building Claude Code Plugins for the Team

    How I went from a personal Claude Code setup to a plugin marketplace for a small engineering team, encoding Clojure conventions, REPL workflows, and git practices into shareable skills. I suggested our CEO adopt the Anthropic Team plan, wrote onboarding guidelines, and built the plugins that the whole team and some of our clients' developers now use daily.

  • Claude Code and Obsidian for Note-Taking

    How I use Claude Code as the primary interface for an Obsidian vault, capturing technical knowledge mid-session with full context, retrieving past notes to inform current work, and keeping vault conventions consistent through a custom plugin.

  • Obsidian Vault as Blog Source

    Using an Obsidian vault as the single source of truth for blog content. Articles live alongside the working notes that informed them, linked through wiki links. The blog site is just a renderer, markdown files are the contract, and the entire authoring workflow happens inside the vault.

  • Building a ClojureScript SPA with Replicant and dispatch-of

    A custom frontend architecture for ClojureScript SPAs using Replicant, where effects are maps, dispatch is a closure, and the state layer is pure .cljc testable on the JVM. Used in both flybot.sg and pattern.flybot.sg.

  • Building a Pure Data API with Lasagna Pull

    How we replaced REST routes and controller functions with a pull-pattern API where collections are nouns, patterns express intent, and authorization is structural, all built on three composable libraries from the lasagna-pattern monorepo.

  • Clojure Monorepo with Babashka

    A Babashka-driven approach to managing Clojure monorepos with auto-discovered components, two-layer task delegation, and consistent deps.edn conventions. Applied to lasagna-pattern, a toolbox of 5 components spanning libraries and full-stack apps.

  • Deploying a Clojure App to AWS with App Runner

    How I migrated our Clojure app deployment from EC2 + load balancers to AWS App Runner with S3-backed storage, cutting monthly costs from ~$50 to ~$15 and eliminating most operational overhead.

  • Flybot.sg - A Full-Stack Clojure Web App

    flybot.sg is a full-stack Clojure web app built to demonstrate the "lasagna stack": a pull-based pattern language where the same declarative patterns handle reads, writes, authorization, and client-server transport. This article is a project overview linking to the detailed articles on each aspect.

  • Managing Web App Modes with Fun-Map in Clojure

    Fun-map turns Clojure's associative data model into a dependency injection system. A system is a map, components are entries, and different environments (prod, dev, dev-with-oauth) are just assoc operations on that map. This article shows how we use it in flybot-site to manage three runtime modes with zero conditionals inside components.

  • Pull Playground - Interactive Pattern Learning

    Pull Playground is an interactive SPA for learning the lasagna-pull pattern DSL. Two modes: sandbox (runs entirely in the browser via SCI) and remote (sends patterns to a live server). Same UI, same pull engine, different transport.

  • Hibou - A Generic Analytics Platform with Rama

    Hibou is a configuration-driven analytics platform built on Rama. A single EDN config generates the Rama module, the API validation schemas, and the UI query forms. The platform supports two complementary module architectures: nested PStates for pre-aggregated hot-path queries, and column-based PStates for flexible ad-hoc exploration. This article covers how I got there and what I learned about where Rama fits in the analytics landscape.

  • Password Security - Managers and Two-Factor Authentication

    My password and authentication setup: Bitwarden for generating and storing credentials, Ente Auth for 2FA codes, and printed recovery codes stored offline. My wife uses the same stack daily, which is a good sign that the setup is genuinely accessible.

  • Email Privacy with Custom Domains and Aliases

    A three-layer email setup (custom domains + alias service + encrypted mailbox) that isolates spam, identifies leaks, and keeps you portable across providers.

  • Gaming Stats Aggregation with Rama

    A proof of concept replacing Apache Druid + Imply with an all-Clojure analytics stack: Kafka events ingested by a Rama module, queried through a pullable API, and visualized in a custom dashboard UI. The POC proved the architecture works but revealed the need for a generic, configuration-driven platform.

  • Testing in Clojure - Tools and Techniques

    How I structure testing across Clojure projects: Rich Comment Tests for internal functions, deftest for system boundaries, Malli validation at entry points only, embedded services for CI, and containerized services for load tests. All wrapped behind bb test and bb rct so nobody needs to remember Kaocha flags.

  • Choosing a Git Workflow

    What Git workflow is suitable for your project: trunk-based, feature branching, forking, release branching, release candidate workflow, Feature branching to develop, GitFlow

  • Date and Time in Clojure with Tick

    Time in programming has too many representations, and jumping between them is where bugs live. This article walks through the progression from timestamp to instant using juxt/tick, explains why zones and offsets are not the same thing, and shows the DST edge case where adding "one day" gives two different answers depending on whether you use a duration or a period.

  • Apex Domain to Subdomain Redirect

    The apex domain problem: flybot.sg cannot use a CNAME record, but most modern hosting services (App Runner, Vercel, Netlify) only provide DNS names, not static IPs. I went from a $36/month ALB+NLB setup to a free redirect service.

  • Porting Clojure Libraries to the CLR with MAGIC

    A step-by-step approach to porting Clojure libraries to the CLR using the MAGIC compiler, covering reader conditionals, type hints, dependency management, and building/testing with Nostrand.

  • Monte Carlo Tree Search for Card Games in Clojure

    I built an AI for Flybot's card games (Big Two, PDK) using Monte Carlo Tree Search. Pure MCTS turned out to be too slow for practical use, especially after compilation to .NET via MAGIC for Unity. The solution was a hybrid: domain knowledge for early game decisions, MCTS only for endgame positions with few cards remaining. The AI depends solely on a Game protocol, making it game-agnostic.

  • MAGIC Compiler and Nostrand Integration

    Flybot uses Unity for card game frontends and Clojure for backend logic. To share code between the two without rewriting in C#, we needed a compiler that produces .NET assemblies compatible with Unity's IL2CPP runtime. I collaborated with Ramsey Nasser, the author of the MAGIC compiler, to stabilize and optimize the toolchain. My main contributions were on the tooling side: NuGet packaging, private repo support, a CI pipeline, and the suggestion to strip compilation out of Magic.Unity and route everything through Nostrand. The result is a three-command workflow that frontend developers have been using in production for years.

  • Card Game APIs in Clojure

    A protocol-driven architecture for card game backends in Clojure, where every game implements the same Game protocol and a meta-game engine composes them recursively via EDN configuration: rounds, scoring strategies, tournaments. All games run on both JVM and CLR (Unity), and external engineers have used the stack to implement their own games.