Before you write any skill code, you need to understand the three-layer stack that powers everything.
01
OpenClaw Gateway
The always-on Node.js process running on the VPS. It's the brain — managing sessions, routing messages, executing skills, and connecting to messaging platforms like WhatsApp.
02
Claude Haiku 4.5 (API)
The LLM that does the thinking. Your skill sends structured prompts to Haiku via the Anthropic API. Haiku reasons, classifies, drafts responses. Your skill acts on the output.
03
NemoClaw (OpenShell)
NVIDIA's security layer wrapped around OpenClaw. Enforces sandbox isolation, policy-based network access, and intent verification before any action executes.
04
Skills
JavaScript files you write that define what an agent can do. Skills are the only thing you directly build. Everything else is infrastructure.
You (Jim)
WhatsApp
MacBook Air
iPhone (Dispatch)
↓ approval gate — all actions reviewed
COO Layer
CORA · Chief Operations & Resource Administrator
↓ routes tasks to specialist agents
Phase 1
ARIA · Customer Service
MAX · Marketing
SUQI · Service Quality
Phase 2
CRAIG · Client Relations
HERA · HR
FINN · Finance
Phase 3
DEVI · Development
RITA · IT Operations
↓ all agents run on
Infrastructure
OpenClaw Gateway · VPS Singapore
NemoClaw · OpenShell Security
Claude Haiku 4.5 API
Tailscale · Private Network
02 · Agent Fleet
The WMG agent roster
Click any agent to see their full brief — tools, responsibilities, what they can and can't do, and which skills belong to them.
03 · Skill Structure
Anatomy of a WMG skill
Every skill is a folder with exactly four files. Understand what each one does before you start building.
📄 skill.json
Metadata, trigger type (schedule/event/manual), tools allowed, target agent, and LLM model. This is what OpenClaw reads to know when and how to run your skill.
⚙️ index.js
The main logic. Fetches data, calls the LLM, parses the response, takes action (send WhatsApp, update CRM, file report). This is the only file with actual JavaScript code.
📝 prompt.md
Plain English instructions that tell Claude Haiku how to reason about the task. Classification rules, output format, WMG-specific context. The most important file — get this right first.
🔧 config.json
WMG-specific settings: contact numbers, thresholds, key account lists, escalation rules. Keeps business logic out of code so non-developers can update it without touching index.js.
// skill.json — tells OpenClaw what this skill does and when to run it
{
"name": "wmg-email-triage",
"version": "1.0.0",
"description": "ARIA email triage for WMG Pte Ltd",
"agent": "aria", // which agent this belongs to"trigger": {
"type": "schedule",
"cron": "*/30 * * * *"// runs every 30 minutes
},
"tools": ["gmail", "whatsapp", "memory"], // ONLY list what you need"model": "claude-haiku-4-5", // always use Haiku unless approved otherwise"requires_approval": false, // set true for financial/HR/client actions"draft_and_approve": false// set true for outbound comms
}
// index.js — the skill logic. Keep it clean and linear.const { getEmails, sendWhatsApp, memory } = require('@openclaw/tools');
const { readFile } = require('fs/promises');
const config = require('./config.json');
module.exports = async functionrun({ llm }) {
// 1. FETCH — get the dataconst emails = awaitgetEmails({ unread: true, since: '30m', maxResults: 20 });
if (emails.length === 0) return; // nothing to do — exit cleanly// 2. PROMPT — load instructions and ask Claude Haikuconst prompt = awaitreadFile('./prompt.md', 'utf8');
const result = await llm.complete({
System: prompt,
user: emails.map(e => `FROM: ${e.from}\nSUBJECT: ${e.subject}\nSNIPPET: ${e.snippet}`).join('\n—\n')
});
// 3. PARSE — extract what you need from the LLM responseconst urgent = JSON.parse(result).filter(e => e.priority === 'URGENT');
// 4. ACT — take action (always wrap in try/catch)try {
if (urgent.length > 0) {
awaitsendWhatsApp({ to: config.jim_whatsapp, message: formatSummary(urgent) });
}
await memory.set('aria.last_triage', newDate().toISOString());
} catch (err) {
// 5. ERROR HANDLING — always notify CORA, never crash silentlyawaitnotifyCORA({ agent: 'ARIA', skill: 'wmg-email-triage', error: err.message });
}
};
# prompt.md — plain English instructions for Claude Haiku
# This is the most important file. Spend the most time here.
You are ARIA, WMG Pte Ltd's customer service agent.
WMG is a Singapore-based logistics company.
Analyse each email and classify it as URGENT or ROUTINE.
## URGENT if any of these apply:- Client complaint or escalation about service failure- Shipment delay, customs hold, or cargo issue- Payment dispute or invoice overdue by 30+ days- Request from a named key account (see config for list)- Explicit deadline mentioned within 24 hours## ROUTINE if:- General enquiry answerable within 24 hours- Newsletter, marketing, or automated notification- Internal FYI with no action required## Rules:- When in doubt, classify as URGENT- Never suggest actions that involve committing WMG funds- Always recommend escalation for legal or compliance matters## Output format — JSON array only, no other text:[{"id": "…", "priority": "URGENT|ROUTINE", "reason": "one line max", "suggested_action": "…"}]
// config.json — business settings, not code logic
// Non-developers should be able to update this safely
{
"jim_whatsapp": "+65XXXXXXXX",
"key_accounts": [
"Evergreen Shipping",
"PT Maju Logistics",
"Sinotrans"
],
"escalation_threshold_days": 30,
"urgent_keywords": ["customs hold", "cargo damage", "missed deadline"],
"working_hours_sg": { "start": 8, "end": 18 },
"silent_hours_notify": false, // don't WhatsApp Jim between 10pm-7am"dry_run": false// set true during testing
}
04 · Coding Standards
WMG skill coding standards
These are non-negotiable. Every skill merged to production must follow all of these.
🔒
Least privilege tools
Only declare the tools your skill actually uses in skill.json. If ARIA's triage skill needs Gmail and WhatsApp, list only those — not filesystem, not browser, not shell.
DO: ["gmail","whatsapp"]DON'T: ["*"] or ["shell"]
🛡️
Always catch errors
Every skill must have a try/catch around the ACT phase. On catch, notify CORA with agent name, skill name, and error message. Never let a skill fail silently.
DO: notifyCORA on catchDON'T: silent failures
✅
Draft-and-approve for comms
Any skill that sends messages to clients, partners, or external parties must set "draft_and_approve": true. Jim reviews the draft before it sends. No exceptions for external comms.
Client email → draft firstInternal only → can auto-send
🚫
No financial actions
No skill may initiate, approve, or commit any financial transaction. FINN may draft payment summaries and flag overdue invoices — but never trigger payment. Always requires_approval: true.
Never trigger paymentsDraft + flag only
🧪
Dry-run before enabling
All new skills must pass a dry-run on the iMac dev environment before deployment to the VPS. Set dry_run: true in config.json, review output, then flip to false for production.
iMac → dry-run → VPSNever skip dry-run
📝
Prompt-first development
Write prompt.md before index.js. Test the prompt manually in Claude.ai with sample data. Only start coding once the LLM output is exactly what you need. Bad prompts waste engineering time.
prompt.md firstDon't code before prompting
💰
Use Haiku, justify Sonnet
All skills default to claude-haiku-4-5. If you believe a task requires Sonnet (complex reasoning, multi-document analysis), document why in a PR comment and get approval before deploying.
Default: HaikuSonnet: needs approval
🧱
One skill, one job
Each skill file does exactly one thing. Email triage is one skill. WhatsApp reply is another. SLA monitoring is another. Don't combine multiple functions — it makes debugging impossible.
One responsibilityNo multi-function skills
05 · Dev Workflow
How to build and ship a skill
Follow this exact sequence every time. No shortcuts.
1
Get a brief from CORA
Every skill starts with a written brief: which agent, what trigger, what tools, what output. If you don't have a brief, ask for one before writing anything.
2
Write and test prompt.md first
Open Claude.ai. Paste 5–10 realistic sample inputs. Iterate the prompt until the output JSON is exactly right. Save all test cases — you'll need them later.
Test in claude.ai before touching code
3
Create the skill folder on iMac
Work locally on your iMac M1 dev machine. Create the four files. Set dry_run: true in config.json. Never develop directly on the VPS.
~/.openclaw/skills/wmg-[agentname]-[function]/
4
Dry-run on iMac
Install and run the skill locally. Review every line of output. Check what would have been sent, filed, or actioned. Fix any issues before proceeding.
openclaw skills install [name] && openclaw skills run [name] --dry-run
5
Push to VPS via Tailscale
Once dry-run is clean, copy the skill to the VPS production gateway. Set dry_run: false only after a second dry-run on the VPS confirms everything.
Enable the skill on the VPS. Watch the first 3 live runs in the dashboard. Check logs. Confirm CORA receives correct output. Only then mark it as production-stable.
Print this. Stick it on your wall. Follow it every time.
✅ Write prompt.md before index.js — always✅ Test with 10+ real sample inputs before shipping✅ Set dry_run: true until you've seen 3 clean dry-runs✅ Catch all errors and notify CORA✅ Use claude-haiku-4-5 as default model✅ Add every skill to NemoClaw policy before enabling✅ Use draft_and_approve: true for all external comms✅ Keep config.json for business settings (thresholds, names, numbers)✅ Log what the skill did to memory after each run✅ One skill = one job, named after what it does
🚫 Never deploy directly to VPS without dry-run on iMac first🚫 Never use wildcard tools: ["*"] in skill.json🚫 Never trigger financial transactions — draft only🚫 Never hardcode API keys, tokens, or passwords in skill files🚫 Never use shell tool unless explicitly approved by Jim🚫 Never send external comms without draft_and_approve: true🚫 Never use Sonnet model without written approval🚫 Never combine two functions into one skill file🚫 Never skip the NemoClaw policy step🚫 Never fail silently — always catch and report to CORA