What You'll Learn in This Chapter
In this chapter, you'll learn how to develop with AOS (Actor Operating System) — the first implementation of the AO protocol.
AOS is a lightweight, Lua-based actor runtime that combines the actor model, persistent storage, and decentralized messaging into a new execution paradigm.
Through hands-on examples, you’ll walk through the full lifecycle: spawning a process → registering logic → sending and receiving messages.
By the end of this chapter, you'll understand:
- What AOS is and how to use the
aoconnect
library - How to connect to MU, SU, and CU in a local setup
- How to write and register Lua handlers for message processing
- How to send messages and simulate reads using
dryrun
- How to build a simple yet practical process like a key-value store
- Best practices for writing secure, modular, and reusable AOS logic
What is AOS?
AOS (Actor Operating System) is the first implementation of the AO protocol — a Lua-based, distributed actor runtime.
It faithfully implements AO’s actor model and messaging architecture, enabling developers to easily build and deploy actor-driven applications on top of Arweave.
Key characteristics of AOS include:
Lightweight Lua Runtime: Lua is a minimal and embeddable scripting language, ideal for compiling to WebAssembly and running in distributed environments.
Full Actor Model Support: Each process maintains its own isolated state and inbox, communicating asynchronously via message passing.
Arweave-Based Persistence: Process states and message histories are permanently stored on Arweave and can be restored at any time.
Dynamic Logic with Eval: After deployment, logic can be extended or updated dynamically by sending Lua scripts to the process using the
Eval
action.Fully Scriptable via JavaScript: With the
aoconnect
library, every part of the development lifecycle — from spawning processes to sending messages — can be controlled programmatically.
AOS serves as an ideal entry point for learning AO’s architecture and rapidly prototyping your own decentralized actor applications.
In this chapter, you’ll explore the entire workflow of building with AOS through hands-on code examples.
AOS Fundamentals
Setting Up AOS
AOS is one of the first implementations of AO and uses the Lua programming language. To begin using AOS, install the aoconnect
library:
npm i @permaweb/aoconnect
Connecting to Units
To use AOS in a local environment, connect to each AO unit (MU, SU, CU) by specifying their URLs:
const { createDataItemSigner, connect } = require("@permaweb/aoconnect");
const { result, message, spawn, dryrun } = connect({
MU_URL: "http://localhost:4002",
CU_URL: "http://localhost:4004",
GATEWAY_URL: "http://localhost:4000",
});
To connect to the mainnet, a simple import is sufficient:
const { result, message, spawn, dryrun } = require("@permaweb/aoconnect");
Spawning an AOS Process
To spawn an AOS process, you need the transaction ID of the AOS module and the address of a scheduler wallet:
const wait = (ms) => new Promise((res) => setTimeout(() => res(), ms));
const pid = await spawn({
module: AOS_MODULE_TXID, // Transaction ID of the module
scheduler: SCHEDULER_WALLET, // Address of the scheduler wallet
signer: createDataItemSigner(jwk),
});
await wait(1000);
console.log(`Spawned process ID: ${pid}`);
Once the AO process is spawned, it exists permanently on Arweave and can start processing messages.
Basics of Lua Handlers
In AOS, Lua is used to define the business logic of each process. The core concept is the handler—a function triggered when a message matches specific criteria.
AOS provides the following global variables and modules:
- Inbox: A table holding unprocessed messages
- Send: A function to send messages
- Spawn: A function to create new processes
- Handlers: A table to register and manage handlers
- ao: A module providing process-related utilities
- json: Module for JSON encoding/decoding
Basic handler structure:
Handlers.add(
"handler_name",
function(msg)
return msg.Action == "some_action"
end,
function(msg)
ao.send({ Target = msg.From, Data = "response message" })
end
)
Common utility matchers from Handlers.utils
:
Handlers.utils.hasMatchingTag("Action", "Get")
Handlers.utils.hasMatchingData("ping")
Handlers.utils.reply("pong")
Creating and Registering a Handler
Here’s how to implement a simple key-value store. Save the following Lua code as kv-store.lua
:
local ao = require('ao')
Store = Store or {}
Handlers.add(
"Get",
Handlers.utils.hasMatchingTag("Action", "Get"),
function (msg)
assert(type(msg.Key) == 'string', 'Key is required!')
ao.send({ Target = msg.From, Tags = { Value = Store[msg.Key] }})
end
)
Handlers.add(
"Set",
Handlers.utils.hasMatchingTag("Action", "Set"),
function (msg)
assert(type(msg.Key) == 'string', 'Key is required!')
assert(type(msg.Value) == 'string', 'Value is required!')
Store[msg.Key] = msg.Value
Handlers.utils.reply("Value stored!")(msg)
end
)
To add this code to a running process, use the Eval
action:
const { readFileSync } = require("fs");
const { resolve } = require("path");
const lua = readFileSync(resolve(__dirname, "kv-store.lua"), "utf8");
const mid = await message({
process: pid,
signer: createDataItemSigner(jwk),
tags: [{ name: "Action", value: "Eval" }],
data: lua,
});
const res = await result({ process: pid, message: mid });
console.log(res);
Sending and Receiving Messages
Use the message
function to send data to a process:
const mid1 = await message({
process: pid,
signer: createDataItemSigner(jwk),
tags: [
{ name: "Action", value: "Set" },
{ name: "Key", value: "test" },
{ name: "Value", value: "abc" },
],
});
const res1 = await result({ process: pid, message: mid1 });
console.log(res1.Messages[0]);
Use dryrun
for read-only queries without writing to Arweave:
const res2 = await dryrun({
process: pid,
signer: createDataItemSigner(jwk),
tags: [
{ name: "Action", value: "Get" },
{ name: "Key", value: "test" },
],
});
console.log(res2.Messages[0].Tags);
Development Patterns and Best Practices
Here are some best practices for building with AOS:
State Management:
- Always initialize global variables (e.g.,
Store = Store or {}
) - Structure large state data appropriately
Error Handling:
- Use
assert
to validate inputs - Provide clear, descriptive error messages
Modularization:
- Group related logic together
- Create reusable functions when possible
Debugging:
- Use
ao.log
for debugging output - Use
json.encode
to log complex structures
Security:
- Always validate untrusted input
- Implement authentication for critical actions
Testing:
- Create separate processes for testing
- Use
dryrun
to simulate changes before committing them
Following these practices will help you develop safer and more efficient AOS applications.
Top comments (0)