A fully autonomous AI assistant in pure x86-64 assembly. No libc. No runtime. No sanity.
$ ./claw -m "Hello"
Thinking...
Hello! I'm claw, your AI assistant. How can I help you?
$ file claw
claw: ELF 64-bit LSB executable, x86-64, statically linked, stripped
$ ldd claw
not a dynamic executable
$ wc -c claw
12056 claw
noclaw ended its README with:
Until someone does one in asm I guess.
So here we are. 4,645 lines of hand-written NASM. Every syscall is a syscall instruction. Every byte of the binary was argued about. The JSON parser does recursive descent. In assembly. I need help.
Everything noclaw does, but with ~7x less binary and ~100x less self-respect:
- Interactive mode with
claw>prompt and conversation history - Single message mode with
claw -m "do the thing" - Tool calling via the Anthropic Messages API (shell execution with stdout/stderr capture)
- Agent loop that keeps going until Claude is done (or 10 iterations, whichever comes first)
- Config from env vars (
ANTHROPIC_API_KEY) or~/.claw/config - TLS by shelling out to
openssl s_clientbecause implementing TLS in assembly is where I drew the line
| OpenClaw | NanoBot | PicoClaw | ZeroClaw | NullClaw | NoClaw | claw | |
|---|---|---|---|---|---|---|---|
| Language | TypeScript | Python | Go | Rust | Zig | C | x86-64 asm |
| RAM | > 1 GB | > 100 MB | < 10 MB | < 5 MB | ~1 MB* | 324 KB | 384 KB** |
| Startup | > 500 s | > 30 s | < 1 s | < 10 ms | < 8 ms | idk man | ~320 μs |
| Binary Size | ~28 MB (dist) | 363 MB (installed) | ~8 MB | 3.4 MB | 678 KB | 88 KB*** | 11.7 KB |
| Build Deps | node, pnpm, 1,219 pkgs | pip, 103 pkgs | go, 82 modules | rustc, cargo, 737 crates | zig, libsqlite3 | cc, libbearssl | nasm, ld |
| Runtime Deps | node + npm | python + pip | libc | libc | libc + curl | 0 | openssl**** |
| Source Files | ~400+ | ~54 | ~129 | ~120 | ~110 | ~14 | 16 |
| libc | V8 | CPython | go runtime | musl | zig std | musl | no |
| Cost | Mac Mini $599 | Linux SBC ~$50 | Linux Board $10 | Any $10 hardware | Any $5 hardware | Any 50¢ hardware | It will probably only run on my laptop anyway... |
Benchmarks measured on x86-64 Linux (Arch). RAM is peak RSS via GNU time. noclaw numbers from their README (arm64 Debian).
*NullClaw's ~1 MB RAM excludes TLS: it shells out to
curlfor every HTTP request, so TLS memory is charged to the child process.**claw's 384 KB is the process itself. During API calls, peak RSS hits ~10.9 MB - but ~98% of that is the openssl child process. Same trick as NullClaw, honestly, but at least we admit it.
***noclaw: 88 KB dynamic (macOS arm64), ~270 KB static musl. claw: 11.7 KB, statically linked, no libc.
****openssl is only needed at runtime for TLS. It is not linked into the binary. The binary itself has zero dependencies.
lddsays "not a dynamic executable" and it means it.
9,628 bytes .text (executable code)
1,947 bytes .rodata (strings, constants)
0 bytes .data (no initialized mutable data)
88,200 bytes .bss (buffers, arena state - not in binary)
─────────────
12,056 bytes total on disk (--nmagic packs text+rodata, no page padding)
main.asm _start, argv parsing, interactive loop
config.asm env vars, ~/.claw/config
agent.asm conversation history, tool dispatch, agent loop
provider.asm Anthropic API request/response serialization
json_write.asm buffer-based JSON builder
json_parse.asm recursive-descent JSON parser
http.asm HTTP/1.1 POST + response parser
tls.asm fork/exec openssl s_client, pipe I/O
tool_shell.asm fork/exec /bin/sh -c, capture output
arena.asm mmap-based bump allocator
str.asm string ops, itoa/atoi, env lookup
io.asm read/write wrappers
data.asm all .rodata strings
Headers: syscalls.inc (syscall numbers), macros.inc (save_regs, write_str, etc.), structs.inc (json_node, arena, message, etc.)
-
Arena allocator: One
mmap(MAP_ANONYMOUS)call gives you 16KB. Bump pointer goes up. When it's full, chain another chunk. When the conversation resets,munmapeverything. No free(). No fragmentation. No mercy. -
JSON parser: Full recursive descent. Strings, numbers, bools, null, arrays, objects. Escape sequence decoding (
\n,\t,\", etc.). All arena-allocated. ~660 lines of assembly to parse{"content":"hello"}. I could have used jq. -
TLS: I am not implementing TLS in assembly. I refuse.
fork(),pipe(),dup2(),execve("/usr/bin/openssl", ["openssl", "s_client", "-connect", "api.anthropic.com:443", "-quiet", "-ign_eof"]). Write HTTP to the pipe. Read response from the other pipe. It works. It's fine. Don't look at it. -
HTTP: Hand-assembled
POST /v1/messages HTTP/1.1\r\nbyte by byte. Content-Length computed viaitoa(). Response parsed by scanning for\r\n\r\n. We support exactly one endpoint. -
No libc, no exceptions: Every function call is
call/ret. Every string operation isrep movsbor a manual loop.printfdoesn't exist.mallocdoesn't exist. Error handling is "return 0 and hope the caller checks". System V AMD64 ABI throughout - arguments in rdi/rsi/rdx/rcx/r8/r9, callee saves rbx/r12-r15/rbp, and god help you if you put a value in rcx and then call a function.
make
Requirements: nasm, ld (GNU binutils). That's it. If nasm isn't in your PATH:
make NASM=/usr/bin/nasm LD=/usr/bin/ld
For the stripped binary:
make stripped
# Set your API key (pick one)
export ANTHROPIC_API_KEY=sk-ant-...
# or
echo "api_key=sk-ant-..." > ~/.claw/config
# Single message
./claw -m "What's in /etc/hostname?"
# Interactive
./claw
claw> List files in the current directory
claw> /new # new conversation
claw> /quit # exitLinux x86-64. That's it. The syscall numbers are hardcoded. The struct layouts are hardcoded. The ABI is hardcoded. If you're on ARM, I'm sorry. If you're on macOS, I'm more sorry - your syscall numbers are different AND unstable.
Compared to noclaw:
- No multiple providers (Anthropic only, claude-sonnet-4-20250514)
- No Telegram/Discord/Slack (CLI only, this is assembly)
- No file tools (shell tool can do
catthough) - No memory system (conversations reset on exit)
- No
\uXXXXUnicode decoding (emits?- sorry emoji users) - No chunked transfer encoding (we send
Connection: closeand read until EOF)
pipe()returns two 32-bit ints. Not 64-bit.mov rdi, [rsp]packs both fds into one register. Usemov edi, [rsp].- Direct syscalls return
-errnoon error, not-1.mmapreturning-12is not a valid pointer. Ask me how I know. rcxis caller-saved. If you compute a string length, store it inrcx, callarena_alloc, and then usercxas if nothing happened - you will get a pointer-sized integer as your "string length" andmmapwill try to allocate 140TB of RAM.
MIT