I build LLM and computer-vision products end-to-end — with the guardrails, tests, and failure semantics that make them safe to actually run.
FastAPI · Next.js / React · PostgreSQL · Docker · TypeScript · 1,072 automated tests in my most recent project alone · 3 live demos below
| Project | One line | Stack | |
|---|---|---|---|
| APILoom | Plain-English REST procedures become reviewed, reusable runbooks with a full audit trail. | FastAPI · Next.js 16 · PostgreSQL · Docker | Code |
| CivicPulse | A bounded, human-reviewed AI workflow where a human correction never overwrites the model. | React 19 · FastAPI · PostgreSQL 18 · Railway | Live · Code |
| Pocket Arcade | Five original browser games, zero runtime assets, production-grade test suite. | TypeScript · Phaser 3 · Vercel · Supabase | Live · Code |
| NewsVerify | Multimodal claim assessment grounded in retrieved evidence the model cannot invent. | GPT-4o · LangChain · SerpApi · Streamlit | Code |
| AI Form Evaluator | Real-time exercise-form coaching from pose landmarks — raw video never leaves the browser. | MediaPipe · FastAPI WebSockets · React | Live · Code |
| Hybrid ML Scheduler | Six GPU/CPU scheduling strategies raced live against a brute-force optimal baseline. | Python · FastAPI · WebSockets · DQN / RF | Code |
Also on this profile: PostgreSQL-backed CLI systems, OS simulations, and C/C++ systems coursework.
Getting an LLM to write an API call is the easy part. The hard part is making that call reviewable before it fires, repeatable afterwards, and honest about what happened when it dies halfway through a mutation.
- Planning is separated from execution. OpenAI function calling is validated into a Pydantic workflow schema and saved as a runbook. Re-running it needs no further model call — execution is deterministic and inspectable.
- Mutations require explicit, one-use approval. The preview token is bound to the plan revision, the resolved-input digest, and the connection versions; changing any of them invalidates it.
- SSRF-aware destination policy. Private, loopback, link-local, and cloud-metadata addresses are denied and re-checked at connection time. Redirects aren't followed, and URL authorities can't be interpolated.
- Honest failure semantics. If a mutation is interrupted after transmission may have begun, the run records
UNKNOWN_OUTCOMEinstead of pretending it failed safely. - Built to be operated. Alembic migrations, startup reconciliation of stale runs, retention purge, health and readiness probes, and Fernet-encrypted credentials bound to an exact origin.
Engineering detail — CI gates, execution model, and the trust boundary
Quality gates in CI: ruff · mypy · pytest at ≥80% enforced coverage · bandit · pip-audit against hash-pinned dependency locks · frontend typecheck, lint, and npm audit. A deterministic mutation-deadline regression is designed to be run 50× in a row to prove it isn't flaky.
Execution model: dependencies must point backwards and form a valid acyclic plan; steps run sequentially with bounded requests and responses, total deadlines, cancellation, and idempotency-aware retries. Safe steps retry transient failures automatically; mutations retry only with an explicit idempotency key.
Trust boundary: self-hosted, single-user, loopback-bound by default, with PostgreSQL never published to the host. Sensitive header names and injected connection values are redacted in storage, and exports refuse to run when heuristics detect literal secrets. The scope is stated in the repo as an explicit non-goal list rather than left implied.
Two workflows on one persistent domain model, built around a single rule: the system records what the model said and what the human decided, separately, forever. All data is synthetic; no outbound sending of any kind exists.
- A five-stage bounded workflow: generate → AI critique → deterministic claim check → at most one revision → human review. The claim check is plain code, not a model — it flags numbers that never appeared in the brief.
- The workflow module never receives a database session. That makes "no transaction is held open across a provider call" a structural property of the architecture rather than a code-review convention. A run costs at most three provider calls no matter how many variants it produces.
- Human corrections are stored beside the model's label, never on top of it, so the dashboard shows classifier output and corrected output side by side — and disagreement between them stays measurable.
- Privacy enforced by import rules. No module under
app/surveymay importlangchainoropenai, because survey text shouldn't leave the machine. Survey responses carry no respondent ID — not even a pseudonymous one — so answers can't be linked across datasets. - 1,072 automated tests: 550 backend (pytest), 427 frontend (Vitest), 95 end-to-end (Playwright), plus eight hand-written Alembic migrations on a single head.
Engineering detail — a production bug found by measurement, not by testing
Rate limits were keyed on the unforgeable end of X-Forwarded-For. In production the limiter silently stopped binding, because the edge terminates TLS on a rotating fleet: 40 requests in 11 seconds all passed a limit of 30/minute. It was caught by measuring the live deployment, not by any test — and the fix and the reasoning are both written up in docs/DECISIONS.md.
Other decisions recorded there, mistakes included: the model never sees a UUID (it gets temporary references and identity is resolved from a stored snapshot); every Alembic revision is hand-written after autogenerate missed things more than once; versioned JSON contracts are frozen once written and dispatched on read rather than edited in place.
Deployed on deterministic stand-in providers, deliberately. A recruiter clicking the link sees the same thing every time, an unauthenticated demo can't spend money on a stranger's request, and nothing anyone types is shipped to a third party. Startup fails closed if public demo mode is ever configured with a live provider.
A games project judged as production software: deterministic logic engines, a real leaderboard backend, and a validation pipeline that catches visual regressions pixel by pixel.
- The architecture boundary is enforced, not requested. Pure deterministic
*Logic.tsengines are fully separated from Phaser renderers by a custom import-boundary check that runs in CI. - Zero runtime assets — no images, sprites, fonts, or audio files ship. Every visual is procedural and all audio is synthesized with WebAudio.
- A real backend, not a mock. The global leaderboard runs on Vercel Functions and Supabase Postgres with server-side name validation, profanity and reserved-name filtering, score-plausibility checks, salted-IP rate limiting, and a service-role key that never reaches the client.
- Validation across four layers: Vitest logic tests, Playwright desktop and mobile e2e, pixel-signature canvas regression tests for rendering drift, plus lint, typecheck, and a secret-grep safety check.
- Product polish that survives contact with real devices: responsive touch controls, a mobile no-scroll layout check in CI, dark/light themes, and reduced-motion support.
Games: Neon Serpent · Bounce Circuit · Star Courier · Lane Rush · Circuit Stack
NewsVerify — multimodal fact-checking assistant
Accepts an image and an optional claim, uses GPT-4o for visual analysis, generates three distinct verification queries, runs them concurrently through SerpApi, and returns a typed likely supported / likely misleading / inconclusive assessment for human review.
The design decision worth reading: evidence is addressed by ID. Retrieved results are normalized, deduplicated, capped, and labelled E001…; the model may only cite those IDs, and the application resolves them back to URLs the search API actually returned. A hallucinated link cannot reach the UI. Reverse-image search is off by default because enabling it publishes the image publicly — an opt-in, not a default.
Python LangChain GPT-4o Pydantic structured output asyncio Streamlit SerpApi
AI Form Evaluator — real-time exercise coaching from pose landmarks
MediaPipe Pose runs in the browser; only landmark coordinates cross the WebSocket, so raw video never reaches the server. A FastAPI service scores them with explicit joint-angle state machines, and the dashboard overlays the skeleton and live angle so every cue can be traced back to the signal that produced it.
Engineering choices that matter: form faults must persist five consecutive frames before being reported, and a sustained fault counts as one incident rather than hundreds. Backpressure exists on both ends — the client keeps one request outstanding, the server drains its queue and processes only the newest landmark frame while preserving control messages in order. Coordinates are normalized against torso length, so feedback doesn't drift with the user's distance from the camera.
Python FastAPI WebSockets MediaPipe React NumPy Web Speech API
Hybrid ML Scheduler — six scheduling strategies, raced live
A simulation framework for heterogeneous GPU/CPU task scheduling. Every generated task is scheduled by all six strategies simultaneously — Round Robin, Random, Greedy, a Random Forest hybrid, a DQN agent, and a brute-force Oracle that grid-searches the optimal GPU fraction.
That Oracle is the point: the learned schedulers are scored against the actual optimum, not just against each other. Results stream to a React dashboard over WebSockets with time, energy, and cost metrics, and the hybrid model retrains on accumulated execution history every 50 tasks.
Python FastAPI WebSockets scikit-learn Deep Q-Learning React
- Boundaries enforced by tooling, not by discipline. An import-boundary check keeps game logic out of the renderer. A workflow module that structurally cannot hold a database session can't hold one open across a network call. A survey package forbidden from importing LLM clients can't leak survey text. Rules a linter enforces survive; rules in a style guide don't.
- Tests are the definition of done. 1,072 across CivicPulse; four validation layers on Pocket Arcade; enforced coverage floors, static typing, dependency auditing, and secret scanning wired into CI rather than run by hand.
- Systems should be honest about failure.
UNKNOWN_OUTCOMEwhen a mutation may have landed. Analysis that clears every label rather than leaving half a dataset classified. A verdict ofinconclusivewhen the evidence doesn't support one. - Decisions get written down, including the wrong ones. My repos document why things are built the way they are, what the limitations are, and which bugs only showed up in production.
- AI tooling is leverage, not an excuse. I use it heavily and I don't treat generated code as done until it builds, passes tests, and survives real use.
- I want the whole path: idea → system design → implementation → validation → deployment → the boring reliability work that makes it usable.
Roles where I own a product from system design through to the deployment that other people depend on:
AI Engineer · Software Engineer, AI Products · Machine Learning Engineer · Backend / Full-Stack Engineer · Applied Computer Vision Engineer · Research / Prototype Engineer
I'm most interested in teams building with LLMs and agents, computer vision, developer tools, data-heavy products, or the infrastructure underneath AI workflows — especially where correctness, auditability, and safe failure actually matter.
Open to opportunities — the fastest way to see how I work is to try CivicPulse or read the APILoom source.





