Murat Can Ümit

Solutions architect and founder. I build production AI systems alone, and I know what they should refuse to claim.

Twelve years across banking, aviation, telecoms and consulting. I have designed, built and operated two AI products end to end and alone: architecture, inference, entitlement, release and on-call. Currently in Istanbul.

01 — The system

Tactiq

Tactiq is a live football match-analysis platform with users in 175 markets. What follows is not a feature list. It is the three decisions that were hard, where a wrong answer would not have crashed anything. It would just have been wrong for months. Then the whole system, drawn once.

2a — Platform
Clients iOS · Android · web · macOS API Gateway latency-based routing health-check failover us-east-1 80 functions primary · batch, calibration, tournament eu-central-1 44 functions user path ap-northeast-1 52 functions user path Single-region authority entitlements · refresh tokens one writer atomic conditional write propagated every minute active-active · 176 functions · 41 DynamoDB tables 16 Route 53 health checks 36 tables replicate globally, five are held outside replication deliberately
Clients iOS · Android · web · macOS API Gateway latency-based routing health-check failover us-east-1 80 functions primary · batch, calibration, tournament eu-central-1 44 functions user path ap-northeast-1 52 functions user path Single-region authority entitlements · refresh tokens one writer · atomic conditional write propagated every minute active-active · 176 functions 41 DynamoDB tables · 16 Route 53 checks 36 tables replicate globally, five are held outside replication

Three regions, one exception

One hundred and seventy-six Lambda functions run in production behind API Gateway across three AWS regions, with latency-based routing and sixteen Route 53 health checks. Forty-three distinct functions are replicated to all three in an active-active layout; the remainder are deliberately single-region scheduled and backfill jobs, because running a cron in three regions produces three writers competing over the same state. The distribution is deliberate rather than symmetric: us-east-1 carries eighty and runs the batch, calibration and tournament workloads, while eu-central-1 and ap-northeast-1 carry forty-four and fifty-two and serve the user path. A user in Tokyo and a user in Frankfurt are served by different regions and see the same product.

Forty-one DynamoDB tables hold the state. Thirty-six of them replicate across all three regions. Five do not, and that exception is the design decision: paid entitlement and refresh tokens live in a single-region authority with one writer and atomic conditional writes, propagated outward every minute, and every region reads its verdict from there rather than replicating it. Propagation and reconciliation are two jobs, not one: propagation is event-driven and runs in minutes, while a weekly reconciliation checks that ledger against store exports, because the stores hold the truth about who has paid and drift has to be measured against the source rather than against ourselves. A replication canary runs every five minutes and alarms on cross-region latency, because a replication lag you cannot see is the same as one that is not happening.

Multi-region last-writer-wins is fine until it silently drops a tier change, and a customer losing paid access is a failure you hear about from an angry message, not an alert.

2b — Guardrails
Request App attestation HMAC request signature Signed short-lived token fail-closed fail-closed fail-closed Generation Grounding check Schema validation Language guard server-authoritative shape enforced requested locale only Cache decision Response Discarded rejected, never repaired flow control and authority rejected HMAC, JWT and batch secrets held per region language mismatch raises a named alarm
Request App attestationfail-closed HMAC signaturefail-closed Signed tokenfail-closed Generation Grounding checkserver-authoritative Schema validationshape enforced Language guardrequested locale Cache decision Response Discarded rejected, never repaired flow control and authority rejected HMAC, JWT and batch secrets held per region language mismatch raises a named alarm

Structural constraint, not instruction

Three fail-closed checks run before a request reaches a model: app attestation, HMAC request signing and a signed short-lived token. If any of them cannot be verified, the request stops there.

After generation the output passes a server-authoritative grounding layer, schema validation and a language guard before any caching decision is made. The model cannot emit a number the server did not supply, cannot return a shape the schema does not accept, and cannot answer in a language the request did not ask for. Output that fails is thrown away, not patched.

Prompt-level guardrails fail silently and you find out from a screenshot on social media.

2c — Calibration
Predictions issued Real outcomes match ends, number is fixed Graded Hit rate perconfidence band Calibration errorvs live baseline not an absolute target Drift alarm threshold breach Manual rollback weekly cycle weekly refit Sunday 22:00 UTC · incremental tracking every six hours · counterfactual replay Mondays thin samples are held, not fitted the alarm is automatic, the rollback is not
Predictions issued Real outcomesmatch ends Graded Hit rate per confidence band Calibration error vs baseline Drift alarmthreshold Manual rollback ↺ weekly cycle weekly refit Sunday 22:00 UTC incremental tracking every six hours thin samples are held, not fitted alarm automatic, rollback by hand

Measured against a live baseline

Every week the system scores its own predictions against real results and computes hit rate per confidence band. The distance between stated confidence and observed accuracy is the calibration error. Past a threshold, and only where the sample is large enough to mean something, it raises a drift alarm. The rollback itself is run by hand from a runbook. The detection is what is automated, not the response.

The comparison is against a live baseline rather than an absolute target, because an absolute target is a number you get to choose. Football supplies ground truth on a fixed schedule and does not negotiate.

I could not construct an evaluation that flattered me even if I wanted to.

2d — The whole system

The shape of the whole thing

The user path is the smallest part of this. Most of the system is what runs when nobody is looking: data synchronisation from a spread of providers, each polled at whatever rate its data actually changes, prediction engines that recompute team ratings and playing-style profiles weekly, calibration that grades its own past work, and a notification layer that has to know a user's timezone before it is allowed to wake them.

The World Cup hub was built as an isolated fleet with its own analysis, sync, live and accuracy-tracking functions. A seasonal event with unpredictable traffic should not be able to take down a product that runs all year, and when the tournament ends the fleet can be removed without touching anything else.

One person wrote all of this, and that is exactly why the boundaries are where they are.

CLIENTS
iOS · iPadOS
macOS · Android
32 locales
EDGE
API GATEWAY
3 REST APIs
10 HTTP APIs
~50 routes per region
CLOUDFRONT
global distribution
ROUTE 53
16 health checks
COMPUTE
USER PATH
auth · authorizer · me · history
analyze · analyze-v2 · analyze-live
simulate · momentum · stats · search
PREDICTION
compute-v5-elo
style-profile · match-style-forecast
player-impact · counterfactual
CALIBRATION
auto-calibration
calibration-tracker
weekly-monitor
bedrock-daily-report
DATA SYNC
fixtures · teams · players
leagues · standings · injuries
xg-rolling · shot-profile
search-sync
NOTIFICATIONS
push-worker · match-reminder
daily-reminder · favorite-reminder
SQS queue + DLQ
TOURNAMENT
wc-26-analyze · wc-26-live
wc-26-sync · wc-26-accuracy-tracker
wc-26-push-scheduler
isolated fleet
PLATFORM
warmer · health · replication-canary
log-export · entitlement-sync
export-user-data · user-hard-delete
STATE
DYNAMODB
41 tables
36 global · 5 single-region
LARGEST
search-index 332k
football-data-v2 55k
player-impact 39k
analyses 20k
S3
11 buckets
SECRETS MANAGER
per-region rotation
80 scheduled jobs from every-minute to monthly · 65 CloudWatch alarms across three regions
staging runs as a parallel fleet with its own authorizer
20,000+ users32 languages175 markets4 operating systems
3 regions176 functions in production41 tables80 scheduled jobs65 CloudWatch alarms
Full technical document See it running at tactiq.club The studio, mcvibeapps.com
02 — Inventory

What it takes to keep it running

Architecture diagrams flatter a system. They show the parts that were designed and hide the parts that accumulated. This is the accumulation, taken from the live account and not from memory.

176
Lambda functions in production, three regions
41
DynamoDB tables, 36 replicated globally
80
scheduled jobs, from every minute to monthly
65
CloudWatch alarms with named thresholds
13
API Gateway deployments, REST and HTTP
16
Route 53 health checks
11
S3 buckets
6
external data providers normalised
332,000
rows in the search index
55,000
fixture records with rolling statistics
39,000
player impact records
9,900
calibration records with reliability bands
7,900
audit log entries
4
payment rails with full lifecycle handling
2
hard budget caps with alarms attached
1
person

Some of these numbers are the result of decisions and some are the cost of them. Eighty scheduled jobs is not a boast, it is what six data providers on different refresh cadences actually requires. Sixty-five alarms is what it takes to sleep when there is nobody on the other shift.

03 — Decisions

Five calls I would make again

01

I switched off features that worked

Two of the markets the language model generated carried measured negative skill once they were graded against real results. Nobody was complaining and they were among the most used features in the product. They were not deleted: they went behind a flag whose documented state is off, and a deterministic calculation over real underlying data became the default, with the displayed probabilities bounded at both ends. Revenue fell the month after and retention went up. The flag is still there, so the decision is reversible without a deploy.

The product does less now and retention improved.

02

I ignored an alarm

A drift alarm fired off-season on a thin sample of roughly eighty events. The high-confidence bucket held ten of them. Refitting on ten events would have taught the model the shape of a fortnight rather than the shape of the game, so I left it alone. Two weeks later, at four hundred events, calibration was healthy.

Knowing when your evaluation is not yet telling you anything matters as much as knowing what it says.

03

I gave one table its own architecture

All state is multi-region except one table. Paid entitlement is held in a single-region authority with one writer and atomic conditional writes, and the other regions read from it rather than replicate it. It costs a little latency on a rare path and removes an entire class of silent failure. It is also derived state rather than a system of record: the stores hold the truth about who has paid, so losing that region is a rebuild rather than a failover.

I stopped treating consistency as a uniform property of the system.

04

I did not use a machine learning framework

The learning layer is plain Python. Serverless rewards a small footprint and a fast cold start, and a transparent model I can correct beats an opaque one that is hard to debug when a single feed shifts. When calibration moves, I can read the reason in the code rather than infer it from a loss curve.

Choosing the cheaper, smaller, inspectable option over the more impressive one is a habit rather than a compromise.

05

I put a hard ceiling on my own inference spend

Two budget caps sit on the account with alarms attached: three hundred dollars a month for Bedrock inference, five hundred for everything else. A generative product without a ceiling discovers its unit economics in a billing email. Setting the ceiling first forced the two-tier model routing, the caching rules and the decision about which requests justify a stronger model. There is a daily report on token spend for the same reason.

They are not forecasts. They are alarms with a name on them.

04 — Second product

Naryu

Naryu turns one birth record into daily personal guidance across Western astrology, Vedic astrology, numerology, Human Design, BaZi and feng shui. The reader can request thirty-five reading types, and a daily piece is generated on top of them. It is in pre-launch, with the computation layer, content pipeline and inference path complete.

The personalisation layer is deterministic, not generative. A Python engine derives planetary positions from JPL ephemeris data and produces the chart, alongside house systems, secondary progressions, solar returns, transits, Vimshottari dasha periods, the four BaZi pillars and the Kua and flying star grids. Every engine is validated against published reference charts before it ships. The generative layer runs on Amazon Bedrock and computes nothing at all: the calculated chart is handed to it as ground truth, it only interprets, and a response that does not conform to the schema is discarded rather than cached.

This is the same decision as Tactiq's server-authoritative grounding, reached twice in two unrelated domains. In both products the model is not allowed to compute the thing that can be checked.

An interpretation built on a wrong degree is wrong however well it reads.

No vector carries a reader

There are no per-user vectors. Any embedding index Naryu builds is over content we wrote, never over a reader's chart, readings, journal or feedback. A natal chart is a fingerprint: a sun sign, moon, ascendant and a few aspects narrow a person to a birth window minutes wide and a location. An embedding derived from a chart is therefore a pseudonymous identifier, one you cannot read a birth date out of but can use to match two records as the same person. Under GDPR that is personal data rather than anonymous data. And a vector store is the component where selective deletion is hardest.

20 languages across 24 locales35 reader-requestable reading types~3,400 assertions
2 AWS regions10 Lambda functions17 function deployments12 DynamoDB tables
05 — The finding

statedintent

I audited my two products' documentation against their code. Six claims came back wrong. The documentation said rollback was automatic; the code had an alarm and a runbook, and the claim had already left the document for my CV and ten job applications. A gate removed eight months earlier was still described in the architecture. A package file called itself a daily cron job that no scheduled rule triggered.

Every software company has this and nobody solves it systematically. AI made generation faster and left verification where it was. Producing is cheap, checking is not, and the gap between them is the problem.

The approach is one sentence: read a codebase, derive what it does, have a person confirm that, then audit the documentation against the confirmed intent. Three levels, kept apart. Code is evidence, the existing document is a claim, the human confirmation is authority.

Neither Tactiq nor Naryu lets the model compute what can be checked. The same principle, now applied to prose: a claim has to be provable. Third time, third unrelated domain.

A tool that finds false claims cannot survive having false claims of its own.

The first codebase it audits is its own

It runs its own gates in its own CI, and it can go red there. Every gate is proved by injecting the fault it exists to catch. Fifty-seven claims from three of their documents are labelled by hand, and that set is what accuracy is measured against. An audit tool that does not measure its own accuracy is the thing it criticises.

57 labelled evaluation items3 documents from 2 production products5 MISALIGNED findings, 3 against code
13 blocking CI checks9 of them prove a gate can fail1 warns without blockingApache 2.0
06 — Before

Twelve years before this

The enterprise years started at Doğan Dağıtım, on the Yaysat and Hürriyet DPP side, where I built the corporate web applications and the PL/SQL data processing behind press distribution and media logistics across all eighty-one provinces, along with the dealer network portals and the warehouse the reporting ran on. What that software produced was not a screen. It was which vehicle went where. Then BBVA Garanti Technology, in the CRM and campaign team at one of Turkey's largest private banks, working across thirty-eight million customer records on the modern and the mainframe stacks at the same time. Then Amadeus, on the backend of customer loyalty systems in aviation technology, where correctness at volume is the entire product.

The startup. I co-founded Tosia Tech and owned product across the portfolio, from a fintech product to mobile games. When funding for the blockchain payments product was withdrawn, I ran the change: the team, the technology and the roadmap were redeployed into mobile products and a vendor delivery model. The business went on to become a studio, raise investment and pass five million users. The technical part was the easier half.

Then Orion Innovation, as a senior software developer on telecom platforms and customer onboarding. Then a unit of my own: three and a half years at BlockchainIST Center leading seven engineers across research, consultancy and product, with enterprise clients in banking, insurance and the public sector. The recurring situation was an organisation arriving with its technology already chosen and a problem it had not yet named. Most of the work was finding the problem underneath the request, and then saying so.

Then Vodafone. Solution design across initiatives spanning more than twenty engineering and business teams, worked through those teams' own architects rather than over them, and two large projects that were mine to lead. On an internal enterprise AI product I was one of its technical architects, and the identity and access management model for the entire employee base was my part of it.

07 — Contact

I am open to conversations about architecture, principal engineering, and teams building AI systems that have to survive contact with real users.

Currently in Istanbul. No notice period.