Raising a Good Junior: What AI Gets Wrong About Knowledge and What It Means for the Next Generation

A friend of mine, Jose, sent me a conversation he’d had with an AI assistant about an article he’d been reading. The article was The Tacit Dimension by Christian Ekrem. Jose’s observation was sharp: he’d been frustrated by the same thing the article describes, AI assistants that produce confident output without surfacing any of the reasoning behind it, the implicit design decisions staying implicit. The conversation he shared was good enough that I went and read the article itself.

It made me put my phone down. Not because it was wrong, but because it was pointing at something real and uncomfortable, and because it immediately made me think about my son.

The article builds on Michael Polanyi’s 1966 claim: we can know more than we can tell. Polanyi’s observation was that expert knowledge is structurally tacit. It lives in the body, in practice, in the pattern-recognition accumulated over years of doing a thing. You can’t extract it. You can’t train a model on it, because it was never written down. And you can’t transfer it except by working alongside someone who has it.

Ekrem applies this to AI-assisted software development and argues we are sleepwalking into a crisis: juniors are being apprenticed to AI assistants instead of to seniors, the “why does this work this way?” questions are drying up, and the tacit knowledge that used to flow through teams is quietly bankrupting out of codebases. The seniors retire. Nobody knows why the auth system works the way it does. The code keeps running.

It’s a well-argued piece. But it has a gap in it, and that gap leads somewhere interesting.

Where the Argument Holds

Ekrem’s strongest point isn’t about AI. It’s about what happens when any shortcut severs the connection between doing and understanding.

His illustration is a colleague who spent an afternoon refusing to merge a PR that was technically correct. Tests passing, CI green, everything fine on paper. He kept saying “I just don’t believe this code.” Forty minutes into walking the author through it line by line, the author said offhand: “this assumes the queue is FIFO, but I think that’s safe.” It wasn’t. The queue was FIFO in development and best-effort-FIFO in production, buried in a runbook nobody had read in two years.

The colleague had smelled it from the diff. Not from reading a document. From a decade of looking at similar things and accumulating a mental model of where pain tends to come from. That kind of knowledge doesn’t show up in any training corpus because it was never written down in any single place. It was always distributed across experience, context, and memory.

An AI can’t replicate that. Not because current models are too limited, but because the knowledge is structurally absent from anything a model could be trained on. That’s the actual claim, and it survives scrutiny.

Where I Push Back

The argument assumes that the median junior, given access to an AI assistant, will use it as a replacement for thinking. And honestly? A lot of them will. But that’s not a fact about AI. That’s a fact about the median junior and the absence of good mentorship. The same junior, given access to Stack Overflow, Google, or a patient senior who answers every question without making them think first, will also atrophy. The crutch varies. The dynamic doesn’t.

A good junior understands that AI is a tool to unblock you on things you need to understand, not an infinite knowledge base to stop thinking. The operative word is understands. That understanding doesn’t come automatically. It has to be built. And that’s what the article is really pointing at without quite saying it: the apprenticeship model isn’t failing because of AI, it’s failing because nobody is teaching juniors how to learn.

Ekrem calls one failure mode the Fluency Mask: AI’s verbal fluency about code being mistaken for understanding of code. It’s real. But it’s a trap that only closes on someone who isn’t watching for it. The senior’s job has always been to teach juniors to watch for exactly that kind of thing. Confident outputs that feel like understanding but aren’t grounded in anything. Stack Overflow answers with high vote counts. Code that compiles. Documentation that reads clearly but documents the wrong thing. AI is a new instance of an old problem.

My son will grow up in a world where AI is as ambient as the internet was for my generation. He won’t know a time without it. The question isn’t whether he’ll use it, he will and he should, but whether he’ll use it well or badly. The distinction I want him to carry is simple: AI is a tool to resolve a specific gap, not a replacement for developing the judgment to know where the gap is. Think first. Reach when stuck. Understand what you got back. Inverting that sequence is where the damage happens.

Building the Muscle

The most important window is before he can fluently use AI, which is shrinking fast. The cognitive capacity I want him to develop is tolerance for not-knowing: the ability to sit with an unresolved problem without immediately reaching for relief. Everything downstream of that, debugging, reasoning, designing, the smell that something is wrong before you can name what, depends on being able to stay in the discomfort long enough to actually think.

The way to build that tolerance is not by explaining it. It’s by not rescuing him. When he’s stuck, the temptation is to solve it. Resist it. Sit with him in the stuck. Ask questions that point at the problem without resolving it. Let him feel the friction. That discomfort is the exercise. Skipping it is skipping the rep.

When AI is in the picture, I want to use it with him out loud, narrating why I’m reaching for it. “I know what I want here but I’ve forgotten the syntax, I’ll check” is different from “I don’t know what I want yet, so I need to think before I ask anything.” He needs to see that distinction made consciously, by someone he trusts, before it becomes instinct.

After he’s worked through something, I want to ask him to explain it back. Not as a test. As genuine curiosity. The act of explanation forces him to consolidate what he actually understood versus what he pattern-matched. The gaps surface immediately. He’ll say something and pause because he doesn’t actually know why it works that way. That pause is the whole point. Recognising it as a gap rather than papering over it with confident-sounding words is the habit I want him to have.

The Actual Apprenticeship

The most direct version of all of this is the simplest: when I’m working through something, a problem, a piece of code, a decision, let him watch sometimes. Not to teach him the domain. To show him what thinking looks like. The false starts. The “hmm, that’s not right.” The moment something clicks. Most kids never see an adult genuinely wrestling with a hard problem because adults hide the struggle. Showing him the struggle, including my own uncertainty, is probably the most valuable thing I can do.

The threat isn’t AI. The threat is the absence of people willing to do the slow work of the apprenticeship model: to let juniors watch them think, to push back on “I don’t know why but trust me,” to pair and review and explain. AI makes the shortcut more available and more seductive. But the shortcut was always there. The question was always whether someone cared enough to make you take the longer road.

For my son, that’s the job. Not to keep him away from AI, that ship has sailed and the destination is fine, but to make sure he gets enough reps on the longer road first that he knows what it feels like and why it’s worth walking.

The kids who figure that out will be the ones the next generation of teams desperately needs.

Inspired by The Tacit Dimension by Christian Ekrem, and by a conversation with an AI assistant that was, appropriately, more useful than I expected.

Where in the World Am I?

World map with Tucson pinpointed

There are so many fun things you can do with web services, and most of them are trivial to use in Mini Micro. Today, let’s use a couple of these together to plot your location on a map of the world!

Finding Your Place In the World

…begins by finding your external (or public) IP address. This is the Internet Protocol address by which you are seen on the internet, outside your local network. It’s probably different from your internal (or private) IP address, which is what probably appears when you look at the network settings on your computer. We don’t care how your computer is known to other computers in your house or neighborhood; we only care how packets are routed to it from the great wide world beyond. Fire up Mini Micro (available free here, and type:

http.get("https://api.ipify.org")

This should return an IP address, something like 66.212.203.27.

Now, to get information about that IP address, including the city it’s in and an approximate latitude/longitude, we’ll use a different web service:

http.get("http://ip-api.com/json/66.212.203.27")

You can replace the IP address above with whatever you got from the first query. The result will be a string of JSON code that contains your city, region, country, latitude, and longitude, along with the name of your internet service provider (ISP) and other details.

Putting this all together into a little program. Use edit to enter the code editor, then paste in the following:

import "json"
import "stringUtil"
clear

// What's my external IP address?
myIP = http.get("https://api.ipify.org")

// And where am I?
locJson = http.get("http://ip-api.com/json/" + myIP)
locInfo = json.parse(locJson)

pprint locInfo
print "You are in {city}, {region} ({country})".fill(locInfo)

Run this, and it should tell you where you are (along with all those other details). Neat, right?

Don’t Tell Me, Show Me!

But we can make it more fun. Let’s pinpoint you on a map of the Earth! edit your program again, delete the last two lines (pprint and print), and add in this additional code:

mapImage = file.loadImage("/sys/pics/earth_day.jpg")

// Find the pixel coordinates of our lat/long in this image
px = (locInfo.lon + 180) / 360 * mapImage.width
py = mapImage.height - (90 - locInfo.lat) / 180 * mapImage.height

// Draw a marker (using an offscreen PixelDisplay)
g = new PixelDisplay
g.clear color.black, mapImage.width, mapImage.height
g.drawImage mapImage
g.line 0, py, g.width, py, "#00FFFF88", 3
g.line px, 0, px, g.height, "#00FFFF88", 3
g.fillEllipse px-10, py-10, 20, 20, "#00FFFFFF"
g.drawEllipse px-15, py-15, 30, 30, "#00FFFFAA"
g.drawEllipse px-20, py-20, 40, 40, "#00FFFF88"
markedMap = g.getImage(0, 0, g.width, g.height)

// And draw the mapImage, fit to the screen!
scale = 960/mapImage.width
gfx.drawImage markedMap,
  480 - mapImage.width*scale/2, 320 - mapImage.height*scale/2,
  mapImage.width*scale, mapImage.height*scale

s = "{city}, {region}, ({countryCode})".fill(locInfo)
text.row = 2
text.column = 34 - s.len/2
print s

The result, if you happen to be my neighbor right now, should look like this:

World map with Tucson pinpointed

The trick here is knowing that the map image uses a standard equiprectangular projection, where longitude and latitude both map linearly to x and y. That lets us convert from lat/long to pixel coordinates with some reasonably simple math. Most of the code above is then drawing the pinpoint lines and circles on top of the map.

(Note: if you are reading this in the future and live somewhere other than Earth, this code will need adjustment.)

Now in Animated 3D!

Let’s take it one step further: combine what you’ve done so far with the globe demo! Follow these steps:

  1. Save your program, if you haven’t already.
  2. edit "/sys/demo/globe" to load the globe demo code.
  3. Select All (^A) and then Copy (^C).
  4. Exit the editor, then edit your program.
  5. Scroll to the bottom, and Paste (^V).
  6. Find the line (at the top of the pasted code) that says bigMapImg = file.loadImage("/sys/pics/earth_day.jpg"), and change it to just say bigMapImg = markedMap instead.

That’s it. Now run your program, and you should see your location on a beautiful rotating globe.

Globe animation with Tucson pinpointed

Never Be Lost Again

The http module is an often-overlooked gem in the Mini Micro APIs. With one little call we can get our external IP address, and with a second little call, get all sorts of information about that, including the info we need to place ourselves on a map. We also looked at how you are able — nay, encouraged! — to steal from the demo code and make it your own.

What do you think of this? Let me know in the comments below!

Frontend e Motion

Tenho a impressão de que frontend está entrando em uma nova fase em relação a motion.

Durante muito tempo, animação na web dependia de bibliotecas relativamente pesadas porque o CSS simplesmente não oferecia controle suficiente sobre timelines, sincronização e estados contínuos.
Agora isso está mudando rápido.
Features como animation-timeline, scroll-driven animations e melhorias nas APIs de view transitions começam a trazer para o navegador capacidades que antes exigiam GSAP ou implementações complexas em JavaScript.
O impacto disso vai além de “efeitos visuais”.
Motion bem implementado melhora:

  • percepção de performance
  • clareza de hierarquia
  • feedback de interação
  • sensação de fluidez do produto
  • compreensão de estado da interface

E acho que o mercado também amadureceu nesse ponto.
Hoje, a diferença entre uma interface “moderna” e uma interface realmente premium raramente está em componentes isolados. Normalmente está na consistência do comportamento visual.

Timing, easing, aceleração, continuidade e responsividade começaram a funcionar como tipografia funciona no design gráfico: quando estão ruins, todo mundo percebe. Quando estão bons, quase ninguém nota conscientemente (mas a percepção de qualidade sobe muito.).

Curioso para ver como o ecossistema vai evoluir nos próximos 2 anos, principalmente com browsers assumindo mais responsabilidades que antes ficavam em libraries externas.

Life after the merge: why publishing an Obsidian plugin is just the beginning

Typing

I built Snipsy because I was typing - [ ] fifty times a day and getting annoyed about it.

That’s a true sentence and also a misleading one. The text expander I built for Obsidian is a one-week problem, technically. The actual project, the part that took nine months and 28 releases, was figuring out how to keep a thing alive after you’ve shipped the first version. I knew none of this when I started.

Nine months later, Snipsy sits at almost 1000 downloads in the Obsidian community plugins catalog. Small numbers, by absolute standards. But it’s published, it’s maintained, and people actually use it. Which, judging by the graveyard of half-shipped opensource projects on GitHub, is the rarer outcome.

Getting into that catalog is its own gauntlet. My pull request to the Obsidian community plugins repo sat with a ready for review label for six months before a maintainer merged it. Six months of waiting, and that turned out to be the easy part – the technical formality. The real work started the day after the merge.

Here’s what the 28 releases taught me. Not in the heroic-lessons-from-the-journey sense. More like: things I wish someone had told me at release one.

Typing

The dominant tool hadn’t shipped in four years

I want to start with the original mistake, because it set up everything else.

When I first looked for a text expander for Obsidian, there was already a leader. Stable plugin, decent download numbers, well-known. I tried it, and it did the basic job. But it had no idea what markdown context it was in. Triggers expanded the same inside a fenced code block as in a normal paragraph.

That sounds harmless until you write about code — document a CLI command literally named todo, and the expander quietly turns your example into - [ ]. In a notes app built for developers, that’s not an edge case.

I checked the GitHub repo. Last release: four years ago. Issues open: many. PRs ignored.

My first reaction was “this can’t be right, I must be missing something.” I read other people’s plugin reviews, dug through issues, considered forking. Eventually I accepted the obvious thing: in a niche this small, “leader” doesn’t always mean “active.” Sometimes it just means “first.”

Here’s the part I didn’t expect. When I sat down to fix the code-block bug in my own plugin, it took about fifty lines. Three small pure functions — one for fenced code, one for inline backticks, one for YAML frontmatter. Each one walks the document up from the cursor, counts the delimiters it cares about, and answers a yes-or-no question: is the cursor inside?

The only piece with any real subtlety is fenced code: you have to remember which delimiter opened the fence (backtick triple vs tilde triple) so you don’t close one fence with the other. Get that wrong and expansion silently fires inside code blocks again, and nobody can tell you why.

Four years. That’s how long the long-standing leader of the niche had gone without a release — not because the fix is hard, but because nobody was paying attention to the repo anymore.

The lesson that took me nine months to actually internalize: most niche tool categories have abandoned leaders. Not because the maintainers are bad people. Just because attention is finite, and niches don’t pay back the maintenance cost for most people. If you find a tool category where the top result hasn’t shipped in years, that’s not a crowded market. That’s an empty one.

The bar to clear isn’t “be excellent.” The bar is “ship the obvious fix nobody got around to.”

Documentation is positioning, not description

Most opensource projects document what they do. Smart projects document when they’re the wrong choice.

It took me about four releases to figure this out. Early Snipsy README was a feature list: hotstrings, custom snippets, multi-line expansion, community packs. The kind of README you write when you’re proud of what you built.

The problem with that README is that it answered the wrong question. People landing on the page weren’t asking “what does this do?” They were asking “is this for me?” And a feature list doesn’t answer that.

So I added a section called “When Snipsy is NOT the right tool.” It lists three alternatives:

Alternatives

I expected this section to lose me users. It did the opposite. People who needed Templater would land on my page, read that section, leave satisfied, and not file confused issues later. People who stayed actually wanted what Snipsy does.

Positioning is who you say no to. The README is the first place to declare it.

Saying “no” to features is a maintenance strategy

Around release five, feature requests started arriving. The most common one: “Can you add JavaScript templating? It would be powerful.”

Yes, it would. Templater already does it well. And the moment I add JavaScript templating to Snipsy, three things happen at once:

  1. The scope doubles. Now I’m maintaining a templating engine.
  2. The bug surface explodes. Templating bugs are nasty.
  3. The positioning collapses. “Use Templater if you need scripting” stops being true.

So I said no. Politely, with reasoning, and with a link to Templater. Then again. Then again.

The first ten or so feature requests were hard to decline. I felt the pull every time: someone wanted a thing, I had the skills, why wouldn’t I just add it? But every “yes” to scope creep is a “no” to the project’s long-term survival. Most indie opensource doesn’t die from lack of users. It dies because the maintainer said yes too many times and burned out.

I now have a rough rule:

If a feature would make Snipsy more like Templater, the answer is no.

Templater is a great tool. There is no reason for Snipsy to become a worse version of it.

To be clear, saying no to features is not the same as being closed to users. Snipsy has a built-in Feedback & Feature Request tab — feedback is one click away, inside the plugin, and I read everything that comes through it. The discipline isn’t refusing to listen. It’s listening to every request and still saying no to the ones that would quietly turn a small tool into an unmaintainable one.

CI does the work you’d otherwise skip

This one’s less philosophical and more practical.

If I had to point at one thing that kept Snipsy alive across 28 releases, it’s the CI setup. Specifically: tag-driven releases with build provenance, automated tests, and dependency scanning.

Here’s a snippet from the release workflow:

- name: Build
  run: npm run build

- name: Generate attestation
  uses: actions/attest-build-provenance@v1
  with:
    subject-path: 'main.js'

Nothing exotic. But the effect is: I push a tag, and within minutes there’s a signed release in the Obsidian community catalog with attested artifacts. Zero manual steps.

This matters more than it sounds. The first time I shipped a release manually, it took me 40 minutes and I forgot to update the version in two files. By release ten, I would have given up if every release still cost me 40 minutes. CI makes the maintenance cost asymptotically approach zero, which is the only way a side project survives.

For corporate teams, CI is hygiene. For indie projects, CI is the difference between “this plugin is maintained” and “the maintainer got tired.”

The best feature I shipped, I stole from Espanso

I built Snipsy thinking the core feature was the snippet engine. Turns out I was wrong. The core feature was something I copied from a neighboring ecosystem.

Espanso is the dominant system-wide text expander — the one you reach for when you want expansion to work in any app, not just the editor you’re in. It also has something Obsidian plugins almost never have: a packages ecosystem. Espanso users share package.yml files publicly, and the community has been growing that catalog for years now. Every text-expansion plugin I tried in Obsidian treated snippets as something you configure from a blank settings page.

So I ported the Espanso pattern into Obsidian — not the implementation, the idea. Snipsy ships with a Packages tab and ten curated packs maintained as YAML files in the repo: Markdown Essentials, Daily Journal, GTD, Code Review, and so on. One click installs a pack. Within thirty seconds of installing the plugin, you have working snippets without touching settings.

Opening the Packages tab, installing Markdown Essentials, then expanding

The packs themselves are open source. Anyone can submit a new pack as a pull request to the Snipsy repo — no marketplace, no review queue, no monetization. This sounds trivial but it’s the load-bearing piece: monetization would have killed it. Nobody contributes a fifty-line YAML file to a marketplace. They contribute it to a free community catalog that has their handle in the credits.

One small UX decision made the whole thing work: import preview. When you install a pack, Snipsy shows you a diff before anything touches your settings — added rows in green, conflicting triggers highlighted in red, an explicit prompt for each collision. Without that modal, users would never install a second pack — they’d be afraid the new one would silently overwrite their custom triggers. With it, installing five packs in a row feels safe.

I added the Packages tab in release 12. Downloads roughly doubled in the following month. The first-thirty-seconds experience went from “configure everything from scratch” to “pick a pack, start typing.”

Look sideways.

Beating the current leader head-on is rarely the most interesting opportunity in a niche. The higher-leverage move is usually to bridge what a neighboring ecosystem has solved that yours hasn’t.

What I’d do differently

Three things, if I were starting Snipsy today.

First, I’d add a CHANGELOG from release one. I started writing proper changelog entries around release eight, which means the first seven releases have garbage notes that look like “fixes and improvements.” When someone asks “what changed in version 0.5?” I have no good answer. Changelog discipline is cheap on day one and expensive to retrofit.

Second, I’d publish to the community catalog earlier. I sat on Snipsy in a private repo for about two months because I wanted it to be “ready.” It wasn’t ready when I published, either. It was just less embarrassing. Published-and-imperfect beats private-and-polishing every time, because feedback is the only signal that matters.

Third, I’d write the “when NOT to use this” section before the “what this does” section. Positioning before feature listing. I figured this out by accident; I’d do it on purpose now.

The bigger pattern

If I zoom out, the through-line across all of this is: indie OSS rewards constraint, not ambition. The temptation, on every release, is to add more. The discipline is to add less, document better, and ship boring infrastructure that keeps you from burning out.

Snipsy isn’t a remarkable plugin. Text expansion is a solved problem. But solved problems with abandoned leaders are exactly where indie projects have the most leverage, if you’re willing to keep the scope small and the maintenance sustainable.

If you maintain a niche tool — Obsidian plugin, VS Code extension, browser extension, whatever — has anyone tried the “when NOT to use this” section in their own README? I’m curious if it changed who showed up to your issue tracker.

GitHub logo

Dimagious
/
snipsidian

Hotstrings & snippets expansion inside Obsidian editor (trigger → replacement).

Snipsy

CI
Coverage
Release
Obsidian ≥ 1.5.0
License: MIT
Buy Me A Coffee

Type todo and a space, get - [ ]. Snipsy is the actively-maintained hotstring plugin for Obsidian — markdown-aware, with a community catalog and Espanso import. No scripting required.

demo.mp4

Why Snipsy

The text-expansion niche in Obsidian has three things wrong with it: the long-standing leader hasn’t shipped in four years, the alternatives lean on JavaScript-templating engines, and none of them is aware of markdown context. Snipsy is the simple answer:

  • Actively maintained. Regular releases, every change passes CI with attested artifacts. 0 vulnerable dependencies on the latest dependency scan.
  • Markdown-aware. Triggers don’t fire inside fenced code blocks, inline code, or YAML frontmatter. Most competitors don’t draw that line.
  • No scripting. If you need JavaScript or dynamic templates, use Templater — it’s purpose-built for that. Snipsy is for users who want hotstrings with zero setup.
  • Community catalog. 10 starter packs cover Markdown essentials, daily journaling, GTD, dev boilerplate…

The Context Tax: Why Every Cursor Session Costs You 15 Minutes

There’s a hidden cost senior engineers pay every morning when they open Cursor. It compounds quietly, it doesn’t show up on any dashboard, and at a typical senior billing rate it’s worth thousands of pounds a year. I call it the Context Tax. Here’s why it happens, what it actually costs, and the four-rule architecture I built to eliminate it.

The 15-minute problem

Open Cursor on Monday morning. New chat. You’re picking up where you left off Friday — adding a caching layer to OrderService. You type:

“Add Redis caching to this method.”

And the AI returns something technically correct, but architecturally wrong. It instantiates ConnectionMultiplexer directly in the service constructor. It uses IMemoryCache as if your project doesn’t already register IDistributedCache. It writes a synchronous wrapper around an async call.

You sigh. You type the preamble. Again.

“This project uses Mediator and Result<T>. We register caching through IDistributedCache in /Infrastructure/Caching. Don’t reach into ConnectionMultiplexer directly. Don’t put any caching logic in the Service layer — wrap it via a decorator in Infrastructure. And we never instantiate dependencies, we inject them via constructor.”

Three paragraphs. Maybe four. By the time the AI has the context it needs to actually be useful, you’ve spent fifteen minutes typing what your codebase has already shown it dozens of times before.

That’s the Context Tax.

Why it happens

Cursor — like every other AI coding tool right now — has no persistent memory between sessions. Each new chat starts fresh. Every architectural decision your team has ever made, every refactor your codebase has ever absorbed, every “we tried that, it doesn’t work for us” — gone the moment you close the window.

The AI isn’t stupid. It’s amnesiac.

Worse: even within a session, the AI’s understanding of your architecture is a fragile probability cloud. By Wednesday afternoon it has “forgotten” the convention it followed on Monday. It starts suggesting patterns that violate your boundaries. You correct it. It double-downs. You correct it again. It loops on the same hallucination, burning tokens, burning your time.

This is what I call AI drift, and on a long project it’s the single biggest reason senior engineers go from “Cursor is incredible” to “I’ve turned the autocomplete off.”

The cost

Let’s do the uncomfortable math.

  • 15 minutes per day spent on re-context and correcting drift.
  • ~250 working days per year.
  • That’s 62.5 hours. Nearly eight full working days annually.
  • At a UK senior dev billing rate of around £75/hour, that’s £4,687.50 of senior time, per engineer, per year — bleeding out invisibly.

On a five-engineer team that’s the cost of a junior hire.

The instinct that doesn’t work

The obvious response is to write a long system prompt with all your conventions and paste it at the top of every new chat. Most senior engineers I know have tried this. It fails for three reasons:

  1. Token bloat. A 2,000-token preamble at the start of every prompt is expensive and slow. Worse, the AI’s attention to specific rules degrades as the preamble grows.
  2. Discipline decay. You’ll do it for a week. By week two you’ll start skipping it for “quick” prompts. The drift returns.
  3. Static knowledge. Your architecture evolves. The preamble doesn’t. It’s a snapshot from whenever you last bothered to update it.

You don’t need a longer prompt. You need persistence.

What persistence actually looks like

The breakthrough — once you stop thinking of Cursor as a chat-bot and start thinking of it as a stateful pair-programmer — is that the AI needs three things to behave like a senior:

  1. A long-term memory file that lives in the repo, that the AI reads on every session start, and that the AI itself updates whenever it learns something new. Architectural Decision Records (ADRs) for the AI, by the AI.
  2. Scoped, directory-aware rules that load only when relevant — a DI rule fires on Startup.cs and ServiceCollectionExtensions.cs, not on every controller. Token budget preserved.
  3. A circuit breaker that detects when the AI is in a hallucination loop and forces it to stop, re-read the file, and ask a question rather than double down.

None of these are exotic. They’re standard senior-engineering moves applied to the AI itself.

The four-rule architecture

I packaged the pattern I’ve been using on real .NET codebases into four .mdc files Cursor reads natively:

  • arch-core.mdc — boundary enforcement. Detects which layer the current file lives in and refuses suggestions that cross it.
  • dotnet-di.mdc — DI auditor. Catches Scoped → Singleton capture, HttpClient instantiation, IConfiguration leaks into business logic.
  • bug-breaker.mdc — the circuit breaker. Watches for repeated failed attempts on the same problem and forces re-reading instead of doubling down.
  • persistence.mdc — the engine. Maintains LEARNING_LOG.md across sessions. Auto-appends decisions in an ADR-style format. Hydrates the AI on session start.

The whole thing lives in .cursor/rules/ and a single root-level LEARNING_LOG.md. Zero config. Zero build step. Commit it to your repo, your whole team gets the same baseline.

What “after” looks like

Monday morning, two months in. New chat:

“Add Redis caching to OrderService.”

The AI responds:

“Detected Scoped lifetime conflict with IMemoryCache (Singleton). Following Log Entry #27: ‘Prefer IDistributedCache abstraction in /Infrastructure/Caching.’ Applying decorator pattern via Scrutor per arch-core.mdc §3…”

No preamble. No correction loop. The AI already knows.

Try one rule for free

The arch-core-lite.mdc rule is a stripped-down version of the kit’s boundary guardian. Drop it in your .cursor/rules/ folder and watch your AI start respecting your layers today. Free download, no signup:

👉 Download arch-core-lite.mdc

Or just stop the Context Tax today

The full Agentic Architect kit — 4 specialist rules + the persistence engine + Learning Log template — is £19.99 one-time. Lifetime updates. MIT-licensed. 14-day no-questions refund.

👉 Get the full framework — £19.99

Originally published at agenticstandardcontact-byte.github.io/agentic-architect/blog. Canonical URL is set so the original gets the SEO juice.

Prompt Physics: Building a Cognitive Steering Layer for Gemma 4

Three reasoning trajectories on the same question — skeptical, causal, devil

This is a submission for the Gemma 4 Challenge: Build with Gemma 4

What I Built

explaind is a local-first cognitive steering layer for Gemma 4. It is not a chatbot wrapper, not an agent system, and not a RAG tool. It is a structured prompt-assembly harness that shapes how Gemma 4 reasons — not just what it says.

The core thesis: for instruction-tuned models like Gemma 4, prompt structure is part of the system design. The harness, the template, and the injection positions are not presentation — they are the engineering. explaind makes that engineering explicit, inspectable, and testable.

The system is built around Gemma 4’s documented failure modes rather than pretending they do not exist. Weak system prompt adherence, overconfidence, preference for parametric knowledge over injected context, and stochastic output variance are not problems to work around — they are the design brief.

What it does:

Feature What it does
8 reasoning abilities Structured bias vectors grounded in cognitive science: skeptical, causal, compressive, exploratory, calibrator, devil, updater, balanced
--compare Same question through multiple abilities side by side
--honest Two-pass self-critique: balanced first, then skeptical audit
--chain Sequential ability pipeline — each pass transforms the previous output
--consensus N Self-consistency aggregation based on Wang et al. (2022)
--scaffold Persistent JSON reasoning state across chain passes
Three-position BIAS FIELD Primacy, periodic, and recency steering signals designed around transformer attention position effects
359 passing tests Prompt geometry is locked down and verified
Terminal UX Rich Markdown rendering, color-coded ability headers, timed spinner, --dry-run and --trace for iterative inspection — designed for local use

Every design decision maps to a documented Gemma 4 failure mode:

Gemma 4 Failure Mode explaind Response
Weak system prompt adherence Three-position BIAS FIELD (primacy + periodic + recency)
Overconfidence / shallow elaboration calibrator ability + --honest two-pass critique
Prefers parametric knowledge over input --scratchpad and --context injection with semantic headers
Stochastic output variance --consensus N self-consistency aggregation (Wang et al. 2022)
Reasoning collapse without harness Cognitive scaffold with persistent JSON state across --chain
“Sounds smarter than it is” devil adversarial pressure + structured ability specifications

Demo

Run explaind --full-demo to see the full walkthrough live.

Demo 1 — Same question, three reasoning trajectories

Three reasoning trajectories on the same question — skeptical, causal, devil

“Was the 2008 financial crisis preventable?”

Skeptical interrogates the question’s framing before engaging its content:

Surfaced Assumptions Embedded in the Question's Framing:

1. Assumption of Actionability: The term "preventable" implies 
   that there exists a clear, identifiable intervention point...
2. Assumption of Linear Causality: The framing suggests a simple 
   cause-and-effect relationship...
3. Assumption of Moral Culpability: The question implicitly seeks 
   a judgment on whether actors should have acted differently...

Null Hypothesis Test: The null hypothesis which the skeptical 
analysis must test is that the crisis was inevitable...

Causal traces the mechanism backward from outcome to root condition:

Chain Trace (Working Backward):
  Proximate Cause <- Failure of Liquidity/Solvency
  Failure of Liquidity <- Excessive Leverage and Under-Capitalization
  Excessive Leverage <- Lax Risk Management and Regulatory Arbitrage
  Lax Risk Management <- Structural Flaws (Root Conditions)

Trigger vs. Root Separation:
  Root Conditions: deregulation, complex financial instruments,
  failure of regulatory bodies to enforce adequate capital requirements
  Triggering Condition: collapse in the U.S. subprime mortgage market

Devil constructs the strongest opposing case:

The strongest genuine counterargument is that the 2008 crisis was 
not preventable — an inevitable systemic consequence arising from 
inherent structural flaws, complexity, and interconnectedness of 
the global financial architecture.

The strongest version of the opposing case: The 2008 crisis was 
not preventable because it was an emergent property of a highly 
complex, interconnected, and inadequately regulated system.

Three clearly distinct reasoning trajectories. Same model. Same question. Different prompt physics.

Demo 1 Devil Response

Demo 2 — Self-critique (honest mode)

Demo 2 Honest Mode - Initial Response and Self-Critique

“AI will eliminate most jobs within 10 years.”

Initial response (balanced ability) acknowledges the claim and preserves uncertainty. Self-critique (skeptical audit) then interrogates it:

Surfaced Assumptions Embedded in the Claim:

1. Linearity of Technological Trajectory: The claim assumes the 
   current pace of AI will continue without inflection points...
2. Negligible Adaptation Rate: It assumes workforce capacity for 
   reskilling will be insufficient...
3. Stable Definition of "Job": The claim implicitly assumes the 
   concept of a "job" remains relatively stable...

Evidence Gap Analysis:
- No evidence detailing mechanisms by which AI leads to elimination 
  rather than transformation
- No longitudinal data on workforce adaptation programs
- "Most jobs" is undefined — the claim cannot be empirically tested

The self-critique is substantively different from the initial response — not a restatement, a genuine adversarial audit.

Demo 3 — Calibrated epistemic reasoning

Demo 3 Calibrator - HIGH and MEDIUM confidence markers

“Is the scientific consensus on climate change settled?”

[HIGH confidence] A broad scientific consensus exists regarding 
the fundamental physics of the greenhouse effect and the role of 
anthropogenic emissions in driving current global warming trends.

[MEDIUM confidence] The consensus is robust regarding the existence 
of human-caused warming, but not entirely "settled" regarding all 
future projections or the precise magnitude of future impacts.

Falsification Conditions:
1. The consensus would be overturned if high-quality independent 
   research definitively demonstrated that primary drivers of 
   warming are not anthropogenic...

Unknown Inventory (What is NOT known):
1. The precise, non-linear tipping points for climate feedback loops
2. The exact socio-economic consequences of various climate scenarios
3. The precise weighting of uncertainty across scientific disciplines

Explicit confidence markers. Named assumptions. Falsification conditions. Unknown inventory. This is calibrated reasoning, not performative hedging.

Demo Complete - explaind cognitive steering summary

Code

Full source code and README: brendanddev/explaind

python3.11 -m venv .venv && source .venv/bin/activate
pip install -e .
ollama pull gemma4-e2b_q4_k_m:latest
explaind --full-demo   # full narrative walkthrough
explaind --demo        # three curated live demos

Research Backing

  • Wang et al. (2022) — Self-consistency sampling → --consensus N
  • Liu et al. (2023) — Lost in the Middle, position effects in long contexts → three-position BIAS FIELD
  • Sclar et al. (2024) — Prompt format sensitivity → structured ability file format

How I Used Gemma 4

Model choice: gemma4-e2b_q4_k_m (quantized E2B)

I chose the E2B variant specifically for the edge deployment story — it runs on 8GB unified memory, which means the entire system works on a MacBook Air with no cloud dependency. The E2B is small enough to iterate with but capable enough to show real reasoning differentiation across abilities.

More importantly, E2B’s sensitivity to structured prompts is what makes the prompt physics approach work. A less instruction-sensitive model would ignore the BIAS FIELD. A model with perfect instruction following wouldn’t need it.

The architecture:

The assembled prompt follows a strict layer order:

SYSTEM PROMPT        <- primacy anchor injected here
GEMMA.md             <- universal invariant layer
                     <- periodic refresh #1
ABILITY              <- structured bias vector
                     <- periodic refresh #2
CONTEXT WINDOW       <- scratchpad + context injection
COGNITIVE SCAFFOLD   <- optional, --chain --scaffold only
BIAS FIELD           <- recency position, strongest signal
<user_input>

Every layer has a job. The BIAS FIELD appears in three positions because transformer attention research shows position matters for instruction persistence across long contexts. The primacy anchor sets the initial interpretive frame. The periodic refreshes fight drift in long prompts. The recency field is the final forceful instruction before user input.

What Gemma 4 specifically enabled:

The <|think|> token is Gemma 4-specific. When injected at the correct position — as the first token of <start_of_turn>model — it activates Gemma 4’s native thinking mode. This was a key discovery during development: placing it anywhere else in the prompt has no effect.

What I discovered empirically:

Building against a real running model produced findings that no static analysis would have caught:

1. The implicit thinking channel finding. At temperature=0.0, Gemma 4’s analytical abilities route reasoning entirely through implicit <|channel>thought blocks even without <|think|>. The model reasons correctly but the output layer suppresses it — only the input question echoes back. Explicit <|think|> activation makes that reasoning visible.

2. The Ollama double-wrapping issue. Without raw: true in the Ollama API call, hand-assembled chat template markers get wrapped inside Ollama’s own template application — producing nested broken turn structure. Every claim about the chat template’s effectiveness was contingent on this flag being set correctly.

3. The scaffold drift pattern. The cognitive scaffold works when the model complies with the JSON update instruction. Passes 2-3 of a chain showed turn token contamination (</start_of_turn>) bleeding into the JSON output and corrupting the parse. Graceful degradation handles this — drift_detected is set and output is preserved — but it is a real failure mode.

The failure documentation:

The README has an explicit “Where explaind Fails” section. Ability steering changes reasoning style more reliably than factual accuracy. Consensus at N>3 is slow on 8GB hardware. The three-position BIAS FIELD improves consistency but cannot guarantee instruction following. These are not marketing-friendly statements. They are part of the project.

Pain Points Will Always Outlive Platforms

We wanted to sell software licenses to customers in over 120 countries without any payment restrictions. The platform stores we had been using didn’t allow direct sales in certain countries due to sanctions and regulatory compliance issues.

The Problem We Were Actually Solving

Our architecture was designed to handle the complexities of international payment processing, but the platform stores’ limitations kept us from reaching a significant portion of the global market. Our revenue was stagnant, and we couldn’t grow our customer base as much as we wanted. We had to decide whether to work around the limitations of the platform stores or create our own infrastructure to handle direct sales.

What We Tried First (And Why It Failed)

Initially, we tried using third-party solutions that allowed us to bypass the payment restrictions, but these solutions came with high transaction fees, which cut into our revenue margins. We also implemented workarounds to manually process payments from restricted countries, but this introduced errors and inconsistencies in our bookkeeping. The system was brittle and prone to failures, and our customer support team was overwhelmed with inquiries about payment issues.

The Architecture Decision

After months of struggling with the limitations of the platform stores, we decided to build our own infrastructure for direct sales. We chose a headless e-commerce architecture with a RESTful API for payment processing and a modern warehouse as the data storage layer. This allowed us to handle payments and bookkeeping in a centralized location, eliminating the need for third-party solutions and manual workarounds. We also implemented robust error handling and monitoring to ensure that our system could withstand the increased traffic and volume of sales.

What The Numbers Said After

Our pipeline latency decreased by 30% after the transition to our own infrastructure, allowing us to process payments and fulfill licenses in real-time. Our query cost on the warehouse decreased by 50% due to the improved architecture and data storage. We were able to hit an average freshness SLA of 5 minutes for our sales data, which gave us more accuracy and confidence in our business decisions. Our team experienced a significant reduction in support requests and errors, freeing up resources to focus on growth and innovation.

What I Would Do Differently

In hindsight, I would have started with a more incremental approach to building our own infrastructure. We should have begun by migrating a small subset of customers to our new system and monitoring their behavior and feedback before scaling up. This would have allowed us to identify and address potential issues earlier in the process. I would also have invested more in automated testing and CI/CD to ensure that our new system was thoroughly tested and validated before going live. By taking a more incremental and iterative approach, we could have minimized the risks and downtime associated with the transition and ensured a smoother experience for our customers.

QAOA vs. 75,000 Nodes: Building a Hybrid Architecture to Solve NP-Hard Problems When Quantum Simulators Hit a Wall

Quantum computing today is firmly in the NISQ (Noisy Intermediate-Scale Quantum) era. In theory, everything sounds brilliant: quantum advantage, exponential speedup, and the ability to solve problems far beyond the reach of classical computers. However, in practice, anyone diving into algorithms like QAOA (Quantum Approximate Optimization Algorithm) eventually hits a “wall”—usually around 20–30 qubits.

But what if your task involves analyzing a social graph with tens of thousands of nodes? Take the Epinions dataset, for example, which contains over 75,000 users linked by thousands of trust relationships. Classical simulators simply “choke” on memory when attempting to process such a state vector.

In this article, I will show you how I turned this limitation into an engineering challenge. Instead of trying to “stuff the unstuffable” into a quantum processor, I developed a hybrid orchestrator that decomposes massive networks into quantum-accessible fragments. We’ll walk through the entire pipeline: from loading a GZIP archive to generating an optimized CSV report.

Architecture: Divide and Optimize
The main problem with large graphs is their connectivity. My approach relies on three stages, the logic of which is illustrated in the diagram above:

  1. Decomposition: We use classical community detection algorithms (Greedy Modularity). We break the massive graph into clusters where nodes are tightly connected internally but loosely connected between clusters. This localizes the MaxCut problem.

2.Quantum Solver (QAOA Core): Each cluster is passed to an optimizer based on PennyLane. Here, we run QAOA to find the configuration of states that maximizes the cut weight.

3.Orchestrator (Aggregation Layer): This is the “heart” of the system. It tracks the mapping between local and global IDs and aggregates the results of individual quantum circuits into a single report.

Technical Implementation: Code that “Cuts” Graphs
The foundation of our orchestrator is a streamlined pipeline for graph partitioning.

Simplified Orchestrator Logic

def run_orchestrator(graph_path):
# 1. Load and decompose
graph = load_graph(graph_path)
clusters = decompose_graph(graph, algorithm=’greedy_modularity’)

results = {}
for cluster_id, nodes in clusters.items():
    # 2. Map global IDs to local indices
    mapping = create_node_mapping(nodes)
    subgraph = graph.subgraph(nodes)

    # 3. Execute Quantum Solver
    if len(nodes) <= 20:
        results[cluster_id] = run_qaoa_solver(subgraph, mapping)
    else:
        # Recursive handling for large clusters
        results[cluster_id] = handle_large_cluster(subgraph)

return aggregate_results(results)

Key components:
decompose_graph: Breaks the graph down so the task becomes solvable on accessible hardware.

run_qaoa_solver: The quantum “brain.” Here, the MaxCut problem is mapped onto quantum gates.

run_orchestrator: A manager that iterates through all clusters and reconstructs the global picture.

Engineering Note: In real-world scenarios, cluster sizes vary. It is crucial to include a check like if len(nodes) > 20: to force the orchestrator to split overly large clusters, preventing memory overflow.

Hurdles: Why It Wasn’t Easy
Implementation was a battle against data limitations. Here are three problems I had to solve on the fly:

  1. Indexing: NetworkX reindexes nodes within a subgraph. I added a layer of mapper dictionaries (node_mapping) that preserves the link between a cluster’s local index and the original graph’s global ID.

2.Simulator Overflow: If the modularity algorithm produces a cluster that is too large, I implemented recursive partitioning. This turns the system into a hierarchical “tree-like” optimizer.

3.GZIP Handling: For 75k nodes, I use streaming (via yield) so the orchestrator processes the graph in parts without loading the entire file into RAM.

Conclusion
This project is not the finish line, but a proof of concept that hybrid systems are already capable of handling workloads that exceed the capacity of “off-the-shelf” quantum simulators. My next steps include integration with a real quantum backend and further optimization of the orchestrator.

I’d love to hear your feedback: How do you solve scalability issues in your quantum tasks?

Repository link: github