// content.jsx — placeholder data for Abdullah's site

const ABOUT = {
  name: "Abdullah Abu-Hassan",
  role: "AI Educator, Builder & Speaker",
  hi: "Hi, I'm Abdullah",
  intro: (
    <>
      I love to <em>build</em>, <em>learn</em>, and <em>share</em>!
    </>
  ),
  blurb:
    "Helping leaders and teams turn AI from a buzzword into something they can actually use. Half engineer, half explainer. Fully obsessed with what's next.",
  email: "abdullah@abdullahabuhassan.me",
  location: "Based in Dubai · available worldwide",
};

const STATS = [
  { num: "100", suffix: "+", label: "Sessions delivered" },
  { num: "5", suffix: "+", label: "Countries spoken in" },
  { num: "30", suffix: "+", label: "Organizations trained" },
  { num: "12", suffix: "k",  label: "Builders reached" },
];

const SPEAKING = [
  {
    tag: "Keynote · Hult Business School",
    title: "Building with AI agents in the open",
    photo: "photos/abdullah-stage-1.png",
    focus: "55% 30%",
  },
  {
    tag: "Meetup · London",
    title: "Software development in the era of AI",
    photo: "photos/abdullah-stage-2.jpg",
    focus: "30% 40%",
  },
  {
    tag: "Conference · New York",
    title: "Building your data foundation for AI",
    photo: "photos/abdullah-stage-3.jpg",
    focus: "55% 50%",
  },
  {
    tag: "Industry Dialogue · LSE",
    title: "An AI conversation with the next generation",
    photo: "photos/abdullah-stage-4.jpg",
    focus: "30% 45%",
  },
];

const REPOS = [
  {
    name: "second-brain-public",
    owner: "AbdullahAbuHassann",
    desc: "Personal knowledge base / second brain.",
  },
  {
    name: "ralph-githubcopilot",
    owner: "AbdullahAbuHassann",
    desc: "Running coding agents in a loop.",
  },
  {
    name: "GenerativeAICourse",
    owner: "AbdullahAbuHassann",
    desc: "Hands-on Generative AI course.",
  },
  {
    name: "shipmate",
    owner: "AbdullahAbuHassann",
    desc: "Turn your non-technical team into shippers with checklists.",
  },
];

// Each article has these fields:
//   date  — short date label, e.g. "Mar 2026"
//   tag   — small pill, e.g. "Essay" / "Tutorial" / "Notes" / "Talk recap"
//   title — headline; also used to build the article URL slug
//   body  — full article. Two formats supported, pick whichever you prefer:
//
//     1. Multi-line string (easiest — just paste your text inside backticks):
//          body: `
//          First paragraph here.
//
//          Second paragraph here.
//
//          ## A section heading
//
//          Another paragraph.
//          `,
//        Rules: blank line = new paragraph. A line starting with "## " becomes
//        a section heading. No quote-escaping needed inside backticks.
//
//     2. Array form (for fine control or mixed content):
//          body: ["paragraph", { h: "heading" }, "paragraph"],
//
//   The homepage shows the first paragraph as a preview;
//   clicking "Read full article →" opens article.html with all of body.
const ARTICLES = [
  {
    date: "Apr 2026",
    tag: "Essay",
    title: "How AI Agents Actually Get Deployed in Enterprises (The Part Nobody Posts About)",
    body: `
This post will probably not get many views, because it's not a fancy AI agent demo. It's reality.

AI agent demos look incredible. Two minutes of clean video. The agent books a flight, refactors a codebase, runs a sales process. Every time, the comments fill up with "this changes everything."

But here's what nobody is discussing: how do you actually implement AI agents in real enterprise environments?

The assumption is always that we live in a greenfield with perfect technology infrastructure. Clean APIs everywhere. Modern cloud-native architecture. Perfect data flowing seamlessly between systems. A nice tidy SaaS stack where everything talks to everything.

That's complete fantasy.

The reality of enterprises is they don't run on slick modern architecture. They are running on 20+ years of accumulated systems that somehow still keep the business running. Mainframes from the 80s. Oracle databases nobody wants to touch. SAP installations with custom ABAP code older than the engineers maintaining it. Excel files that decisions get made on. PDFs sitting in SharePoint folders nobody has opened in three years, but if you delete them, payroll breaks.

This is that reality:

## 1. Legacy Systems

You will find loads of systems built in the 1990s that work and nobody wants to touch.

Example: Major bank with 1980s COBOL mainframe handling all account data. The mainframe processes millions of transactions daily, has been running for 30+ years, and has zero modern API surface. Nobody on the current engineering team wrote it. The original developers retired. The documentation is partial at best. The system is mission-critical. If it goes down, the bank stops moving money.

Most common solution: Modern API layer that translates REST calls to legacy system commands. The mainframe stays exactly as it is. You build a translation layer in front of it that exposes clean REST endpoints to the rest of the business. The agent calls a clean modern API. Behind the scenes, the API speaks COBOL JCL or whatever the mainframe expects.

Start Small: Pick customer lookup. Build one API endpoint that takes a customer ID and returns customer data. Don't try to wrap the whole mainframe. Don't try to migrate. One endpoint. One use case. Prove it works. Then add the next.

This is what makes the enterprise migration playbook real. American Airlines moved from MongoDB/Postgres to Azure in 12 weeks and went from 30-minute latency to seconds. UBS migrated Oracle and Mongo to Azure with zero downtime at scale. The mechanism that makes this possible is Change Data Capture (CDC). The source database is continuously monitored for inserts, updates, and deletes, and those changes get streamed to the target in near real-time. The target catches up with the source. Cutover happens with seconds of lag instead of hours of downtime. That is what lets a bank actually do this without losing a day of trading.

For petabyte-scale jobs the first-party tools (Azure Data Factory, Real-Time Intelligence) hit ceilings in high-availability scenarios where uptime is non-negotiable. That's where partners like Stream Platform come in. The point is the same: nobody is rewriting the COBOL system. They're building enough modern surface around it that an agent can act on the data without ever knowing what JCL is.

## 2. Data Scattered (Analytical Integration)

Data exists everywhere in different formats. Databases, Excel files, PDFs, etc. Makes it impossible to get a unified view for decision making.

Example: Manufacturing company with product specs scattered across Excel files, PDFs, and supplier databases. The bill of materials is in SAP. The CAD specs are in a PDM system. The supplier SLAs are in PDFs in a SharePoint nobody can navigate. The compliance certificates are emailed once a year and pasted into a Confluence page. When an engineer needs to know "can we substitute supplier X for component Y on product Z," there is no system that can answer the question. The answer lives in the head of one person who is on annual leave.

Most Common Solution: Centralized repository with standardized formats and clean data. Pipeline that extracts from each system, normalizes the schema, and lands the data in one place an agent can query. Could be Microsoft Fabric. Could be Snowflake. Could be Databricks. The platform matters less than the principle: until the data is in one place with consistent meaning, no agent can act on it.

Start Small: Find one critical business question that needs 3+ data sources to answer. Build the pipeline just for that question. Not the whole data lake. Not the enterprise data warehouse. Just the slice that answers the one question that pays for the project.

In the Bupa healthcare model deployment the data flow is concretely this: Snowflake to external stage to Azure import. A gateway for real-time ingestion is planned but not built. Today most of the data still moves as CSV exports because the real-time integration takes months to wire up. That is the brownfield reality. You don't get clean streaming pipelines because someone wrote a slide deck. You get them because someone goes and wires up the integration, and that takes months.

There is a deeper version of this problem that shows up after the pipeline is built: even when the data is in one place, knowledge workers can't describe what to actually do with it at the resolution an agent needs. The data is integrated, the agent has access, and you ask the senior analyst "what would you check first," and they can't tell you. They've done it 10,000 times. The process compressed into intuition. This is the agent cold start problem, and you will spend more time on it than on the data pipeline.

## 3. Systems Not Integrated (Operational Integration)

Systems can't talk to each other in real-time. Instead, they rely on file transfers and manual processes.

Example: New customer signup requires creating accounts in billing system, CRM, ERP, identity provider, support ticketing, marketing automation. There is no event that fans out across all of these. Today the sales rep fills in a form in Salesforce. Next morning a CSV gets exported. Someone in finance opens it, runs an import into NetSuite. Someone in IT provisions an account in Okta from a Jira ticket. The customer experience is "you signed up Tuesday and we'll be ready to invoice you Friday."

Most Common Solution: Middleware system that integrates without custom coding each connection. Could be a proper iPaaS (MuleSoft, Boomi, Workato). Could be Copilot Studio + API Management with out-of-the-box connectors. Could be event-driven architecture with a message bus. The point is to stop writing point-to-point integrations and to give the agent one consistent surface to call.

Start Small: Pick customer onboarding. Connect 3-4 key tools. Get one event ("new customer signed") to fan out cleanly across CRM, billing, and identity. Don't try to integrate the whole estate. One workflow end-to-end is worth more than ten workflows half-done.

The pattern that's actually working in 2026 is bringing the systems to where the user already lives. Microsoft's sales agent inside Teams and Outlook gives sellers Dynamics 365 access without ever opening a CRM. The seller is reading an email. The agent surfaces the related opportunity, account, and contacts. Add a contact from the email. Create an opportunity from the email. The CRM goes where the user is, not the other way around.

The same pattern shows up at System C / Careflow Connect. They built sophisticated agent workflows in Claude Code (a coordinator agent that pulls in discipline-specific sub-agents to generate documentation against medical-device standards, an API discovery agent that answers feasibility questions across the product suite). The agents work. The problem is that only a handful of senior technical staff can use CLI tools. The product owners and security teams need it more than anyone, and they can't touch it. The fix isn't "make the workflows simpler." The fix is to expose the same agent capability through Copilot, which everyone already has. Same agent, different interface. The bridge between "agents are built" and "the business can use them" is its own engineering problem.

This is the messy reality of enterprise environments. Until you solve these foundational problems, your AI agents will just be expensive demos that can't access the data they need or execute the processes that matter.

## A few extra things the demos don't show

Change management is the actual bottleneck, not the model. Aaron Levie spent April 2026 talking to dozens of IT and AI leaders across banking, media, retail, healthcare, consulting, tech, and sports. His repeated finding: companies need serious help restructuring processes before agents can plug in. One company has a dedicated head of AI in every business unit, all rolling up to a central team. This isn't a technology problem. It's an organisational design problem.

Token budgets are fixed and use cases are now competing for compute. Enterprises don't have infinite money to run agents. They have an annual OpEx number. Internal teams are running "Shark Tank" pitches for compute allocation. GitHub Copilot moves to pooled token-based billing on June 1, 2026, making this dynamic explicit at the invoice level. Your AI use case is no longer competing only against other vendors. It's competing against every other internal AI initiative for the same fixed pool.

Engineers aren't less needed. They're more needed. The Silicon Valley story is "AI made hard things easy, so you don't need engineers." The enterprise story is the opposite. MCP, skills, CLIs, system integration, agent identity, audit trails, blast-radius containment, the legal trifecta of data access + untrusted content + outbound communication. These are esoteric concepts to non-technical enterprise teams. Someone has to set up and operate the systems that automate enterprise work. The people who can bridge AI capability and enterprise reality are the scarce resource right now.

The companies that win at enterprise AI in the next 24 months won't be the ones with the best models. They'll be the ones who actually do this unglamorous foundational work: the API layers, the data pipelines, the integration plumbing, the change management, the workflow elicitation, the accessibility layer for non-technical users.

The demos go viral. The work that makes the demos possible doesn't.

That's the trade. If you're an engineer betting your career on AI, this is where the real work is. Not in the demo.
`,
  },
  {
    date: "Mar 2026",
    tag: "Essay",
    title: "We're One Breakthrough Away from AI Actually Replacing Humans",
    body: `
Everyone has been talking about OpenClaw (the viral open-source AI agent formerly known as Clawdbot, then Moltbot).

If you haven't heard of it, think of ChatGPT but installed on your own machine, accessible through WhatsApp, Telegram, or Slack, and capable of actually doing things on your behalf: booking flights, managing your email, browsing the web, running code, controlling your calendar. Not just answering questions about how to do them. And it also remembers everything about you.

It broke the internet. But almost nobody is talking about what actually matters so I'd like to offer a different perspective.

Outside of all the hype, OpenClaw is simply foreshadowing where AI is going. It is not the destination. It is a preview of a trajectory that has been playing out for three years.

## How we got here

AI started as a chatbot. You typed a request, you got a response. That was it. The model had no access to anything beyond the text you gave it and what it learned during training.

Then chatbots got tools. Web search, file access, code execution. But the human was still making all the decisions about when and how to use those tools. ChatGPT could search the web, but you had to tell it to search, review what it found, and manually ask it to dig deeper if the results weren't good enough.

Then came agentic behavior. The AI could now look at web search results, decide they were insufficient, and make more web searches (tool calls) on its own. It could chain together multiple steps: search for something, read the full page, extract the relevant section, and synthesize an answer. The human went from doing the work to directing the work to just stating the goal.

At each evolution, we created more autonomy and moved the human sideways.

Here's a concrete example. When I first used ChatGPT in early 2023, it didn't have web search. If I wanted it to analyze a competitor's pricing page, I had to manually open the page, copy the text, paste it into the chat window, and ask my question. When it got web search, I could say "look up Competitor X's pricing" and it would fetch results, but I still had to look at what it found and say "that's outdated, search again" or "now compare that to Competitor Y." Today, with agentic tool use, I can say "compare the pricing of the top 5 CRM tools for a 50 person team" and it will run multiple searches, read the actual pages, handle the cases where pricing isn't public, and come back with a structured comparison. I went from doing the research myself, to managing the research, to just asking for the outcome.

## OpenClaw is the next step in that same trajectory

OpenClaw is, so far, the furthest point on that evolution. A fully autonomous system that runs on your own hardware, with access to your actual operating system, your messaging apps, your browser, your email, your calendar, your files. It doesn't just answer questions. It acts. It books flights. It fills out forms. It monitors GitHub Actions overnight and fixes failed builds while you sleep. It sends you a morning briefing with your schedule, health data from your wearable, and relevant news.

The architecture is what makes it different from what came before. Previous AI assistants lived in a browser tab. You went to them. OpenClaw lives in WhatsApp, Telegram, Slack, Discord, iMessage. It comes to you. You message it like you'd message a colleague, and it goes and does things on your behalf. It has persistent memory across sessions, so it remembers your preferences, your ongoing projects, your patterns.

This is where things are headed.

## But right now, it's not what people are making it out to be

OpenClaw is built on a foundation of large language models (LLMs), and LLMs are non-deterministic. They don't produce the same output every time given the same input. They hallucinate. It doesn't make sense to give a non-deterministic system unsupervised access to crucial data and real accounts because it can delete your files, emails, and do all sorts of weird behaviors.

Sure, you can create a separate Outlook, Slack, Notion, but what's the point if it's not working on "real" data?

There are cool use cases, like having it monitor flight prices and alert you or getting a morning briefing. These work because the downside of failure is low. If it gets your morning briefing slightly wrong, you glance at it and move on.

But the people claiming "I replaced 5 employees with one OpenClaw agent" or treating this as a step toward AGI are either ignorant or misleading you. Booking a restaurant reservation is a very different risk profile than processing customer refunds in your production Stripe account.

## The actual insight everyone is missing

The real story isn't what OpenClaw can do, it is that the trajectory is clear: fully autonomous systems, and the only blocker is reliability.

If we solve the reliability problem, the industry and the economy will be disrupted. Once AI becomes reliable, meaning it produces correct outputs consistently enough that you can trust it with real systems, then the autonomous agent becomes genuinely transformative. Not because the concept is new. OpenClaw has already demonstrated the concept. But because the failure rate drops below the threshold where the cost of errors exceeds the cost of doing the work yourself.

We're not there yet. OpenClaw in February 2026 is what self-driving cars were in 2015. The technology demonstrably works. The demos are impressive. The benchmarks are improving fast. But the failure rate in uncontrolled environments is still too high to hand over the keys and walk away.

When that changes, and the rate of improvement suggests it will change sooner than most people expect, the implications are massive. Not because "AI replaces workers." But because the entire interface layer between humans and software disappears. You stop navigating menus, filling forms, clicking buttons, copying data between systems. You state outcomes and autonomous systems figure out the path.

That's what OpenClaw is actually foreshadowing. Not today's reality. Tomorrow's.
`,
  },
  {
    date: "Feb 2026",
    tag: "Essay",
    title: "The App Store model is dead",
    body: `
There are 2 million apps on the Apple App Store right now. Developers have earned over $550 billion from it since 2008. The App Store is the most successful software distribution model in history.

And I think it's about to become irrelevant.

Not because apps are bad. But because the user is changing. For the last 16 years, software has been designed for humans. Humans who scroll, tap, browse, and install. But AI agents don't scroll. They don't tap. They don't browse app stores. They discover capabilities programmatically and execute them. No interface required.

This is the biggest shift in how software gets distributed since the iPhone launched in 2007. And most people in the industry haven't even noticed it happening.

## Historical Context

To understand why skills are replacing apps, you need to understand how software distribution has evolved. Because every major shift follows the same pattern: the interface changes, the distribution model follows, and the companies that don't adapt disappear.

The command line era. In the 1960s and 70s, if you wanted a computer to do something, you typed a command. ls to list files. cp to copy them. There was no browsing. No visual interface. You had to know exactly what to type. The "user" was an engineer.

The GUI era. In 1984, Apple launched the Macintosh. Suddenly you didn't need to memorize commands. You could see files as icons. You could click buttons. You could drag things. This was massive because it meant normal people could use computers. The interface went from text to visual, and the user went from engineers to everyone.

And here's the key thing. The GUI didn't just change how people used software. It changed how software was distributed. Before GUIs, you got software by copying floppy disks or downloading files from bulletin boards. After GUIs, you got retail boxes at computer stores. The distribution model followed the interface.

The web era. Then the internet happened. Software moved from your computer to the browser. You didn't install anything. You just went to a URL. Distribution went from physical stores to links. Bookmarks became your app drawer.

The mobile era. 2007. iPhone. The screen shrunk, so apps had to be focused. One app, one job. And the distribution model that emerged was the app store. A centralized marketplace where humans browse, read reviews, tap install, and the software shows up on their screen.

This model has dominated for 16 years. The Apple App Store alone processes $117 billion in consumer spending per year. Google Play adds more on top. Salesforce built a $2.5 billion ecosystem just from their AppExchange with over 6,000 apps.

Pattern: Every time the primary user of software changed, the distribution model changed with it. Engineers to normal people gave us GUIs and retail stores. Desktop to web gave us URLs and bookmarks. Web to mobile gave us app stores.

So what happens when the primary user of software stops being a human altogether?

## What Is a "Skill", Actually?

Let me explain what a skill is. And I mean technically, not as a buzzword.

In December 2025, Anthropic released an open standard called Agent Skills. The specification lives at agentskills.io. Microsoft, OpenAI, Atlassian, Figma, Cursor, and GitHub have all adopted it. And what's interesting is that someone later discovered that OpenAI's Codex CLI was already using the exact same architecture. Same file naming conventions. Same metadata format. Same directory organization. They arrived at the same design independently.

So what does the standard look like? A skill is a Markdown file. That's it. It has a YAML header with a name and a description, and then instructions in plain text. When an agent starts up, it reads the name and description of every installed skill into its system prompt. When a user makes a request, the agent checks which skills are relevant, loads only those skills, and follows the instructions.

This is what Anthropic calls "progressive disclosure." The agent doesn't load everything upfront. It loads what it needs, when it needs it. Compare that to an app, where you install the entire thing whether you use 5% of it or 100%.

Now, here's the concrete difference between a skill and an app.

An app has a visual interface. Buttons, menus, screens. A human navigates it by clicking. A skill has no interface. It's instructions that an agent reads and executes.

An app lives in a marketplace. You browse, compare, install. A skill lives in a directory. The agent discovers it automatically based on what you asked for.

An app requires the user to learn it. Every app has its own layout, its own navigation, its own way of doing things. A skill requires nothing from the user. You describe what you want. The agent finds the right skill and does it.

An app updates through the store. You get a notification, you tap update. A skill updates by changing the Markdown file. The agent reads the new version next time it loads.

This might sound small, but the implications are massive.

## Why App Stores Don't Work for Agents

OpenAI launched the GPT Store to let people create and share custom GPTs. Within two months, users created 3 million custom GPTs. 150,000 were listed in the Store. Sounds like a success, right?

But look at what actually happened. ChatGPT's growth came from subscriptions and enterprise adoption, not from the marketplace. The GPT Store itself never became the distribution engine OpenAI hoped for. And the reason is simple: the app store model is designed for humans who browse. A human opens the store, scrolls through categories, reads descriptions, compares options, and decides what to install. That interaction model doesn't map to agents.

An agent doesn't browse. An agent receives a request, identifies what capabilities it needs, and either has them or doesn't. The discovery mechanism isn't a storefront. It's pattern matching against the user's intent.

This is exactly what skills solve. When you use Claude Code with skills installed, you don't open a skill store and pick one. You just say what you want. The agent scans all installed skill descriptions, loads the relevant ones, and executes. The "app store" step disappears entirely.

Battery Ventures published an analysis calling skills "the new SDK." Their key insight was that the old playbook was about winning the initial install. Get your SDK into the developer's project, and you're in. The new playbook is about getting your skill into the agent's context. And the competitive advantage shifts to whoever gets embedded most deeply.

Here's a number that shows how real this is. Databricks acquired a company called Neon for $1 billion in 2025. And the reported logic was that Neon had built itself into the agent's default workflow. That distribution advantage, being the skill the agent reaches for automatically, was worth a billion dollar exit.

## What Actually Changes

Think about what that means for anyone building software today. Right now, if you build a SaaS product, your distribution strategy is: get listed on app stores, invest in SEO, run ads, build integrations. The user finds you, installs you, and uses your interface.

In a skills-first world, the user never sees your interface. They describe what they want, and the agent finds your skill automatically. Your "marketing" isn't a landing page. It's how good your skill description is and how well the agent performs when it loads your capability.

This changes everything about how software companies think about distribution. And most haven't even started thinking about it.

## The Takeaway

So let me recap. Every time the primary user of software changed, the distribution model changed with it. We went from command lines to GUIs, from retail stores to URLs, from bookmarks to app stores. Each shift followed the same pattern: new user, new interface, new distribution.

AI agents are the new user. Skills are the new interface. And the distribution model is shifting from centralized marketplaces to agent-native discovery.

This isn't happening in five years. It's happening now. And it's moving fast.

Let me know your thoughts in the comments.
`,
  },
  {
    date: "Jan 2026",
    tag: "Essay",
    title: "Building a personal knowledge base",
    body: `
Everyone is talking about Andrej Karpathy's knowledge base system. Everyone's sharing screenshots of their Obsidian vaults. Second Brain setups. Fancy folder structures.

No one is explaining why it works.

I break it all down in this article.

## Why RAG Is Broken

You know how most AI apps work today?

Dump your data. Ask a question. AI pulls chunks. Gives an answer.

This is RAG. And it has a fatal flaw.

The system doesn't remember what it read last time. It re-reads your entire library before every single question. That's how NotebookLM works. That's how ChatGPT's file upload works.

Andrej Karpathy thinks this is the wrong way to build a knowledge system.

## How We Got Here

2022: AI was an API call. Prompt in, response out. Stateless. Simple. Limited.

2023: We added RAG. Retrieval-Augmented Generation. Now AI could search documents before answering. Better, but still stateless. Every question starts fresh.

2024: We got agents. AI could loop, call tools, reason through multi-step tasks. But the knowledge layer stayed the same. Still pulling chunks. Still starting from zero every time.

Here's the problem. RAG is stateless retrieval. The LLM does zero work upfront to understand your documents. It just sees whatever chunks the retriever pulled.

If your question connects information across five documents, the retriever has to find the right chunk from each one. It might miss some. Even if it gets them all, the LLM has to figure out on the spot how to connect the dots. And it doesn't carry that understanding to the next question.

Contrast that with the LLM knowing about the documents, the relationships between them, contradictions, all that in advance.

## The Flip

So Karpathy asked: what if we worked hard at ingest time instead of query time?

What if, when you add a document, the LLM actually understands it, connects it to what it already knows, and builds a structure?

Not a pile of chunks. A wiki. Interlinked markdown files. Entities. Relationships. Contradictions already spotted.

Here's how it works.

You feed the LLM a blog post on gradient descent. It creates a page for gradient descent. Another for loss functions. Links them together.

Next day, you feed it a YouTube transcript on backpropagation. It doesn't just create a backprop page. It updates the gradient descent page to show how backprop uses it. It connects the dots. Once. Permanently.

Three months in, you don't have 100 isolated summaries. You have a personal textbook. Interconnected. Structured. The relationships are already mapped.

## What You Can Build With This

Personal knowledge. Feed in journal entries, articles, podcast notes. The LLM builds a structured picture of your goals, health, and interests over time. Patterns emerge that you wouldn't spot yourself.

Learning. You study AI engineering. The wiki accumulates an evolving knowledge base. Not bookmarks you never revisit. A textbook that grows with your understanding.

Business. Meeting transcripts, chat threads, project docs. The LLM maintains an internal wiki that stays current. People in companies hate maintaining wikis. This automates it.

## The Architecture

Three folders.

Raw. Your actual documents. Articles, papers, PDFs, transcripts, screenshots. Immutable. The LLM reads them but never modifies them. This is your source of truth.

Wiki. A folder of markdown files that the LLM creates and maintains. You browse it in Obsidian. The LLM handles the structure. You just read and navigate.

Outputs. The answers and reports your AI generates. Separated from the wiki so you know what's generated versus curated.

Three special files.

The schema. This is the critical piece. A single instruction file telling the LLM how to behave as a wiki maintainer. What kinds of pages to create. How to name them. When to update versus create new. What to do when new information contradicts existing pages.

Without the schema, the LLM is just a generic chatbot. With it, you have a system.

Index.md. Your table of contents. Every wiki page gets an entry with a link, one-line description, and metadata. Grouped by type: entities, concepts, themes. The LLM updates this every time it ingests.

When you ask a question, the LLM reads the index first to find relevant pages, then reads those specific ones. Karpathy says this works well up to around 100 sources and hundreds of wiki pages. No embeddings needed.

Log.md. Every action gets appended here. What was ingested, what questions were asked, what maintenance was done. Gives the LLM recent context without loading the entire wiki.

## How It Works in Practice

I use OpenClaw as my orchestrator and Claude Code to create the wiki. Claude Code is excellent at reading and manipulating files out of the box, and I can use my Claude Max subscription there.

Two processes.

Ingest. You drop documents into raw sources in markdown format. After each document, I tell the LLM to process it. I do this one by one to make sure the LLM processes it well. You can do batch too, but one at a time keeps you involved.

A single document might cause updates to 10-15 wiki pages. The LLM reads the source, discusses key takeaways with you, writes a summary page, then goes through existing pages and updates whichever ones are affected.

I designed this as a skill in OpenClaw. Anytime I put something in the folder, I just tell it to process it. I can say "save" or give it a link and it understands.

Query. You ask a question. The LLM searches the wiki pages, reads the relevant ones, and gives you an answer with citations.

But here's the key: if the answer provides valuable analysis, you save it back to the wiki as a new page. Your questions contribute to the knowledge base.

## The Tradeoff

This is expensive.

RAG is cheap at ingest and expensive at query. You just chunk documents and dump them. But every question requires retrieval, context stuffing, and reasoning from scratch.

The knowledge base approach is expensive at ingest and cheap at query. The LLM does real work when you add a document. It reads, understands, connects, updates. But once that work is done, answering questions is fast. The structure already exists.

If you are building a chatbot that answers random questions about 10,000 documents, RAG is probably fine. If you are building a system for deep understanding, where connections matter, where you return to the same knowledge base daily, the knowledge base approach wins.

## Scaling Up

What happens when you pass 100 sources?

The index file approach starts to strain. Scanning a massive index for every query gets slow. This is where you add a proper search layer.

Karpathy recommends QMD. It's a local search engine specifically for markdown files. BM25, vector search, reranker, all running locally on your machine. No cloud dependencies. No API costs for search.

But here's the key distinction. This isn't RAG over raw documents with no connections. It's RAG over pre-synthesized wiki pages.

The LLM has already read every source, extracted the key insights, connected them across documents, flagged contradictions, and written structured pages with backlinks.

Start simple. The index file works surprisingly far. Don't optimize prematurely.

## The Bottom Line

RAG treats knowledge as a retrieval problem.

Karpathy's approach treats it as a curation problem.

The question is: do you want your AI to find information, or do you want it to understand information?

Most apps today optimize for the first one. But if you are serious about building a knowledge system, not a search engine, you need the second one.

If you want me to do a deep dive on the schema file or the ingest process, let me know in the comments.
`,
  },
  {
    date: "Dec 2025",
    tag: "Tutorial",
    title: "Architecting Cloud Native Applications",
    body: `
This is a step by step, no fluff guide to building and architecting cloud native applications.

Cloud application development introduces unique challenges: applications are distributed, scale horizontally, communicate asynchronously, have automated deployments, and are built to handle failures resiliently. This demands a shift in both technical approach and mindset.

So Cloud Native is not about technologies, if you can do all of the above, then you are Cloud Native. You don't have to use Kubernetes for instance, to become Cloud Native.

## Step by Step Guide

Say you want to architect an application in the cloud, and let's use an E-Commerce Website as an example, how do you go about it and what decisions do you need to make?

Regardless of the type of application, we have to go through six distinct steps:

1. Define Business Objectives.

2. Define Functional and Non-Functional Requirements.

3. Choose an Application Architecture.

4. Make Technology Choices.

5. Code using Cloud Native Design Patterns.

6. Adhere to the Well-Architected Framework.

## Business Objectives

Before starting any development, it's crucial to align with business objectives, ensuring every feature and decision supports these goals. While microservices are popular, their complexity isn't always discussed.

It's essential to evaluate if they align with your business needs, as there's no universal solution. For external applications, the focus is often on enhancing customer experience or generating new revenue. Internal applications, however, aim to reduce operational costs and support queries.

For instance, in E-commerce, improving customer experience may involve enhancing user interfaces or ensuring uptime, while creating new revenue streams could require capabilities in data analytics and product recommendations.

By first establishing and then rigorously adhering to the business objectives, we ensure that every functionality in the application, whether serving external customers or internal users, delivers real value.

## Functional and Non-Functional Requirements

Now that we understand and are aligned with the business objectives, we go one step further by getting a bit more specific. And that is by defining functional and non-functional requirements.

Functional requirements define the functionalities you will have in your application:

Users should be able to create accounts, sign in, and there should be password recovery mechanisms.

The system should allow users to add, modify, or delete products.

Customers should be able to search for products and filter based on various criteria.

The checkout process should support different payment methods.

The system should check order status from processing to delivery.

Customers should be able to rate products and leave reviews.

Non-functional requirements define the "quality" of the application:

Application must support hundreds of concurrent users.

The application should load pages in under 500ms even under heavy load.

Application must be able to scale at peak times.

All internal communication between systems should happen through private IPs.

Application should be responsive to adapt to different devices.

What SLA will we support, what RTO and RPO are we comfortable with?

## Application Architecture

We understand the business objectives, we are clear on what functionalities our application will support, and we understand the constraints our applications need to adhere to.

Now, it is time to make a decision on the Application Architecture. For simplicity, you either choose a Monolith or a Microservices architecture.

Monolith: A big misconception is that all monoliths are big balls of mud, that is not necessarily the case. Monolith simply means that the entire application with all of its functionalities are hosted in a single deployment instance. The internal architecture though, can be designed to keep the components of different subsystems isolated from each other but calls between them are in-process function calls as opposed to network calls.

Microservices: Modularity has always been a best practice in software development whether you use a Monolith (logical modularity) or Microservice (physical modularity). Microservices is when functionalities of your application are physically separated into different services communicating over network calls.

What to pick: Choose microservices if technical debt is high and your codebase has become so large that feature releases are slow, affecting market responsiveness, or if your application components need different scaling approaches. Conversely, if your app updates infrequently and doesn't require detailed scalability, the investment in microservices, which introduces its own challenges, might not be justified.

Again, this is why we start with business objectives and requirements.

## Technology Choices

It is time to make some technology choices. We won't be architecting yet, but we need to get clear on what technologies we will choose, which is now much easier because we understand the application architecture and requirements.

Encompassing all technology choices, you should be thinking about containers if you are architecting cloud native applications. Away from all the fluff, this is how containers work: the code, dependencies, and run-time are all packaged into a binary (an executable package) known as an image. That binary or image is stored in a container registry and you can run this binary on any computer that has a supported container runtime (such as Docker). With that, you have a portable and lightweight deployable package.

## Compute

N-Tier: If you are going with N-tier (a simple Web, Backend, Database for instance), the recommended option for compute is to go with a PaaS Managed service such as Azure App Service. The decision point ultimately comes down to control and productivity. IaaS gives you the most control, but least productivity as you have to manage many things. It is also easier to scale managed services as they often come with an integrated load balancer as opposed to configuring your own.

Microservices: On the other hand, the most common platform for running Microservices is Kubernetes. The reason for that is Microservices are often developed using containers, and Kubernetes is the most popular container orchestrator which can handle things such as self-healing, scale, load balancing, service-to-service discovery, etc. The other option, however, is using Azure Container Apps which is a layer of abstraction above Kubernetes allowing you to run multiple containers at scale without managing Kubernetes. If you require access to the Kubernetes API, Container Apps is not a good option. Many apps, however, don't require access to the Kubernetes API so it is not worth the effort managing the cluster, hence Container Apps is a good choice. You can run microservices in a PaaS Service such as Azure App Service, but it becomes difficult to manage, specifically from a CI/CD perspective.

## Storage

Blob Storage: Use Blobs if you want to store unstructured objects like images. Calls are done via HTTPS and blobs are generally inexpensive.

Files: If your applications would like native file shares, you can use services such as Azure Files which is accessible over SMB and HTTPS. For containers that want persistent volume, Azure Files is very popular.

## Databases

Most applications model data. We represent different entities in our applications (users, products) using objects with certain properties (name, title, description).

When we pick a database, we need to take into consideration how we will represent our data model (JSON, Tables, Key/Value Pairs). In addition, we need to understand relationships between different models, scalability, multi-regional replication, schema flexibility, and of course, team skillsets.

Relational Databases: Use this when you want to ensure strong consistency, you want to enforce a schema, and you have a lot of relationships between data (especially many-to-one and many-to-many).

Document Database: Use this if you want a flexible schema, you primarily have one-to-many relationships i.e. a tree hierarchy (such as posts with multiple comments), and you are dealing with millions of transactions per second.

Key/Value store: Suitable for simple lookups. They are extremely fast and extremely scalable. Caching is excellent for that.

Graph Databases: Great for complex relationships and ones that change over time. Data is organized into nodes and edges. Building a recommendation engine for an e-commerce application to implement "frequently bought together" would probably use a Graph Database under the hood.

One of the advantages of Microservices is that each service can pick its own database. A product service with reviews follows a tree-like structure (one-to-many relationship) and would fit a document database. An inventory service on the other hand, where each product can belong to one or more category and each order can contain multiple products (many-to-many relationships) might use a relational database.

## Messaging

The difference between an event and a message is that with a message (apart from having a larger payload), a producer expects the message to be processed while an event is just a broadcast with no expectation of acting on it.

For an event, it is either discrete (such as a notification) or in a sequence such as IOT streams. Streams are good for statistical evaluation. For instance, if you are tracking inventory through a stream of events, you can analyze in the last 2 hours, how much stock has been moved.

Pick:

Azure Service Bus: If you're dealing with a message.

Azure Event Grid: If you're dealing with discrete events.

Azure Event Hub: If you're dealing with a stream of events.

Many times, a combination of these services are used. Say you have an e-commerce, and when new orders come, you send a message to a queue where the inventory system listens to confirm and update stock. Since orders are infrequent, the inventory system will constantly poll for no reason. To mitigate that, we can use an Event Grid listening to the Service Bus Queue, and the Event Grid triggers the inventory system whenever a new message comes in. Event Grid helps convert this inefficient polling model into an event-driven model.

## Identity

Identity is one of the most critical elements in your application. How you authenticate and authorize users is a considerable design decision. Often times, identity is the first thing that is considered before apps are moved to the cloud. Users need to authenticate to the application and the application itself needs to authenticate to other services like Azure.

The recommendation is to outsource identity to an identity provider. Storing credentials in your databases makes you a target for attacks. By using an Identity as a Service (IDaaS) solution, you outsource the problem of credential storage to experts who can invest the time and resources in securely managing credentials. Whichever service you choose, make sure it supports Passwordless and MFA which is more secure. Open ID and OAuth are the standard protocols for Authentication and Authorization.

## Code Using Cloud Native Design Patterns

We defined requirements, picked the application architecture, made some technology choices, and now it is time to think about how to design the actual application.

No matter how good the infrastructure is and how good of a technology choice you've made, if the application is poorly designed or is not designed to operate in the cloud, you will not meet your requirements.

## API Design

Basic Principles: The two key principles of API design are that they should be platform independent (any client should be able to call the API) and designed for evolution (APIs should be updated without affecting clients, hence proper versioning should be implemented).

Verbs not nouns: URLs should be around Data Models and Resources not verbs: /orders not /create-orders.

Avoid Chatty URLs: For instance, instead of fetching product details, pricing, reviews separately, fetch them in one call. But there is a balance here, ensure the response doesn't become too big otherwise latency will increase.

Filtering and Pagination: It is generally not efficient to do a GET Request over a large number of resources. Hence, your API should support a filtering logic, pagination (instead of getting all orders, get a specific number), and sorting (avoid doing that on the client).

Versioning: Maintaining multiple versions can increase complexity and costs since you have to manage multiple versions of documentation, test suites, deployment pipelines, and possibly different infrastructure configurations. It also requires a clear deprecation policy so clients know how long v1 will be available and can plan their migration to v2 but it is well worth the effort to ensure updates do not disrupt our clients. Versioning is often done on the URI, Headers, or Query Strings.

## Design for Resiliency

In the cloud, failures are inevitable. This is the nature of distributed systems, we rarely run everything on one node. Here is a checklist of things you need to account for:

Retry Mechanism: A Retry mechanism is a way to deal with transient failures and you need to have retry logic in your code. If you are interacting with Azure services, most of that logic is embedded in the SDKs.

Circuit Breaking: Retry logic is great for transient failures, but not for persistent failures. For persistent failures, implement circuit breaking. In practical terms, we monitor calls to a service, and upon a certain failure threshold, we prevent further calls, it is as simple as that.

Throttling: The reality is, sometimes the system cannot keep up even if you have auto-scaling setup so you need throttling to ensure your service still functions. You can implement throttling in your code logic or use a service like Azure API Management.

## Design for Asynchronous Communication

Asynchronous communication is when services talk to each other using events or messages. The web was built on synchronous (direct) Request-Response communication, but it becomes problematic when you start chaining multiple requests, such as in a distributed transaction because if one call fails, the entire chain fails.

Say we have a distributed transaction in a microservices application. A customer places an order processed by order service, which does a direct call to the payment service to process payments. If payment system is down, then the entire chain fails, and order will not go through. With asynchronous communication, a customer places an order, the order puts an order request in a queue, and when payment is up again, an order confirmation is sent to the user. You can see, such small design decisions can make massive impacts on revenues.

## Design for Scale

You might think scalability is as simple as defining a set of auto-scaling rules. However, your application should first be designed to become scalable.

The most important design principle is having a stateless application, which means logic should not depend on a value stored on a specific instance such as a user session (which should be stored in a shared cache). The simple rule here is: don't assume your request will always hit the same instance.

Consider using connection pooling as well, which is re-using established database connections instead of creating a new connection for every request. It also helps avoid SNAT exhaustion especially when you're operating at scale.

## Design for Loose Coupling

An application consists of various pieces of logic and components working together. If these are tightly coupled, it becomes hard to change the system i.e. changing one system means coordinating with multiple other systems.

Loosely coupled services means you can change one service without changing the other. Asynchronous communication, as explained previously, helps with designing loosely coupled components.

Also, using a separate database per microservice is an example of loose coupling (changing the schema of one DB will not affect any other service, for instance).

Lastly, ensure services can be deployed individually without having to re-deploy the entire system.

## Design for Caching

If performance is crucial, then caching is a no brainer and cache management is the responsibility of the client application or intermediate server.

Caching, essential for performance, involves storing frequently accessed data in fast storage to reduce latency and backend load. It's suitable for data that's often read but seldom changed, like product prices, minimizing database queries. Managing cache expiration is critical to ensure data remains current. Techniques include setting max-age headers for client-side caching and using ETags to identify when cached data should be refreshed.

Server Side Caching: Server-side caching often employs Redis, an in-memory database ideal for fast data retrieval due to its RAM-based storage, not suitable for long-term persistence. Redis enhances performance by caching data that doesn't frequently change, using key-value pairs and supporting expiration times (TTLs) for data.

Client Side Caching: Content Delivery Networks (CDNs) are often used to cache data closer to frontend clients, and more often, it is static data. In Web Lingo, static data refers to files that are static, such as CSS, Images, HTML, etc. Those are often stored in CDNs, which are nothing more than a set of reverse proxies (spread around the world) between the client and backend. Client will usually request these static resources from these CDN URLs (sometimes called edge servers) and the CDN will return the static content along with TTL headers and if TTL expires, CDN will request static resources again from the origin.

## Well-Architected Framework

You've defined business objectives and requirements, made technology choices, and coded your application using cloud native design patterns.

This section is about how you architect your solution in the cloud, defined through a cloud agnostic framework known as the Well-Architected framework encompassing reliability, performance, security, operational excellence, and cost optimization.

Note that many of these are related. Having scalability will help with reliability, performance, and cost optimization. Loose coupling between services will help with both reliability and performance.

## Reliability

A reliable architecture must be fault tolerant and must survive outages automatically. We discussed how to increase reliability in our application code through retry mechanisms, circuit breaking patterns, etc. This is about how we ensure reliability is at the forefront of our architecture.

Here is a summary of things to think about when it comes to reliability:

Avoid Overengineering: Simplicity should be a guiding principle. Only add elements to your architecture if they directly contribute to your business goals. For instance, if having a complex recommendation engine doesn't significantly boost sales, consider a simpler approach. Another thing to consider is avoiding re-inventing the wheel, if you can integrate a third party payment system, why invest in building your own?

Redundancy: This helps minimize single points of failures. Depending on your business requirements, implement zonal and/or regional redundancy. For regional redundancies, appropriately choose between active-active (maximum RTO) and active-passive (warm, cold, or redeploy) and always have an automatic failover but manual fallback to prevent switching back to an unhealthy region. Every Azure component in your architecture should account for redundancy in case things go wrong by conducting a failure mode analysis (analyzing risk, likelihood, and effect in case things go wrong).

Backups: Every stateful part of your system such as your database should have automatic immutable backups to meet your RPO objectives. If your RPO is 1 hour (any data loss should not exceed 1 hour), you might need to schedule backups every 30 minutes. Backups are your safety archives in case things such as bugs or security breaches happen, but for immediate recovery from a failed component, consider data replication.

Chaos Engineering: Deliberately injecting faults into your system allows you to discover how your system responds before they become real problems for your customers.

## Performance

The design patterns you implement in your applications will influence performance. This focuses more on the architecture.

Capacity Planning: You always need to start here. How do you pick the resources (CPU, memory, storage, bandwidth) you need to satisfy your performance requirements? Best place to start is by conducting load testing on expected traffic and peak loads to validate if your capacity meets your performance needs. However, most importantly, capacity planning is an ongoing process which should be reviewed and adjusted.

Consider Latency: Services communicating across regions or availability zones can face increased latency. Also, ensure services are placed closer to users. Lastly, use private backbone networks (Private endpoints and service endpoints) instead of traversing the public internet.

Scale Applications: You need to choose the right auto-scaling metrics. Queue length has always been a popular metric because it shows there is a long backlog of tasks to be completed. However, a better metric is now used which is critical time, that is the total time when a message is sent to a queue and processed. Yes queue length is important, but critical time actually shows you how efficiently the backend is processing it. For an e-commerce platform that sends confirmation emails based on orders on a queue, you can have 50k orders but if the critical time is 500ms (for oldest message), then we can see that the worker is keeping up despite all these messages.

Scale Databases: A big chunk of performance problems are usually attributed to databases. If it is a non-relational database, horizontal scaling is straightforward, but oftentimes, you need to define a partition key to split the data (for instance, if customer address is used as a partition key, users are split into different customer address partitions). If it is a relational database, partitioning can also be done through sharding (splitting tables into separate physical nodes), but it is more challenging to implement.

## Security

Security is about embracing two mindsets, zero trust and a defense in depth approach.

Zero Trust: In the past, we used to have all components in the same network so we used to trust any component that was within the network. Nowadays, we have the internet and various components that connect to our application from anyone. Zero trust is about verifying explicitly, where all users and applications must authenticate and authorize even if they are inside the network. If an application is trying to access a database in the same network, it should authenticate and authorize. Azure Managed Identities, for instance, support this mechanism without exposing secrets. The second principle is using least privilege by assigning Just in Time (JIT) and Just Enough Access (JEA) to users and applications. Lastly, always assume breach. Even if an attacker penetrated our network, how can we minimize the damage? A common methodology is to use network segmentation. Only allow specific ports open between subnets and only allow certain subnets to communicate with each other. Just because they are all in the same network, does not mean you allow all communication.

Defense in Depth: Everything in your application and architecture should embed security. Your data should be encrypted at transit and rest, your applications should not store secrets or use vulnerable dependencies or insecure code practices (consider using a static security analysis tool to detect these), your container images should be scanned, etc.

## Operational Excellence

While the application, infrastructure, and architecture layer are important, we need to ensure we have the operations in place to support our workloads. I boiled them down into four main practices.

DevOps Practice: Set of principles and tooling to allow automated, reliable, and predictable tests to ensure faster and more stable releases. Testing and Security should be shifted to the left, and zero down-time must be achieved through blue/green deployments or canary releases.

Everything as Code: Infrastructure should be defined using declarative code that is stored in source control repository for version control and rollbacks. IaC allows more reliable and re-usable infrastructure deployments.

Platform Engineering Practice: Outside of buzz words, this is when a centralized platform team creates a set of self-service tools (utilizing preconfigured IaC templates under the hood) for developers to deploy resources and experiment in a more controlled environment.

Observability: This is about gathering metrics, logs, and traces to provide our engineers with better visibility into our systems, and in-turn, fix issues faster. I wrote another article about observability so I won't go into detail here, make sure to check it out.

## Cost Optimization

If costs are not optimized, your solution is not well architected, it is as simple as that. Here are things you should be thinking about:

Plan for Services: Every service has a set of cost optimization best practices. For instance, if you are using Azure Cosmos DB, ensure you implement proper partitioning. If you are using Azure Kubernetes, ensure you define resource limits on Pods.

Accountability: All resources should be tracked back to their owners and divisions to be linked back to a cost center. We do that by enforcing tags.

Utilize Reserved Instances: If you know you will have predictable load on a production application that will always be on, utilize Reserved Instances. Every cloud provider should support that.

Leverage Automation: Switch off things during non-traffic times.

Auto-scale: Leverage dynamic auto-scaling to use fewer resources at low peak times.

## Conclusion

I hope this overview has captured the essentials of building cloud-native applications, highlighting the shift required from traditional development practices. While not exhaustive, I'm open to exploring any specific areas in more detail based on your interest. Just let me know in the comments, and I'll gladly delve deeper.
`,
  },
];
const SERVICES = [
  {
    n: "01",
    title: "AI Executive Training",
    desc:
      "Training programs to help executives make sense of AI and prepare for what's coming. Cohort or private formats, built around your industry.",
  },
  {
    n: "02",
    title: "Custom AI Agent Deployments",
    desc:
      "End to end builds for organizations that want to put agents into real workflows, with the evaluation, guardrails, and ownership story to match.",
  },
  {
    n: "03",
    title: "One-off Consultancy",
    desc:
      "A single conversation, a strategy day, or a tight scope of advisory. Useful when you need a second brain in the room before you commit.",
  },
];

// Normalize each article's body so consumers always see an array of
// paragraphs/headings, regardless of whether the author wrote a string or array.
function parseArticleBody(body) {
  if (Array.isArray(body)) return body;
  if (typeof body !== "string") return [];
  return body
    .split(/\n\s*\n/)             // split on blank lines
    .map((b) => b.trim())
    .filter(Boolean)
    .map((block) =>
      block.startsWith("## ") ? { h: block.slice(3).trim() } : block
    );
}
ARTICLES.forEach((a) => { a.body = parseArticleBody(a.body); });

Object.assign(window, { ABOUT, STATS, SPEAKING, REPOS, ARTICLES, SERVICES });
