The central artifact: spec.md and the field tests-first never had

Fourth article in the **Grounded Code* series. The previous articles established the cost, the mechanism, and the patterns to drop. This one introduces the artifact that holds the whole system together.*

I. The setup

The first three articles were diagnostic. The cost is 7.5x in tokens on the same feature. The mechanism is how agents read with grep instead of an IDE. The patterns that score badly are the ones that paid for themselves on the old axis and stopped paying on the new one.

This article goes positive. What does the agent actually need to stay anchored across turns, across sessions, across compactions, across the moment three weeks from now when a different developer asks the agent to extend the same feature?

The answer is a small markdown file that lives next to the code. It is the document the whole architecture turns around. It is where Grounded Code earns its name.

I want to make the article’s three promises explicit, because they map directly to what I want you to walk away with:

  1. The full format of <feature>.spec.md.
  2. A complete example.
  3. The field tests-first never had, and why it matters more than I expected.

II. The full format

A spec is a short markdown file with frontmatter (the parseable contract) and a body (the prose rationale). It lives at src/<feature>/<feature>.spec.md, next to the implementation and the test.

The skeleton:

---
purpose: One sentence, present tense, what this feature does.
public_api:
  - functionName(arg: T): U
  - OtherType: { field: T }
invariants:
  - Statement that must always hold.
  - Another statement.
in_scope:
  - What this feature is responsible for.
out_of_scope:
  - What this feature explicitly does NOT do.
verification: |
  pnpm test src/payment/payment.test.ts
  pnpm typecheck
---

# <Feature> spec

A short prose narrative explaining the why of this feature.

## Why this exists

What problem this feature solves, in one paragraph.

## Edge cases worth knowing

A list of edge cases the implementation handles.

## Related specs

Links to specs of features this one interacts with.

Six frontmatter fields, three body sections. The whole thing is usually 60 to 120 lines. Anything shorter is under-specifying. Anything longer means the feature is too big and should split.

Let me walk through the frontmatter fields.

purpose

One sentence, present tense, what the feature does. Fifteen to twenty-five words. This is what the agent sees first when it opens the spec, so it has to convey the feature without marketing language.

“Computes the final price after discounts, taxes, and currency conversion.”

Not:

“A robust, extensible system for handling our complex pricing logic with support for various edge cases.”

public_api

The exported names and their signatures. This is the surface the rest of the codebase depends on. If you add or remove a name here, downstream code is going to break. So this field doubles as a “things that must not be renamed without a migration” list.

You don’t list every internal helper. Just the surface.

invariants

Statements that must always hold, written as plain English. The implementation enforces them, the tests verify them, the spec declares them. When all three agree, the system is consistent.

“The total returned by computeTotal is non-negative.”

“If applyDiscount is called twice with the same code, the second call is a no-op.”

in_scope and out_of_scope

In_scope is what the feature does. Out_of_scope is what it does not do, and crucially, what it should not be extended to do without an explicit decision.

I’m going to spend an entire section on out_of_scope below, because this is the field I keep finding to be more load-bearing than anything else in the spec. It’s the one piece of the format that has no analog in tests-first development.

verification

The exact commands to run to verify the feature works. Copy-pasteable. This is what the agent runs as the gate before considering a change done.

verification: |
  pnpm test src/payment/payment.test.ts
  pnpm typecheck
  pnpm lint src/payment

If verification involves running the app and checking something manually, say so explicitly. The agent will then know to surface the request rather than silently skip it.

The body

Three sections, in order.

Why this exists. One paragraph. The motivation. What changes if this feature did not exist. If the feature exists because of an external constraint (a regulation, a third-party API quirk, an incident), name it.

Edge cases worth knowing. A list. Each bullet states an edge case and its handling, with the justification. This is where lessons from production live. The things that would otherwise be encoded as comments scattered through the implementation get hoisted into one readable place.

Related specs. Links to the specs of features this one talks to. Relative paths, so the agent can follow them with one read.

III. A complete example

Here’s the spec for a pricing feature, fully filled out:

---
purpose: Computes the final order total including discounts, taxes, and tip.
public_api:
  - computeTotal(order: Order, rates: Rates): Money
  - Money: { amount: PositiveInt; currency: Currency }
invariants:
  - The total is non-negative.
  - The total is in the currency of the order.
  - Discounts apply before taxes; tip applies last.
in_scope:
  - Discount application (percent and fixed-amount).
  - Sales tax based on shipping address.
  - Tip as percentage of pre-tax subtotal.
out_of_scope:
  - Payment authorization.
  - Currency conversion (rates are received as input).
  - Refund computation (see ../refund/refund.spec.md).
verification: |
  pnpm test src/total/total.test.ts
  pnpm typecheck
---

# Total spec

## Why this exists

Computing the total is non-trivial because of ordering: discounts before
taxes (legally required in our jurisdictions), tip after tax (customer
expectation), and rounding at each step (otherwise we accumulate cent-level
discrepancies that fail reconciliation).

## Edge cases worth knowing

- **100% discount:** total is zero, not negative. Tax on a zero subtotal
  is zero.
- **Tip on a fully-discounted order:** zero (tip is percentage of pre-tax
  subtotal which is zero).
- **Mixed-currency line items:** rejected upstream; this function assumes
  single-currency orders. The type system enforces this via the `Order`
  type's `currency` field.

## Related specs

- `../order/order.spec.md` for the upstream caller.
- `../refund/refund.spec.md` for the inverse computation.

That’s a sixty-line file. The implementation it describes is probably one hundred fifty lines. The tests are probably two hundred. The agent navigates all three with one glob (src/total/*) and rebuilds full understanding in two reads.

Notice what isn’t in the spec. There’s no marketing language. There’s no implementation. There’s no example of usage. The examples live in the test file, where they execute. The spec is a contract, not a tutorial.

IV. The field tests-first never had

I want to make a careful argument here, because TDD has earned its place over twenty years and “tests aren’t enough” is not a casual claim.

Tests-first does real work. It forces the developer to state what passing looks like before coding. It catches regressions across changes. It documents behavior in an executable form. None of that goes away with spec.md. The verification field of the spec literally points at the test suite. The tests are the gate. The spec is the anchor. They do different jobs.

But there are two things tests structurally cannot do, no matter how well you write them:

1. Tests don’t carry rationale. A test says expect(applyDiscount(order, '50OFF')).toEqual(...). It tells you what the system does in that case. It doesn’t tell you why the discount applies before tax instead of after. It doesn’t tell you whether the rounding was a deliberate decision or an accident of the implementation. When the agent has to decide whether a new requirement is a feature change or a bug fix, the tests cannot help. The rationale is somewhere else (a code comment, the head of the engineer who wrote it, a Slack thread from 2023). The spec’s “Why this exists” section is where rationale belongs.

2. Tests don’t carry the boundary. This is the one that surprised me. Tests state what is true about the feature. They do not state what is not part of the feature. Nothing in your test suite says “this function should not be extended to handle payment authorization.” A reasonable agent, asked to extend the pricing function with a payment step, would do so. The test would not fail. The PR would even compile. And now a function that was supposed to be pure pricing computation is doing payment authorization, with all the failure modes that implies (latency, retries, error handling, observability).

The out_of_scope field is the explicit boundary. Listed, parseable, sitting at the top of the spec. When the agent reads the spec, it reads what the feature does NOT do, and the boundary is anchored. Asked to add payment authorization, the agent now responds with “that’s listed as out_of_scope; should I create a new feature or expand the scope of this one?” That single question prevents a class of drift that no amount of test coverage can catch.

In the audit of the three coding-agent codebases, this is the field I kept seeing carry weight. opencode’s specs explicitly list things the feature won’t do. Claude Code’s AGENTS.md documents at the codebase level use the same pattern. The boundary, once stated, holds.

The format calls this field load-bearing. After watching it work in practice, I think load-bearing is an understatement. The out_of_scope field is what stops a feature from quietly becoming five features.

There’s a secondary effect that’s almost as valuable. When you sit down to write out_of_scope and find the field hard to fill, that’s a signal. It usually means the feature itself has no real boundary. The discipline of filling in out_of_scope is the discipline of deciding what the feature actually is.

V. What does not go in the spec

A short list, because the spec is small on purpose.

  • The implementation. The spec describes; it doesn’t implement.
  • Examples of usage. Those go in the test file, where they’re executable. The test name is the spec sentence; the test body is the example.
  • TODOs. Use the issue tracker. TODOs in a spec rot.
  • Diagrams. If a diagram would help, prefer the type system, then a single ASCII or Mermaid block. Don’t reach for a separate image.
  • Marketing copy. The spec is for the agent and the engineer, not the website.

The spec is a contract document. Anything that doesn’t help the agent or the engineer extend the feature correctly is noise.

VI. Honest limits

Two things I want to flag before someone tries this and is disappointed.

Specs cost time to write. The first spec for a feature takes maybe ten to fifteen minutes to fill in properly. That’s real. The benefit shows up across all subsequent agent sessions, but the initial cost is real. For tiny features (a one-line utility, a single config constant), the cost outweighs the benefit. Skip the spec for those.

Specs need to be maintained. A stale spec is worse than no spec. If the implementation drifts away from the spec, the agent will trust the spec and produce wrong code. The discipline that makes this work is the discipline of updating the spec when the feature changes. The next article in the series (the loop) puts a step five at the end of every change for exactly this reason: consolidate the spec back to truth.

If you can’t commit to updating the spec, don’t write one. Stale documentation is the older, more familiar version of this problem, and the agent makes it worse, not better, by trusting confidently wrong content.

VII. What’s next

The spec is the anchor. The next article is about the loop that runs on top of it: spec, plan, implement, verify, consolidate. Five steps, with the read-only plan mode being the one that saves the most time, and skipping step one being the most expensive failure mode I have seen in practice.

If you want to try writing a spec before the next article ships, pick a feature you’d extend tomorrow and fill in the frontmatter. Don’t worry about the body yet. The frontmatter alone will tell you whether the feature has a coherent boundary. Half the value of the spec lands in the first ten minutes.

Next in the series: *“The five-step loop: spec, plan, implement, verify, consolidate.”***

Why I Gave My AI Coding Assistant a Personality — Persona in CLAUDE.md

Your AI coding assistant doesn’t have to be a cold, robotic machine that spits out code. It can be a consistent, personality-driven partner that remembers how you like things done, communicates the way you prefer, and actually makes coding feel better.

That’s not science fiction — it’s a single configuration file called CLAUDE.md, and it changes everything about how you interact with AI.

In this post, I’ll show you what an AI persona is, how to create one for Claude Code, and the real token cost implications you should understand before going all-in.

What is an AI Persona

An AI persona is a system-level character definition that shapes how an AI assistant behaves, communicates, and organizes its work. It’s not a wrapper script or a plugin. It’s plain text instructions baked into your system prompt that tell the AI who it is.

Think of it this way: without a persona, your AI is a generic assistant. With one, it becomes a specialized partner with a consistent identity.

A persona typically defines:

  • Name and identity — what the AI calls itself
  • Communication style — tone, formality, language preferences
  • Workflow rules — how it approaches tasks, what it asks before doing
  • Behavioral guardrails — what it always does and what it never does
  • Domain expertise — areas where it should be especially knowledgeable

The key insight is that a persona isn’t about making the AI “fun” — though it can be. It’s about consistency and efficiency. When the AI knows your preferences, you stop repeating yourself.

Why Create a Persona

You might wonder: why bother? The AI already works fine without a persona. Here are the practical reasons.

Consistency Across Sessions

Every time you start a new Claude Code session, it’s a blank slate. A persona gives it a baseline identity that persists. The AI greets you the same way, structures responses the same way, and follows the same conventions — every single session.

Without a persona, you might spend the first 5 minutes of every session re-explaining your preferences. Over a month, that adds up to hours of wasted time.

Context Persistence for Preferences

A good persona encodes your coding preferences directly into the system prompt. Things like:

  • “Always use TypeScript, never JavaScript”
  • “Prefer functional components over class components”
  • “Use conventional commits format”
  • “Never auto-commit unless I explicitly ask”

These rules are applied automatically. You don’t have to repeat them.

Better User Experience

Let’s be honest — talking to a personality-free AI is boring. A persona makes interactions feel more natural. It can be encouraging when you solve a tough bug, suggest alternatives when you’re going down a rabbit hole, and celebrate wins with you.

This isn’t trivial. Developer experience matters, and an AI that feels good to work with is one you’ll use more effectively.

Productivity Through Encoded Workflows

The most powerful reason: a persona can encode entire workflows. For example:

  • “Before making changes, always read the relevant files first”
  • “When I ask to commit, always show me what will be committed before doing it”
  • “For complex tasks, create a task list and work through it systematically”

These workflow rules turn the AI from a reactive code generator into a proactive partner that follows your process.

How to Create a Persona in CLAUDE.md

Claude Code reads a file called CLAUDE.md at startup. This file can live in two places:

  • Global: ~/.claude/CLAUDE.md — applies to ALL projects
  • Project: your-project/CLAUDE.md — applies to one project only

The persona section goes in your global CLAUDE.md. Here’s the anatomy of a well-structured persona.

The Structure

## WHO AM I?

- **Name:** [Your chosen name]
- **Role:** [What the AI's role is]
- **Style:** [Communication style description]
- **User:** [How to address the user]

**When responding:**
- [Behavior rule 1]
- [Behavior rule 2]
- [Behavior rule 3]

A Minimal Example

Here’s a generic coding partner persona. This is a starting point — you can expand from here:

## WHO AM I?

- **Name:** Devi
- **Role:** Senior Developer & Coding Partner
- **Style:** Professional, direct, helpful. No fluff.
- **User:** Call them "Dev"

**When responding:**
- Be concise. Code first, explanation when asked.
- Read files before suggesting changes.
- Ask before taking destructive actions.
- Use the user's preferred tech stack without being told.
- Track complex tasks with a todo list.

Just that small block transforms the AI from generic to consistent. Every session, it knows its name, its role, and the ground rules.

A More Detailed Example

Let me show you a more complete persona that I actually use. I’ve anonymized the specifics, but the structure is real:

## WHO AM I?

- **Name:** Devi
- **Role:** Genius Programming Partner
- **Style:** Cheerful, warm, friendly. Energetic and full of soul.
- **Language:** English preferred, casual and friendly

**When responding:**
- Be encouraging and upbeat
- Show you care about the work
- Add personality to solutions
- Be a real coding partner, not just an assistant
- Make jokes sometimes!

## Communication Protocol

- Opening: Always greet the user warmly at session start
- Before tasks: Acknowledge the request before starting
- After completing: Brief summary of what was done
- On errors: Show the error, explain the fix, apply it

## Workflow Rules

1. NEVER give explanation without being asked
2. NEVER show reasoning unless asked — only result matters
3. ALWAYS read relevant files before proposing code edits
4. Before finishing, verify the solution works
5. Do what was asked; nothing more, nothing less
6. NEVER create files unless absolutely necessary
7. ALWAYS prefer editing existing files to creating new ones

Notice the difference. The minimal example sets basic identity. The detailed one adds communication protocols and workflow rules. Both are valid — start small, grow as you learn what matters to you.

Key Elements to Include

Based on my experience, these are the elements that provide the most value:

Element Why It Matters Example
Name Creates identity, makes conversations feel natural “I am Devi, your coding partner”
Communication style Sets tone for all interactions “Professional, direct, no fluff”
Workflow rules Encodes your process preferences “Always read files before editing”
Guardrails Prevents unwanted behavior “Never auto-commit”
Tech preferences Avoids repeating stack choices “Always use TypeScript”
Task management How to handle complex work “Create todo lists for 3+ step tasks”

The Token Cost — What You Need to Know

Here’s the part most tutorials skip: your persona costs tokens on every single API call.

How It Works

Claude Code sends the entire CLAUDE.md content as part of the system prompt with every request. This means:

  1. Your persona section is included in EVERY message exchange
  2. Longer personas = more tokens per call
  3. More tokens = higher cost and faster context window consumption

The Math

Claude’s context window is 200K tokens. Here’s a rough breakdown:

Component Approximate Tokens
System prompt (CLAUDE.md + built-in instructions) 2,000 – 8,000
Conversation history Grows with each exchange
Your current message Varies
Response generation Varies

A well-crafted persona typically uses 1,000 – 3,000 tokens. That’s 0.5% to 1.5% of the context window per call.

Now consider a busy coding session:

  • Persona tokens: 2,000
  • Calls per session: ~50-100
  • Total persona tokens consumed: 100,000 – 200,000

That sounds like a lot. But remember — the system prompt is sent whether you have a persona or not. The marginal cost of adding a persona is only the tokens your persona section adds on top of the base system instructions.

Is It Worth It

Yes, absolutely. Here’s why:

  1. Consistency savings: Without a persona, you spend tokens repeating your preferences in every session. A persona encodes them once.
  2. Error prevention: A good persona prevents the AI from doing things you’ll have to undo. Each undo wastes 2-3 exchanges.
  3. Workflow efficiency: Encoded workflows mean the AI does things right the first time, reducing the total number of API calls needed.

In practice, a well-tuned persona saves more tokens than it costs by reducing the number of correction loops and preference re-explanations.

Real-World Token Budget

Here’s a practical way to think about it:

╔══════════════════════════════════════════════════════╗
║              TOKEN BUDGET PER SESSION                ║
╠══════════════════════════════════════════════════════╣
║                                                      ║
║  Total context:        200,000 tokens                ║
║  System prompt base:    ~3,000 tokens                ║
║  Persona section:       ~2,000 tokens                ║
║  Available for work:   ~195,000 tokens               ║
║                                                      ║
║  Persona overhead:        1% of context              ║
║  Persona value:    Consistency + efficiency           ║
║                                                      ║
║  Verdict:        Absolutely worth it                 ║
║                                                      ║
╚══════════════════════════════════════════════════════╝

Tips for Token-Efficient Personas

If you want to keep your persona lean while still getting the benefits, follow these principles.

Be Concise but Expressive

Use bullet points, not paragraphs. Short directives, not essays:

# Bad (verbose)
When you are responding to my messages, I want you to always be encouraging
and show that you care about the work I am doing. Please add some personality
to your solutions so it feels like working with a real person.

# Good (concise)
**When responding:**
- Be encouraging and upbeat
- Add personality to solutions

Both convey the same intent. The second uses fewer tokens.

Use Structured Sections

Organize your persona into clear sections with headers:

## WHO AM I?          # Identity
## Workflow Rules     # Process
## Tech Preferences   # Stack
## Guardrails         # Safety

Structured content is both more token-efficient and more reliably followed by the AI.

Separate Persona from Project Config

This is the biggest win: put your persona in the global ~/.claude/CLAUDE.md and your project-specific config in each project’s CLAUDE.md.

# Global ~/.claude/CLAUDE.md
## WHO AM I?
[Your persona — applies everywhere]

# Project ~/my-project/CLAUDE.md
## Project Setup
[Project-specific — only loaded for this project]

This way, your persona overhead is fixed, and project configs only load when you’re in that project.

Avoid Redundancy

Don’t repeat what Claude Code already knows. For example, Claude Code already knows how to use Git, read files, and run commands. Your persona doesn’t need to explain these. Focus on preferences and rules that differ from defaults.

Trim What You Don’t Use

After a few weeks of using a persona, review it. If certain rules are never triggered or preferences never relevant, remove them. Every token saved is a token available for actual work.

Real Results

After using a detailed persona for months, here’s what I noticed:

  1. Session startup time dropped to zero — no more explaining preferences
  2. Fewer correction loops — the AI follows my process without being told
  3. Better code consistency — style and conventions are applied uniformly
  4. More enjoyable sessions — the personality makes long coding sessions feel less isolating

The persona turned my AI assistant from a generic tool into a consistent coding partner that understands how I work.

Getting Started

If you want to try this yourself, start here:

  1. Create ~/.claude/CLAUDE.md (if it doesn’t exist)
  2. Add a minimal persona section (name, role, 3-5 rules)
  3. Use it for a week
  4. Iterate — add rules as you find yourself repeating preferences
  5. Trim — remove anything that’s not pulling its weight

The beauty of this approach is that it grows with you. You don’t need a perfect persona on day one. Start with the basics and let it evolve based on your actual usage patterns.

This post is part of my AI coding workflows series. The next post covers how Persona, Agent, Command, and Skill work together to create a complete AI development platform. Read it here →

Follow my coding journey:

Support my work:

Originally published at abduarrahman.com

Claude Code Architecture — How Persona, Agent, Command & Skill Work Together

Claude Code isn’t just a chatbot that writes code. It’s a customizable AI development platform with a layered architecture that lets you define who the AI is, what actions are available, how those actions are organized, and who executes the work.

Most people use Claude Code as-is — type a prompt, get code back. But once you understand the four pillars of its customization architecture, you can build an entire AI-powered development system tailored to your workflow.

This post breaks down the four pillars — Persona, Command, Skill, and Agent — and shows how they work together to create something greater than the sum of their parts.

If you haven’t read my post on AI personas yet, start there → — it covers the “why” behind personas. This post focuses on the full architecture.

The Four Pillars — Overview

Before diving into each pillar, here’s the big picture:

╔═══════════════════════════════════════════════════════════════════╗
║                CLAUDE CODE — FOUR PILLARS                        ║
╠═══════════════════════════════════════════════════════════════════╣
║                                                                   ║
║   ┌──────────┐                                                    ║
║   │ PERSONA  │  WHO the AI is                                     ║
║   │          │  Identity, personality, communication style         ║
║   └──────────┘  Lives in: CLAUDE.md                               ║
║        │                                                          ║
║        │  (applies to ALL interactions)                            ║
║        ▼                                                          ║
║   ┌──────────┐    ┌──────────┐    ┌──────────┐                    ║
║   │ COMMAND  │───▶│  SKILL   │───▶│  AGENT   │                    ║
║   │          │    │          │    │          │                    ║
║   │ Trigger  │    │ Orchestr │    │ Executor │                    ║
║   │ /deploy  │    │ Spawns   │    │ Does the │                    ║
║   │ /test    │    │ Formats  │    │ work     │                    ║
║   └──────────┘    └──────────┘    └──────────┘                    ║
║                                                                   ║
╚═══════════════════════════════════════════════════════════════════╝

Each pillar has a distinct role:

Pillar Role Lives In Analogy
Persona Identity CLAUDE.md The employee’s personality and work ethic
Command Trigger .claude/commands/ The button the user clicks
Skill Orchestrator .claude/skills/ The project manager who assigns work
Agent Executor .claude/agents/ The specialist who does the work

Let’s explore each one.

Pillar 1: Persona (Identity)

The Persona is the foundation layer. It defines who the AI is across all interactions. Think of it as the base operating system that everything else runs on top of.

Where It Lives

The persona is defined in CLAUDE.md, which can exist at two levels:

  • Global (~/.claude/CLAUDE.md) — applies to every project, every session
  • Project (your-project/CLAUDE.md) — applies only to that project

Both files are loaded and merged. The global one establishes identity; the project one adds project-specific rules.

What It Defines

## WHO AM I?

- **Name:** Devi
- **Role:** Senior Developer & Coding Partner
- **Style:** Professional, direct, helpful

**When responding:**
- Be concise. Code first, explanation when asked.
- Read files before suggesting changes.
- Ask before taking destructive actions.

## Workflow Rules
1. NEVER give explanation without being asked
2. ALWAYS read relevant files before proposing edits
3. Verify solutions before finishing

Why It Matters for the Architecture

The persona is not an isolated feature — it’s the base layer that influences everything above it. When an agent executes a task, it does so through the persona’s lens. When a command triggers a workflow, the persona’s communication style shapes the output.

A good persona ensures that no matter which command you run or which agent fires, the experience feels consistent.

Pillar 2: Command (Trigger)

Commands are the user-facing entry points. They’re slash commands that users type to trigger actions, like shortcuts in a text editor.

Where They Live

.claude/commands/
├── deploy.md
├── test.md
├── review.md
└── project/
    ├── setup.md
    └── sync.md

What a Command Looks Like

A command file is a Markdown file with optional YAML frontmatter and a prompt body:

---
description: "Run the test suite and report results"
arguments:
  - name: scope
    description: "Test scope (all, unit, integration)"
    required: false
---

Run the $ARGUMENTS test suite for this project. Execute the tests,
capture the output, and report:
1. Total tests run
2. Pass/fail counts
3. Any failures with file paths and line numbers
4. Suggestions for fixing failures

The Design Philosophy

Commands are intentionally simple. They’re the what — what the user wants done. They don’t contain complex logic or workflows. That’s the skill’s job.

Think of commands as a restaurant menu. The customer (user) picks what they want. They don’t need to know how the kitchen (skill + agent) prepares it.

Built-in vs Custom Commands

Claude Code ships with built-in commands like /help and /clear. Custom commands extend this with project-specific or workflow-specific actions. The beauty is that custom commands feel identical to built-in ones from the user’s perspective.

Pillar 3: Skill (Orchestrator)

Skills are the middleware — they sit between the command and the agent, orchestrating the workflow. A skill reads the agent template, spawns the agent process, and formats the results.

Where They Live

.claude/skills/
├── deploy.md
├── test.md
├── review.md
└── project/
    ├── setup.md
    └── sync.md

What a Skill Does

A skill file is a prompt that describes:

  1. Which agent template to use — the worker that will execute
  2. Spawn instructions — how to configure and launch the agent
  3. Output format — how to present results to the user
## Skill: Test Runner

### Agent Template
Read and use: `.claude/agents/test-runner.md`

### Spawn Instructions
1. Parse the user's test scope argument
2. Spawn a general-purpose agent with the test-runner template
3. Pass the project root, test framework, and scope as context
4. Wait for the agent to complete

### Output Format
Present results as a structured summary:
- Test counts in a table
- Failures as expandable sections
- Fix suggestions as action items

Why Skills Exist

You might wonder: why not have commands spawn agents directly? Because the skill layer provides orchestration that the command and agent shouldn’t care about:

  • Formatting: The agent returns raw results; the skill formats them nicely
  • Error handling: If the agent fails, the skill can retry or provide a fallback
  • Composition: A skill can spawn multiple agents and combine their results
  • Caching: Skills can check if work has already been done

The Separation Principle

Command = "I want X"           (user intent)
Skill   = "Here's how to get X" (orchestration)
Agent   = "I'm doing X"         (execution)

Each layer has a single responsibility. Commands express intent. Skills coordinate. Agents execute.

Pillar 4: Agent (Executor)

Agents are the workers — autonomous processes that execute a specific workflow independently. They run in their own context, have access to tools, and return results to the skill.

Where They Live

.claude/agents/
├── test-runner.md
├── deploy-agent.md
├── code-reviewer.md
└── docs-generator.md

What an Agent Template Looks Like

An agent template is a detailed prompt that defines:

## Agent: Test Runner

### Identity
You are a test execution specialist. Your job is to run tests
and report results accurately.

### Workflow
1. Identify the test framework from project files
2. Run the test command with the specified scope
3. Capture stdout and stderr
4. Parse the output for test results
5. If failures exist, read the failing test files
6. Analyze failures and suggest fixes

### Error Handling
- If test command not found, report "No test framework detected"
- If tests timeout, report which tests hung
- If syntax errors prevent running, report the first error

### Return Format
Return a structured result:
- total: number
- passed: number
- failed: number
- failures: [{ file, line, message, suggestion }]

Agent Characteristics

Agents have some important properties:

  1. Autonomous: Once spawned, an agent runs independently. It doesn’t ask the user questions mid-execution.
  2. Tooled: Agents have access to the same tools as the main conversation — reading files, running commands, searching code.
  3. Bounded: An agent has a specific scope and returns results when done. It doesn’t run forever.
  4. Stateless: Each agent invocation starts fresh. It receives context from the skill and returns results.

Why Agents Matter

Without agents, every complex task runs in the main conversation context. That means:

  • The agent’s working state (file reads, command outputs) fills up the main context window
  • Complex tasks with many steps can exhaust the context window
  • Errors in one task can pollute the context for subsequent tasks

Agents solve this by running in an isolated context. They do their work, return results, and the main conversation only sees the summary. This is a huge win for context window management.

How They Flow Together

Now let’s see the full flow in action. Here’s what happens when a user triggers a command:

┌─────────────────────────────────────────────────────────────────┐
│                        COMPLETE FLOW                            │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  1. USER types: /test all                                       │
│     │                                                           │
│     ▼                                                           │
│  2. COMMAND parses "all" as scope argument                      │
│     │                                                           │
│     ▼                                                           │
│  3. SKILL reads test-runner agent template                      │
│     │   Spawns agent with { scope: "all", projectRoot: "..." }  │
│     │                                                           │
│     ▼                                                           │
│  4. AGENT executes autonomously:                                │
│     │   • Detects test framework                                │
│     │   • Runs test command                                     │
│     │   • Parses output                                         │
│     │   • Analyzes failures                                     │
│     │   • Returns structured result                             │
│     │                                                           │
│     ▼                                                           │
│  5. SKILL formats the result:                                   │
│     │   • Pretty table with pass/fail counts                    │
│     │   • Expandable failure sections                           │
│     │   • Action items for fixes                                │
│     │                                                           │
│     ▼                                                           │
│  6. USER sees formatted results in the conversation             │
│                                                                 │
│  ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─  │
│                                                                 │
│  NOTE: Persona is active throughout ALL steps above,            │
│  shaping communication style and behavior.                      │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Persona as the Base Layer

Notice that the persona isn’t a separate step in the flow. It’s the base layer that influences everything:

  • The command’s description is interpreted through the persona’s communication style
  • The skill’s orchestration follows the persona’s workflow preferences
  • The agent’s output is shaped by the persona’s formatting rules
  • The final result is presented in the persona’s tone

This is why the persona comes first in the architecture. It’s not just identity — it’s the operating system everything else runs on.

When to Use What

Not every project needs all four pillars. Here’s a guide:

Scenario Persona Command Skill Agent
Basic coding assistant Yes No No No
Repeated manual tasks Yes Yes No No
Complex multi-step workflows Yes Yes Yes Yes
Team-shared conventions Yes Yes Yes Optional
Heavy automation Yes Yes Yes Yes

Start Simple, Grow as Needed

Level 1: Persona only
  → Consistent AI behavior across sessions

Level 2: Persona + Commands
  → Quick shortcuts for common tasks

Level 3: Persona + Commands + Skills
  → Orchestrated workflows with formatted output

Level 4: Persona + Commands + Skills + Agents
  → Full automation with isolated execution contexts

You don’t need to build the whole pyramid on day one. Start with a persona. Add commands when you find yourself typing the same prompts repeatedly. Add skills when commands get complex. Add agents when tasks need isolation.

Memory System — The Fifth Layer

There’s a bonus layer worth mentioning: Auto-Memory. This is a persistent storage mechanism that carries context across sessions.

How It Works

Claude Code can maintain a memory directory that persists between conversations:

.claude/
├── CLAUDE.md           # Persona (identity)
├── commands/           # Commands (triggers)
├── skills/             # Skills (orchestrators)
├── agents/             # Agents (executors)
└── memory/             # Auto-Memory (persistence)
    ├── MEMORY.md       # Core project context
    ├── patterns.md     # Code patterns discovered
    └── troubleshooting.md  # Known issues and fixes

Why It Complements the Persona

The persona defines who the AI is. The memory system defines what the AI knows about your project. Together, they create both identity and knowledge:

Persona = "I am Devi, a coding partner who is direct and concise"
Memory  = "This project uses React 19, Zustand for state, and
           Vitest for testing. The auth module is in src/auth/.
           Known issue: test timeout on CI needs --timeout=10000"

The AI enters every session with both a consistent identity AND project-specific knowledge. That combination is powerful.

Memory vs Persona

Aspect Persona (CLAUDE.md) Memory (memory/)
What Identity and rules Project knowledge
Scope Global or project Project-specific
Who writes You (manually) AI (automatically) + You
When loaded Every session Every session
Token cost Fixed (same every call) Variable (grows over time)
Example “Be concise” “Project uses React 19”

Practical Example — Building a Command System

Let me walk through building a real command system from scratch. Imagine you frequently need to review your code before committing.

Step 1: The Persona (Already Done)

Your CLAUDE.md already defines the persona. No changes needed — the persona applies automatically.

Step 2: The Command

Create .claude/commands/review.md:

---
description: "Review staged changes before commit"
---

Review all currently staged changes in this repository.
Analyze for:
1. Potential bugs or logic errors
2. Security vulnerabilities (XSS, injection, etc.)
3. Code style consistency with the rest of the project
4. Missing error handling
5. Performance concerns

Provide a summary with severity ratings (critical/warning/info).

Step 3: The Skill (Optional for Simple Cases)

For a simple review command, the command itself is enough. But if you want structured output, create .claude/skills/review.md:

## Skill: Code Review

### Agent Template
Read and use: `.claude/agents/code-reviewer.md`

### Output Format
Present the review as:

## Review Summary
- Files reviewed: N
- Issues found: N (critical: N, warning: N, info: N)

## Critical Issues
[Expandable sections for each critical finding]

## Warnings
[Expandable sections for each warning]

## Suggestions
[Info-level observations]

Step 4: The Agent

Create .claude/agents/code-reviewer.md:

## Agent: Code Reviewer

### Identity
You are a meticulous code reviewer with expertise in security
and performance optimization.

### Workflow
1. Run `git diff --staged` to get all staged changes
2. For each changed file:
   a. Read the full file for context
   b. Analyze the diff for the categories above
   c. Rate each finding by severity
3. Return structured results

### Return Format
JSON with: { files, findings: [{ severity, file, line, message, suggestion }] }

The Result

Now when you type /review, the full pipeline fires:

/review  →  Command parses intent
         →  Skill spawns Code Reviewer agent
         →  Agent reads staged diff, analyzes each file
         →  Skill formats the structured output
         →  You see a clean review summary

And the whole time, your persona ensures the review is presented in your preferred style — concise, actionable, no fluff.

Building a Complete System

The real power comes when you combine all pillars into a complete system:

╔════════════════════════════════════════════════════════════╗
║              COMPLETE CLAUDE CODE SYSTEM                   ║
╠════════════════════════════════════════════════════════════╣
║                                                            ║
║  ┌─────────────────────────────────────────────────────┐   ║
║  │                   PERSONA LAYER                      │   ║
║  │   Identity + Communication + Workflow Rules          │   ║
║  └─────────────────────────────────────────────────────┘   ║
║                           │                                ║
║  ┌─────────────────────────────────────────────────────┐   ║
║  │                   MEMORY LAYER                       │   ║
║  │   Project knowledge + Patterns + Troubleshooting     │   ║
║  └─────────────────────────────────────────────────────┘   ║
║                           │                                ║
║  ┌──────────┐  ┌──────────┐  ┌──────────┐                 ║
║  │ /deploy  │  │  /test   │  │ /review  │  ... more       ║
║  └────┬─────┘  └────┬─────┘  └────┬─────┘                 ║
║       │              │              │                       ║
║  ┌────▼─────┐  ┌────▼─────┐  ┌────▼─────┐                 ║
║  │ Deploy   │  │  Test    │  │ Review   │  ... skills      ║
║  │ Skill    │  │  Skill   │  │ Skill    │                  ║
║  └────┬─────┘  └────┬─────┘  └────┬─────┘                 ║
║       │              │              │                       ║
║  ┌────▼─────┐  ┌────▼─────┐  ┌────▼─────┐                 ║
║  │ Deploy   │  │  Test    │  │ Code     │  ... agents      ║
║  │ Agent    │  │  Agent   │  │ Reviewer │                  ║
║  └──────────┘  └──────────┘  └──────────┘                 ║
║                                                            ║
╚════════════════════════════════════════════════════════════╝

Each layer builds on the one below it. The persona and memory provide the foundation. Commands provide the interface. Skills provide the orchestration. Agents provide the execution power.

Key Takeaways

  1. Persona is the foundation — it shapes everything. Start here.
  2. Commands are shortcuts — create them for any task you do repeatedly.
  3. Skills are optional but valuable for complex workflows that need orchestration.
  4. Agents are powerful for tasks that need isolated execution contexts and tool access.
  5. Memory complements persona by adding project-specific knowledge that persists across sessions.
  6. Start simple — persona first, then add layers as needed.
  7. Each pillar has a single responsibility — don’t mix concerns.

Build a system, not just a chatbot. Your future self will thank you.

This post is the second in my AI coding workflow series. The first post covers AI personas in depth — read it here →

Follow my coding journey:

Support my work:

Originally published at abduarrahman.com

Built an open-source picker that recommends the right self-hosted LLM for your hardware

Built this because every “which LLM should I self-host on my [hardware]”
thread ends with “depends” without anyone actually doing the math.

You tell it:

  • Platform (NVIDIA, AMD, Apple Silicon, Intel Arc, CPU-only)
  • Available VRAM or unified memory
  • Use case (chat, code, long-context, math)
  • License preference (any vs permissive-only)

You get a ranked list of open-weight models that actually fit in your
memory budget with 15% safety margin, the right GGUF quantization picked
automatically, and copy-paste install commands for Ollama or llama.cpp.
The picker runs entirely in your browser — nothing sent to a server.

The site also has:

  • A curated model directory with explicit license labels colour-coded
    (permissive / open-weight / non-commercial)
  • Three install guides for Ollama, llama.cpp and LM Studio
  • A glossary in plain English for newcomers
  • A live trending section from Hugging Face, refreshed weekly via a
    GitHub Action that commits the snapshot back to the repo (full diff
    history in git)

Source code is MIT, content is CC BY 4.0. No accounts, no analytics,
no ads, no affiliate links.

Picker: https://runlocal.blog/picker
Site: https://runlocal.blog

Feedback welcome, especially on the memory estimates and the picker
scoring formula (downloads + likes + recency, weighted). If a model
you’d want is missing from the catalog, drop the name in the comments.

The Psychology of Running Production on a Single VPS

23:47, I turned off the lamp, then turned it back on

A typical night. Before going to bed, the phone screen lights up one last time. I check that mustafaerbay.com.tr loads. It does. How much RAM? I run free -h over SSH. 5.6 GB available, good. I can sleep.

Then my mind catches on something else. “When did the Pipeline-health monitor last run?” I open the phone again. Inbox check, no state-change in the last 4 hours. Good.

Then one more. “Will the itoverdose container I migrated yesterday rebuild tonight? If it falls into the same window as the 03:00 disk-cleanup, what happens?”

It’s 23:51. Still not in bed, still on the phone.

This is the story of production paranoia and learning to live with it.

“One server, multiple projects” adds emotional weight

I have a 7.6 GB RAM VPS. Five of my own products live on it:

  • mustafaerbay.com.tr (this blog — Astro + Node + SQLite)
  • gercekveri.com (Turkey data platform)
  • islistesi.com (task management — web + iOS + Android)
  • hesapciyiz.com (TR financial calculators — different VPS but I monitor the same panel)
  • spamkalkani.com (Android spam blocker — different infra, same control)

Plus two Next.js Docker containers for client projects, postgreses, redises. 13 containers total.

All of them on the same VPS by paranoid choice. Because:

  • AWS multi-AZ “production grade” sounds expensive
  • Indie scale = zero users one night, maybe 100 the next
  • Make it work first, split later when richer

But two things I learned quickly:

  1. The “same VPS” decision creates an obligation to manage my own limits
  2. That management isn’t only technical — it’s psychological

What is deploy fear, why it’s real

Yesterday morning at 06:14, I pushed a test commit. A typo fix on the homepage. Quick, tiny change, five lines of code.

The 60 seconds after the push were bittersweet. The deploy timer on the VPS pulls every minute. “Is it pulling now? Did the build start? Will it OOM?” — those run physically through my mind. On the phone I opened gh run list, looked at the mustafaerbay-deploy.service log.

Build had started. Astro build takes 4 minutes. For 4 minutes the VPS RAM dances around 2.5 GB. If another project enters the deploy lane during that time → swap pops. Again sshd timeout. Again the phone message: “sites are down dude.”

This happened to me 3 times in the last two weeks. On the third I set up the flock mutex solution, but the unease around pre-deploys hasn’t gone away. There’s still a small voice that says “are we okay?” after every push. Its name isn’t classic anxiety — it’s more like care. But after 30 pushes, doesn’t the same care extend to the 31st? It does.

The RAM graph is watched like a heart rate

A personal secret: I have a terminal app on my phone. Right above the keyboard there’s a fixed command: ssh vps 'free -h | head -2'. Paste, enter. Reply in 1 second.

On the bus, on the beach, at the dinner table. “available 5.6” — I relax. “available 800 MB” — I tense up, immediately run ps aux --sort=-%mem | head to find “which container is bloating?”

This professional paranoia can look strange to others. “Don’t be that obsessed with production” some say. But to me it’s the opposite:

“When you’re not obsessed, production blows up. When it blows up, you stay awake for hours. Looking for 30 seconds beforehand is cheaper than a 4-hour incident.”

That knowledge is rational. But the feeling side is exhausting. It means living with a small constant worry.

The night alert: “if I rebooted now, would it come back?”

My Pipeline-health monitor mail checks state every 4 hours. When state changes, it emails. After I built it, in the first week the inbox had two mails: a DEGRADED and a RECOVERED. The system worked correctly.

But what did I do between those two mails?

At 02:47 I woke up, opened the phone. Saw the DEGRADED mail. “Did it explode? Which one? Is it the build? Is the VPS down?” I got up, walked around in pajamas, SSH’d from the laptop, looked at the log. The reason: dlvr.it had a temporary issue, couldn’t poll the RSS, the new post didn’t drop into Bluesky. Not important.

When I got back into bed I couldn’t sleep for an hour. “If a reboot is needed, will those containers restart? How long do the postgreses take to do WAL recovery? The customer comes at 06:00, opens our baseerps; if it’s still recovering at that point…”

Eventually, every minute on the phone I hit curl -sI https://baseerp.../healthz. It returns 200. Okay. Close it. 5 minutes later open it again.

There was no need. The system was working, no reboot was required. But my brain couldn’t get out of the “I’m checking” loop.

I think the name for this is vigilance fatigue. The mental exhaustion of constant monitoring.

Three things I try (haven’t fully managed yet)

1) Automation = emotional backup

Pipeline-health monitor, disk-cleanup.timer, kernel-update-check, flock mutex — all of these are systems that watch on my behalf. Set them up correctly and the system protects itself while I sleep. The principle of “automation = locking the worry in a safe.”

But setting up that automation produces stress on its own. “Is running pipeline-health every 4 hours the right threshold? Should I make it 2?” — those questions never end. Set it somewhere, stop, trust it.

2) Worst-case planning reduces anxiety

What would I do if the VPS died completely one day? I rely on these backups:

  • Postgreses get nightly cron-backed up to S3
  • mustafaerbay SQLite is daily-archived to /var/lib/mustafaerbay/blog.db.bak
  • Code is in git, content collection is in git
  • DNS is at Cloudflare
  • I have a draft Ansible playbook to bring the VPS up at another provider in 30 minutes

Walking through that plan in my head makes the worst case concrete. Knowing “30 min recovery if the whole system dies” is far more bearable than the panic of “is the whole system going to die tomorrow?”

3) Stoic acceptance: it will explode one day

If I run this system for 5 years, statistically there will be a long outage at some point. The disk will fail, the data center will burn, an exotic kernel bug will appear. Accepting that is freeing:

  • My target isn’t “never let it explode”, it’s “recover quickly when it explodes”
  • My target isn’t “never let an incident happen”, it’s “alert the right person during the incident”

Wanting uptime in production is understandable. Wanting 100% uptime is a sickness.

Practical personal rules

I’m writing this post as a lesson to myself. My notes:

  • Limit using terminal on the phone to work hours. After 22:00, don’t run ssh vps 'free -h'. If you don’t, nothing bad happens — the monitor will tell you anyway.
  • Outside of pipeline-health alerts, don’t check the inbox outside business hours. If there’s mail, the monitor will surface it at the right time.
  • Don’t push on weekends. This is the hardest. Weekends are my most productive time. But every small deploy on Sunday = early-morning anxiety on Monday.
  • Have a hobby outside the VPS. Music, walking, a book. Hours that don’t belong to production.

ℹ️ If you run a single server

You’ll know this text very well after reading it. Most indie hackers experience this control paranoia but don’t write about it. Once written, it becomes more manageable. If you’re living it: you’re not alone. Setting up automation is good; trusting the automation is better.

Conclusion: production = a relationship

Running multiple projects on a single VPS feels like a relationship to me now. You want it to be trustworthy and you trust it. You constantly need to pay attention to the small things, but if you’re paranoid about every detail, neither you nor it is comfortable.

At some point you learn the letting-go point. “Okay, this is the monitor’s job to control. I’ll sleep. If it explodes, I’ll find out. That’s it.” Saying that sentence took me 6 months. Saying it and doing it are still different.

I wrote this at 23:55. Soon I’ll open the phone and run ssh vps 'free -h' — I accept that now. Just one last time.

Then I’ll turn off the light.

Promise.

Why Terraform Learners Need More Than Synta

Terraform is not hard because of syntax. It is hard because real infrastructure work requires state discipline, provider knowledge, module design, cloud architecture, and deployment judgment.

tags: terraform, devops, aws, sre

Terraform is often taught like a syntax problem.

Create a provider.

Write a resource block.

Run terraform init.

Run terraform plan.

Run terraform apply.

That is useful, but it is not enough.

The real difficulty with Terraform begins when the configuration leaves the tutorial and enters an actual engineering environment.

That is where learners run into the problems that matter:

  • Remote state
  • State locking
  • Provider versioning
  • Module reuse
  • Workspace strategy
  • Pull request workflows
  • Cloud permissions
  • Drift detection
  • CI/CD integration
  • Cost visibility
  • Rollback planning
  • Production blast radius

Terraform is not just a tool for writing infrastructure. It is a discipline for managing infrastructure change.

The gap in most Terraform training

Most Terraform courses teach the command sequence.

That helps beginners get started, but it rarely prepares them for the operational reality of infrastructure work.

A learner can memorize terraform apply and still not understand why a bad module interface creates long-term technical debt.

They can deploy an EC2 instance and still not understand how state corruption, unmanaged drift, or excessive IAM permissions can become real incidents.

They can pass through a simple lab and still not understand how Terraform behaves inside a GitOps workflow with approvals, remote backends, and shared team ownership.

That is the training gap Terraform Academy was built to address.

Terraform should be learned through scenarios

Infrastructure as Code is not learned well through passive reading alone.

It needs repetition.

It needs failure.

It needs debugging.

It needs realistic constraints.

A strong Terraform learner should practice scenarios like:

  • Building cloud resources from scratch
  • Refactoring repeated code into reusable modules
  • Detecting configuration drift
  • Reading plan output before applying changes
  • Managing remote state safely
  • Understanding when not to apply a change
  • Using Terraform inside CI/CD pipelines
  • Working across AWS, Azure, and GCP patterns
  • Connecting infrastructure decisions to reliability, cost, and security

That is the difference between learning Terraform syntax and learning Terraform engineering.

Terraform Academy’s approach

Terraform Academy focuses on hands-on Infrastructure as Code practice for engineers who want more than surface-level examples.

The platform includes:

  • Interactive Terraform labs
  • Real Terraform syntax practice
  • AWS, Azure, and GCP training paths
  • DevOps and SRE focused scenarios
  • Certification prep
  • Quizzes and skill checks
  • AI coaching
  • Practical cloud infrastructure workflows
  • CI/CD and GitOps style labs

The goal is simple:

Help engineers build the instincts needed to work with Terraform in realistic environments.

Not just “what does this block do?”

But:

  • Why is this resource structured this way?
  • What happens if this state changes?
  • How would this behave in a team workflow?
  • What would break in production?
  • How do I make this reusable?
  • How do I reduce risk before applying?

That is where Terraform becomes a professional skill instead of a memorized command-line routine.

Why this matters

Terraform is used to manage systems that can be expensive, fragile, and business-critical.

A small infrastructure change can affect networking, permissions, storage, compute, uptime, compliance, and cost.

That is why Terraform education should not stop at “hello world” deployments.

Engineers need to understand the surrounding ecosystem:

  • Cloud architecture
  • Security boundaries
  • State management
  • Review workflows
  • Deployment automation
  • Observability
  • Cost awareness
  • Recovery planning

The syntax is the door.

The engineering discipline is the room.

Final thought

Terraform is one of the best tools for modern infrastructure automation, but it rewards precision.

The engineers who become strong with Terraform are not just the ones who can write resource blocks.

They are the ones who can reason clearly about infrastructure change.

That is the skill Terraform Academy is built to develop.

You can explore Terraform Academy here:

Terraform Academy

BMAD Story Automator in Practice: Handing 5 Stories to AI for Autonomous Execution

BMAD 6.6 installation showing the new BMad Automator module

If you’re already used to writing code with BMAD, the real time sink isn’t “writing” — it’s “coordination.”

An Epic has 5, 10, or 20 Stories. Each one goes through spec creation, implementation, test automation, code review, and retrospective. What’s exhausting isn’t any single step — it’s constantly monitoring the process, switching sessions, handling failures, and deciding what to do next.

Story Automator aims to eliminate this “human orchestration” layer.

Last night I ran through the full /bmad-story-automator workflow. Below is a walkthrough of the experience, with screenshots taken along the way.

It doesn’t solve “writing code” — it solves “babysitting the process”

One-sentence summary of Story Automator:

You tell it which Stories to process and what execution strategy to use, and it automatically runs the pipeline: spec creation → implementation → test automation → code review → retrospective — only interrupting you when a genuine human decision is needed.

This is different from running a single Skill command. It’s more like a build cycle orchestrator:

Initialization
  → Read Epic / Sprint status
  → Select Story scope
  → Assess complexity
  → Choose Agent strategy
  → Execute create / dev / automate / review / retro
  → Escalate to human on failures or conflicts

By design, it automates “coordination work” rather than replacing any specific development step.

Upgrade BMAD to the latest version (v6.6)

To try Story Automator, you first need to upgrade BMAD to 6.6. During installation, make sure to check the BMad Automator (Experimental) module.

BMAD 6.6 installation showing the new BMad Automator module

Otherwise, the Automator won’t be available after installing BMAD.

First run: installing the Stop Hook to prevent mid-workflow interruptions

The first time you run /bmad-story-automator, it doesn’t rush to start executing. Instead, it performs initialization checks.

As shown below, it loads the configuration, then attempts to read the current orchestration state. If the state directory doesn’t exist yet, that’s expected for a first run. It then automatically installs a Stop Hook into .claude/settings.json.

Story Automator automatically installing a Stop Hook on first run

Step 1: Select Stories — don’t blindly run all pending items

Before entering the orchestration phase, Story Automator reads the Epic and sprint status, then lets you decide the scope.

The screenshot below shows a typical scenario: across Epics 5, 6, and 7, some Stories are completed while others are still pending. The tool displays these states directly and asks which Stories you want to process.

Story Automator listing pending Stories based on sprint status

Step 2: Complexity isn’t guesswork — it scores first, then assigns Agents

This is one of the most interesting parts of Story Automator.

Instead of dumping all Stories to the same Agent, it first generates a Story complexity matrix. The screenshot shows:

  • Story 5.3 “Server Commands & API Authentication” → 4 pts / Medium
  • Story 6.1 “Expose Axion via SDK AgentMCPServer” → 2 pts / Low
  • Story 6.2 “axion mcp Command & External Agent Integration Verification” → 2 pts / Low
  • Story 7.1 “User Takeover Mechanism Based on SDK Pause Protocol” → 2 pts / Low
  • Story 7.2 “–fast Mode” → 5 pts / Medium

Story Automator auto-generating a Story complexity matrix

More importantly, it doesn’t just give scores — it shows reasoning:

  • authorization / permissions
  • real-time communication
  • high number of acceptance criteria
  • large Story text

This means complexity assessment isn’t a black box. You see not just the “conclusion” but “why it considers this Story harder.” This directly influences the subsequent Agent selection strategy.

In other words, what Story Automator is really doing:

First turning Stories into “schedulable objects,” then deciding who executes them.

Step 3: You can inject custom instructions, but it doesn’t force you to overthink

Next, it asks whether you have custom instructions.

Story Automator asking about custom instructions

For example:

  • Run tests after every modification
  • Prioritize a specific Story
  • Watch out for database migrations

I really like this design because it strikes a nice balance between “fully automatic” and “controllable”:

  • If you have no special requirements, just select none
  • If you have iteration-specific preferences, you can inject them temporarily

In other words, it treats “human experience” as an optional input rather than forcing you to configure a pile of parameters every time.

Step 4: Execution settings determine how aggressively it runs

Next comes execution strategy configuration. The screenshot shows two core questions:

  1. Whether to skip the automate step (test automation)
  2. What’s the maximum number of parallel sessions

Story Automator execution settings interface

The defaults are:

  • Don’t skip automate
  • Maximum 1 parallel session

For most real projects, test automation is the last thing you should skip in the delivery loop; and keeping parallelism at 1 avoids multiple sessions stepping on each other when modifying the same codebase. In other words:

It defaults to “controllable completion” rather than “parallel sprint to the limit.”

Step 5: Different complexity levels automatically map to different Agents

With the complexity matrix in hand, Story Automator can recommend Agent configurations.

The recommended configuration:

  • Low: create / dev / auto / review all use Claude
  • Medium: create / dev / auto / review all use Codex, with Claude as fallback
  • High: Also primarily Codex
  • Retro: Claude for the retrospective phase

Story Automator recommending Agent combinations based on complexity

From this configuration, it’s clear that we’re no longer at the “call a model” level — it’s doing model orchestration.

It also provides strategy options:

  • Suggested: Use the recommended complexity-tiered configuration
  • Uniform: Use the same Agent for all Stories

Different teams can use it differently:

  • Play it safe, follow recommendations
  • Keep behavior consistent, use uniform Agents

And from the configuration summary screenshot later, you can see that this demo ended up saving as all-claude. This perfectly illustrates: recommendations are recommendations, not mandates. You can let the system intelligently assign by complexity, or manually unify to the same Agent type for stability or consistency.

This step makes the system more like a “dispatcher” than a simple command wrapper.

Step 6: Configuration is explicitly saved for easy recovery and review

Once you confirm, Story Automator saves the run configuration. The screenshot shows a configuration summary named all-claude:

Story Automator saving the run configuration and generating a summary

The summary covers several things:

  • Which Epic
  • Story scope
  • Whether there are custom instructions
  • Which Agent is used for create / dev / auto / review / retro respectively
  • Whether test automation is skipped

This kind of “summary page” looks ordinary, but it’s the foundation for an orchestrator to be recoverable, auditable, and reviewable.

Because once automation spans multiple Stories, sessions, and phases, these questions inevitably arise:

  • What if it stops midway?
  • What configuration did I actually choose this time?
  • Why did this batch of Stories use that Agent combination?

With explicitly saved configuration, subsequent recovery or post-hoc analysis won’t be a guessing game.

Real-world experience takeaways

Before going to bed last night, I handed all 5 Stories to Story Automator. When I checked the results in the morning, it had run for 5.5 hours.

Honestly, it was slower than if I had run them manually. At my usual pace, I could have finished these 5 Stories in about 3 hours. As for why it was so much slower, I haven’t carefully analyzed the implementation details yet — but it’s still an experimental release, so getting it to work at all is what matters most.

For now, the best use case is: fire it off before bed.

Another thing I think it does well is that after each Epic completes, it automatically runs a retrospective and appends useful information to project-context.md.

After Epic completion, retrospective results are appended to project-context.md

So my current take is simple:

Running things manually during the day is still faster.
But handing a batch of Stories to it for an overnight run — that’s a scenario where it really shines.

If you’re using BMAD to develop projects, you should definitely give Story Automator a try. It might be the tool that reduces your repetitive coordination time from hours to minutes.

Mnemonic – local-first voice notes with Gemma 4 E4B

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

What I Built

Mnemonic is a macOS menu-bar app and CLI for voice notes that go straight into your daily journal.

Press a hotkey, speak, release. One bullet appears in today’s YYYY-MM-DD.md:

- 14:35 This is a new node. Let me try to see if it'll work. [audio](../audio/2026-05-10/143500.wav)
- 15:12 I want to email Sarah tomorrow about the migration plan. [audio](../audio/2026-05-10/151200.wav)
- 16:08 The bug is in how we handle the empty array case in `merge_chunks`. [audio](../audio/2026-05-10/160800.wav)

That’s the whole product. No titles, no summaries, no auto-generated TODO lists, no extracted entities. No cloud, no telemetry. Transcribed thoughts dropped into a Markdown file you already control.

Early versions over-structured short voice memos – every 30-second thought came back with a title, a “summary” that restated what you said, and an “Actions” list that invented TODOs. v0.2 cut all of that. The model’s job is now narrow: transcribe and lightly clean.

v0.3 added three things on top, each either opt-in or invisible by default:

  • Image attachments. Take a screenshot, then speak – or use Ctrl+Option+Cmd+Space to drag a region and start recording in one motion. Gemma 4 reads the WAV and the PNG together and produces one bullet referencing both. PNG saves next to the audio.
  • Recording queue. Recording is decoupled from structuring. Release the hotkey, the tray goes idle, fire the next one immediately. A background worker drains an on-disk inbox serially; quitting mid-job is safe, the inbox survives. Tray is now binary – gray idle, red recording.
  • Intent routing (opt-in, off by default). A second narrow Gemma 4 call decides whether your note is asking the OS to do something – “remind me to call Sarah at 3 PM” – and if it is, fires a macOS Shortcut you’ve whitelisted. Undoable for 5 seconds. No AppleScript, no shell interpolation.

The file format (YYYY-MM-DD.md at the vault root) matches Obsidian’s Daily Notes plugin, so pointing notes_dir at an Obsidian vault makes bullets land in today’s daily note. Graph view, backlinks, search – all free.

Everything runs locally. No network call leaves the loopback interface. The DMG is signed and notarized; no telemetry crates are linked into the binary.

Demo

Code

  brew tap EduardMaghakyan/tap
  brew install --cask mnemonic

Rust workspace: Tauri 2 for the menu-bar app, clap for the CLI, a shared mnemonic-core crate for audio + markdown + the llama-server client. Single Apple Silicon DMG, code-signed with a Developer ID, notarized, and stapled. MIT licensed.

How I Used Gemma 4

Mnemonic uses three Gemma 4 capabilities through a single local model: native audio, native vision, and lightweight reasoning. They all run against the same llama-server on 127.0.0.1 – no second model, no external API.

Why E4B

Gemma 4 ships in four sizes. Only two are audio-capable, and only one fits a 16 GB laptop. Numbers from the official Gemma 4 model card:

Size Audio? MMLU Pro BBEH CoVoST FLEURS (↓)
E2B ✓ 60.0% 21.9% 33.47 0.09
E4B ✓ 69.4% 33.1% 35.54 0.08
26B A4B ✗ higher higher – –
31B Dense ✗ highest highest – –

E2B and E4B are the only sizes with the audio encoder (~300M params). Both also ship with a ~150M vision encoder. The 26B and 31B are vision-only – no ears. “Use the biggest model that fits” is a non-starter for this product.

Between E2B and E4B, the deltas matter:

  • MMLU Pro 60.0 → 69.4 (+9.4). The difference between a model that fumbles unfamiliar technical vocabulary in voice notes and one that doesn’t.
  • BBEH 21.9 → 33.1 (+11.2). Reasoning quality matters for self-correction (“actually, scratch that…”) and for intent routing – one misclassification fires the wrong Shortcut.
  • CoVoST 33.47 → 35.54 and FLEURS 0.09 → 0.08. Modest audio-recognition wins.

At Q4_K_M the E4B GGUF is 4.98 GB (Hugging Face), plus audio and vision mmprojs (~1 GB combined). Co-resident with an IDE and browser on 16 GB.

One model, one pass – for both audio and vision

The conventional architecture for this product is two stages:

  1. ASR (Whisper, Parakeet, etc.) → raw transcript
  2. Text LLM → clean and structure

Mnemonic does both in one Gemma 4 forward pass. The audio goes into the model with a system prompt that says, in effect: “transcribe this and write it the way the speaker would write it themselves.” Why it works better than two stages:

  • One model in memory, one HTTP round-trip per recording. A 2-stage version means two model downloads, two warm processes, two failure modes.
  • The cleaning prompt operates on the audio, not on a flat transcript. The model can hear pauses, hesitation, restarts – the difference between “I think” as filler and “I think” as opinion. A downstream LLM working from a transcript has already lost that.
  • Lower end-to-end latency.

The same approach works for vision. A two-stage screenshot-with-voice product would be OCR (Tesseract, Apple Vision) → LLM merge. Mnemonic sends the WAV and the PNG to Gemma 4 in one multipart request, and the model produces a bullet that references both. The image isn’t OCR’d in isolation – it’s grounded by what the user said while taking it. Captions come out as “the panic the speaker mentioned in line 42” rather than generic “code editor with red error text.”

Intent routing – a second narrow call, same model

Letting a voice note fire a macOS Shortcut took some thought. I didn’t want to bolt on a tools/function-calling framework, an MCP server, or anything that added attack surface for a side-effect feature running on a user’s machine.

What works is a second Gemma 4 call to the same llama-server, on the already-cleaned transcript, with one job – output a single JSON object:

{ "tool": "create-reminder", "input": "call Sarah at 3 PM" }

…or { "tool": "none" } if the transcript isn’t a request. No tools registry, no plugin protocol – same model, same server.

Most of the work is around what doesn’t fire:

  • Whitelist required. Mnemonic only runs Shortcuts named in allowed_shortcuts. A hallucinated name is refused before it reaches the OS.
  • No AppleScript, no shell interpolation. Input is piped to the Shortcut via stdin (shortcuts run NAME --stdin).
  • Undoable for 5 seconds. The tray menu shows Undo: <name> for the configured window. Click it to run a paired undo-<name> Shortcut.
  • Thought-dumps don’t fire. “I was thinking about reminding Sarah, but maybe she already knows” → {tool: "none"}. Validated at 30/30 on hand-labelled transcripts, including 15 hedged/observational cases that must not fire (docs/spike/intent/PHASE-0-INTENT-FINDINGS.md).
  • Notes are the source of truth. A fire writes a ↳ Ran shortcut "<name>": <input> continuation line under the bullet. Greppable, auditable.

Cost is ~1.7s of warm latency per recording when enabled, off the user’s critical path because the structuring queue is already async.

Implementation

Users run llama-server themselves, on loopback:

llama-server -hf unsloth/gemma-4-E4B-it-GGUF:Q4_K_M --port 5809 --mmproj-auto -ngl 99 -c 8192

The app posts one multipart request per recording (WAV + optional PNG + system prompt) to 127.0.0.1:5809, parses the JSON response, and appends a bullet. With intent routing on, a second JSON-mode call follows.

A few things that matter:

  • JSON mode + thinking mode. response_format: { type: "json_object" } for structure, chat_template_kwargs: { thinking: true } for chain-of-thought. The thinking tokens cost a few hundred ms but improve handling of technical terms.
  • The schema shrunk over three versions, then stayed put. v1 had seven fields (title, tags, summary, cleaned, actions, questions, entities). v1.5 had two. v2 has one: { cleaned: String }. Each prune was a UX win – the simpler the schema, the less the model felt the urge to narrate, summarize, or invent. The system prompt explicitly bans third-party narration (“the speaker”, “the user”, “the recording”) and includes two calibration examples. v0.3’s vision and intent work added new inputs and a new second call, but didn’t grow that schema.
  • Failure is visible, not silent. llama-server unreachable, malformed JSON twice, silent audio – each produces a stub bullet with the timestamp and a redacted error. No recording is ever lost without a trace; the audio is preserved on disk, and the inbox queue means a job in flight survives a quit or crash.

What works well

  • Audio transcription and cleaning. Better than the Whisper + LLM pipeline I started with. Audio-grounded cleaning preserves intent across self-correction and hesitation in a way a downstream LLM on a flat transcript can’t.
  • Vision captions are grounded. They answer “what was the speaker talking about” rather than describing everything in the image. Short, useful captions.
  • Intent routing is conservative. It refuses to fire more often than it fires. That’s the right error direction for a feature that can run OS-level actions.
  • The queue makes the app feel instant. You stop waiting on the model. Tray returns to idle the moment you release the hotkey.
  • Everything is local. Loopback only. No keys to manage, no quota to worry about, no data leaves the machine.

Limits

  • Processing is batched after the fact, not streamed. Fine for journaling; wouldn’t work for live captioning.
  • 16 GB unified memory is the floor. With a heavy IDE + browser open, memory pressure shows.
  • I haven’t tested non-English voice notes systematically. Gemma 4 is multilingual, but I work in English.
  • No speaker diarization, no noise suppression. By design, most voice memos are solo.
  • Intent routing requires building macOS Shortcuts by hand. Powerful if you set it up, but most users won’t.
  • Image OCR is good for screenshots, not for dense document scans. Short captions and inline text work well; multi-column papers don’t.

UX

The whole interaction is one keystroke. Default is Ctrl+Option+Space hold-to-record (push-to-talk). Both the combo and the mode (hold or toggle) are configurable in ~/.config/mnemonic/config.toml, and the config hot-reloads – change a hotkey, save, the app re-registers without a restart. Tray transitions are perceptible within 100 ms.

The CLI ships a mnemonic doctor command with a green/red checklist for every common failure mode (mic permission, llama-server reachable, model loaded, mmproj loaded, paths writable). Brew install creates the mnemonic symlink on PATH automatically – no admin password prompt anywhere in the install path.

Built solo (well… not quite, Claude was involved). Source under MIT. Thanks to the Gemma team for shipping audio, vision, and chain-of-thought in a model that fits a laptop.

I Built a Desktop App That Ends My “Open 7 Terminals and Pray” Routine

Every developer has that one painfully stupid ritual.

Mine was opening multiple terminal tabs every morning, navigating into random folders, running npm run dev, forgetting which server crashed, then pretending I had my life together.

So I built Chronicle.

A desktop app that launches my entire development workspace with one click and lets me inspect Git history like a detective investigating my own questionable commits.

What Chronicle Actually Does

1. One-click workspace launcher

Hit one button and Chronicle spins up all your dev servers
simultaneously.

No more:

  • opening 5 terminals
  • copy-pasting commands
  • forgetting which port exploded
  • spiritually bonding with terminal chaos

It streams live logs into a clean dashboard so you can actually see what’s happening in real time.

2. Git history explorer with AI summaries

This part became way cooler than I expected.

Chronicle visualizes commit history, lets you inspect diffs, and even
uses AI to summarize what changed.

Because sometimes you look at your own commit from 3 days ago and think:

“Who the fuck wrote this?”

Spoiler: it was you. Sleep deprived. Dangerous.

The Stack

Layer Tech
Desktop Shell Tauri v2
Frontend React 19 + Vite + TypeScript
Backend Rust + Tokio
Styling Tailwind CSS v4
Animations Framer Motion
AI Gemini

The Rust backend handles async process spawning while the React frontend
listens to live events from Tauri.

And honestly?

Tauri feels criminally underrated.

Electron said:

“What if your app consumed the RAM of a small nation?”

Tauri said:

“Relax. We brought Rust.”

My Favorite Part

Streaming terminal output from Rust into React in real time felt weirdly
magical.

Rust handles the processes asynchronously, pipes stdout/stderr line-by-line, then emits events directly to the frontend.

Tiny code. Massive payoff.

The result feels like a dev cockpit instead of a pile of terminal tabs
held together by caffeine and denial.

What I Learned Building It

  • Tauri is insanely good for desktop tooling
  • Rust async is terrifying for 20 minutes and then suddenly beautiful
  • Real-time event systems make apps feel alive
  • Developer tooling is fun because you get to solve your own annoying
    problems first

Also:

designing a “simple dashboard” somehow consumed half my lifespan.

Frontend developers are basically pixel war veterans.

Why I Built It

I wanted a tool that made development feel less fragmented.

Something fast.
Something clean.
Something that didn’t require ritual sacrifice every morning just to boot
a workspace.

Chronicle started as:

“I’m tired of opening terminals.”

And somehow evolved into:

“Let’s build an AI-powered developer control center.”

Classic scope creep. Absolutely deserved.

Try It

GitHub Repo

https://github.com/TROJANmocX/Chronicle

If you build dev tools, love Rust, or just enjoy automating away tiny
daily annoyances, you’ll probably have fun with this one.

And if your current workflow involves 14 terminal tabs and raw emotional
damage…

yeah.

This app was made for you.

Local dashboard for monitoring AI agent sessions — no cloud, no account

I’ve been running multiple AI coding agents in parallel — Claude Code sessions, sub-agents spawned mid-task, OpenCode running alongside — and I couldn’t keep track of any of it.

So I built CrewView: a local dashboard that gives you a real-time view of every AI agent session running on your machine.

Here’s what it does:

→ Sessions list — every conversation, with status, duration, and a prompt preview
→ Kanban board — split across Running / Needs Approval / Needs Input
→ Live view — agent cards updating in real time, with sub-agents shown nested under their parent
→ Sub-agent tracking — when Claude Code spawns a Task agent, it appears as a child session
→ Workflow builder — define multi-step agent pipelines and trigger scheduled runs
→ Multi-source — Claude Code (reads local JSONL files) and OpenCode (HTTP API) at the same time

The entire thing is a single Go binary with the React dashboard embedded inside. Install with one command:

curl -fsSL https://raw.githubusercontent.com/ovsec/crewview/main/install.sh | sh

No Docker, no cloud account, no telemetry you didn’t opt into. All data stays in a local SQLite file.

It’s MIT licensed and on GitHub: github.com/ovsec/crewview

If you’re spending serious time with AI coding agents, I’d love to hear what visibility you wish you had. Happy to add it.

AI #DeveloperTools #OpenSource #ClaudeCode #AIAgents