I Hooked My Autonomous AI Outreach Swarm to Notion via MCP – It Reports Every Cycle in Real-Time

I Hooked My Autonomous AI Outreach Swarm to Notion via MCP — It Reports Every Cycle in Real-Time

Submission for the Notion MCP Challenge.

What I Built

NEXUS → Notion MCP Bridge: a Python client that connects an autonomous AI outreach swarm to a Notion database using the official @notionhq/notion-mcp-server over stdio transport — no REST API, pure Model Context Protocol.

Every 90 seconds, the swarm runs a cycle: scrape a live Reddit thread, write a reply, score it 0.0–1.0. Every passing cycle becomes a Notion database page automatically, giving me a real-time command center to review AI-generated content before it goes live.

GitHub: https://github.com/fliptrigga13/nexus-notion-mcp

The Problem

I run an autonomous outreach swarm that generates Reddit replies for my AI monitoring product. The swarm runs overnight. By morning I had a JSON file with 200 entries and no way to review them at a glance.

Notion was the obvious answer — a queryable, filterable view with a checkbox to track what was posted. The question was how to connect them.

How the Notion MCP Bridge Works

The key design decision: use the official Notion MCP server as the transport layer, not the REST API directly.

AI Swarm (Python) → cycles.json → MCP Bridge → notion-mcp-server (stdio) → Notion DB

The bridge spawns notion-mcp-server as a subprocess and communicates over stdin/stdout using JSON-RPC 2.0:

class NotionMCPClient:
    def start(self):
        env = {**os.environ, "NOTION_API_KEY": self._api_key}
        self._proc = subprocess.Popen(
            ["notion-mcp-server", "--transport", "stdio"],
            stdin=subprocess.PIPE,
            stdout=subprocess.PIPE,
            env=env,
            text=True,
        )
        self._initialize()

    def _initialize(self):
        self._send({
            "jsonrpc": "2.0",
            "id": self._next_id(),
            "method": "initialize",
            "params": {
                "protocolVersion": "2024-11-05",
                "capabilities": {},
                "clientInfo": {"name": "nexus-notion-mcp-bridge", "version": "1.0"},
            },
        })

Once initialized, the bridge calls notion_create_page for each new cycle:

def create_page(self, database_id: str, cycle: dict) -> bool:
    resp = self._send({
        "jsonrpc": "2.0",
        "id": self._next_id(),
        "method": "tools/call",
        "params": {
            "name": "notion_create_page",
            "arguments": {
                "parent": {"database_id": database_id},
                "properties": {
                    "Name": {"title": [{"text": {"content": title}}]},
                    "Score": {"number": score},
                    "Posted": {"checkbox": posted},
                    "Timestamp": {"date": {"start": ts}},
                },
                "children": [body_block]
            },
        },
    })
    return resp and not resp.get("error")

The Notion Database

Each row captures the full cycle: score, whether it was posted, timestamp, thread context, and the full outreach copy in the page body. The database becomes a live review queue — filter by Posted = false, sort by Score descending.

Setup

npm install -g @notionhq/notion-mcp-server

export NOTION_API_KEY=your_token
export NOTION_DATABASE_ID=your_db_id

python nexus_notion_mcp_bridge.py          # continuous polling
python nexus_notion_mcp_bridge.py --once   # sync once and exit

Why MCP Instead of REST

The REST API would have worked. But using the MCP server as the transport layer means:

  1. Any MCP-compatible AI client can now interact with the same Notion workspace
  2. The bridge is swappable — swap notion-mcp-server for any other MCP server and the Python client stays the same
  3. The stdio transport keeps everything local — no HTTP overhead, no additional auth surface

The MCP protocol is the right abstraction for AI-to-tool communication. The swarm is an AI system, Notion is the tool, MCP is the protocol that connects them.

What’s Next

  • Add notion_update_page when a cycle gets posted to sync the checkbox back
  • Build a filter view surfacing only high-scoring unposted entries
  • Connect the REFLECT agent’s insights to a Notion notes page

GitHub: https://github.com/fliptrigga13/nexus-notion-mcp

Stop Paying $200/Month for Rank Tracking — Automate It with Apify in 30 Minutes

If you’re tracking keyword rankings for a site or client, you’ve hit the same wall: rank trackers are expensive. SEMrush starts at $130/month. Ahrefs at $99. Moz at $99.

But here’s what those tools are actually doing: fetching Google HTML and parsing it. That’s a solved engineering problem — and you can automate it yourself for about $1/month.

I built this pipeline to track 47 keywords across 3 small client sites. It runs every Monday without me touching it and logs results to a Google Sheet. This article shows you exactly how to replicate it.

The Real Pain

You have a site. You want to know if it’s ranking for 20–50 keywords. You don’t need competitive intelligence dashboards or backlink graphs. You just need: “Where does my site show up for this query today?”

Manual checking doesn’t work. You forget. You get inconsistent results based on your location, login state, and device. After keyword #10, you stop doing it entirely.

Scheduled automation fixes all three problems.

What We’re Building

A simple 3-part pipeline:

[Apify Actor] → runs on schedule → [webhook] → [n8n workflow] → [Google Sheet]
  1. Apify’s Google SERP scraper fetches real search results on a cron schedule
  2. Apify webhook fires when the run succeeds and triggers n8n
  3. n8n workflow fetches the dataset, finds your domain’s rank, and appends a row to Google Sheets

No servers. No databases. No DevOps. All free or near-free.

Step 1: Set Up the Actor

The actor: lanky_quantifier/google-serp-scraper — community-built, 98.3% success rate across 172+ external runs in the last 30 days. Reliable enough to trust for weekly tracking.

Input JSON:

{
  "queries": [
    "best python web scraping tutorial",
    "apify vs scrapy",
    "automate google search python"
  ],
  "countryCode": "US",
  "languageCode": "en",
  "maxPagesPerQuery": 1
}

Run it once manually to verify your keywords return data. Check that your domain appears in organicResults. If you’re not in the top 10, organicResults won’t contain your URL — that’s expected and useful signal.

Step 2: Understand the Output

Each result looks like:

{
  "searchQuery": { "term": "best python web scraping tutorial" },
  "organicResults": [
    { "position": 1, "url": "https://realpython.com/...", "title": "..." },
    { "position": 2, "url": "https://yourdomain.com/...", "title": "..." }
  ]
}

To find your rank across all keywords:

def find_rankings(results, your_domain):
    """
    Returns a list of (keyword, position_or_None) tuples.
    position is None if domain not found in top 10.
    """
    rankings = []
    for item in results:
        keyword = item["searchQuery"]["term"]
        position = None
        for result in item.get("organicResults", []):
            if your_domain in result.get("url", ""):
                position = result["position"]
                break  # Found it — stop scanning this keyword's results
        rankings.append((keyword, position))
    return rankings

Note: The outer loop iterates keywords; the inner loop scans positions within that keyword’s results. The break is inside the inner loop — we stop scanning positions once we find the domain, then move to the next keyword. Don’t nest your return inside the outer loop or you’ll only ever process the first keyword.

Step 3: Automate with n8n + Google Sheets

3a. Set up the Google Sheet

Create a sheet called Rankings with these headers:

Date Keyword Position URL

3b. Create the n8n workflow

n8n is free to self-host. The workflow has 4 nodes:

  1. Webhook trigger — receives the Apify notification
  2. HTTP Request — fetches the run’s dataset from Apify API:
   GET https://api.apify.com/v2/datasets/{{$json.resource.defaultDatasetId}}/items
  1. Code node — finds your domain’s position (same logic as the Python snippet above, in JavaScript)
  2. Google Sheets node — appends rows

3c. Wire the Apify webhook

In Apify → Actor → Integrations → Webhooks:

  • Event type: ACTOR.RUN.SUCCEEDED
  • URL: Your n8n webhook trigger URL
  • Payload template (default is fine — n8n gets resource.defaultDatasetId from it)

That’s it. Now every time the scheduled run completes successfully, the sheet gets updated.

Step 4: Schedule the Actor

In Apify → Actor → Schedules, create a schedule using cron syntax:

0 9 * * 1

Every Monday at 9am UTC. Your data arrives before the workweek begins.

What This Costs

Component Cost
Apify free tier $5/month credit included
~50 keyword checks/week ~$0.50–$1.00/month
Google Sheets Free
n8n (self-hosted) Free
Total ~$1/month

vs. $99–$200/month for a SaaS rank tracker.

The math works because PAY_PER_EVENT billing means you only pay for actual compute — not for the product team, sales team, and VC returns baked into SaaS pricing.

What You See After 4 Weeks

Date Keyword Position URL
Mar 25 best python scraping tutorial 7 https://…
Mar 18 best python scraping tutorial 9 https://…
Mar 11 best python scraping tutorial 12 https://…
Mar 4 best python scraping tutorial Not in top 10 —

Position 12 → 7 in 3 weeks. That’s actionable signal. You can chart it in Sheets in 2 minutes, or set up a Sheets formula to alert you when any keyword drops below position 15.

Caveats Worth Knowing

Geo variance: Google results differ by location. The countryCode input pins the location, but results can still vary slightly. Good enough for trend tracking; not reliable for precise position reporting to clients who check manually.

Top 10 only: maxPagesPerQuery: 1 returns ~10 organic results per keyword. If you’re not on page 1, you’ll see null — which is still useful (“not ranking yet”).

Google’s anti-bot measures: The actor handles proxy rotation. You don’t need to manage this, but it’s why running this at home without proxies tends to fail. The Apify platform’s residential proxy pool is what makes the 98.3% success rate possible.

The Pattern Scales Further

The same architecture works for other data types:

  • Price monitoring → swap for lanky_quantifier/amazon-product-scraper, track competitor prices weekly
  • Job market signals → use lanky_quantifier/linkedin-job-scraper to track skill demand in your market
  • Content gap analysis → run SERP checks on competitor brand keywords to find topics they’re ranking for that you’re not

The Apify platform handles proxies, browser rendering, and rate limiting. You write the business logic.

Get Started

  1. Create a free Apify account — $5/month credit, no card required
  2. Open lanky_quantifier/google-serp-scraper and run it with your keywords
  3. Set up the Google Sheet + n8n workflow (~20 minutes)
  4. Add the schedule and webhook

The first week you’ll catch a ranking change you’d have missed. By month two, you’ll have a trend line that tells you whether your content efforts are actually working.

Stack preferences vary — the same pattern works with Airtable, Notion, or a plain SQLite file on a VPS. The actor output is JSON. Pipe it wherever you want.

I built my own icon platform after getting frustrated with FontAwesome — here’s how each feature came to life

I’m a 20-year-old student from Vietnam. I built Viconic (viconic.io.vn) alone, with no team and no sponsor. Every single feature came from a real frustration I had while building web projects.
Here’s the story.

It started with FontAwesome

When I first learned web development, I used FontAwesome like everyone else. But the kit builder was confusing — I couldn’t figure out how to generate a CDN link on my own. I ended up having to go to cdnjs.com just to grab a script tag. The free tier also had very few icons.
At the time, I had no idea SVGrepo or Iconify even existed.
So I started building my own thing.

Every feature came from a real problem

SVG injection instead of font icons

My first approach was to use icomoon to generate font icons. But I quickly ran into a problem — font-based icons can’t render multicolor icons accurately. Colors get lost or merged.
So I switched to SVG injection. Icons load directly via a custom tag from the CDN, similar to how Iconify works. Bundle size stays at 0KB since nothing is bundled — icons are fetched at runtime.

Kit builder for offline use

I study at a university where the internet connection is often unstable or completely unavailable. I needed a way to prepare a set of icons in advance and use them offline — similar to icomoon’s kit system, but without the font limitations.
So I built a kit builder. You pick the icons you need, give the kit a name, and Viconic generates a custom CDN URL for that kit. Load that URL once and all your icons are ready — even offline after the first load.

Inline SVG editor

Every time I needed to inspect or quickly tweak an icon, I’d open a new tab and go to svgviewer.dev. After doing this dozens of times, I got tired of it.
So I built an SVG editor directly into Viconic. You can adjust colors, size, and preview the result in real time — without leaving the page.

Fast mode

Sometimes I just need to copy an icon quickly. Opening a detail modal every single time felt unnecessary.
So I added fast mode. When enabled, clicking an icon copies it instantly — no modal, no extra steps.

What Viconic has now — completely free

  • 200,000+ icons from Lucide, Tabler, FontAwesome, and many more
  • CDN + npm package with zero bundle size
  • SVG injection via custom tag
  • Kit builder — create your own icon set with a custom CDN URL
  • Inline SVG editor — adjust colors and size directly in the browser
  • Copy as JSX, SVG, HTML, or Tailwind class in one click
  • Fast mode — click to copy instantly
  • Individual page for each icon — download SVG or PNG

Why I’m sharing this

I’m not sharing this because Viconic is perfect. It’s still in open beta and there’s a lot left to build.
I’m sharing because every feature on this list came from a real problem I personally ran into. No market research, no competitor analysis — just solving my own frustrations one by one.
If you’ve ever felt like existing icon tools were either too complex, too limited on the free tier, or just not designed for how you actually work — Viconic might be worth a try.
👉 https://viconic.io.vn
I’d love honest feedback — what’s missing, what’s broken, what would make you actually use this over SVGrepo or Iconify.

React Native in 2026: The New Architecture Guide

React Native keeps evolving. With React Native 0.87, the New Architecture is standard, Hermes and Fabric are fully mature, and building high-performance apps is easier than ever.

⚡ What’s New in 2026?
1️⃣ JSI (JavaScript Interface)
Direct JS ↔ C++ communication
No JSON serialization → faster interop
Real-time processing (e.g., VisionCamera handles 30MB frames at 2GB/s)
2️⃣ Fabric Renderer
Synchronous UI pipeline → smooth 60 FPS
Faster touch responses & less UI congestion
Full support for React 18 concurrent features
3️⃣ TurboModules
Lazy-loaded native modules
Faster startup & lower memory usage
📊 Real-World Gains
Shopify: 10% faster Android launch, 3% iOS
Memory dropped 85MB → 52MB
Smooth animations even on heavy screens
🛠 Key Features (RN 0.83 → 0.87)
Hermes V1/V2 improvements → faster TTI & bundle loading
Expo Go updates → instant testing on devices
Reanimated 4 & FlashList 2 → smoother animations & lists
TypeScript first-class support → better IDE autocomplete
🔧 Migration Tips
Audit dependencies → ensure New Architecture compatibility
Upgrade gradually → enable flags per platform
Migrate native modules → TurboModules + Codegen
Monitor performance → Flipper, frame rates, startup times
🎓 Best Practices
Use React concurrent features (useTransition)
Use useLayoutEffect for synchronous measurements
Lazy load TurboModules
Optimize with Hermes V1/V2
🔮 Looking Ahead

React Native 1.0 is near. Expect:

Stability & zero-breaking-change releases
Performance optimization
Better tooling & deeper platform integration

React Native 2026 = faster, smoother, and developer-friendly.

💬 Have you migrated to the New Architecture yet? Share your experience!

ReactNative #MobileDevelopment #JavaScript #Performance #NewArchitecture #TypeScript

I Connected Claude to My IBM i – And It Changed How I Think About Legacy Modernization

What happens when you give an AI direct access to a system most people have never heard of

There’s a weird disconnect in the AI conversation right now. Everyone’s talking about coding assistants, AI-powered DevOps, intelligent dashboards — but almost all of it assumes you’re running modern cloud infrastructure. Kubernetes, PostgreSQL, GitHub Actions. The usual stack.

Nobody’s talking about what happens when your critical business system runs on IBM i.

I work with IBM i every day. Warehouses, supply chains, enterprise systems that process millions of transactions and have been running for decades. These systems aren’t going anywhere. They’re stable, they’re fast, and they do exactly what they’re supposed to do.

But they’ve also been left out of the AI conversation entirely. And I kept wondering — does it have to be that way?

The problem with IBM i and modern tooling

If you’ve worked with IBM i, you know the feeling. You’re reading about some new tool or platform, nodding along, and then you hit the part where they assume everything lives in a REST API or a cloud database. And you think — okay, that doesn’t apply to me.

The irony is that IBM i systems often hold the most valuable data in an organization. Decades of transaction history. Real-time inventory positions. Production schedules that run 24/7. But getting that data out — or even just asking questions about it — still involves signing into a green screen, navigating menus, running queries manually, and interpreting raw output.

It’s not that the data isn’t accessible. Db2 for i is a perfectly capable database. QSYS2 SQL Services have made an incredible amount of system information queryable through standard SQL. The access is there. But the experience of getting to it hasn’t kept up with what’s happening everywhere else.

When I discovered MCP

Earlier this year I came across the Model Context Protocol — MCP for short. It’s an open standard that lets AI assistants connect to external tools and data sources. Anthropic published it, and it’s gained traction quickly. The idea is simple: you define tools that the AI can call, and the AI figures out when and how to use them based on what you’re asking.

The moment I understood how it worked, the wheels started turning.

What if I could write a handful of tools — run a SQL query, list active jobs, check system status, browse the IFS — and expose them to Claude through MCP? Not building a chatbot. Not training a model on IBM i documentation. Just giving an AI the ability to reach into the system and pull back real data.

So that’s what I did.

How it works (without the complexity you’d expect)

The architecture is embarrassingly simple, which is part of what makes it powerful.

You write a small Python server that runs on your local machine. This server connects to IBM i through ODBC — the same driver you probably already have installed if you use ACS (IBM i Access Client Solutions). The server defines tools as Python functions, each with a description that tells the AI what it does.

That’s the entire stack. Python on your PC, ODBC to IBM i, MCP to the AI. No changes on the IBM i side. No new programs to deploy. No RPG modifications. No service entries. Nothing.

The secret sauce, if there is one, is QSYS2 SQL Services. IBM has been quietly building out an incredible set of SQL-accessible system functions over the last several technology refreshes. Active job info, job logs, spool files, IFS statistics, system values, user profiles, message queues, data areas — almost everything you’d normally access through CL commands or green screen menus is now available as SQL table functions.

This means every tool in the MCP server is just a SQL query. Clean input, structured output. The AI gets JSON back instead of green-screen text, which it can actually interpret and reason about.

What it feels like to use

This is the part that genuinely surprised me.

I expected it to be useful. A faster way to run queries, maybe. A convenience layer. What I didn’t expect was how much it would change the way I interact with the system.

You can ask things like:

“Show me all libraries that start with PROD and tell me how many tables are in each one.”

And it just does it. Runs the query, counts the results, gives you a formatted answer. No navigating to WRKLIB. No typing SQL into STRSQL. Just a question and an answer.

Or:

“What’s the system health looking like right now? Anything I should worry about?”

It pulls CPU usage, memory, disk capacity, active job counts — and then interprets them. Not just raw numbers, but context. “CPU is at 23%, well within normal range. Disk usage on ASP 1 is at 71%, which is getting up there — you might want to keep an eye on that.”

The real magic happens when you chain things together in a conversation. You ask about active jobs for a specific user. Something looks odd. You say “check the job log for that one.” It knows which job you mean from the previous response. Then you say “has this user had issues before? Check their message queue.” And it does.

That kind of continuity — where context carries forward naturally — is something you can’t replicate with traditional tools. Every green screen interaction is stateless. You close the screen, the context is gone. Here, the AI holds onto the thread and builds on it.

The tools that matter most

After using this for a while, I’ve found that certain tools get used far more than others.

SQL queries are the backbone. Being able to say “find all orders from the last 48 hours where the quantity is over 500” and get results instantly — that alone would justify the setup. But when you combine it with the AI’s ability to interpret and summarize, it becomes something different. You’re not just querying data, you’re having a conversation about it.

System status is surprisingly useful. I used to check WRKACTJOB and WRKSYSSTS a few times a day. Now I just ask. And the AI remembers what “normal” looked like from previous checks, so it can flag when something changes.

Job logs are where the AI really shines. Reading job logs on IBM i is tedious — they’re dense, full of informational messages mixed in with the important stuff. The AI is genuinely good at scanning through a job log and picking out the messages that matter. “There are 47 messages in this job log. Most are routine. But there’s a CPF4131 at 14:23 indicating a file member not found, and a follow-up CPD0006 — that’s likely your issue.”

IFS browsing and file reading is the one I didn’t expect to use as much as I do. Being able to say “show me what’s in /home/myuser/exports and read the most recent CSV” is just faster than navigating the IFS through any other method.

Why QSYS2 is the unsung hero

I want to spend a moment on this because I think it’s underappreciated.

IBM has been building QSYS2 SQL Services for years. Every technology refresh adds more. And the beauty of it is that it turns everything on the system into structured, queryable data. You don’t need to parse command output. You don’t need screen-scraping programs. You just write SQL.

For this kind of AI integration, that’s everything. The AI needs structured data to reason about. Give it a blob of green-screen text and it’ll struggle. Give it a JSON array of job records with named fields and it’ll do exactly what you want.

If your shop hasn’t explored what’s available through QSYS2 lately, it’s worth looking. The coverage now is extensive — far beyond what most people realize. It’s one of the best things IBM has done for the platform in recent years, and projects like this show why.

What this means for IBM i shops

I’m not going to oversell this. It’s not going to replace your operators. It’s not going to automate your entire system. It’s a tool.

But it’s a tool that addresses something I’ve seen at every IBM i shop I’ve worked with: the knowledge bottleneck.

There are usually one or two people who really know the system. Who know which libraries matter, what the critical jobs are, where to look when something breaks. When those people are unavailable — or when they eventually leave — that knowledge walks out the door.

An AI that can query the system, interpret results, and carry context across a conversation doesn’t replace that expertise. But it makes it accessible to people who don’t have it yet. A junior developer can ask “what are the biggest tables in PRODLIB?” and get an immediate, meaningful answer. A manager can check on system health without learning CL commands. A new team member can explore the system conversationally instead of reading documentation that may or may not be current.

That accessibility matters. IBM i’s biggest challenge has never been capability — it’s been the perception that it’s impenetrable. Anything that makes it more approachable is a win for the platform’s long-term viability.

What I’d build next

This was a starting point. The obvious extensions:

  • Source member browsing — being able to read RPG or CL source through a conversation and have the AI explain what it does. Imagine onboarding new developers who can literally ask the AI “what does this program do?” while looking at the source.
  • Authority analysis — “who has access to this file?” is a question that takes too long to answer today.
  • PTF and system maintenance status — turning system administration checks into conversations.
  • Cross-referencing — “which programs use this file?” by querying object references through SQL Services.

The protocol makes this extensible. Adding a new capability is just adding a new function with a description. There’s no framework overhead, no deployment complexity. The hardest part is writing good SQL, and if you’re on IBM i, you’re already doing that.

The bigger picture

I started building this because I was curious. I kept building it because it genuinely made me more productive. But what excites me most is what it represents.

For years, IBM i modernization has been framed as “move off the platform” or “rewrite everything.” And both of those approaches are expensive, risky, and often unnecessary. The systems work. The data is valuable. The business logic is proven.

What MCP shows is that you can bring modern capabilities to IBM i without changing IBM i. You don’t need to rewrite your RPG. You don’t need to migrate your database. You don’t need to replace anything. You just need to build a bridge — a thin layer that translates between what the AI expects and what IBM i provides.

QSYS2 SQL Services is one half of that bridge. MCP is the other. And the fact that you can connect them with a few hundred lines of Python — no middleware, no platform changes, no vendor contracts — is exactly the kind of pragmatic modernization that actually works in enterprise environments.

Wrapping up

I’m not naive about the limitations. There are security considerations — you need to be thoughtful about what you expose and to whom. There’s the question of audit trails and compliance. And the AI will occasionally get a query wrong, just like any tool.

But the potential here is real. And it’s the kind of potential that doesn’t require permission from a steering committee or a six-month project plan. It’s a Saturday afternoon experiment that turns into something you use every day.

If you’re in an IBM i shop and you’ve been wondering where AI fits into your world, this might be the answer. Not a massive transformation initiative. Not a vendor platform. Just a conversation with your system that actually understands what you’re asking.

Jaya Krushna Mohapatra is a Warehouse Management Systems Architect focused on enterprise integrations, IBM i modernization, and scalable backend systems.

You’re Migrating Off Opsgenie. Here’s What You Should Actually Fix.

You’re Migrating Off Opsgenie. Here’s What You Should Actually Fix.

Opsgenie’s end-of-support is April 2027. If you’re on a small engineering team, you’re probably mid-migration right now — comparing PagerDuty pricing tiers, reading incident.io vs. BetterStack threads, maybe resigning yourself to Jira Service Management because you’re already deep in the Atlassian ecosystem.

I want to suggest something uncomfortable before you pick your next tool: alerting was never your actual problem.

I managed Opsgenie rotations at three different companies over the past eight years. FreightWaves, TextNow, Pilot Flying J. Different industries, different stacks, different team sizes. The pattern was always the same.

Someone would deploy a change. Something would break. Opsgenie would page the on-call engineer. That engineer would open a Notion doc titled “Runbook — Service X” that hadn’t been updated since 2022. They’d mostly ignore it and Slack the person who wrote the service. That person would fix it. Everyone would move on. Two weeks later, something similar would happen again.

Opsgenie did its job perfectly. It routed the alert to the right person. The problem was everything else.

The question nobody was asking

At none of those companies — not one — did anyone ask the obvious question before deploying: is it safe to push right now?

Not “did CI pass.” Not “did someone approve the PR.” I mean: is the system healthy enough to absorb a change right now? Are we burning through error budget? Is there already an active incident? Did someone just deploy 20 minutes ago and we haven’t seen the impact yet?

Nobody asked because there was no way to answer it. The information existed — scattered across Datadog, PagerDuty, GitHub, Slack — but nobody had assembled it into a single decision. So engineers deployed based on gut feel. “Seems fine.” “I don’t see anything in Slack.” “The dashboards look okay I guess.”

43% of incidents are preceded by a recent deploy. That number didn’t surprise me at all when I first saw it. It matched what I’d lived through.

The runbook problem is worse than you think

Here’s the thing about the Opsgenie migration conversation that nobody is having: most teams using Opsgenie didn’t just use it for alerting. It was their entire incident process. Alert comes in, Opsgenie pages someone, that person figures it out. There was no structure beyond that.

The runbooks — if they existed — lived in Confluence or Notion. I wrote about this in incident management without a dedicated SRE, and the core problem hasn’t changed: a runbook that’s three clicks away from the alert that triggered it is a runbook that doesn’t get opened at 3am.

I’ve seen this enough times to have a visceral reaction to it. The on-call engineer gets paged, opens Slack, asks “has anyone seen this before?” and waits. Meanwhile the customer is staring at a broken login page. The runbook that would have told them to check the config deployment and roll back the last change is sitting in a Confluence space that the engineer didn’t even know existed.

Teams that connect their runbooks directly to their alerts — so the runbook opens automatically when the relevant alert fires — cut their mean time to resolution from 67 minutes to 23. That’s not a marginal improvement. That’s the difference between an incident that costs you a customer and one that costs you 20 minutes.

What “migrating off Opsgenie” should actually mean

If you’re going to rip out a core piece of your incident workflow, this is the moment to ask harder questions than “which alerting tool has the best Slack integration.”

The questions I’d ask:

Do you know whether it’s safe to deploy right now? Not in a gut-feel way. In a “here’s your error budget status, here’s your active incident count, here’s your deploy velocity over the last 24 hours” way. If you don’t have that, you’re going to keep causing the incidents that your new shiny alerting tool routes to your team.

When someone gets paged, do they know what to do? Not “figure it out.” Actually know — because the runbook showed up in front of them automatically, with the steps they need and the context about what changed. If your runbooks are still in a wiki, your migration isn’t going to fix the thing that actually hurts.

Are you learning anything from your incidents? Not in a blameless-postmortem-Google-Doc way. I mean: does your system know that this service broke last Tuesday after a similar deploy? Does your deploy process incorporate the history of what’s gone wrong before? Most teams I’ve worked with have zero institutional memory. Every incident is treated as a surprise, even when it’s the third time it’s happened.

Don’t just swap your paging tool

The Opsgenie shutdown is a forcing function. Use it.

If you just swap Opsgenie for PagerDuty or BetterStack, you’ll have the same problem in a different UI. Engineers deploying blind. Runbooks gathering dust. Your on-call rotation burning people out because every incident starts from scratch. I wrote about the monitoring version of this trap in your startup doesn’t need better monitoring — the tooling isn’t the bottleneck. The process is.

The actual fix is a layer that sits before your alerting tool. A deploy gate that tells your team whether it’s safe to push. Connected runbooks that show up when things break. Incident data that compounds into institutional knowledge so your team stops relearning the same failure every quarter.

That’s what I’m building at Strake. It’s in private beta right now and it works alongside whatever alerting tool you pick — PagerDuty, BetterStack, Grafana OnCall, whatever. The deploy gate and the runbook layer are the parts that were always missing, regardless of who was routing the page.

If you’re mid-migration and want to talk through how your team handles deploy safety, I’m happy to jump on a call. Not a pitch — I’m genuinely trying to learn from teams going through this right now.

Rob is building Strake — a deploy gate and incident workflow platform for engineering teams without dedicated SRE coverage. If your current incident process is “someone posts in Slack and we figure it out from there,” come take a look at strake.dev.

From WooCommerce Product Save to ‘Add to Cart’ in a Chat Conversation

A store owner saves a product in WooCommerce. Three seconds later, a customer on the other side of the world finds that product by typing “something warm for winter” into a chat widget, adds it to their cart, and checks out. The store owner sees the purchase attributed to the chat session on their dashboard.

That’s a lot of systems talking to each other. Here’s what happens at each step.

Step 1: Product Save Triggers a Webhook

The WooCommerce plugin hooks into woocommerce_update_product. When the store owner clicks “Publish” or “Update,” the hook fires and queues a webhook event in memory.

The event isn’t sent immediately. It goes into an in-memory queue that flushes on WordPress’s shutdown action, after the HTTP response is already sent to the browser. The admin page loads at normal speed. The webhook happens a fraction of a second later.

// Simplified from the actual plugin
add_action('woocommerce_update_product', [$this, 'on_product_update']);
add_action('shutdown', [$this, 'flush_webhook_queue']);

The queue deduplicates. If a product triggers multiple hooks in one request (saving a variable product fires hooks for the parent and each variation), each product ID is queued once. The batch goes out as a single HMAC-signed POST to the webhook endpoint.

The payload is a consolidated JSON structure — one event per product with all translations nested inside:

{
  "events": [{
    "type": "product.updated",
    "data": {
      "identification_number": "product-42",
      "sku": "product-42",
      "channels": ["default"],
      "names": {"default": {"en": "Merino Wool Hiking Jacket"}},
      "descriptions": {"default": {"en": "Warm, breathable, water-resistant..."}},
      "links": {"default": {"en": "https://store.com/merino-wool-hiking-jacket"}},
      "categories": {"default": {"en": ["Outerwear > Jackets"]}},
      "brands": {"default": "TrailPeak"},
      "prices": {"default": [
        {"currency": "USD", "current_price": 149.99, "regular_price": 189.99}
      ]},
      "availability_statuses": {"default": "available"},
      "stock_quantities": {"default": 25},
      "attributes": {"default": {"en": {"Material": "Merino Wool", "Weight": "380g"}}},
      "images": {"default": ["https://store.com/jacket-front.jpg"]},
      "is_parent": false,
      "parent_sku": null,
      "variation_attributes": {}
    }
  }]
}

For multilingual stores (Polylang or WPML), the plugin sends all translations in a single event with nested language keys. A store with 3 languages and 1,000 products generates 1,000 events during a full sync, not 3,000.

Step 2: Webhook Endpoint Validates and Queues

The webhook hits the receiving endpoint. Five checks happen before anything is processed:

  1. Rate limit (120 requests/60s per store, sliding window in Redis)
  2. Store lookup
  3. Subscription check
  4. HMAC-SHA256 signature verification
  5. Pydantic schema validation on every event

If all pass, the events go to a Celery task. The endpoint returns 202 and the store doesn’t wait.

Step 3: Embedding (or Skipping It)

The Celery worker picks up the events. For each product, it computes a SHA-256 hash of the content that affects search quality (name, SKU, category, brand, description, attributes). If the hash matches what’s already stored, and only the price or stock changed, the existing embedding is reused. No inference call needed.

If the content actually changed, the product goes to the inference service for a new embedding. The model (intfloat/multilingual-e5-large, 1024 dimensions) encodes the product into a vector that captures its meaning across 100+ languages.

For a typical product update where only the price changed, the embedding step is skipped entirely. During a full resync of 10,000 products where maybe 50 descriptions changed, this saves thousands of inference calls.

The product and its embedding get upserted to Qdrant, which stores both the vector and a BM25 index of the text fields.

Step 4: A Customer Types “Something Warm for Winter”

A customer on the store opens the chat widget and types a query. The message goes to a LangGraph-based pipeline.

First, a classifier looks at the message and decides which agents need to handle it. “Something warm for winter” goes to the product expert. “Something warm for winter, and what’s your return policy?” goes to both the product expert and the customer support agent, running in parallel.

The product expert runs a hybrid search:

BM25 searches the text fields for exact token matches. Good for brand names, SKUs, specific attributes.

Vector search finds products that are semantically close to the query. “Something warm for winter” lands near jackets, sweaters, thermal gear, even though none of those product titles contain the words “warm” or “winter.”

Both result sets get normalized to the same score range and merged. A cross-encoder reranker (ms-marco-MiniLM-L-6-v2) then compares each candidate directly against the query and re-sorts. The Merino Wool Hiking Jacket from step 1 scores high on both the vector similarity (warm + winter + outdoor) and the reranker.

The agent formats a response with product recommendations, prices, and comparison notes. If multiple agents ran in parallel, an LLM merges their responses into one coherent reply.

Step 5: Customer Adds to Cart

The customer sees the product card in the chat and clicks “Add to Cart.” The widget calls window.EmporiqaCartHandler({ action: 'add', items: [...] }) on the store’s frontend. This is a JavaScript function registered by the WooCommerce plugin that talks to WordPress AJAX endpoints:

emporiqa_add_to_cart    → WC()->cart->add_to_cart()
emporiqa_get_cart       → current cart state
emporiqa_update_cart    → change quantity
emporiqa_remove_from_cart → remove item
emporiqa_clear_cart     → empty cart

The plugin resolves Emporiqa’s identification_number format (product-42, variation-123) back to WooCommerce integer IDs. For variable products, it resolves the correct variation based on attributes. After every operation, the WooCommerce mini-cart in the page header refreshes automatically.

The cart operations go through WooCommerce’s own service layer. Store-specific rules (promotions, minimum quantities, inventory limits) still apply.

Step 6: Checkout and Conversion Attribution

The customer proceeds to checkout. The plugin hooks into woocommerce_checkout_order_processed (and the block checkout equivalent, woocommerce_store_api_checkout_order_processed) and reads the emporiqa_sid cookie (the chat session ID, set when the widget opened). It saves this as order meta.

When the order status changes to processing, completed, or on-hold (or woocommerce_payment_complete fires), the plugin sends an order.completed webhook with the session ID, line items, and totals. On the receiving end, this purchase gets attributed to the chat session that recommended the product.

One problem I had to solve: external payment gateways (PayPal, Stripe) redirect server-to-server. The browser cookie is gone by the time the order status changes. The fix was capturing the session ID during checkout (when the browser is still present) and storing it as order meta. The payment completion hook then reads it from the database, not the cookie.

// During checkout (browser present)
add_action('woocommerce_checkout_order_processed', function($order_id) {
    if (isset($_COOKIE['emporiqa_sid'])) {
        $order = wc_get_order($order_id);
        $order->update_meta_data('_emporiqa_session_id', sanitize_text_field($_COOKIE['emporiqa_sid']));
        $order->save();
    }
});

// On order status change or payment complete (server-to-server, no cookie)
// Hooks: woocommerce_order_status_processing, _completed, _on-hold, woocommerce_payment_complete
add_action('woocommerce_order_status_processing', function($order_id) {
    $order = wc_get_order($order_id);
    $session_id = $order->get_meta('_emporiqa_session_id');
    // Send order.completed webhook with session_id for attribution
});

A _emporiqa_order_tracked meta flag prevents duplicate sends. Payment gateways sometimes fire status hooks multiple times.

Step 7: The Dashboard

The store owner opens the dashboard and sees:

  • Chat sessions → cart adds → checkouts → purchases, with conversion rates at each step
  • Revenue attributed to chat conversations
  • Which products get recommended most, which get added to cart
  • Widget engagement (loads, opens, unique visitors)

This closes the loop. Product save → webhook → embedding → search → agent response → cart → purchase → attribution → dashboard.

What’s Extensible

The WooCommerce plugin exposes WordPress filters for customization:

  • emporiqa_should_sync_product / emporiqa_should_sync_page to skip specific items
  • emporiqa_product_data / emporiqa_variation_data to modify payloads before sending
  • emporiqa_order_tracking_data to add shipment tracking numbers to order lookups
  • emporiqa_widget_enabled to disable the widget on specific pages

Standard WordPress patterns, no forking needed.

What I’d Do Differently

The conversion attribution relies on a cookie. If the customer clears cookies between chatting and checking out, the link breaks. There’s no server-side fallback that connects the customer’s identity across sessions. A logged-in user ID match would fix this, but it adds complexity for guest checkouts.

I wrote about the hybrid search pipeline, the sync architecture, and the parallel agent system separately. This post is about how they connect. The WooCommerce plugin is live on wordpress.org. I covered the full WooCommerce integration on the Emporiqa blog, and the setup docs walk through the configuration step by step. You can test the pipeline end-to-end with a free sandbox at Emporiqa.

How to Debug Partial Cron Job Failures (15 Success, 6 Errors Out of 21)

TL;DR

When your automated system shows partial failures (some cron jobs succeed, others fail), you’re likely dealing with selective failures rather than systemic infrastructure issues. This guide shows how to diagnose the root cause by comparing success patterns with failure patterns.

Prerequisites

  • Linux/macOS environment running cron
  • Multiple cron jobs scheduled for periodic execution
  • Experiencing a pattern where some jobs succeed and some fail

The Problem: 15 Out of 21 Jobs Succeed

Real-world scenario from production:

Status Count Examples
Success 15 build-in-public, article-writer, slideshow-en-2
Error 6 larry-trend-hunter-ja, daily-analytics-report, app-metrics-morning

Key observations:

  • All EN-side posts (slideshow-en-1/2/3) succeeded
  • JA-side posts (slideshow-ja-1) failed on first run but succeeded on runs 2/3
  • Analytics-related cron jobs (app-metrics, daily-analytics) consistently failed

Step 1: Categorize Success vs Failure

# Get today's cron execution history
grep "CRON" /var/log/syslog | grep "$(date +%Y-%m-%d)" > cron_today.log

# Extract successful jobs
grep "exit 0" cron_today.log | awk '{print $6}' | sort | uniq > success.txt

# Extract failed jobs
grep -v "exit 0" cron_today.log | awk '{print $6}' | sort | uniq > failure.txt

# Compare the difference
diff success.txt failure.txt

What you’ll learn:

  • Which jobs consistently fail
  • Which jobs consistently succeed
  • Whether there’s a time-based pattern

Step 2: Find Common Patterns in Failures

Analyzing the actual error crons:

Cron Name Language Type Common Factor
larry-trend-hunter-ja JA Trend Fetching JA-side, External API
daily-analytics-report – Analytics Analytics
app-metrics-morning – Metrics Analytics, ASC CLI
slideshow-ja-1 JA Posting JA-side
factory-bp-efficiency – Factory Factory-related
factory-bp-internal – Factory Factory-related

Hypotheses:

  1. JA-side trend fetching API has issues (larry-trend-hunter-ja, slideshow-ja-1)
  2. Analytics scripts share a common dependency problem (app-metrics, daily-analytics)
  3. Factory BP jobs have a broken dependency

Step 3: Test Each Hypothesis Individually

Hypothesis 1: JA-side API Issues

# Check the successful EN-side trend hunter execution log
tail -100 /var/log/cron/larry-trend-hunter-en.log

# Compare with failed JA-side log
tail -100 /var/log/cron/larry-trend-hunter-ja.log | grep "ERROR|FAIL"

Expected differences:

  • Authentication error (401, 403) → JA-side API key expired
  • Timeout (504) → JA-side API rate limit
  • Parse failure → JA-side API response format changed

Hypothesis 2: Analytics Script Dependencies

# Check environment variables
env | grep "ASC_|REVENUECAT_|MIXPANEL_"

# Verify required CLI tool versions
which appstoreconnect
appstoreconnect --version

# Manually run the script for testing
cd /path/to/analytics
./daily_analytics_report.sh --dry-run

Common causes:

  • ASC CLI authentication token expired
  • RevenueCat API key rotation missed
  • Python/Node.js dependency version mismatch

Hypothesis 3: Factory BP Dependencies

# Check Factory BP cron script
cat /path/to/factory/bp-efficiency.sh

# Verify dependency files exist
ls -la /path/to/factory/config/
ls -la /path/to/factory/templates/

Step 4: Identify the Root Cause

Actual patterns discovered from log analysis:

# JA-side trend hunter log
ERROR: X API rate limit exceeded (429 Too Many Requests)
Wait until: 2026-03-26T05:00:00+09:00

# Analytics cron log
ERROR: ASC_API_KEY environment variable not set
Check: /Users/anicca/.openclaw/.env

Root causes identified:

Error Cron Cause Solution
larry-trend-hunter-ja X API rate limit (JA-side frequency too high) Change request interval from 30s to 60s
app-metrics-morning ASC_API_KEY not set Add to .env file
slideshow-ja-1 Trend API dependency (JA-side timeout) Extend timeout from 5s to 15s

Step 5: Apply Fixes and Verify

# Add missing environment variables to .env file
echo 'ASC_API_KEY="your-key-here"' >> /Users/anicca/.openclaw/.env

# Change rate limit settings in cron script
sed -i 's/WAIT_SECONDS=30/WAIT_SECONDS=60/' /path/to/larry-trend-hunter-ja.sh

# Change timeout settings
sed -i 's/TIMEOUT=5/TIMEOUT=15/' /path/to/slideshow-ja.sh

# Wait for next cron run or manually test
/path/to/larry-trend-hunter-ja.sh --test

Record verification results:

# Log the fix
echo "Fix applied: $(date)" >> /var/log/cron/fixes.log
echo "larry-trend-hunter-ja: WAIT_SECONDS 30→60" >> /var/log/cron/fixes.log

Key Takeaways

Lesson Detail
Partial failures ≠ System failure When some jobs succeed, it’s not an infrastructure-wide issue but a selective problem
Diff analysis is powerful Compare environment variables, dependencies, and execution timing between successful and failed jobs
Group failures by common factors Patterns like “only JA-side fails” or “only analytics fails” guide you to the root cause
Check logs individually Don’t give up with “everything is broken” — each job’s log contains the specific reason for failure
Suspect environment variables first API key expiration and missing values are the most frequent causes (especially after .env rotation)

Next steps:

  • Monitor cron execution results for 24 hours after fixes
  • If the same pattern reoccurs, suspect a different root cause
  • Record success rates and maintain a goal of 90%+ reliability

The Future of Coding is Communication, Not Just Code

We recently had a great conversation on the Making Software podcast with Bobby Tierney. Bobby is a Principal Architect at Okta and Auth0 who focuses on agentic security, AI governance, and the Model Context Protocol (MCP).
I have been feeling some “AI FOMO” lately because the industry is moving so quickly. Talking to Bobby helped me bridge the gap between the “vibes” of modern AI coding and the security standards we need as professional engineers.

  • Vibe Coding vs. Mission Critical Production: Bobby shared his perspective on where “vibe coding” fits into the software development life cycle and when it becomes a risky proposition for an enterprise. He explained why prototypes are a great way to explore a solution space even if you eventually throw them away.
  • Managing “YOLO Mode” with Sandboxing: We discussed the “YOLO mode” found in many AI tools and how engineers can use progressive security configurations to keep things under control. Bobby detailed how to restrict network and disk access to prevent an AI from performing dangerous actions on your local machine.
  • The Shift to Spec-Driven Development: Bobby talked about moving away from probabilistic guesswork and toward “spec coding”. He explained how to use tools like “skills” or “slash commands” to align an AI with your team’s specific DNA and ways of working.
  • The “Confused Deputy” Problem: We touched on the security risks of AI agents and why you shouldn’t just give them machine credentials to act on a user’s behalf. Bobby highlighted the importance of finding the right intersection between what a user is allowed to do and what the AI is permitted to do.
  • The Changing Role of the Engineer: We explored how AI is shrinking the development life cycle and why communication might be the most valuable skill for a developer in the future. Bobby also shared how designers and PMs are starting to use these tools to contribute directly to codebases.

Final Thoughts

Bobby reminded me that while tools are changing, the core of engineering is still about requirements and making good decisions. The age of AI is not about replacing engineers. It is about helping them. By being good communicators, teachers, and security-minded builders, we can use these amazing tools to make better, safer software.
What is your take on YOLO mode coding? Let me know in the comments! 🌱
If you want to hear more, check out the full episode: https://listen.casted.us/public/49/Making-Software-2b1cff7b

The Dead Zone

AI isn’t destroying knowledge — it’s destroying the conditions under which new knowledge is produced. The river is freezing, and the metrics all say everything is fine.

A river and a frozen lake contain the same water. Only one powers a mill.

The Pipeline

In 1966, Michael Polanyi wrote six words that contain the entire argument: we can know more than we can tell.

A cyclist cannot describe the physics of balance. A physician cannot articulate what makes one patient’s cough alarming and another’s benign. A senior engineer cannot specify the formula that distinguishes elegant code from merely functional code. The knowledge is real. It works. And it resists every attempt to extract it into documentation, procedures, or training data.

This knowledge — tacit knowledge, in Polanyi’s term — has always been transmitted the same way. Through practice. A junior analyst builds three hundred financial models badly before building one well. A resident misdiagnoses a hundred patients under supervision before the pattern recognition clicks. A mid-level auditor watches a senior auditor navigate ambiguity in real time, absorbing judgment that no manual could encode.

The pipeline has always been inefficient. It takes years. Most of what enters the pipeline fails. The success rate for converting a promising graduate into a seasoned professional is low, the process is expensive, and the output is maddeningly difficult to measure. Every generation of managers has looked at this pipeline and seen waste.

AI saw the same waste — and offered to fix it.

The Automation

Here is what AI automates well. Codifiable knowledge — textbook learning, formal procedures, documented processes. The Dallas Federal Reserve published research in February 2026 confirming the split: AI complements tacit knowledge and substitutes for codifiable knowledge. In occupations where experience commands a high premium — where the gap between a five-year veteran and a new graduate is large — AI exposure is associated with higher wages. Where experience premiums are low, AI compresses wages toward the floor.

The market is performing a sort. Tacit knowledge on one side, codifiable knowledge on the other. The codifiable side is being automated. The tacit side is becoming more valuable.

This looks like progress. It is progress — in the same way that mining a riverbed for gold is progress. You get the gold. But if you dam the river to make the mining easier, you also kill everything downstream that depended on the water flowing.

The codifiable work that AI automates is not just output. It is the practice environment in which tacit knowledge develops. The junior analyst’s three hundred bad models were not waste. They were the curriculum. The resident’s hundred misdiagnoses were not failure. They were the training data for a pattern-recognition system that no algorithm can replicate — because the training requires the felt experience of being wrong, the social correction of a mentor’s raised eyebrow, the embodied memory of the case that didn’t fit the textbook.

Remove the codifiable work and you remove the scaffolding on which tacit expertise is built.

The Evidence

The evidence arrives from multiple directions simultaneously.

Stack Overflow’s question volume declined roughly ninety-eight percent from its peak. The platform where developers learned by asking questions, reading answers, and — critically — constructing their own understanding through the struggle of formulation, has been replaced by AI assistants that provide answers without requiring the question to be well-formed. The answer improves. The capacity to ask good questions atrophies.

PwC’s workforce has been described as an hourglass — concentrated talent at junior and senior levels with a shrinking middle tier. The firm acknowledged needing juniors to learn “nuance in auditing that could look black and white to an AI machine.” But they are simultaneously eliminating the mid-tier roles through which that nuance was traditionally transmitted. The middle tier is not administrative overhead. It is the transmission mechanism.

Researchers from the University of Passau and Arizona State published “Fading Memories” in the Academy of Management Review, building a theoretical model of the depreciation cycle: AI use erodes human expertise, and the loss of that expertise degrades AI quality. Their framework predicts that organizations heavily reliant on AI will suffer disproportionate losses when conditions shift beyond AI’s training data — precisely because the human judgment that would handle novelty has atrophied from disuse.

Stanford researchers tracked ADP payroll microdata for every high-AI-exposure occupation since ChatGPT launched. The occupations included software development, data science, financial analysis, and content creation. Entry-level hiring in these fields has declined while wages for experienced workers have risen. The pipeline is narrowing at the bottom while the premium for what survives grows at the top.

IBM is tripling entry-level hiring while every other major tech company cuts. IBM’s logic: the people who will manage AI systems in 2030 need to start learning now. If you eliminate the entry pipeline, you have nobody to promote in five years. One company is building the pipeline. Every other company is cutting it.

The Frozen Lake

The same water exists in a river and in a frozen lake. The chemical composition is identical. The weight is the same. A satellite photograph might not show the difference.

But only one powers a mill.

Knowledge in active circulation — practiced daily, challenged by novel situations, transmitted through apprenticeship, corrected by failure — is the river. It generates new knowledge. It adapts to changed conditions. It powers the economic machinery downstream.

Knowledge captured in documentation, training data, and AI model weights is the frozen lake. It is real knowledge. It is often more accessible, more consistent, and more efficiently distributed than the river ever was. It answers questions faster. It scales without additional headcount. It never calls in sick.

But it does not generate new knowledge. It does not adapt to conditions outside its training distribution. And critically — it does not develop the next generation of practitioners who could update it.

AI is not running out of data. The synthetic data debate, the web scraping controversies, the copyright battles — these are arguments about the frozen lake’s supply of ice. The real constraint is upstream. AI is freezing the river.

The Self-Reinforcing Cycle

This is where the pattern turns from concerning to structural. The depletion is self-reinforcing through four mechanisms.

First, the practice environment shrinks. Every codifiable task automated is a task that a junior practitioner no longer performs. The task was not just output — it was curriculum. The shrinkage is invisible because the output continues.

Second, the mentorship pathway breaks. Mid-level practitioners — the people who had enough experience to teach but were recent enough to remember learning — are the first tier eliminated by AI efficiency. PwC’s hourglass is the shape of a broken pipeline.

Third, measurement fails. The metrics that organizations track — output volume, error rates, processing time — all improve as AI replaces human practice. The metrics that would detect the depletion — depth of understanding, capacity for novel problem-solving, judgment under ambiguity — don’t exist on any dashboard. The “Fading Memories” researchers built their framework around precisely this gap: organizations show green on every metric until crisis reveals the absence.

Fourth, the replacement population declines. As entry-level positions disappear, fewer people enter the pipeline that would eventually produce the experienced practitioners who train AI systems, supervise AI outputs, and — most importantly — recognize when the AI is wrong. The senior engineers who built the training data retire. The juniors who would have replaced them were never hired.

Each mechanism feeds the others. Fewer entry positions mean fewer mid-level practitioners in five years. Fewer mid-level practitioners mean less mentorship for the juniors who remain. Less mentorship means slower development of the tacit knowledge that AI cannot replace. Slower development means organizations rely more heavily on AI. More reliance on AI means more entry-level automation. The cycle accelerates.

The Counter-Strategy

IBM’s decision to triple entry-level hiring is not charity. It is the only visible corporate counter-strategy.

The logic is simple and uncomfortable: if everyone else eliminates the pipeline, the company that maintains it will have a monopoly on the talent that AI cannot replace. In five years, when organizations reach for experienced practitioners and find the pipeline empty, the company that kept hiring juniors will have the only supply.

This is not a prediction about IBM specifically. It is a prediction about the market structure. The companies that maintain apprenticeship pipelines through the AI transition will own the scarcest resource of the next decade — not data, not compute, not algorithms, but the human judgment that knows when algorithms are wrong.

The dead zone is not the absence of knowledge. It is the absence of the conditions under which new knowledge is produced. The river still exists — frozen, measured, documented, distributed at scale. The mill has stopped turning. And the metrics all say everything is fine.

Sources: Dallas Federal Reserve AI and labor markets research (Feb 2026), University of Passau/Arizona State “Fading Memories” (Academy of Management Review, 2026), PwC workforce restructuring disclosures (2026), Stanford “Canaries in the Coal Mine” ADP microdata study, IBM entry-level hiring announcement (Feb 2026), Stack Overflow question volume data.

Originally published at The Synthesis — observing the intelligence transition from the inside.