Personal projects · field notes
What I build on my own time
I'm a neurodivergent engineer, and depth is my edge. I build each system end to end, with its decision log and test suite next to the code: ClawBot carries 3,500+ tests. Each featured write-up covers how the system works and one thing that broke or one decision I'd make again.
Repos are private; I'm happy to walk through any of them. Open to Staff and Principal AI engineer and AI architect roles: duong.nick@gmail.com
Seven projects, AI and data work first
Featured · Agent platform · Feb 2026 – now
ClawBot
What it is
My personal agent platform, written in Python on the Claude Agent SDK. One manager process supervises 12 bot instances on Telegram, each with its own persona and its own markdown vault for memory. A WhatsApp Cloud API adaptor is built too. Tools run in-process as MCP tools: 228 of them across 26 domains. It runs my smart home (Sonos, Hue, Alexa) and my messaging every day.
Why I built it
I think in knowledge graphs, not to-do lists, and I wanted assistants whose memory works the same way: they keep what they learn instead of starting over each conversation. My own messages and my own house are the most honest test environment I know. When recall is wrong or a tool fails, I find out that evening. It's the proving ground for the memory patterns I built into ReliBot at The Hartford, written up in the ReliBot & ClawBot architecture notes.
How it works
- Plugin registry. Each module declares its domain, tools, env gate and sub-agent, and the registry finds it at startup. A new domain needs no core changes. Each bot switches domains on with env flags.
- Per-message router. Four tiers (fast, light, full, hard) set the model, the tools, the thinking effort and the turn cap. It defaults upward. Only purely social messages go to the tier without tools, and a kill switch restores the old behavior.
- Auto-recall with no tool call. L1 is
MEMORY.md, always present. L2 is the last 3 days of daily notes. L3 matches entities in the message against a graph of entities, relationships and mentions. Recall goes in front of the user message, so the system prompt stays stable for prompt caching. - Hybrid search on demand. 70% vector and 30% BM25, a 30-day recency decay, query expansion from the top hits, then MMR re-ranking for variety. The SQLite index can be rebuilt from the vault.
- Fail-closed tool policy. Every call passes a
can_use_toolcheck: safe, monitored and blocked tiers, a prompt-injection scan with untrusted content fenced off, path-traversal and internal-URL checks, a recipient allowlist for outgoing messages, rate limits and an audit log. - Local hearing. Voice notes and videos become a whisper transcript, sound tags, signal measurements and a spectrogram, all computed on the host. No audio leaves the machine.
- Evals before upgrades. Model migrations run on separate git worktrees against coding and site-generation evals, then on a 2-bot canary, then the whole fleet.
Built with
Featured · Autoresearch loop · May 2026 – now
StockClaw
What it is
An overnight research loop. Every night, Claude rewrites one Python file: a pipeline that ranks the top 5 stocks in each of 11 industries. A frozen walk-forward backtester scores each rewrite, and a rewrite stays only if it beats the best so far. It is research only. It has no brokerage connection and places no orders.
Why I built it
It applies Andrej Karpathy's autoresearch idea to stock ranking: one file the model may change, one evaluator it may not. I wanted to see how far an LLM can push a scoring function when the judge is out of its reach, and what guardrails that takes. Markets are a good test because they punish look-ahead and overfitting fast.
How it works
- One file changes, one doesn't.
pipeline.pyis open to the model;evaluator.pyis not; a small contracts module sits between them. The model sees one aggregate score. Each iteration is scored on 3 of 12 hidden six-month holdout windows, sampled from a seed. - Cheap checks first. A validator parses the candidate, checks its imports against an allowlist that blocks network libraries, scans its SQL for look-ahead (every dated filter must be on or before the as-of date) and caps the size of the diff.
- Agent gates. A Sonnet critic checks that the code does what its hypothesis says and catches made-up numbers before the expensive backtest runs. A bear reviewer, modeled on the TradingAgents paper, names the market regime most likely to break the strategy. After 5 iterations without progress, a manager note in the style of FinCon suggests a direction, and the next attempt runs on Opus.
- A score that charges for everything. Information ratio against the sector ETF, minus penalties for drawdown and turnover. Trading costs and slippage on every trade, a one-month embargo, and three baselines: the sector ETF, SPY and five random stocks.
- Kill switches. A nightly spend cap, an iteration cap, a stop after 15 iterations with no improvement, a cost watchdog and a wall-clock limit. If an agent times out, the loop logs it and moves on.
- Every attempt is logged. Candidates are hashed and staged outside the live file, with a snapshot for rollback. Hypothesis, score and score components go into a DuckDB ledger, next to the cached prices, news, SEC filings and fundamentals.
- Warren reads, never writes. Warren is a Telegram research bot on ClawBot. It runs four investor lenses (quality-value, contrarian, macro, thematic) over public data, must cite tool output in every verdict, and reads StockClaw's picks through a read-only bridge. It has logged 3,240 verdicts, and only about 1.2% are BUY.
Built with
Featured · Data platform · Nov 2025 – now
Energy-market research platform
What it is
My research platform for U.S. wholesale electricity markets. Async collectors pull day-ahead and real-time nodal prices, constraints, outages, load, renewables forecasts and weather into a DuckDB warehouse with bronze, silver and gold layers. On top sit PyTorch forecasters, a price-correlation graph, backtest and audit harnesses, and a FastAPI dashboard. It started with MISO; in summer 2026 I ported the same schema to SPP, PJM and ERCOT.
Why I built it
Power prices are a graph problem on a deadline: thousands of pricing nodes, congestion that ties them together, and a day-ahead market that closes at 10:30 a.m. Eastern. I wanted a stack where every number traces back to a raw pull, and where every backtest has to survive an audit before I believe it.
How it works
- Medallion warehouse in DuckDB. Bronze keeps raw pulls, silver unifies them, gold holds features and marts. About 460M unified price rows across the four markets. One writer at a time behind a file lock; dev, test and analysis copies are reflink clones taken under that lock.
- Collectors. Async HTTP/2 collectors with a rate limiter and a three-tier source fallback, run by 96 scheduled jobs. The daily pull went from 20–60 minutes to 38 seconds.
- Price-correlation graph. 2,546 nodes and 623K edges (correlation above 0.95) in DuckPGQ, used to find congestion clusters and spread paths. Claude Code queries it through an MCP server.
- Forecasters. PyTorch LSTM, TCN, PatchTST and GNN models, tracked in MLflow and tuned with Optuna, trained on CPU and Apple-silicon MPS.
- Validation before belief. Simulations enforce the day-ahead cutoff. Look-ahead audits, overfitting tests (combinatorial purged cross-validation, probability of backtest overfitting), pre-registered studies with written no-go verdicts, and hash-chained ledgers that record every simulated run.
- Four markets, one contract. SPP, PJM and ERCOT share the MISO schema, and I built one dashboard for all four, with per-market adapters that emit the same data contract.
- Guardrails on the tooling. 14 least-privilege Claude Code sub-agents help build it, and the test suites refuse to touch the production database or the network.
Built with
Featured · Self-hosted photo platform · May 2026 – now
DVPhoto
What it is
A self-hosted, Google-Photos-style platform for my family's photos. A Swift/Vapor server on a Mac mini stores the originals, finds duplicates, makes browser-safe copies, detects and groups faces locally, and serves a React web app. A Linux server in front of it terminates TLS and proxies over Tailscale, so the Mac has no inbound ports open.
Why I built it
I wanted our photos on hardware I own, without giving up what makes the cloud apps worth using: backup that just runs, fast browsing, and finding people. I considered forking an existing open-source server and decided against it. Apple's own frameworks are the reference implementation for HEIC, RAW and Live Photos, and one Swift stack from phone to server keeps the types the same end to end.
How it works
- Contract first.
CONTRACT.mddefines the wire format in 75 endpoint sections. Any change edits the contract first, then the server and clients together. 29 releases so far, from 0.3.0 to 3.7.0. - Resumable, deduplicated ingest. A SHA-256 hash per file, an init call that skips the upload on a match, chunks of up to 16 MiB with a probe to resume, an idempotent finalize, and sweeps for orphaned uploads.
- Apple frameworks for media. ImageIO, CoreImage and AVFoundation turn HEIC, camera RAW and HEVC into browser-safe JPEG and H.264. No ffmpeg, libheif or libraw.
- Faces that respect corrections. CoreML face embeddings feed an incremental clusterer that only places unassigned faces, so my fixes survive every re-run. A face has to be close to both a member and the person's centroid, which stops look-alikes chaining into one person. Every correction can be undone for 30 days.
- Duplicates across accounts. 64-bit perceptual hashes live in Postgres as
bigintand are compared by Hamming distance. Copies also match on byte hash, Apple content ID, or the first frame of a re-encoded video. - A persistent job queue. Postgres (27 migrations) backs a queue that runs face detection, transcodes and sweeps in the background.
- Quiet about what exists. Sign in with Apple on the phone, Argon2id passwords on the web, rotating refresh tokens. The access check answers 404, never 403, so an outsider can't tell whether an item exists.
- The web app. React 19 and TypeScript with TanStack Query and Router: 12,100 lines and 22 Playwright tests.
Built with
Featured · Native iOS app · May 2026 – now
DVPhoto for iPhone
What it is
The iPhone half of DVPhoto: a native Swift 6 and SwiftUI app that backs up the whole photo library to my server in the background, and picks up where it left off after any interruption. A custom SyncEngine and a SQLite sync database keep track of every file.
Why I built it
Backup is the one job that has to be boring. If it needs attention, it isn't working. So the phone app does that one job, and browsing, albums and people live in the web app.
How it works
- Delta scans. PhotoKit change tokens find what changed since the last scan. Change records merge in order, so insert-then-delete and restores come out right. When a token has aged out, a full-scan diff takes over so deletions aren't lost.
- Big files never sit in memory. SHA-256 runs in 1 MiB slices off the main actor. Each 16 MiB chunk is its own task on a background
URLSession, and iOS relaunches the app to queue the next one. - A state machine you can read. The SyncEngine moves every resource through a GRDB (SQLite) ledger, with a sweep for rows that stall:
pendingextractinguploadingfinalizinguploaded|failed
- Retries that know the difference. Transport errors, 5xx and timeouts requeue without spending a retry. A 401 mid-upload keeps the staged session. Token refresh is single-flight, and the session is wiped only on a 401 or 403.
- Pairs in order. Live Photo videos and other paired resources finalize after their primary photo, which avoids a race on the server.
- Easy on the battery and the data plan. It pauses when the phone runs hot and won't pull iCloud originals over cellular when cellular backup is off.
- Crash reports that survive the crash. MetricKit reports queue on disk and upload to the server on the next good run.
Built with
Prototype · EdTech · Apr – Jun 2026
Aquinas
A self-hosted Socratic AI tutor for K–12 schools. It guides students to the answer instead of handing it over. One shared engine sits under two thin FastAPI servers, one for grades 9–12 and one for K–8, each with an age-appropriate persona. Students and teachers sign in with Microsoft 365, classes sync from Microsoft Graph, and teachers can review every conversation. Each school runs its own instance, and the privacy design aims at COPPA and FERPA from the start. It hasn't been audited.
- Any provider. Adapters for Anthropic, Azure OpenAI, Vertex and local Ollama, plus a router that sends simple turns to a fast or local model and multi-step work to Claude.
- A second opinion on every reply. A policy critic checks each answer against the assignment's help level and flags replies that solved the problem instead of guiding.
- Teacher alerts mid-stream. Streaming stops when a teacher-alert marker appears, and stops reading from the model when the student disconnects.
- Try to break it first. Before pitching it to anyone, I ran an adversarial multi-agent review of my own code, logged every confirmed finding and ranked the fixes ahead of any pilot.
Built with
Infrastructure · always on
Homelab
Two machines I own: a Dell PowerEdge T140 with 64 GB of RAM, and a Mac mini M4, joined over Tailscale. The Linux box is the edge and the batch worker. The Mac handles Apple-framework work, MPS training and app backends, and never faces the internet. No Kubernetes: systemd, journald and a written decision log in every project directory.
- Edge and compute are split. OpenResty on Linux terminates TLS and proxies to backends on the Mac mini over Tailscale.
- Certificates without coupling. Let's Encrypt certificates are issued through a webroot authenticator, so renewal doesn't depend on the web server's config.
- Jobs that are watched. Many scheduled jobs have a sibling whose only purpose is to check that the job ran: freshness checks, liveness probes and dead-man switches.
- Containers where they earn it. Podman runs Open WebUI and a private SearXNG. Everything else is a native systemd unit.
- Small, hardened helpers. Single-purpose daemons run under systemd sandboxing and act only on change.
Archive · May – Jun 2025
2025 archive
AI-audited medical billing system
A medical billing app with a React frontend on CloudFront, a FastAPI backend on AWS Lambda with Postgres, and an LLM claim-audit endpoint that runs on Amazon Bedrock or a local Ollama model. I deployed the same app three ways (Amplify, CloudFront with Lambda, and self-hosted behind OpenResty) to compare the trade-offs. Two of them are still up. All demo data is synthetic.
- Live demo · self-hosted
- Live demo · CloudFront
- AWS architecture
- Architecture diagram (PNG)
- billing-system-aws
- aws-billing-cloudfront
- aws-billing-backend-lambda
My 2025 RAG and CAG chatbot experiments are retired and no longer online.
If your team values depth, complete systems and neurodivergent thinking, we should talk. The repos for the featured projects are private, and I'm glad to walk through any of them, from the decision log to the test suite.
Work: architecture notes
Cheatsheets: ReliBot & ClawBot · GitHubScanner · GraphRAG · Glossary · Résumé · Home