A minimal, extensible coding agent written in hand-written x86-64 assembly — static, no libc, Linux-first, with a linux-aarch64 port, a macOS arm64 port and a Windows x86-64 port.
For production use, pick agentc.
opcodeis a research project — the far end of one experiment: a real coding agent in hand-written x86-64 assembly, statically linked and libc-free.agentcis the supported production sibling (freestanding C23, the same feature surface, real releases) — use it for anything you actually depend on.
- ~0.2 ms to launch, ~0.5 MB resident in the TUI, ~706 KiB static binary — no runtime, no interpreter, no GC, no dynamic linker.
- One source tree, four live targets (Linux x86-64/aarch64, macOS arm64, Windows x86-64) plus a riscv64 target gated closed until its port lands.
- Direct syscalls and in-tree networking — sockets, DNS and TLS are implemented in the tree; TLS is vendored freestanding mbedTLS.
- A real TUI, providers, tools, sessions and a stable C ABI — the feature
surface matches
agentc's, reimplemented from scratch in assembly.
Measured on one Linux x86-64 machine, make release (stripped), the same
method for both. opcode and agentc are the two implementations of the same
design; the external agents are agentc's published figures, shown for scale.
| agent | language | on-disk (stripped) | cold start | idle TUI RSS |
|---|---|---|---|---|
| opcode | hand-written x86-64 assembly | 706 KiB | ~0.2 ms | ~0.5 MB |
| agentc | freestanding C23 | 925 KiB | ~0.2 ms | ~0.6 MB |
| codex 0.161.0 | native (Rust) | 279 MiB | ~9 ms | ~25 MB |
| Claude Code 2.1.293 | single-file binary | 241 MiB | ~9 ms | ~39 MB |
| pi 0.99.2 | Node bundle + Node 24 | 17.5 MiB + runtime | ~249 ms | ~113 MB |
- Cold start is
--version(process start to exit, median of 200 runs). - Idle TUI RSS is
VmHWMsampled from/proc/<pid>/statuswhile the TUI sits at its first screen in a pty. - on-disk is the stripped release binary;
opcodeandagentcare built here, the other three areagentc's README figures (vendor Linux releases). - The gap is the runtime.
opcodemaps ~0.7 MB of its own text and holds a few hundred KB of heap; the others carry a language runtime and an interpreter.
Linux x86-64 is the reference. The toolchain is GNU as and ld, clang
(vendored freestanding TLS, C plugins, and the arm64 cross-assembler/compiler)
and python3 (generated assets/catalog/plugins).
make # build the debug binary build/opcode
make release # relink build/opcode stripped
make test # build the unit-test binaries
make check # build everything and run the full suite (tests/run.sh)
make clean # remove build/
nix build # package with Nix (x86_64-linux, aarch64-linux)
nix develop # development shell with the same toolchain plus cross binutilsmake check runs the golden unit binaries, the CLI checks and the shell/python
integration suites; a clean tree ends TESTS <n> passed, 0 failed (run it for
the current count).
Cross targets (the toolchain must be on PATH or reachable through nix develop):
| Command | Result |
|---|---|
make TARGET=linux-aarch64 |
cross-build the static, no-libc AArch64 ELF build/opcode-linux-aarch64 |
make test-qemu |
build the AArch64 unit binaries and run the full suite against the ELF under qemu-aarch64 (0 skipped) |
make arm64-translate |
translate + assemble every source for arm64-apple-macos11 (the syntax gate; also compiles the arm64 C) |
make darwin-arm64 |
build the mac object set and link build/opcode-darwin-arm64 on a host with a Mach-O linker |
make darwin-arm64-cross ARM64_LD="…/zig cc" |
Linux cross-link of the app + smoke with zig (smoke check only) |
nix build .#darwin-arm64-cross |
cross build + tools/check-macho.sh (static Mach-O validation) |
make TARGET=windows-x86_64 / make test-wine |
PE32+ build and the Wine execution lane |
Tagged pushes are built by .github/workflows/release.yml:
git tag v0.3.0 && git push origin v0.3.0Each artifact is named opcode-<os>-<arch>[.exe], plus a SHA256SUMS file; all
of them are attached to the GitHub release for the tag. Tags containing - are
marked prerelease.
| target | builds | CI | driven by hand |
|---|---|---|---|
| Linux x86-64 | yes (reference) | native make check |
yes |
| Linux aarch64 | yes (cross) | golden suite under qemu-aarch64 |
no (qemu only) |
| macOS arm64 | yes (translated Mach-O) | macos-14: build + smoke gate; full suite informational (port not hardware-verified) |
no |
| Windows x86-64 | yes (PE32+, -nostdlib) |
Wine lane (suite subset) | no (Wine only) |
| Linux riscv64 | no — no port sources yet | gated closed | no |
| macOS x86-64 | no — the Darwin work is AArch64-only | — | no |
Driven by hand means a person ran a real session on that platform, not that a
suite passed. Linux x86-64 is exercised end to end. Everything else is
cross-built and validated as far as a Linux host allows: Linux aarch64 runs the
whole suite under qemu-aarch64 but has never run on silicon; macOS arm64
builds, packages and passes the Layer-0 smoke in CI (macos-14) but its full
suite is still red and is therefore informational until the Darwin platform
layer is driven on real hardware; Windows builds and runs under Wine but has
never run on real Windows. The riscv64 target has no port sources and is gated
closed.
If you have a Mac, an AArch64 Linux box, or a Windows machine, testing is genuinely useful — platform differences bite first at resize, unicode width, Ctrl-C, paste, and the inline bottom region.
- macOS arm64 —
make && make check, then a real session (./build/opcode --version, then a prompt that calls a tool). Mention Apple silicon or Intel. - AArch64 Linux —
make linux-aarch64 test-aarch64, then run the unit binaries directly (build/a64/tests/*againsttests/data/*.expected), sincemake test-qemualways goes through the emulator. - Windows —
opcode --version,opcode models, then a prompt that calls a tool; most useful from a machine that only has Windows PowerShell 5.1. - Report the exact command, its full output and
uname -ain an issue. See.agents/docs/ports.mdfor the port checklist.
Toolchain: GNU as + ld for the assembly, clang for the vendored
freestanding TLS and C plugins, python3 for generated assets, and Nix for
reproducible packaging. A static no-libc binary cannot dlopen, so extensions
link in statically (see Extensions).
./build/opcode # interactive TUI (inline region by default)
./build/opcode --tui-mode fullscreen # alternate-screen renderer
./build/opcode -p "explain this project" # one-shot: run and print the answer
./build/opcode --mode json -p "hi" # machine-readable JSONL events
./build/opcode --mode rpc # JSONL commands in, events out
./build/opcode login [provider] [--manual] # OAuth subscription login; --manual pastes a code
./build/opcode --list-models [filter] # list known models, then exit
./build/opcode --refresh-models # force model discovery (ignores the 24 h cache)
./build/opcode models --refresh # refresh and list available models
./build/opcode --list-sessions # list this directory's sessions, then exit
./build/opcode --resume # pick a session to resume (newest first)
./build/opcode --verbose # same TUI, with request/tool diagnostics on stderr
./build/opcode fetch https://example.com/ # minimal HTTP(S) client
./build/opcode update # check for a newer releaseWith no provider configured the first run opens provider and model pickers. Built-in
providers: anthropic (claude-*), openai (gpt-*), google
(gemini-*, served through Google's OpenAI-compatible endpoint), ollama
(local, no key) and ollama-cloud. OpenAI is the default provider. Cloud
providers are authenticated with opcode login <provider> or an API key (flag,
environment variable or auth.jsonc); Google uses
GEMINI_API_KEY/GOOGLE_API_KEY or --api-key.
- Built in:
anthropic,openai,google,ollama(local, no key),ollama-cloud. OpenAI-compatible endpoints are reachable throughproviders.<id>.base_url. - Discovery:
opcode models --refreshprobes the configured endpoint (and Ollama's/api/tags) and writes<config>/models.jsonc; credentials live in<config>/auth.jsonc(0600). Successful probes also record afetchedtimestamp in<config>/models-cache.jsonc, reused for 24 h;--list-modelsreuses a fresh cache,--refresh-modelsignores it, and--offlinedisables the network probes (a cache miss is never fatal). - Auth precedence:
--api-key› stored OAuth credential ›auth.jsoncapi_key › provider environment variable › configapi_keys. A stored OAuth login owns its provider: an expired token is an error, never a silent fallback to an ambient key. - OAuth subscription logins:
opcode login [anthropic|openai]uses an authorization-code + PKCE S256 flow with a loopback-only callback and an atomic 0600 token store;--manual(alias--paste) instead prints the authorize URL and reads a pasted code, for remote/headless use. On success the provider becomesdefault_provider.opcode logout [provider]removes the stored OAuth credential and clears the provider's stored api_key.
Four ways in, cheapest first:
- MCP servers — stdio JSON-RPC configured in
mcp.jsonc(config dir and trusted project.opcode/); server tools are exposed asmcp__<server>__<tool>. Transport is stdio and onlytools/*is read; a serverisErrormaps to a tool error, and shutdown isSIGTERM→ bounded reap →SIGKILL. - Static C plugins — the stable C ABI lives in
include/opcode_plugin.h. Plugins are listed inplugins/manifest.json;tools/gen-plugins.pyand the Makefile link them with a per-pluginopcode_plugin_init_<name>symbol. A plugin can register tools (with prompt snippets/guidelines), commands, status keys and event handlers, and call back into the host vtable. Loading is static — a static, no-libc binary cannotdlopen. - Declarative resources — context files, skills, prompt templates, named
themes, and project trust. Plugins can contribute skill/prompt/theme roots
through
resources_discover. - Machine modes —
--mode json(JSONL events) and--mode rpcfor editors and harnesses.
Current host limits (the plugin event bus records handlers but only
resources_discover is delivered; set_status/set_title are inert; no async
tools, custom providers or dynamic loading) are documented in
.agents/docs/extensibility.md.
- Slash commands:
/model [id],/thinking,/theme [dark|light|<name>],/compact,/skill:<name>, any registered prompt template, plugin extension commands,/clear,/new,/quit,/help. - Pickers:
/modelwith no argument opens a modal, type-to-filter list of the current provider's models (context window, reasoning/image flags, and(current));/thinkingwith no argument opens the filteredoff|low|medium|highlist. Up/Down move, Enter selects, Esc cancels.--resumeopens the same picker over this directory's sessions, newest first, before the TUI starts;--continuestays a shorthand for the newest session. - TUI modes:
inline(default) owns a fixed region at the bottom and keeps finished transcript blocks in the terminal's own scrollback;scrollbackis the append-only renderer;fullscreenuses the alternate screen;--tui-mode autoresolves to inline. A live queue strip shows messages submitted during a run, andEscreturns them to the editor. - Editor: readline keymap, kill/yank/transpose, a disk-backed history,
bracketed-paste collapse, and
@fileTab completion. Markdown is rendered incrementally; tool calls render as cards with a spinner, elapsed time, diff colouring andCtrl+Oexpand. - Themes: built-in
dark/light,systemfrom$COLORFGBG, and namedthemes/<name>.jsonc; colour is downgraded 24-bit → 256 → 16 automatically.
All files are JSONC (comments and trailing commas; unknown keys are ignored):
- User config —
$XDG_CONFIG_HOME/abird/opcode/config.jsonc(default~/.config/abird/opcode/config.jsonc). - Project config —
<cwd>/.opcode/config.jsonc, loaded only when the directory is trusted:--approve, an interactivetrust this directory? [y/N]prompt in a TTY TUI (saved totrust.jsonc), or an existingtrust.jsoncentry. - Keys read by this build:
default_provider,default_model,session_dir,theme,default_thinking,providers.<id>.base_url,api_keys.<id>. - Sessions —
$OPCODE_SESSION_DIR, else configsession_dir, else$XDG_DATA_HOME/abird/opcode/sessions/--<sanitized-cwd>--/; one JSONL entry per line,--continue/--resume/--session/--session-dir/--no-session. - Context files —
AGENTS.override.md,AGENTS.md,OPCODE.md,CLAUDE.mdfrom the config dir and, when the directory is trusted, every cwd ancestor;SYSTEM.mdreplaces the system-prompt preamble andAPPEND_SYSTEM.mdappends to it (project variants apply only when trusted). - Skills —
<config>/skills/**/SKILL.mdand<cwd>/.opcode/skills/**/SKILL.md. - Prompt templates —
<config>/prompts/*.mdand<cwd>/.opcode/prompts/*.md, expanded with$1..$9/${N:-default}/$@; select with--template NAME [args...].
| Layer | Contents |
|---|---|
base/ |
allocator, SB/VEC, strings, JSONC parser, log, poll/watch event loop |
plat/ |
Layer 0 kernel surface: files, processes, PTY/TTY, time, poll, RNG (plat.inc) |
net/ |
Layer 1 networking: sockets, DNS, TLS vtable (net.inc) |
wire/ |
HTTP/1.1, SSE, JSON DOM/writer, URL |
core/ |
agent loop, transcript/messages, prompt, compaction, session JSONL, config, auth, catalog |
prov/ |
Anthropic Messages and OpenAI Chat/Responses adapters |
tools/ |
built-in tools: read, bash, edit, write, ls, find, grep |
tui/ |
terminal, cell-grid renderer, editor, input, markdown, cards, status, themes |
ext/ |
static plugins (C ABI), MCP client |
app/ |
TUI/print/JSON/RPC modes, subcommands, flags |
Rules between the layers: portable code calls Layer 0/1 and includes opcode.inc
plus the shared interface headers it needs (core/core.inc, net/net.inc,
wire/url.inc/wire/http_client.inc); plat/<os>/ and net/<os>/ never include
core headers; backends are link-time choices (tests link net/mock.s for
deterministic replay); every layer returns negative Linux errno values in
rax; JSON is used only at external boundaries.
- Language: hand-written x86-64 assembly (GNU
as, Intel syntax); the only C is the vendored freestanding mbedTLS and freestanding plugin code. - Runtime: none — no libc, no dynamic linker, no GC, no interpreter; direct Linux syscalls.
- TLS: vendored freestanding mbedTLS 3.6.2 on Linux; the
tls_*contract keeps a future swap link-local. - Formats: JSONC for config/auth/themes, our own JSONL schema for sessions.
- Auth: OAuth subscription logins ship in the core feature set.
- Extensibility: MCP first, then a versioned, append-only C ABI; loading
is static because a
-nostdlibstatic binary cannotdlopen. - Parallel tool execution on by default. MIT licensed.
| Document | Contents |
|---|---|
.agents/plans/DESIGN.md |
goals, requirements, architecture, decisions |
.agents/docs/design-decisions.md |
the decisions the implementation follows, with code references |
.agents/docs/platform.md |
Layer 0/1 contracts, ports, DNS, the vendored TLS decision |
.agents/docs/core-agent.md |
agent loop, data model, tools, providers, OAuth, sessions, config |
.agents/docs/extensibility.md |
plugin ABI, events, MCP, skills, RPC |
.agents/docs/tui.md |
renderer, editor, markdown, cards, status, themes |
.agents/docs/roadmap.md |
per-component status, testing, builds |
.agents/docs/ports.md |
the linux-aarch64, macOS arm64 and Windows ports, and the riscv64 plan |
Opcode builds upon the designs of two projects.
The rhun project, a hand-written assembly editor: it showed that a serious, full-featured application can be built in hand-written assembly, and opcode's allocator, JSON-parser approach and build style owe a debt to it.
The pi project: opcode's UX and extensibility — context files, skills, prompt templates, MCP, machine modes and the session model — are inspired by it.
See THIRD_PARTY.md for the specific adaptations.
We build opcode in the open and would love your ideas, architectural challenges, research, and focused improvements. See CONTRIBUTING.md for the best ways to take part.
Human-centric design. AI-centric implementation. Opcode is built with extensive AI assistance for research, code, tests, and documentation. We believe the best software comes from human-led design and AI-assisted implementation: humans own the architecture, priorities, and acceptance criteria, while AI helps implement, test, and iterate. We don't hand direction to autonomous models or claim every generated line is manually reviewed — correctness rests on explicit designs, reproducible tests, and continuous validation.
MIT © 2026 Prasanna Loganathar and contributors. Opcode-authored text and software are MIT-licensed. Vendored third-party components (mbedTLS, the CA bundle and the adapted base files) and other assets retain their respective upstream licenses; see THIRD_PARTY.md.