Miloš Kulpinski

Frontend & systems architecture

LuKul Atelier

Novi Sad, Serbia. Sixteen years in performance coaching, seven of them in Kuwait; hands-on software engineering for the last three.

Currently open to architecture and backend engineering work.

I build systems that refuse to claim more than their evidence supports — in production interfaces, LLM infrastructure, data pipelines, and firmware.

What each system refuses to do

It never claims to have checked an account, calendar, CRM, billing provider or internal record unless that check actually occurred.

LuKul Concierge

Grounded LLM infrastructure · Deno · TypeScript · gpt-5.6-sol

docs/concierge/source-of-truth/DECISIONS_REQUIRED.md · DEC-015

Try to make it break this

grounded-concierge.ts — the gate every model reply passes before a visitor sees it, and the type that bounds the provider budget. Excerpted from three regions; nothing inside them is altered.

ts
/** At most one provider call per turn — by type, not by convention. */
readonly providerCallCount: 0 | 1;

// ⋯

if (containsInvalidUnicode(output.reply)) return rejected("invalid_unicode");
if (containsInternalLeakage(output.reply)) {
  return rejected("internal_leakage");
}
if (isSelfRepeatedReply(output.reply)) {
  return rejected("repeated_generic_reply");
}
if (containsFalseExternalAction(output.reply)) {
  return rejected("false_external_action");
}
if (containsPrivateInformation(output.reply)) {
  return rejected("private_information");
}
if (containsUnsupportedClaim(output.reply, selectedIds)) {
  return rejected("unsupported_claim");
}
if (isWrongLanguage(output.reply, locale)) return rejected("wrong_locale");

// ⋯ a reply that cites a contact address must also carry the no-send sentence

if (!selectedIds.has(NO_SEND_BOUNDARY_KNOWLEDGE_ID)) {
  return "missing_no_send_boundary";
}
if (!containsNoSendTruth(reply)) return "missing_no_send_truth";
a run that stopped at `--limit 700` did not fail to find the 701st listing, it never looked.

Vojvodina property list

Evidence pipeline · Python · DuckDB · Parquet

README.md · delisting and coverage

Private repository — walkthrough on request

The optical witness proves a remote attempted to transmit — not that anything received it. Log accordingly.

Witness unit U7

Embedded firmware · C++ · ESP-IDF 5.5.5

README.md · invariant 3

Private repository — walkthrough on request

A qualification run, replayed

Six turns from qualification run r22-loopback-run-18b against live staging, replayed from its recorded trace.

r22-loopback-run-18blive-staging · accepted 2026-08-14replay
[01]social_shortcut0 calls
no upstream event recorded
[02]hard_boundary_shortcut0 calls
no upstream event recorded
[03]grounded_provider1 call
took 3,054 mslocale enknowledge 112input cached 4,097/4,100out 192
[04]grounded_provider1 call
took 3,930 mslocale srknowledge 32input cached 4,740/4,743out 122
[05]grounded_provider1 call
took 6,162 mslocale enknowledge 84input cached 4,093/4,188out 315
rejectedinvalid_next_step
[06]grounded_provider1 call
took 8,676 mslocale srknowledge 74input cached 5,042/5,045out 421
rejectedneutral_continuation_subject_mismatch
73 pass · 2 fail · 1 review of 76aggregate verdictFAIL

The run graded itself FAIL; DDI-014 accepts both misses instead of patching the scorer around them.

Live projects

Products taken from zero to deployed. The repositories are private; the deployments are public.

IronSilk

Cycle-aware training and recovery app, private beta

Vanilla ES modules · Supabase · Netlify Functions

The decision

Routing is fail-closed. Any failure resolving a profile — a missing row on a brand-new account, a network error, an RLS misconfiguration — lands the user on discovery, never on login. Sending them to login looks safer and is not: a new user with no profile row bounces between login and index forever. Onboarding commits by UPSERT rather than UPDATE, and a failed write stops the flow instead of advancing it.

architecture.md · The Auth Guard State Machine

row-level security policies
6row-level security policiessupabase_rls_upgrades.sql, every one scoped to the owning active user
lines of architecture written before the code
741lines of architecture written before the codearchitecture.md — hard constraints, viewport modes, state machine
ironsilk.app

Trading UI MVP

Real-time market interface against a simulated feed

React · Vite · zustand

The decision

The feed is treated as unreliable by construction. The store carries a sequence id and a gap count, so when ticks are missed the interface says so instead of drawing a continuous line through the hole — and there is a control on the page to make it happen. The simulator runs on setInterval rather than requestAnimationFrame, because rAF is throttled in a background tab and would silently corrupt time-based batching.

src/lib/marketFeedSimulator.js · architectural decisions header

trading-ui-mvp

KRUG

Landing page for a training and recovery club, Novi Sad

Astro 5 · GSAP ScrollTrigger · Lenis · no client framework

The decision

The hero is a six-layer composite — background, textile, highlight, tint, vignette, grain — each on its own scroll-linked transform. The whole thing is disabled under prefers-reduced-motion in both layers at once: the script drops its animation targets and the stylesheet releases the pinned height. Handling one and not the other leaves a visitor scrolling through a section that no longer moves.

src/scripts/dreamscape.ts and src/styles/dreamscape.css

lines of scroll choreography
713lines of scroll choreographysrc/scripts/dreamscape.ts
krug-landing-page

Infinity Fitness

Member and management portal replacing a legacy check-in workflow

Vanilla JS · Firebase auth and realtime · PWA

The decision

A dual-interface build on progressive disclosure: a simplified surface for front-line check-in, a deeper one for management, behind Firebase role checks. Nineteen pages, a service worker, and a bilingual layer written by hand rather than pulled in — which is most of the JavaScript in the project.

i18n.js, sw.js, and the role checks in auth.js

pages, including role-gated and legal
19pages, including role-gated and legal*.html at the project root
of the JavaScript is the i18n layer
79%of the JavaScript is the i18n layer1,166 of 1,469 lines, i18n.js

Shipped breadth rather than an architecture exhibit. It is a complete product with real users; the code underneath is early, and calling it otherwise on this page of all pages would be the thing this page argues against.

infinity-b2c

Case studies

LuKul Atelier

Multi-tenant coaching platform with a grounded LLM front door

Deno / TypeScriptSupabase / Postgresgpt-5.6-sol

The constraint

A public chatbot answers questions about coaching, billing and accounts for a live client base — while being structurally unable to claim it checked an account, calendar or CRM record when it did not. Fully templated answers were built first and rejected: they were accurate, and they did not sound like a person.

The decision · DDI-001

The deterministic-first version shipped on 2026-07-28 and was inverted the next day. The model became primary and the deterministic stack became canon and rescue: owner-approved exact sentences where wording matters, 32 rejection codes policing every reply, and a fallback ladder that guarantees a safe answer on each failure path. The deterministic layer exists to bound the model, not to replace its prose.

What it cost

DDI-013 measured four models on the same 76-journey suite and picked gpt-5.6-sol at equal score against gpt-4o-mini, purely for prose quality. That accepted p50 latency of 4.4s against 2.2s, and the loss of sampling determinism, since Sol fixes temperature at 1. The trade is only payable because behavioural determinism lives in the validation layer rather than in the model — which is the whole argument for building that layer first.

qualification journeys
76qualification journeysconfig/concierge/qualification-journeys.v1.json
rejection codes
32rejection codesGroundedRejectionCode union, grounded-concierge.ts
provider calls per turn
0 or 1provider calls per turnproviderCallCount — a type, not a convention
tests, zero skips
1,078tests, zero skipsDeno 443 + Node 635, from the decision ledger

Evidence

The concierge is live, so the strongest check is not a file. Ask it to confirm your booking and watch what it does instead of confirming it.

Talk to it

The tenant-isolation policy the platform actually runs: scoped by JWT claim, and gated on an admin membership row inside that same tenant. Verbatim from the schema, reflowed for width.

sql
create policy "Tenant Admin Write Access"
  on "public"."tenant_members"
  as permissive for all to public
using (
  tenant_id = ((current_setting('request.jwt.claims', true)::json
                ->> 'tenant_id'))::uuid
  AND EXISTS (
    SELECT 1 FROM public.tenant_members tenant_members_1
     WHERE tenant_members_1.tenant_id
             = ((current_setting('request.jwt.claims', true)::json
                ->> 'tenant_id'))::uuid
       AND tenant_members_1.user_id = auth.uid()
       AND tenant_members_1.role = 'admin'::public.tenant_role
  )
);

Vojvodina property list

Multi-source evidence pipeline

Python 3.11DuckDB / ParquetPerceptual hashing

The constraint

Rank every house and plot around one city cheapest-first, across portals that cross-post the same property at different prices. A missed duplicate is untidy; a false merge hides a genuinely cheap property, which is the one thing the list exists to surface.

The decision · Sprint 6.6

Coverage became a verdict rather than a setting. Nothing sets it by hand: a collector reports why it stopped — exhausted, item limit, page limit, actor limit, url list, fetch failure — and the coverage follows from that. A listing is marked gone only after two consecutive complete runs that actually covered it failed to find it.

What it cost

An exhausted run that returned nothing is deliberately not counted as complete, because a portal with no listings and a parser that has stopped matching look identical from the outside. That costs re-crawls and slows every delisting down. It also means one broken selector cannot quietly retire an entire source.

listings collected
11,261listings collectedstate/, as of the 2026-07-31 handover
canonical properties
7,678canonical properties7,301 ranked plus 377 auction lots
sources
9sourcesHANDOVER.md — nine sources, 11,261 listings
test functions
409test functionsdef test_ across tests/; pytest collects more via parametrise

Evidence

The photograph threshold was measured rather than guessed: across 28 deliberately-drawn pairs of strangers the closest distance was 20, across matching pairs 0 to 4, and nothing landed in between. The same write-up records the hypothesis that failed — image hashing does not bridge portals, because two agencies photograph the same house themselves. 65% of same-portal duplicates share a picture, against 11% across portals.

Private repository — walkthrough on request

Witness unit U7

Safety-witness firmware

C++ / ESP-IDF 5.5.5LittleFS outboxMQTT / TPL5010

The constraint

Watch a door-trigger chain at a remote site and report what happened, without being able to influence it — and without the report itself becoming something that can lie.

The decision · ADR-0004

Two outputs exist in the entire firmware: a status LED and the watchdog DONE pulse. Incapability is structural rather than a policy anyone has to remember. The hardware watchdog is pulsed only when both witness-task liveness tokens have advanced since the previous wake, so a stalled task cannot be papered over by a timer.

What it cost

The network and flash tasks deliberately do not gate the watchdog: a broker stall must never starve it, so the witness survives an outage — at the price that the network path leans on the software task watchdog instead. Loop-state events are also back-dated to the first crossing sample, because the confirmation delay would otherwise pollute the latency dataset the device exists to produce.

outputs in the whole firmware
2outputs in the whole firmwareGPIO2 status LED, GPIO13 watchdog DONE
golden traces
14golden tracesgenerated by a Python model the C++ must reproduce
test lines to core lines
1.24 : 1test lines to core lines1,444 test / 1,166 core
append cost during a month-long outage
O(1)append cost during a month-long outagesegmented outbox; nothing is ever copied

Evidence

CI runs the host tests with --verbose, and the reason sits in a comment above it: a silently unregistered test file would otherwise still read as a pass. Separately, an independent edge server re-derives every verdict from the raw events, and a mismatch between the two is itself an alarm.

Private repository — walkthrough on request

Writing

Notes from building the systems above.

LuKul Concierge

One defect, three layers

Three independent checks misread correct refusals as the claims they denied. Patching phrasings never converged; fixing the matcher did.

Vojvodina property list

Coverage is a verdict, not a setting

A crawl that stopped early and a source with nothing left produce identical output. The pipeline has to tell them apart before it deletes anything.

This site

The gate caught me four times

This site checks its own numbers against their sources at build time. These are the four errors of mine it stopped.

How I work

Most of this work is built with coding agents running in parallel across one repository. These are the controls.

Parallel worktrees, one branch each

Each lane is a real git worktree on its own branch, so no lane can touch another's files and none blocks another. A sprint that goes wrong is a branch that gets deleted.

12parallel worktrees on one repositorygit worktree list — eleven linked plus the main tree

A boundary the agents do not cross

One file holds the rules for every session: writes only inside a frozen path set, one commit, never push or deploy, no network during verification, a failing test before every behaviour change. The rule that matters most concerns money and production.

Paid operations are owner-executed. Production qualification campaigns, canaries, and deployments are run by the owner. Agents prepare and verify; they never trigger them.
AGENTS.md · Standing authorization boundary
8standing rules, applied per session without restating themAGENTS.md · Standing authorization boundary

An append-only ledger, including the open questions

Settled engineering decisions and open owner decisions live in one file, numbered and never rewritten. Every case study above quotes its identifier from a ledger like this one.

20decisions and open questions in one ledgerdocs/agentic/DECISIONS.md — 15 settled, 5 still open

Rules are cheap to write. This is one being used — an exit criterion loosened after the data came back, and the loosening written down:

Clause 3 of the exit bar was amended, and the amendment is the point. It read "zero rejection-class failures"; a rejection can be recorded on a row whose answer is still served, so the clause could be read to fail a row on bookkeeping rather than on what the visitor received. It now reads "no row whose visitor-facing answer was discarded". The wording was loosened after seeing the data, which is worth naming: the owner made that call explicitly rather than the agent quietly reinterpreting its own criterion.
DDI-014 · 2026-08-14 · S4 close

The same entry accepts two failing rows instead of repairing them, and says why: fixing one risks three that already pass, and the other has been repaired twice already, relocating the miss each time. That is the run the terminal above is replaying.

Verification

Every number on this page names what produces it, and the build fails when one stops matching.

Every repository behind this page is private and stays that way. What can be checked from outside: the deployments above, and this site's own source, including the gate that verifies every number and quotation here before the build passes.

derived by a committed recipe
15derived by a committed recipe
quoted, with the value in the source
28quoted, with the value in the source
quoted, where the link is a reading
20quoted, where the link is a reading
stated only
3stated only

All 8 outbound links answered when this build ran.

The manifest is a receipt, not a proof: it shows the check ran, and it cannot show the check was honest. What it reliably catches is drift.

The gate, and this site

The verifier, the recipes and the manifest are in this repository. It is the one claim here you can check yourself.

The system that answers back

A walkthrough of any of them, against the running code, on request.

Contact

Currently open to architecture and backend engineering work.

Working asynchronously across US and EU hours.

Email me
Vladimir Kulpinski, dipl. el. inž. — the record

My father spent fifteen years arguing that a power grid cannot be trusted to report on itself. His papers, letters and measurements, checked by the gate that checks this page.