Skip to main content

Selected work

Portfolio.

Five things that exist, run, and can be inspected. Two are public repositories; one is a security boundary I use every day; one is the infrastructure serving this page; one is a practice rather than a project.

01

HomeTube

Open-source self-hosted media service

300,000+ Docker image pulls · ~1,400 GitHub stars

Proof by adoption: a personal project that became a product real people depend on.

A self-hosted media download and management service — a web app shipped as a single Docker image — that acquires media from more than 1,800 platforms at the best available quality, ad-free, and delivers it properly named and organised into a clean, self-hosted media library.

An automatic quality-selection strategy you can override, processing (clipping, subtitles, format conversion), and whole-playlist synchronisation with resilient state tracking, so a local library stays aligned with its sources over time.

Because it is used at scale by people I have never met, every release carries real constraints: bug reports, edge cases across 1,800+ source platforms, and backwards compatibility. That is the part that does not show up in a side project — and the part that makes it worth citing.

HomeTube's next generation now lives inside Content, as one of its web interfaces. The standalone project remains separate and maintained.

PythonDockeryt-dlpffmpegRelease engineering

GitHub →

02

Content

Self-hosted content engine — one core, eight surfaces

Public monorepo · AGPL-3.0 · sole maintainer · released August 2026

The infrastructure that agents consume — not another wrapper around someone else's API.

A self-hosted engine that turns a supported source — a URL, a file, plain text — into media, subtitles, transcripts, summaries, translations, chapters, thumbnails, metadata, Markdown and PDF. The public request describes what you want to obtain, never the technical operations to perform.

The backend analyses the source, resolves the capabilities actually available, builds a deterministic execution plan and runs it as an observable job — states, ordered events, progress, logs, provenance, cancellation, resumption — then delivers properly named files into a media library. yt-dlp, ffmpeg, Whisper, the LLMs and the PDF renderer stay replaceable implementation details behind dedicated boundaries.

AI capabilities are optional and local by default: summaries, translations and derived chapters run through a local Ollama, or an explicitly configured Anthropic/OpenAI key; transcription through a local Whisper runner. A missing runner does not make the service unhealthy — the capability is simply declared unavailable, and an impossible request is refused before the job starts.

The interesting problems were architectural: keeping one core usable by a human, by another developer and by an agent, without letting three implementations diverge.

One core, eight surfaces

Backend engine
Domain model and application architecture — intent, execution plan, job and artifact stay distinct objects. SQLite as the source of truth, embedded worker.
REST API + OpenAPI
API design, and one public contract shared by every other surface.
content-sdk — typed Python SDK
Designing for other developers: typing, ergonomics, packaging, versioning. Sync and async, published on PyPI.
content-cli
The operator surface: terminal, scripts, cron, request files. Distribution and release engineering.
content-mcp — MCP server
Intent-level tools for agents (analyse, generate, track, cancel, fetch artifacts) with no shell access and no backend internals exposed. Running in production on the homelab.
Chromium extension
A user surface inside a constrained execution context — a JavaScript client of the same API.
Three web interfaces
Content Studio for general workflows, HomeTube for the YouTube path, Content Console for health, configuration, storage, jobs, events and logs — an operations console, not a demo UI.
Multi-container Docker stack
Single-host deployment a third party can actually install: one compose file, updates by pull. amd64 and arm64, no Redis, Celery or Kubernetes required.
PythonFastAPIMCPSQLiteDocker ComposeOllamaWhisperGitHub ActionsPyPI

GitHub → content-sdk → content-cli → content-mcp →

03

Agent-to-corpus gateway

A security boundary for agentic systems

In daily production use since July 2026 · personal system

How do you give a remote AI agent useful access to a private corpus — read and write — without giving it a shell, without exposing the tree, and without a compromised client being able to exfiltrate anything beyond what it asked for?

A single entry point: an SSH forced command. Whatever the client sends, the only thing executed is the gateway — no shell, no file transfer, no forwarding possible with those keys.

Behind it, an LLM gatekeeper decides on its own where to look and what leaves. The client never supplies a path; a path that is sent is ignored by design. A routing map shared with clients describes what kind of information lives in each domain — never file names, never amounts, never identifiers.

A written, versioned contract governs the exchange: free-text requests, stable response statuses, a JSON mode, additive writes applied automatically, and mutating or destructive writes gated behind confirmation. Per-client delegated identity — one dedicated SSH key per calling agent — and a timestamped audit log for every request, with duration and return code.

The number that describes the object: 224 lines of code for 582 lines of specification — protocol, client onboarding, gatekeeper rules, routing map. That ratio does not describe a script. It describes a protocol.

It is a personal system, not a product sold or deployed at a third party. Its value is that it runs daily, it is specified, it is audited — and it answers a question the industry is only now starting to formalise, under headings like agent privilege escalation and tool security.

SSH forced commandsLLM gatekeeperVersioned contractDelegated identityAudit logging

04

The homelab

30+ production services, designed and operated solo

Two physical servers · Proxmox · Docker under LXC · 24 GB GPU

The training ground that backs every production and DevOps claim on this site — including the server that sends you this page.

Two physical machines: an Intel NUC as the compact, always-on node, and a custom AMD build with a Radeon RX 7900 XTX (24 GB) on an open frame, for heavy virtualisation and local AI inference. Proxmox is the virtualisation layer; workloads are split between VMs, LXC containers and Docker according to what they need. Systems and volumes are encrypted, with the TPM in the protection chain.

The network is deliberate: a 5G router with its own SIM — chosen so that nothing here depends on a fixed-line ISP — and an architecture designed to work behind CGNAT with no public IP and no port forwarding. Private access over Tailscale and WireGuard, explicit per-service publication through Cloudflare Tunnel, reverse proxies, a personal DNS with recursive resolution. Private by default; exposed one service at a time.

By domain: networking and remote access, reverse proxying and TLS, S3-compatible object storage, a self-hosted Git forge with CI, encrypted and tested backups, monitoring and alerting, automation and orchestration, local GPU inference. Most of the building blocks of an enterprise platform, in miniature and without a safety net.

Local models are served with Ollama on the 24 GB card — a 30 B model quantised to Q4_K_M occupies 18 GB, which leaves about 6 GB for the KV cache and the context window. That is where the quantisation / context-length / latency trade-off stops being theoretical. A local embedding model means the RAG stack depends on no external API at all. Being on AMD rather than NVIDIA is a deliberate difficulty: ROCm and Vulkan instead of the default CUDA path, so the inference stack has to be understood rather than followed.

ProxmoxLXCDockerLinuxTailscale / WireGuardCloudflare TunnelNginx / TraefikForgejo CIResticMinIOOllamaROCm

How this site is served →

05

AI-assisted engineering

Daily practice, not commentary

Coding agents · local LLMs · agent memory

In 2026 the difference is no longer knowing what an agent is. It is having operated both sides of the managed-API-versus-self-hosted question.

Coding agents as real working tools on real codebases — exploration, multi-file changes, refactors, test writing, Git, the shell, browser automation, MCP servers, context management and diff review. Claude Code and Codex daily, OpenCode as another agentic environment.

The discipline that goes with it: agents accelerate exploration, implementation and maintenance; they replace neither architecture, nor tests, nor review, nor human responsibility. Final quality is still judged in Git, by the tests, and by how the system behaves in production.

Local and self-hosted inference on my own GPU server, which is what makes the judgement calls concrete — model and quantisation choice, VRAM against latency against quality, serving, and the privacy-first cases where data cannot leave.

Anticipating the failure modes that belong to agents specifically: context drift, loops, wrong tool calls, and costs that run away.

Claude CodeCodexOpenCodeMCPOllamaWhisper

Also

In progress, and off to the side

Ambient intelligent assistant
An always-listening assistant, in development. Part of ongoing work on persistent agents and long-term memory: keeping, retrieving and reusing context beyond a single interaction.
Trading research & market automation
Personal trading research projects built with Freqtrade, VectorBT and PostgreSQL — strategy backtesting and exchange API integration (Coinbase, Kraken). Python price-tracking bots operated continuously. Deliberately stopped short of unsupervised live trading.
Upstream open-source contributions
Contributed to third-party projects across IoT, home automation and graphics-card configuration — including the AMD vendor reset work that makes a Radeon resettable under virtualisation passthrough.