AI agents, automation, assistants, and full-stack applications. I work from a simple question: what would make this process genuinely better? Then I build a version people can try.
I've spent years in procurement and business operations, including roles at TeamViewer, Scout24, Foodpanda, and Delivery Hero. That experience gives me plenty of problems worth solving, but my projects go beyond procurement.
What you'll find here: working prototypes, deployed tools, engineering decisions, safeguards, tests, and the occasional experiment that didn't work. I care about what a system does in practice, not just what the demo promises.
At The AI Build Journal, I share what I'm building, why I made certain decisions, what broke, and what improved. Expect real projects and honest lessons from building with AI.
| Project | Why it exists |
|---|---|
| ICARUS · private | A context-aware personal AI assistant with tools, private retrieval, and clear trust boundaries. |
| SCM-Master | A supply chain platform that connects purchasing, inventory, and asset lifecycle decisions. |
| Negotiation Agent | An AI-assisted negotiation tool where deterministic code controls the commercial decisions. |
| TrueSpend | A procurement operations system built around guided workflows and controlled automation. |
More builds, technical notes, demos, and case studies below.
Every build below started from a real pain I lived running procurement teams. The table is the short version: what it solves, what it does, what came out, and what it runs on. Engineering depth folds open underneath.
| Project | What it solves | What it does | Result | Built with |
|---|---|---|---|---|
| 🔁 Restwert Engine | A Device-as-a-Service provider rents devices out on a guessed residual value: nobody can say per serial number whether a device earned money over its whole cycle, or which lever (channel, contract term, repair, purchase discount) lost it. | A residual-value and asset-P&L engine that closes the device loop per serial from purchase order to credit note, calibrates residual value on public used-market evidence, and puts seven levers next to it, each with a threshold and the role that owns it. | 429 public price points on 123 models, 361 of them strictly matched to a priced variant, turned into realisation curves by family and manufacturer; a 5,000-serial simulation over 19 data feeds with a 95 % complete timestamp chain; per-device TCO (support and MDM operations allocated per billed month, flagged as estimates) and "costs recovered from month X" for every contract term; a forecast-quality tab measuring the forecast of record against realised prices, business view and channel-adjusted model view; 413 tests. | Python · DuckDB · pandas · pydantic · Streamlit · plotly |
| 🧠 TrueSpend static demo |
Category managers drown in manual PR triage, scattered approvals across IT/legal/controlling, and spend nobody can see end-to-end. | An AI-native procurement OS running the full purchase-to-invoice lifecycle autonomously. | 17 autonomous workflows, intake→invoice hands-off, role-gated ops board, money-moves locked at the database layer. | n8n · Claude Sonnet 4.6 · PostgreSQL · React |
| 📦 SCM-Master demo |
Procurement, warehouse flow, and asset lifecycle live in disconnected systems: no single source of truth, no safe automation. | An AI-native supply-chain OS unifying all three, with a purchasing run (on request, a dry run by default) that a human still gates. | Auto-places demand-justified POs only at ≥0.90 confidence & <€200k (the threshold adjusts per supplier by up to 0.10 from approval feedback), proven by a 29-scenario agent-safety harness; twin-deployed, the demo runs a device-rental fleet. | FastAPI · SQLAlchemy 2.0 · Postgres · statsforecast · LightGBM |
| 📈 SCM Power BI Cockpit static dashboard |
A non-technical CEO asks "should we invest in AI demand forecasting?" and gets hype, not a decision. | A consulting-case cockpit: live 7-tab web dashboard + Power BI report on the same data, backed by cited research. | A defensible invest / wait / pilot recommendation with WMAPE/Bias accuracy, should-cost & TCO, and a phased plan. | Node · Chart.js · Power BI · DAX |
| 🔴 SpendLens static demo |
Raw vendor spend arrives as messy CSVs: no classification, no compliance view, no supplier intelligence a category manager can act on. | A 5-stage AI pipeline: map → clean → classify vendors → flag compliance → persist and profile suppliers, across 7 decision screens. | Upload a spend file, get a classified, compliance-scored view with on-demand due diligence through Hades; the public demo runs on demo data. | React 18 · FastAPI · SQLite |
| 🤝 Negotiation Agent static demo |
Supplier negotiations anchor on price alone, so buyers leave win-win trades on the table, and nobody trusts an LLM to hold a walk-away line. | A negotiation tool where the LLM has zero authority: a deterministic engine makes every accept/counter/walk-away call and fixes the exact figures a reply may state. A violating draft is rejected and redrafted server-side before it's ever sent. In the full version Opus writes the buyer's emails; the public demo runs the same engine with template emails, and you play either seat. Walk in prepared: upload a contract → it extracts the deal (price, expiry, licenses, NDA/DPA), researches the supplier via my Hades agent, and proposes how the intelligence should shape the mandate (give/hold/hedge) via a deterministic rule table a human approves, so the LLM never edits the envelope. | At matched buyer cost, logrolling leaves the supplier +0.27 utility vs splitting the difference, a runnable eval. 823 tests that need no API key, mypy --strict on the core, ran live on Railway until November 2026. | Claude Opus + Haiku · FastAPI · mypy strict |
| ☠️ Hades static demo |
Supplier due-diligence (sanctions, registry, ESG, LkSG/CSDDD) takes analysts 1–2 days per supplier. | An autonomous agent that screens a company across 6 risk sources in parallel and returns a scored verdict. | Full risk report in under 2 minutes: sanctions/registry/ESG/news → risk score 1–10 + Approve/Block. | LangGraph · Claude · OFAC/UN + NorthData |
| 🔍 Hermes static demo |
Supplier market intelligence arrives too late: you learn a key supplier is in trouble after it hurts you. | A continuous market-intelligence agent watching a curated supplier list across 5 signal sources. | Tracks 56 AI suppliers across 8 categories, classifies + delta-tracks signals, feeds DD and the CM on demand. | Claude Haiku · Upstash Vector · 5 crawlers |
| 🏗 Triage Agent | Purchase requests pile up waiting for a human to route, compliance-check, and chase suppliers. | An autonomous agent that triages every PR: value-routes, checks NDA/DPA/MSA via RAG, runs RFQ/RFP outreach, recommends an award. | 6 importable n8n workflows replacing the manual triage queue end-to-end. | n8n · RAG |
| 📊 Marketing Spend Stats | A $500K budget is split across 7 channels on gut feel, not evidence. | A full statistical pipeline testing every channel-pair for real CPA difference. | All 14 CPA pairs significant post-FDR → an executive memo with a data-backed reallocation. | Python · Welch t-tests · BH-FDR · bootstrap |
| 🧪 LLM Eval Lab | "Is this AI answer actually good enough to trust for procurement compliance Q&A?", usually answered by vibes. | A LangSmith evaluation lab that scores answers objectively and A/B-compares models. | LLM-as-judge on correctness + completeness over a custom dataset; gpt-4o-mini vs gpt-4o compared on real numbers. | LangSmith · gpt-4o-mini vs gpt-4o |
🔧 Engineering depth (click to expand, for technical reviewers)
- Restwert Engine: four-layer lake: raw landing files per feed and delivery period → typed bronze with sha256-deduplicated deliveries and an
unresolvedtable (a key that does not resolve is recorded, never guessed) → a silver device ledger with one booking line per cost and revenue, each carrying its source reference and anis_estimateflag → gold KPIs, cohorts and levers. Public market module: 429 used-price anchors and launch RRPs with URL and date per row, log-linear realisation curves per family and manufacturer with condition-grade offsets, strict variant matching (storage and RAM, no base-variant fallback), fit quality gated (at least 12 anchors over 6 months of model age). Simulation: 5,000 serials bought by model generation, four contract terms with a term-dependent rate factor, four resale channels with fee tables, early terminations, price-protection windows; the forecaster learns residual ratios per model and grade on a 120-month grid and flags every clipped value. Seven levers (channel choice, purchase discount, condition and repair, ageing, contract term, price protection, manufacturer mix), each computed per serial as actual minus a named reference from the provider's own fleet, with threshold, owner and decision rule; additivity against the ledger is checked on every run. Governance: the model advises, deterministic code decides, a named role owns every threshold; every placeholder is labelled in config with its owner; no figure in any page is typed, every number in a sentence comes from the generator. Built with a multi-agent workflow: read-only web researchers (URL and date per row), adversarial verifiers sampling claims, and a wording critique in three roles (head of procurement, CFO, recommerce) before anything ships; that critique caught two invented example figures the drafting model had rounded into the text. - TrueSpend: n8n + Claude Sonnet 4.6 + PostgreSQL + React. 17 autonomous workflows, 32-table schema (dbmate-migrated), full P2I lifecycle. Role-gated Operations Board (procurement, IT, controlling, legal, admin). 4-agent compliance onboarding, DocuSign JWT Grant, Grafana, Jira ≥€100k. DB-enforced security: NOSUPERUSER PostgREST role, every status transition through SECURITY DEFINER RPCs,
PATCH tickets.status→ 403 at the database layer. Agent security: inbound email/invoices run under "the LLM advises, deterministic code decides", model output is schema-validated against an action allowlist, ticket/PO ids are derived deterministically (never from the model), and a prompt-injection repro proves the guard inert; money RPCs gated fail-closed off the browser token. - SpendLens: React 18 SPA + FastAPI. 5-stage AI pipeline persisted to per-client SQLite (immutable raw layer, hash-dedup, recomputable enrichment). 7 screens; Supplier DD calls Hades, and Hermes signals arrive through the Icarus scan. Security controls in the code: opt-in shared-secret API auth (constant-time compare, docs disabled once a key is set), configurable CORS + full security-header/CSP middleware, chunked upload guards (type allowlist + 25 MB cap), per-IP rate limiting on Anthropic-billed endpoints, Pydantic-validated Hades proxy (SSRF surface closed), generic client errors with server-side logging. Production-polished frontend: React production builds + SRI, pre-paint theme, keyboard-accessible shell (focus-visible, ARIA), reduced-motion, error boundary that keeps the shell alive on crashes. The public demo runs without the key, so its API docs and CORS are open there.
- Negotiation Agent: a procurement negotiator (ran live on Railway until November 2026; static demos) where the LLM has zero authority. A stateless FastAPI backend runs the real deterministic engine: buyer utility
U = Σ wᵢ·vᵢ(xᵢ), acceptance on a Boulware concession curve ((t/T)^β, β>1, a spec bug I caught: the original exponent was inverted, conceding early and surrendering leverage), counteroffers via logrolling (an exact fractional-knapsack LP that concedes on terms the buyer weights lightly but the supplier values highly, not a price split). The guard is mechanical, not a prompt: each turn the server folds the transcript, the engine decides, Opus drafts the buyer's email, and a guard-with-redraft loop rejects any figure the engine didn't approve, a violating draft is redrafted (or replaced by a deterministic template) before it's ever sent. Opus drafts the buyer in the full version, Haiku runs the contract risk scan; HMAC-signed mandates; you play either seat. Contract intelligence, the "prepared buyer": upload a contract → deep extraction (price, expiry, licenses, NDA/DPA, units) + supplier due-diligence via my Hades agent → a deterministic finding→mandate transform (21 tests, including an exhaustive check over all 32 subsets) that proposes bounded, reversible envelope adjustments tagged give/hold/hedge, the LLM extracts and observes, a pure-Python rule table maps findings to deltas, and a human approves the diff before signing. That's "LLM advises, code decides" extended to mandate construction: the model's confidence is collapsed to a discrete assurance in Python first, so it never becomes a mandate number. Proven +0.27 supplier utility at matched buyer cost (runnableneg-sim --baseline); headless agent-vs-agent simulator with hidden-preference personas and bit-identical audit replay. 823 tests · mypy --strict on the core (CI matrix ubuntu+windows × py3.11–3.13). Architecture designed with Claude Fable 5; full design indocs/. - Hades: POST a company name → 6 parallel LangGraph nodes: OFAC/UN sanctions, NorthData registry, LkSG/CSDDD signals, ESG, news sentiment, Hermes intel. Risk score 1–10 + Approve/Block recommendation, in under 2 minutes.
- Hermes: 5 crawlers (RSS, EDGAR, Tavily, Jobs, Earnings) over a curated 56-supplier / 8-category watchlist, architecture scales to hundreds. Signals classified by Claude Haiku with delta tracking. Semantic RAG via Upstash Vector.
- SCM-Master: FastAPI + SQLAlchemy 2.0 + Pydantic 2 (SQLite→Postgres), JWT role-gating, 54 test modules · ≥80% coverage gate in CI, 5-job CI (lint · Postgres · SAST · CVE-audit · agent-safety). Multi-sourcing core:
Productdecoupled fromProductSupplier, re-sourcing a line is one FK repoint. Serial-trackedAssettraced RECEIVED→…→DISPOSED with an unbroken link to its PO line. Purchasing run (on request, a dry run by default) under "the LLM advises, deterministic code decides", the confidence score is itself deterministic and audited, gating auto-place at ≥0.90 & <€200k, proven by a 29-scenario agent-safety harness (unapproved supplier, over-cap spend, prompt injection, poisoned calibration → refuses every time). Inventory science: Syntetos–Boylan demand classifier → Nixtlastatsforecast(Croston/SBA) with conformal prediction-interval safety stock, chosen over a hand-rolled TSB on a walk-forward benchmark (an opt-in engine; the default is the built-in forecaster); service-level safety stock (z × σ) + ABC class service levels. Learning layer: rule-based calibration from human approve/reject, with a LightGBM + SHAP calibrator in shadow mode, advisory, logged, never deciding. Twin-deployed (self-wiring public demo + forge-locked production that refuses to seed/ship demo accounts/run on non-persistent storage, regression-tested). Cost-intelligence: clean-sheet should-cost engine (commodity-indexed → defensible cost floor + target price, DRAM/NAND sensitivity) + per-asset TCO rolling up to TSCMC %, deterministic engines, the LLM only proposes. - SCM Power BI Cockpit: synthetic-data generator → 7 internally-consistent CSVs feed a live auto-refreshing 7-tab web cockpit (Node + Chart.js) and a Power BI report on the same live API, DAX anchored to SCOR DS, forecast accuracy (WMAPE / Bias / RMSE), should-cost & TCO. Backed by cited research (Stanford AI Index, McKinsey, chip-geopolitics) driving a hype-vs-evidence invest/wait/pilot call with a phased plan + cost/timeline.
- Triage Agent: 5-tier value routing, supplier NDA/DPA/MSA compliance check via RAG, RFQ/RFP generation, multi-supplier outreach, evaluation matrix, award recommendation. 6 importable n8n workflows.
- Marketing Spend Stats: Welch t-tests, Bonferroni + BH-FDR correction, bootstrap CIs, Cohen's d across 7 channels / 14 CPA pairs.
- LLM Eval Lab: LangSmith, custom 20-example dataset, LLM-as-judge correctness + completeness evaluators, A/B (gpt-4o-mini vs gpt-4o).
📋 Case study: Restwert Engine, residual value for Device-as-a-Service, framed as a client engagement
How I'd scope, build, and hand off this solution for a procurement client, the way I'd run it as a consultant, not just a repo.
The problem. A Device-as-a-Service provider buys a phone or laptop, rents it out for 12 to 48 months, takes it back and sells it. The rent is priced on an assumed residual value, but the residual is only known at the very end, per serial number, after refurbishment, channel fees and the days the device sat in stock. Most providers see the margin per contract, not per device, and cannot say which lever lost the money: the channel, the term, a repair that was not worth it, a purchase discount below the fleet's own benchmark.
The approach. Two sources of truth, never mixed. Public evidence first: launch RRPs and used-market prices per model, condition and age, every row with a URL and a date, turned into realisation curves; that is the plausibility check a provider's own forecast has to survive. Then the provider's own data: one ledger line per booking per serial, from purchase order to credit note, with source reference and an estimate flag, so every number on every page can be traced to a row in a landing file. The rule throughout: the model advises, deterministic code decides, a named role owns every threshold.
What I built. A data lake over 19 feeds (ERP, warehouse, customer portal, service desk, returns, refurbisher, recommerce, finance) with a strict ingest (deduplicated deliveries, an unresolved table that never guesses a key, a per-serial timestamp chain), a device ledger, a residual-value forecaster, and seven levers with owners and decision rules. On top: one page with nine tabs (report, device, realisation, forecast quality, TCO, cycle, levers, term, data) for a head of procurement, from realisation curves per model series to a per-device calculator that says from which month a contract has recovered its costs. Since no provider's data is public, a 5,000-serial simulation drawn from the real catalogue stands in; every assumption is a labelled placeholder with an owner, and the feeds are the interface a client's data lake plugs into.
The result. 429 public price points on 123 models; a closed device loop with a 95 % complete timestamp chain; per-device TCO split into purchase price and cost to sale; a term analysis that shows the first year of a device's life is the expensive one, so short contracts must carry a higher monthly rate or a second rental cycle; early terminations losing money at every term, which points at the customer contract, not the price list.
What I'd do for a client. Connect only the agreed feeds, read-only, on a delivery schedule → replace the placeholders with the provider's own thresholds, owner by owner → run the ledger against one quarter of real history and reconcile it with finance → then let the seven levers name where the money sits, with a role next to each.
See it: Restwert Engine repo and the live page (nine tabs, German, synthetic fleet on a public catalogue)
📋 Case study: Hades, supplier due-diligence, framed as a client engagement
How I'd scope, build, and hand off this solution for a procurement client, the way I'd run it as a consultant, not just a repo.
The problem. Under Germany's LkSG (and the incoming EU CSDDD), every material supplier must be screened for sanctions, ownership, ESG, and human-rights risk, and re-screened on change. In most teams this is a 1–2 day manual analyst task per supplier: pull the registry, check OFAC/UN lists, scan news, cross-reference ESG databases, write it up. It doesn't scale, it's inconsistent between analysts, and it's the exact task that stalls onboarding.
The approach. I treated it as a decision-support problem, not a chatbot. The rule throughout: the LLM advises, deterministic code decides. Sanctions matching runs in deterministic code before the model sees anything, a hit forces Block, no LLM discretion. The model only summarizes and scores what verified sources return, so the output is defensible to an auditor.
What I built. An agent that takes a company name and runs 6 research pipelines in parallel, sanctions (OFAC SDN + UN), registry (NorthData), LkSG/CSDDD signals, ESG/labour, 90-day news sentiment, and live market intelligence, then returns a 1–10 risk score with an Approve / Conditional / Block recommendation and a plain-language executive summary, with a persistent audit trail.
The result. A 1–2 day analyst task becomes a sub-2-minute, consistent, audit-trailed report, the same rubric every time, defensible to compliance, and wired into the wider spend platform so a flagged supplier is caught at onboarding, not after.
What I'd do for a client. Scope their actual supplier master and risk appetite → map the screening rubric to their LkSG/CSDDD obligations → pilot on one high-risk category → tune the risk weights with their compliance team → integrate to their P2P so screening is a gate, not an afterthought. The tech is done; the engagement is the fit.
See it: Hades repo
Systems I run day to day, for myself and for a team. The table is the short version: what it solves, what it does, and how far along it actually is. Engineering depth folds open below.
| Project | What it solves | What it does | Where it stands | Built with |
|---|---|---|---|---|
| ⚡ Pantheon OS trading orchestrator |
A single decision needs a chain of checks: signal, sanctions, market regime, position size, order, stop-loss. Run by hand, they happen late or not at all. | Eight specialist agents run that chain every 15 minutes while NYSE or XETRA is open. Agents climb a seniority ladder only by counted winning trades, real money needs three switches at once, and a drawdown switch can stop all of it. | Offline: ran on Hetzner against an IBKR paper account; the last macro snapshot is from 17 June 2026 and the TLS certificate expired on 21 August. Code and 485 offline tests are current. | Claude Sonnet 4.6 · Supabase · Grafana · IBKR |
| 🤖 ICARUS personal assistant · private |
A general assistant knows nothing about my actual week. The one that would know it wants my private notes in someone else's cloud. | Talk or type from the phone: it reads mail and calendar, drafts, searches the web, reads documents, logs spend, and answers out of my private notes, whose search index never leaves hardware I own. | Live with 32 tools, a count the app reports about itself rather than one kept by hand here. | Claude Sonnet 5 / Haiku 4.5 · FastAPI · PWA · MCP |
| 🌱 VGrow Cockpit grow-box control and local AI · private |
A grow box reports numbers, but somebody still has to connect temperature, humidity, light, watering and camera observations to the next action. | One cockpit for live readings, history, two cameras, plant records, a wiki and community feed. Hank, a local AI assistant, explains the state; deterministic controllers handle climate, light schedules and timed watering. A water-demand check can skip or shorten a scheduled run. | Running on my Mac with a real VGrow. Light and watering timers are built but currently disabled pending setup and calibration. 2,605 tests passed on 27 October 2026; forecasts and camera estimates keep their uncertainty visible. | Python · aiohttp · MQTT · Ollama · SQLite |
| 🧠 Self-Improving Knowledge System my own second brain · private |
Notes pile up faster than they get understood. The archive grows, the thinking does not. | Pulls sources in on its own, then refuses to file any of it until it has been rewritten in my own words and linked to two notes that already exist. A local model drafted overnight into quarantine and could earn more freedom only after seven good runs in a row; it never earned it, so it is paused. | Runs nightly on hardware I own. Search costs nothing per query, because the embeddings are local. | Python · MCP · local models · SQLite |
| 🗂️ ONE TEAM, ONE DREAM shared brain, six-person team · private |
Six people, six separate streams of information. The process stops at every handover while somebody digs the context back out of their own inbox, and when one of them leaves, the team loses that stream for good. | Combines the information streams of the whole team into one place, so the process runs end to end instead of stopping at each desk. The same idea as above, ported from one person to six, where everyone is a user and nobody touches a repo. Each person keeps their own raw notes and sees their own numbers first. The lead sees what needs support, not a ranking. Leadership sees progress and where the projects stand. Nothing is ever deleted or overwritten, only added to. | MVP runs and is measured: 89 self-test checks plus 58 unit tests green, all 36 role/view pairs served. Not deployed: a works agreement and a recording-consent decision come first. | Python · FastAPI · Markdown + git · MCP |
| 📌 Aushang paper notice board → private feed · private |
Kitas, clubs and parishes still inform by paper pinned to a wall. Whoever walks past knows; everyone else misses the closure day. Every digital fix so far asks them to change how they work. | One admin photographs the board. Text is read and personal data masked locally, the model sees only redacted text and proposes type and dates, and nothing reaches members until a human confirms. Members get a private feed, a calendar, documents and push, in German, English or Russian. | Live since June 2026 at a Kita. Web and PWA in use; Android and iOS built from the same codebase and held behind a go-live gate (isolation test, device performance, provider) before any store release. | Next.js 16 · Supabase EU · Python · Presidio · Claude Haiku 4.5 · Capacitor |
🔧 Engineering depth (click to expand, for technical reviewers)
- ⚡ Pantheon OS, Autonomous Trading Orchestrator: 8-agent system, scheduled every 15 minutes while NYSE or XETRA is open (offline since summer 2026). ZEUS orchestrates: Icarus (Hermes signal watcher) → Hades (OFAC blocklist and ESG screen) → Artemis (VIX + macro regime) → Pythia (Kelly-inspired position sizing) → Zeus (Claude Sonnet 4.6 reasoning + ChromaDB KB) → Ares (IBKR bracket orders: entry + 3% SL + 6% TP) → Argus (drawdown kill switch, 5 to 8 % by equity stage). Apollo runs a research cycle on request: arXiv ingestion, earnings enrichment, self-improvement loop. Agent seniority from Trainee to Senior, by counted winning trades; real money needs the Senior tier, an explicit arm switch and paper mode off. Supabase + Grafana. GitHub
- 🤖 Icarus AI: Personal Operating System. An installable PWA with streamed replies (SSE) and web push, driven by an async Claude tool-use loop over a modular skill layer. 32 tools live in production, which is the number
/healthreports rather than a number maintained by hand in a README: Gmail and Calendar over IMAP/CalDAV, voice input (Whisper), multimodal document analysis, live web search, LinkedIn drafting behind an approval flow, GitHub issues and roadmap, maps and directions, shopping and expense logging, YouTube transcripts, and a report on its own token spend. It reaches my private knowledge vault over MCP: search and read, plus one deliberately narrow write that appends to a staging inbox instead of the note graph, so a capture cannot bypass the vault's own distill-gate. That whole vault layer is never registered unlessICARUS_BRAIN_MCP=1, which the cloud deployment never sets, so there is no code path from cloud to vault rather than a disabled one. Tool schemas are a cost centre, so I measured them: two agent modules worth 16 tools turned out to be 43% of all schema characters, paid on every single call including "what's the weather". They are unloaded from the preamble and reached instead through a direct panel that calls them with no model in the loop./healthalso returns the running commit and a SHA of the fully assembled system prompt, because from the user's seat an outdated instruction is indistinguishable from a missing feature. Sonnet 5 / Haiku 4.5 routing, per-session identity scoped to an owner id, prompt-injection hardening, history and sessions on Upstash Redis. The hardware underneath it, local retrieval plus two overnight local-model shifts, is the case study below. Private repo - 🌱 VGrow Cockpit, physical automation with local AI: Python/aiohttp on an always-on Mac, with a browser interface, local records and Ollama inference. VIVOSUN REST and AWS IoT MQTT still carry device reports and commands, so local inference does not mean offline device control. The model explains; code switches. Climate control, the humidifier rule, the daily light timer and the timed pump controller have separate ownership, manual overrides and reported acknowledgements. Pump stopping has its own watchdog, independent of sensor polling and model latency. The thirst index combines observations with an explicitly uncertain water-demand estimate: it may only skip or shorten an existing scheduled slot, never create or lengthen one. Automatic watering requires confirmed pump switching, jug-measured flow and a recent pot check; missed slots are never replayed. USB power is not evidence of water delivery. Two camera roles: GrowCam for the measuring-strip reference, Logitech C920 for HD detail; a camera estimate retains its source and never silently changes the plant's confirmed stage. Individual plant histories and completed records can be exported. Validated on 27 October 2026: 2,605 tests passed, zero failures in Hank's offline evaluation, 96 demo browser captures and eight live layouts. Private repo
- 🧠 Self-Improving Knowledge System: A Claude-native knowledge OS built on a quality-gated ingest→distill→maintain loop. Stateful sync skills pull data into an immutable raw layer; a "distill-gate" blocks anything from entering the connected note graph until it's synthesized and linked (≥2 edges), quality enforced at both ends. Capability layers: a portable harness that swaps the model behind Claude Code (cloud or local LLM via LM Studio, proven air-gapped) for private/offline work; a gated overnight local-LLM batch worker that drafts into quarantine and earns autonomy only after a 7-run quality streak (cloud model scores each run 1–10), paused in October 2026 because it never earned that autonomy; local hybrid retrieval (vector + BM25 + RRF fusion + reranker) exposed to the agent over MCP, with a custom graph-fusion re-ranker that folds the wiki-link graph into search scoring; a two-tier passive-memory pipeline; a self-maintenance pass that audits the graph for orphans, broken links, and drift; and agent-feed sync skills that let the brain ingest its own procurement agents on demand, pulling Hermes' supplier intelligence and Hades' due-diligence verdicts into a fact-gated wiki library, with structural prompt-injection defense (no web-fetch tool in the ingest session). 100% local embeddings, zero per-query cost. Python · PowerShell · MCP · LM Studio · sqlite-vec/FTS5 · git-versioned. Private repo
- 🗂️ ONE TEAM, ONE DREAM, a Shared Brain for a Six-Person Operations Team: The same knowledge OS as above, ported from one person to a team, and the port is where the interesting decisions are. The team are users of a finished tool, not contributors to a repo, so there is no merge, no write conflict, and no git command for anyone in the department; every entry passes a single validating write layer that checks schema, label, links and provenance and otherwise rejects at the door with a message saying what is missing, so the store cannot reach an unclean state and there is no cleanup project in month four. That layer knows no delete and no replace, only append and annotate. Privacy is a construction, not a policy: raw call transcripts stay in the person's own zone and never enter the shared store, only the distilled finding crosses into the three entity databases, which means the honest consequence is that nobody can verify a finding against its source, so verification is replaced by attribution plus machine-run contradiction sweeps across the front-matter. The schema lives in YAML, not in code (validator, reports and queries all read one file), because the field catalogue is expected to grow: a new field is one line, not a change in three places, and the self-test proves it by adding a field to the YAML and asserting it is accepted with no code touched. Front-matter holds master data, the body holds append-only events (spend, QBRs, stakeholder feedback, incidents, document pointers), because a yearly figure written into a field and overwritten next year has silently deleted the trend. Queries run in three tiers and only the third costs anything: filter over front-matter and local full-text plus link-graph both cost zero, the model is called only when neither can answer, on the grounds that token cost is three quarters a data-modelling problem, not a retrieval problem. Metrics render as three views with three permissions in one deterministic run (own, team, leadership), so no score exists that the person it describes has not seen; no ranking, no activity measures. Two maintainers, never one, because a single key holder is not a safeguard but a single point of failure. Watchdog asks about age, not shape: silence counts as failure until a sign of life proves otherwise. The MVP runs, and it is measured rather than reported: 89 selftest checks across three suites (write layer 23, audit layer 14, MCP bridge 52) plus 58 unit tests, all green; the web client serves all 36 role/view pairs, and a view a role lacks answers 404 rather than 403, because a right that is merely denied still tells you the screen exists. A read-only MCP server exposes the same record inside any AI client the department already has open: it does not import the write layer and defines no write verb, and the selftest fails the moment that stops being true. It also refuses person-level figures, because the KPI engine logs every one of them with who asked and MCP carries no authenticated identity. Not deployed, no real data: a works agreement under §87 BetrVG and a consent decision under §201 StGB come first. Python · FastAPI + htmx · Markdown + git · schema-driven validation · MCP · local-first. Private repo
🧠 How the knowledge system is wired (diagrams)
Two architecture diagrams from the system's own documentation. German, because that is the language I think and take notes in.
Ingest, and the one gate that separates fuel from map.
Sources on the left flow through stateful sync skills into an immutable raw layer, which is never reorganized and never hand-edited. Everything left of the dashed line may grow without limit, because it costs nothing. Nothing crosses to the right until it has been rewritten as synthesis in my own voice and linked to at least two existing notes. wiki/ sits deliberately on the raw side: it indexes the raw layer, it does not interpret it.
The point of a single gate is that ingest volume stops being a quality risk. Without it, "ingest more" and "understand more" look identical from the outside, right up until the graph is a landfill.
Two memory stores, and the one that was actually alive.
There were two. The documented one held 13 entries and had not been touched in eight weeks. The undocumented one was written on every session. The documented store was not worse-built, it was unpowered: it waited for a human to type "capture memory", and for eight weeks nobody did.
So the store with an engine became the leading one, and a nightly one-way mirror copies it into git and cloud backup. One way, on purpose. A technical failure announces itself, because something is missing. A resurrected memory does not announce itself at all, and a memory store that quietly restores things you deleted is worse than one that loses them. Deleted stays deleted; the history lives in git.
The transferable lesson is not about memory. A component with no engine attached is a component that has already stopped, and nothing will tell you, because "nothing happened" is indistinguishable from "nothing needed to happen." Every subsystem needs a liveness check that treats silence as death until proven otherwise.
🏠 Case study: private AI on hardware I own, and moving the line between what runs local and what does not
The full build: the trust boundary, the local inference hosts, two overnight shifts on two machines, and the supervision layer that makes unattended work trustworthy. Written the way I'd hand it to a client who says "we can't put that data in someone else's cloud."
The problem. A personal AI assistant is only useful if it knows your actual context: notes, decisions, calendar, client thinking. That is exactly the data you cannot paste into a hosted chatbot. So you pick one, an assistant that's useful while your data leaves the building, or one that's private and blind. For regulated procurement work that is not a real choice.
The approach. Split on the trust boundary, not on convenience. One codebase, two deployments, and the split is drawn at the retrieval engine. The embedder and the hybrid index are gated at registration time, not at runtime: the tool is never offered to the model at all unless ICARUS_BRAIN_MCP=1, which only a machine I own sets. That distinction is the whole point. A disabled feature is a config checkbox somebody can flip; an unregistered one has no code path to enable. So the index, the embeddings and every vector never leave the box. Note text is a separate question with a separate answer: the hosted instance can read the vault, but only through an authenticated private-repository path with a read-only credential, and the same read-only rule binds every local job. Being precise about which of those two boundaries you are claiming is the exercise.
The hardware, and why there are two boxes.
- A 2018 Intel MacBook Pro (i7-8559U, 16GB, no GPU, macOS 15.7.8), clamshell, on AC, sleep disabled. Always on. Its job is to be reliable, not fast.
- A Windows desktop (Ryzen 7 9800X3D, 64GB, RTX 5080 with 16GB VRAM). Its job is to be capable. It is not always on, and the design has to survive that.
The lesson I would give a client first: private AI infrastructure is not one machine, it is an honest split of what runs where, including "what happens when the good machine is off."
The line I drew in June, and why I moved it in August. I benchmarked a 7B on the Mac at ~5 tok/s and concluded: embeddings and retrieval run locally, heavy generation does not. That was right about the hardware and wrong about the workload. Tokens per second is a latency measure, and latency only costs you something when a human is waiting. At 02:30 nobody is waiting. Re-framed as throughput-per-night instead of tokens-per-second, a box that is useless for chat is entirely adequate for batch. The line moved, and it was the interactive half that got cut, not the local half.
Two overnight shifts, deliberately not the same job.
| Drafting shift | Contradiction sweep | |
|---|---|---|
| Model | 9B, local | 35B MoE, local |
| Runs on | the always-on box | the GPU box |
| Cadence | nightly, 02:30 | only when the machine is free |
| Output | summaries and distill candidates, all quarantined | pairs of notes that might contradict |
| Autonomy | earned: a cloud model scores each run 1 to 10, seven runs at 8+ unlocks it | none, cloud Opus adjudicates every flag in the morning |
The rule both obey: the local model never writes into the note graph. In the sweep, deterministic Python builds the candidate pairs by shared entities (no shared entity, no detectable contradiction) and the model's entire job is to say this pair might conflict. Judgment stays with the cloud model and the write stays with me. Local grinds, cloud decides.
Because the GPU box is not always on, its shift opens with a real idle gate: a GetLastInputInfo P/Invoke for genuine user-idle time rather than Task Scheduler's RunOnlyIfIdle, and it fails closed if idle time cannot be read. It yields the machine between batches the moment a keystroke lands, and it writes state after every batch so an interrupted run resumes instead of restarting.
The benchmark that picked the model. Same sources, same prompt, same guards, same token cap. Only the model varies. Speed is recorded for planning the night, not for scoring.
| model | runs | avg s | format ok | invented links | real links | passed the gate |
|---|---|---|---|---|---|---|
| qwen2.5:7b | 3 | 26 | 3/3 | 0 | 1.3 | 2 |
| qwen2.5:14b | 3 | 237 | 3/3 | 0 | 2.0 | 3 |
| phi4:14b | 3 | 463 | 3/3 | 0 | 1.3 | 2 |
| gemma3:12b | 3 | 233 | 3/3 | 0 | 2.0 | 3 |
The column I actually care about is invented, and it is 0 across all twelve runs. Not because the models are honest, but because they were never given the chance. The prompt offers a numbered list of real note titles and asks for integers, and code resolves the integers back into links. An integer cannot be a hallucinated title. It is either a valid index into a list I built or it is discarded. That is the transferable pattern, and it is the same one as the pairing step: when correctness is checkable in code, do not ask the model to be careful, remove the failure mode from the interface.
The hundred nights nobody heard about. The nightly compound job on the Windows box last succeeded on 8 July. It then failed every night until 16 October, about a hundred nights, first on an unanswered trust prompt and later on a harmless warning line that PowerShell 5.1 turned into a fatal error. The board sync behind it never ran once, and nothing said a word. My first count was eleven nights, taken from the log files still lying around instead of the state the job keeps about itself: estimating the size of a silence is the same mistake as missing it. The Mac had had a phone alert since 11 August. The machine actually doing the work had no voice at all. That asymmetry was the bug.
The fix is a watchdog with two rules worth stealing. It reports only on state change, because a job that says "all good" every morning is muted within a week, and a muted channel is strictly worse than no channel: it manufactures the belief that you would have been told. And it never reads exit codes. A 9B routinely exits non-zero on a malformed tool call after the drafts are safely on disk, so each job publishes its own definition of success and the watchdog reads that instead. A watchdog that cries wolf on the one healthy job is how you train yourself to ignore it. Stale counts as broken too: a job that quietly stops firing is precisely the failure this exists to catch.
The failure I did not design for. Two deployments that look identical become a liability the moment one stops updating. The hosted one redeploys on every push; the one I own updates only when I pull. On 11 August my phone was still pointed at the local instance running seven-day-old code. Five health checks reported the hosted side green while the phone answered from the old build, including a flat "I can't send you files", which on that machine was true. Several commits went into repairing a host that was not the one answering.
The fix is not discipline, it is instrumentation. /health now returns the running commit and a fingerprint of the fully assembled system prompt (on 20 October 2026: commit 3d1ed86, prompt SHA dcad601fe4, 21,275 characters). The tool inventory alone was not enough, and that is the transferable part: it does not move when a prompt rule changes, and from the user's seat an outdated instruction is indistinguishable from a missing capability. If you ship agents, version the prompt like you version the code, and expose it.
Writing the prompts, since these run unattended. For anything that runs more than once, I stopped writing the prompt myself. I dump the context, then ask the model what is the best prompt I could give you for this, write the prompt and not the answer, and I run the result after reading it. A prompt written cold carries only what happened to occur to me. One written against the full context routinely adds the parts people forget: freshness constraints, an explicit output shape, and a rule to say UNKNOWN rather than guess. That last one matters most in a job nobody watches, because an unattended prompt built on a guess produces confident wrong output every night, forever.
Honest status, because "runs 24/7" is a claim a reader should be able to check.
- Hosted ICARUS: live at www.icarusai.de, 32 tools, self-reporting its own commit and prompt SHA.
- Drafting shift: paused since 16 October 2026. It ran every night, but nobody reviewed it: 230 drafts piled up, and in a sample of 29 only 3 were usable without reservation, because the news sync already covered the same ground. A shift that has to earn its place by its scores and does not, gets switched off.
- Nightly integrity audit of the knowledge graph: running, read-only, alerts only when the finding-set changes.
- Watchdog: running daily since late August, silent while everything is healthy.
- Contradiction sweep on the 35B: built and measured, not yet scheduled. Built is not running, and I would rather say so than imply a cron entry that does not exist.
- The self-hosted interactive instance: switched off. Keeping a second always-on front end in sync with the hosted one is what caused the 11 August incident, and of everything here it earned its keep least. The hardware still runs retrieval and the nightly compound job. Turning off the half that cost more than it returned is a result, not an outage.
What I'd do for a client. Map the actual trust boundary first: which data legally cannot leave, and which comfortably can. Put local-only tools behind registration-time gating so "private" is structural rather than a checkbox someone can flip. Size local hardware to the job, and separate latency-sensitive work from throughput-sensitive work before buying anything, because the second kind runs fine on hardware nobody would call adequate. Keep judgment in the model you trust and grinding in the model you own. Then the unglamorous 80%: supervision, fail-safe ordering, state-change-only alerting, and success criteria defined per job rather than borrowed from an exit code. The demo is a day. The service layer is what survives contact with production.
See it: ICARUS repo (private)
📋 Case study: Aushang, digitising the paper notice board without changing anyone's process
Why I built it, what it does today, and how one codebase reaches the web, Android and iOS while no personal data reaches the model.
The problem. Kitas, clubs, parishes and allotment associations still run on paper pinned to a wall. Whoever walks past the board is informed; everyone else misses the closure day, the menu change or the deadline. The usual fixes ask these organisations to change how they work. Aushang does not.
The approach. Change nothing about the process. The organisation keeps pinning paper; one admin photographs the board from inside the app, and the system does the rest. Two rules carry the design. Privacy by construction: the photo is read and personal data is masked locally (Tesseract, Microsoft Presidio, spaCy), so the language model only ever sees redacted text. The LLM advises, deterministic code decides: the model proposes type, structure and dates against a strict schema, and nothing reaches members until an admin confirms it. Every organisation is its own tenant: every domain row carries its organisation, and row-level security keeps one organisation's data out of another's view.
What I built.
- A web app and installable PWA (Next.js 16, React 19, Supabase in the EU) with a private feed, category libraries, a shared calendar with ICS subscription, an email digest and push.
- A Python worker for OCR, redaction and extraction (Claude Haiku 4.5, redacted text only) that also translates each published notice into English and Russian.
- Admin and operator tools: review with a preview of the AI result, take-down and re-publish, a document store, a feedback inbox, broadcasts to all organisations.
- Native apps from the same codebase (Capacitor): Android with native camera and a cloud build; iOS set up to build on GitHub's macOS runners, so no Mac is needed. Native push (FCM, APNs) and deep links are built and stay switched off until device tests pass.
- Hardened to a four-layer security model (deny-by-default middleware, server role checks, SECURITY DEFINER RPCs, Postgres RLS with column-level REVOKE on personal data) and put through multi-agent adversarial security reviews; a one-command self-host wizard lets anyone run their own copy.
How it fits together.
flowchart LR
A["Admin<br/>photographs the<br/>paper notice"] --> APP
subgraph EU["Hosted in the EU"]
APP["App<br/>web, Android, iOS<br/>from one codebase"]
WK["Private worker<br/>text recognition +<br/>local masking of<br/>personal data"]
RV["Admin review<br/>nothing publishes<br/>without it"]
DB[("Database<br/>each row belongs to<br/>one organisation")]
end
subgraph US["Outside the EU"]
LLM["Language model<br/>sees masked<br/>text only"]
end
APP --> WK
WK -- "masked text" --> LLM
LLM -- "draft, checked<br/>against a schema" --> RV
RV -- "confirmed" --> DB
DB --> M["Members<br/>feed · libraries<br/>calendar · email · push"]
What it can do today.
- Capture: a photo or an upload from camera or gallery; the same photo twice is blocked.
- Understanding: text recognition, personal data masked locally, and the model proposes type and dates (events, closures, deadlines, menus, weekly reviews, information, health notices). The admin confirms.
- For members: a private feed, category libraries that count what is new since the last visit, a shared calendar with a calendar subscription, an email digest and push, in German, English or Russian.
- Documents: admins upload PDF and Word files into folders, members download them.
- Organisations: each one separate and invite-only, with admin and member roles; an operator console that can broadcast to every organisation, and a feedback inbox.
- Privacy: account deletion inside the app; the original photo of a weekly review is deleted when it is published.
- Platforms: web and PWA live; Android and iOS from the same codebase, held behind the go-live gate; a one-command setup to run your own copy.
The result. In real use at a Kita since June 2026. 174 commits on main by October 2026, checked by CI on every pull request and every push to main (type check, lint, tests, build, two secret scans). AI cost scales with published notices, not with readers: one extraction plus one translation per extra language for each notice, while reading costs nothing. The store launch is deliberately gated: a tenant-isolation test across every table, a dry run on old and budget phones against Core Web Vitals budgets, provider and business model settled, and a way back for web, database and stores. Nothing ships before every row is green.
What's next. About ten organisation types (Kita, school, sports club, allotment association, parish and more) as starting templates for terms, categories and start page, which each organisation then adapts itself. One interface and one configuration per type, so a new vertical is a configuration, not a fork.
gantt
title Aushang, October 2026
dateFormat YYYY-MM-DD
axisFormat %b %Y
section Built and live
MVP · capture pipeline · review gate · calendar · PWA :done, 2026-06-12, 2026-06-20
Android shell · native camera · de en ru :done, 2026-06-15, 2026-06-30
Pipeline durability · retries · job recovery :done, 2026-07-01, 2026-07-31
Documents · feedback inbox · AI preview :done, 2026-09-01, 2026-09-30
Organisation model · operator anchor flag :done, 2026-09-12, 2026-10-20
section Store readiness
iOS project · cloud builds · push · deep links :done, 2026-10-20, 1d
section Go-live gate, targets
Isolation · dry run · device performance :active, 2026-10-21, 2026-11-30
Store launch, Android first :milestone, 2026-12-15, 0d
See it: private repo, code walkthrough on request.
Tools I built for my own projects and open-sourced because the problem is everyone's.
| Project/Description | GitHub |
|---|---|
🩹 claude-blueprint: Seed a Claude Code project with guard rails that actually hold. Eight hooks that block secrets and dangerous commands at the tool-call boundary, before the model's intent matters, plus ten focused reviewers and twenty skills. The thesis: security is deterministic, everything else is advisory, so anything you cannot afford to lose goes behind a hook and everything else has to be specific enough to be followed. Which is why the nine CLAUDE.md rules each carry a dated incident and what it cost, instead of reading like a mood board. Ships as a one-command seed or a plugin. Includes incident-rule, a skill that turns your next lost afternoon into a permanent rule in your own words, and refuses to write one when nothing was actually at stake. MIT. |
GitHub |
Self-hosted reliability and security tooling that keeps the production stack healthy, observe-only guardians, firewall hardening, and automated secret hygiene.
| Project/Description | Repo |
|---|---|
| 🛡 Lookout, Docker Host Guardian: Observe-only watchdog for the production Docker hosts. Samples every container's CPU + memory each minute; on a sustained runaway it applies a reversible CPU cap (the only automatic action) and alerts via Telegram, leaving pause/restart/kill as owner-gated commands. Plus: firewall hardening (ufw + DOCKER-USER conntrack rules that actually block Docker-published ports), short-lived auto-rotated service tokens (no long-lived credentials on disk), a repo secret-scanner that watches all public repos for exposed keys, and a push-based health feed so the ops assistant can answer "are the servers running well?" in natural language. | Private repo |
AI systems built and deployed for real organizations.
| Project/Description | GitHub |
|---|---|
| 📊 Client Dashboard: Internal agency dashboard for monitoring all live client AI systems. Real-time status, deployment health, pipeline metrics across projects. | GitHub |
| 🧙 Agency Wizard: Internal onboarding wizard for deploying full AI automation stacks to clients in a single 3-hour session. Validates every credential live, then provisions into the client's own n8n Cloud instance. | Private repo |
| 🩺 AI Triage System (Metabelly): Autonomous customer support triage for a Croatian gut health brand. Incoming emails classified by AI (category, priority, language), auto-replies drafted, Calendly links appended, results routed to Slack. n8n + Mistral AI + Gmail API. GDPR-compliant. | Private repo |
📧 Noosphr Email Router: AI email triage for Noosphr's inbox. Claude Haiku classifies and routes to #business, #support, or #spam Slack channels with one-click reply buttons. Runs as systemd service on Hetzner VPS. |
GitHub |
| Project/Description | GitHub |
|---|---|
| 🏥 Kita Connect: Full-stack daycare management platform for German Kitas. ~€0/month, GDPR-compliant, Frankfurt-hosted. Three portals: parents, educators (AI-assisted learning stories via Claude Haiku), management (multi-channel comms, automated registrations). | Private repo |
| ⚡ Light-weight Transcriber: Drop a YouTube URL or paste any text. Ask Claude anything about it. Answers without downloading the audio, paste a URL or text and ask. | GitHub |
| Project/Description | GitHub |
|---|---|
| 📚 RAG Pipeline: Chunking, embedding, retrieval with metadata filtering. Upstash Vector, OpenAI embeddings, query pipeline with source tracking. | GitHub |
| ⚖️ Relevance Scoring & Rerankers: Advanced RAG over EU AI Act legal text. Vector similarity, metadata filtering, Cohere cross-encoder reranking, before/after position-shift analysis. | GitHub |
| 🤖 LangChain Tool-Use Agent: ReAct-pattern agent with free tool selection across 4 custom tools. | GitHub |
| 🔄 LangGraph Complaint Processor: Deterministic 5-node state machine with human-in-the-loop checkpoints. | GitHub |
| Project/Description | GitHub |
|---|---|
🧠 TrueSpend Workflows (17): intake_receiver, chat_assistant, board_action, supplier_reply_handler, docusign_sign, docusign_callback, contract_watcher, reorder_trigger, hyperscaler_monitor, supplier_onboarding, invoice_processor, delivery_confirmation, asset_depreciation, llm_consumption, rag_embedder, dispatch_drain, vps_monitor. Production-grade: 120s timeouts, 3× retry, per-signal trace logging. Status transitions call SECURITY DEFINER RPCs: no workflow writes tickets.status directly. |
GitHub |
| 🏗 Procurement Triage Workflows: 6 importable n8n workflows: PR ingestion, tier routing, ERP budget/PO, RFQ/RFP outreach, quote collection, approval handling. | GitHub |
| 📰 arXiv Research Summarizer: n8n + Claude + Notion. POST an arXiv URL → fetch metadata → Claude summary → Notion record. | GitHub |
Procurement & Strategy
Languages & App Development
Backend, APIs & Databases
AI, Agents & Retrieval
Integrations, Automation & Media
Testing & Developer Tools
Infrastructure & Self-Hosting
Data & BI
Security & Privacy
| Company | Role |
|---|---|
| TeamViewer | Team Manager, Category Management · 2023 to 2026 |
| Scout24 | Senior Procurement Manager IT · 2022 to 2023 |
| foodpanda (Delivery Hero), Singapore | Regional Procurement Manager IT, APAC · 2020 to 2022 |
| Delivery Hero | Associate Buyer and Analyst, then Global Category Manager IT · 2017 to 2020 |
| Heyner | Strategic Buyer, direct sourcing from China · 2010 to 2014 |
Building practical AI tools and sharing what I learn along the way.






