I had 60+ custom skills. Most were fine. But the AI kept forgetting steps, skipping checklists, and silently creating files without telling me where. So I built a three-layer system to fix it.
The Problem
- The agent forgets to load the skill and wings it
- The agent loads the skill but skips the checklist halfway through
- The agent finishes work but doesn't tell you where files ended up
- You fix the same mistake for the third time this week
The Three Layers
| Layer | Trigger | Purpose | Analogy |
|---|---|---|---|
| Skill | Agent loads it (load_skills) | Knowledge injection — teach HOW | A textbook |
| Command | User types /command | Workflow trigger — do THIS | A recipe card |
| Hook | Event fires automatically | Guardrail — don't FORGET | A smoke alarm |
Text
Will the user explicitly say "do X"?
├─ YES → Command (user-triggered workflow)
│ └─ Does it have fixed steps? → YES → Write it as a Command
└─ NO → Does it need to run automatically on events?
├─ YES → Hook (event-driven)
└─ NO → Skill (knowledge injection, loaded on demand)
Layer 1: Skills — The Knowledge Base
- Complex domain knowledge (infrastructure conventions, API patterns)
- Best practices that apply across many tasks
- Reference material the agent needs to "know" but shouldn't memorize
Markdown
## Container Setup Checklist ⚠️ MANDATORY
Before creating ANY container, verify ALL items:
- [ ] Icon: HD icon from official source (min 256x256)
- [ ] Template: XML template with all port/volume mappings
- [ ] Labels: net.unraid.docker.icon, net.unraid.docker.webui
- [ ] Data: Persistent volume mapped to standard path
- [ ] Network: Correct bridge network
- [ ] Permissions: PUID/PGID set correctly
Layer 2: Commands — User-Triggered Workflows
- Users regularly say "do X for me" (blog, backup, deploy)
- The workflow has a fixed sequence of steps
- It benefits from argument parsing (/blog my-topic, /cert my-domain)
| Before (Skill) | After (Command) | Why |
|---|---|---|
| blog-publisher | /blog [topic] | Fixed workflow: write → image → build → deploy → verify |
| backup | /backup | One-click, no decisions needed |
| knowledge-distill | /save [topic] | "Save this experience" is a clear user intent |
| release-note | /release [version] | Generates notes + visual card on demand |
| network-proxy | /network [action] | Switches proxy profiles — explicit action |
Markdown
---
description: "Publish a blog post to my portfolio site"
argument-hint: "[topic or title]"
---
# /blog — Publish Blog Post
## PHASE 1: PREPARE
1. Load the blog-publisher skill for format reference
2. Generate URL-friendly slug from topic
...
## PHASE 2: WRITE CONTENT
...
## RULES
- MUST have both English and Chinese sections
- MUST run build before deploying
Layer 3: Hooks — Automatic Guardrails
Hook Event Types
| Event | When It Fires | Use Case |
|---|---|---|
| Stop | Agent finishes a response | Quality gates, reminders |
| PreToolUse | Before calling a tool | Intercept, remind, inject context |
| PostToolUse | After a tool returns | Validate output, optimize |
| PreCompact | Before context compression | Save important knowledge |
The Hook Protocol
Bash
#!/usr/bin/env bash
set -euo pipefail
INPUT=$(cat) # JSON from stdin
# Parse and decide
RESULT=$(echo "$INPUT" | python3 -c "
import sys, json
data = json.load(sys.stdin)
tool = data.get('tool_name', '')
command = data.get('tool_input', {}).get('command', '')
if 'docker' in command and 'your-server' in command:
print('REMIND')
else:
print('SKIP')
" 2>/dev/null || echo "SKIP")
if [ "$RESULT" = "REMIND" ]; then
cat <<'EOF'
{
"decision": "allow",
"reason": "⚠️ DOCKER CHECKLIST:\n1. HD icon from official source\n2. XML template with all mappings\n3. Required labels set\n4. Persistent volumes configured\n5. Correct network bridge\n6. PUID/PGID permissions"
}
EOF
else
echo '{"decision": "allow"}'
fi
What I Built
| Hook | Event | What It Does |
|---|---|---|
| verify-before-complete | Stop | Blocks "done!" claims without evidence (no test/build run) |
| auto-changelog | Stop | Reminds to update changelog when code changed |
| artifact-completion | Stop | Reminds to show file paths, offer to open files |
| save-before-compact | PreCompact | Saves key knowledge before context window compresses |
| debug-reminder | PreToolUse/Bash | Detects blind retry patterns, reminds to analyze root cause |
| docker-checklist | PreToolUse/Bash | Injects container checklist when Docker commands detected |
| upload-checklist | PreToolUse/API | Injects upload checklist when document API tools are called |
| image-optimization | PostToolUse/Write | Flags images >200KB that should be compressed |
Hook Registration
Json
{
"hooks": {
"Stop": [{
"matcher": "",
"hooks": [
{"type": "command", "command": "~/.agents/hooks/verify-before-complete.sh"},
{"type": "command", "command": "~/.agents/hooks/auto-changelog.sh"},
{"type": "command", "command": "~/.agents/hooks/artifact-completion.sh"}
]
}],
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{"type": "command", "command": "~/.agents/hooks/debug-reminder.sh"},
{"type": "command", "command": "~/.agents/hooks/docker-checklist.sh"}
]
},
{
"matcher": "SomeApiTool",
"hooks": [{"type": "command", "command": "~/.agents/hooks/upload-checklist.sh"}]
}
],
"PostToolUse": [{
"matcher": "Write",
"hooks": [{"type": "command", "command": "~/.agents/hooks/image-optimization.sh"}]
}]
}
}
- Matcher = tool name, not argument content. To filter by arguments, parse stdin JSON inside the script.
- Multiple hooks per matcher — they all execute. A single Bash matcher can run both a debug reminder and a Docker checklist.
- Same script, multiple matchers — register the same script under different tool names if it applies to several tools.
The Three-Layer Defense
📚 Layer 1: Skill (root cause fix)
Teaches the agent HOW to do it right
Works when: agent loads the skill
Fails when: agent forgets to load it
Works when: agent loads the skill
Fails when: agent forgets to load it
↓ Agent didn't load the skill?
🚨 Layer 2: Hook (automatic guardrail)
Fires automatically, no agent action needed
Works when: relevant event occurs
Fails when: agent ignores the reminder
Works when: relevant event occurs
Fails when: agent ignores the reminder
↓ Agent ignored the hook?
🛡️ Layer 3: Global Rule (permanent baseline)
Loaded into EVERY session automatically
Lightest weight but most persistent
Example: "Always show file paths after creating"
Lightest weight but most persistent
Example: "Always show file paths after creating"
| Mistake Type | Best Layer | Why |
|---|---|---|
| Doesn't know how (missing knowledge) | Skill | Needs full documentation |
| Knows but forgets (skipped steps) | Hook | Auto-trigger, no memory needed |
| Recurring bad habit (universal) | Global Rule | Always active, zero overhead |
| Complex multi-step omission | Skill + Hook | Skill teaches, hook reminds |
Practical Recommendations
Start with Skills, Graduate to Commands
Hooks Are Reminders, Not Enforcers
Keep Global Rules Minimal
- "Always show file paths after creating files"
- "Always load skill X for domain Y operations"
- "Never skip required labels on containers"
Test Your Hooks
Bash
# Positive: should trigger
echo '{"tool_name":"Bash","tool_input":{"command":"docker run..."}}' \
| bash ~/.agents/hooks/docker-checklist.sh
# Negative: should be silent
echo '{"tool_name":"Bash","tool_input":{"command":"ls -la"}}' \
| bash ~/.agents/hooks/docker-checklist.sh
The 80/20 of Agent Mistakes
- Forgot to follow the process → Skill checklist (Layer 1)
- Didn't tell me what it did → Stop hook + global rule (Layer 2+3)
- Skipped verification → Stop hook with evidence check (Layer 2)
Results
- 13 skills → commands (anything with "Phase 1, 2, 3" structure)
- 8 hook scripts covering Stop, PreToolUse, PostToolUse, and PreCompact events
- 3 global rules for universal behaviors
- ~70% reduction in "forgot the checklist" errors (estimated from personal experience)
- 100% of hooks pass both positive and negative tests
Talk to Your Agent: One Message Does It All
💬 You: /blog opencode-skill-command-hook 🤖 Agent: Auto-loads blog-publisher skill → extracts content → generates cover → builds & deploys → verifies live 💬 You: Publish to WeChat MP 🤖 Agent: Loads wechat-mp-publisher skill → converts HTML → uploads cover → creates draft → reminds you to review ⚙️ Hook (background): Auto-checks file paths reported, images compressed, changelog updatedThe entire process takes two sentences from you. Skills provide the how-to knowledge, Commands orchestrate the what-to-do steps, Hooks ensure nothing's missed in the background — each layer does its job so you just make requests.That's the ultimate goal of the three-layer system: turn your AI agent from an intern who needs constant correction into a reliable colleague.
Let AI Do It
Markdown
I want to organize my AI agent skills into a three-layer defense system (Skills → Commands → Hooks).
Reference: https://mjshao.fun/blog/opencode-skill-command-hook
My setup:
- AI agent tool: [ask me — OpenCode, Claude Code, Cursor, etc.]
- Skills directory: [ask me — e.g. ~/.agents/skills/]
- Number of existing skills: [ask me]
- Top 3 recurring agent mistakes: [ask me]
Steps:
1. Scan my skills directory and list all skills with their structure
2. Identify skills with fixed step-by-step workflows → convert to /commands
3. Analyze my top recurring mistakes → create hook scripts (Stop, PreToolUse, PostToolUse)
4. Extract universal behavior rules → write to CLAUDE.md or AGENTS.md as global rules
5. Test each hook with positive (should trigger) and negative (should stay silent) cases
6. Output a summary: how many skills→commands, hooks created, rules extracted
我有 60 多个自定义 skill,大部分没问题。但 AI 总是忘步骤、跳过清单、做完文件不告诉我存哪了。所以我搞了个三层防御体系来治这些毛病。
问题出在哪
- Agent 忘了加载 skill,自己瞎搞
- 加载了 skill 但做到一半跳过 checklist
- 做完工作不告诉你文件存哪了
- 同一个错误你已经纠正第三次了
三层结构
| 层级 | 触发方式 | 用途 | 类比 |
|---|---|---|---|
| Skill | Agent 加载 (load_skills) | 知识注入——教"怎么做" | 教科书 |
| Command | 用户输入 /command | 工作流触发——做"这件事" | 菜谱卡片 |
| Hook | 事件自动触发 | 防遗漏——别"忘了" | 烟雾报警器 |
Text
用户会主动说"帮我做X"吗?
├─ YES → Command(用户触发的工作流)
│ └─ 有固定步骤?→ YES → 写成 Command
└─ NO → 需要在特定事件时自动运行?
├─ YES → Hook(事件驱动)
└─ NO → Skill(知识注入,按需加载)
Layer 1:Skill——知识库
- 复杂的领域知识(基础设施规范、API 模式)
- 跨多个任务的最佳实践
- Agent 需要"知道"但不用死记的参考资料
Markdown
## 容器部署清单 ⚠️ 必须执行
创建任何容器前,确认以下所有项目:
- [ ] 图标:官方源 HD 图标(最小 256x256)
- [ ] 模板:包含所有端口/卷映射的 XML 模板
- [ ] 标签:必要的 docker labels 全部设置
- [ ] 数据:持久化卷映射到标准路径
- [ ] 网络:正确的 bridge 网络
- [ ] 权限:PUID/PGID 正确设置
Layer 2:Command——用户触发的工作流
- 用户经常说"帮我做 X"(发博客、备份、部署)
- 工作流有固定步骤
- 需要参数解析(/blog 我的主题、/cert 我的域名)
| 转换前(Skill) | 转换后(Command) | 为什么 |
|---|---|---|
| blog-publisher | /blog [topic] | 固定流程:写 → 配图 → 构建 → 部署 → 验证 |
| backup | /backup | 一键执行,不需要决策 |
| knowledge-distill | /save [topic] | "存一下这个经验"是明确的用户意图 |
| release-note | /release [version] | 按需生成 release notes + 视觉卡片 |
Layer 3:Hook——自动护栏
Hook 事件类型
| 事件 | 触发时机 | 用途 |
|---|---|---|
| Stop | Agent 完成回复 | 质量门禁、完成提醒 |
| PreToolUse | 调用工具前 | 拦截、提醒、注入上下文 |
| PostToolUse | 工具执行后 | 验证输出、优化 |
| PreCompact | 上下文压缩前 | 保存重要知识 |
Hook 通信协议
Bash
#!/usr/bin/env bash
set -euo pipefail
INPUT=$(cat) # stdin 接收 JSON
RESULT=$(echo "$INPUT" | python3 -c "
import sys, json
data = json.load(sys.stdin)
command = data.get('tool_input', {}).get('command', '')
if 'docker' in command and 'your-server' in command:
print('REMIND')
else:
print('SKIP')
" 2>/dev/null || echo "SKIP")
if [ "$RESULT" = "REMIND" ]; then
cat <<'EOF'
{
"decision": "allow",
"reason": "⚠️ 容器部署清单:\n1. HD 图标\n2. XML 模板\n3. 必要标签\n4. 持久化卷\n5. 网络配置\n6. 权限设置"
}
EOF
else
echo '{"decision": "allow"}'
fi
我建了什么
| Hook | 事件 | 功能 |
|---|---|---|
| verify-before-complete | Stop | 阻止没跑测试/构建就声称"搞定了" |
| auto-changelog | Stop | 有代码变更时提醒更新 changelog |
| artifact-completion | Stop | 提醒显示文件路径、询问是否打开 |
| save-before-compact | PreCompact | 上下文压缩前保存关键知识 |
| debug-reminder | PreToolUse/Bash | 检测盲目重试,提醒先分析 root cause |
| docker-checklist | PreToolUse/Bash | 检测到容器命令时注入部署清单 |
| upload-checklist | PreToolUse/API | 检测到文档 API 调用时注入上传清单 |
| image-optimization | PostToolUse/Write | 标记 >200KB 的图片应该压缩 |
关键经验
- Matcher 匹配的是工具名,不是参数内容。参数过滤要在脚本内部解析 stdin JSON。
- 一个 matcher 可以挂多个 hook——都会执行。一个 Bash matcher 下可以同时挂调试提醒和容器清单。
- 同一个脚本可以注册多个 matcher——如果同一个检查逻辑适用于多个工具。
三层防御机制
📚 Layer 1: Skill(根因修复)
教 Agent 怎么做对
有效条件:Agent 加载了 skill
失效条件:Agent 忘了加载
有效条件:Agent 加载了 skill
失效条件:Agent 忘了加载
↓ Agent 没加载 skill?
🚨 Layer 2: Hook(自动护栏)
自动触发,不需要 Agent 主动做任何事
有效条件:相关事件发生
失效条件:Agent 无视提醒
有效条件:相关事件发生
失效条件:Agent 无视提醒
↓ Agent 无视了 hook?
🛡️ Layer 3: Global Rule(永久基线)
每个会话自动加载
最轻量但最持久
例:"创建文件后必须显示路径"
最轻量但最持久
例:"创建文件后必须显示路径"
| 错误类型 | 最佳层级 | 原因 |
|---|---|---|
| 不知道怎么做(缺知识) | Skill | 需要完整文档 |
| 知道但忘了做(遗漏步骤) | Hook | 自动触发,不依赖记忆 |
| 反复犯的通用坏习惯 | Global Rule | 始终生效,零开销 |
| 复杂多步骤遗漏 | Skill + Hook | Skill 教怎么做,Hook 提醒别漏 |
实用建议
先写 Skill,再升级成 Command
Hook 是提醒器,不是强制器
Global Rule 要精简
- "创建文件后必须显示完整路径"
- "某类操作必须加载对应 skill"
- "容器部署不能跳过必要标签"
测试你的 Hook
Bash
# 正向:应该触发
echo '{"tool_name":"Bash","tool_input":{"command":"docker run..."}}' \
| bash ~/.agents/hooks/docker-checklist.sh
# 反向:应该静默
echo '{"tool_name":"Bash","tool_input":{"command":"ls -la"}}' \
| bash ~/.agents/hooks/docker-checklist.sh
Agent 犯错的 80/20 法则
- 忘了走流程 → Skill 清单(Layer 1)
- 不告诉我做了什么 → Stop hook + global rule(Layer 2+3)
- 跳过验证 → Stop hook + 证据检查(Layer 2)
最终成果
- 13 个 skill → command(所有"Phase 1, 2, 3"结构的)
- 8 个 hook 脚本,覆盖 Stop、PreToolUse、PostToolUse、PreCompact 事件
- 3 条 global rule,覆盖通用行为
- "忘了走清单"的错误降低约 70%(个人体感估计)
- 所有 hook 100% 通过正向和反向测试
给 AI Agent:一句话搞定
💬 你:/blog opencode-skill-command-hook 🤖 Agent:自动加载 blog-publisher skill → 提取内容 → 生成配图 → 构建部署 → 验证上线 💬 你:发到微信公众号 🤖 Agent:加载 wechat-mp-publisher skill → 转换 HTML → 上传封面 → 创建草稿 → 提醒你审核 ⚙️ Hook(后台):自动检查文件路径是否告知、图片是否压缩、changelog 是否更新整个过程你只说了两句话。Skill 提供了「怎么做」的知识,Command 编排了「做什么」的步骤,Hook 在后台确保「别漏了」——三层各司其职,你只管提需求。这就是三层体系的最终目的:把 AI Agent 从一个需要反复纠正的实习生,变成一个靠谱的同事。
让 AI 帮你搞定
Markdown
帮我把现有的 AI agent skill 整理成三层防御体系(Skill → Command → Hook)。
参考:https://mjshao.fun/blog/opencode-skill-command-hook
我的环境:
- AI agent 工具:[问我——OpenCode、Claude Code、Cursor 等]
- Skill 目录:[问我——比如 ~/.agents/skills/]
- 现有 skill 数量:[问我]
- 最常犯的 3 个错误:[问我]
执行步骤:
1. 扫描 skill 目录,列出所有 skill 及其结构
2. 识别有固定步骤流程的 skill → 转成 /command
3. 针对我最常犯的错误 → 创建 hook 脚本(Stop、PreToolUse、PostToolUse)
4. 提取通用行为规则 → 写入 CLAUDE.md 或 AGENTS.md 作为 global rule
5. 每个 hook 做正向(应触发)和反向(应静默)测试
6. 输出清单:多少 skill→command、创建了哪些 hook、提取了哪些规则