Companion notes to IndyDevDan's 4-Layer Architecture video. Maps his concepts to Claude Code's built-in primitives.
Also more in CLIAI/agentic-4layer-architecture repo
Custom commands are markdown files in .claude/commands/ that act as reusable,
parameterized prompts invoked with /command-name in Claude Code.
What they are:
- Markdown files (
.md) stored in.claude/commands/(project-scoped) or~/.claude/commands/(global) - Invoked via
/command-nameinside a Claude Code session - Can accept arguments via
$ARGUMENTSplaceholder - Can reference files with
@file-pathsyntax
IndyDevDan's "Orchestration Layer" maps directly to this — his UI review prompt, Amazon automation prompt, and higher-order prompts are all custom commands.
Example structure:
.claude/commands/
├── ui-review.md # Orchestrates parallel QA agents
├── automate.md # Higher-order prompt: wraps a workflow
└── deploy-check.md # Pre-deploy validation
Example command (.claude/commands/ui-review.md):
# UI Review
## Purpose
Run parallel user story validation against the application UI.
## Variables
- stories_dir: `./ai-review/stories/`
- output_dir: `./ai-review/output/`
## Workflow
1. Discover all `.story.md` files in stories_dir
2. For each story, spawn a sub-agent with @browser-qa agent
3. Each agent: parse steps → navigate → screenshot → report pass/fail
4. Collect results, generate summary reportKey insight from the video: Commands are the orchestration layer — they compose skills and agents into repeatable workflows. Think of them as the API layer for your agentic system.
Agent definitions live in .claude/agents/ as markdown files with YAML front
matter. They define specialized sub-agents that can be referenced with
@agent-name in prompts or spawned programmatically.
What they are:
- Markdown files in
.claude/agents/(project) or~/.claude/agents/(global) - YAML front matter can specify:
allowed_tools,model,temperature - Referenced via
@agent-namein commands or conversation - Can be spawned as sub-agents for parallel execution
IndyDevDan's agent layer — his Playwright Browser Agent, Claude Browser Agent, and Browser QA Agent are all custom agents that scale skills into specialized workflows.
Example structure:
.claude/agents/
├── browser-qa.md # UI validation via user stories
├── playwright-browser.md # Generic Playwright browser tasks
└── claude-browser.md # Chrome-flag browser automation
Example agent (.claude/agents/browser-qa.md):
---
allowed_tools:
- Bash
- Read
- Write
- Glob
---
# Browser QA Agent
## Purpose
UI validation agent that works through user stories using Playwright CLI.
## Variables
- session_name: unique per story
- screenshots_dir: `./output/{story_name}/screenshots/`
## Workflow
1. Parse user story into discrete steps
2. Create output directory for this story
3. For each step:
- Execute via `playwright-cli` (headless)
- Take screenshot: `playwright-cli screenshot --session {session_name}`
- Validate expected outcome
4. Report PASS/FAIL with screenshot evidence
5. Close browser session
## Output Format
Story: {name}
URL: {url}
Result: PASS | FAIL
Steps: {completed}/{total}
Evidence: {screenshots_dir}Key insight: Agents give you scale. The skill is raw capability; the agent is how you parallelize and specialize it. With Claude Code's team orchestration, you can spawn multiple agents working toward a common goal.
Skills are Claude Code's mechanism for encoding Standard Operating Procedures. They can include both instructions (markdown) AND executable scripts.
What they are:
- Directory-based:
.claude/skills/{skill-name}/containing aSKILL.mdand optional scripts SKILL.mddefines the capability, usage patterns, defaults- Scripts (bash, python, etc.) can live alongside the skill markdown
- Activated in agent front matter or referenced in commands
IndyDevDan's "Capability Layer" — his Playwright Browser and Claude Browser skills are foundational capabilities that agents and commands build upon.
Example structure:
.claude/skills/
├── playwright-browser/
│ ├── SKILL.md # Instructions + defaults
│ ├── setup.sh # Install/configure Playwright
│ └── helpers/
│ └── take-screenshot.sh
├── claude-browser/
│ └── SKILL.md
└── data-pipeline/
├── SKILL.md
└── validate.py # Data validation script
Example skill (.claude/skills/playwright-browser/SKILL.md):
# Playwright Browser Skill
Token-efficient CLI for browser automation.
Runs headless, supports parallel sessions, persistent profiles.
## Setup
Run `./setup.sh` to install Playwright and browsers.
## Defaults
- Headless: true (override with `--headed` for debugging)
- Timeout: 30s per action
- Screenshots: saved to `./screenshots/` by default
## Core Commands
- `playwright-cli navigate --url {url} --session {name}`
- `playwright-cli click --selector {sel} --session {name}`
- `playwright-cli type --selector {sel} --text {text} --session {name}`
- `playwright-cli screenshot --session {name} --output {path}`
- `playwright-cli close --session {name}`
## Best Practices
- Always use named sessions for state isolation
- Take screenshots after each significant action
- Use `--wait-for` to handle dynamic content
- Prefer CSS selectors over XPath for reliabilityScripts bundled with skills — this is a key differentiator. Skills aren't just prompts; they can include executable code:
#!/bin/bash
# .claude/skills/playwright-browser/setup.sh
# Ensures Playwright is installed and browsers are available
if ! command -v playwright &> /dev/null; then
npm install -g playwright
npx playwright install chromium
echo "Playwright installed successfully"
else
echo "Playwright already available"
fiKey insight: Skills are the foundational layer. They encode domain knowledge, tool-specific patterns, and opinionated defaults. Build skills for capabilities; build agents and commands on top of them.
Hooks are shell commands that execute automatically in response to Claude Code lifecycle events. They enable guardrails, automation, and integration without modifying your prompts or skills.
What they are:
- Configured in
.claude/settings.json(project) or~/.claude/settings.json(global) - Fire on specific events in the Claude Code lifecycle
- Can modify behavior, enforce policies, or trigger side effects
- Run synchronously — Claude Code waits for them to complete
Hook events:
| Event | When it fires | Use case |
|---|---|---|
PreToolUse |
Before a tool executes | Validate/block dangerous operations |
PostToolUse |
After a tool completes | Log actions, post-process results |
Notification |
On notifications | Route alerts to Slack, desktop, etc. |
Stop |
When agent stops | Cleanup, final reporting |
Example configuration (.claude/settings.json):
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"command": "python3 .claude/hooks/validate-bash.py"
}
],
"PostToolUse": [
{
"matcher": "Write",
"command": "bash .claude/hooks/post-write-lint.sh"
}
],
"Notification": [
{
"command": "bash .claude/hooks/notify-desktop.sh"
}
],
"Stop": [
{
"command": "bash .claude/hooks/cleanup-screenshots.sh"
}
]
}
}Example hook — block dangerous commands:
#!/usr/bin/env python3
# .claude/hooks/validate-bash.py
# Reads tool input from stdin, blocks dangerous patterns
import json, sys
data = json.load(sys.stdin)
command = data.get("tool_input", {}).get("command", "")
blocked = ["rm -rf /", "DROP TABLE", "format c:", "--no-verify"]
for pattern in blocked:
if pattern in command:
print(json.dumps({
"decision": "block",
"reason": f"Blocked dangerous pattern: {pattern}"
}))
sys.exit(0)
# Allow by default
print(json.dumps({"decision": "allow"}))Example hook — auto-lint after file writes:
#!/bin/bash
# .claude/hooks/post-write-lint.sh
# Auto-format files after Claude writes them
FILE=$(cat - | jq -r '.tool_input.file_path // empty')
if [[ "$FILE" == *.py ]]; then
ruff format "$FILE" 2>/dev/null
elif [[ "$FILE" == *.ts ]] || [[ "$FILE" == *.tsx ]]; then
npx prettier --write "$FILE" 2>/dev/null
fiKey insight: Hooks are the guardrails and glue layer. They don't appear in IndyDevDan's 4-layer stack directly, but they complement it — ensuring your agentic workflows stay safe, consistent, and integrated with your dev environment.
┌─────────────────────────────────────────────┐
│ Layer 4: Just Files / Task Runners │ ← Reusability
│ (just, make, scripts — entry points) │
├─────────────────────────────────────────────┤
│ Layer 3: Custom Commands │ ← Orchestration
│ (.claude/commands/*.md — reusable prompts) │
├─────────────────────────────────────────────┤
│ Layer 2: Custom Agents │ ← Scale
│ (.claude/agents/*.md — specialized bots) │
├─────────────────────────────────────────────┤
│ Layer 1: Skills │ ← Capability
│ (.claude/skills/*/SKILL.md + scripts) │
├─────────────────────────────────────────────┤
│ Foundation: Hooks │ ← Guardrails & Glue
│ (.claude/settings.json — lifecycle events) │
└─────────────────────────────────────────────┘
Each layer builds on the one below it. Skills provide raw capabilities. Agents scale and specialize those capabilities. Commands orchestrate agents into repeatable workflows. Task runners (just, make) provide the top-level entry points. Hooks run throughout, enforcing safety and consistency.
Based on IndyDevDan's 4-Layer Claude Code architecture. Video: My 4-Layer Claude Code Playwright CLI Skill