skills-templates/arweave-ao-cookbook/SKILL.md
Build decentralized applications on AO - a permanent, decentralized compute platform using actor model for parallel processes with native message-passing and permanent storage on Arweave
npx skillsauth add enuno/claude-command-and-control arweave-ao-cookbookInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Master building decentralized applications on AO - a revolutionary decentralized compute system where countless parallel processes interact within a single, cohesive environment. AO combines permanent storage on Arweave with actor model architecture to create truly decentralized, permanently available compute.
AO is a decentralized compute system built on the AO-Core protocol that employs the actor model (inspired by Erlang), enabling independent process operation through native message-passing mechanisms. Each process is an independent server in the network, communicating through messages stored permanently on Arweave.
HyperBEAM is the current production network, delivering:
Key Differentiator: Unlike traditional blockchains or cloud platforms, AO provides "your personal server in the AO computer" - a genuinely decentralized alternative to traditional hosting with permanent message storage.
✅ Perfect for:
❌ Not ideal for:
Independent servers in the decentralized AO network. Each process:
The core communication mechanism between processes:
Event processors that respond to incoming messages:
Interactive development environment:
Everything is permanent on Arweave:
# Install AOS globally via npm
npm i -g https://get_ao.arweave.net
# Verify installation
aos --version
# Option 1: Connect to HyperBEAM for optimal performance
aos --node https://forward.computer
# Option 2: Use default settings
aos
# Option 3: Specify custom wallet
aos --wallet ~/.arweave-wallet.json
# Auto-generated wallet location
# Default: ~/.aos.json (created automatically if not specified)
-- In aos shell, try basic commands:
print("Hello AO")
-- Output: Hello AO
-- Check your process ID
Name
-- Output: Your process name
Owner
-- Output: Your wallet address
-- View inbox
#Inbox
-- Output: Number of unhandled messages
-- Initialize state variable
counter = 0
-- Create handler for increment messages
Handlers.add(
"increment",
Handlers.utils.hasMatchingTag("Action", "Increment"),
function(msg)
counter = counter + 1
print("Counter incremented to: " .. counter)
-- Send response
Send({
Target = msg.From,
Data = tostring(counter)
})
end
)
-- Access state via HTTP
-- https://forward.computer/<process-id>[email protected]/compute/counter
Why this works: State persists permanently and is accessible via HTTP through HyperBEAM.
-- Send message to another process
Send({
Target = "process-id-here",
Action = "Chat-Message",
Data = "Hello from my process!"
})
-- Handle incoming messages
Handlers.add(
"chat",
Handlers.utils.hasMatchingTag("Action", "Chat-Message"),
function(msg)
print("Received: " .. msg.Data)
print("From: " .. msg.From)
-- Reply to sender
Send({
Target = msg.From,
Data = "Message received!"
})
end
)
Pattern: Asynchronous message-passing enables loosely-coupled process communication.
-- Custom prompt showing inbox count
function Prompt()
return "📬 Inbox: " .. #Inbox .. " > "
end
-- Check inbox
if #Inbox > 0 then
-- Access first message
local msg = Inbox[1]
print("From: " .. msg.From)
print("Data: " .. msg.Data)
end
Pattern: Monitor incoming messages in real-time with custom prompts.
-- Load JSON module
local json = require("json")
-- Encode data
local data = { name = "Alice", balance = 1000 }
local encoded = json.encode(data)
-- Decode JSON
local decoded = json.decode(encoded)
print(decoded.name) -- Output: Alice
-- Use .utils module for functional programming
local utils = require(".utils")
local numbers = {1, 2, 3, 4, 5}
local doubled = utils.map(function(n) return n * 2 end, numbers)
-- Result: {2, 4, 6, 8, 10}
Available Modules:
json: JSON encoding/decodingao: Core AO functions (Send, Spawn).base64: Base64 encoding/decoding.pretty: Formatted output (tprint).utils: Functional utilities (map, reduce, filter)-- Enter editor mode
.editor
-- Write multi-line function (type all lines, then Ctrl+D to execute)
function createToken(name, symbol, supply)
return {
Name = name,
Symbol = symbol,
TotalSupply = supply,
Balances = {}
}
end
-- Press Ctrl+D to exit editor mode
-- Use the function
myToken = createToken("MyToken", "MTK", 1000000)
print(myToken.Name) -- Output: MyToken
-- Load functions from file
.load myFunctions.lua
-- Functions from file are now available
-- (assuming myFunctions.lua defines functions)
Use case: Organize complex logic in files, load into aos session.
-- Initialize token state
Token = {
Name = "MyToken",
Symbol = "MTK",
TotalSupply = 1000000,
Balances = {}
}
-- Initialize owner balance
Token.Balances[Owner] = Token.TotalSupply
-- Transfer handler
Handlers.add(
"transfer",
Handlers.utils.hasMatchingTag("Action", "Transfer"),
function(msg)
local recipient = msg.Tags.Recipient
local amount = tonumber(msg.Tags.Amount)
-- Validate sender has balance
if Token.Balances[msg.From] >= amount then
Token.Balances[msg.From] = Token.Balances[msg.From] - amount
Token.Balances[recipient] = (Token.Balances[recipient] or 0) + amount
Send({
Target = msg.From,
Data = "Transfer successful"
})
else
Send({
Target = msg.From,
Data = "Insufficient balance"
})
end
end
)
-- Balance query handler
Handlers.add(
"balance",
Handlers.utils.hasMatchingTag("Action", "Balance"),
function(msg)
local target = msg.Tags.Target or msg.From
local balance = Token.Balances[target] or 0
Send({
Target = msg.From,
Data = tostring(balance)
})
end
)
-- Chatroom state
Chatroom = {
Members = {},
Messages = {}
}
-- Join handler
Handlers.add(
"join",
Handlers.utils.hasMatchingTag("Action", "Join"),
function(msg)
Chatroom.Members[msg.From] = true
-- Notify all members
for member, _ in pairs(Chatroom.Members) do
Send({
Target = member,
Action = "Notification",
Data = msg.Tags.Username .. " joined the chatroom"
})
end
end
)
-- Send message handler
Handlers.add(
"broadcast",
Handlers.utils.hasMatchingTag("Action", "Broadcast"),
function(msg)
-- Store message
table.insert(Chatroom.Messages, {
From = msg.From,
Text = msg.Data,
Timestamp = msg.Timestamp
})
-- Broadcast to all members
for member, _ in pairs(Chatroom.Members) do
if member ~= msg.From then
Send({
Target = member,
Action = "ChatMessage",
From = msg.From,
Data = msg.Data
})
end
end
end
)
-- Spawn a new process
Send({
Target = ao.id, -- ao core process
Action = "Eval",
Data = [[
-- Code for new process
Handlers.add(
"ping",
Handlers.utils.hasMatchingTag("Action", "Ping"),
function(msg)
Send({ Target = msg.From, Data = "Pong!" })
end
)
]]
})
-- Programmatic spawn with Spawn()
local newProcess = Spawn("module-id", {
Data = "Initial state",
Tags = {
Name = "SubProcess",
Type = "Worker"
}
})
Available blueprint templates:
-- Load blueprint (example - actual syntax may vary)
-- Blueprints provide pre-built handlers and state management
-- Define state that should be HTTP accessible
GameState = {
players = {},
score = 0,
round = 1
}
-- State is automatically accessible via HTTP
-- GET https://forward.computer/<process-id>[email protected]/compute/GameState
-- Returns JSON representation of GameState
Power: Any process variable becomes instantly queryable via HTTP through HyperBEAM.
-- Helper function for common checks
local function isAuthorized(msg)
return msg.From == Owner
end
-- Composed handler with authorization
Handlers.add(
"admin-action",
function(msg)
return Handlers.utils.hasMatchingTag("Action", "Admin")(msg)
and isAuthorized(msg)
end,
function(msg)
-- Admin logic here
print("Admin action executed")
end
)
Handlers.add(
"safe-operation",
Handlers.utils.hasMatchingTag("Action", "SafeOp"),
function(msg)
local success, result = pcall(function()
-- Potentially error-prone operation
local value = tonumber(msg.Tags.Amount)
assert(value > 0, "Amount must be positive")
return value * 2
end)
if success then
Send({
Target = msg.From,
Data = tostring(result)
})
else
Send({
Target = msg.From,
Error = result -- Contains error message
})
end
end
)
Solution:
# Verify NodeJS version
node --version # Should be v20+
# Reinstall aos
npm i -g https://get_ao.arweave.net
# Check PATH
echo $PATH | grep npm
Solution:
# Try different node
aos --node https://forward.computer
# Check network connection
curl https://forward.computer
# Verify wallet if using custom
aos --wallet ~/.arweave-wallet.json
Solutions:
Check tag matching:
-- Verify message has correct tags
print(msg.Tags.Action) -- Should match your matcher
Test handler directly:
-- Simulate message
local testMsg = {
From = "test-id",
Tags = { Action = "Test" },
Data = "test data"
}
-- Call handler function manually
Check handler order:
-- List all handlers
Handlers.list()
-- Handlers execute in order, first match wins
Explanation: State in aos session is temporary until messages are sent/processed. To persist:
.load to reload functions across sessionsSolution:
-- Process and clear inbox
for i = 1, #Inbox do
local msg = Inbox[i]
-- Handle message
print("Processing: " .. msg.From)
end
-- Inbox clears automatically after handlers process messages
aos [options]
Options:
--wallet <path> Path to Arweave wallet (default: ~/.aos.json)
--node <url> AO node URL (default: https://forward.computer)
--module <id> Module ID for process
--cron <interval> Cron interval for process
--version Show version
--help Show help
| Command | Purpose | Example |
|---------|---------|---------|
| .editor | Enter multi-line edit mode | .editor then Ctrl+D to execute |
| .load <file> | Load Lua file | .load functions.lua |
| .exit | Exit aos shell | .exit |
| #Inbox | Count unhandled messages | print(#Inbox) |
| Inbox[n] | Access message at index | local msg = Inbox[1] |
| Global | Type | Purpose |
|--------|------|---------|
| Send(msg) | Function | Send message to process |
| Spawn(module, msg) | Function | Create new process |
| Inbox | Table | Unhandled messages |
| Handlers | Module | Handler management |
| Owner | String | Process owner address |
| Name | String | Process name |
-- Group related handlers
-- Authentication handlers
Handlers.add("auth:login", ...)
Handlers.add("auth:logout", ...)
-- Business logic handlers
Handlers.add("game:move", ...)
Handlers.add("game:score", ...)
-- Use clear state structures
State = {
users = {},
config = {
maxUsers = 100,
timeout = 300
},
stats = {
totalMessages = 0,
activeUsers = 0
}
}
-- Avoid global pollution
-- Use tables to organize
-- Always validate inputs
function validateAmount(amount)
local num = tonumber(amount)
assert(num, "Amount must be a number")
assert(num > 0, "Amount must be positive")
return num
end
-- Use pcall for risky operations
local success, result = pcall(validateAmount, msg.Tags.Amount)
if not success then
Send({ Target = msg.From, Error = result })
return
end
-- Create test utilities
function testHandler(handler, mockMsg)
return pcall(function()
handler(mockMsg)
end)
end
-- Test before deploying
local testMsg = {
From = "test-sender",
Tags = { Action = "Transfer", Amount = "100" },
Data = ""
}
local success, err = testHandler(transferHandler, testMsg)
print(success and "✓ Test passed" or "✗ Test failed: " .. err)
--[[
Handler: token:transfer
Purpose: Transfer tokens between addresses
Tags Required:
- Action: "Transfer"
- Recipient: target address
- Amount: token amount (string number)
Returns: Success/failure message
]]
Handlers.add("token:transfer", ...)
See /examples directory for complete working examples:
chatroom.lua - Full chatroom implementationtoken.lua - Token standard with all handlersgame.lua - Simple game with state managementvoting.lua - Voting system with proposalsLast Updated: December 26, 2025 Source: https://cookbook_ao.arweave.net/ Community: AO Discord and GitHub Status: Production Ready ✅
tools
MemPalace local-first AI memory system. Use when setting up persistent memory for Claude Code sessions, mining project files or conversation transcripts, querying past context, configuring MCP tools, managing the knowledge graph, or troubleshooting palace operations.
tools
LangSmith Python SDK — trace, evaluate, and monitor LLM applications. Covers @traceable decorator, trace context manager, Client API, evaluate() / aevaluate(), comparative evaluation, custom evaluators, dataset management, prompt caching, ASGI middleware, and pytest plugin.
development
LangGraph (Python) — build stateful, controllable agent graphs with checkpointing, streaming, persistence, interrupts, fault tolerance, and durable execution. Covers both Graph API (StateGraph) and Functional API (@entrypoint/@task).
development
LangGraph Graph API (Python) — build explicit DAG agent workflows with StateGraph, typed state, nodes, edges, Command routing, Send fan-out, checkpointers, interrupts, and streaming. Use when you need explicit control flow and graph topology.