- SPEC.md: Add new source files, update config location, document conversation catch-up feature, fix message flow description - customize/SKILL.md: Fix file references (was Python, now TypeScript), update launchd service name Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
18 KiB
NanoClaw Specification
A personal Claude assistant accessible via WhatsApp, with persistent memory per conversation, scheduled tasks, and email integration.
Table of Contents
- Architecture
- Folder Structure
- Configuration
- Memory System
- Session Management
- Message Flow
- Commands
- Scheduled Tasks
- MCP Servers
- Deployment
- Security Considerations
Architecture
┌─────────────────────────────────────────────────────────────────────┐
│ NanoClaw │
│ (Single Node.js Process) │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌────────────────────┐ │
│ │ WhatsApp │────────────────────▶│ SQLite Database │ │
│ │ (baileys) │◀────────────────────│ (messages.db) │ │
│ └──────────────┘ store/send └─────────┬──────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ MESSAGE LOOP │ │
│ │ • Polls SQLite for new messages every 2 seconds │ │
│ │ • Filters: only registered groups, only trigger word │ │
│ │ • Loads session ID for conversation continuity │ │
│ │ • Invokes Claude Agent SDK in the group's directory │ │
│ │ • Sends response back to WhatsApp │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ CLAUDE AGENT SDK │ │
│ │ │ │
│ │ Working directory: groups/{group-name}/ │ │
│ │ Context loaded: │ │
│ │ • ../CLAUDE.md (global memory) │ │
│ │ • ./CLAUDE.md (group-specific memory) │ │
│ │ │ │
│ │ Available MCP Servers: │ │
│ │ • gmail-mcp (read/send email) │ │
│ │ • schedule-task-mcp (create cron jobs) │ │
│ │ │ │
│ │ Built-in Tools: │ │
│ │ • WebSearch, WebFetch (internet access) │ │
│ │ • Read, Write, Edit (file operations in group folder) │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────────┘
Technology Stack
| Component | Technology | Purpose |
|---|---|---|
| WhatsApp Connection | Node.js (@whiskeysockets/baileys) | Connect to WhatsApp, send/receive messages |
| Message Storage | SQLite (better-sqlite3) | Store messages for polling |
| Agent | @anthropic-ai/claude-agent-sdk | Run Claude with tools and MCP servers |
| Runtime | Node.js 18+ | Single unified process |
Folder Structure
nanoclaw/
├── CLAUDE.md # Project context for Claude Code
├── SPEC.md # This specification document
├── README.md # User documentation
├── package.json # Node.js dependencies
├── tsconfig.json # TypeScript configuration
├── .mcp.json # MCP server configuration (reference)
├── .gitignore
│
├── src/
│ ├── index.ts # Main application (WhatsApp + routing + agent)
│ ├── config.ts # Configuration constants
│ ├── types.ts # TypeScript interfaces
│ ├── db.ts # Database initialization and queries
│ └── auth.ts # Standalone WhatsApp authentication
│
├── dist/ # Compiled JavaScript (gitignored)
│
├── .claude/
│ └── skills/
│ ├── setup/
│ │ └── SKILL.md # /setup skill
│ └── customize/
│ └── SKILL.md # /customize skill
│
├── groups/
│ ├── CLAUDE.md # Global memory (all groups read this)
│ ├── main/ # Self-chat (main control channel)
│ │ ├── CLAUDE.md # Main channel memory
│ │ └── logs/ # Task execution logs
│ └── {Group Name}/ # Per-group folders (created on registration)
│ ├── CLAUDE.md # Group-specific memory
│ ├── logs/ # Task logs for this group
│ └── *.md # Files created by the agent
│
├── store/ # Local data (gitignored)
│ ├── auth/ # WhatsApp authentication state
│ └── messages.db # SQLite message database
│
├── data/ # Application state (gitignored)
│ ├── sessions.json # Active session IDs per group
│ ├── archived_sessions.json # Old sessions after /clear
│ ├── registered_groups.json # Group JID → folder mapping
│ └── router_state.json # Last processed timestamp + last agent timestamps
│
├── logs/ # Runtime logs (gitignored)
│ ├── nanoclaw.log # stdout
│ └── nanoclaw.error.log # stderr
│
└── launchd/
└── com.nanoclaw.plist # macOS service configuration
Configuration
Configuration constants are in src/config.ts:
export const ASSISTANT_NAME = process.env.ASSISTANT_NAME || 'Andy';
export const POLL_INTERVAL = 2000;
export const STORE_DIR = './store';
export const GROUPS_DIR = './groups';
export const DATA_DIR = './data';
export const TRIGGER_PATTERN = new RegExp(`^@${ASSISTANT_NAME}\\b`, 'i');
export const CLEAR_COMMAND = '/clear';
Changing the Assistant Name
Set the ASSISTANT_NAME environment variable:
ASSISTANT_NAME=Bot npm start
Or edit the default in src/config.ts. This changes:
- The trigger pattern (messages must start with
@YourName) - The response prefix (
YourName:added automatically)
Placeholder Values in launchd
Files with {{PLACEHOLDER}} values need to be configured:
{{PROJECT_ROOT}}- Absolute path to your nanoclaw installation{{NODE_PATH}}- Path to node binary (detected viawhich node){{HOME}}- User's home directory
Memory System
NanoClaw uses a hierarchical memory system based on CLAUDE.md files.
Memory Hierarchy
| Level | Location | Read By | Written By | Purpose |
|---|---|---|---|---|
| Global | groups/CLAUDE.md |
All groups | Main only | Preferences, facts, context shared across all conversations |
| Group | groups/{name}/CLAUDE.md |
That group | That group | Group-specific context, conversation memory |
| Files | groups/{name}/*.md |
That group | That group | Notes, research, documents created during conversation |
How Memory Works
-
Agent Context Loading
- Agent runs with
cwdset togroups/{group-name}/ - Claude Agent SDK with
settingSources: ['project']automatically loads:../CLAUDE.md(parent directory = global memory)./CLAUDE.md(current directory = group memory)
- Agent runs with
-
Writing Memory
- When user says "remember this", agent writes to
./CLAUDE.md - When user says "remember this globally" (main channel only), agent writes to
../CLAUDE.md - Agent can create files like
notes.md,research.mdin the group folder
- When user says "remember this", agent writes to
-
Main Channel Privileges
- Only the "main" group (self-chat) can write to global memory
- This prevents other groups from modifying shared context
Session Management
Sessions enable conversation continuity - Claude remembers what you talked about.
How Sessions Work
- Each group has a session ID stored in
data/sessions.json - Session ID is passed to Claude Agent SDK's
resumeoption - Claude continues the conversation with full context
data/sessions.json:
{
"main": "session-abc123",
"Family Chat": "session-def456"
}
The /clear Command
When a user sends /clear in any group:
- Current session ID is moved to
data/archived_sessions.json - Session ID is removed from
data/sessions.json - Next message starts a fresh session
- Memory files are NOT deleted - only the session resets
Message Flow
Incoming Message Flow
1. User sends WhatsApp message
│
▼
2. Baileys receives message via WhatsApp Web protocol
│
▼
3. Message stored in SQLite (store/messages.db)
│
▼
4. Message loop polls SQLite (every 2 seconds)
│
▼
5. Router checks:
├── Is chat_jid in registered_groups.json? → No: ignore
├── Does message start with @Assistant? → No: ignore
└── Is message "/clear"? → Yes: handle specially
│
▼
6. Router catches up conversation:
├── Fetch all messages since last agent interaction
├── Format with timestamp and sender name
└── Build prompt with full conversation context
│
▼
7. Router invokes Claude Agent SDK:
├── cwd: groups/{group-name}/
├── prompt: conversation history + current message
├── resume: session_id (for continuity)
└── mcpServers: gmail, scheduler
│
▼
8. Claude processes message:
├── Reads CLAUDE.md files for context
└── Uses tools as needed (search, email, etc.)
│
▼
9. Router prefixes response with assistant name and sends via WhatsApp
│
▼
10. Router updates last agent timestamp and saves session ID
Trigger Word Matching
Messages must start with the trigger pattern (default: @Andy):
@Andy what's the weather?→ ✅ Triggers Claude@andy help me→ ✅ Triggers (case insensitive)Hey @Andy→ ❌ Ignored (trigger not at start)What's up?→ ❌ Ignored (no trigger)/clear→ ✅ Special command (no trigger needed)
Conversation Catch-Up
When a triggered message arrives, the agent receives all messages since its last interaction in that chat. Each message is formatted with timestamp and sender name:
[Jan 31 2:32 PM] John: hey everyone, should we do pizza tonight?
[Jan 31 2:33 PM] Sarah: sounds good to me
[Jan 31 2:35 PM] John: @Andy what toppings do you recommend?
This allows the agent to understand the conversation context even if it wasn't mentioned in every message.
Commands
Commands Available in Any Group
| Command | Example | Effect |
|---|---|---|
@Assistant [message] |
@Andy what's the weather? |
Talk to Claude |
/clear |
/clear |
Reset session, keep memory |
Commands Available in Main Channel Only
| Command | Example | Effect |
|---|---|---|
@Assistant add group "Name" |
@Andy add group "Family Chat" |
Register a new group |
@Assistant remove group "Name" |
@Andy remove group "Work Team" |
Unregister a group |
@Assistant list groups |
@Andy list groups |
Show registered groups |
@Assistant remember [fact] |
@Andy remember I prefer dark mode |
Add to global memory |
Scheduled Tasks
NanoClaw can schedule recurring tasks that run at specified times via the scheduler MCP.
Creating a Task
User: @Andy remind me every Monday at 9am to review the weekly metrics
Claude: [calls mcp__scheduler__create_task]
{
"instruction": "Remind user to review weekly metrics",
"trigger_type": "cron",
"cron_expression": "0 9 * * 1"
}
Claude: Done! I'll remind you every Monday at 9am.
MCP Servers
MCP servers are configured in the Claude Agent SDK options:
mcpServers: {
gmail: { command: 'npx', args: ['-y', '@gongrzhe/server-gmail-autoauth-mcp'] },
scheduler: { command: 'npx', args: ['-y', 'schedule-task-mcp'] }
}
Gmail MCP (@gongrzhe/server-gmail-autoauth-mcp)
Provides email capabilities. Requires Google Cloud OAuth setup.
Available Tools:
| Tool | Purpose |
|---|---|
search_messages |
Search inbox |
get_message |
Read full email |
send_message |
Send email |
reply_message |
Reply to thread |
Scheduler MCP (schedule-task-mcp)
Provides cron-style task scheduling.
Available Tools:
| Tool | Purpose |
|---|---|
create_task |
Schedule a new task |
list_tasks |
Show scheduled tasks |
delete_task |
Cancel a task |
update_task |
Modify schedule |
Deployment
NanoClaw runs as a single macOS launchd service.
Service: com.nanoclaw
launchd/com.nanoclaw.plist:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "...">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.nanoclaw</string>
<key>ProgramArguments</key>
<array>
<string>{{NODE_PATH}}</string>
<string>{{PROJECT_ROOT}}/dist/index.js</string>
</array>
<key>WorkingDirectory</key>
<string>{{PROJECT_ROOT}}</string>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>{{HOME}}/.local/bin:/usr/local/bin:/usr/bin:/bin</string>
<key>HOME</key>
<string>{{HOME}}</string>
<key>ASSISTANT_NAME</key>
<string>Andy</string>
</dict>
<key>StandardOutPath</key>
<string>{{PROJECT_ROOT}}/logs/nanoclaw.log</string>
<key>StandardErrorPath</key>
<string>{{PROJECT_ROOT}}/logs/nanoclaw.error.log</string>
</dict>
</plist>
Managing the Service
# Install service
cp launchd/com.nanoclaw.plist ~/Library/LaunchAgents/
# Start service
launchctl load ~/Library/LaunchAgents/com.nanoclaw.plist
# Stop service
launchctl unload ~/Library/LaunchAgents/com.nanoclaw.plist
# Check status
launchctl list | grep nanoclaw
# View logs
tail -f logs/nanoclaw.log
Security Considerations
Prompt Injection Risk
WhatsApp messages could contain malicious instructions attempting to manipulate Claude's behavior.
Mitigations:
- Only registered groups are processed
- Trigger word required (reduces accidental processing)
- Main channel has elevated privileges (isolated from other groups)
- Claude's built-in safety training
Recommendations:
- Only register trusted groups
- Review scheduled tasks periodically
- Monitor logs for unusual activity
Credential Storage
| Credential | Storage Location | Notes |
|---|---|---|
| Claude CLI Auth | ~/.claude/ | Managed by Claude Code CLI |
| WhatsApp Session | store/auth/ | Auto-created, persists ~20 days |
| Gmail OAuth Tokens | ~/.gmail-mcp/ | Created during setup (optional) |
File Permissions
The groups/ folder contains personal memory and should be protected:
chmod 700 groups/
Troubleshooting
Common Issues
| Issue | Cause | Solution |
|---|---|---|
| No response to messages | Service not running | Check `launchctl list |
| "QR code expired" | WhatsApp session expired | Delete store/auth/ and restart |
| "No groups registered" | Haven't added groups | Use @Andy add group "Name" in main |
| Session not continuing | Session ID not saved | Check data/sessions.json |
Log Location
logs/nanoclaw.log- stdoutlogs/nanoclaw.error.log- stderr
Debug Mode
Run manually for verbose output:
npm run dev
# or
node dist/index.js