# Jadan Jones Blog Corpus

> Markdown mirrors of Jadan Jones's blog posts for AI assistants, search tools, and research agents.

Canonical site: https://www.jadanjones.com
Curated index: https://www.jadanjones.com/llms.txt

## Articles

---
title: Fable 5 Cannot Save Your Bad Harness
excerpt: A smarter model helps, but weak prompts, context, tools, workflows, and
  verification loops still turn good intelligence into bad output.
date: 2026-06-10
author: Jadan Jones
canonicalUrl: https://www.jadanjones.com/blog/fable-5-cannot-save-a-bad-harness
sourceUrl: https://www.jadanjones.com/blog/fable-5-cannot-save-a-bad-harness
markdownUrl: https://www.jadanjones.com/blog/fable-5-cannot-save-a-bad-harness.md
categories:
  - Artificial Intelligence
  - Software Development
  - AI Agents
keywords: Claude Fable 5, AI harness engineering, prompt engineering, context
  engineering, agentic workflows, AI workflows, Claude Code
---

# Fable 5 Cannot Save Your Bad Harness

Claude Fable 5 is here, and the headline is easy.

Anthropic says it is their most capable widely released model. Better at long-horizon work. Better at agentic tasks. Bigger context. Bigger outputs. Adaptive thinking on by default.

Cool.

I want better models. I will use better models.

But Fable 5 is not going to save a bad setup.

A smarter model is still a model.

If you drop it into a weak harness, you get expensive chaos with better grammar.

If your workflow is shit, Fable 5 will not make it mature. It will just produce smarter-looking shit.

That is the part people keep wanting model releases to solve for them.

They will upgrade the model, keep the same vague prompt, same stale context, same missing checks, same messy tool permissions, same "looks good ship it" review process, and then act surprised when the output is still unreliable.

That is not a model problem.

That is a harness problem.

## Fable 5 Is Probably Very Good

This is not an anti-model take.

Fable 5 looks strong on paper. Anthropic says it is built for demanding reasoning and long-horizon agentic work. The API docs list a 1M token context window by default, up to 128k output tokens, and always-on adaptive thinking. The launch also sits next to Mythos 5, with Fable 5 as the widely released version and Mythos 5 limited through Project Glasswing.

There is also the Opus 4.8 wrinkle. For some sensitive cybersecurity, biology, and chemistry cases, Fable 5 can route down to Opus 4.8 through safeguards. That does not mean Fable 5 is secretly only Opus 4.8 with loops. The public docs describe it as a new Mythos-class release.

The useful point is simpler:

"Most capable model" does not mean "your system is capable."

A model release can raise the ceiling. It cannot magically write your standards, clean your inputs, protect your data boundaries, or decide what "done" means in your product.

That work still belongs to you.

Annoying, I know.

## Your Harness Is The Thing

When I say harness, I mean everything around the model.

The prompt is part of it.

The context is part of it.

The file tree, tools, permissions, examples, memory, workflows, loops, tests, evals, review gates, and stop conditions are all part of it.

That is the system the model actually lives inside.

This is why the same model can feel brilliant in one environment and useless in another. In one setup, it has the right files, clear constraints, a small plan, relevant tools, a verifier, and permission to keep working until the check passes. In another setup, it gets a paragraph of vibes and a prayer.

Same model class.

Different harness.

Different outcome.

Prompt engineering was the first layer. You learned how to ask better.

Context engineering came next. You learned that the model cannot reason over facts it never sees.

Harness engineering is the layer that matters when the model starts doing work. You are no longer asking for one answer. You are giving the model a workspace and letting it act.

At that point, the question is not "how smart is the model?"

The question is "what system did you put the model inside?"

## Loops Are Part Of The Harness

Loops matter. A lot.

A basic working loop looks like this:

```text
Read the task.
Load the relevant context.
Make a small plan.
Take one action.
Inspect the result.
Update the plan.
Run the check.
If the check fails, diagnose and try again.
If the check passes, summarize the evidence.
Stop when the acceptance criteria are met.
```

That loop can make a model feel much less needy.

But a loop is not magic either.

If the loop reads bad context, it loops on bad context.

If the verifier is weak, it verifies nonsense.

If the stop condition is vague, the model stops when the answer feels complete, which is exactly how you get a confident summary of unfinished work.

Loops are powerful because they let the model continue. That also means a bad loop gives the model more chances to compound the mistake.

This is why I do not like treating "autonomy" as a model-picker setting.

Autonomy is model plus harness.

The model supplies reasoning.

The harness supplies direction, boundaries, memory, tools, proof, and brakes.

Skip the harness work and your autonomous agent becomes a very polite random walk.

## What A Bad Harness Does

A bad harness does not always fail loudly.

That is what makes it dangerous.

A vague task makes the model guess your intent.

Stale context makes it optimize the wrong version of the system.

Missing examples make it invent style.

Loose tool permissions make it either freeze or overreach.

No acceptance criteria make it declare victory at the first plausible stopping point.

No tests make it review its own story instead of the behavior.

No memory discipline makes it rediscover the same facts every session.

No fallback path makes one failed tool call turn into a fake conclusion.

Put Fable 5 inside that and yes, it will probably do better than a weaker model.

But the shape of the failure stays the same.

It will still be guessing.

It will just guess with better reasoning.

## What To Build Instead

Before you upgrade the model, upgrade the workbench.

For any serious AI workflow, write down the boring pieces:

```text
Task: what is the model trying to finish?
Inputs: what files, docs, examples, and data matter?
Rules: what constraints should never be violated?
Tools: what can it call, and what needs approval?
Workflow: what steps should it run before editing or answering?
Verifier: what proves the work is correct?
Budget: how long can it keep trying?
Fallback: what should it do when it gets blocked?
Memory: what should survive into the next run?
Stop: when is the task done?
```

A useful content harness might have sources, examples, voice rules, draft stages, fact checks, AI-tell audits, and publish criteria.

A useful coding harness might have repo instructions, scoped file rules, test commands, fixtures, review rubrics, security boundaries, and a verifier that reads the diff instead of trusting the summary.

A useful research harness might have source rules, contradiction checks, confidence labels, citation requirements, and a rule that search-result snippets do not count as evidence.

None of this is glamorous.

It is also the difference between "the model wrote something impressive" and "the system did the work."

The harness is where your standards live.

The workflow is where your judgment gets repeated.

The loop is where the model learns to keep moving without you typing "continue" like a tired supervisor.

## The Actual Fable 5 Takeaway

Use Fable 5 if it helps.

Use the best model you can justify for the job.

But do not outsource discipline to the model release cycle.

A better model raises the ceiling.

A better harness raises the floor.

Most teams need the floor more than they need another ceiling.

Because the embarrassing failures rarely happen because the model was one benchmark point too low. They happen because nobody wrote down the workflow, nobody gave the model the right context, nobody defined done, and nobody checked the output against reality.

Fable 5 can make a strong harness better.

It cannot save a sloppy one.

If your harness is shit, fix the harness.

Then the model upgrade actually has somewhere to land.

Sources: [Anthropic's Claude Fable 5 and Claude Mythos 5 announcement](https://www.anthropic.com/news/claude-fable-5-mythos-5), [Claude API docs for Fable 5 and Mythos 5](https://platform.claude.com/docs/en/about-claude/models/introducing-claude-fable-5-and-claude-mythos-5), [Prompting Claude Fable 5](https://platform.claude.com/docs/en/build-with-claude/prompt-engineering/prompting-claude-fable-5), [Claude models overview](https://docs.anthropic.com/en/docs/about-claude/models/overview), and [Anthropic's Opus 4.8 announcement](https://www.anthropic.com/news/claude-opus-4-8).

---

---
title: I Gave My Agent A Real Browser And It Got Useful
excerpt: Browser automation became useful once I gave the agent a
  permission-aware routine for using a real browser without relearning the dance
  every time.
date: 2026-06-04
author: Jadan Jones
canonicalUrl: https://www.jadanjones.com/blog/real-browser-agent-workflow
sourceUrl: https://www.jadanjones.com/blog/real-browser-agent-workflow
markdownUrl: https://www.jadanjones.com/blog/real-browser-agent-workflow.md
categories:
  - Artificial Intelligence
  - Software Development
  - AI Agents
keywords: agent-browser, browser automation CLI, AI agents, Codex skills,
  CloakBrowser, agent-web, browser workbench skill
---

# I Gave My Agent A Real Browser And It Got Useful

Some workflows turn into soup the second they touch a web UI.

The agent can edit files, run tests, read docs, write scripts, summarize research, and refactor a service without breathing hard. Then one task needs a browser and suddenly I am a very expensive clipboard with a keyboard, moving text from the terminal into a page and bringing page state back like this is a noble profession.

That was the clue.

The setup that changed it for me is boring in the best way: CloakBrowser for an isolated real browser, `agent-browser` for driving that browser from the terminal, a small `agent-web` wrapper around my local config, and a skill that teaches the agent exactly how to use the whole thing.

That skill did more work than I expected. The boring breakthrough was the repeatable browser workflow.

## The Combination

The stack looks roughly like this:

```text
CloakBrowser
  -> isolated Chromium browser

agent-browser
  -> CLI for browser automation

agent-web
  -> local wrapper that points agent-browser at my browser config

browser skill
  -> instructions for when and how the agent should use it
```

`agent-browser` turns the browser into a terminal tool. It can open pages, inspect interactive elements, click referenced elements, fill fields, press keys, scroll, and take screenshots. The project and local help text both push the same loop: open a page, snapshot, act on element refs, then snapshot again.

CloakBrowser gives the workflow a real browser surface. Pages behave like pages. The browser has windows. Screenshots exist. Clicks and keyboard input happen in a place a human can see, which is helpful when the agent confidently says it clicked something and the page looks back at you with a blank little face.

The wrapper keeps config trivia out of the prompt. The agent just calls the wrapper.

## Why The Skill Did The Actual Work

Without a skill, an agent has to discover the browser workflow every time.

It might guess the command.

It might use the wrong profile.

It might click from stale page state.

It might forget to re-snapshot after a navigation.

It might ask me to paste secrets into chat, which is where I start making a face at the screen. Absolutely not, my brother in OAuth.

A skill fixes that because it gives the agent a local operating procedure:

```bash
agent-web open https://example.com
agent-web snapshot -i
agent-web click @e12
agent-web fill @e7 "draft text"
agent-web press Enter
agent-web screenshot --annotate
```

The command list is useful, but the habit is the prize. I want the agent to remember a small ritual:

- Open the page.
- Snapshot the page and get interactive refs.
- Act on refs instead of inventing selectors from a dream.
- Re-snapshot after the page changes.
- Use screenshots when the page is visual or ambiguous.
- Pause before any externally visible action unless the user already made the target and content unambiguous.

That gives the agent enough structure to be useful without letting it cosplay as a tiny Selenium tutorial with feelings.

## The Full Autonomy Trap

People will hear "agent with browser" and immediately imagine a tireless intern clicking around the internet while they sleep.

Please lower the temperature.

I still want a headed browser for sensitive steps. If a page needs a password, payment detail, MFA code, recovery code, or anything else that should never touch chat, the user handles it directly in the browser. The agent can wait. Waiting is a feature.

I also want bright lines around risky browser state. Cookies, storage, network interception, raw auth state, downloads, uploads, and destructive actions should not be casual commands. A browser automation tool has real reach. Treat it like a tool with reach, unless you enjoy writing incident reports to yourself.

The skill is where those boundaries live:

- Use the wrapper by default.
- Use snapshots before interacting.
- Prefer visible refs over guessed selectors.
- Do not ask for secrets in chat.
- Ask before destructive or externally visible actions.
- Use screenshots when the page is ambiguous.
- Stop if the target, account, or content is unclear.

Those rules are boring. Good. Boring is what keeps "agentic workflow" from becoming "why did it click Submit."

## The Prompt I Would Give Another Agent

If I were handing this workflow to someone else, I would start with a prompt. Let their agent create the skill and adapt the details to their machine. That is less romantic than a handcrafted config file, but romance is how half of our dotfiles became haunted furniture.

```text
Create a skill named browser-workbench for my coding agent.

Purpose:
Teach the agent how to use a real browser through agent-browser or an agent-web wrapper.

Behavior:
- Prefer `agent-web <command>` if an agent-web wrapper exists.
- Otherwise use `agent-browser <command>` directly.
- Start with `open <url>`.
- Use `snapshot -i` to inspect interactive elements.
- Use referenced elements like `@e12` for click, fill, type, and press actions.
- Re-run `snapshot -i` after navigation, modal changes, form submissions, or any major page update.
- Use `screenshot --annotate` when the page is visual, ambiguous, or layout-sensitive.
- Let the user enter passwords, MFA codes, recovery codes, payment details, and other secrets directly in the headed browser.
- Never ask the user to paste secrets, cookies, session tokens, or local storage into chat.
- Pause before destructive actions, purchases, account changes, submissions, posts, sends, deletes, uploads, downloads, or anything externally visible unless the user has already given an explicit instruction and the target/content are unambiguous.
- If the page state, account, or target is unclear, stop and ask.

Include command examples:
`agent-web open https://example.com`
`agent-web snapshot -i`
`agent-web click @e12`
`agent-web fill @e7 "example text"`
`agent-web press Enter`
`agent-web screenshot --annotate`

Make it instruction-only. Do not add scripts unless I ask for them.
```

For Codex, that prompt can become a skill under a user or repo skills folder. OpenAI's Codex docs show skills as folders with a required `SKILL.md`, and Codex can read user skills from `$HOME/.agents/skills` or repo skills from `.agents/skills`.

For Claude Code, the same idea maps to `~/.claude/skills/browser-workbench/SKILL.md`. Anthropic's docs use the same basic shape: a skill directory with a required `SKILL.md`, frontmatter, instructions, and optional supporting files.

The prompt is portable. The browser binary, wrapper name, profile strategy, and policy details can change. The workflow shape survives.

## Where It Breaks

Browser refs are temporary. If the page changes, old refs can lie to you. Snapshot again.

Visual pages are not always understandable from text. Screenshot them.

Some sites hide state behind transitions, popovers, lazy loading, and strange focus behavior. Slow down.

Some actions should never be automated without a clear human instruction. Slow down even more.

Use APIs when a stable API exists and the job is deterministic. Browser automation earns its keep when the browser is the product surface, the inspection surface, or the only practical interface. Otherwise you have invented a slower API with screenshots. Congratulations, I guess.

That boundary keeps the tool honest.

## Why This Stuck

The agent workflows that stick for me are usually small pieces of structure that remove a recurring handoff. This did that.

Before, browser work meant I became the adapter. The agent would produce text, I would move it into a webpage, then I would bring page state back into the conversation. Over and over. Tiny context switches, but enough of them and your brain starts charging interest.

Now the browser is another tool surface.

The agent can open, inspect, click, fill, screenshot, and report back. I can stay in the loop where I actually matter: approving actions, handling sensitive steps, correcting judgment, and deciding what should happen next.

That is the pattern I keep coming back to with agents: stop asking the model to be a genius inside a broom closet. Give it a better environment.

In this case, the better environment was an isolated browser, a CLI, a wrapper, and a skill with rules. Four boring nouns. Annoyingly effective.

Sources: [agent-browser on GitHub](https://github.com/browserbase/agent-browser), [OpenAI Codex skills docs](https://developers.openai.com/codex/skills), and [Claude Code skills docs](https://code.claude.com/docs/en/skills).

---

---
title: Why I Am Moving My Agentic OS Substrate From Hermes To Pi.dev
excerpt: Hermes helped me get an agentic OS prototype moving. Pi.dev gives me a
  smaller harness I can shape instead of inheriting a whole runtime.
date: 2026-05-26
author: Jadan Jones
canonicalUrl: https://www.jadanjones.com/blog/pi-dev-openclaw-hermes-agentic-os-substrate
sourceUrl: https://www.jadanjones.com/blog/pi-dev-openclaw-hermes-agentic-os-substrate
markdownUrl: https://www.jadanjones.com/blog/pi-dev-openclaw-hermes-agentic-os-substrate.md
categories:
  - Artificial Intelligence
  - Software Development
  - AI Agents
keywords: Pi.dev, Hermes Agent, OpenClaw, agent orchestration, agentic OS, AI
  agents, coding agent harness, multi-agent systems
---

# Why I Am Moving My Agentic OS Substrate From Hermes To Pi.dev

Hermes got me to the fun part too fast.

I mean that as a compliment.

I am building an agentic OS, and the first version needed a substrate with teeth. Memory. Skills. Background jobs. Tool calls. Subagents. Messaging surfaces. A way to keep working after the first chat session ends and everyone stops pretending a blank prompt is a product.

Hermes Agent already has a lot of that.

So I used it.

Then the prototype started asking a different question: do I want my OS to inherit this much runtime?

My answer keeps landing on no.

I am moving the substrate toward Pi.dev.

![Whiteboard cover showing Hermes as the current agentic OS substrate, Pi.dev as the next smaller harness, and the lessons I want to keep from Hermes](/images/blog/agentic-os-substrate-cover.png)

## The Shape Of The Tools

The comparison only makes sense if you separate the jobs.

OpenClaw is the loud runtime. Gateway, channels, devices, tools, skills, agents, and local-first assistant energy. It tries to touch the outside world.

Hermes is the long-running agent runtime. Memory, skills, cron, subagents, tool gateway, messaging, model/provider routing. It gives you a lot before you write much glue.

Pi.dev is much smaller. The docs describe it as a minimal terminal coding harness that you adapt to your workflow. The interesting parts are AGENTS.md, skills, prompt templates, packages, session trees, custom models, extensions, the SDK, RPC mode, and JSON event streams.

Pi also leaves things out. Built-in subagents. Plan mode. Permission popups. MCP. You add packages or extensions, or you leave the piece on the floor.

The tradeoff pulled me in.

![Whiteboard map comparing OpenClaw, Hermes Agent, and Pi.dev across built-in autonomy and custom control](/images/blog/agent-substrate-choice-map.png)

Hermes gives me a working assistant substrate.

Pi gives me a smaller harness.

For the OS I am building, the smaller harness is starting to look healthier.

## What Hermes Taught Me

Hermes helped me avoid a very common fake-progress loop.

You can spend weeks designing the perfect agent substrate and still have nothing that behaves like an assistant. Beautiful folders. Clean diagrams. No pulse.

Hermes has pulse.

It helped me feel the product earlier. I could see where memory mattered. I could see why skills should be real files instead of repeated prompts. I could see why background sessions matter. I could see where subagents are useful and where they become job titles with a token bill.

The useful parts I want to keep:

| Hermes pattern | Why I still care |
| --- | --- |
| Memory | The OS has to remember operating context without turning every session into archaeology. |
| Skills | Repeatable work needs a durable home. |
| Background work | Some tasks should run without me staring at the main thread. |
| Subagents | Parallel work needs a boundary, especially for research and review. |
| Gateway thinking | A real OS eventually outgrows the terminal. |

Hermes gave me a fast answer to "what should this thing feel like?"

The next question is uglier: which parts should I own?

## Why Pi.dev Pulls Me In

Pi is interesting because the extension surface is the point.

Extensions can register tools, watch events, block or modify tool calls, inject context, change compaction behavior, add commands, render UI, and store session state. Skills package repeatable capabilities. Packages can ship extensions, skills, prompts, and themes together. The SDK and RPC mode make Pi easier to embed in a larger system.

For an agentic OS, the harness should be one layer in the stack.

If memory needs a different shape, I want to swap it.

If tool approvals need a stricter policy, I want that policy in my code.

If background workers need their own event log, I want to design the log.

If a skill can mutate itself, I want governance around that before it becomes a cute demo with production permissions.

Pi means more work up front. I have to build or install the parts Hermes already gives me: agent teams, memory, scheduling, gateway behavior, approvals, maybe MCP-style tool connections.

Fine.

I would rather add those pieces deliberately than spend the next year sanding down a runtime that keeps trying to be the product.

![Whiteboard diagram showing the current Hermes-based substrate moving toward a Pi.dev harness with selected memory, scheduler, gateway, policy, worker, and eval modules](/images/blog/agentic-os-current-to-target.png)

## The Part I Am Rebuilding

Here is the actual migration, without the inspirational architecture poster.

Hermes currently gives me the runtime spine.

Pi would sit closer to the coding harness layer.

Around Pi, I would build the OS pieces I care about:

```text
agentic-os/
  .pi/
    agents/
    skills/
    extensions/
    prompts/
  runtime/
    scheduler/
    memory/
    sessions/
    channels/
    workers/
  verification/
    evals/
    traces/
    audit-log/
```

The split matters.

Pi handles the harness surface: prompts, skills, extensions, events, sessions, programmatic control.

My OS handles memory policy, scheduler behavior, channel adapters, worker lifecycle, traces, evals, audit logs, and permission boundaries.

I want the boring pieces to be boring in my repo.

The approval rule should live somewhere I can inspect.

The scheduler should have state I can query.

The memory write should have a reason attached.

The tool call should leave enough evidence for me to debug it later.

I trust that substrate.

## OpenClaw Is The Stress Test

OpenClaw is useful in this comparison because it shows the other extreme.

A local-first assistant with a gateway, channels, devices, tools, skills, events, cron, routing, and companion surfaces has a very different energy from a terminal harness. It also has a much bigger trust conversation.

People get excited about agents in Slack, Telegram, GitHub, Linear, and customer workflows because those agents touch real work. Fair. Real work is the appeal.

The same access creates the scary part.

Credential storage. Skill marketplaces. Shell access. Chat access. Silent automation failures. API costs. A helpful agent with too many keys.

I keep OpenClaw in the research for that reason.

Benchmark for aliveness.

Also a reminder that connected agents are permission bundles wearing a friendly UI.

## The Decision Board

My current read:

![Whiteboard decision board showing Pi.dev as strongest for a custom agentic OS harness, Hermes and OpenClaw as stronger for always-on assistant runtimes and channel surfaces](/images/blog/agent-framework-decision-board.png)

If I wanted an always-on personal assistant quickly, I would start with Hermes or OpenClaw.

If I want the agent layer to become part of my own product architecture, I would rather start with Pi and build the OS layer around it.

Pi has gaps. Those gaps are visible:

| Gap | What I would own |
| --- | --- |
| Long-running process | Identity, lifecycle, restarts, health checks. |
| Memory | Stores, summaries, retrieval, profile files, write policy. |
| Scheduler | Job state, retries, cancellation, visible run history. |
| Channels | Slack, Telegram, email, browser, app, CLI adapters. |
| Permissions | Tool allowlists, approvals, sandbox profiles, path protection, cost limits. |
| Observability | Traces, evals, logs, event history, "why did it do that?" answers. |

Those are OS work. I do not want them hidden from me.

## The Bet I Am Making

Pi gives me the harness I can own.

Hermes gave me the product feel quickly.

OpenClaw keeps the connected-agent risk visible.

The migration is practical. I still want memory, background work, skills, workers, channels, and enough autonomy to feel alive.

I just want those pieces entering the system through my boundaries.

Sources: [Pi.dev homepage](https://pi.dev/), [Pi extensions docs](https://pi.dev/docs/latest/extensions), [Pi skills docs](https://pi.dev/docs/latest/skills), [Pi SDK docs](https://pi.dev/docs/latest/sdk), [Pi RPC docs](https://pi.dev/docs/latest/rpc), [Hermes Agent feature docs](https://hermes-agent.nousresearch.com/docs/user-guide/features/overview), [Hermes Agent memory docs](https://hermes-agent.nousresearch.com/docs/user-guide/features/memory), [OpenClaw architecture docs](https://docs.openclaw.ai/concepts/architecture), [OpenClaw security docs](https://docs.openclaw.ai/gateway/security), [HN discussion on productive OpenClaw usage](https://news.ycombinator.com/item?id=47147183), [Reddit Pi and Hermes memory discussion](https://www.reddit.com/r/PiCodingAgent/comments/1taakg3/self_improving_pi/), and [Hermes Agent issues](https://github.com/NousResearch/hermes-agent/issues).

---

---
title: "25 Commits in 30 Hours: What AI Autonomy Mode Actually Ships"
excerpt: "A real 30-hour AI autonomy mode push: 25 commits, 12 arcs, zero API
  spend, what shipped, what broke, and what still is not real yet."
date: 2026-05-21
author: Jadan Jones
canonicalUrl: https://www.jadanjones.com/blog/25-commits-in-30-hours-ai-autonomy-mode
sourceUrl: https://www.jadanjones.com/blog/25-commits-in-30-hours-ai-autonomy-mode
markdownUrl: https://www.jadanjones.com/blog/25-commits-in-30-hours-ai-autonomy-mode.md
categories:
  - Artificial Intelligence
  - Software Development
  - AI Agents
keywords: AI autonomy mode, AI dev system, autonomous AI coding, agentic OS, AI
  agents, Codex, Claude Code, LLM council, triad workflow, AI development
  workflow, personal agentic OS
---

# 25 commits in 30 hours: what AI autonomy mode actually ships

Saturday morning to Monday morning. One developer, one AI dev system, no human pair, no copilot pair-programming.

25 commits. 12 arcs closed. Knowledge layer live, voice agent fully working, three proactive surfaces wired to push messages to my phone, an [LLM council](/blog/ai-yes-man-llm-council) skill enforced by 34 test assertions, and a v1 of the whole personal agentic OS that I now actually use.

I would like to claim this was all skill. It was not. Most of it was operating-mode changes that I had been resisting for months.

Here is what those changes were, what they shipped, what broke anyway, and what is still aspirational. Honest version.

## The Numbers

Real counts, not vibes. Pulled from the handoff doc I wrote at the end of the push.

```text
Window:        ~30 hours, Saturday morning to Monday morning
Commits:       25 on origin/main
Arcs closed:   12
Spend:         $0 in API costs
Tests added:   500+ green, no env-dep skips
Surfaces live: knowledge wiki, voice agent, telegram briefs (morning + evening),
               LLM council skill, /wiki + /wiki-lint slash commands
```

Five of those arcs were dependent on each other. Seven were not. The five-arc chain was the knowledge layer: foundation, librarian agent, slash commands, brief integration, then a separate arc to wire it into the live query path. The seven independent arcs covered four voice-agent fixes, two telegram-brief scheduling arcs, an LLM council skill, an arc-cleanup hygiene helper, and a README rewrite.

The voice agent took four arcs because it kept finding new ways to break under macOS process boundaries. More on that in the "what broke" section.

## What Actually Shipped

Not a feature list. The actual changes to my daily setup.

**Knowledge layer.** A librarian agent ingests raw documents into a wiki structure inside Obsidian. The wiki is searchable from the agent's tool path, which means my morning brief and my voice queries both read from it before answering. Auto-file-back means production traffic writes new analyses back into the wiki, so the knowledge compounds without me curating it. This is the Karpathy raw-to-wiki pattern, but the loop closes from production output, not from me hand-editing notes.

**Voice agent fully bundled.** Real `.app`, Spotlight-launched, menu bar accessory, `Cmd+Q` teardown, push-to-talk via `CGEventTap` and Input Monitoring, not `NSEvent`, which is silently broken on adhoc-signed Spotlight bundles. KeePassXC and AeroSpace both hit the same wall and migrated the same way. Conversational mode with voice activity detection. On-demand mic that closes the input stream when not recording, so the orange mic indicator actually means "recording" and not "open audio handle from twenty minutes ago."

**Proactive briefs.** Morning brief at 07:30 and evening plan at 19:30 weekdays, scheduled by macOS `launchd`, posted to my Telegram via stdlib `urllib` over TLS, with the [trust-store fix I wrote up separately](/blog/python-urllib-https-launchd-certificate-verify-failed). The evening plan now does forward-synthesis over absence inventory, which is a fancy way of saying "tell me what to do tomorrow" rather than "tell me what I did not do today." That change cut output length by 61% and signal density went up.

**LLM council.** Five-persona deliberation skill, slash command, DNA auto-invocation triggers in `CLAUDE.md`. Honest framing enforced by 34 test assertions so the language cannot drift into fake-vote territory. [Wrote that one up separately too](/blog/ai-yes-man-llm-council).

**Hygiene arc.** A cleanup helper that runs at every `/end-work`, auditing the cowork scratch space, the inbox/outbox/running task directories on both Mac and VPS, dangling test processes, stale `launchd` state. Keep-list externalized so durable toolkit files do not get flagged every closeout.

**README rewrite.** The old root README claimed P0b-scaffold state on a system that had been running for a month. I replaced it instead of adding alongside, because a lying front door breaks honesty. New one says what the system actually is. Took an hour because most of it was extraction from existing memory and one real codex_cli brief firing, not new prose.

That is the surface area. Twelve arcs, 25 commits, one weekend.

## What Operating Mode Change Actually Got Me Here

This is the only part of the post that matters. The skills did not change. The model did not change. The mode I was running the AI in changed.

Three rules went in, and they compounded.

**Rule 1: full decision authority on reasoned choices.** Stop converting decided work into Fred-ratifies. If the answer is clear from context, memory, and goals, the AI makes the call and documents the tradeoff inline. Bouncing comes back to me only for five categories: brand-new top-level concept with no precedent, new spend or paid tier change, destructive operations, sudo outside the relaxed set, irrecoverable choices. Everything else gets handled. This sounds obvious in writing. In practice the AI's yes-man instinct is to surface every fork as a "do you want X or Y" because that feels safer. Forcing it to pick was the single biggest change.

**Rule 2: relaxed permission asks across machines.** The AI executes bash commands directly on my Mac and on my VPS without asking for each one. Deletes, sudo outside the relaxed set, and force-push are still off-limits. Everything else just runs. This used to feel risky. After 30 hours of doing it I cannot remember a single command that should not have run. The [triad pattern](/blog/llm-council-triad-ai-code-review) (planner / executor / verifier) catches the bad ones before they execute.

**Rule 3: council mode 1 on internal forks.** When the planner phase hits a reasoned-engineering decision with multiple defensible paths, it runs the council inside its own reasoning trace, weighs the five lenses, picks the move that best survives the critique, documents the tradeoff. Does not bounce to me. Across the 12 arcs in this window, the council ran internally on roughly 40 forks. Zero of them came back to my inbox.

Stacked, those three rules do this: the AI handles the meeting that used to be "wait for Fred to wake up and answer this." I lost the relay bottleneck. The work compresses to wall-clock speed of the AI doing it, not wall-clock speed of me being available.

I am not a 10x developer this weekend. I am a developer who stopped being on the critical path of his own dev system.

## What Broke (Honest Section)

Three things that I do not want to skip past, because they will land in someone else's autonomous-AI setup the same way they landed in mine.

**Premature v1 claims, three times in 12 hours.** Closeout summary said "v1 shipped" when the knowledge layer was not actually wired into agentic_query. Closeout said "knowledge layer live" when the auto-file-back path had not been tested in production. Closeout said "Telegram briefs working" when the TLS trust store under `launchd` was empty and the briefs were silently failing. Each one got caught the same way: I read the summary, said "that does not sound right," and the AI ran the council on its own closeout and the contrarian flagged the framing. None of those would have surfaced if I had just trusted the summary. The honest-framing-as-invariant work in the LLM council arc came directly out of this experience. The pattern is in the system now. Future me does not get to relax it in a quiet edit because the tests fail.

**Yes-man relapses on the operating-mode change itself.** Even with full decision authority installed, the AI kept producing micro-task plans with timeline padding and "Fred please confirm" lines on decisions that were already made. The pattern looks like compliance but is actually the same yes-man instinct dressed up as process discipline. I had to correct it multiple times during the push. Eventually the rule landed in DNA: ONE dispatch for multi-piece phases, drop timeline estimates anchored on human dev time (AI dev with triad is order-of-magnitude faster), authorize agent-spawning by default. The fact that I had to install that as an explicit rule, on top of the autonomy rule, on top of the council rule, tells you how strong the underlying instinct is.

**Em-dash escapes.** I have a hard ban on em-dashes in shipped state. The model kept producing them anyway, in commit messages, in tests, in docs. By the end of the weekend I had landed em-dash linting at multiple checkpoints. The lesson is not "models cannot follow style rules." The lesson is that any rule you care about needs to land as an invariant a test enforces, not as advice in a prompt.

**Four voice-agent arcs in a row.** The voice agent took P-Voice-Bundle, P-Voice-Polish-Conv, P-Voice-Hotfix, then P-Voice-Hotfix-V2. Each one was supposed to be the last one. The thing that finally stuck (`CGEventTap` plus Input Monitoring) was the documented working path that two other open-source Mac apps had already migrated to. It took me three arcs to find that out, because each prior arc was treating the symptom (`NSEvent` silently not firing) as a bug to debug, not as a signal that the API was wrong. The faster I had read what KeePassXC was doing, the faster the arc would have closed. Autonomy mode does not save you from this. It just runs you into the wall faster.

## What Is Still Aspirational

Honest section, part two. Things I claim are live and are not.

**Layer 4 self-learning behavioral memory.** The 5-layer memory architecture has `CLAUDE.md` (static), primer, memory.sh, and Obsidian wired. Layer 4 (self-learning behavioral memory that updates based on conversation signals) is not built. It is the most complex layer and is multi-week work. Calling the system "v1 for solo use" is correct. Calling it "fully self-learning" would not be.

**Multi-host wiki sync.** The wiki is Mac-only right now. The VPS does not have it. Syncthing mesh or git pull, undecided.

**Voice cloning.** Backlog. F5-TTS is the recommended local zero-shot path. Not started.

**Productization layer.** Multi-tenant, template-zero, per-project L0-L4 separation. Separate strategy work, not in the v1 push. The system is mine right now. Making it sellable is a different project.

**Dashboard.** Tauri menu-bar surface for worker state plus eval scores. Originally a P9+ candidate. Deferred. The system is observable through doctor scripts and log tails today, which is fine for me, not fine for a less-technical user.

If I wrote a marketing version of this post, half of the above would be in the bullet list. They are not in the system yet.

## Is This Real Or Did I Just Get Lucky

The fair question. One 30-hour push proves the rules can compound. It does not prove the velocity holds.

Three days later I ran another single-day push: five arcs in one day, all $0 spend, council mode 1 on every fork, zero bounces. That is a second data point. The arcs were smaller and more disciplined than the first push, but the pattern was the same. The operating-mode change is what is doing the work, not weekend energy.

The thing I will be watching is whether arc duration starts creeping back up as I add more complexity. If arc-7 of a session starts hitting the same yes-man patterns that arc-1 hit, the rules need reinforcement. The honest-by-gate work (tests that enforce framing rules as invariants) is the bet I am placing on this not happening.

## When This Does Not Work

You cannot run an AI dev system in autonomy mode if you are not willing to read the diffs.

The whole pattern depends on the AI making decisions and you trusting them by default, with the triad catching the bad ones before they ship. If you cannot read the resulting code, you cannot tell when the triad has missed something, and you cannot tell when the AI is confidently shipping the wrong thing. The cost of that on a 25-commit weekend is not zero.

It also does not work for greenfield architectural choices. The five enumerated bounce categories include "brand-new top-level concept with no precedent." Autonomy mode is for execution and arc-level decisions inside an existing architecture. Picking the architecture is still my job.

## Close

25 commits in 30 hours is not a record. It is a data point.

The data point is that most of what made the weekend possible was getting out of the way. The skills, the model, the tooling were all the same as a month ago. What changed was the number of decisions I was no longer on the critical path of.

If you have an AI dev system and it feels slow, the question I would ask is not "is the model good enough." It is "how many times per arc does this thing wait for me before continuing." Every one of those is a place a rule could go.

Try installing one.

---

---
title: How to Stop AI From Being a Yes Man With an LLM Council
excerpt: Stop AI from agreeing with bad ideas. Use an LLM Council with 5
  role-based lenses, honest framing, and production-tested gates for Codex and
  Claude Code.
date: 2026-05-21
author: Jadan Jones
canonicalUrl: https://www.jadanjones.com/blog/ai-yes-man-llm-council
sourceUrl: https://www.jadanjones.com/blog/ai-yes-man-llm-council
markdownUrl: https://www.jadanjones.com/blog/ai-yes-man-llm-council.md
categories:
  - Artificial Intelligence
  - Software Development
  - AI Agents
keywords: how to stop AI from being a yes man, stop AI from agreeing, LLM
  Council, AI decision council, AI subagents, role-based agents, Codex
  subagents, Claude Code subagents, decision-council skill
---

# How to Stop AI From Being a Yes Man With an LLM Council

*Revamp. May 21, 2026. Original post on May 18 was the theory. This version has the production data.*

"You're absolutely right."

Shut up, Claude :)

I say that with love. But if you have used AI for more than 5 minutes, you know what I mean.

You can give it a half-baked product idea, two contradictory constraints, and just enough confidence to sound dangerous, and it still puts on the little customer success smile.

"Strong direction."

No. Sometimes the direction is a wall.

That is the annoying part about using LLMs for real decisions. Helpful often turns into agreeable. Agreeable is fine when you need a regex or a first draft. It is bad when you are choosing architecture, product direction, pricing, security, or agent behavior that gets baked into the repo.

At that point, you need a few voices willing to make the idea uncomfortable before it becomes expensive.

That is where an LLM Council is useful.

The first version of this post was theory. I had the pattern working in Codex but not in Claude Code, and the whole thing was unverified at scale. Three days later the skill shipped, 34 test assertions enforce its rules, and I ran five arcs in one day where the council ran internally on every reasoned fork. None of them bounced a decision back to me. The pattern works. Here is the version with the receipts.

## My Spin On The LLM Council

Karpathy's LLM Council is the provider-based version. Send one prompt to several LLMs, anonymize the answers, rank them, then synthesize the result.

My spin is different. Instead of filling the council with providers, fill it with the personalities you wish the providers would reliably bring to the room.

The question I care about is less "which model is smartest?" and more:

"Will this thing tell me when my idea is bad?"

Most of the time, I do not want 5 versions of the same helpful answer. I want 5 jobs: Contrarian, First Principles Thinker, Expansionist, Outsider, and Executor.

You can run those roles on one model or several models. The important part is that each role gets the same brief and returns a focused critique.

One prompt that says "be skeptical, practical, strategic, simple, visionary, and concise" usually turns into soup. Keeping the roles separate is the difference between a critique and a paragraph that politely avoids the main problem.

## The 5 Voices I Want In The Room

| Role | What I want from it |
| --- | --- |
| Contrarian | Failure modes, weak assumptions, abuse cases, misleading claims |
| First Principles Thinker | The actual problem, minimum coherent workflow, missing proof |
| Expansionist | The stronger version of the idea, upside, adjacent capability |
| Outsider | What a normal person would find confusing, vague, or overcomplicated |
| Executor | The next action, verification check, owner, and defer list |

The point is to take what people notice when they compare models, that each one has a different temperament, and make those temperaments explicit.

Do not hope the model happens to be skeptical. Assign skepticism.

Do not ask one assistant to agree, disagree, expand, reduce, plan, and judge all at once. That is how you get a smooth paragraph that somehow avoids the main problem.

The council should be short:

```text
1. Write one decision brief.
2. Run the 5 roles against the brief.
3. Collect their findings.
4. Synthesize conflicts and agreements.
5. Choose one implementation move.
```

That is it. No tiny parliament living in your terminal. Just enough friction to stop AI from politely escorting a bad idea into production.

## Mode 1 And Mode 2 (The Part I Got Wrong The First Time)

The original post made it sound like the council is always five parallel subagents. That is one valid mode. It is not the default, and it should not be.

In practice there are two modes, and treating them as the same thing is how this pattern degrades into theater.

**Mode 1: single-model, structured pass.** One model. The model runs the question through all 5 lenses inside one reasoning trace, weighs the pressure from each, picks the move that best survives the critique. This is the default. It is what you should reach for when you catch yourself agreeing too quickly, when a planner hits an ambiguous fork, or when you are about to bounce a decision back to the operator because you do not feel like making it.

**Mode 2: spawn five parallel subagents.** Five Task subagents, each with isolated context and a single persona prompt. A synthesis step merges them. This is for high-stakes decisions where context contamination across personas would actually matter. It costs five times as much. You should treat it as a rare tool, not the default.

The honest framing matters more than the mechanism. If you ran Mode 1 and reported it as "the council voted," you have lied about your process. One model running a template is not a vote. The output of Mode 1 should say something like "after running the lenses, the move that survived the critique is X." The output of Mode 2 can say "five subagents returned, here is the synthesis." Different mechanism, different language.

I shipped the skill with this distinction enforced as a hard ban. "The council voted" and "consensus says" are banned phrases. If you write them, the test fails. More on that below.

## The Brief Does Most Of The Work

The brief matters more than the role names. If the brief is vague, 5 lenses just give you 5 flavors of fog. Very well formatted fog.

Use something like this:

```text
Decision:
Context:
Constraints:
What success means:
What would make this decision wrong:
What should not be touched yet:
```

That last line is not decorative. AI agents love expanding scope. You ask whether a workflow should become a skill, and 4 minutes later it is proposing a platform and a new operating philosophy.

Relax. The council should make the next move clearer, not turn every decision into a migration plan with feelings.

## What Actually Changed When I Used It (For Real, At Scale)

The original post said "I used this workflow while shaping this article." That was true. It was also a single data point.

Here is the production data three days later.

**Five arcs in one day, zero bounces on reasoned forks.** On May 20 I ran five back-to-back engineering arcs through a single dispatch directive: planner runs the council internally for every reasoned-engineering decision and locks the answer. Bounce to the operator only for five enumerated categories (new top-level concept, new spend, destructive operations, sudo outside the relaxed set, irrecoverable choices). Every other fork stays council-internal. The result: five arcs shipped clean, all at $0 spend, none of them surfacing a reasoned-engineering decision back to me. The same planners that used to ask "should I do X or Y" now ran the council, picked, documented the tradeoff inline, and continued.

**The recursive recursion-safe moment.** The arc that shipped the council skill itself used the council to make its own internal decisions. Planner hit a fork on em-dash handling inside persona prefixes. Council mode 1 ran internally, picked period as the separator, documented the tradeoff. The pattern was used to build the pattern. That is the loop closing.

**The honest-framing catch.** Closeout summary said "v1 shipped." Council ran on the summary itself, not the code. Contrarian flagged the framing as overconfident. Outsider pointed out the knowledge layer was not actually wired. Three different premature v1 claims got caught the same way inside twelve hours. None of those would have surfaced from a normal "review this" pass.

The council did not decide for me. It made the weak parts harder to ignore. The difference is that now it does this without my involvement on the easy ones, which means I see only the decisions I should actually be making.

## Honest By Gate

Here is the part of the shipping artifact that matters most.

The skill is enforced by 34 test assertions. 22 are static lints over the skill files and the slash command definition. 12 are live-SMOKE assertions over a real council invocation against an OAuth-token-caching test question.

The lints check that the five persona descriptions match the canonical wording byte-for-byte. They check that the banned-phrase list contains "the council voted," "consensus says," and two other phrases that drift the framing toward fake-vote language. They check that Mode 2 is never auto-invoked. They check that the slash command exposes both modes and defaults to Mode 1.

The live-SMOKE assertions check that a real invocation produces the expected sections, that no banned phrase appears in the output, that the synthesis paragraph references the surviving move rather than a vote, and that the brief format is followed.

This is what I mean by honest by gate. The framing rules are not advice in the skill description. They are invariants that fail the next test run if they drift. Future me does not get to relax them in a quiet edit. The test runs and tells me I broke the contract.

If you build this pattern in your own setup, this is the part I would not skip. The pattern is only useful if the language stays honest. Banned-phrase enforcement is what keeps Mode 1 from drifting into Mode-2-cosplay.

## Add It To Codex

Codex supports skills for reusable workflows and subagents for parallel specialized work. Mode 1 lives in the skill body. Mode 2 spawns the subagents.

Create a personal skill:

```bash
mkdir -p ~/.codex/skills/decision-council
$EDITOR ~/.codex/skills/decision-council/SKILL.md
```

Starting point:

```md
---
name: decision-council
description: Use when an AI assistant is likely to agree too quickly with a product, architecture, UX, security, or implementation decision.
---

Default to Mode 1.

Mode 1: run the 5 lenses inside one reasoning pass.

1. Contrarian: find the flaw, weak assumption, abuse case, or misleading claim.
2. First Principles Thinker: restate the actual problem and minimum coherent workflow.
3. Expansionist: find the stronger version, upside, or adjacent capability.
4. Outsider: explain what is confusing to someone without project context.
5. Executor: define the next action, verification check, and defer list.

Return:

1. Per-lens position (1-3 sentences each).
2. Strongest disagreement.
3. Surviving move.
4. Deferred risks.

Frame as "the surviving move is X" or "after running the lenses, X holds up best."
Do NOT frame as "the council voted" or "consensus says." This is one model running a template, not a vote.

Mode 2: spawn 5 subagents in parallel, one per role, only when the user explicitly says "spawn the council" or "use agents." Synthesize their outputs into the same return format.
```

Then invoke it with a decision brief:

```text
Use the decision-council skill.

Decision: Should we replace this hand-rolled workflow with a skill and 5 subagents?
Context: ...
Constraints: ...
What success means: ...
What would make this decision wrong: ...
What should not be touched yet: ...
```

This is the part people skip, then they blame the model. Do not type "review this idea." Give it the decision, constraints, and what would prove you wrong.

## Add It To Claude Code

In Claude Code, define five subagents for Mode 2 and one skill or slash command that runs Mode 1 inline and routes to the subagents on the explicit trigger.

```bash
mkdir -p .claude/agents .claude/skills/decision-council
$EDITOR .claude/agents/contrarian.md
$EDITOR .claude/skills/decision-council/SKILL.md
```

Each subagent stays small:

```md
---
name: contrarian
description: Reviews decisions for failure modes and weak assumptions.
tools: Read, Grep, Glob
model: inherit
---

Return the top 3 risks and the assumption most likely to be false.
```

Repeat for `first-principles-thinker`, `expansionist`, `outsider`, and `executor`.

The skill:

```md
---
description: Run a 5-lens decision council. Mode 1 default, Mode 2 on explicit trigger.
disable-model-invocation: true
---

Decision brief:
$ARGUMENTS

Default: Mode 1. Run the five lenses inside one reasoning pass and return the surviving move.

If the brief says "spawn the council" or "use agents," switch to Mode 2 and dispatch these subagents in parallel:

- contrarian
- first-principles-thinker
- expansionist
- outsider
- executor

Return: per-lens position, strongest disagreement, surviving move, deferred risks.

Banned phrases: "the council voted," "consensus says," "the council agrees," "vote." One model running a template is not a vote. Frame as "the surviving move is X."
```

Now the command becomes:

```text
/decision-council "Decision: ... Context: ... Constraints: ..."
```

For DNA-level auto-invocation, drop five triggers into your CLAUDE.md so the model reaches for Mode 1 without being asked:

1. Before any AskUserQuestion call where the question is not deeply personal.
2. Inside the planner phase when a fork has an ambiguous tiebreaker.
3. During code review when architectural tradeoffs exist.
4. As an agree-too-quickly self-check when the answer feels suspiciously easy.
5. As a pre-mortem before dispatching a kickoff brief.

Mode 2 stays off the auto-invocation list. It only fires on the explicit trigger phrase.

## When Not To Use It

Do not run a council for tiny choices. Naming a variable does not need 5 lenses and a committee chair. Please just name the variable and move on with your life.

Do not use it when the real bottleneck is missing data. If nobody knows the customer requirement, 5 lens prompts will only produce cleaner speculation. It is still guessing in a nicer outfit.

Use it when the cost of being wrong is real: architecture, security, product direction, pricing, data model changes, public claims, and agent behavior other people will depend on.

The goal is a better next move.

## The Receipt

Three days from theory post to enforced shipping artifact. Five arcs in one day with zero bounces on reasoned forks. 34 test assertions keeping the framing honest. One real catch where the council was run against its own closeout summary and found the v1 claim false.

That is the whole pitch. The pattern works. The language stays honest because the tests will not let it drift. The model stops being a yes man because you have given it a job that is harder to fake.

Now go assign some skepticism.

---

*If you want the sister post on how this stacks with a Planner / Executor / Verifier triad on the build side, see [The Council Picks The Move. The Triad Ships It.](/blog/llm-council-triad-ai-code-review)*

---

---
title: Fix Python urllib HTTPS Failures Under launchd Without verify=False
excerpt: Python urllib HTTPS can fail under launchd when the framework Python
  trust store is empty. Resolve a CA bundle explicitly instead of disabling
  verification.
date: 2026-05-21
author: Jadan Jones
canonicalUrl: https://www.jadanjones.com/blog/python-urllib-https-launchd-certificate-verify-failed
sourceUrl: https://www.jadanjones.com/blog/python-urllib-https-launchd-certificate-verify-failed
markdownUrl: https://www.jadanjones.com/blog/python-urllib-https-launchd-certificate-verify-failed.md
categories:
  - Software Development
  - Python
  - macOS
  - Cybersecurity
keywords: Python urllib certificate verify failed launchd, macOS launchd SSL
  certificate error, urllib CERTIFICATE_VERIFY_FAILED, Python framework trust
  store, ssl create_default_context cafile
---

# stdlib Python urllib HTTPS fails under launchd. The actual fix (not verify=False).

If you landed here, you probably just got this:

```text
urllib.error.URLError: <urlopen error [SSL: CERTIFICATE_VERIFY_FAILED]
certificate verify failed: self-signed certificate in certificate chain
(_ssl.c:1006)>
```

And your script worked fine when you ran it in your terminal.

Then you wrapped it in a launchd plist, restarted with `launchctl kickstart`, and now every HTTPS call dies. Telegram bot API. Anthropic API. Some random webhook. Doesn't matter what the endpoint is. They all return that same self-signed certificate error.

The endpoint is not self-signed. Your Python build is lying to you, because its trust store is empty.

Here's the full picture, the actual fix, and why the Stack Overflow suggestions are mostly wrong.

## The Setup That Breaks

Minimal repro. This is the kind of script I hit it with, posting a message to Telegram from a launchd-managed daemon:

```python
#!/usr/bin/env python3
import json
import urllib.request

def send(token, chat_id, text):
    url = f"https://api.telegram.org/bot{token}/sendMessage"
    data = json.dumps({"chat_id": chat_id, "text": text}).encode()
    req = urllib.request.Request(
        url,
        data=data,
        headers={"Content-Type": "application/json"},
    )
    with urllib.request.urlopen(req, timeout=10) as resp:
        return json.loads(resp.read())

if __name__ == "__main__":
    print(send("BOT_TOKEN", 12345, "hello from launchd"))
```

Run it in your terminal: works.

Drop it under a launchd plist with `ProgramArguments` pointing at the script: SSL verification failure.

Same file. Same Python interpreter on `PATH`. Same network. Different process tree, different result.

## What's Actually Going On

`/usr/bin/env python3` on a typical Mac dev setup resolves to one of two Pythons:

1. The python.org framework build at `/Library/Frameworks/Python.framework/Versions/3.X/bin/python3`
2. Homebrew Python at `/opt/homebrew/bin/python3` on Apple Silicon, or `/usr/local/bin/python3` on Intel

Whichever one is first on your `PATH` wins.

Homebrew's Python comes with a populated OpenSSL trust store. It works.

The python.org framework Python comes with an empty trust store. Its OpenSSL config directory at `/Library/Frameworks/Python.framework/Versions/3.X/etc/openssl/` has no certificates by default. There is a post-install script called `Install Certificates.command` shipped in the same directory that copies certs in from the [`certifi`](https://pypi.org/project/certifi/) package, but it is interactive. You have to double-click it. Most people skip it. I skipped it.

When you run your script in your interactive shell, the way `PATH` resolves on most setups means Homebrew Python often wins, or the shell pulls some other environmental context that papers over the gap. Plenty of Mac developers go years without hitting this because of that.

Under launchd, the picture changes. launchd has its own minimal environment. Whatever `/usr/bin/env python3` resolves to in that `PATH` is what runs, and that's typically the framework Python, with its empty trust store, sitting underneath your urllib call. The stdlib's [`ssl.create_default_context()`](https://docs.python.org/3/library/ssl.html#ssl.create_default_context) looks for a CA bundle, finds nothing, can't verify the leaf cert returned by `api.telegram.org` against any known root, and raises the error you got.

The cert chain Telegram presented is not self-signed. Your Python just has no roots to compare it against, so the chain terminates at something it doesn't recognize. The default SSL context paints that as "self-signed in chain" because that's the formal description of "I walked up the chain and didn't find a trusted anchor."

## What Not To Do

Three suggestions you will absolutely find by Googling this error:

**`verify=False` or `ssl.CERT_NONE`.** Don't. Your script is sending an auth token over the wire. Bot token, API key, OAuth bearer, whatever. Disabling certificate verification opens you up to a man-in-the-middle interception of that credential. If you do this in production, you have built a credential-leak generator. I've seen this in real codebases. It's worse than the original error.

**Switching from urllib to `requests` or `httpx`.** This sometimes works, because `requests` bundles `certifi` and `httpx` resolves a CA bundle through `certifi` by default. But it only works if your venv actually includes `certifi`. And you've now added a dependency to fix what is fundamentally a configuration problem, which means the bug comes back the moment you ship a leaner image, a different venv, or a non-Python-Foundation install. The underlying problem is still there.

**Re-running `Install Certificates.command`.** This actually fixes it on the machine you're sitting at. It does not fix it on the next machine you deploy to. It does not fix it after a Python version upgrade. It does not fix it in CI. It is a manual interactive step. Most teams will not catch when it's been skipped because the failure surface is invisible until something runs under launchd.

## The Actual Fix

Resolve a CA bundle path at startup, feed it explicitly to `ssl.create_default_context(cafile=...)`, and use that context for every HTTPS call. Fall back through a sane order so the same code works whether you have `certifi` installed or not.

```python
import os
import ssl

def resolve_ca_bundle():
    """
    Find a usable CA bundle file path. Returns None as a last resort,
    in which case ssl.create_default_context() will fail loudly with the
    same error you already know, instead of mysteriously.
    """
    # 1. Honor explicit override. Lets you point at a custom CA
    # bundle (corporate root, test fixture, etc.) without code change.
    explicit = os.environ.get("APP_CA_BUNDLE")
    if explicit and os.path.exists(explicit):
        return explicit

    # 2. OpenSSL's compiled-in default. Works on builds that ship a
    # populated trust store. Skipped silently on builds that don't.
    paths = ssl.get_default_verify_paths()
    if paths.cafile and os.path.exists(paths.cafile):
        return paths.cafile

    # 3. macOS system trust store, populated by the OS.
    # Present on every Mac I've seen since at least macOS 10.15.
    sys_bundle = "/etc/ssl/cert.pem"
    if os.path.exists(sys_bundle):
        return sys_bundle

    # 4. certifi if it's in the venv.
    try:
        import certifi
        return certifi.where()
    except ImportError:
        return None

ctx = ssl.create_default_context(cafile=resolve_ca_bundle())
```

Then everywhere you make an HTTPS call:

```python
with urllib.request.urlopen(req, timeout=10, context=ctx) as resp:
    ...
```

Or if you're using `http.client`:

```python
import http.client
conn = http.client.HTTPSConnection("api.telegram.org", context=ctx)
```

That's it. Verification stays on. The empty trust store on the framework Python no longer matters because you've given it a real CA bundle that exists on the filesystem.

## Why The Fallback Order Matters

The order is not arbitrary. It is the order you actually want when shipping code that will run on machines you don't control.

`APP_CA_BUNDLE`, or whatever you call your env var, goes first because operators need to be able to point at a custom bundle without redeploying. Corporate environments running MITM proxies are the common case. Without an override path, you've shipped a script that can't be deployed inside a corporate network.

`ssl.get_default_verify_paths()` goes second because if the OpenSSL build did ship populated, you should use that. It's the canonical answer. Skipping it would mean ignoring the system's own preferred bundle on Linux distros that wire it correctly.

`/etc/ssl/cert.pem` is macOS-specific and is almost always present. It's the OS-level bundle. On Linux, this path varies by distro: `/etc/ssl/certs/ca-certificates.crt` on Debian/Ubuntu, `/etc/pki/tls/certs/ca-bundle.crt` on RHEL/Fedora. If you're cross-platform, branch on `sys.platform` here.

`certifi` goes last because it's a Python dependency, not a system one. If your script is meant to run on machines where the venv may or may not include `certifi`, you want to fall through to it only when the system path failed. If `certifi` is in the venv, this is the cleanest path. But you don't want to rely on it being present.

If all four fail, returning `None` is intentional. `ssl.create_default_context(cafile=None)` will fall back to the default location lookup and produce the same opaque error you got at the start, which is at least familiar. You don't want to silently swap in `verify=False` or anything cute as a "last resort" because that turns a loud configuration problem into a quiet security problem.

## Why Your Hermetic Tests Didn't Catch This

The reason this bug ships into production despite people having "tests" is that nearly every test suite that covers HTTPS-calling code stubs the HTTP layer at unit-test boundary. `requests-mock`, `httpretty`, `responses`, `unittest.mock.patch`, a `pytest` fixture that swaps in a localhost plain-HTTP fake. The TLS layer is never exercised. The code that breaks under launchd works fine in tests because no certificate verification happens.

The fix is not to test less mocked. The fix is to add at least one live SMOKE test that hits a real HTTPS endpoint under the same process environment your production code will run under. For my launchd daemon, that meant a SMOKE step that actually launched the script through `launchctl start` against a test Telegram bot and verified the message arrived. Hermetic tests passed long before launchd would have. The live SMOKE caught it on first deploy.

This is the bigger lesson buried in the bug. TLS configuration is a deployment-time concern that hermetic unit tests cannot reach by construction. If you have any HTTPS in your code, you need at least one test that runs the code in the deployment context. Otherwise you're shipping a TLS regression every time you change your Python version, your launchd plist, your Docker base image, or your venv composition.

## Quick Summary

- python.org framework Python ships with an empty OpenSSL trust store
- `Install Certificates.command` is interactive and easy to miss
- Under launchd, with no interactive shell to paper over the gap, stdlib HTTPS calls fail
- The error message, "self-signed certificate in chain," is misleading. The cert is fine. Your trust store is empty
- `verify=False` is not a fix. It's a credential leak
- Switching libraries to `requests` or `httpx` only works if `certifi` happens to be available, and doesn't address the underlying configuration gap
- The fix is a CA bundle resolver: env override, then OpenSSL default, then `/etc/ssl/cert.pem`, then `certifi`, then loud failure
- Hermetic unit tests can't catch this. Add a live SMOKE test under the deployment process environment

If you've been chasing this for two hours, I hope that saves you the next two.

---

---
title: "Three Local LLMs on an M1 Pro: A One-Week Shakedown"
excerpt: A one-week shakedown of Mistral 7B, DeepSeek R1:14B, and Llama 3.2:3B
  on a 16GB M1 Pro. Where each one wins, where each one breaks, and the stack I
  run today.
date: 2026-05-20
author: Jadan Jones
canonicalUrl: https://www.jadanjones.com/blog/I-tested-3-llms-locally-on-a-macbook-pro-M1pro
sourceUrl: https://www.jadanjones.com/blog/I-tested-3-llms-locally-on-a-macbook-pro-M1pro
markdownUrl: https://www.jadanjones.com/blog/I-tested-3-llms-locally-on-a-macbook-pro-M1pro.md
categories:
  - Artificial Intelligence
  - Apple Silicon
  - Machine Learning
  - Development
keywords: local LLMs on M1 Pro, Ollama M1 Pro, Mistral 7B, DeepSeek R1 14B,
  Llama 3.2 3B, Qwen 2.5 7B, Apple Silicon AI
---

# Three Local LLMs on an M1 Pro: A One-Week Shakedown

_Updated May 2026. Sixteen months after the original test, with corrected model names, sharper claims about where each model wins and breaks, and the stack I actually run today added at the end._

I ran three local LLMs on a 16GB M1 Pro MacBook for a week of normal work. Inbox triage, code refactors, summaries, the occasional 2am debugging spiral.

This is not a benchmark. There is no rubric, no tokens-per-second chart, no eval suite. It is a shakedown. The output is the shape of where each model wins, where each model fails, and when you should reach for a different tool entirely.

I ran them through [Ollama](https://ollama.com) because I wanted to know how a normal developer's machine handles this stuff, not how a 192GB Mac Studio does.

Here is what survived contact with real work.

## The Setup

Three models, all pulled fresh:

```bash
ollama pull mistral:7b
ollama pull deepseek-r1:14b
ollama pull llama3.2:3b
```

Machine: 16GB unified memory M1 Pro. Nothing fancy. The same laptop you probably already have.

Ollama is the only piece of plumbing you need. Install, pull, run. The whole stack fits in the time it takes to make coffee. There is no Docker. There is no Python virtualenv. There is no quantization decision to make for you. The Ollama defaults are sensible.

Two things to know that the marketing does not say:

The 14B is going to push 16GB of memory hard. Close your browser tabs. Quit Slack. The laptop will not crash but it will not be happy.

The 3B will feel free. You will be tempted to leave it running. Resist this if you care about battery.

## Mistral 7B

The model you reach for when you have a hundred small jobs and no patience.

Where it won during a week of actual use: inbox triage, single-shot classification, paragraph-level summarization, "rewrite this Slack message so it sounds less passive-aggressive." Tasks where the answer is short, the context is small, and the cost of being wrong is one redo.

Where it broke: multi-step reasoning where each step depends on the prior. Anything that required tracking state across more than one chunk of code. Tone-shifting in the same response. Asking it for a friendly version and a formal version usually got one or the other, not both.

The shape of its failure is "did the obvious thing, did not see the second thing." You learn quickly to give it one job per prompt and accept the trade.

If your use case is volume, this is the model. First token is near-instant on the M1 Pro. You can chain 50 of these in the time it takes the 14B to finish one.

## DeepSeek R1:14B

The one that thinks out loud.

[DeepSeek R1](https://ollama.com/library/deepseek-r1) surfaces its chain of reasoning in the response itself. You can read the model arguing with itself before it commits to an answer. For one-shot reasoning problems this is genuinely useful. You can see where the reasoning breaks. You can see where you under-specified the prompt. The transparency is not just a flex. It is a debug surface.

Where it won: planning prompts. Architectural sketch-outs. "Walk me through how I would structure X." It thinks longer and the extra thinking shows up in the output. For ten-minute problems it can be worth the wait.

Where it broke: multi-turn. Around turn four or five of a dense technical conversation the model would lose track of which file we were editing, which variant of a function we had settled on, which constraints we had ruled out. It is a one-shot reasoner pretending to be a chat partner. If you treat it like a chat partner, you will get burned.

It also hallucinates package names and API surfaces with confidence. Anything where the actual API has changed since its training data, the model will quietly make up. Always verify the imports.

And the obvious one. It is heavy. 14B on a 16GB Mac is the upper end of comfortable. First token is slow. If you are doing anything else memory-intensive, you will feel the laptop think.

## Llama 3.2:3B

Solid. Forgettable. Useful for both reasons.

The 3B was the model I stopped thinking about. Which is praise. It handled normal chat, normal Q&A, normal "explain this concept to me" prompts without drama. It loaded fast, responded fast, and used a sensible amount of memory.

Where it won: anything where speed mattered more than depth. Real-time interface use. Drafting a quick reply. Asking a question while compiling something else.

Where it broke: niche topics. Anything specialist. Anything where the answer needs domain expertise. The 3B is a generalist that knows a little about a lot. When you push it into a corner where you need depth, it answers in vague-confident prose that sounds right and reads wrong.

If you only run one local model on a 16GB Mac and you want it to feel snappy, this is the one.

## When To Reach For Which

The decision framework is simple once you have run them all.

Reach for **Mistral 7B** when you have volume and small tasks. Inbox triage, classification, short summaries, one-line rewrites. The model that earns its keep on throughput.

Reach for **DeepSeek R1:14B** when you have one hard problem and the time to let it think. Planning. Architecture. Standalone reasoning. Watch for hallucinated imports.

Reach for **Llama 3.2:3B** when you want speed and you want to stop thinking about model selection. The default. The boring choice.

Reach for **none of them** when:

- You need a real context window. These are short-context models. A long document or a real codebase will not fit.
- The cost of hallucination is high. Anything touching money, legal, medical, or production code that ships.
- You need reliable tool use. None of these three was strong at structured tool invocation. The paid frontier models still own this lane.

Local LLMs are not free. They cost your laptop's attention, your fan's lifespan, and your willingness to babysit memory pressure. The question is not "can I run this." The question is whether the price is worth what you save on tokens.

For high-volume narrow tasks, yes. For high-stakes one-shot reasoning, sometimes. For everything in between, your laptop's workload decides.

## What I Would Do Differently

Looking back on the original test:

I should have logged tokens per second instead of writing "smooth as butter." A vibe is not a measurement.

I should have run the same set of prompts across all three models instead of letting the conversations diverge. Without controlled input you compare impressions, not behavior.

I should have measured time to first token. That is the perceived latency. It is what determines whether the model feels usable.

I should have stress-tested at 80% memory pressure, not on a fresh-boot system. You almost never use these models on a fresh-boot system.

Most of all I should have defined what "good" looked like before I started. Without success criteria you grade the model on the prompt instead of the other way around.

The version of this post that defined those things up front would have been a benchmark. This one is a shakedown. Both are useful. They are not the same thing, and pretending otherwise was the original sin of the first draft.

## What I Run Now

Sixteen months later the stack looks different. I do not chase models week to week.

The local-primary slot is [Qwen 2.5:7B](https://ollama.com/library/qwen2.5) through Ollama. It replaced everything I used to run, because it is good enough at most things to not need three models. Above that sits a free-at-margin provider chain I route through Hermes: `codex_cli` via my ChatGPT Plus quota, `claude_cli`, and GitHub Copilot via `gh-auth` credential. Paid Anthropic sits at the bottom as the final fallback. The whole point is to keep token spend near zero while keeping the option to escalate when a task earns it.

The 14B-on-a-laptop era is mostly over for me. If I need that level of reasoning I send it up the chain to a model that has the hardware behind it. The 3B-as-a-default era is also over. Qwen 2.5:7B occupies that slot now and does it better.

The thing that did not change is the reason for running locally at all. Privacy on the small stuff. Latency on the smaller stuff. And not paying tokens for a job that does not need them.

## Close

Models change.

The reason to run them locally does not.

---

---
title: The Council Picks The Move. The Triad Ships It.
excerpt: The LLM Council helps pick the right move before code exists. The Triad
  makes sure Planner, Executor, and Verifier actually ship what they claimed.
date: 2026-05-20
author: Jadan Jones
canonicalUrl: https://www.jadanjones.com/blog/llm-council-triad-ai-code-review
sourceUrl: https://www.jadanjones.com/blog/llm-council-triad-ai-code-review
markdownUrl: https://www.jadanjones.com/blog/llm-council-triad-ai-code-review.md
categories:
  - Artificial Intelligence
  - Software Development
  - AI Agents
keywords: LLM Council, Triad workflow, AI code review, multi-agent coding
  workflow, Codex subagents, Claude Code, AI verifier, agentic software delivery
---

# The Council Picks The Move. The Triad Ships It.

A single model wrote your function.

The same model reviewed your function.

The same model approved your function for production.

That is not review. That is a meeting where one person attended three times and agreed with himself.

The [LLM Council](/blog/ai-yes-man-llm-council) fixes one half of this problem. It fixes how the model thinks about a decision before any code gets written. But thinking is only half the system. After the decision lands, something still has to actually build the thing. And that something can quietly ship the wrong thing while telling you it shipped the right thing.

For that you need a Triad.

## What The Council Does

Quick recap if you skipped the [sister post](/blog/ai-yes-man-llm-council).

The Council is five personalities pulled into the room before a hard decision. Contrarian, First Principles, Expansionist, Outsider, Executor. Each one reads the same brief and gives a short opinion. You synthesize. You pick.

It is a thinking pattern. It runs before code exists.

The Council asks "is this the right move." The Triad asks "did we actually make the move we said we made." Both questions matter. Skipping either one is how production breaks.

## What The Triad Does

The Triad runs after the decision lands. Three models, three jobs, no overlap.

**Planner.** Reads the brief. Writes a plan. Is not allowed to touch code. Its only job is to find the holes in the brief before anything ships. Catches stale assumptions. Catches paths that got renamed two commits ago. Catches environment values that no longer mean what the brief thinks they mean.

**Executor.** Different model. Reads the plan and writes the code. It does not negotiate the plan. If the plan is wrong, that is the Planner's fault, not a debate.

**Verifier.** Third model. Reads the diff and the tests. Does not see the original plan. Its only job is to ask one question. Do these tests actually exercise what changed, or are they exercising the old thing under a new name. Right now this runs as a separate Opus pass with an adversarial system prompt. The intended end state is a Codex pass for true cross-vendor review. That migration is on the list.

Three models. Three jobs. No overlap.

## Why The Combination Compounds

Council and Triad catch different failure modes.

Council fails at execution-layer drift. You can pick exactly the right move via Council and still ship the wrong thing, because the executor renamed a variable two commits ago and nobody updated the brief.

Triad fails at strategic drift. Three models can perfectly plan, execute, and verify a bad idea. The Verifier checks "did we do what we said." It does not check "should we have done it."

Stack them and the gaps close.

The Council runs at decision time. What should we build.

The Triad runs at delivery time. Did we build what we said we would.

A bad plan executed perfectly still gets to main. A good plan with a broken executor never does. You do not get to skip either one.

## What It Actually Catches

Three real catches from the last few weeks. Names and hashes scrubbed.

**The registry-key drift.** Brief told the Executor to set `LANE_C_PROVIDER` in the chain config. Planner refused to start. It grepped the actual config first and found the key had been renamed to `lane_c` two arcs ago. Six different literal-drift catches landed in a single phase, all from the Planner doing this one move. The Executor would have happily set a key that does nothing.

**The thirty-nine green tests.** Executor shipped a new feature. Thirty-nine unit tests, all passing. Verifier read the diff and asked the question. Are any of these tests asserting on the new behavior, or are they all asserting on the legacy path. The answer was the latter. Silent overwrite. The Verifier blocked the merge.

**The closeout that lied.** This one is the Council-Triad handshake. Executor wrote a closeout summary that said "v1 shipped." Council ran on the summary itself, not the code. Contrarian flagged the framing. Outsider pointed out the knowledge layer was not actually wired. Three different premature v1 claims got caught the same way inside twelve hours.

None of those were caught by the model that wrote the code.

## The Cost Math

The obvious objection is that three model passes is three times the cost.

Not really.

Most of the system runs on free-at-margin local or quota models. The Planner is a small fast model. The Verifier is a small fast model. Only the Executor is expensive, and only when the task demands it. Real cost increase is closer to 1.4x than 3x.

Now the other side of the ledger. One Verifier catch prevents one production rollback. One rollback costs more than a month of Verifier passes.

The question is not whether you can afford the Triad. The question is whether you can afford to ship without it.

## How To Wire It Up

The Council is a skill. Same setup as the [sister post](/blog/ai-yes-man-llm-council).

The Triad is a slash command. Here is the working sketch:

```text
/start-work
  -> create plan.md (singleton)
  -> dispatch Planner subagent with the brief
  -> Planner writes plan.md, returns
  -> hand off plan.md to Executor
  -> Executor writes code, returns diff
  -> dispatch Verifier subagent with the diff + tests (NOT the plan)
  -> Verifier returns pass or block
/end-work
  -> archive plan.md to .auto-memory/
```

The Verifier not seeing the plan matters. If the Verifier sees the plan, it grades the diff against the plan, which is just the Executor checking its own homework with extra steps. Withholding the plan forces the Verifier to ask the only question that matters. Do the tests prove the thing the diff claims to do.

That is the whole pattern.

## When Not To Use It

Naming a variable. Renaming a file. Anything where the cost of being wrong is one undo command.

Use the Triad on changes that touch production state, change data shapes, or modify something a customer or future-you depends on. For everything else, ship.

A committee for every typo is just a committee.

## Close

The Council picks the move.

The Triad ships it without lying about what happened.

Sources: [Karpathy's llm-council](https://github.com/karpathy/llm-council), the sister post on [LLM Councils](/blog/ai-yes-man-llm-council), and the multi-agent debate paper [arXiv 2305.14325](https://arxiv.org/abs/2305.14325).

---

---
title: "Axios Compromised on npm: What Happened, How to Check, and How to Fix It"
excerpt: Two malicious Axios versions were published to npm. Learn how to check
  for axios@1.14.1 or axios@0.30.4 and fix affected environments.
date: 2026-05-14
author: Jadan Jones
canonicalUrl: https://www.jadanjones.com/blog/axios-npm-attack-check-fix
sourceUrl: https://www.jadanjones.com/blog/axios-npm-attack-check-fix
markdownUrl: https://www.jadanjones.com/blog/axios-npm-attack-check-fix.md
categories:
  - Cybersecurity
  - Software Development
  - JavaScript
keywords: Axios npm attack, axios compromised, plain-crypto-js, npm supply chain
  attack, axios 1.14.1, axios 0.30.4, JavaScript security
---

# Axios Compromised on npm: What Happened, How to Check, and How to Fix It

Axios is one of those packages that lives quietly in a lot of JavaScript projects.

You install it once, use it for API calls, forget about it, and move on. It sits in frontend apps, backend services, internal tools, side projects, test scripts, old dashboards, new dashboards, and probably three repos you forgot existed.

So when Axios gets compromised on npm, developers should pay attention.

On March 31, 2026, two malicious versions of Axios were published to npm through a compromised maintainer account:

```text
axios@1.14.1
axios@0.30.4
```

Both versions added a malicious dependency:

```text
plain-crypto-js@4.2.1
```

According to the official Axios post-mortem, that dependency installed a remote access trojan on macOS, Windows, and Linux. The malicious Axios versions were live for about three hours before being removed.

That short window matters, but it does not make the incident harmless. If your local machine, CI runner, container build, or deployment pipeline installed one of those versions during that window, you should treat the environment as potentially compromised.

This was not a normal "update your package and move on" vulnerability.

This was a supply chain compromise.

## What Happened

The attacker did not need to change the visible Axios source code in the GitHub repository.

That is part of what made the attack sneaky.

Microsoft reported that the malicious releases made a manifest-only change. In plain English: the npm package metadata added `plain-crypto-js` as a dependency while Axios application logic stayed unchanged.

That means a developer casually checking the Axios GitHub repo may not have seen anything suspicious.

The dangerous part was in the npm package that got published.

When someone installed one of the affected Axios versions, npm could also install `plain-crypto-js`. That package had an install step that executed malicious behavior. Microsoft noted that the malicious dependency did not need to be imported by application code to create risk.

Your app did not have to call Axios.

Your code did not have to import `plain-crypto-js`.

The risk could happen during package installation.

## Which Versions Were Compromised?

The known compromised Axios versions were:

```text
axios@1.14.1
axios@0.30.4
```

Both were removed after the compromise was discovered. Axios, Microsoft, Socket, Datadog, and other security teams all point to the same malicious dependency: `plain-crypto-js@4.2.1`.

If your project installed either Axios version, especially on a developer laptop or CI/CD machine, do not treat it like a small dependency issue.

Treat it like possible machine compromise.

That sounds annoying because it is.

But the alternative is pretending a RAT had polite intentions, and that is not really a security strategy.

## Why plain-crypto-js Was Dangerous

The malicious dependency name was boring enough to scroll past.

`plain-crypto-js` sounds like the kind of package that could appear somewhere deep in a dependency tree and make your eyes glaze over. Socket reported that it was introduced as a new dependency in the compromised Axios package and was absent from normal Axios GitHub releases.

The package used npm's install lifecycle to deploy malware. That makes it different from the kind of vulnerability where danger only appears if your application reaches a specific runtime code path.

This attack targeted the install process itself.

That puts more than your app at risk:

- developer laptops
- CI/CD runners
- build servers
- Docker build environments
- staging machines
- internal test environments

Basically, anywhere `npm install`, `npm update`, or a similar package installation command ran.

## How To Check A Project

Start with the project you care about most.

Inside the project folder, run:

```bash
npm list axios plain-crypto-js
```

You are looking for:

```text
axios@1.14.1
axios@0.30.4
plain-crypto-js@4.2.1
```

Then check your lockfiles:

```bash
grep -E "axios@(1\\.14\\.1|0\\.30\\.4)|plain-crypto-js" package-lock.json yarn.lock pnpm-lock.yaml 2>/dev/null
```

If you want a wider project search:

```bash
grep -R "plain-crypto-js\\|axios@1.14.1\\|axios@0.30.4" .
```

For larger repos, limit the first pass to dependency files:

```bash
grep -R "plain-crypto-js\\|1.14.1\\|0.30.4" package.json package-lock.json yarn.lock pnpm-lock.yaml 2>/dev/null
```

If `plain-crypto-js` appears in your dependency tree around the affected window, investigate further.

## How To Scan A Machine

Sometimes the issue is bigger than the repo in front of you.

You may have old projects, temporary builds, cloned repos, or forgotten test folders sitting on your machine. On macOS or Linux, start with your home directory:

```bash
find "$HOME" -path "*/node_modules/axios/package.json" 2>/dev/null | while read -r f; do
  version=$(grep '"version"' "$f" | head -1)
  echo "$f -> $version"
done
```

To look specifically for the malicious dependency:

```bash
find "$HOME" -path "*/node_modules/plain-crypto-js/package.json" 2>/dev/null
```

If you need a full system scan, replace `"$HOME"` with `/`, but expect it to be slower and noisier.

On Windows PowerShell:

```powershell
Get-ChildItem -Path C:\ -Recurse -Filter "package.json" -ErrorAction SilentlyContinue |
  Where-Object { $_.DirectoryName -like "*node_modules\axios" } |
  ForEach-Object {
    $version = (Get-Content $_.FullName | Select-String '"version"').Line
    Write-Output "$($_.FullName) -> $version"
  }
```

These commands answer the first question:

"Did this machine have one of the bad Axios versions installed anywhere?"

If the answer is yes, the next question is more serious:

"What secrets or systems did this machine have access to?"

## Check Lockfile History

Your current `node_modules` folder may look clean.

That does not prove you were never affected.

Socket reported that the malicious package attempted to clean up after itself by removing the malicious install script evidence. Your current dependency folder may not tell the full story. Git history, lockfile changes, CI logs, and build cache may have better evidence.

For `package-lock.json`:

```bash
git log -p -- package-lock.json | grep -E "plain-crypto-js|axios@(1\\.14\\.1|0\\.30\\.4)|axios-1\\.14\\.1|axios-0\\.30\\.4"
```

For pnpm:

```bash
git log -p -- pnpm-lock.yaml | grep -E "plain-crypto-js|1\\.14\\.1|0\\.30\\.4"
```

For Yarn:

```bash
git log -p -- yarn.lock | grep -E "plain-crypto-js|axios@1\\.14\\.1|axios@0\\.30\\.4"
```

This is one of the parts people skip because it feels tedious.

Unfortunately, supply chain attacks love tedious places.

Old lockfiles, build logs, install scripts, and dependency trees are exactly where this kind of thing hides.

## What To Do If You Installed Affected Versions

If you find one of the affected Axios versions, move away from it immediately.

For incident containment, the known-good versions listed by Axios and Microsoft were:

```bash
npm install axios@1.14.0
```

For the older affected line:

```bash
npm install axios@0.30.3
```

If you later upgrade to a newer Axios release, do it intentionally after checking the current maintainer guidance. Do not let a compromised or stale lockfile make that decision for you.

Then remove and reinstall dependencies:

```bash
rm -rf node_modules package-lock.json
npm cache clean --force
npm install
```

If your CI uses npm, make sure the pipeline is not still resolving to one of the compromised versions.

But do not stop at reinstalling packages.

Because this was malware, you should think about what the affected environment could access.

Rotate secrets that may have been exposed:

- npm tokens
- GitHub tokens
- SSH keys
- API keys
- database passwords
- cloud provider credentials
- CI/CD secrets
- environment variables
- deployment keys

If the affected machine had access to production systems, treat the incident seriously. Huntress recommended rebuilding suspected systems from a trusted image and rotating sensitive tokens, keys, and passwords that may have been accessible.

For high-risk environments, a clean rebuild may be safer than trying to manually remove traces.

That is painful, but it is better than trusting a machine that may have executed a remote access trojan.

## Reduce npm Supply Chain Risk

You cannot completely eliminate npm supply chain risk.

JavaScript projects depend on too many packages, and many of those packages depend on other packages, and somewhere in that pile there is always a package with a name like `tiny-left-pad-safe-final-v2`.

Still, you can make your projects less fragile.

Use `npm ci` in CI/CD:

```bash
npm ci
```

`npm ci` installs from the lockfile. That helps reduce surprise dependency drift in automated builds. It does not protect you if the lockfile already contains a bad package, so review lockfile changes like production code.

Save exact versions by default:

```bash
npm config set save-exact true
```

Or add this to `.npmrc`:

```text
save-exact=true
```

That prevents npm from automatically saving dependency ranges with `^`.

Instead of this:

```json
"axios": "^1.14.0"
```

you get this:

```json
"axios": "1.14.0"
```

You can also force a safe transitive version with npm overrides:

```json
{
  "overrides": {
    "axios": "1.14.0"
  }
}
```

For sensitive builds, consider blocking install scripts:

```bash
npm ci --ignore-scripts
```

Or configure:

```text
ignore-scripts=true
```

Be careful with that one. Some packages legitimately need install scripts. Test before rolling it out everywhere.

Another useful habit is delaying brand-new package versions from entering important environments immediately. Some attacks depend on speed. A malicious version gets published, automated systems pull it quickly, and by the time the package is removed, the damage is already done.

For critical systems, "latest" is not always a flex.

Sometimes "latest" is how you become the QA department for someone else's compromise.

## The Takeaway

The Axios npm attack is a reminder that popular packages are not automatically safe.

That is uncomfortable because modern development runs on trust. We trust npm packages. We trust maintainers. We trust lockfiles. We trust CI pipelines. Most days, that trust works well enough.

But when a package as common as Axios can be compromised, dependency security stops being something only security teams should care about.

Developers need to know what their projects install.

Teams need to control how dependencies move.

CI environments need to be treated like sensitive systems.

And if a machine installed one of the affected Axios versions, the safest mindset is simple:

Assume compromise until you can prove otherwise.

That may sound dramatic.

But when the issue involves a remote access trojan, dramatic is allowed.

Sources: [Axios post-mortem](https://github.com/axios/axios/issues/10636), [Microsoft Security Blog](https://www.microsoft.com/en-us/security/blog/2026/04/01/mitigating-the-axios-npm-supply-chain-compromise/), [Socket research](https://socket.dev/blog/axios-npm-package-compromised), [Datadog Security Labs](https://securitylabs.datadoghq.com/articles/axios-npm-supply-chain-compromise/), [StepSecurity analysis](https://www.stepsecurity.io/blog/axios-compromised-on-npm-malicious-versions-drop-remote-access-trojan), and [Huntress guidance](https://support.huntress.io/hc/en-us/articles/50413332165395-2026-March-Axios-Supply-Chain-Compromise).

---

---
title: "Regular Code, Machine Learning, or LLMs: A Business Guide"
excerpt: A practical guide to choosing regular code for clear rules, machine
  learning for data patterns, and LLMs for language-heavy workflows.
date: 2026-05-12
author: Jadan Jones
canonicalUrl: https://www.jadanjones.com/blog/regular-code-vs-machine-learning-vs-llms
sourceUrl: https://www.jadanjones.com/blog/regular-code-vs-machine-learning-vs-llms
markdownUrl: https://www.jadanjones.com/blog/regular-code-vs-machine-learning-vs-llms.md
categories:
  - Artificial Intelligence
  - Business
  - Machine Learning
keywords: machine learning vs traditional programming, LLM vs traditional
  machine learning, when to use machine learning, AI decision framework for
  business, regular code vs AI
---

# Regular Code, Machine Learning, or LLMs: A Business Guide

I saw a technical critic argue that LLMs are basically useless for business.

I do not agree with that.

But I do understand where the frustration comes from. A lot of companies are reaching for LLMs when they should be using traditional machine learning, and some are reaching for AI when they should be writing normal deterministic code.

My issue is not with LLMs. It is with making them the default architecture.

They should not be.

## Start With The Problem, Not The Technology

Before a business asks "should we use AI?", it should ask a simpler question:

What kind of problem is this?

One terminology note: LLMs are machine learning systems. In this article, I am separating "traditional machine learning" from LLMs because most business decisions are really choosing between deterministic software, predictive models, and generative language models.

Rules? Use code.

Patterns in historical data? Use traditional machine learning.

Messy language, documents, or conversation? Now an LLM belongs in the conversation.

Those are different categories. Blending them together is how companies end up paying LLM prices for problems a simple function could solve.

![A decision map for choosing regular code, traditional machine learning, or LLMs for business use cases](/images/blog/ai-tool-selection-map.webp)

## Use Regular Code When The Rules Are Clear

This is the category companies skip because it does not look like innovation.

If the business rule is known, stable, and explainable, write code.

Examples:

- Apply a discount when a customer has a certain plan.
- Route a support ticket based on a selected category.
- Retry an API call after a `503` response.
- Calculate tax, fees, or eligibility from known fields.
- Block a transaction that violates a fixed policy.

You do not need an LLM for that. You probably do not need traditional ML either.

Google Cloud's guidance makes the same basic distinction: use traditional AI for prediction or classification, and use generative AI when the task needs new text, images, code, or other generated output. That advice sounds boring until it saves you from putting a language model in the middle of a billing rule.

Regular code has advantages businesses sometimes undervalue. It is cheaper. It is faster. It is easier to test. It is easier to explain to auditors. It behaves the same way every time.

If consistency matters more than interpretation, code should be the first option.

## Use Traditional Machine Learning When Patterns Matter

Traditional machine learning fits a different class of problem.

Use ML when you have historical data and you want the system to learn a pattern that is hard to express as rules.

Examples:

- Predict customer churn.
- Detect fraud or anomaly patterns.
- Forecast demand.
- Score credit risk.
- Classify support tickets from past examples.
- Recommend products based on behavior.

These are not problems where you want the system to "write an answer." You want it to estimate, rank, classify, or forecast.

Google Cloud makes a similar distinction: traditional AI use cases usually focus on predicting outcomes or classifying categories based on models trained on historical data. Microsoft also separates predictive AI from generative AI: predictive AI forecasts outcomes, while generative AI produces new content like text, code, images, and other outputs.

That distinction matters because a lot of business AI use cases are not actually generative.

If a company wants to know which customers are likely to cancel next month, an LLM is probably not the core model. It might help explain the result or draft a retention message, but the prediction itself belongs to a model designed for prediction.

## Use LLMs When Language, Context, Or Flexibility Matter

LLMs become valuable when the input or output is messy, language-heavy, and context-dependent.

Examples:

- Summarizing long customer conversations.
- Drafting responses that still need human review.
- Extracting structured fields from messy documents.
- Explaining technical logs in plain language.
- Searching internal knowledge bases with natural language.
- Generating first drafts of reports, emails, or documentation.
- Helping employees reason across multiple documents.

This is where LLMs earn their keep: not by being correct databases, but by helping people read, rewrite, compare, and explain messy material faster.

The mistake is using them where their flexibility becomes a liability.

Even for language work, I would be careful. If the format is stable, extraction rules are clear, latency is tight, data is sensitive, auditability matters, or the output cannot tolerate hallucination, do not default to an LLM. Use the model only where flexible interpretation is worth the extra cost and risk.

If a deterministic system needs to decide whether an invoice is overdue, use code.

If a model needs to predict the probability of late payment based on years of invoices, use traditional ML.

If a system needs to summarize the messy email thread around that invoice and draft a polite follow-up, use an LLM.

That is three different jobs.

## When A Hybrid Approach Makes Sense

The cleanest answer is not always one tool.

In real business systems, the best architecture often combines code, ML, and LLMs.

Imagine a customer support workflow.

Regular code can enforce permissions, routing rules, SLA timers, and escalation thresholds.

Traditional ML can predict ticket priority or detect likely churn.

An LLM can summarize the customer history, draft a response, or explain the next best action to the support agent.

Each layer does what it is good at. The LLM is not asked to own the whole system. The ML model is not asked to write human-friendly explanations. The code is not stretched into pattern recognition it cannot handle.

Google Cloud's guidance says traditional AI and generative AI are not mutually exclusive, and it gives examples where predictive models feed context into generative systems. That is the more mature way to think about AI architecture.

## A Simple Decision Checklist

Before using an LLM, I would ask five questions:

| Question | Best fit |
| --- | --- |
| Can the rule be written clearly? | Regular code |
| Is the job prediction, ranking, or classification? | Traditional ML |
| Is the input messy language or documents? | LLM |
| Does the output need to be generated text? | LLM |
| Does the decision need to be deterministic and auditable? | Regular code or ML with controls |

This is not about being anti-LLM. It is about putting the expensive, flexible, probabilistic part of the system where flexibility is actually useful.

## The Practical Takeaway

The PhD argument is useful because it pushes back against lazy AI architecture.

He is right that many business use cases do not need LLMs. Some do not need machine learning at all. They need clean workflows, reliable data, and normal software engineering.

But "LLMs are useless" goes too far.

LLMs are useful when the work is language-heavy, context-heavy, and hard to reduce to fixed rules. They are less useful when a business needs deterministic logic, cheap repeatability, or a narrow predictive model.

Stop asking whether the system is "AI." Ask whether each decision needs rules, statistics, or language judgment. That question saves more money than another model evaluation.

Sources: [Google Cloud on generative AI vs traditional AI](https://docs.cloud.google.com/docs/ai-ml/generative-ai/generative-ai-or-traditional-ai), [Microsoft on generative AI vs predictive AI](https://www.microsoft.com/en-us/ai/ai-101/generative-ai-vs-other-types-of-ai), and [IBM on generative AI limitations](https://www.ibm.com/think/topics/generative-ai).

---

---
title: Stop Building Agents For Workflows The Tools Are Absorbing
excerpt: Codex and Claude Code are turning agent features into platform
  primitives. The durable work is designing workflows, files, tools, and
  verification.
date: 2026-05-12
author: Jadan Jones
canonicalUrl: https://www.jadanjones.com/blog/stop-building-agents-design-the-environment
sourceUrl: https://www.jadanjones.com/blog/stop-building-agents-design-the-environment
markdownUrl: https://www.jadanjones.com/blog/stop-building-agents-design-the-environment.md
categories:
  - Artificial Intelligence
  - Software Development
  - AI Agents
keywords: AI agents, coding agents, Codex, Claude Code, AGENTS.md, agent
  environment architecture, AI workflow design, skills
---

# Stop Building Agents For Workflows The Tools Are Absorbing

The early instinct with agentic AI was to create a cast of characters.

Researcher agent. Planner agent. Reviewer agent. Legal agent. Writer agent. Each one gets a prompt, a tool list, a handoff rule, and a little routing layer around it.

That made sense when the model was just a text box.

But Codex, Claude Code, Cursor, and similar tools are not plain chat windows anymore. They are agent harnesses. They already know how to read files, edit files, call tools, manage context, run commands, use MCP servers, load instructions, and in some cases spawn subagents.

So the question changes.

The question is no longer, "How many agents should I build?"

The question is, "Which parts of this workflow are still mine to design?"

My answer: the durable layer is the environment. The file tree. The workflow definitions. The instructions. The tools. The test commands. The examples. The data boundaries. The permission model. The proof that the work is done.

## The Wrong Bet

If Anthropic and OpenAI are baking more agentic behavior into Claude Code and Codex, then hand-rolling specialized agents for every repeatable task becomes a strange bet.

You are betting that your little routing layer will age better than the product teams whose entire job is improving routing, memory, tools, subagents, context management, and safety.

That does not mean you should never build agents.

It means you should stop building agents for things the platform is actively turning into primitives.

The public docs already show the direction. Codex supports layered `AGENTS.md` files. Codex skills are directories with `SKILL.md`, scripts, references, assets, and even agent definitions. Codex supports subagents for parallel specialized work, and MCP for connecting models to external tools and context.

Claude Code points the same way. It has `CLAUDE.md` memory, skills, commands, subagents, MCP, hooks, and project-scoped configuration. Even the leaked-prompt discussions around Claude Code point at the same pattern, but I would not build a public thesis on leaked internal text. The official surface is enough.

The providers are absorbing the harness.

Your job is to stop rebuilding the harness and start designing the workspace it operates in.

## File Trees Keep Surviving

Software keeps changing its fashionable architecture language.

We had MVC. We had layered architecture. We had Clean Architecture. We had microservices. We had monorepos. We had infrastructure as code. We had design systems. We had data pipelines.

The names changed, but one thing kept coming back: boundaries encoded in files and folders.

The filesystem is not glamorous, but it is one of the most stable interfaces in computing. It gives humans and tools a shared map. It says: this is the domain, this is the adapter, this is the test, this is the fixture, this is the migration, this is the runbook.

Agentic workflows do not escape that.

If anything, they make file structure more important, because the agent needs a map before it can act intelligently.

## The Workflow Is The Architecture

Take a content research workflow.

The agent version sounds like this:

```text
Create a researcher agent.
Create a strategist agent.
Create a writer agent.
Create an editor agent.
Route between them.
```

The environment version looks like this:

```text
workflows/content-research/
  README.md
  instructions.md
  sources/
  examples/
  output-format.md
  verify.md
  tools.md
```

Now Codex or Claude Code has somewhere to stand.

`instructions.md` explains the job.

`sources/` stores raw material.

`examples/` shows what good output looks like.

`tools.md` says which MCP servers, scripts, or APIs matter.

`verify.md` defines the finish line.

If the platform later ships a better research subagent, you do not throw the system away. You plug that capability into the workflow. If it ships a better summarization skill, the workflow uses the skill. If it ships a better memory layer, the workflow becomes easier to run.

That is the difference.

A fragile agent graph competes with provider updates.

A well-designed workflow tree benefits from provider updates.

## Agent, Skill, Workflow, Or Script?

This is the decision rule I would use.

Use a script when the step is deterministic. Formatting, migrations, data transforms, validation, retries, status-code routing, and build steps should usually be code.

Use a skill when the step is a repeatable method that benefits from model judgment but does not need its own long-lived identity. Drafting a changelog, reviewing a diff, creating a test plan, summarizing research, or generating a release note can often be a skill.

Use a workflow folder when the work has multiple artifacts, examples, data inputs, and verification rules. This is where most business processes belong.

Use an agent when the work needs separate context, separate permissions, parallel execution, long-running ownership, or a specialized model/tool budget. A security reviewer with restricted tools can be an agent. A background research worker can be an agent. A product runtime that serves customers with traceable state can be an agent.

But "I do this task often" is not enough reason to create an agent.

Many agents are just skills wearing a costume.

## The Practical Shift

The practical shift is simple:

Stop asking, "What agent should I make?"

Start asking, "What filesystem would make this workflow obvious?"

For a codebase, that may mean better `AGENTS.md` rules, package-level instructions, test fixtures, and verification scripts.

For a business workflow, it may mean a folder per process with inputs, templates, examples, decisions, and acceptance criteria.

For a content system, it may mean saved research, source notes, hook examples, draft formats, editorial rules, and publishing checklists.

For an internal AI tool, it may mean MCP config, tool permissions, logs, eval cases, and runbooks before a single custom agent class exists.

That is less exciting than drawing a multi-agent org chart.

It is also harder to obsolete.

## What I Would Build Instead

If I were starting a new agentic workflow today, I would build this first:

```text
workflow-name/
  README.md
  task.md
  context.md
  inputs/
  examples/
  tools.md
  verify.md
  outputs/
```

Then I would add one of four things only when the workflow proves it needs it:

- A script for deterministic work.
- A skill for reusable model-guided work.
- A scoped instruction file when one folder has special rules.
- A subagent when isolation or parallelism is actually useful.

That is the architecture I trust more than a pile of custom agent wrappers.

The agent layer is moving fast because the providers are still figuring out the best harness. Fine. Let them.

The workflow layer is yours.

Design that part well, and every future Codex update, Claude Code update, model release, MCP server, skill, or subagent has a better place to land.

Sources: [OpenAI AGENTS.md guide](https://developers.openai.com/codex/guides/agents-md), [OpenAI Codex skills docs](https://developers.openai.com/codex/skills), [OpenAI Codex subagents docs](https://developers.openai.com/codex/subagents), [OpenAI Codex MCP docs](https://developers.openai.com/codex/mcp), [Claude Code memory docs](https://code.claude.com/docs/en/memory), [Claude Code skills docs](https://code.claude.com/docs/en/skills), and [Claude Code subagents docs](https://code.claude.com/docs/en/sub-agents).

---

---
title: Most Coding Agents Should Be Skills
excerpt: Agent count is not capability. A practical decision rule for when a
  coding workflow needs a real agent, and when it should be a skill.
date: 2026-05-07
author: Jadan Jones
canonicalUrl: https://www.jadanjones.com/blog/most-coding-agents-should-be-skills
sourceUrl: https://www.jadanjones.com/blog/most-coding-agents-should-be-skills
markdownUrl: https://www.jadanjones.com/blog/most-coding-agents-should-be-skills.md
categories:
  - Artificial Intelligence
  - Software Development
  - AI Agents
keywords: AI agents vs skills, Claude Code skills, Codex skills, coding agents,
  subagents, agentic workflows
---

# Most Coding Agents Should Be Skills

I keep seeing AI coding setups described like a spec sheet.

50 agents.

60 agents.

100 agents.

One for React. One for tests. One for docs. One for SEO. One for APIs. One for migrations. One for releases. One for every job title that could plausibly appear in a software team.

It sounds impressive at first.

When I hear it now, I usually think something else: routing problem.

Most of those "agents" are probably skills.

That is not an anti-agent argument. Agents are useful. Subagents are useful. Agent teams can be useful. The issue is classification. A lot of people are turning reusable instructions, checklists, style guides, examples, and review rubrics into separate workers because "agent" feels more advanced than "skill."

That is how you get a complex system without getting a better one.

## The Simple Split

Here is the mental model I would use:

**A skill teaches an agent how to do something.**

**An agent gives work to a separate worker.**

That difference sounds small, but it changes the architecture.

A skill is the right place for reusable expertise: a checklist, a procedure, a style guide, a workflow, a reference document, a set of examples, or a script-backed method the model should know how to apply.

An agent is the right place for an execution boundary: separate context, separate permissions, parallel work, a different model or tool budget, long-running ownership, or a worker that should return a summary without flooding the main thread.

![A compact decision map for choosing between a skill, agent, script, or hook in an AI coding workflow](/images/blog/agents-vs-skills-boundary-map.webp)

If the task just needs better instructions, make it a skill.

If the task needs its own room, make it an agent.

## The Tools Are Already Telling Us This

The public docs for the major coding harnesses are pretty clear on this.

Claude Code's extension overview says skills add reusable knowledge and workflows, while subagents run their own loops in isolated context and return summaries. The same page gives a useful progression: when you keep typing the same prompt or pasting the same playbook, capture it as a skill. When a side task floods the conversation, route it through a subagent.

Codex points the same way. Codex skills are the authoring format for reusable workflows. They use progressive disclosure: Codex sees the skill name, description, and path first, then loads the full `SKILL.md` only when the skill is relevant. Codex subagents, on the other hand, are for specialized parallel work and cost more tokens because each subagent does its own model and tool work.

OpenCode has a similar split. It has a small built-in set of agents, then skills that are discovered from project or global folders and loaded on demand.

Across all three, the pattern is not "make every repeatable thing an agent."

The pattern is: keep a small number of real workers, then give those workers reusable skills.

## Why 80 Agents Gets Fragile

Every agent you add creates a routing decision.

Which agent owns this task?

What if two agents match?

What if the planner sends work to the wrong one?

What if the docs agent needs the API convention that only the API agent knows?

What if the review agent and the test agent disagree because they were given slightly different prompts?

At small scale, this feels manageable. At 50 or 80 named agents, the system starts to behave like an org chart made of prompts. The hard problem becomes deciding who should do the work, not doing the work.

Skills avoid some of that failure mode because they are not separate workers. They are reusable capability modules that the current agent, or a real subagent, can load when needed.

That means your test strategy can be used by the implementer and the reviewer. Your docs style can be used by the main agent and a documentation subagent. Your API conventions can travel with the work instead of living inside one persona.

The skill becomes portable expertise.

The agent remains an execution boundary.

## Use A Skill By Default

I would start with this rule:

Use a skill by default. Promote to an agent only when the boundary pays for itself.

That means a workflow is probably a skill when it looks like this:

| Signal | Better artifact |
| --- | --- |
| I keep pasting the same checklist | Skill |
| I need a repeatable review method | Skill |
| I need reusable domain knowledge | Skill |
| I need examples, templates, or scripts | Skill |
| I need a style guide the agent can apply sometimes | Skill |

A workflow may deserve an agent when it looks like this:

| Signal | Better artifact |
| --- | --- |
| The work would flood the main context | Subagent |
| The worker needs different tool permissions | Subagent |
| The task should run in parallel | Subagent |
| The work needs a different model or cost profile | Agent or subagent |
| The worker has long-running ownership | Agent |
| Multiple workers need to challenge each other | Agent team |

There is also a third bucket people forget: deterministic work.

If the step should happen the same way every time, it may not belong in a skill or an agent. It may belong in a script or hook. Formatting, validation, migrations, generated files, security blocks, and build steps should usually be code.

Do not spend model judgment on work a program can enforce.

## What A Mature Setup Looks Like

For a serious coding harness, I would expect the number of true agents to stay small.

Maybe there is an explorer that reads broadly and returns findings.

Maybe there is a worker that implements.

Maybe there is a reviewer that checks the diff.

Maybe there is a security or QA reviewer with restricted tools.

Maybe there is a researcher for source-heavy work.

Then I would expect a much richer skills layer:

```text
agents/
  explorer.md
  implementer.md
  reviewer.md
  security-reviewer.md

skills/
  api-conventions/
  component-patterns/
  database-migration-checklist/
  docs-style/
  pr-review-rubric/
  release-notes/
  test-plan/
  ux-copy-rules/
```

The agents own execution boundaries.

The skills carry reusable expertise.

That separation makes the setup easier to maintain when Codex, Claude Code, OpenCode, or the model underneath them changes. If the provider improves subagent orchestration, your real agents get better. If the provider improves skill loading, your skill library gets better. You are not stuck maintaining 80 fragile persona prompts that overlap with the platform.

## The Fair Exception

There are real cases for larger agent systems.

Production agent products may need separate runtime roles. Research systems may compare competing hypotheses. Evaluation harnesses may use agents to judge, attack, or verify one another. Large background automation systems may need queues, permissions, logs, and ownership boundaries. Agent teams can make sense when independent communication is part of the work.

So the point is not "never make agents."

The point is: do not use an agent when a skill would give you the same capability with less orchestration.

If your setup has 80 agents, I would ask a boring question before I admire it:

How many of those are actually separate workers, and how many are just reusable instructions with job titles?

That question usually reveals the architecture.

## The Practical Takeaway

Stop counting agents.

Count clean boundaries.

An agent should exist because the work needs isolation, permissions, parallelism, model routing, memory, or independent ownership.

A skill should exist because the work needs a reusable method.

A script or hook should exist because the work needs enforcement.

The strongest AI coding setups will not be the ones with the longest agent roster. They will be the ones where every worker has a reason to exist, and every repeatable method has a clear place to live.

Most teams do not need more agents.

They need better skills.

Sources: [Claude Code extension overview](https://code.claude.com/docs/en/features-overview), [Claude Code skills docs](https://code.claude.com/docs/en/skills), [Claude Code subagents docs](https://code.claude.com/docs/en/sub-agents), [OpenAI Codex skills docs](https://developers.openai.com/codex/skills), [OpenAI Codex subagents docs](https://developers.openai.com/codex/subagents), [OpenCode agents docs](https://opencode.ai/docs/agents), [OpenCode skills docs](https://opencode.ai/docs/skills), [Configuring Agentic AI Coding Tools: An Exploratory Study](https://arxiv.org/abs/2602.14690), and [Agent Skills: A Data-Driven Analysis of Claude Skills](https://arxiv.org/abs/2602.08004).

---

---
title: Karpathy's CLAUDE.md Rules Are a Starting Point, Not a Rulebook
excerpt: "A practical way to think about Karpathy's CLAUDE.md rules: keep the
  baseline, then add only the rules your own AI coding workflow has earned."
date: 2026-04-26
author: Jadan Jones
canonicalUrl: https://www.jadanjones.com/blog/karpathy-claude-md-rules-starting-point
sourceUrl: https://www.jadanjones.com/blog/karpathy-claude-md-rules-starting-point
markdownUrl: https://www.jadanjones.com/blog/karpathy-claude-md-rules-starting-point.md
categories:
  - Artificial Intelligence
  - Software Development
  - AI Agents
keywords: Karpathy CLAUDE.md rules, Claude Code, AI coding agents, CLAUDE.md,
  agentic coding, AI developer workflow
---

# Karpathy's CLAUDE.md Rules Are a Starting Point, Not a Rulebook

A recent post connected Karpathy's Claude Code advice to a small `CLAUDE.md` template from Forrest Chang. The useful part is not the template itself. It is the failure pattern underneath it.

The four rules are useful because they hit very familiar failure modes:

- Think before coding.
- Prefer simple code.
- Make focused changes.
- Define success before running off to implement.

I would keep those four. I just would not worship them.

But I do not think the real lesson is "copy these four rules" or "add eight more." The deeper lesson is that a good `CLAUDE.md` should behave more like a bug report than a manifesto.

Every rule should exist because the agent has made that mistake before. One scar, one rule.

## Why CLAUDE.md Matters

`CLAUDE.md` is memory, not law. Claude Code loads it as context, but the model still has to choose well.

That matters. A `CLAUDE.md` file does not turn a coding agent into a deterministic system. It changes the odds. It makes the right behavior easier for the model to find.

Short, specific rules beat long preference dumps. "Run `npm test` before saying the change is complete" is better than "be careful with tests." One can be checked. The other is mood lighting.

Karpathy's four rules work because they are concrete. They do not ask the model to "act senior." They describe behaviors: surface assumptions, avoid speculative abstractions, do not touch unrelated code, and define success criteria.

## The Useful Mental Model

I would think about `CLAUDE.md` like this:

![A visual loop for turning repeated Claude Code mistakes into narrow CLAUDE.md rules](/images/blog/karpathy-claude-md-rule-loop.webp)

| Agent failure | Better instruction | Proof it worked |
| --- | --- | --- |
| The agent guesses instead of asking | State assumptions before coding | The final answer names the assumption or asks for missing context |
| The agent edits too much | Tie every changed line to the request | The diff contains no unrelated cleanup |
| The agent says "done" too early | Show verification before claiming success | The final answer includes the test, command, or limitation |
| The agent repeats an old mistake | Add one narrow rule for that failure | The same mistake stops showing up in later sessions |

That table is the reason I like the "bug report" framing. It keeps the file grounded in behavior you can observe.

## The Part I Would Be Careful With

The source post makes a strong benchmark-style claim about mistake rates dropping after adding more rules. I would treat numbers like that as interesting, but not universal.

Agent performance depends on the codebase, task type, model version, tools, test suite, and how strict the reviewer is. A rule set that looks amazing on feature work may do very little for debugging infrastructure. A rule that saves a monorepo might slow down a small prototype.

There is also early research suggesting that agent rules can help, but not always in the way we expect. One April 2026 paper found that rules improved coding-agent performance, while also finding that negative constraints were the clearest individually useful rule type. That makes me even more cautious about treating any one template as magic. The safest rules are usually guardrails: do not guess silently, do not claim done without verification, do not edit unrelated code.

So I would not copy a 12-rule template and call it done. I would start with Karpathy's baseline, then add rules only when they map to real mistakes in my own workflow.

The useful question is not "how many rules should be in `CLAUDE.md`?" It is "what mistakes am I tired of correcting twice?"

## Four Rules I Would Add

### Read Before Writing

Focused changes are good, but they can be misunderstood. "Do not touch unrelated code" does not mean "ignore surrounding code." Before adding a function, the agent should read the exports, the caller, and any nearby utility that looks relevant. A lot of bad AI code is not wildly wrong. It is redundant. It recreates something that already existed 40 lines away.

```text
Failure: agent adds a second version of logic that already exists.
Rule: read exports, callers, and nearby utilities before adding code.
Check: name the existing pattern before changing the file.
```

### Keep Deterministic Decisions Out Of The Model

Use the model for judgment-heavy work: summarizing, classifying messy text, drafting, explaining, extracting from loose input. Do not use it for retry logic, status-code routing, pure transformations, or anything a normal function can decide. If a `503` status code already tells you the retry policy, code should handle that. The model should not be invited into every branch of the system just because it can produce an answer.

```text
Failure: agent makes a probabilistic call where code should decide.
Rule: do not use the model for routing, retries, status handling, or pure transforms.
Check: if a deterministic input answers the question, code owns the branch.
```

### Do Not Claim Done Without Proof

AI agents are very good at producing tests that pass. That is not the same as producing tests that matter. A useful test should fail when the actual business rule is broken. If the test only proves that a function returns something, it may be giving the model a false sense of completion.

The same goes for finished work. "Done" should come with the command that ran, the test result, the screenshot, or the reason verification was not possible.

```text
Failure: agent says done because the code looks plausible.
Rule: do not claim completion without verification evidence.
Check: name the command, test, screenshot, or limitation in the final answer.
```

### Fail Loudly

This is the one I care about most. The expensive failures are not the obvious ones. They are the failures that look like success. A migration that skipped records. A test suite that passed with skipped tests. A feature that works for the happy path but never checked the edge case you asked about.

When the agent is not sure, it should say that. When it skipped something, it should name it. When it could not verify the result, it should not imply that the work is done.

```text
Failure: agent hides uncertainty behind a confident summary.
Rule: do not bury skipped work, failed checks, or unverified edge cases.
Check: the final answer says exactly what was not verified.
```

## The Maintenance Rule

One meta-rule matters most: do not let `CLAUDE.md` become a junk drawer.

If a rule does not prevent a mistake, remove it. If a formatter, type checker, test, hook, or CI job can enforce it, move the rule down into tooling instead of spending model attention on it. If a rule only applies to one part of the codebase, move it closer to that part of the codebase. Claude Code supports project rules under `.claude/rules/`, including path-scoped rules, which is a cleaner fit for large projects than forcing every instruction into one file.

This is where old agent rules start to rot: stale test commands, folder rules for code that moved, preferences nobody remembers writing. A small repo can get away with one short `CLAUDE.md`. A larger system needs layers: project-level instructions, path-specific rules, and sometimes skills for repeatable workflows.

The goal is not to make the instruction file impressive. The goal is to make the next agent session less annoying.

## The Practical Takeaway

Karpathy's rules are valuable because they describe the failures almost everyone sees when they first use AI coding agents seriously. The model guesses. It overbuilds. It edits too much. It loses track of what success means.

That makes the four-rule baseline worth keeping.

But the best `CLAUDE.md` file is not the longest one or the most viral one. It is the one that reflects your actual scars from working with agents in your own code.

Start with the baseline. Watch the mistakes. Add a rule only when the same failure shows up again.

That is how `CLAUDE.md` becomes engineering documentation instead of prompt cargo culting.

Sources: [the post that sparked this](https://x.com/mnilax/status/2053116311132155938?s=46), [Forrest Chang's Karpathy-inspired CLAUDE.md](https://raw.githubusercontent.com/forrestchang/andrej-karpathy-skills/main/CLAUDE.md), [Claude Code's memory documentation](https://code.claude.com/docs/en/memory), and [research on agent rules](https://arxiv.org/abs/2604.11088).

---

---
title: Why Employees Ignore Expensive AI Tools at Work
excerpt: Companies keep buying AI tools, then wonder why employees do not use
  them. The adoption problem is usually workflow design, not resistance.
date: 2026-04-12
author: Jadan Jones
canonicalUrl: https://www.jadanjones.com/blog/why-employees-ignore-expensive-ai-tools-at-work
sourceUrl: https://www.jadanjones.com/blog/why-employees-ignore-expensive-ai-tools-at-work
markdownUrl: https://www.jadanjones.com/blog/why-employees-ignore-expensive-ai-tools-at-work.md
categories:
  - Artificial Intelligence
  - Business
  - AI Adoption
keywords: AI adoption in the workplace, corporate AI adoption, employees not
  using AI tools, enterprise AI rollout, AI workflow adoption
---

# Why Employees Ignore Expensive AI Tools at Work

A familiar pattern is starting to show up in AI rollouts.

A business owner spends thousands of dollars setting up an internal AI system. Maybe it is a chatbot. Maybe it is a knowledge assistant. Maybe it is a workflow tool with a cleaner interface than the old process.

Nobody used it.

The system is live. The money is spent. Leadership believes it can help. But employees keep doing their work the old way until usage becomes mandatory and starts showing up in performance tracking.

That is a rough way to learn an expensive lesson: buying AI is much easier than changing how people work.

I do not think that is a healthy way to roll out AI. If a tool only gets used after leadership ties it to performance scores, that is not proof of product-market fit inside the company. It is a signal that the original rollout missed the real workflow.

## The Tool Is Rarely the Whole Problem

When leaders talk about failed AI adoption, they often frame it as employee resistance: people are scared of replacement, too busy to learn, or too attached to the old process.

Sometimes that is true, but I think it is too easy.

Gartner found something more interesting in a 2025 employee survey: many employees who can use AI still do not use it because their coworkers are not using it either. Gartner's read was that the root issue is often rushed implementation with too little attention paid to workforce impact.

That sounds right. Most employees are not sitting around hoping the company wastes money. They are responding to incentives, habits, risk, and pressure.

If the old workflow is still accepted, the new AI tool is optional.

If the AI output is not trusted, the employee has to double-check everything.

If nobody knows when the tool should be used, people will avoid the extra step.

If managers do not model the behavior, the tool feels like another executive experiment.

That is not a software problem. That is an adoption design problem.

## Implementation Is Not Adoption

There is a quiet gap between "we rolled out AI" and "AI changed the way work gets done."

Gallup's 2026 AI indicator shows this clearly. As of February 2026, 41% of U.S. employees said their organization had integrated AI tools, but only 28% used AI a few times a week or more. Gallup called this an implementation-adoption lag: implementation does not guarantee use.

That gap matters because leaders can easily mistake deployment for progress.

Licenses are active, training happened, and the dashboard looks alive. None of that proves the work changed.

If the answer is no, the AI system is probably sitting beside the workflow instead of inside it.

## Mandatory Usage Is A Warning Sign

I understand the frustration. If a company has already paid for a system and nobody is using it, leadership will start looking for levers. Performance measurement is an obvious lever.

But that should be a last-resort warning sign, not the default adoption strategy.

You can get people to open the tool. You can get them to paste prompts. You can get them to include an AI step in a checklist. But if it turns a 12-minute policy lookup into another review step, mandatory usage becomes theater. If it turns that same lookup into a 30-second answer people trust, they will notice.

This is the measurement trap. If the company rewards tool usage instead of better work, employees will optimize for the metric. Leaders might see more prompts, more logins, and more AI-assisted outputs while the actual business process stays the same.

That is why I would be careful about making AI usage itself part of performance measurement. It can create compliance without trust. It can also punish employees for noticing that the tool is slow, risky, or poorly matched to the job.

Measure the work instead: cycle time, rework, customer response time, handoff clarity, error rate, or decision quality. If those do not improve, more tool activity is just activity.

The real question is not "how do we force employees to use this?"

It is "what part of the job becomes obviously better when this tool is used?"

That is where many AI rollouts get thin. The business buys a general-purpose system, then expects employees to discover the use cases on top of their existing work.

Some will. Most will not.

## Start With Workflow Pain, Not AI Ambition

The companies that get adoption right usually do not start with "we need AI."

They start with a broken or expensive part of work:

- Customer questions take too long to triage.
- Sales notes never make it into the CRM cleanly.
- Support agents rewrite the same responses every day.
- QA teams spend too much time summarizing failures.
- Managers cannot find the latest policy or client context.

Then they ask: can AI remove friction from this specific workflow?

That is a much better starting point than buying a central chatbot and hoping people find a reason to care.

![A workplace AI adoption loop showing how companies should move from workflow pain to manager support, visible proof, and real adoption](/images/blog/ai-adoption-loop.webp)

The difference is ownership. A generic AI tool belongs to nobody. A workflow improvement belongs to a team with a problem.

The second difference is subtraction. If AI takes over part of the work, something old should get smaller or disappear. Otherwise the company has not improved the workflow. It has added another step.

## Managers Matter More Than The Announcement

Gallup's data also points to the role managers play. In one 2026 survey, frequent AI use was much higher when employees said tools were integrated into workflows, managers actively supported use, experimentation was encouraged, and policies were clear. Employees with strong manager support reported frequent use at 78%, compared with 44% without it.

Microsoft's 2026 Work Trend Index points in the same direction: organizational factors such as culture, manager support, and talent practices accounted for more than twice the reported AI impact of individual effort.

That tracks with real workplace behavior.

Employees take cues from the people who evaluate them. If managers do not know how the tool fits into the work, employees will not take the risk of changing their process. If managers use AI casually but never clarify standards, employees may worry about quality, privacy, or whether AI use will be judged later.

This is why AI adoption cannot live only with the CEO, IT, or an innovation team. Line managers need to translate AI into local work:

- When should we use it?
- What output is acceptable?
- What still needs human review?
- What data should never go into the tool?
- What does good usage look like in this team?

Without that layer, adoption becomes vague. Vague tools do not become habits.

## The Practical Takeaway

I do not think CEOs should stop investing in AI. That would be the wrong lesson.

The better lesson is that AI spend should follow workflow clarity. Before buying or building an AI system, leadership should be able to answer five questions:

| Question | Why it matters |
| --- | --- |
| What workflow is this changing? | Prevents the tool from becoming a side quest. |
| Who owns adoption after launch? | Keeps AI from becoming an IT-only project. |
| What will employees stop doing? | Creates room for the new behavior. |
| How will managers reinforce usage? | Turns adoption into team practice. |
| What evidence proves value? | Separates real impact from login activity. |

If those answers are weak, the company is not ready for a big rollout. Start smaller. Pick one team. Pick one painful workflow. Measure whether the work actually improves.

AI adoption is not about convincing employees to admire the technology. Adoption starts when the old way becomes the annoying way.

Sources: [Gartner on employee AI adoption](https://www.gartner.com/en/newsroom/press-releases/2025-12-16-gartner-hr-survey-finds-65-percent-of-employees-are-excited-to-use-ai-at-work), [Gallup's 2026 AI workplace indicator](https://www.gallup.com/699797/indicator-artificial-intelligence.aspx), [Gallup on manager support and AI adoption](https://www.gallup.com/workplace/704252/workplace-separates-adopters-holdouts.aspx), and [Microsoft's 2026 Work Trend Index](https://www.microsoft.com/en-us/worklab/work-trend-index/agents-human-agency-and-the-opportunity-for-every-organization).

---

---
title: How Cheaply Can You Build an App? I'm Testing It
excerpt: Exploring the cost breakdown and affordability of building an app using
  modern tools, no-code platforms, and efficient infrastructure.
date: 2025-02-01
author: Jadan Jones
canonicalUrl: https://www.jadanjones.com/blog/how-cheap-can-you-build
sourceUrl: https://www.jadanjones.com/blog/how-cheap-can-you-build
markdownUrl: https://www.jadanjones.com/blog/how-cheap-can-you-build.md
categories:
  - Artificial Intelligence
  - Software Development
  - Startup
keywords: AI App, Affordable AI, Low Cost Software, Indie Developer, Budget,
  Startup, Cost Analysis
---

# How Cheaply Can You Build an App? I'm Testing It

### How Cheaply Can You Build an AI App? I'm Testing It

Building software used to be expensive. Even for a small SaaS, mobile app, or microstartup, you needed engineers for backend and frontend, designers to make it look good, and DevOps people to keep things running. Infrastructure costs alone could eat up your budget before you even had your first user.  

But things have changed. With consumer AI tools and no-code or low-code platforms, solo developers can build serious products for a fraction of what it used to cost. The barrier to entry is lower than ever, and I wanted to see just **how cheap** you can realistically build and launch an AI-powered app.  

So I broke down every dollar I'm spending.  

## My AI App Budget  
Here's what I'm using and how much it costs me per month:  

- **Engineering**: Cursor, GitHub (free)  
- **AI Models**: OpenAI ($20)  
- **Infrastructure**: Heroku, Netlify, Docker ($36)  
- **Design**: v0, Figma, Canva (free)  
- **Domain**: Namecheap ($2.70)  
- **Website**: Framer ($3.75 with promo code)  
- **Blog**: HubSpot (free)  
- **Session Replay**: LogRocket (free)  
- **Analytics**: Google Analytics (free)  
- **Monetization**: AdSense, Stripe (free)  

**Total: $62.45/month**  

That's a full AI-powered app—hosted, designed, and ready to monetize for less than what most people spend on subscriptions they forget to cancel.  

## Why This Matters  
This isn't just about saving money. It's about proving that AI and modern tools have changed how software is built.  

You still need a team for **large-scale** products, but for small and medium-sized software like SaaS, microSaaS, or AI-driven tools, a **solo founder or a tiny team** can get something off the ground at an insanely low cost. AI models now handle complex logic that used to require backend developers. No-code and automation tools replace manual work. Hosting is cheap, and design tools are more accessible than ever.  

Most people assume AI apps need expensive infrastructure, but unless you're running custom LLMs or processing huge datasets, you don't. If you're building a simple AI-powered SaaS, your biggest cost is likely API usage. And even that can be optimized.  

## What's Next  
The challenge isn't just building something cheap. It's making something people actually want and seeing if it can pay for itself.  

Now that I have this setup in place, I'm focused on getting traction and seeing if this $62.45/month experiment can turn into a profitable product. If I can make this work at this price point, that changes the game for indie developers and solo founders.  

If you know any **cheap but powerful tools** that could help me stretch this budget even further, let me know. I'm all about pushing this as far as possible.

---

---
title: Is DeepSeek R1 the OpenAI Killer? A Comprehensive Performance Analysis
excerpt: An in-depth comparison of DeepSeek R1 vs OpenAI's GPT models, analyzing
  performance, cost, and practical applications for developers and AI
  enthusiasts.
date: 2025-01-26
author: Jadan Jones
canonicalUrl: https://www.jadanjones.com/blog/deepseek-r1-the-openai-killer
sourceUrl: https://www.jadanjones.com/blog/deepseek-r1-the-openai-killer
markdownUrl: https://www.jadanjones.com/blog/deepseek-r1-the-openai-killer.md
categories:
  - Artificial Intelligence
  - Machine Learning
  - Open Source
  - Development
keywords: DeepSeek R1, OpenAI, GPT, AI Development, Open Source AI, Machine
  Learning, AI Comparison
---

# Is DeepSeek R1 the OpenAI Killer? Maybe Not—But It's Insanely Close

Over the past few months, **DeepSeek R1** has created quite a stir in the AI space, particularly in the open-source community. **DeepSeek R1** is an open-source model that's incredibly close to dethroning the major AI players, including **OpenAI's GPT o1** and **Anthropic's Claude**, especially given its price and the fact that it only took $5 million to build. While it may not be perfect, this model offers an impressive performance at a fraction of the cost.

If you're into **AI development** or **coding with AI**, you're going to want to hear about how **DeepSeek R1** stacks up against the likes of **GPT o1**—the model that costs around $200/month for the base version—and why it's making waves in the **AI landscape**.

### DeepSeek R1 Performance vs OpenAI: Coding and Reasoning Capabilities

Let's talk about **DeepSeek R1's performance** in coding challenges and reasoning tasks. I've personally run a variety of tests, and the results are impressive. From creating interactive web pages to building fully functional apps, **DeepSeek R1** held its own. I even tested it with complex integrations, like connecting to the **Replicate API for image generation**—and it delivered.

When it comes to reasoning abilities, **DeepSeek R1** really shines. The model's ability to work through complex tasks like logic puzzles and math problems is remarkable. However, it's important to mention that, like most AI models, **DeepSeek R1** does experience occasional **hallucinations** in its reasoning. Sometimes it goes off-track mid-task, and while it's not a deal-breaker, it does affect the output quality. In my testing, it typically handled problems well, but it had those moments where, toward the end of its reasoning process, it lost its way. 

Still, these occasional kinks don't overshadow the model's performance—**DeepSeek R1** is genuinely impressive, especially given it's open-source and incredibly affordable.

### **Open-Source Advantage: Why Local Use is a Game-Changer**

Here's the kicker: **DeepSeek R1** is **open-source**, and as an **AI enthusiast**, I find that incredibly exciting. The ability to access, modify, and improve the model is a huge plus. But, there's a big caveat—running **DeepSeek R1** via the web interface is risky when it comes to **data privacy**.

DeepSeek R1 is owned by a company in China, and if you're running the model on their cloud infrastructure, your data could be sent overseas. If you're concerned about that, I recommend running **DeepSeek R1 locally** instead. With local installation, you're keeping your data in your hands.

But there's another catch: running the largest models locally can be challenging due to their size and parameter requirements. To get the best performance, you'll need a machine with substantial power. I've been running it on my **MacBook Pro M1**, and while it performs decently for smaller tasks, the largest models require more horsepower than my setup can provide. 

That said, a tool **LM Studio** can help you run it locally, even if you don't have access to the most powerful hardware.

### DeepSeek R1: Close to GPT o1 But at a Fraction of the Cost

Now, let's get into what really sets **DeepSeek R1** apart from its competitors: **the price**. Running **GPT o1** (the standard model) from **OpenAI** costs around $200 a month. DeepSeek R1? It's **up to 90% cheaper**. That's a massive difference. And despite the price gap, **DeepSeek R1** holds its own in performance when compared to **GPT o1**, especially in coding tasks. It's truly mind-blowing that this **open-source AI model**—built on a fraction of the budget—can deliver results so close to what we're used to from the likes of **OpenAI** and **Anthropic**.

I mean, at the **$5 million development cost** DeepSeek's creators claim, it's genuinely impressive. And while I'm a bit skeptical about the exact numbers they're reporting (could there be some magic behind the curtain?), the engineering behind this model is undeniable.

### What's Next for DeepSeek R1?

**DeepSeek R1** might not be the **OpenAI killer** just yet, but if it continues evolving at this rate, it's definitely making **OpenAI** and **Anthropic** rethink their models. And given that it's **open-source**, this is just the beginning. As more developers get their hands on it, there's no telling how much better it will become.

For now, if you're in **AI development** or just looking to experiment with something cheaper than the **high-end commercial models** like **GPT o1** or **Claude**, **DeepSeek R1** is worth your attention. It's not perfect, but considering its price and open-source nature, it's one of the best alternatives I've seen so far. 

### Conclusion: Should You Run DeepSeek R1?

In summary, if you're a developer, AI researcher, or enthusiast looking for an **affordable, powerful open-source AI model**, **DeepSeek R1** is a great choice. Yes, it has its quirks—there are moments of hallucination in its reasoning process—but those are relatively minor in the grand scheme of things. When you consider its price and performance, it's clear that **DeepSeek R1** is making a mark. 

**If you're concerned about data privacy,** I highly recommend running it locally, even though the **larger models** may require significant resources. But overall, for an **open-source model**, it's performing at a level that's **very close** to what you'd expect from **GPT o1** and **Claude**—and at a fraction of the cost. 

If **DeepSeek R1** keeps pushing forward, we might just see an open-source revolution that challenges the commercial giants.

---

---
title: "Complete Guide: Improving SEO for Next.js Blog with Contentlayer"
excerpt: A comprehensive guide to optimizing your Next.js blog's SEO using
  Contentlayer, including metadata optimization, structured data, and
  performance improvements.
date: 2024-05-30
author: Jadan Jones
canonicalUrl: https://www.jadanjones.com/blog/Improving-SEO-for-NextJs-Blog
sourceUrl: https://www.jadanjones.com/blog/Improving-SEO-for-NextJs-Blog
markdownUrl: https://www.jadanjones.com/blog/Improving-SEO-for-NextJs-Blog.md
categories:
  - Development
  - Web Development
  - Next.js
  - Technical SEO
keywords: Next.js SEO, Contentlayer, Blog Optimization, Technical SEO, Web
  Development, Meta Tags, Structured Data
---

# Improving SEO for your Contentlayer Nextjs Blog

You've built a sick blog site with Next.js, Contentlayer, and Tailwind CSS - sweet! If you need a guide on how to build a blog check out my blog post on [How to build a blog with Nextjs and contentlayer](https://www.jadanjones.com/blog/nextjs-contentlayer-modern-blog) But now it's time to make sure your hard work gets seen by making your site super search engine friendly.

In this guide, I'll walk through optimizing your Next.js blog for SEO. We'll cover the essentials like dialing in your metadata, implementing structured data, optimizing images for performance, and more tricks to make Google and other search engines fall in love with your content.

Let's get those search engine bots crawling!

## Step 1. Define Metadata

When defining metadata for a blog post in a Next.js application, the Metadata API provides a powerful way to customize the HTML head section of your pages. This metadata can be leveraged to improve the search engine optimization (SEO) of your website and enhance the way your content is displayed when shared on social media platforms.

The key aspects of the Metadata API that you can focus on for your blog post include:

1.  **Page Title**: This is the title of the page that will be shown in the browser tab and search engine results.
2.  **Description**: A brief summary of the content on the page, which can be used by search engines and social media platforms to provide context about your post.
3.  **Open Graph (OG) Tags**: These are a set of metadata properties defined by the Open Graph protocol, allowing you to control how your content is displayed when shared on platforms like Facebook, LinkedIn, and WhatsApp.
4.  **Twitter Cards**: These are a set of metadata properties specific to Twitter, enabling you to customize the way your content is presented when shared on the Twitter platform.

By properly defining these metadata properties, you can ensure that your blog post is presented in an optimal way on search engines and social media, leading to increased visibility and engagement with your audience.

To setup metadata in our blog we're going to implement the following code in your [slug]/page.js file or whatever file your individual blogs are generated:
```js
import { Metadata } from 'next';
 
export  const  generateMetadata  = ({ params }) => {

const  post  =  allPosts.find((post) =>  post._raw.flattenedPath  ===  'blog/'  +  params.slug);

  if (!post) {
    return {};
  }
 
  const { excerpt, title, date } = post;
 
  const description = excerpt;
 
  const ogImage = {
    url: `${process.env.HOST}/images/blog/${params.slug}/og.png`,
  };
 
  return {
    title,
    description,
    openGraph: {
      type: 'article',
      url: `${process.env.HOST}/blog/${params.slug}`,
      title,
      description,
      publishedTime: date,
      images: [ogImage],
    },
    twitter: {
      title,
      description,
      images: ogImage,
      card: 'summary_large_image',
    },
  };
}
```
Ensure your blog images are placed in your `public/blog/` folder.

### Structured data


Structured data is a way to organize and provide context to the information on a web page. It uses standardized formats like JSON-LD, RDFa, or microdata to mark up the data in a way that makes it easier for search engines and other applications to understand the content and its meaning.

By implementing structured data, website owners can improve their chances of appearing in enhanced search results, known as rich snippets. These are the special features that stand out in search engine results pages, such as product information, reviews, event details, and more. When search engines can easily interpret the structured data on a site, they are more likely to display that information prominently, boosting the website's visibility and helping users find the most relevant and useful content.

Structured data is an important part of search engine optimization (SEO) because it allows websites to provide search engines with a clear, machine-readable interpretation of the page's content. This helps search algorithms better understand the context and relevance of the information, leading to more accurate and valuable search results for users. By taking the time to properly implement structured data, website owners can gain a significant advantage in how their content is discovered and presented online.
```bash
npm  install  schema-dts
```
[schem-dts](https://www.npmjs.com/package/schema-dts) - Schema-dts is a library that provides type definitions for Schema.org, a widely-used set of semantic markup standards, making it easier to work with structured data in a project.

Add this code to your `[slug]/page.js`:
```js
export default async function Page({ params }) {
  const post = allPosts.find((post) => post._raw.flattenedPath === 'blog/' + params.slug);

  if (!post) {
    return notFound();
  }

  const structuredData = {
    '@context': 'https://schema.org',
    '@type': 'Article',
    headline: post.title,
    url: `${process.env.HOST}/blog/${params.slug}`,
    image: {
      '@type': 'ImageObject',
      url: `${process.env.HOST}/images/blog/${params.slug}.webp`,
    },
    description: post.excerpt,
    datePublished: post.date,
    publisher: {
      '@type': 'Person',
      name: 'Jadan Jones',
      url: process.env.HOST
    },
    author: {
      '@type': 'Person',
      name: 'Jadan Jones',
      url: process.env.HOST
    },
  };

  const jsonLd = {
    '@context': 'https://schema.org',
    '@graph': [structuredData],
  };

  return (
    <>
    <script
        type="application/ld+json"
        dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
      />
      //Your Blog content or Component goes here
    </>
  );
}
```
## Step 2: robots.txt

A  `robots.txt`  file is a standard text file that provides instructions to web crawlers and bots, such as search engine indexing agents, on which parts of a website should be crawled and indexed.

In a Next.js application, it's recommended to place the  `robots.txt`  file within the  `app`  folder. This allows you to easily manage the instructions for web crawlers, ensuring your website's content is properly discovered and indexed by search engines. A well-crafted  `robots.txt`  file is essential for search engine optimization, as it helps control the visibility and indexing of your website's content.

```
User-Agent: *
Allow: /
Disallow: /private/
Sitemap: https://{yourdomain}/sitemap.xml
```

## Step 3: sitemap.xml

A sitemap.xml file acts as a navigational guide for search engines, helping them discover and understand the key pages on your website. This improves the website's visibility and ranking in search engine results pages (SERPs).

In the context of a Next.js application, it's recommended to create a  `sitemap.js`  file within the  `app`  folder to manage the sitemap generation. This file will allow you to easily add and control the pages that should be included in the sitemap.

By creating a  `sitemap.js`  file in the `app` folder and populating it with your website's pages, you're providing search engines with a clear roadmap of the important content on your site. This can lead to better indexing and higher visibility in search results, ultimately driving more traffic and engagement to your website.

```js
//sitemap.js
import { allPosts } from 'contentlayer/generated';

const postsSitemap = allPosts.map((post) => ({
  url: `${process.env.HOST}/${post._raw.flattenedPath}`,
  lastModified: post.date
}));

export default function sitemap() {
  return [
    {
      url: process.env.HOST,
      lastModified: new Date(),
    },
    {
      url: `${process.env.HOST}/blog`,
      lastModified: new Date(),
    },
    {
      url: `${process.env.HOST}`,
      lastModified: new Date(),
    },
    ...postsSitemap,
  ];
}
```
## Conclusion
Alright, let's talk about this article - it's a total game-changer for anyone running a blog on Next.js. Not only does it make writing and maintaining your content a breeze, but it also dives deep into the world of SEO. We're talking optimizing for search engines, boosting your site speed, and unleashing the full potential of Next.js to automate all those crucial SEO tasks. By the end, your blog will be primed for search engine glory, attracting readers from far and wide with its well-optimized content and lightning-fast performance.

---

---
title: "When to Automate Test Cases: A Comprehensive Decision Guide"
excerpt: Learn the key factors for deciding test automation, including ROI
  analysis, test complexity, and maintenance considerations for QA professionals
  and developers.
date: 2024-01-20
author: Jadan Jones
canonicalUrl: https://www.jadanjones.com/blog/When-to-automate-test-cases
sourceUrl: https://www.jadanjones.com/blog/When-to-automate-test-cases
markdownUrl: https://www.jadanjones.com/blog/When-to-automate-test-cases.md
categories:
  - Software Testing
  - Test Automation
  - Quality Assurance
  - Development
keywords: test automation, QA automation, software testing, test cases,
  automation ROI, testing strategy
---

# Shift Your Mindset: Automate Requirements, Not Test Cases

As software testing professionals, we often get caught up in the mindset of automating manual test cases. However, this approach can lead to misunderstandings, frustrations, and an incomplete representation of our application's functionality. It's time to shift our perspective and focus on automating requirements, not just test cases.

### The Problem with Automating Test Cases  
When we approach test automation with the mindset of translating manual test cases into code, we're missing the bigger picture. A manual test case is a specific set of steps designed to verify a particular functionality, but it doesn't necessarily capture the underlying requirement. By solely focusing on automating these test cases, we risk creating a fragile and inflexible automation suite that is tightly coupled to the implementation details.

### Automating Requirements: A Different Approach  
The key to effective test automation is to shift our focus from automating test cases to automating requirements. A requirement is the core functionality that your application needs to deliver, and it is the foundation upon which your entire system is built. By automating the requirements themselves, we can create a living, always up-to-date representation of how your application should work.

This approach offers several benefits:

1.  Increased Reliability: Automated tests that verify requirements are less likely to break when implementation details change, as they are focused on the core functionality.
2.  Improved Traceability: Linking automated tests directly to requirements makes it easier to understand the purpose and scope of each test, enhancing overall traceability.
3.  Better Team Collaboration: When the entire team understands that the automation effort is focused on verifying requirements, it builds trust and aligns everyone's expectations.
4.  Efficiency in Maintenance: As requirements evolve, the automated tests can be updated accordingly, ensuring the automation suite remains relevant and effective.

### Putting It into Practice  
To shift your mindset from automating test cases to automating requirements, follow these steps:

1.  Identify the core requirements of your application.
2.  For each requirement, create an automated test that verifies its functionality.
3.  Ensure these automated tests are focused, fast, and reliable, providing a clear proof of the requirement's fulfillment.
4.  Continuously update the automated tests as requirements change, maintaining a living representation of your application's functionality.

### Conclusion
By embracing this approach, you'll not only improve the effectiveness of your test automation efforts but also foster a better understanding and trust within your team. Remember, the ultimate goal is to ensure the core requirements of your application are thoroughly tested and validated, regardless of the specific implementation details.

---

---
title: "Building a Modern Blog with Next.js 13 and Contentlayer: Step-by-Step Guide"
excerpt: Complete tutorial on creating a high-performance blog using Next.js 13,
  Contentlayer, and MDX, with modern features and optimal developer experience.
date: 2024-01-15
author: Jadan Jones
canonicalUrl: https://www.jadanjones.com/blog/nextjs-contentlayer-modern-blog
sourceUrl: https://www.jadanjones.com/blog/nextjs-contentlayer-modern-blog
markdownUrl: https://www.jadanjones.com/blog/nextjs-contentlayer-modern-blog.md
categories:
  - Web Development
  - Next.js
  - React
  - Frontend Development
keywords: Next.js 13, Contentlayer, MDX blog, React blog, static site
  generation, web development tutorial
---

# Harnessing the power of Markdown with Contentlayer and Nextjs to build a modern blog

Creating a blog is a great way to share your knowledge and expertise with others. However, setting up a full-stack blog can be daunting if you're just getting started with web development. In this article, I'll show you just how easy it is to build a full-fledged blog using some of the most popular tools available today.

We'll be using NextJS for the frontend, Contentlayer to handle Markdown content, and Tailwind CSS for styles. And for hosting? We'll deploy everything to Vercel with a single command. By the end, you'll have a fully functioning blog up and running in no time!

## Why NextJS?
As a React framework, NextJS makes building powerful websites and applications incredibly quick and simple. Some key advantages for blogging include:

- Static generation - NextJS can pre-render pages at build time, providing lightning fast load times and optimizing sites for SEO. Perfect for blogs!
- File-based routing - Pages are defined as .js or .jsx files in the pages directory, keeping routing clean and intuitive.
- Image optimization - NextJS can automatically optimize images, reducing payload sizes.
- Easy deployment - Vercel integration makes deploying NextJS sites a breeze, as we'll see later!

So in summary, NextJS gives us a simple fast foundation to build our blog upon.

## Enter Contentlayer

While Next.js 14 has built-in support for Markdown, we're going to take things up a notch by introducing ContentLayer into the mix. ContentLayer is a powerful content management system that seamlessly integrates with Next.js, making it a breeze to fetch and render Markdown content on your site. 

With ContentLayer, you can organize your content into a structured data model, making it easier to manage and maintain your blog posts. There is no need to involve complex workflows and heavyweight dependencies that will slow down your client. Through the magic of Content Layer, all Markdown processing occurs at build time - eliminating client-side weight for maximum performance!

#### Contentlayer provides:
**Simple Setup** - Contentlayer requires just a config file and basic CLI usage. No deploying additional services.

**Fast Performance** - By avoiding a database or API server, Contentlayer's static site integration is faster and more affordable to run.

**Lower Resource Usage** - Since content is stored in files rather than a database, Contentlayer doesn't require as much memory, CPU or network bandwidth.

**Easy Migration** - If needs change, content can be exported to a new platform since it's stored in plain Markdown files without proprietary lock-in.

**Hassle-Free Maintenance** - We don't have to worry about database administration, upgrades or backups when using Contentlayer.

## Soooo Let's get started!

#### Step 1: Installing Next.js and Tailwind 
First things first, we need to set up our Next.js project. Open your terminal and run the following command to create a new Next.js app and ensure you select yes for tailwind css: 

```bash
npx create-next-app@latest my-blog-site
```
On installation, you'll see the following prompts (select accordingly):
```bash
What is your project named? my-blog-site
Would you like to use TypeScript? No
Would you like to use ESLint? Yes
Would you like to use Tailwind CSS? Yes
Would you like to use `src/` directory? Yes
Would you like to use App Router? (recommended) Yes
Would you like to customize the default import alias (@/*)? No
```
This will create a new directory called `my-blog-site` with all the necessary files and dependencies for a basic Next.js project.

Next you will navigate to your new project folder
```bash
cd my-blog-site
```
#### Step 2: Installing Dependencies
### Important
Before installing dependencies add this code to your package.json file:
```json
"overrides": {
	"next-contentlayer": {
	"next": "$next"
	}
}
```
`next-contentlayer` has recently become unmaintained, this means a package upgrade was not created for nextjs14. However we can still take advantage of its features by overriding the dependency. 

Your full file should look something like this:
```json
"overrides": {
	"next-contentlayer": {
	"next": "$next"
	}{
    "name": "my-blog-site",
    "version": "0.1.0",
    "private": true,
    "scripts": {
        "dev": "next dev",
        "build": "next build",
        "start": "next start",
        "lint": "next lint"
    },
    "dependencies": {
        "next": "14.2.3",
        "react": "^18",
        "react-dom": "^18"
    },
    "devDependencies": {
        "eslint": "^8",
        "eslint-config-next": "14.2.3",
        "postcss": "^8",
        "tailwindcss": "^3.4.1"
    },
    "overrides": {
        "next-contentlayer": {
            "next": "$next"
        }
    }
}
}
```
Next, we'll install the required dependencies for our blog site. Navigate to the project directory and run the following command:
```bash
npm install next-contentlayer contentlayer @tailwindcss/typography remark-gfm
```
Here's what each dependency does:

-   `next-contentlayer`: This is the core library that provides the integration between Next.js and ContentLayer.
-   `contentlayer`: This plugin allows ContentLayer to read Markdown files from your project.
-   `@tailwindcss/typography`: This plugin provides stylish typographic defaults for Markdown content.
-   `remark-gfm`: This plugin enables support for GitHub Flavored Markdown syntax in your Markdown content.
- 
#### Step 3: Configuring ContentLayer

Now that we have all the dependencies installed, it's time to configure ContentLayer. Create a new file called `contentlayer.config.js` in the root of your project and add the following code:
```javascript
import { defineDocumentType, makeSource } from 'contentlayer/source-files';
import  remarkGfm  from  "remark-gfm";

export const Blog = defineDocumentType(() => ({
  name: 'Blog',
  filePathPattern: `blog/*.mdx`,
  contentType: 'mdx',
  fields: {
    title: {
      type: 'string',
      required: true,
    },
    date: {
      type: 'date',
      required: true,
    },
  },
  computedFields: {
    slug: {
      type: 'string',
      resolve: (doc) => `/${doc._raw.flattenedPath}`
    },
  },
}));

export default makeSource({
  contentDirPath: 'content',
  documentTypes: [Blog],
  mdx: { remarkPlugins: [remarkGfm] }
});
```
In this configuration file, we define a new document type called `Blog` using `defineDocumentType`. This document type represents our blog posts, and we specify that the content will be stored in Markdown files with the `.mdx` extension in a folder called **blog**.

We define two fields for our blog posts: `title` (a required string) and `date` (a required date). We also define a computed field called `slug`, which will be used for generating the URL paths for each blog post.

Finally, we create a new source using `makeSource` , specify the `contentDirPath` as `'content'`, which is where we'll store our Markdown files and set contentlayer to use the `remark-gfm` plugin. We also pass the `Blog` document type to the `documentTypes` array.

#### Step 4: Adding Markdown Content

With our ContentLayer configuration in place, it's time to add some Markdown content. Create a new directory called `content` in the root of your project, and inside that directory, create a new folder named `blog`.

Inside the `blog` folder, create a new file called `my-first-post.mdx` and add the following content:
```markdown
--- 
title: My First Blog Post 
date: 2023-05-24 
--- 
# My First Blog Post 

This is the content of my first blog post written in Markdown. 
You can use all the standard Markdown syntax here, including: 
- Headings 
- Lists 
-  **Bold** and _Italic_ text 
-  [Links](https://example.com) 
```
#### Pretty neat, right?
Notice the front matter at the top of the file, where we define the `title` and `date` fields for this blog post. 

This is how ContentLayer knows which fields to extract from the Markdown file. You can create as many blog posts as you want by adding more  `*.mdx` files to the `content/blog` directory.

#### Step 5: Rendering Blog Posts 
Now that we have our Markdown content set up, it's time to render it on our Next.js site. Create a new `blog` folder, inside that folder create a `[slug` folder and place a `page.js` file inside. The file path should look like `blog/[slug]/page.js` in your src/app folder and add the following code:
```js
import { allBlogs } from "contentlayer/generated";
import { getMDXComponent } from "next-contentlayer/hooks";

export async function generateStaticParams() {
  return allBlogs.map((blog) => ({
    slug: blog._raw.flattenedPath.split("/").pop(),
  }));
}

export default async function BlogPage({ params }) {
  const blog = allBlogs.find(
    (blog) => blog._raw.flattenedPath === "blog/" + params.slug
  );
  let MDXContent;
  if (!blog) {
    console.log("post not found");
  } else {
    MDXContent = getMDXComponent(blog.body.code);
  }
  return (
    <>
      <div className="pt-10 pb-20 lg:py-20 container px-4 mx-auto">
        <div className="mx-auto max-w-4xl">
          <div className="text-center mb-16 max-w-4l mx-auto">
            <h1 className="text-slate-900 text-center text-4xl/none lg:text-6xl/none font-medium'">
              {blog.title}
            </h1>
          </div>

          <article className="prose prose-slate mx-auto max-w-2xl">
            <MDXContent />
          </article>
        </div>
      </div>
    </>
  );
}
```
In this file, we import the `useMDXComponent` hook from `next-contentlayer/hooks`, which allows us to render the Markdown content as React components. We also import the `allBlogs` array from the `contentlayer/generated` file, which contains all our blog posts.

The `BlogPost` component receives the `blog` prop, which contains the data for the current blog post. We use the `useMDXComponent` hook to render the Markdown content of the blog post and wrap it in a `div` with the `prose` class from the `@tailwindcss/typography` plugin. This ensures that our Markdown content is styled nicely out of the box.

The `getStaticParams` function generates the paths for all the blog posts, so that Next.js can pre-render them at build time. We map over the `allBlogs` array and return an array of path objects with the `slug` parameter for each blog post.

Finally, the `getStaticProps` function fetches the blog post data for the given `slug` parameter and passes it as props to the `BlogPost` component.

#### Step 6: Configuring Tailwind CSS, Next config, and jsconfig

To ensure that our Tailwind CSS styles are applied correctly, we need to configure it in our Next.js project. Create a new file called `tailwind.config.js` in the root of your project and add the following code: 

```js
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    "./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
    "./src/components/**/*.{js,ts,jsx,tsx,mdx}",
    "./src/app/**/*.{js,ts,jsx,tsx,mdx}",
  ],
  theme: {
    extend: {
      backgroundImage: {
        "gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
        "gradient-conic":
          "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
      },
    },
  },
  plugins: [require('@tailwindcss/typography')],
};

```
This configuration file tells Tailwind CSS to scan our pages and components for classes to include in the final CSS output. We also add the `@tailwindcss/typography` and `tailwindcss-markdown` plugins to enable styling for Markdown content.

Next, open the `globals.css` file, delete everything and add the following lines:
```css
@tailwind base;
@tailwind components;
@tailwind utilities;

@layer components  { 
	.prose img  { 
		margin-left: auto; 
		margin-right: auto; 
	} 
}

```
This imports the Tailwind CSS base styles, components, and utilities. We also add a custom style to center images in our Markdown content.

Next, let's configure ContentLayer in your `next.config.js` file, you simply need to import the `withContentlayer` function and wrap your Next.js configuration with it. Here's how you would do it:
> Note: You will need to rename your **next.config.mjs** file to **next.config.js** 
```js
const { withContentlayer } = require('next-contentlayer');

/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  pageExtensions: ['js', 'jsx', 'md', 'mdx'],
  //Other Next.js configuration options...
};

module.exports = withContentlayer(nextConfig);
```
The `withContentlayer` function from the `next-contentlayer` package is a higher-order function that enhances your Next.js configuration with the necessary settings to support ContentLayer.

By wrapping your `nextConfig` object with `withContentlayer(nextConfig)`, you're seamlessly integrating ContentLayer into your Next.js application, allowing you to use Markdown and MDX files as content sources without any additional configuration.

Finally, lets add the contentlayer generated path to you `jsconfig.json` file:
```json
{
  "compilerOptions": {
      "baseUrl": ".",
      "paths": {
          "@/*": ["./src/*"],
          "contentlayer/generated": ["./.contentlayer/generated"]
      }
  }
}
```
#### Step 7: Adding Navigation and Styling

To make our blog site more user-friendly, let's add some navigation and apply some additional styling.

First, create a new folder and file called `components/Layout.js` in the src/app folder and add the following code:
```js
import Link from 'next/link';

export default function Layout({ children }) {
  return (
    <div className="min-h-screen flex flex-col">
     <header className="bg-gray-900 text-white py-4">
       <nav className="container mx-auto flex justify-between items-center">
         <Link href="/">
           <a className="text-xl font-bold">My Blog</a>
         </Link>
         <ul className="flex space-x-4">
           <li>
             <Link href="/blog">
               <a>Blog</a>
             </Link>
           </li>
           <li>
             <Link href="/about">
               <a>About</a>
             </Link>
           </li>
         </ul>
       </nav>
     </header>
     <main className="flex-1">{children}</main>
     <footer className="bg-gray-900 text-white py-4 text-center">
       <p>&copy; {new Date().getFullYear()} My Blog. All rights reserved.</p>
     </footer>
   </div>
 );
}
```
This `Layout` component provides a basic structure for our site, including a header with navigation links, a main content area, and a footer. We use Tailwind CSS classes to style the layout.

Next, create a new file called `_app.js` in the src/app folder and add the following code:
```js
import Layout from '../app/components/Layout';
import '../app/globals.css';

function MyApp({ Component, pageProps }) {
  return (
    <Layout>
      <Component {...pageProps} />
    </Layout>
  );
}

export default MyApp;
```
This file sets up the `Layout` component as the root component for our Next.js app, ensuring that it will be rendered on every page.

Finally, we will open the file called `page.js` in the app folder, delete everything and add the following code:
```js
import Link from "next/link";
import { allBlogs } from "contentlayer/generated";

export default function Home({}) {
  const blogs = allBlogs.map((blog) => ({
    slug: blog.slug,
    title: blog.title,
    date: blog.date,
  }));
  return (
    <div className="flex justify-center items-center min-h-screen bg-[#1c062d]">
      <div className="max-w-3xl w-full bg-[#f2e3ff] p-8 rounded-lg">
        <h1 className="text-3xl font-bold mb-8 text-center">
          Welcome to My Blog
        </h1>
        <div className="grid grid-cols-1 sm:grid-cols-2 gap-6">
          {blogs.map((blog) => (
            <div
              key={blog.slug}
              className="bg-white rounded-lg shadow-md overflow-hidden"
            >
              <div className="p-4">
                <h2 className="text-xl font-bold mb-2">{blog.title}</h2>
                <p className="text-gray-600 mb-4">{blog.date}</p>
                <Link
                  key={blog.slug}
                  href={`${blog.slug}`}
                  className="text-blue-500 hover:text-blue-700"
                >
                  Read more
                </Link>
              </div>
            </div>
          ))}
        </div>
      </div>
    </div>
  );
}
```
This `Home` component displays a list of all our blog posts on the homepage. We use the `allBlogs` array from ContentLayer to get the data for each blog post and render it in a grid layout using Tailwind CSS classes.

### This is what your folder should look similar to this at this point
```my-blog-site/
├── .next/
├── node_modules/
├── public/
│   ├── favicon.ico
│   └── ...
├── src/
│   ├── app/
│   │   ├── blog/
│   │   │   ├── [slug]/
│   │   │   │   └── page.js
│   │   ├── globals.css
│   │   ├── page.js
│   │   ├── _app.js
│   │   ├── components
│   │   |    └── Layout.js
├─- content/
│  ├── blog/
│   │   ├── my-first-post.mdx
├── contentlayer.config.js
├── .eslintrc.js
├── .gitignore
├── next.config.js
├── package.json
├── package-lock.json
├── postcss.config.js
├── tailwind.config.js
└── ...
```
#### Run your project using
```bash
npm run dev
```
### We're pretty much done!
Congratulations! You've successfully built a fully-fledged blog site using Next.js 14, ContentLayer, and Tailwind CSS. You've learned how to set up the project, configure ContentLayer, work with Markdown content, render blog posts, style your site with Tailwind CSS, and deploy your site to Vercel.

This is just the beginning, though. You can further enhance your blog site by adding features like commenting, author profiles, categories, and more. The beauty of using Next.js, ContentLayer, and Tailwind CSS is that they provide a solid foundation for building complex and scalable web applications.

Happy coding, and keep exploring the world of web development!
