| name | build-aos |
| description | Write AOS scripts and comprehensive in-memory tests. Iterates until all tests pass 100%. Use when user says 'build the AOS script', 'write the Lua code', or 'implement the handler'. Not for scaffolding — use /create-aos. |
| argument-hint | <task-id> |
| disable-model-invocation | true |
| allowed-tools | Read, Write, Edit, Bash, Grep, Glob |
Build AOS scripts and/or in-memory tests for a task. Iterates until all tests pass 100%.
Steps
-
Read tasks.json and find the task matching $ARGUMENTS (task id). Update its status to "in_progress".
-
Read plan.md for the AOS script specifications:
- Script names and actions
- Input tags and expected values
- State management (what gets stored, how)
- Reply format (JSON, plain text, tags)
- Edge cases to handle
-
Read docs/aos-lua.md for AOS Lua patterns and conventions.
3.5. Verify prerequisites:
- Check
src/ directory exists: ls src/ 2>/dev/null
- If
src/ doesn't exist, create it: mkdir -p src
-
If the task type is aos — write AOS scripts:
Write script files in src/ following the plan specs. For each script:
- Use
Handlers.add(name, pattern, callback) pattern
- Action tags must be uppercase (e.g.,
"Transfer", not "transfer")
- Validate all input: nil checks on
msg.Tags.*, type validation, boundary values
- Handle error cases: missing fields → reply with error, unauthorized → reject
- Use
ao.send() for fire-and-forget (NOT Send().receive() — broken on genesis-wasm)
- Require dependencies at top:
local json = require('json'), local bint = require('.bint')(256)
- State stored in module-level locals
-
If the task type is aos-test — write in-memory tests:
Write test files in test/ using this pattern:
import { describe, it, before, after } from "node:test"
import assert from "node:assert"
import { AO, acc } from "wao/test"
import { readFileSync } from "fs"
const src_data = readFileSync("src/{name}.lua", "utf8")
describe("{feature} tests", () => {
let ao, p
before(async () => {
ao = await new AO().init(acc[0])
const result = await ao.deploy({ src_data })
p = result.p
})
it("should handle happy path", async () => {
const { res } = await p.msg("Action", { ... })
assert.equal(res.Output.data, expected)
})
it("should reject missing fields", async () => {
})
})
For multi-user tests, share ao.mem between AO instances:
const ao2 = await new AO({ mem: ao.mem }).init(acc[1])
-
Run the tests:
yarn test test/{name}.test.js
-
If any tests fail:
- Read the error output carefully
- Identify whether the bug is in the AOS script or the test
- Fix the code
- Re-run the tests
- Repeat until 100% pass
-
Update the task status to "done" in tasks.json.
Troubleshooting
Tests fail with "Action not found"
- Action tags must be UPPERCASE:
"Transfer" not "transfer"
- Check that
Handlers.add name matches the Action tag exactly
State resets between test cases
- Each
ao.deploy() creates a fresh process — state doesn't carry over
- Use
before() to deploy once, then test sequentially
bint arithmetic errors
- Always wrap in
tostring() when replying: tostring(bint(x) + bint(y))
- Use
pcall(bint, value) for safe parsing of user input
Iteration Protocol
For each test-fix cycle:
- Run tests and capture FULL output
- Identify the FIRST failing test (fix failures in order)
- Classify: is the bug in source code or test code?
- Fix ONE issue per iteration (don't change multiple things at once)
- Re-run only the failing test first, then full suite
- If the same error persists after 3 different fix attempts, step back:
- Re-read the relevant docs (AOS patterns, Lua conventions, etc.)
- Check if the plan itself has a wrong assumption
- Try a completely different approach
- Continue iterating until all tests pass — there is no retry limit
Only escalate to the user if:
- The failure is environmental (missing binary, permissions, port conflict)
- The test requires user input (wallet, external service URL)
- You've tried 10+ iterations with no progress on the same error