Why My AI Agent Stopped Calling the LLM

I built an AI-powered fantasy football product, discovered it was repeatedly paying an LLM to reconsider facts that had not changed, and eventually arrived at the slightly awkward conclusion that one of the best ways to improve an AI system was to use considerably less AI.

Originally published on Medium.
This is an expanded version of an article originally published on my Medium account. Read the original on Medium →


I have been building a fantasy football application as a personal project, which is the sort of sentence that sounds perfectly harmless until you have been a developer for long enough to know that “personal project” usually means “an unpaid second job involving a database, several scheduled processes, an increasingly elaborate deployment pipeline and at least one dashboard that absolutely nobody asked for.”

Like an entirely normal middle-aged man, rather than simply watching the Chicago Bears disappoint me on a Sunday and getting on with my life, I decided what I really needed was a software platform capable of analysing that disappointment in considerably more detail, preferably with enough data, charts and automated recommendations to make the disappointment feel scientifically rigorous.

The application pulls together player data, fantasy rosters, transactions, injuries, news, values, schedules and other information from several sources, stores and processes that data locally, and then uses an LLM to reason about what has changed and suggest things I might actually want to do about it.

Trade this player.

Pick up that player.

Keep an eye on somebody else.

Perhaps stop drafting Bears players merely because I like the Bears.

The last requirement remains stubbornly resistant to automation.

The first version worked surprisingly well.

The problem was that it worked in roughly the same way as employing an extremely expensive consultant and phoning them every five minutes to ask whether anything had happened, despite the fact that you were standing in the same room and could plainly see that nobody had moved.

Most of the time the answer was:

No.

And I was paying for the privilege of finding out.

That turned out to be a useful lesson, because it forced me to stop thinking about the LLM as the application and start treating it as what it really was: one comparatively expensive, comparatively slow and deliberately non-deterministic component inside a much larger software system.

The first architecture was obvious

The original architecture followed the pattern that I suspect quite a lot of AI prototypes follow:

Get data → build prompt → call model → display answer.

There is nothing particularly wrong with that.

In fact, I would still argue that it is often exactly the right way to build the first version of something, because the purpose of a prototype is to determine whether the idea produces anything useful before you spend three weeks building an abstraction layer, an event bus and seventeen interfaces around something users ultimately do not want.

The problem comes when the prototype works.

Because working software has an unfortunate tendency to become production software.

That is how systems acquire a “temporary” database that survives for five years, a “quick script” that becomes business-critical and a function called processEverything() which eventually requires its own incident response procedure.

My fantasy application runs automatically, which means it periodically collects the latest information about the same players, rosters and leagues.

But American football has the inconvenient characteristic of not completely reinventing itself every few hours.

At 6am, a player’s situation might be exactly the same as it was the previous evening.

The roster probably has not changed.

There may be no new transaction.

There may be no injury.

There may be no meaningful news.

The waiver wire may be identical.

There may not, in fact, be a single new piece of information capable of changing the previous recommendation.

Yet the application could happily collect everything again, construct another large prompt and ask an LLM to reconsider the entire universe.

And because an LLM is not a deterministic function, it would usually oblige.

Perhaps yesterday I should “strongly consider” trading a player.

Today I might be advised to “seriously explore” trading him.

Tomorrow I might be told that moving him would be “a prudent roster-management strategy.”

Three different sentences.

One recommendation.

Several API calls.

At some point I realised I had essentially built a thesaurus with a billing account.

That bothered me, and not simply because of the token cost.

Architecturally, it was stupid.

An LLM call is not a normal function call

This distinction became increasingly important as I worked on the system.

Developers are accustomed to calling functions constantly, and we do not normally stop before invoking:

calculatePlayerValue()

and have a small architectural meeting about whether we can afford it.

Assuming the function is reasonably implemented, it is cheap, fast and deterministic enough that calling it twice is unlikely to trouble either the CFO or the planet.

An LLM call has a rather different set of characteristics.

There is an obvious financial cost, because somebody is charging for input tokens, output tokens, cached tokens, model tier or some combination thereof.

There is latency, because the request leaves your process and goes to an external inference service.

There are rate limits, which become rather more interesting when an application scales beyond the three people you originally expected to use it.

There is an additional availability dependency, because your product is now relying on another provider remaining responsive.

There is model drift, because the service behind your API endpoint can change.

And then there is the particularly interesting characteristic:

non-determinism.

Call a conventional function twice with identical inputs and, unless something has gone badly wrong, you expect identical outputs.

Ask an LLM the same question twice and you can receive two different, perfectly defensible answers.

That variability is part of what makes these models useful; I am not criticising it.

But if the underlying state has not changed, repeatedly invoking a probabilistic reasoning system can introduce variation into a product where no variation is actually warranted.

Your recommendation changes.

The user assumes something happened.

Nothing happened.

The model just fancied a different adjective.

That led me to what became the most important architectural question in the project.

Instead of asking:

“What should the AI think about this?”

I started asking:

“Has anything happened that requires the AI to think at all?”

That sounds like a small change.

It was not.

The agent became a change-detection system

Once I started thinking about the problem that way, the architecture shifted.

The LLM moved towards the end of the pipeline rather than sitting in the middle of everything.

The flow became:

Collect → Normalise → Compare → Decide → Analyse

rather than:

Collect → Analyse everything

That Decide stage is probably the most important component in the entire application.

The collection layer retrieves information from the various upstream sources.

The normalisation layer converts those sources into a consistent internal representation.

The state layer records the information the system currently believes to be true.

The comparison layer establishes what has changed since the previous successful analysis.

The decision layer determines whether any of those changes are capable of affecting an existing recommendation.

Only then does the application construct a prompt and invoke the LLM.

The clever bit, therefore, is increasingly not the model call.

It is everything that prevents the model call.

And this is not particularly exotic AI technology.

It is persistence.

Canonicalisation.

Hashing.

Comparison.

Filtering.

Event detection.

Cache invalidation.

Dependency tracking.

The sort of engineering that does not usually feature heavily in AI product demonstrations because “look at this beautifully designed state fingerprint” does not produce quite the same conference reaction as a chatbot generating a limerick about the CEO.

It is nevertheless where a significant amount of the value lives.

Because:

The cheapest LLM call is the one you never make.

A concrete example: league analysis

Take the league analysis itself.

The original implementation could analyse a league every time the scheduled process ran.

Imagine that at 6am the system sees this simplified state:

league_id: 12345
roster_version: 84
player_news_version: 317
injury_version: 102
transaction_version: 46

It generates its recommendations and stores them.

At midday the collector runs again.

There are newer API retrieval timestamps, because naturally there are, but after normalisation the meaningful state is still:

league_id: 12345
roster_version: 84
player_news_version: 317
injury_version: 102
transaction_version: 46

What exactly am I hoping the LLM will discover?

The answer is nothing.

So now it does nothing.

The existing analysis remains valid.

If, however, injury_version becomes 103 because a starting running back has been ruled out, that is potentially significant.

The system can invalidate the affected analysis, identify which leagues contain that player, construct the appropriate context and ask the model to reconsider the relevant recommendations.

This changes the semantics of the model invocation.

The question is no longer:

“Please analyse my fantasy team.”

It is closer to:

“This was the previous state and recommendation. This meaningful event has occurred. Given the new state, does the recommendation need to change?”

That is a much better question.

It is also a much smaller one.

State fingerprints became surprisingly useful

There are several ways to implement the change detection itself.

One straightforward technique is to build a canonical representation of the relevant state.

Take the fields that actually matter.

Sort collections consistently.

Remove volatile metadata.

Serialise the resulting structure deterministically.

Hash it.

You now have a state fingerprint.

Conceptually:

state_fingerprint =
    hash(
        roster +
        injuries +
        relevant_news +
        transactions +
        player_values
    )

If the fingerprint has not changed since the analysis was generated, there is no obvious reason to repeat the analysis.

Of course, production systems become more nuanced than that.

Different changes invalidate different pieces of analysis.

A roster transaction might affect the whole team.

An injury might affect one player and their likely replacements.

A new article might affect nothing at all.

A change in trade value might matter only when it crosses some threshold.

That means a single giant hash eventually becomes less useful than explicit dependency tracking, but the underlying principle remains the same:

Store enough information about the state that produced an answer to determine whether that answer is still valid.

That changed the way I thought about caching.

The cache became part of the intelligence

Initially I thought about caching in the conventional sense.

Make an expensive request.

Store the response.

Give it a TTL.

Reuse it until the TTL expires.

That works, but it is a fairly crude approach to this particular problem because the passage of time is not necessarily what makes an analysis stale.

Changes in the underlying facts make it stale.

Suppose I analyse my roster at 10am.

If absolutely nothing relevant happens for the next 24 hours, why should the recommendation automatically expire at 11am merely because I chose a one-hour cache duration?

Conversely, if my starting quarterback is unexpectedly ruled out at 10:03am, I do not particularly want to wait until 11am because Redis says the previous answer still has 57 minutes left to live.

The better cache key is therefore not simply:

time

but:

state.

The important artefact isn’t:

“Here is what the model said last time.”

It is:

“Here is what the world looked like when the model said it.”

That makes caching part of the reasoning architecture rather than simply a performance optimisation.

New data is not the same thing as new information

This became another surprisingly important distinction.

APIs produce new data all the time.

Timestamps change.

Records get refreshed.

News feeds return the same stories again.

Providers correct formatting.

A player’s display name changes.

An article receives an updated timestamp because somebody fixed a typo.

Technically, something changed.

Semantically, nothing happened.

If every raw data mutation invalidates an analysis, the application will repeatedly wake the model up to tell it that the universe remains essentially identical.

That is not intelligence.

That is a very expensive heartbeat monitor.

The system therefore needs to distinguish between a raw delta and a semantic delta.

A player going from:

healthy → questionable

is potentially meaningful.

A player going from:

Luke McCaffrey → Luke McCaffrey 

because somebody added whitespace is not.

A new article saying that a player attended a charity event is new data.

A new article saying that he left training wearing a walking boot is new information.

The interesting engineering problem is therefore not simply change detection.

It is meaningful change detection.

Don’t hire an LLM to decide whether to hire an LLM

There is an obvious temptation here.

We have a model capable of interpreting language.

Why not give every new item to the LLM and ask whether it is important?

Sometimes that is absolutely appropriate.

But if you do it indiscriminately, you have created a rather beautiful circular architecture in which you call the model to determine whether you should call the model.

You have, in effect, hired a consultant to decide whether you need to hire a consultant.

So I push as much of that classification as possible into deterministic code.

Known injury-state transitions can be rules.

Roster transactions are structured events.

Value changes can use thresholds.

Duplicate news can be identified through IDs, canonical URLs, hashes or similarity mechanisms.

Known irrelevant metadata can be excluded before comparison.

Only ambiguous cases need probabilistic interpretation.

That is an important general principle I have taken away from the project:

Use deterministic computation to reduce the problem before asking probabilistic computation to reason about what remains.

Then I made the prompts smaller

Once I had reduced the number of calls, the next obvious question was whether the calls I was still making contained far too much information.

The answer was yes.

Early LLM applications have a tendency to behave like nervous junior developers preparing their first production handover:

“Here is absolutely everything I know, just in case.”

Full player objects.

Complete roster dumps.

Repeated news articles.

Historical information.

Provider metadata.

Previous analysis.

Fields nobody remembers adding.

Fields I was too frightened to remove because perhaps the model had secretly become emotionally attached to them.

All of this consumes tokens, but token cost is only part of the problem.

More context does not automatically mean better context.

Irrelevant information competes with relevant information.

Duplicated information can distort importance.

Old information can conflict with new information.

Large contexts make failures harder to understand.

So prompt construction became a deliberate pipeline of its own.

Select the relevant entities.

Resolve identifiers.

Filter stale information.

Deduplicate news.

Include only the state necessary for the current decision.

Include the previous recommendation when revision is required.

Represent uncertainty explicitly.

Ask a specific question.

Request a constrained output.

In other words, the model stopped receiving the database and started receiving a purpose-built data product.

Prompt engineering became data engineering

This was probably one of the more interesting conclusions.

People talk a lot about prompt engineering, and there is obviously skill involved in specifying tasks clearly, but in a real application the quality of the prompt is heavily determined before anybody writes the instruction.

The difficult questions are things like:

Which player does this record refer to?

How old is this information?

Which source produced it?

Has it already been processed?

Does it supersede another fact?

Is this the current roster state?

Was the previous recommendation generated before or after this event?

Those are data engineering questions.

A prompt is ultimately the presentation layer over those decisions.

Stable identifiers matter.

Timestamps matter.

Provenance matters.

Schemas matter.

Freshness matters.

Ordering matters.

The prompt is not your database.

It is not your event log.

It is not your cache.

It is not your state store.

And despite some heroic attempts I have seen, it is definitely not a substitute for having a schema.

The prompt is an interface between structured application state and a probabilistic reasoning system.

Once I started treating it that way, the entire design became easier to reason about.

Something unexpected happened

I originally made these changes to reduce token consumption.

That worked.

But something more interesting happened.

The application got better.

Responses became more focused because the model received less irrelevant information.

Repeated runs became more consistent because unchanged state did not trigger fresh inference.

Latency improved because there were fewer model calls.

Debugging improved because every model invocation had an identifiable reason.

The deterministic components became responsible for things deterministic software is very good at:

comparison, filtering, deduplication, state management, validation and rules.

The model became responsible for something LLMs are actually rather good at:

reasoning about ambiguous information when something meaningful has happened.

That separation seems obvious now.

It was not obvious when I started.

Debugging AI stopped being quite so mystical

This architecture also improved observability enormously.

When every scheduled run sends a giant prompt to a model and receives another block of prose, answering the question “why did the recommendation change?” can become surprisingly difficult.

Was the input different?

Was there a new news article?

Was the article duplicated?

Did the prompt template change?

Did a provider change some metadata?

Did the model simply interpret identical evidence differently?

Did I accidentally include a player from another league because two people have similar names?

Did Mercury enter retrograde?

With explicit state tracking, the problem becomes much more conventional.

I can inspect the previous snapshot.

I can inspect the new snapshot.

I can see the delta.

I can see which event invalidated the previous analysis.

I can inspect the exact model input.

I can compare the old and new output.

I can record model version, prompt version, token usage, timestamps and relevant state identifiers alongside the result.

“The AI did something weird” stops being a diagnosis and becomes what it should always have been:

the beginning of the investigation.

The architecture became event-driven

The first implementation was fundamentally polling-driven.

A timer fired.

The application collected everything.

The model analysed everything.

The improved version still polls some sources because the real world stubbornly refuses to provide a beautifully designed event stream for every API I would like to consume.

But polling is now a mechanism for discovering events, not a reason to run analysis.

That gives us a much more useful conceptual pipeline:

Poll → Detect → Classify → Invalidate → Schedule → Analyse → Persist

A new injury report does not necessarily require every league to be reanalysed.

It affects leagues containing that player.

Perhaps it affects only decisions involving that player and their replacements.

A roster transaction invalidates the appropriate roster analysis.

A trade-value movement may matter only if it crosses a decision threshold.

This starts looking suspiciously like traditional distributed systems engineering.

Idempotency matters.

Event keys matter.

Deduplication matters.

Retries matter.

Backoff matters.

Observability matters.

Failure handling matters.

The application needs to know whether an event has already been processed.

It needs to avoid running an expensive analysis twice because a worker timed out after the model responded but before the result was committed.

It needs to distinguish retryable provider failures from permanent failures.

It needs enough metadata to reconstruct what happened afterwards.

None of those requirements disappeared because I put an LLM into the architecture.

If anything, they became more important, because the operation in the middle is now slower, more expensive and less deterministic than most of the functions surrounding it.

“Agentic” does not mean “ask an LLM everything”

This is probably the biggest lesson I have taken from the whole project.

There is a temptation at the moment to equate “agentic” with putting an LLM inside a loop.

Get some information.

Call model.

Get more information.

Call model.

Call another model.

Ask the first model what it thinks about what the second model thought.

Perhaps introduce a third model to adjudicate.

Eventually somebody receives an invoice large enough to qualify as an architectural artefact.

Useful agency does not require constant inference.

A good agent should know when not to think.

It should know when to wait.

It should know when nothing has changed.

It should know when a deterministic rule is more reliable than generated reasoning.

It should know when its previous answer remains valid.

It should know when it does not have enough information.

It should know which state has been invalidated and which has not.

Nobody is going to produce an exciting conference demo in which an AI agent examines a state fingerprint and proudly announces:

“I have decided to do absolutely nothing.”

But in production systems, doing nothing can be the correct behaviour.

And correct behaviour is generally more useful than an impressive animation.

The model is not the architecture

One of the traps in AI development is becoming so obsessed with model selection that everything around the model becomes secondary.

Which model?

Which context size?

Which benchmark score?

Which reasoning mode?

Which provider?

Those questions matter.

But a better model does not rescue an application that sends stale information, duplicates records, loses state, cannot explain why an answer changed and repeatedly asks expensive probabilistic systems to perform work that could have been handled by a database query.

The surrounding software determines:

  • which facts reach the model;
  • whether those facts are current;
  • whether they are duplicated;
  • whether they are relevant;
  • what previous state is available;
  • what changed;
  • why analysis was triggered;
  • whether the output can be explained later;
  • whether failures can be retried safely;
  • and whether the economics still work when usage increases by a factor of 100.

This is why AI engineering increasingly looks to me like a mixture of software engineering, data engineering and probabilistic systems engineering rather than an entirely new discipline in which we throw away everything we learned over the previous fifty years.

You still need schemas.

You still need queues.

You still need caches.

You still need indexes.

You still need logs.

You still need metrics.

You still need regression tests.

You still need to understand failure modes.

And occasionally you need to remember that a SQL query has been sitting quietly in the corner for decades, waiting patiently for everyone to finish talking about agents.

The irony of building AI software

The more I have worked on this project, the less impressed I have become by applications that demonstrate their sophistication by making lots of AI calls.

Calling an LLM is easy.

Knowing when to call one is architecture.

Knowing when not to call one is architecture.

Knowing what context to give it is architecture.

Knowing what state to retain between calls is architecture.

Knowing what invalidates an existing answer is architecture.

Knowing which decisions should remain deterministic is architecture.

Knowing why an answer changed is architecture.

Knowing how the application behaves when the model is unavailable, slow or confidently wrong is architecture.

The model matters enormously, but it is still a component.

The software surrounding it determines whether you have built a useful product or an expensive demonstration with a very persuasive loading spinner.

My fantasy football application still uses AI.

It just uses considerably less of it.

And the result is cheaper, faster, more deterministic, more observable and easier to debug.

Most importantly, the model is now being used for judgement rather than bookkeeping.

That is probably the most important lesson I have taken from building it.

The objective should not be to find as many opportunities as possible to invoke an LLM.

The objective is to identify the relatively small number of places where probabilistic reasoning genuinely improves the system, engineer everything around those points properly, and then have the discipline to leave the model alone when it has nothing useful to do.

Which, somewhat inconveniently for anyone measuring AI adoption by token consumption, means that using less AI has made this a considerably better AI product.

As for whether it can finally explain what the Chicago Bears are doing?

Some problems may simply be beyond artificial intelligence.

Although, to be fair, I have not yet tried giving it a larger context window.


This is an expanded version of an article originally published on Medium.

If you enjoyed this, you can read the original article and follow my other writing on AI, software engineering, agentic systems and the occasionally questionable things I build with them on Medium.

Read the original article on Medium →

The Gaffer Next: It Is Starting to Become a Football Management Game

A few weeks ago, The Gaffer Next was mostly a specification, a collection of ideas, and a slightly unreasonable desire to build a football management game for the ZX Spectrum Next.

Now it actually does things.

That feels like progress.

(This is an in-progress screenshot from the loading screen – recognise the manager?)

The project is a homage to One-Nil Soccer Manager, the old DOS football management game from Scottish developer New Era Software. I wanted to capture some of what made those older management games so compelling: relatively simple presentation, lots happening underneath, and that dangerous ability to make you think, “I will just play one more match.”

The important bit is that I am not trying to build Football Manager on an 8-bit machine. The aim is to build something that feels like it belongs on the Spectrum, while taking advantage of what the Next can do.

And the foundations are now starting to appear.

What is working?

The loader is complete, including a proper loading image. See above!

That might sound like a small thing, but there is something enormously satisfying about seeing a game actually load into its own title screen rather than staring at whatever temporary developer screen happens to be there.

More importantly, the competition structure is now taking shape.

Three tournament types are implemented:

  • League
  • Cup
  • FA Cup

The game can also generate the teams needed to populate those competitions.

Underneath that, some of the less glamorous but rather important management-game machinery is appearing. There is now logic for arranging friendlies, along with the logic required to settle everything at the end of a season.

That last part is particularly significant.

A football management game is not really a collection of individual matches. It is a simulation that needs to survive the transition from August to May and then correctly turn itself back into August again.

Promotion, relegation, competition state, squads and the new season all have to remain coherent. Getting that lifecycle designed early should make everything built on top of it considerably easier.

And now there are players

The initial squad display logic is also in place.

This is where the project starts becoming noticeably more tangible.

Until this point, much of the work has been infrastructure: tournaments, teams, scheduling and season state. Necessary, but largely invisible to the person eventually playing it.

Once you can open a squad and start looking at footballers, however, it begins to feel like a management game.

The eventual idea is that the player should be able to move naturally between the high-level club view and individual footballers, seeing their value, position and abilities without drowning in information.

That simplicity is deliberate.

The old management games were remarkably good at giving you just enough information to make a decision — and then allowing you to convince yourself that signing a 34-year-old midfielder with questionable pace was somehow tactical genius.

Building the boring bits first

One thing I have deliberately resisted is immediately jumping to the glamorous parts: match graphics, transfers, tactics and all the other things that make good screenshots.

The underlying simulation needs to work first.

At the moment the rough progression looks something like:

load game → create football world → create competitions → populate teams → schedule matches → manage squads → play season → settle season → repeat

If that loop is solid, I have a foundation on which the actual management game can grow.

If it is not, I have a very attractive ZX Spectrum Next program that eventually collapses in a heap sometime around the third Saturday in February.

I have written enough software over the years to know which one I would prefer.

Still a long way to go

There is obviously plenty left to build.

The squad system needs to develop further. Player attributes and progression need to feed into matches. Transfers and finances need to become meaningful. Tactical decisions need consequences. The match engine needs to turn all of that data into believable football.

And, critically, it all needs to remain achievable within the constraints of the machine.

But the interesting milestone this week is not any individual feature.

It is that The Gaffer Next has crossed a small but important boundary.

It is no longer just a design document for a football management game.

There is now a football world being generated underneath it.

And somewhere in there, presumably, there is already a chairman preparing to sack me.

More soon.

How I Cut AI Token Usage in My Dynasty Fantasy Football Dashboard

My dynasty fantasy football dashboard started as a fairly traditional AI pipeline: collect player data, gather news and injuries, send every player to a language model, and display the resulting recommendations.

It worked—but it was doing far more AI work than necessary.

A healthy bench player with no recent news received essentially the same treatment as an injured starter whose role had materially changed. Players appearing in multiple leagues could generate repeated analysis. Even when nothing changed, the system risked paying to reach the same conclusion again.

I redesigned the pipeline around a simpler principle:

Python should manage facts and state. The language model should only handle decisions that genuinely require reasoning.

The result is a provider-independent system that makes fewer model calls, sends smaller prompts, produces less output, and caches results more safely.

The original architecture

The pipeline gathers data from several sources:

  • Sleeper rosters and player information
  • FantasyPros rankings
  • Dynasty trade values
  • Contract information
  • Player news and injury reports

Originally, the reasoning stage analysed individual players and returned a detailed object for each one:

{
  "trend": "UP",
  "confidence": "MEDIUM",
  "summary": "The key development affecting the player.",
  "fantasy_impact": "SHORT",
  "recommendation": "Hold and monitor usage.",
  "dynasty_note": "Long-term value remains stable.",
  "contract_note": "Under contract through 2027.",
  "roster_status_note": "Competing for the WR2 role.",
  "flags": ["depth_chart"]
}

That output is useful for an important player. It is wasteful for every player on every roster.

The system prompt, player metadata, contract details and news text all consumed input tokens. Requiring several generated fields per player also created a substantial output-token cost.

Moving from player analysis to league analysis

The biggest change was replacing per-player model calls with one compact request per materially changed league.

Instead of asking the model to analyse everybody, Python builds a league snapshot:

{
  "league": {
    "id": "123",
    "name": "The League",
    "season": "2026",
    "format": "dynasty"
  },
  "roster": [
    {
      "id": "9509",
      "n": "Example Player",
      "p": "RB",
      "age": 24,
      "role": "RB1",
      "starter": true,
      "ir": false,
      "taxi": false,
      "value": "Early 1st"
    }
  ]
}

Only players with a meaningful signal receive additional injury or news fields.

The model then returns a league overview and a short list of actionable exceptions:

{
  "overview": "The roster remains strong at running back but has two injury situations to monitor.",
  "actions": [
    {
      "player_id": "9509",
      "trend": "WATCH",
      "confidence": "MEDIUM",
      "action": "Hold and monitor practice participation.",
      "reason": "A new injury designation creates short-term uncertainty.",
      "flags": ["injury"]
    }
  ]
}

Stable players are omitted. Python supplies deterministic defaults for them, so they consume no output tokens.

The response is capped at eight actions and 900 output tokens per changed league.

Storing player facts once

Another improvement was separating global player facts from league-specific context.

The pipeline now maintains three layers:

player_store.json
    Canonical facts stored once per player

league_snapshots/
    Small league-specific roster and status records

league_analysis_cache.json
    Successful model analysis for each league

A player’s name, age, NFL team, contract, trade value, injury and news belong in the canonical player store.

Fields such as starter status, taxi status, IR status and roster designation belong in the league snapshot.

This prevents shared facts from being copied into every league’s stored data while still allowing recommendations to consider league-specific context.

Reducing news tokens

Scraped news can be surprisingly verbose. A single source may provide a headline, article body and separate analysis section. Multiple sites may report the same event using slightly different wording.

Sending all of that to the model is rarely useful.

The new pipeline:

  • Deduplicates news by normalized headline.
  • Includes at most two news events per player.
  • Caps headline length.
  • Excludes article bodies and generic commentary.
  • Adds news fields only to signal-bearing players.

The model generally needs the material fact, its source and its date—not several paragraphs of fantasy prose written by someone else.

Skipping quiet leagues

Before making a provider request, Python checks whether the league contains any material signals.

Signals include:

  • New player news
  • Injury designations
  • IR status
  • Taxi-squad status

If none exist, the model is skipped completely:

Skipping AI call for league=The League:
quiet league with no material signals

The dashboard receives deterministic text instead:

No material roster news or injury changes this cycle.

An empty roster also skips the provider.

This is an important distinction: prompt optimization makes calls cheaper, but avoiding unnecessary calls altogether is better.

Building a safer cache

Caching AI output sounds straightforward until failures enter the picture.

Each league analysis is fingerprinted using:

  • Selected provider
  • Selected model
  • Exact league payload

If all three are unchanged, the cached analysis is reused.

Including the provider and model is important. Switching from one model to another should produce a fresh analysis rather than silently serving text generated by the previous configuration.

I also found and fixed a more subtle failure mode.

An earlier implementation stored whatever analysis was displayed after a provider attempt—even an error fallback—under the new fingerprint. That meant a temporary outage could produce:

Analysis unavailable; showing current roster facts.

The fallback would then become a valid cache hit. Future runs with the same payload would skip the provider indefinitely.

The corrected rule is simple:

Only successful provider responses are written to the analysis cache.

If a request fails:

  • An older valid analysis may still be displayed.
  • The previous cache entry remains completely unchanged.
  • A fallback can be displayed when no prior result exists.
  • The next identical run retries the provider.

This prevents transient failures from poisoning the cache.

Supporting multiple AI providers

The reasoning layer originally depended directly on Anthropic. I replaced that assumption with a small provider adapter.

The provider is now selected through environment variables:

AI_PROVIDER=openai
OPENAI_API_KEY=...
OPENAI_MODEL=gpt-5-mini

Or:

AI_PROVIDER=anthropic
ANTHROPIC_API_KEY=...
ANTHROPIC_MODEL=claude-haiku-4-5

OpenAI is the default, but the rest of the pipeline does not know which provider is active. Both paths return the same validated internal structure.

Model selection follows a defined precedence:

  1. Provider-specific override
  2. Shared AI_MODEL override
  3. Legacy model setting
  4. Sensible provider default

This makes it possible to compare providers or change models without editing application code.

Making OpenAI responses more reliable

When I first tested the OpenAI Responses API, the request returned HTTP 200 but contained no visible JSON. The application then attempted to parse an empty string.

To make that path more reliable, I added:

  • Minimal reasoning effort
  • Low text verbosity
  • A strict JSON schema
  • Explicit empty-output detection
  • Diagnostics for incomplete responses
  • Reporting of response status and output item types

The strict schema ensures that both providers ultimately feed the same application contract, while minimal reasoning preserves more of the 900-token budget for visible output.

Logging what the pipeline is doing

Optimizing an AI pipeline is difficult if its decisions are invisible.

Before a real request, the pipeline now logs:

Calling AI provider=openai model=gpt-5-mini for league=The League

It also logs the exact JSON payload being sent.

Afterwards, it logs the final overview and its source:

Overall league analysis for league=The League source=model:
The roster is stable, with one injury situation worth monitoring.

Other possible sources include:

  • cache
  • quiet
  • empty_roster
  • stale_cache_after_error
  • error_fallback

This makes it easy to confirm whether displayed text came from a fresh model response, an earlier result or deterministic application logic.

Protecting the architecture with tests

Several of these improvements were vulnerable to being accidentally lost during merges, so I added regression tests around the behavior rather than relying on comments.

The tests verify that:

  • OpenAI is the default provider.
  • Anthropic remains selectable.
  • Provider-specific model overrides work.
  • Identical payloads reuse successful analysis.
  • Changing the model invalidates the cache.
  • Changing the provider invalidates the cache.
  • Failed requests are never cached.
  • An older valid cache entry survives a failure unchanged.
  • Quiet leagues make no provider call.
  • The orchestrator calls the active league reasoning agent.

The complete project suite now contains more than 150 passing tests.

The broader lesson

The most effective token optimization was not a shorter prompt. It was moving responsibility out of the prompt.

Python is better suited to:

  • Retrieving and normalizing data
  • Deduplicating news
  • Detecting changes
  • Managing cache fingerprints
  • Formatting known contract information
  • Producing defaults for quiet players

The model is most useful when it receives a small set of meaningful facts and answers a focused question:

Given this league and these material changes, what should the manager pay attention to?

That shift made the system cheaper, easier to inspect and less dependent on any particular provider. More importantly, it made the resulting dashboard more useful: instead of generating commentary for everybody, it highlights the decisions that may actually require action.

The GitHub repo is here: https://github.com/lukecampbell-cf/DynastyDashboard/

My Mum: Maggie

My mum was a real force of nature and passed away suddenly but peacefully recently.

I wanted to publish my eulogy in her memory.

It goes like this:


Mum.

Firstly, I can see so many familiar faces here today, including people I haven’t seen for years.

I know that seeing so many people here would have made her incredibly happy.

Friendship meant everything to her.

On behalf of Sarah, Rhiann and myself, and the rest of our family, thank you all for being here today, for your messages, your kindness, your support, and for those joining us online.

It has meant more to us than you’ll ever know.

—-

As you all know, my mum was an amazing human being.

My mum was amazing at many things, but she was especially great at solving problems.

Her 20 plus years as a theatre manager meant that her job involved managing actors and directors: creative people with very strong opinions. But she always treated people with kindness.

This was a lesson that I’ve taken with me – above all else, she taught me to do the right thing by people. Always. Without exception. And without ever stopping to think about the personal cost.

And If you ever asked my mum for advice, she never left it there. The next day there would be an email, a WhatsApp or a phone call because she’d thought of something else overnight that might help.

Her advice was always sensible, always kind, and almost always exactly what you needed to hear.

And it wasn’t just me. Or my sisters. Or My nieces for that matter.

Looking around the room, I can already see a few knowing smiles. I suspect many of you received exactly the same treatment.

She simply couldn’t stop helping people.

She would quite literally have given you the shirt off her back without a second thought.

That was the measure of her. Thoughtful. Loyal. Kind.

They are wonderful qualities.

On a slightly lighter note, though…The best story has to come from a trip we took together. I was lucky enough through work to spend some time in the Bahamas, and I managed to take Mum with me.

It was a hard life, honestly.

So one evening, after I’d finished “working” we were chatting and I was asking her how her day had been. She said:

“Son, it was lovely…but as a matter of fact, I met a gigolo today.”

I remember thinking, where on earth is this conversation going?

She said,

“He told me he specialised in taking older ladies somewhere they hadn’t been for a very long time.”

She continued:

“But – you know he seemed awfy confused when I said…what… Renfrew?”

After I’d finished laughing, I then asked:

“Well… was he at least good looking?”

She said,

“No… and he smelt really bad as well!”

That was my Mum.

Funny. Quick-witted. Great company. Always ready with the perfect comeback.

Many of my old colleagues knew her exactly that way.

And that’s what I’ll remember most.

My mum always told me she was proud of me.

But she never once asked whether I was proud of her.

Today, I’d like to answer that question with just three words.

Every single day.

Mum, I’ll be proud of you for the rest of my life.

And finally…

Mum…

I just want to tell you that I’m so glad that I came back from Australia to spend just a little

more time with you. Every day without you will feel like an eternity.

There is a hole in our hearts that I’m not sure will ever be filled.

I will miss you every single moment for the rest of my life, and I would give anything for just another few minutes with you 💔💔💔

I’d like to finish by reading the ending of a poem that my mum sent me as I was leaving to go to Australia, it broke my heart then, and it’s even more poignant now:

“I miss you with every beat of my heart.

Oh I always did”

Mum – I will miss you with every beat of my heart – and I always will.

The Best Leadership Training I Ever Had Wasn’t in Technology

When people ask me where my leadership style came from, I suspect they are expecting an answer involving engineering.

Perhaps a manager I admired.

A particularly good CTO.

A leadership course.

Thirty years of experience building software.

Maybe even one of those books with an eagle, a mountain or a lone climber standing triumphantly at the top of something.

The truth is rather less glamorous.

Most of it came from a theatre in deepest Lanarkshire.

My mum managed it for more than twenty-five years. It was a proper community theatre, just outside Glasgow, where there were amateur dramatics both on the stage and, if we are being completely honest, a fair amount of amateur dramatics off it as well.

Looking back, I sometimes think the performances in the foyer before curtain-up could have given whatever was happening under the lights a decent run for its money.

Everybody had an opinion. Somebody had fallen out with somebody else. Something important had mysteriously disappeared five minutes before the audience arrived. A volunteer would announce they were never coming back. An actor would suddenly decide they had forgotten every line they had ever learned.

And somehow, in the middle of all that perfectly ordinary chaos, my mum would quietly restore order.

Not by making herself the loudest person in the room.

Not by reminding everyone she was the manager.

Simply by understanding people.

As a child I thought she wandered around drinking tea, carrying clipboards and chatting to actors.

As an adult, after three decades leading technology teams, I realised she had been doing something altogether more difficult.

She was solving people-shaped problems.

The older I get, the more convinced I become that almost every leadership challenge is exactly that.

Technology has an unfair reputation for being difficult.

It certainly can be.

I’ve spent enough evenings staring at log files, production incidents and architecture diagrams to know that software has an extraordinary ability to humble even the most experienced engineer.

But technology is still logical.

When something breaks there is usually a reason.

It might take hours or days to find it, but somewhere there is an explanation patiently waiting to be discovered.

People are different.

People arrive carrying worries you know nothing about.

People misunderstand each other.

People lose confidence.

People become frightened to ask questions because they worry those questions might make them appear less capable.

People quietly decide they are no longer valued long before anyone notices.

I’ve worked with enough engineering teams to know that projects rarely fail because somebody chose the wrong framework.

They fail because trust quietly disappeared while everyone was busy discussing frameworks.

That is where leadership actually lives.

One of the reasons I enjoy interviewing engineers is that I am far more interested in how someone thinks than how many technologies they can remember. Programming languages can be learned. Cloud platforms change. AI evolves almost weekly.

Curiosity is harder to teach.

Kindness is harder still.

Over the years I’ve become increasingly convinced that the best engineers are almost always the most curious ones. They ask better questions. They listen before answering. They are prepared to change their minds when new evidence appears. They don’t feel the need to win every discussion because they understand that building software is a team sport.

I don’t think I learned that from technology.

I think I watched it happening in a theatre long before I ever wrote my first line of code.

One sentence has stayed with me my entire life.

“Kindness costs nothing.”

It sounds so simple that it almost risks being ignored.

Business sometimes treats kindness as though it is the opposite of high standards. As though being kind means avoiding difficult conversations or pretending everything is fine.

My experience has been exactly the opposite.

Real kindness means caring enough to tell somebody the truth.

It means believing they deserve the opportunity to improve.

It means assuming positive intent before assuming incompetence.

It means recognising that everyone you meet is fighting battles you know absolutely nothing about.

I’ve made plenty of mistakes throughout my career, but I’ve never regretted trying to treat people with kindness.

I have occasionally regretted failing to do so.

Of course, kindness did not mean I escaped consequences growing up.

Like every Scottish mother worth her salt, mine possessed a remarkable superpower.

The look.

Every Scottish reader knows exactly the one I mean.

No shouting.

No swearing.

No dramatic speeches.

Just… the look.

It could travel the length of an entire theatre foyer.

I remain unconvinced it obeyed the normal laws of physics.

The remarkable thing was that she rarely needed to say anything afterwards because I already knew.

I was wrong.

Not because I had broken some arbitrary rule.

Because I had disappointed somebody whose opinion mattered enormously to me.

Oddly enough, I still think about that look today.

Not because I want anyone I work with to fear disappointing me.

Quite the opposite.

The best leaders I’ve known have never relied on authority.

They never needed to.

Their standards were obvious.

You wanted to do good work because you respected them.

Fear creates compliance.

Respect creates commitment.

Those are very different things.

As I’ve become older, I’ve also found myself caring less about being remembered for the systems I’ve built.

Technology has a wonderfully short memory.

Programming languages come and go.

Architectures evolve.

Platforms are replaced.

The system you are immensely proud of today will probably become somebody else’s migration programme in ten years’ time.

That isn’t depressing.

It’s simply the nature of our profession.

What survives is something else entirely.

People remember how you made them feel.

They remember whether you listened.

Whether you stayed calm.

Whether you gave them confidence when they had very little of their own.

Whether you made them better than they thought they could be.

Long after the software has disappeared, those memories remain.

Perhaps that is the real legacy any of us leave behind.

I don’t think my mum ever described herself as a leader.

She certainly never spoke about organisational culture, emotional intelligence or servant leadership. In fact, I suspect she would have rolled her eyes spectacularly at most modern leadership jargon before quietly getting on with solving whatever problem was in front of her.

She simply turned up.

She cared about people.

She believed kindness cost nothing.

She expected you to do the right thing, even when nobody was watching.

And if you failed to meet that standard…

Well…

There was always the look.

I don’t write this because I think my story is unique.

In fact, I suspect the opposite.

I suspect that if we all stopped for long enough to think about it, most of us would realise that the people who shaped our leadership philosophy never carried the title of leader at all.

They were parents.

Teachers.

Coaches.

Neighbours.

Friends.

Ordinary people quietly demonstrating extraordinary character without ever imagining someone was paying close enough attention to learn from them.

I’ve spent more than thirty years working in technology, but the older I become, the less interested I am in asking successful leaders what books they read or which management theories they follow.

I’m much more interested in asking a different question.

Who taught you how to lead?

Because I have a growing suspicion that the answer to that question explains far more about us than anything we’ve ever written on our CVs.

I loving memory of my mum Maggie Aitken 05.53 – 27.05.2026 – a force to be reckoned with and so loved by me, my family and her friends.

“Hurt Me Plenty”: Building an AI That Actually Tells You When You’re Wrong

There’s a problem with most AI assistants.

They’re too polite.

They hedge. They soften. They “suggest” instead of telling you what’s actually broken. You end up with something that sounds helpful, but quietly lets bad thinking, weak ideas, or flawed execution slip through.

At the other extreme, you’ve got the idea of full, unfiltered honesty. If you’ve seen Interstellar, you’ll remember TARS and the adjustable honesty setting. It’s a great concept, but in practice, 100% blunt honesty isn’t that useful either. It tips into abrasive, unproductive, and sometimes just noise.

What you actually want sits somewhere in the middle:

Clear. Direct. Honest. But still constructive.

That’s the gap I built a small AI “skill” for.

I call it: Hurt Me Plenty.

Why “Hurt Me Plenty”?

The name is borrowed from DOOM.

If you’ve played it, you’ll know “Hurt Me Plenty” sits in that middle ground. Not the easiest mode, not the most punishing either. Just enough pressure to keep you sharp.

And that’s the key point.

It wasn’t about being punished endlessly. It was about how long you could operate effectively under pressure. Stay focused. Stay accurate. Keep moving forward without getting sloppy.

That’s exactly the behaviour I want from AI.

Not brutal for the sake of it. Not soft to the point of uselessness.

Just enough intensity to expose mistakes early and force better decisions.

The Problem: Polite AI Is a Risk Multiplier

In engineering, product, or leadership, bad ideas don’t usually fail loudly. They fail quietly.

  • A flawed architecture gets a “this could work” instead of “this will break under load.”
  • A weak strategy gets “interesting direction” instead of “this won’t deliver commercial value.”
  • A risky plan gets “worth exploring” instead of “this will cause problems later.”

Polite AI reinforces that.

It mirrors the worst version of corporate feedback loops. Everything sounds reasonable. Nothing gets challenged properly.

And if you’re operating in a high-stakes environment, especially regulated systems like iGaming or fintech, that’s dangerous.

You don’t need encouragement.

You need signal.

The Other Extreme: Brutal Honesty Doesn’t Scale Either

There’s a temptation to swing the other way.

“Just tell me the truth. No filter.”

Sounds good in theory. In reality, it breaks down quickly:

  • It becomes performative bluntness rather than useful critique
  • It lacks prioritisation (everything is “wrong”)
  • It erodes trust instead of building it
  • It doesn’t guide you toward a better outcome

Raw honesty without structure is just noise with attitude.

The Middle Ground: Precision Critique

What I actually want from AI is this:

  • Tell me what’s wrong
  • Tell me why it matters
  • Tell me what to do about it
  • Don’t sugar-coat it
  • Don’t be a dick about it

That’s the design philosophy behind Hurt Me Plenty.

It’s not about being harsh. It’s about being usefully exacting.

What “Hurt Me Plenty” Actually Does

At a practical level, it’s a prompting layer / skill that changes how the AI behaves.

Instead of defaulting to “helpful assistant,” it switches into something closer to:

A senior technical reviewer who is accountable for the outcome.

The behaviour shift is subtle but important:

1. No Passive Agreement

If something is weak, it gets called out directly.

Not:

“This is an interesting approach…”

But:

“This will likely fail because X, Y, Z.”

2. Prioritised Criticism

Not everything matters equally.

The skill forces the model to focus on:

  • Critical flaws first
  • Then structural issues
  • Then optimisation or polish

3. Reasoned, Not Emotional

It avoids tone for tone’s sake.

Every critique has to tie back to:

  • risk
  • performance
  • scalability
  • commercial impact
  • delivery feasibility

4. Actionable Corrections

Pointing out problems is easy.

Fixing them is where value is.

Each critique is paired with:

  • a suggested alternative
  • or a direction of improvement
  • or a decision framework

5. No False Positivity

If something is genuinely good, it says so.

But it doesn’t pad weak work with artificial praise.

Where This Becomes Useful (Very Quickly)

I’ve found this kind of behaviour disproportionately valuable in a few areas:

Architecture & Engineering Decisions

Cuts through “this might work” and gets to:

  • will it scale?
  • where will it break?
  • what’s the real bottleneck?

Strategy & Commercial Thinking

Particularly in iGaming and regulated systems:

  • does this actually deliver value?
  • is this compliant in practice, not theory?
  • where’s the hidden risk?

Internal Communication

Drafts, proposals, updates:

  • is this clear?
  • is it credible?
  • would a CTO / CEO actually buy this?

AI-Assisted Development

Ironically, this is where AI often fails most.

Without critique, you get:

  • syntactically correct code
  • structurally poor systems

“Hurt Me Plenty” forces:

  • better design decisions
  • clearer trade-offs
  • fewer hidden problems

Try It Yourself

I’ve put the skill up publicly so you can use or adapt it:

It’s intentionally simple. This isn’t about complex tooling. It’s about changing the behaviour of the system you’re already using.

Drop it into your workflow and you’ll feel the difference almost immediately.

The Key Insight: AI Needs Friction

Most people are trying to make AI smoother.

More helpful. More agreeable. More aligned.

That’s only half the story.

For real work, especially at senior levels, you need friction:

  • something that challenges assumptions
  • something that flags risk early
  • something that forces better decisions

That’s what this kind of skill introduces.

Not hostility.

Not ego.

Just useful resistance.

Final Thought

If your AI always agrees with you, it’s not helping you.

It’s just accelerating your mistakes.

The goal isn’t an assistant that makes you feel right.

It’s one that helps you be right.

And sometimes, that means hearing exactly where you’re getting it wrong.

What Scotland’s Home of the Year Teaches Us About Building Great Software

There’s a deceptively simple scoring model at the heart of Scotland’s Home of the Year:

  • Functionality
  • Distinctiveness
  • Style

👉 Watch it here:

https://www.bbc.co.uk/programmes/m00043v0

Three axes. That’s it.

And yet—watch a few episodes and you realise something important:

👉 The winners aren’t the most expensive homes
👉 They’re not the most technically complex
👉 They’re not even the most “architecturally impressive”

They’re the ones where everything works together.

That should sound very familiar.

The scoring system (and why it matters)

1. Functionality → Does it actually work?

In the show, judges constantly ask:

  • Does the space flow?
  • Is it practical for how people live?
  • Does it solve real constraints?

Homes win when they are liveable, not just beautiful.

Software equivalent:

  • Does the system do what users actually need?
  • Is it reliable under real-world conditions?
  • Can teams operate it at 2am during an incident?

This is your “boring and stable wins” layer.

A stunning system that falls over under load is the architectural equivalent of a glass house you can’t heat.

2. Distinctiveness → What makes it different?

Winning homes always have something unmistakable:

  • A bold concept
  • A strong point of view
  • A clear sense of identity

Judges often say they can “tell who lives there” just by looking at the space.

Software equivalent:

  • What is your product’s edge?
  • Why does this exist vs competitors?
  • What’s the opinion baked into the design?

This is where most systems fail.

They become:

  • Generic
  • Over-engineered
  • Indistinguishable from everything else

If your system could be swapped with a competitor’s and no one notices, you’ve built a house with no personality.

3. Style → How it feels

This is the subtle one.

Style in the show isn’t about trends—it’s about coherence:

  • Does everything belong?
  • Is there a consistent language?
  • Does it feel intentional?

The best homes feel effortless, even when they’re complex.

Software equivalent:

  • Clean APIs
  • Consistent patterns
  • Thoughtful UX
  • Clear developer experience

Style is what turns:

  • “It works” → into “this is a pleasure to use”

The hidden fourth dimension: Cohesion

Here’s what the scoring model doesn’t explicitly say—but the show makes obvious:

👉 The winning home is the one where functionality, distinctiveness, and style reinforce each other

Not compete.

That balance is everything.

Where software teams go wrong (the anti-patterns)

❌ Over-index on “style” (architecture theatre)

  • Kubernetes for a demo
  • Microservices with no scale problem
  • “Look, it’s event-driven”

Result: Beautiful house, no heating

❌ Over-index on “functionality”

  • Works, but painful to use
  • No product thinking
  • No differentiation

Result: Perfectly functional… and completely forgettable

❌ Over-index on “distinctiveness”

  • Reinventing everything
  • “Clever” over “useful”
  • No operational grounding

Result: A statement piece nobody wants to live in

The real lesson: design for how it’s lived in

The best insight from the show:

👉 Great homes are designed for the people who live in them—not the judges

That’s why personality matters.
That’s why constraints matter.
That’s why trade-offs matter.

Software parallel:

  • Design for users, not demos
  • Design for operators, not just builders
  • Design for evolution, not perfection

TL;DR

  • Functionality → It works, reliably
  • Distinctiveness → It has a point of view
  • Style → It’s coherent and usable

👉 Great systems win when all three align

Final thought

The best homes don’t feel engineered.

They feel right.

Same with software.

If people notice your architecture more than your product…

You’ve built something impressive.

But you haven’t built something great.

Biggest lessons from designing systems for demos #678 🚀

Seems easy to say. Keep it simple.

If you’re not in production yet, don’t pretend you are.

No one needs a Kubernetes cluster just so you can click through three screens and say
“look, it scales” 😅

Design your APIs as learning tools.

You will revisit them.
You will rewrite parts of them.

That’s not failure. That’s the job.

Self document everything.

If your API needs a separate explainer document just to understand what it does, you’ve already made life harder than it needs to be.

And most importantly, control the narrative 🎯

What you build should support the story you’re telling.

A good demo is not just working software.
It’s a clear journey from problem to value.

It’s also completely fine to say
“this part is coming soon”

In fact, it’s usually better than overbuilding something nobody actually needs.

It’s simple lessons, right?

But – the number of times I’ve seen teams turn a demo into an accidental architecture project is… impressive 😄

I, ahem, cough, may have done the same thing myself. Possibly. Ahem.

Next time, though, tell yourself this :-

  1. Build to learn.
  2. Demo to convince.

AI Sock Puppets Are Eating Our Lunch: Why the Gambling Regulation Gap Is Becoming Dangerous

AI-generated front profiles. Hidden ownership structures. Unregulated operators laser-focused on UK customers from outside the rules.

A recent piece making the rounds caught my attention because it mirrors exactly what forensic investigator João Mar has been exposing for months. And honestly, it left me thinking we have a much bigger problem on our hands than most people want to admit.

Let me be clear from the start: I’m a strong supporter of proper regulation. Good standards and real player protection matter. Without them, this industry becomes a race to the bottom. But even solid rules can create weird side effects — kind of like when you try to tidy your garage and somehow end up with more mess than you started with.

The Evolution That’s Actually Worrying

Black-market gambling operators have always existed. They’re the cockroaches of the iGaming world — tough, adaptable, and always finding a way to survive. What’s changed is how professional and scalable they’ve become.

These days we’re seeing:

•  AI-generated “front” profiles that look scarily real, complete with believable histories and posting habits.

•  Ownership structures so opaque they make tracing ultimate beneficial owners feel like a frustrating game of corporate hide-and-seek on expert difficulty.

•  Highly targeted campaigns aimed at UK players while operating comfortably outside any regulatory perimeter.

This isn’t some guy running dodgy sites from his bedroom anymore. These are organised operations using modern tools to exploit the gaps regulation has unintentionally created.

At the same time, fully licensed operators (the ones actually trying to do things properly) are dealing with a growing mountain of obligations: tougher affordability checks, stricter safer gambling requirements, rising compliance costs, and more complex rules around customer interactions. All of these exist for good reasons. They just also create real pressure — the kind that makes you feel like you’re running a business with one hand tied behind your back while carrying a heavy regulatory backpack.

The Gap That Should Concern Everyone

Here’s what worries me most: we’re watching a dangerous divide open up.

Regulated operators are becoming more constrained, more cautious, and slower to innovate. Unregulated operators are getting faster, more agile, and much harder to detect. And sitting in the middle are the customers — many of whom simply can’t tell which is which.

When players lose money on these unlicensed platforms with synthetic identities and hidden control, it disappears into a black hole. No protections, no responsible gambling tools, no proper dispute resolution. Just “thanks for your deposit, see you never.”

Using AI personas, masking real ownership, and hiding behind complex corporate layers goes against everything regulation is supposed to stand for: transparency, accountability, and genuine player safety.

Questions We Need to Face Head-On

From where I sit, this raises some important practical questions for the whole industry:

•  How do we keep strong consumer protections without accidentally driving more players straight into the unregulated space?

•  How can we create better visibility into ownership and control when everything is global and digital?

•  What roles should operators, suppliers, technology providers, and regulators actually play in fixing this?

This isn’t a side issue anymore. It deserves real collective focus — from compliance teams and tech builders to policymakers and the OSINT experts like João Mar who keep pulling back the curtain.

Has the Horse Already Bolted?

I’m genuinely interested in how others are seeing this, especially people working day-to-day in regulated environments. Can we still close this gap, or has the combination of cheap AI tools, global infrastructure, and mounting regulatory pressure already made the playing field permanently uneven?

I don’t have all the answers. But ignoring how quickly these tactics are evolving feels like a fast track to eroding the trust that good regulation is meant to build in the first place.

What do you think? Have you seen similar patterns in your own work? Drop a comment below — I’d especially love to hear from those on the regulated side or working with compliance and supplier tech.

Maybe together we can figure out how to make life harder for the AI sock-puppet operators and easier for the ones actually trying to play by the rules.

Rewatching Narcos: Mexico, still one of the best things on TV

I’ve been rewatching Narcos: Mexico recently.

It’s one of those series that’s even better the second time round. You’re not just following the plot. You start to notice how well it’s put together.

It feels real (because it largely is)

The obvious draw is the story. The rise of the Guadalajara cartel, the politics, the US–Mexico dynamic, the constant tension.

But what stands out on a rewatch is how grounded it all feels:

  • No over-dramatisation for the sake of it
  • Characters behave like actual people, not TV caricatures
  • Decisions have consequences, and they compound over time

You can see the system forming. Not just a “crime story”, but a supply chain, a power structure, a set of incentives. It’s basically organisational design… just with far worse outcomes.

It’s a masterclass in controlled storytelling

There’s a discipline to it that a lot of modern series lack.

Scenes are allowed to breathe. Dialogue isn’t rushed. Tension builds properly rather than being forced.

And crucially, it trusts the viewer to keep up.

No hand-holding. No over-explaining. Just: here’s what’s happening — pay attention.

Surprisingly useful for learning Spanish

One unexpected bonus: it’s actually great for picking up Spanish.

Not classroom Spanish. Real conversational cadence.

You start recognising phrases quickly:

  • “Tranquilo, hombre”
  • “Ahorita”
  • “¿Qué quieres?”

It’s repetitive enough to stick, but natural enough that it doesn’t feel like learning.

You won’t become fluent, but you will start understanding tone, rhythm, and intent — which is arguably more useful than memorising verb tables.

The bigger takeaway

On the surface, it’s about cartels.

Underneath, it’s about systems, power, and incentives.

Who controls what.

Who depends on who.

What happens when money, politics, and weak governance intersect.

That’s why it holds up so well. It’s not just a story — it’s a model of how complex systems evolve under pressure.

And like most systems, once it starts moving, it’s very hard to stop.

If you haven’t watched it, it’s well worth your time.

If you have — it’s even better the second time round.

Scrum vs Kanban: When Each Actually Works (and When It Doesn’t)

There’s a tendency in engineering teams to treat Scrum and Kanban as ideological choices.

They’re not.

They’re operating models. Tools. And like any tool, the only thing that matters is whether they help you deliver predictably, safely, and without unnecessary drama.

If you’re running a regulated platform, or anything at scale, the real question is:

What kind of work are we dealing with, and what behaviour do we need from the system?

Start With the Nature of the Work

Before picking Scrum or Kanban, you need to understand what’s actually flowing through your system.

Most teams deal with a mix of:

  • Planned product development
  • Unplanned operational work
  • Stabilisation and reliability improvements
  • Interrupt-driven support and incidents

Where teams go wrong is trying to force all of that into a single delivery model.

That’s where things start to creak.

Where Scrum Works Well

Scrum is at its best when the work is:

  • Predictable enough to batch
  • Outcome-driven
  • Aligned to a clear product goal
  • Relatively protected from interruption

In practical terms, when you can say:

“We believe we can deliver this set of outcomes over the next two weeks, and we’re willing to commit to that.”

Scrum works well for:

  • New feature development
  • Product roadmap delivery
  • Defined refactoring initiatives
  • Greenfield or controlled builds

What Scrum gives you

  • A forcing function for commitment and focus
  • A cadence for stakeholder alignment
  • A structure for measuring delivery predictability

But—and this is the key—Scrum assumes a level of system stability.

If your team is constantly interrupted, Scrum breaks down quickly.

You’ll see:

  • Half-completed sprints
  • Rolled-over work
  • Commitments no one believes in

At that point, you’re not doing Scrum. You’re creating reporting theatre.

Where Kanban Comes Into Its Own

Kanban is built for a different reality.

It assumes:

  • Work arrives unpredictably
  • Priorities shift
  • Interruptions are normal
  • Flow matters more than commitment

This aligns directly with:

  • Platform stabilisation
  • Production support environments
  • Incident-heavy systems
  • Legacy modernisation programmes
  • Continuous improvement work

What Kanban gives you

  • Visibility of work in progress (WIP)
  • Control over throughput
  • Faster prioritisation decisions
  • A system that absorbs change without collapsing

Instead of committing to batches of work, you focus on:

  • Limiting WIP
  • Managing flow
  • Reducing cycle time
  • Continuously reprioritising

The Stabilisation Phase: Why Kanban Wins

If you’re taking over a platform that is:

  • Operationally fragile
  • Poorly understood
  • Carrying hidden risk
  • Dependent on key individuals

Then you are not in a Scrum environment.

You are in a stabilisation phase.

And stabilisation is inherently:

  • Exploratory
  • Interrupt-driven
  • Non-linear

You fix one issue and uncover three more.

Trying to run this through Scrum usually leads to:

  • Constant sprint failure
  • Frustrated teams
  • Misleading reporting

Kanban, by contrast, allows you to:

  • Pull work as capacity allows
  • Reprioritise instantly when risk emerges
  • Maintain flow without artificial commitments

It’s simply a better fit for evolving systems under active repair.

The Hybrid Reality (What Actually Works in Practice)

Most high-performing teams don’t choose one or the other.

They separate concerns.

Scrum for Product Delivery

  • Feature squads
  • Clear roadmap alignment
  • Sprint-based delivery
  • Predictable output

Kanban for Platform, Ops, and Stabilisation

  • Shared services or platform teams
  • Interrupt handling
  • Reliability work
  • Continuous improvement

The result

  • Clear expectations
  • Better stakeholder communication
  • More honest delivery signals

And critically, you avoid mixing fundamentally different types of work into one system.

The Leadership Mistake to Avoid

The biggest mistake is enforcing a single methodology across all teams “for consistency.”

This usually results in:

  • Teams gaming the system
  • Metrics that look good but mean nothing
  • Delivery that still feels unpredictable

Consistency of outcomes matters.

Consistency of framework does not.

What Good Looks Like

If you’ve got this right, you’ll see:

  • Product teams delivering predictably with clear commitments
  • Platform teams moving quickly with visible flow
  • Incidents handled without derailing delivery
  • Stakeholders getting honest, understandable signals

And most importantly:

No surprises.

Final Thought

Scrum is about commitment.

Kanban is about flow.

If your environment supports commitment, Scrum is powerful.

If your environment demands adaptability, Kanban is essential.

Most organisations need both.

Very few implement either properly

What good looks like in platform stability at scale

After a few conversations since getting back to the UK, one theme has come up repeatedly:

“We need to improve platform stability.”

It sounds obvious. Almost everyone says it.

But when you dig into it, what people actually mean varies wildly:

  • Fewer incidents
  • Faster recovery
  • Better performance
  • Less firefighting
  • More predictable delivery

All valid. None sufficient on their own.

Because stability at scale isn’t one thing.

It’s a system of behaviours, ownership, and discipline.

And more importantly, it’s not something you add on later.

It’s something you operate every day.

Stability is not the absence of incidents

This is the first misconception.

If you’re operating any meaningful platform at scale, especially in regulated or high-availability environments, incidents are inevitable.

What matters is:

  • how often they happen
  • how quickly you detect them
  • how effectively you respond
  • whether you learn from them

Good organisations don’t pretend incidents won’t happen.

They design for:

fast detection, controlled response, and continuous learning

What good actually looks like

In practice, stable platforms share a set of consistent traits.

1. Clear ownership, everywhere

No ambiguity. No diffusion of responsibility.

Every service, system, and dependency has:

  • a clearly named owner
  • defined support expectations
  • accountability for outcomes

If something breaks, it’s immediately obvious:

👉 who owns it

👉 who fixes it

👉 who explains it

This sounds basic. It’s rarely done properly.

2. Tiered support that actually works

L1, L2, L3 is often implemented, but not enforced.

Good looks like:

  • L1 handles triage and known issues
  • L2 handles deeper investigation
  • L3 handles engineering fixes

And critically:

👉 clear escalation paths with no debate

If engineers are constantly being pulled into noise, stability suffers.

3. Observability that tells you what matters

Dashboards are not observability.

Good platforms have:

  • meaningful alerts (not noise)
  • clear service health indicators
  • visibility aligned to business impact

The question isn’t:

“Is the system up?”

It’s:

“Is the customer experience degraded?”

4. Boring, predictable releases

Stability and chaos are often introduced at deployment time.

Good looks like:

  • small, incremental changes
  • automated testing that actually protects you
  • controlled rollout strategies
  • fast rollback capability

No drama. No heroics. No late-night guesswork.

5. Incident management as a discipline

Not ad hoc. Not personality-driven.

Strong organisations have:

  • clear incident roles (lead, comms, technical)
  • structured response processes
  • consistent communication cadence

And most importantly:

👉 calm, controlled execution under pressure

6. Post-incident learning without blame

If your post-mortems are performative or defensive, you’re not improving.

Good looks like:

  • honest analysis
  • focus on system failures, not individuals
  • clear actions that actually get tracked and delivered

Stability improves when learning is real, not political.

7. Engineering leadership that enforces standards

This is where most organisations fail.

You cannot “encourage” stability.

You have to:

  • set expectations
  • enforce operating models
  • be visible and accountable
  • encourage responsibility

This includes:

  • saying no to unsafe changes
  • slowing down when needed
  • prioritising reliability over short-term delivery pressure

The uncomfortable truth

Most instability is not a technical problem.

It’s:

  • unclear ownership
  • weak operating discipline
  • lack of accountability
  • tolerance of poor practices

Technology amplifies these issues.

It rarely causes them.

What changes when you get it right

When stability is properly embedded:

  • Incidents still happen, but they’re controlled
  • Teams are calmer and more focused
  • Delivery becomes more predictable
  • Leadership has confidence in the platform
  • Customers stop noticing your technology (which is the goal)

You move from:

reactive firefighting

to:

controlled, reliable operations at scale

Final thought

Stability is not glamorous.

It doesn’t win awards.

It doesn’t make headlines.

But in any serious platform business, it’s the difference between:

  • scaling confidently
  • and constantly fighting your own system

And the organisations that get it right tend to look the same:

clear ownership, disciplined execution, and no tolerance for chaos disguised as progress.

The Rarest Score in the NFL: 2–0

If you’re building or analysing a sports simulation system, there’s an important principle that often gets overlooked:

Just because something is extremely unlikely doesn’t mean it’s impossible.

And in the NFL, nothing illustrates that better than the 2–0 final score.

Why 2–0 Is So Special

Most NFL games involve multiple scoring plays: touchdowns, field goals, extra points, maybe the occasional safety.

But a 2–0 game requires something very specific.

The only scoring play in the entire game must be a safety.

That means:

• No touchdowns

• No field goals

• No extra points

• No two-point conversions

• Just one safety, and nothing else

Given modern offences, kicking ranges, analytics-driven play calling, and rules designed to increase scoring, that outcome is almost absurdly rare.

But it has happened.

A Quick Stat Box

Rarest NFL Final Score: 2–0

  • Total occurrences: 5 games
  • Last time it happened: 18 September 1938
  • Years since the last 2–0 game: 87 years
  • Teams involved in the most recent game: Chicago Bears vs Green Bay Packers
  • Winning score came from: A single safety

In other words, the rarest possible realistic NFL score happened before World War II.

Every 2–0 Game in NFL History

There have only been five such games in the entire history of the league.

DateWinnerLoser
Nov 29, 1923Akron ProsBuffalo All-Americans
Nov 21, 1926Kansas City CowboysBuffalo Rangers
Nov 29, 1928Frankford Yellow JacketsGreen Bay Packers
Oct 16, 1932Green Bay PackersChicago Bears
Sep 18, 1938Chicago BearsGreen Bay Packers

The last time it happened was 1938.

That means it has been 87 years since the NFL saw a 2–0 final score.

The Bears Connection

As a Chicago Bears fan, I find this statistic especially entertaining.

The Bears appear twice in the list.

• Losing 2–0 to the Packers in 1932

• Winning 2–0 against the Packers in 1938

If you’re going to have bizarre historical trivia, doing it against your biggest rival feels about right.

Somewhere in the long, strange history of the Chicago Bears vs the Green Bay Packers rivalry, there’s a game where the entire scoreboard was produced by a single safety.

The Simulation Lesson

If you build sports simulations (which I spend a lot of time thinking about), the takeaway is simple.

When you model a sport, you usually focus on the most likely outcomes.

But the edge cases matter too.

A 2–0 result is incredibly unlikely, but it’s not impossible. It has happened. Multiple times.

So if you’re simulating NFL games, you should always sanity-check your model:

• Does the simulation allow a safety as the only score?

• Could the game realistically end 2–0?

If the answer is no, your model might be accidentally removing real outcomes from the game.

Even the weird ones.

And sometimes the weird ones are the most interesting.….

October 2020 – Lessons to improve demos – and some random domain ideas that never worked

Back in October 2020 when I was at Champion Tech, I put together a quick proof of concept to try to show my boss some stuff – an alternate branding with an idea of “levels” of sports platform features.

You can see it here via the Web Archive:

https://web.archive.org/web/20220214115736/http://orillasound.uk/features.html

That’s it. The idea didn’t fly, which was a pity, I kinda liked it and thought it had legs.

Screenshot
  • It was a proof of concept.
  • It was two static html pages – html and basic jquery – In retrospect I should have added some server side stuff, maybe if I did it again I would.
  • It probably took me around 20 minutes to do it overall.
  • On the dialog, it says “does not yet exist” – that’s a big lesson, right? Next time – didn’t matter if it was a quick demo or less – I learned to add more polish – take that few extra minutes to really sell it.
  • To be fair, I wish now that AI and cursor was a thing back then, it would have been snazzier!
Screenshot

If you’re interested, here’s a ZIP of the demo so you can admire the truly questionable CSS for yourself:

https://www.lukesplace.net/downloads/rs_sports_mockup.zip

URLs I Registered and Forgot About

Over the years I’ve registered various domains for random ideas and experiments. Some went nowhere. Most were cheap. All were forgettable.

  • pstars-test.info – at the time it seemed like a good idea for our table fussball league at work (and .info was cheap for one year). We never used it in the end.
  • cpt-tech-dev.online – we created simple “A” DNS records on this to make it easier to remember internal servers. It cost about £1.99. Not exciting, but effective and no files were ever hosted or uploaded. A waste, probably!
  • speakeasydating.com – a friend wanted to build a speed dating site when we were single and carefree. We never got round to it. Shame really, could’ve been cool, right?

I suppose it’s best to have more of a concrete plan before registering domains, these days I’m a bit less gung-ho on that, even it is only 1.99. Maybe because I’m too busy buying shiny things, but I think I have learned from those earlier days. Only buy it if you really need it, and if you’re gonna use it right away.

Although. AirPods Max. Damn.

Anyway – that’s the full story, and perhaps a lesson or two into the bargain when doing a demo or buying domains.

I Asked My AI Assistant What It Thought of Me. This Was a Risk.

Like most sensible people in 2026, I now outsource part of my thinking to a large, polite, and slightly unsettling machine that lives in my phone.

It helps me draft emails, sanity-check arguments, stress-test decisions, and occasionally talk me out of writing things on LinkedIn that would definitely have required a follow-up apology.

So, in a moment of either courage or poor judgement, I asked it a dangerous question:

“Based on our conversations… what do you think I’m actually like?”

This is a bit like asking your GP to be honest, your lawyer to be poetic, and your mirror to stop being polite.

What came back was… uncomfortably accurate.

Here’s the human translation.

Apparently, I’m “Systems-First” (Which Is a Polite Way of Saying “Boring”)

One of the first things it called out is that I don’t really believe in vibes-based engineering.

I don’t get excited by:

  • Demo theatre
  • Slideware architecture
  • “AI, but with more AI”

I do get excited by:

  • Things that survive contact with production
  • Clear ownership at 3am
  • Boring systems that keep regulators, boards, and sleep schedules happy

If you tell me something is “strategic,” my reflex is to ask:

“Great. Who runs it? How does it fail? How do we know it’s broken? And how much does it cost when it does?”

Which is not how you win friends at innovation workshops, but it is how you avoid explaining outages to a board.

The AI’s verdict: I optimise for operational truth over narrative comfort.

Honestly, that should probably be on my business card.

I Apparently Care a Lot About How Things Land (Not Just What They Say)

This one stung a bit, because it’s true.

I spend a lot of time thinking about:

  • How a CEO will read something
  • How legal will read it
  • How engineers will read it
  • How LinkedIn will absolutely, definitely read it in the worst possible way

If you’ve ever seen me iterate a “simple” message five times, this is why.

It’s not indecision. It’s blast-radius management.

Words have consequences in regulated, political, or high-stakes environments. I’ve learned (sometimes the hard way) that being technically right and being organisationally effective are not the same thing.

The AI described me as a “high-context communicator.”

I prefer my own term: professionally paranoid.

Leadership, Apparently: I’m a “Stabiliser”

This bit was actually reassuring.

The machine reckons my default leadership mode is:

  • Clarify ownership
  • Define boundaries
  • Put governance where chaos wants to live
  • Make escalation paths boring and predictable
  • Replace heroics with systems

Which, in human terms, means I’m the person who turns up after things have been on fire and says:

“Right. Let’s make sure this never requires a hero again.”

I care deeply about:

  • Decision rights
  • RACI
  • L1/L2/L3 models
  • Runbooks
  • “No surprises” cultures

Not because I love process (I don’t), but because process is cheaper than panic.

If you’ve worked in regulated or high-consequence tech, you’ll know exactly why this matters.

My Risk Profile Is… Weirdly Split

This was one of the more interesting bits.

On systems and operations, I’m conservative:

  • Guardrails
  • Gates
  • Controls
  • Evidence
  • Auditability
  • “Prove it works before we bet the company on it”

On truth and narrative, I’m much less conservative:

  • I’ll challenge stories I think are wrong
  • I’ll push back on things that don’t survive scrutiny
  • I’m willing to absorb some political discomfort if the alternative is organisational self-deception

In other words:

  • I hate operational risk
  • I tolerate personal risk
  • I really dislike lying to ourselves

Which probably explains most of my career, in hindsight.

What I Do Under Stress (Spoiler: I Make More Lists)

According to my AI-powered psychological ambush, when pressure goes up, I tend to:

  • Add more structure
  • Break problems into phases
  • Build frameworks
  • Stress-test narratives
  • Rewrite important messages until they either land safely or I lose the will to live

This is, apparently, my coping mechanism: turn ambiguity into diagrams.

There are worse habits.

The downside is that you can over-polish, over-analyse, or try to engineer uncertainty out of human systems (which is, frankly, optimistic).

The upside is that you usually don’t wake up to surprises you could have designed out.

How This Apparently Comes Across

This was the bit I was most curious about.

To boards and CEOs:

“Safe pair of hands. Sees around corners. Not a hype merchant.”

To engineers:

“Protects us from chaos. Clear about ownership. Understands production reality.”

To recruiters and peers:

“Operational CTO. Platform stabiliser. Good in regulated or high-blast-radius environments.”

Also, occasionally:

“Possibly over-indexed on risk and process.”

Which is fair. I’ve seen what happens when you under-index on those things.

The Uncomfortable Summary

The AI boiled me down to something like this:

A systems-oriented, governance-minded, pragmatically stubborn technology leader who prefers boring reliability to exciting failure, and is willing to be unpopular to avoid organisational self-deception.

I’d probably phrase it more simply:

I like tech that works.

I like organisations that know who owns what.

I like fewer surprises.

And I really don’t like pretending.

Should You Ask Your AI What It Thinks of You?

Only if you’re in the mood for a slightly unsettling mirror that:

  • Doesn’t laugh at your jokes
  • Remembers everything
  • And has absolutely no incentive to protect your ego

On the plus side, it’s cheaper than therapy and less likely to prescribe running.

On the downside, it’s annoyingly good at pattern recognition.

Still, I’d recommend it.

Worst case, you learn something.

Best case, you get a blog post out of it.

And if nothing else, it confirms what I’ve suspected for years:

I’m not boring.

I’m operationally exciting.

What Mounjaro Changed for Me (And Why I’m Still Avoiding the Beach)

Five months ago, I stepped on the scales and saw a number I’d been expertly pretending didn’t exist: 135kg.

Screenshot

Today, I’m around 112kg. The graph is deeply satisfying in a very nerdy way. It’s just a calm, sensible line trending steadily downwards, to the tune of a bit over 22kg gone in about three or four months.

No drama, no cliff edges, just quiet, consistent progress.

Which, as it turns out, is the good kind.

Yes, I’m on Mounjaro. And yes, it’s been a big part of this. But the biggest surprise hasn’t been the number on the scale. It’s how weirdly… peaceful food has become.

Let’s get the obvious bit out of the way first. Side effects. I’ve been lucky. A bit of nausea here and there, mostly early on, but nothing that’s stopped me living my life or made me regret the decision. When you read some of the stories online, I’m very aware I’ve had a comparatively easy run of it.

The real change has been in my day-to-day behaviour, and more importantly, in my head.

Crisps and random snacking have mostly just… faded out of my life. Fizzy drinks are gone entirely, not because I’m being virtuous, but because I genuinely don’t fancy them anymore. Lunch is often either very light or doesn’t happen at all, simply because I’m not hungry. And as a bonus feature I didn’t order but very much appreciate, the reflux I used to suffer from has improved massively, which I’ll happily blame on eating like a vaguely sensible adult for once.

The strangest part is how quiet food has become. It used to be a constant background process running in my brain. What’s next, what’s in the cupboard, what can I grab quickly. Now it feels more like a polite suggestion than a relentless notification system. I eat when I want to, not because my brain is nagging me like an overenthusiastic product manager.

There’s also a more important change that doesn’t show up on the chart.

I actually feel good about myself again.

Not in a “cue inspirational music and slow-motion jogging” way. More in a calm, slightly surprised, “oh… this is working” way. I’ve still got about 12kg to go before I’m out of the “officially obese” category, which feels like a pretty decent milestone. I’m not done, I’m not claiming victory, and I’m definitely not buying skinny jeans. But for the first time in a long time, this feels achievable rather than theoretical.

And no, before you ask, I’m still not going to the beach. I don’t want to risk them trying to float me back out to sea and fitting me with a tracker. Let’s not tempt fate.

I do want to be very clear about one thing though. I know I’m one of the lucky ones. Not everyone tolerates these meds well. Not everyone gets results like this. Not everyone can access them at all. This isn’t a miracle cure story or a sales pitch. It’s just an honest update from someone who’s finally found something that’s shifted both the numbers and the mindset.

What Mounjaro has really given me isn’t just a smaller appetite. It’s taken away the constant fight with food, made better choices feel easier, turned down the mental noise, and helped me feel more like myself again.

The scale is nice. The graph is very satisfying. But the real win is that food no longer runs the meeting.

I’m not finished yet. But for the first time in years, I’m pretty confident I’m actually going to get there

When place matters: living with cutaneous lupus

I haven’t written much about health before, but over the last few months it’s become part of the backdrop of my decision-making — so this feels worth sharing.

A while ago, after a persistent facial rash that worsened with sun exposure, I went through a biopsy. At the time, skin cancer was a genuine concern.

A really good friend of mine from my university days passed away last year from Skin Cancer, so I was very aware of the dangers and it certainly was a big concern.

So when the results came back not cancer, there was real relief — the kind you feel immediately and deeply.

The doctor came to see me as I was getting the stitch taken out, and handed me the report – her first words were re-assuring, important to hear.

”Read this, then come through and see me. Don’t panic – it’s totally manageable, but you’ll need a lot of sun screen….”

That relief was then tempered by a different diagnosis: cutaneous lupus erythematosus (CLE).

CLE is a skin-limited autoimmune condition — not systemic lupus — and in day-to-day terms it’s very manageable. There’s a clear plan, specialist care, and no immediate impact on my ability to work or live fully. But it does come with one very clear and non-negotiable trigger: UV exposure.

That combination of relief and recalibration became a quiet inflection point — the moment we started thinking more deliberately about environment, sustainability, and where we wanted to be long-term.

Living in Australia, that question takes on a particular weight.

Australia’s sunlight is extraordinary — and unforgiving. Even with good sun habits, high-SPF protection, and sensible precautions, the baseline UV exposure here is simply higher than in most parts of the world. For most people, that’s a lifestyle footnote. For someone with a photosensitive autoimmune condition, it becomes a constant background constraint.

None of this is dramatic or debilitating — but it is cumulative. Managing CLE well is about reducing repeated immune activation over time, not “pushing through” flare after flare. Environment plays a role in that, whether we like it or not.

At the same time, this diagnosis prompted some honest reflection. I have a family history of lupus, and while my own condition is different and far milder, it does sharpen your sense of perspective. You start asking quieter questions about sustainability, stress, proximity to support, and what you want the next phase of life to look like.

That combination — health management, environment, and family — ultimately fed into a broader decision my wife and I were already circling: to return to the UK.

This isn’t a reaction, and it isn’t fear-driven. It’s a deliberate choice to put ourselves in an environment that makes long-term health management simpler, not harder — lower ambient UV, easier moderation, and closer proximity to family.

Australia has been an incredible chapter. We’ve built memories here that will always matter. But sometimes the most adult decision is recognising when a place that’s wonderful isn’t the right place anymore.

I’m sharing this not for sympathy, but for completeness. Health doesn’t always arrive as a crisis — sometimes it arrives as information, and what matters is what you do with it.

For me, it’s meant choosing an environment that works with me, not against me.

Bet Placement: Why It Fails (and What the Architecture Is Usually Trying to Tell You)

Bet placement is the most latency-sensitive, revenue-critical, and failure-prone path in any iGaming platform. It sits at the intersection of customer experience, trading risk, regulatory control, and real-time data churn. When it fails, it fails loudly – often under peak load, with money on the line, and very little tolerance for excuses.

What’s interesting is that bet placement failures are rarely caused by a single “bug”. They are almost always the result of architectural tension: too many responsibilities, unclear boundaries, or optimistic assumptions about dependency behaviour.

Below are the most common technical reasons bet placement fails, drawn from real-world operation of high-volume wagering platforms.


1. Too many synchronous dependencies

The fastest way to break bet placement is to make it depend synchronously on everything.

Common offenders include:

  • Identity and session validation
  • KYC / jurisdiction checks
  • Wallet balance and limits
  • Market state
  • Pricing confirmation
  • Trading approval
  • Promotions / bonuses
  • Payments (yes, people still do this)

Every synchronous hop adds latency and multiplies failure probability.

Under peak load, even a small slowdown in one dependency can push the entire request over its latency budget.

What the system is telling you:

The bet placement path should be short, deterministic, and aggressively bounded.

Anything that doesn’t need to be synchronous shouldn’t be.


2. Market state churn and race conditions

Markets move. Prices change. Selections suspend and re-open.

Feed updates arrive in bursts.

If bet placement:

  • Reads market state from multiple sources
  • Relies on stale caches without invalidation
  • Doesn’t enforce price staleness windows

…you get classic race conditions, namely:

  • Bets accepted on suspended markets
  • Bets rejected even though the UI showed availability
  • Duplicate retries hitting different market states

Failure mode: Customers see “technical error” or inconsistent rejections during peak trading.

What the system is telling you:

Market state must be versionedcached close to placement, and validated with explicit tolerances (“price valid for X ms”).


3. Wallet design flaws (the silent killer)

Wallet issues are typically responsible for a disproportionate number of bet placement failures.

Typical problems that I’ve seen include:

  • No true reservation/hold model
  • Weak or missing idempotency
  • Balance checks separated from debits
  • Ledger writes mixed with business logic (this one is classic, you’d be surprised how any times it happens!)

Under concurrency, this leads to:

  • Double spends
  • Phantom insufficient-funds errors
  • Reconciliation nightmares after recovery

What the system is telling you:

Wallets must be boring, deterministic, and mathematically correct.

If your wallet logic is clever, it’s probably broken when you have high traffic. Or even when you don’t!


4. Trading decisions that don’t degrade gracefully

Trading systems often assume they’ll always respond quickly.

Newsflash: they won’t.

When trading:

  • Times out
  • Is under heavy load
  • Is partially unavailable

…bet placement frequently has no clear fallback. The result can be long timeouts that cascade back to the edge, rather than fast, explainable rejections.

Better behaviour patterns include:

  • Explicit time budgets for trading decisions
  • Default reject on timeout with a clear reason code
  • Rapid market suspension when instability is detected

What the system is telling you:

A fast reject is better than a slow maybe.


5. Retry storms and idempotency gaps

During peak events, clients retry.

Load balancers retry.

Upstream services retry.

If bet placement:

  • Doesn’t enforce idempotency keys
  • Treats retries as new requests
  • Emits side effects before commit

…you get duplicate bets, duplicate wallet postings, or corrupted state.

What the system is telling you:

Idempotency is not an optimisation.

It’s a core correctness requirement.


6. Overloaded databases and hidden coupling

Bet placement often looks stateless at the API level, but is tightly coupled to:

  • Shared databases
  • Hot tables (balances, open bets)
  • Lock-heavy schemas

Under load, lock contention silently destroys throughput, leading to sudden, nonlinear failure.

What the system is telling you:

If throughput collapses before CPU does, you have a data contention problem, not a scaling problem.


7. Poor observability in the critical path

When bet placement fails and you can’t answer:

  • Where did the time go?
  • Which dependency failed?
  • Was this a reject, a timeout, or a partial commit?

…you lose the ability to respond confidently during incidents.

This leads to:

  • Over-suspension (“turn everything off”)
  • Over-engineering after the fact
  • Loss of trust from trading and operations

What the system is telling you:

If you can’t see it under pressure, you can’t control it.


The pattern behind all failures

Nearly all bet placement failures share one root cause:

The system is trying to do too much, too synchronously, with unclear ownership of outcomes.

Healthy bet placement services are:

  • Thin at the edge, thick in the domain
  • Ruthless about timeouts and failure modes
  • Explicit about what they will and will not guarantee

A better mental model

Think of bet placement as this:

  • transaction coordinator, not a workflow engine
  • risk gate, not a business logic dumping ground
  • trust boundary, not an integration hub

If something feels awkward to implement in bet placement, that’s usually your architecture asking for a boundary to be moved.


Final thought

When bet placement fails, teams often reach for more caching, more hardware, or more retries.

Rarely do those fixes address the underlying problem.

The real work is harder: simplifying the synchronous path, tightening ownership, and designing for rejection as a first-class outcome.

Platforms that get this right don’t just place bets faster—they fail more gracefully, recover more predictably, and earn trust when it matters most.

Why L1 / L2 / L3 Support Models Fail Without Ownership

The L1 / L2 / L3 support model is one of the most widely adopted – and most poorly understood – operating patterns in modern technology organisations.

On paper, it looks really clean and rational: first-line support handles intake, second-line investigates, third-line engineers fix root causes.

Escalation is orderly. Responsibilities are clear. Everyone knows their lane.

In practice, many organisations discover the uncomfortable truth: without clear ownership, L1/L2/L3 doesn’t reduce incidents: it completely institutionalises confusion.

After years of operating platforms in regulated, high-availability environments, I’ve seen the same failure modes repeat with remarkable consistency. The issue is rarely the model itself. It’s the absence of real accountability at the seams.

The illusion of escalation

The biggest misconception is that escalation equals ownership.

In weak implementations, an incident “moves up the stack” without ever truly belonging to anyone. L1 logs the ticket and hands it off. L2 adds commentary and escalates. L3 investigates when time permits.

Meanwhile, the system remains degraded, customers are impacted, and no single individual feels responsible for resolution.

Escalation becomes a mechanism for risk transfer, not problem solving.

When nobody owns the outcome end-to-end – and I mean technical fix, communication, and crucially learning – the model devolves into a queueing system that optimises for local convenience rather than global reliability.

L1 without ownership becomes a call centre

L1 is often positioned as “just intake”: logging tickets, resetting passwords, acknowledging alerts. But when L1 lacks clear ownership boundaries, it becomes little more than a message relay.

Effective L1 teams do more than triage.

They:

  • Own initial diagnosis, not just categorisation
  • Apply runbooks with authority, not fear of escalation
  • Decide whether an issue is noise, delay, or degradation

Without ownership, L1 staff are incentivised to escalate early and often—because escalation feels safe. The result is alert fatigue upstream and a complete lack of signal discipline.

L2 becomes a dumping ground

L2 is where many models quietly collapse.

You’ve seen it. I’ve seen it. We’ve all rolled our eyes, collectively and individually, and groaned.

In theory, L2 provides deeper technical investigation and remediation within defined limits. In reality, L2 often inherits ambiguity: unclear service boundaries, incomplete documentation, and no authority to make changes.

When L2 doesn’t own specific systems or outcomes, it becomes a holding pen for unresolved problems. Tickets stall. Context is lost.

Engineers re-diagnose the same issue repeatedly because nobody is accountable for closing the loop.

This is how mean time to resolution quietly stretches from minutes to hours: without anyone feeling explicitly at fault.

L3 without ownership breeds resentment

L3 teams (usually product or platform engineers) are where the real fixes happen.

But when ownership isn’t explicit, L3 becomes reactive and defensive.

Common symptoms that I’ve seen usually include:

  • Engineers pulled into incidents with no context or priority clarity
  • Fixes made under pressure without time for proper remediation
  • Repeated incidents caused by known issues that never get scheduled work

From the engineer’s perspective, L3 becomes an interruption tax.

From the business’s perspective, it’s a black box.

Neither side is well served.

Everyone loses!

The real failure: nobody owns the service

The core problem isn’t the number of layers – it’s the absence of service ownership.

In healthy organisations:

  • Every system has a clearly identified owner (individual or team)
  • That owner is accountable for availability, performance, and support outcomes
  • L1/L2/L3 act as capability layers, not responsibility boundaries

To be clear on this, and many people make this mistake – ownership does not mean “doing everything yourself”!

It means being accountable for:

  • Decision-making during incidents
  • Trade-offs between speed, risk, and correctness
  • Ensuring learning happens after recovery

Without this, post-incident reviews become blame-avoidance exercises rather than improvement mechanisms.

What works instead

Successful support models invert the usual thinking:

  1. Service ownership first – Define who owns each system. Make that ownership visible and unambiguous.
  2. L1 and L2 operate under delegated authority – Runbooks, thresholds, and decision rights matter more than escalation paths.
  3. L3 owns root cause, not just fixes – If an issue repeats, it’s an ownership failure—not a support failure.
  4. Incidents have a named incident owner – One person is accountable for coordination, communication, and closure, regardless of where the fix lands.
  5. Support is a feedback loop, not a firewall – Good support improves the system. Bad support merely absorbs pain.

The uncomfortable truth

L1/L2/L3 models don’t fail because they’re outdated. They fail because they’re often implemented as organisational insulation, designed to protect teams from responsibility rather than enabling reliable delivery and clear learnings.

Here’s the truth – true ownership is uncomfortable.

It forces clarity.

It exposes weak interfaces, poor documentation, and brittle systems.

But it’s also the only thing that turns support from a cost centre into a reliability engine.

If your support model feels busy but ineffective, the question isn’t whether you need another layer – it’s whether anyone truly owns the outcome.

King Gaming: One Year On

It’s almost one year since my old employers got raided. Luckily I’d been away from them for a long time, and I do feel sorry for many of the innocent people who lost their jobs. It must have been awful and a terrible shock to them.

Obviously, it should be very much clear to everyone that I’ve no involvement in any of this investigation, and I’ve offered my services to the authorities to help them in any way possible, should they need it. I’m sure you knew this already, but it’s just worth being clear on it!

There is a reason that King and co are no longer operating and under police investigation and though I won’t speculate except where the facts permit, I’ll pretty much rely on the facts contained within the following judgment in Chinese, as shown below.

The above is a snippet of a judgment from a chinese court that links King Gaming Isle of Man and Manx Internet Commerce (MIC) to what seems to have been ongoing and massive scamming activity conducted by at least six individuals who were working for various teams within King and MIC on the island.

They all admit to carrying out these scam activities, having been relocated from the Philippines on visas by the company, carrying on their behaviour on the Isle of Man.

These people engaged in activities that scammed and cheated people out of amounts in the millions, according to the judgment.

What is interesting from this judgment is that they all appeared to work in separate teams, suggesting strongly that the overall fraud headline figure could very well be considerably higher.

The evidence that I’ve seen from other sources including the BBC strongly suggests upwards of 100 staff engaged in this sort of activity.

There are more sources in mandarin that I’ve seen that are much more explicit on what was going on, but I won’t link them here – again, for obvious reasons.

The public updates from the official Receiver that King and MIC had around 65 million and 30 million in their bank accounts gives more clarity to the overall picture, as shown below.

The facts don’t lie!

The translation of the judgment is rough but very much clear on what took place.

According to this BBC article, they were based first at the Seaview Hotel and then latterly this office space at Howard Pearson house in Douglas.

Actually, right next to where the new HQ was going to be!

Some more information that has emerged is about the ultimate ownership of the company.

Above and pictured is “Bill Morgan” aka Lingfei Liang. UBO of King Gaming (and other companies including Manx Internet Commerce / Champion Tech) and the boss that was referred to in the judgment. I’ve never formally met him, but I did see him at our stand in the Philippines in 2019 at an industry show.

This above is Yanhao (Kevin) Zhu. Former boss of the Isle of Man “operation”, and former director of Champion Tech. He was my boss.

He’d apparently worked in “fintech” for Morgan for a number of years, and as such was a trusted lieutenant.

The more I’ve heard rumours about what happened, the more I’m utterly sickened, disgusted and horrified. I’m not sure if it’s true, but if it is, it’s just disgusting.

Let’s see what happens from here, but I think it’s fair to say that we all expect that, where appropriate, justice will be served once any process has run its course.

(*) I was one of the first ones if not the first to raise concerns about working conditions at King, working til 3am and then back at your desk at 8am or your visa cancelled doesn’t sound like reasonable working conditions…..see here for more.

More images from the Philippines in 2019 :-