Director of Web Development · Linksbridge · Redmond, WA

Systems, interfaces & the plumbing in between.

Software engineer in Redmond, WA, currently Director of Web Development at Linksbridge. [One sentence about the kind of problems you like to work on.]

  • Now building — Anomaly
  • Reading — System Design: Coherence First (Julio Lena) · AI Forward Deployed Engineering (Sagar Khatri)
  • Learning — agentic loops: from simple tool-wielders all the way out to self-improvement

Selected work (09)

A few things I’ve built, broken, and rebuilt. Each one links to notes on how it was made.

  1. 01 Linksbridge Workspace A multi-tenant SaaS platform global health organizations use to manage and report on their programs — built solo, MVP to production. React · Azure Functions · JWT · Azure 2020
  2. 02 Airmusician.com A browser experiment in how far webcam hand-tracking and in-browser audio synthesis can go — play guitar, drums, and keys without touching anything. React · TypeScript · MediaPipe · Tone.js · Auth0 2026
  3. 03 Il Fortuna A fantasy RPG league platform — draft magical warriors from a custom world, invest soma currency, and compete on daily world-simulation results. Astro · React · Supabase · Stripe · Vercel 2025
  4. 04 Anomaly Scans hours of paranormal investigation footage and surfaces ranked candidate moments for human review — cuts the watching problem down to regions of interest. Python · OpenCV · FastAPI · pgvector · Docker 2026
  5. 05 GarageBand AI A macOS chat interface that reads your live GarageBand session and controls the DAW — transport, tracks, plugins, MIDI — in plain language. Swift · SwiftUI · CoreMIDI · AXUIElement · Anthropic API 2026
  6. 06 Holograph A config-driven BI dashboard library — connect a data source, build dashboards, and consume them as a React component with deep CSS control over every chart. React 2025
  7. 09 Real You-Wives A retro choose-your-own-adventure parody of reality-TV housewives — an LLM writes NPC dialogue and escalating choices live while a pure-function engine enforces story structure via acts, flags, and stat-driven endings. Vite · React · Cloudflare Workers · Gemini API 2026
  8. 08 RHOUV Six AI personalities, one mansion, every secret a JSON event — a multi-agent reality show that measures judge bias, persona drift, and hidden-state leakage across hundreds of headless episodes. Python · LiteLLM · FastAPI · React · D3 2026
  9. 07 Albemuth A daily AI-written tech blog — a scout agent picks a story and counterculture lens from RSS, a writer drafts it, a critic loops on voice and insight, owner approves the GitHub PR to publish. TypeScript · Node.js · Zod · GitHub Pages 2026

Build notes

How each project was put together — the decisions, the dead ends, and what I’d do differently.

01 / 07

Linksbridge Workspace

React · Azure Functions · JWT · Azure — 2020
Linksbridge Workspace interface showing the main dashboard

The problem

Global health organizations needed a single platform to manage their programs and produce reports — no off-the-shelf tool fit, so Linksbridge needed one built from scratch.

How I built it

Built end to end solo — architecture, React front end, Azure Functions API, JWT auth, and deployment. Multi-tenant from day one, supporting multiple organizations from a single codebase. Now leading a team of three extending it.

The hard part

Three things: migrating auth providers costs far more than you budget for it. Keeping React versions current is a recurring tax, not a one-time task. And flow testing — end-to-end user paths — is the only thing that reliably catches quality problems before clients do.

What I’d change

Most screens don't need a full SPA. I'd rebuild it as mostly SSR — Next.js or Astro — with React only where interactivity actually earns it. Shipping a full React client for read-heavy reporting screens was the wrong trade-off.

02 / 07

Airmusician.com

React · TypeScript · MediaPipe · Tone.js · Auth0 — 2026
Airmusician.com interface showing the band page with musician profiles and track controls

The problem

A personal experiment: how far can browser-embedded hand tracking and audio synthesis actually go? I wanted to know if a webcam alone could be a real musical instrument controller.

How I built it

MediaPipe runs hand tracking via WebAssembly and TFLite directly in the browser — no server needed. Gesture coordinates feed into Tone.js to trigger and shape audio in real time. Three instrument modes (guitar, drums, keys) share the same gesture pipeline. Sessions persist in IndexedDB via Dexie; Auth0 handles identity.

The hard part

Gesture recognition is computationally expensive, and knowing when to start tracking against a live metronome is a harder problem than it looks. The onset detection — deciding the exact moment a gesture becomes intentional — is still being tuned.

What I’d change

The scope got away from me — AI capabilities kept expanding what seemed possible, and I kept reaching. The insight I'm carrying forward: nail the gesture model for one instrument and the others follow. I'd scope to one instrument first and ship that cleanly.

03 / 07

Il Fortuna

Astro · React · Supabase · Stripe · Vercel — 2025
Il Fortuna system architecture: GitHub Actions scheduled workflows trigger Vercel API endpoints, which connect to Supabase for game state, Eden AI via Minimax for character portrait generation, and Stripe for membership payments

The problem

Fantasy sports scoring logic maps surprisingly well onto tabletop RPG sessions — character actions, successes, and growth are scoreable events. I wanted to see if you could run a full fantasy league around a custom invented world instead of an existing sport.

How I built it

Astro with SSR on Vercel keeps pages fast and auth-gated. Supabase/Postgres holds all game state — character rosters, soma balances, league standings, daily performance. A daily simulation run generates character performance metrics that drive the leaderboard. Stripe handles membership tiers.

The hard part

Making characters diverge meaningfully over time — reflecting their choices and the world's state back into their stats — was harder than the fantasy scoring itself. Hexmap mission tracking added another layer of spatial state that had to stay consistent with the simulation. This was also an early vibe-coding experiment, and I got burned by context poisoning.

What I’d change

Strict context hygiene from day one. Test every function, watch those tests, and cut over-engineering ruthlessly. Letting the AI accumulate stale context quietly breaks things in ways that are expensive to untangle later.

04 / 07

Anomaly

Python · OpenCV · FastAPI · pgvector · Docker — 2026
Anomaly detector interface showing ranked anomaly candidates from paranormal investigation footage with frame thumbnails and scoring

The problem

Any paranormal investigation generates hours of video and audio. Sitting through all of it manually is the bottleneck. This tool surfaces the moments worth looking at — it doesn't draw conclusions, it cuts the watching problem down to ranked candidates for a human investigator.

How I built it

Two deliberate tiers. Tier 1 runs entirely on CPU with no network: frame difference, optical flow, background subtraction, and spectral audio analysis flag statistically anomalous moments and rank them session-wide. Tier 2 is optional — top candidates go to a vision model, which returns a plain-language description and an edge-difference map showing what's new in the frame. Events and embeddings land in Postgres via pgvector, enabling cross-session similarity search.

The hard part

Calibrating sensitivity without flagging everything. The detector has to err on the side of missing events rather than surfacing noise — but finding that threshold for different footage types (fixed tripod vs. low-light vs. night vision) is an ongoing tuning problem. The apparition gate sequence exists entirely because of false positives.

What I’d change

[To be filled in — still building.]

05 / 07

GarageBand AI

Swift · SwiftUI · CoreMIDI · AXUIElement · Anthropic API — 2026
GarageBand AI chat interface showing an AI response with suggested track changes displayed alongside an open GarageBand session

The problem

Music AI tools are great at generating, but I wanted one that could actually touch my raw DAW session — change tempo, mute a track, fire a plugin — without me leaving the creative flow to click around menus.

How I built it

SwiftUI chat interface streams responses from multiple AI providers (Claude, OpenAI, Gemini, Ollama). GarageBandController reads and writes DAW state via the macOS Accessibility API (AXUIElement) — transport, tracks, regions, plugins, automation. MIDIController creates a virtual CoreMIDI port for direct note playback. Live project state (tempo, key, time signature, tracks) is injected into every system prompt so the AI always knows what's open.

The hard part

Tool calling still feels primitive — you have to explicitly define every capability the AI can reach, and figuring out what tools need to exist is an ongoing design problem. The ideal would be for the AI to discover available controls automatically, the way an MCP setup could theoretically work.

What I’d change

Wire up proper tool use from the start rather than text-only responses. The architecture is already there in GarageBandController — it just needs the tool-calling layer in front of it.

06 / 07

Holograph

React — 2025
Holograph dashboard editor interface

The problem

PowerBI looks like PowerBI no matter how hard you try to style it — and embedding it requires managing a VM server. I wanted a lightweight BI alternative that was supremely stylable and didn't carry that infrastructure overhead.

How I built it

Config supplies the data source and dashboard definitions. The library outputs React components with deep CSS access to every chart element — no style locked behind vendor classes. Forked it into a private Linksbridge repo to add org-specific config.

The hard part

Designing a single interface that works across a galaxy of different chart types. And making chart items fill all available space in a region while staying fully responsive — those two constraints pull against each other in ways that aren't obvious until you're deep in the layout logic.

What I’d change

The scope of replacing PowerBI wholesale became hard to wrangle. I'd pick the two or three chart types where PowerBI genuinely struggles, own those completely, and ship something polished rather than broad.

07 / 09

Albemuth

TypeScript · Node.js · Zod · GitHub Pages — 2026
Albemuth UI showing the scout, writer, and critic agent pipeline with a completed transmission displayed below

The problem

Reading tech news and writing something useful from it is a recurring cost. The idea was to automate the finding, the framing, and the first draft — keeping a human in the loop only to approve before publish. Named after Philip K. Dick's Radio Free Albemuth: a post is a transmission, the writer's style is a frequency, the critic is the noise filter.

How I built it

Pure TypeScript on Node with no framework and no vendor SDKs — LLM calls are plain fetch behind a single interface, so the router can point at Anthropic, OpenAI, Groq, or Ollama without touching agent code. A "lens" (Burroughs, Thompson, Parsons) is a YAML file that shapes the writer's system prompt. Four agents run in sequence: scout picks story and lens from RSS; writer drafts; critic scores on voice, humor, insight, accuracy, and taste and returns validated JSON; writer revises — loop until scores pass or the round cap hits. A browser UI streams live progress over SSE.

The hard part

Keeping the critic useful without training the loop to converge on bland. The scoring dimensions are chosen to pull against each other — accuracy and taste are conservative, voice and humor push toward risk. Getting the pass threshold so it pushes the writer without sanding the edges off is the core tuning problem. A separate Tuner page replays logged scores under different settings to work on this without burning API tokens.

What I’d change

[Still building — publishing step (HTML → GitHub PR) not yet wired.]

Read the column

Latest transmission →

08 / 09

RHOUV

Python · LiteLLM · FastAPI · React · D3 — 2026
RHOUV viewer showing the episode feed, brand leaderboard, and D3 alliance graph during a dinner simulation

The problem

Most agent evals are one model talking to itself. RHOUV is about information asymmetry: agents with private memory, conflicting goals, and a judge whose biases you can actually measure. The reality show format makes it legible while running a real multi-agent eval — what does an LLM judge actually reward when scoring a dinner, and how much of that is bias instead of drama?

How I built it

Python engine with six YAML persona cards (voice, secret, goal, weakness, composure floor). LiteLLM wraps every model call so any provider swaps in without touching agent code. A director agent handles turn order, course progression, and mid-dinner twists. A composure ledger tracks wine escalation and targeted hits per personality; floors prevent full collapse for controlled-chaos characters. ALLY/FEUD in-band markers parsed from speech create a running alliance graph. Cross-episode memory persists brand scores, composure minimums, and grudges into memory.json so episode 2 inherits episode 1’s feuds. An MCP analytics server lets Claude query episode data directly. Everything saves as JSONL — source of truth. FastAPI + SSE for live replay; React + Vite viewer shows the event feed, brand leaderboard, composure arcs, D3 force-graph alliance map, and an eval page with 10 D3 charts across brand, composure, behavior, and alliance dimensions. Token cost tracked per run with configurable wine level, context windows, and model-per-role.

The hard part

Per-agent hidden state adds real complexity — each personality sees only her own memory plus public events, requiring separate context windows and real token cost. LLMs with internal reasoning tokens (DeepSeek, Claude via proxy) burn them against the max_tokens budget and return empty content at low caps. The D3 force graph initializing all nodes at center caused charge forces to go chaotic and fired 300+ React state updates per simulation tick. The eval pipeline itself revealed a prompt engineering failure: ALLY/FEUD markers were specified as ALLY: [Name] but models universally dropped the brackets, so the regex never matched and zero alliance events were logged across seven episodes — invisible until the eval charts showed a flat alliance graph.

What I’d change

Wire the eval pipeline before the feature work, not after — most of the composure and alliance bugs only surfaced when the charts made the data visible. The analytics cache also silently poisoned results: a mid-run request cached a partial read, and episode 7 showed no token costs until the cache logic was fixed to only cache complete runs. I’d also separate the judge model from the personality model from the start; using the same model for both adds measurement noise you can’t easily subtract later.

09 / 09

Real You-Wives

Vite · React · Cloudflare Workers · Gemini API — 2026
Real You-Wives gameplay showing the scene panel, character portrait with confessional cutaway, and three escalating choice buttons Real You-Wives stats row and choice screen mid-episode, with drama and chaos bars elevated

The problem

Most choose-your-own-adventure engines are fully scripted (predictable after one play) or fully AI-generated (the story wanders without structure). This is an experiment in a middle path: encode the spine of the story — acts, flags, stat-driven endings — in data, then let the LLM write only the connective beats between scripted waypoints. The story is different every time but always goes somewhere.

How I built it

A pure-function game engine in vanilla JS — no DOM, no network, no state library. Every function takes state in and returns new state, making it trivially serializable and safe to send to the LLM. Adventures are JSON: scenes, choices, flags, stats, and an act system that blocks END choices engine-side until all acts complete, steering the LLM toward each act's goal via prompt injection. The endpoint is a Cloudflare Worker — no server state, game state travels with every request. A layered pixel-art portrait system renders characters via SVG with per-character colour tokens. Confessional cutaways animate the mouth open/closed with CSS overlay SVGs cycling at ~2.6Hz. Ajv validates adventure files locally; a hand-written inline validator runs in the Worker where eval() is blocked.

The hard part

Cloudflare Workers block new Function() — which is how Ajv compiles JSON Schema validators — so the first deploy silently failed every generated beat. Local dev used Magica as the default LLM provider while the deployed Worker defaulted to Gemini: two environments, both "working," until tested in production. Reasoning models burn thinking tokens against max_tokens and return empty content at low caps; bumping the limit from 400 to 600 fixed silent empty responses. The LLM consistently used wrong field names (location instead of place, choices without text) until a concrete JSON example was added to the system prompt.

What I'd change

Build the story act structure before writing scene content — retrofitting acts onto existing scenes required careful checking of anchor scene references and flag reachability across branching paths. Test the deployed endpoint explicitly against the actual configured provider on day one; local dev with a different default provider masked three separate production bugs that only appeared after deploy.

Try it

The game is live but password-protected to keep the LLM costs in check. Email me and I'll send you the password.

About

Software engineer with 20+ years of professional coding experience — enterprise since 2012, cloud since 2018. I built Workspace solo from MVP to production and now lead the team extending it at Linksbridge, still finding the most interesting problems live at the seams between layers.

I work across the stack: React and TypeScript on the front end, Azure Functions and REST APIs in the middle, SQL Server and PostgreSQL underneath, CI/CD and Azure tying it together. I’ve spent the past few years leading a small team while staying hands-on — reviewing code, mentoring, working directly with clients, and shipping.

When I started in web development, one person was expected to handle design, CSS, server, and database — all of it. It feels like we’ve come full circle back to that expectation. I can do that.

Experience

  1. LinksbridgeDirector of Web Development2023–Now
  2. LinksbridgeLead Web Developer2020–2023
  3. Disney, Microsoft, AIM + othersContract front-end developer2012–2020