| name | arweave-ao-cookbook |
| description | 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 |
| version | 1.0.0 |
| category | blockchain-development |
| tags | arweave, ao, decentralized-compute, smart-contracts, blockchain, lua, permanent-storage, distributed-systems |
| source | https://cookbook_ao.arweave.net/ |
| author | AO Community |
| last_updated | Fri Dec 26 2025 00:00:00 GMT+0000 (Coordinated Universal Time) |
AO Cookbook Skill
Purpose
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.
What is AO?
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:
- High-performance message processing
- Instant HTTP access to process state
- Trustless, permissionless infrastructure
- Permanent availability
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.
When to Use This Skill
✅ Perfect for:
- Building decentralized applications requiring permanent storage
- Creating smart contracts with complex state and interactions
- Developing chatbots, games, and interactive applications on blockchain
- Implementing token systems, voting mechanisms, and governance
- Building systems that require trustless, permanent compute
- Creating applications that benefit from parallel process execution
- Developing Arweave-integrated applications
❌ Not ideal for:
- Simple static websites (use traditional hosting)
- Applications requiring real-time sub-millisecond latency
- Systems that need to delete or modify historical data
- Traditional centralized database applications
- Projects without permanent storage requirements
Core Concepts
1. Processes
Independent servers in the decentralized AO network. Each process:
- Has its own state and memory
- Executes autonomously
- Communicates via messages
- Persists permanently on Arweave
- Can spawn other processes
2. Messages
The core communication mechanism between processes:
- Permanently stored on Arweave
- Asynchronous by nature
- Contain data and metadata
- Trigger handler functions
- Enable process interaction
3. Handlers
Event processors that respond to incoming messages:
- Pattern-match on message properties
- Execute logic based on message content
- Update process state
- Send responses to other processes
- Enable reactive programming model
4. aos (AO Shell)
Interactive development environment:
- Command-line interface for AO processes
- Built-in Lua REPL
- Supports modules and blueprints
- Enables rapid prototyping
- Provides process management tools
5. Permanence
Everything is permanent on Arweave:
- Messages never disappear
- Code is immutable once deployed
- State history is preserved
- Audit trails are built-in
- No centralized control
Installation & Setup
Prerequisites
- NodeJS: Version 20 or higher
- Code Editor: Any editor (VS Code recommended)
- Arweave Wallet (optional): For custom wallet authentication
Step 1: Install AOS
# Install AOS globally via npm
npm i -g https://get_ao.arweave.net
# Verify installation
aos --version
Step 2: Connect to HyperBEAM Network
# 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)
Step 3: Verify Connection
-- 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
Basic Usage Patterns
Pattern 1: Simple State Management
-- 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>~process@1.0/compute/counter
Why this works: State persists permanently and is accessible via HTTP through HyperBEAM.
Pattern 2: Message Passing Between Processes
-- 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.
Pattern 3: Inbox Monitoring
-- 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.
Pattern 4: Using Modules
-- 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)
Pattern 5: Multi-line Code with Editor Mode
-- 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
Pattern 6: Loading External Lua Files
-- 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.
Common Workflows
Workflow 1: Building a Token System
-- 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
)
Workflow 2: Creating a Chatroom
-- 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
)
Workflow 3: Spawning New Processes
-- 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"
}
})
Workflow 4: Using Blueprints
Available blueprint templates:
- chatroom: Complete chatroom implementation
- token: Token standard with transfer/balance
- voting: Voting mechanism with proposals
- staking: Token staking with rewards
- CRED: Reputation/credit system
-- Load blueprint (example - actual syntax may vary)
-- Blueprints provide pre-built handlers and state management
Advanced Patterns
Pattern: State Persistence & HTTP Access
-- 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>~process@1.0/compute/GameState
-- Returns JSON representation of GameState
Power: Any process variable becomes instantly queryable via HTTP through HyperBEAM.
Pattern: Handler Composition
-- 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
)
Pattern: Error Handling
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
)
Troubleshooting
Issue: "aos command not found"
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
Issue: Connection timeout
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
Issue: "Handler not triggering"
Solutions:
Check tag matching:
-- Verify message has correct tags print(msg.Tags.Action) -- Should match your matcherTest handler directly:
-- Simulate message local testMsg = { From = "test-id", Tags = { Action = "Test" }, Data = "test data" } -- Call handler function manuallyCheck handler order:
-- List all handlers Handlers.list() -- Handlers execute in order, first match wins
Issue: State not persisting
Explanation: State in aos session is temporary until messages are sent/processed. To persist:
- Send messages to your process (triggers permanent storage)
- Deploy handlers as permanent code
- Use
.loadto reload functions across sessions
Issue: "Inbox full" or performance degradation
Solution:
-- 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
CLI Reference
aos Command-Line Options
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
aos Shell Commands
| 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 Functions & Variables
| 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 |
Best Practices
1. Handler Organization
-- Group related handlers
-- Authentication handlers
Handlers.add("auth:login", ...)
Handlers.add("auth:logout", ...)
-- Business logic handlers
Handlers.add("game:move", ...)
Handlers.add("game:score", ...)
2. State Management
-- Use clear state structures
State = {
users = {},
config = {
maxUsers = 100,
timeout = 300
},
stats = {
totalMessages = 0,
activeUsers = 0
}
}
-- Avoid global pollution
-- Use tables to organize
3. Error Handling
-- 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
4. Testing Handlers
-- 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)
5. Documentation
--[[
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", ...)
Resources
Official Documentation
- Cookbook: https://cookbook_ao.arweave.net/
- HyperBEAM: https://forward.computer
- GitHub: https://github.com/permaweb/ao
Community
- Discord: Community support and discussions
- Tutorials: Begin track, Bots & Games, Advanced guides
Learning Paths
- Getting Started: Welcome → Installation → Basic messaging
- Token Development: Token tutorial → Transfer mechanics → Balance queries
- Game Development: Bots & Games track → State management → Multiplayer
- Advanced: Custom modules → Process spawning → HTTP integration
Examples Repository
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 proposals
Version History
- 1.0.0 (2025-12-26): Initial skill creation
- Core concepts and patterns
- Installation and setup
- Basic and advanced workflows
- Troubleshooting guide
- CLI reference
- Best practices
Last Updated: December 26, 2025 Source: https://cookbook_ao.arweave.net/ Community: AO Discord and GitHub Status: Production Ready ✅