A minimal, extensible coding agent in freestanding C23 — zero libc, static binaries, direct platform networking, vendored mbedTLS, and a versioned C ABI for C and Rust extensions.
Related research: the sibling project opcode takes the same design to its extreme — a real coding agent written entirely in hand-written x86-64 assembly, statically linked and libc-free.
agentcis built off the learnings fromopcode: this is the production, supported sibling (opcodepoints back here for real work).
- ~0.7 MB RSS at cold start, ~2–4 MB in a working session — roughly 0.7 MB plus 2.5× the on-disk transcript
- Under 1 ms to launch; the 12 MB session budget holds up to ~4 MB transcripts
- ~940 KiB stripped (~2.0 MiB with debug info) — 304× smaller on disk than codex, and ~285× less session memory than Claude Code
- One tree → six targets: Linux x86-64/aarch64/riscv64, macOS arm64/x86-64, Windows x86-64
- MCP first — tools, prompts, resources — then a versioned C ABI (
include/agentc_ext.h) - Benchmarked against pi for UX and extensibility
make bench reports cold-start time, peak RSS and binary size for agentc on your machine;
Footprint measures the same four numbers against codex, Claude Code and pi.
Four agents doing the same job, measured on one Linux x86-64 machine. Everything below is
observed, not estimated: peak RSS is VmHWM.
| agent | version | on-disk payload | cold start | cold-start RSS | first TUI paint | session RSS |
|---|---|---|---|---|---|---|
| agentc | 0.8.0 | 940 KiB static binary, no runtime | 0.3 ms | 0.7 MB | ~1 ms | 0.7 MB |
| codex | 0.161.0 | 279 MiB native binary | 9 ms | 25 MB | ~20 ms | 85 MB |
| Claude Code | 2.1.293 | 241 MiB single-file binary | 9 ms | 39 MB | ~180 ms | 191 MB |
| pi | 0.99.2 | 17.5 MiB bundle + Node 24 (75 MiB) | 249 ms | 113 MB | ~800 ms | 209 MB |
- Cold start is
--version(process start to exit, best of three) viatools/measure.c. - Session RSS is the peak while the TUI sits at its prompt in a pty, sampled from
/proc/<pid>/statusevery 50 ms; first TUI paint is the first byte it writes. Both include whatever the agent does before it paints. - Artifacts: agentc is
make release; codex and Claude Code are their vendor Linux x86-64 release tarballs — self-contained native executables at 279 MiB and 241 MiB; pi is its npm bundle plus the Node runtime it requires (Node is shared, not pi's own). - The gap is the runtime, not the feature set. agentc ships no interpreter and no GC, so at the prompt it maps ~0.4 MB of its own text and holds ~0.2 MB of heap. The other three hold 85 MB (codex), 188 MB (Claude Code) and 139 MB (pi) of heap and shared memory — before a transcript exists.
# Linux / macOS
curl -fsSL https://raw.githubusercontent.com/abird-ai/agentc/master/install.sh | sh
# Windows (PowerShell)
irm https://raw.githubusercontent.com/abird-ai/agentc/master/install.ps1 | iex
agentc setup # pick a provider, store credentials, pick a model
agentc -p "summarise this repository" # one-shot; drop -p to start the TUIThe installer verifies the release archive's SHA-256 and installs to ~/.local/bin
(AGENTC_INSTALL_DIR overrides, AGENTC_VERSION=0.8.0 pins).
make # -> build/agentc (native: Linux x86-64, macOS)
make check # offline golden suite + extension pipeline
make bench # startup time, RSS, binary size
make install PREFIX=~/.local
make dist # release tarball + SHA256 -> dist/
make ext-pipeline # C + Rust extension harness -> build/ext_harness
make client # framed-protocol client -> build/agentc-client
make lib # embeddable SDK archive -> build/libagentc.a
make libcheck # SDK archive + symbol deny-lists + PIE host link
make libstrict # prove the SDK archive has no src/app/*.o
make install-lib PREFIX=~/.local # SDK headers + libagentc.a/.so + agentc.pc
./tests/e2e.sh # mock provider: tool round-trip, --continue, pty TUI
./tests/net.sh # live loopback HTTP/SSE/TLS
./tests/mcp.sh # MCP stdio against a python mock
sh tests/serve.sh # agentc serve: unix/TCP/WebSocket/TLS, protocol, dialogs
make windows # cross build -> build/agentc.exe (LLVM on PATH)
make wine-check # run the Windows golden suite under Wine (Linux)
nix build # or nix developRelease matrix — built by Nix on one Linux runner, attached to the tag:
nix build .#release-all
ls result-release/bin/
# agentc-linux-x86_64 agentc-linux-aarch64 agentc-linux-riscv64
# agentc-windows-x86_64 agentc-macos-arm64 agentc-macos-x86_64
# agentc-macos-universal- Linux builds are freestanding on three architectures.
- Windows links no CRT/SDK; import libraries are generated from
src/win/*.def. - macOS slices carry the Apple SDK (fetched by Nix) and link libSystem through LLVM's
Mach-O linker;
llvm-lipomerges them into a universal binary.
To cross build by hand:
make release CC="clang --target=aarch64-unknown-linux-gnu" ARCH_FLAGS=-fno-pic \
REL_OBJDIR=build/obj/aarch64/release OUT=build/agentc-aarch64| target | builds | CI | driven by hand |
|---|---|---|---|
| Linux x86-64 | yes | golden, e2e, live net/TLS, MCP, extensions | yes |
| Linux aarch64 | yes | golden suite under qemu-user |
yes |
| Linux riscv64 | yes | golden suite under qemu-user |
yes |
| macOS arm64 | yes | golden suite, native Mac runner | no |
| macOS x86-64 | yes | same Mac runner | no |
| Windows x86-64 | yes | golden suite under Wine, real cmd.exe |
no |
| Windows arm64 | yes (PE header checked) | native Wine on an ARM64 runner (non-gating) | no |
Driven by hand means a person ran a real session on that platform — not the same as a suite passing in CI. Linux is exercised end to end, cross-built binaries included.
- Windows CI covers: the Win32 syscall surface, CRT-free startup, file/console/socket
paths, the
WSAPolltranslation, and realcmd.exeexecution (capture, redirection, exit codes, cancellation, background drain, truncation). - Still unverified: Wine is not Windows — SChannel/TLS against a real provider, real
console behaviour, PowerShell 5.1/
pwshexecution and Windows ARM64 (Wine does not emulate a CPU) are untested. macOS and Windows produce valid Mach-O/PE images, but nobody has driven them by hand.
- macOS —
make && make checkis enough; mention Apple silicon or Intel, since the two slices are built separately. - Windows —
agentc --version,--list-models, then a prompt that calls a tool. Most useful from a machine that only has Windows PowerShell 5.1. - TUI — platform differences bite first at resize, unicode width, Ctrl-C and paste.
- Reporting — an issue with the binary you downloaded and your OS version.
Patches welcome: src/plat/<os> and src/net/<os> are the only directories a port touches.
Toolchain: clang -std=c23 -ffreestanding -nostdlib -static (no libc, own allocator,
own _start), GNU make, optional python3 for the extension registry. Freestanding targets
build with -nostdinc plus third_party/freestanding/, so a host libc header cannot leak
in through a vendored library.
Shell on Windows: cmd.exe by default. The shell config key selects auto, cmd,
powershell (7 if installed, otherwise 5.1) or pwsh; AGENTC_SHELL overrides the file,
and a missing requested shell is a clear error, never a silent fallback.
Dynamic extensions: CI builds and loads a real extension library on macOS/Windows
(make dyn-check), so runtime loading there is CI-gated rather than hand-tested.
agentc setup [--offline] # provider, credentials, model
agentc --list-models [filter] # catalog + discovered
agentc --refresh-models # ignore the 24 h discovery cache
agentc --provider ollama --model llama3.2
agentc --provider xai --model grok-4 --api-key $XAI_API_KEY- Built in:
anthropic,openai,google(native Gemini API),ollama(local, no key),ollama-cloud, plus the OpenAI-compatible presetsopenrouter,xai,deepseek,groq,mistral,together,gemini— one adapter, different base URL. - Discovery: built-in catalog plus
GET /modelson the configured endpoint (Ollama's/api/tagsfirst, so family and size show in the picker). Cached per provider for 24 h in~/.config/abird/agentc/models-cache.jsonc;--offlinedisables probes and a cache miss is never fatal. - First run: a bare
agentcon a tty with no credential, setup file or flags offers a short menu (local Ollama is probed for you), writingsetup.jsoncandauth.jsonc(0600, merged so existing keys and OAuth logins survive). - Subscriptions:
agentc login anthropic|openaisigns in with a subscription (OAuth) and makes that provider the default for the next start (it rewritessetup.jsonc, preserving any other keys you added);--manualprints the URL and reads the pasted code.agentc logout [provider]removes the stored credential — a subscription token or anauth.jsoncAPI key — for any provider id. Ifconfig.jsoncpinsdefault_provider, login says so instead of silently writing an overriddensetup.jsonc. - Precedence: flags ›
config.jsonc›setup.jsonc; credentials--api-key› stored OAuth › provider env vars ›auth.jsonc› configapi_keys. A stored OAuth credential owns the provider, so a failed refresh is an error, never a fallback to an ambient key. - Custom gateways: an unknown
providers.<id>.base_urlinconfig.jsoncbecomes an OpenAI-compatible provider named<id>; it needs no credential (a local gateway may accept unauthenticated requests) and readsapi_keys.<id>/auth.jsoncwhen one is set, so--provider <id>,--list-modelsand discovery work without a linked extension. For a one-off endpoint,--base-url Uworks alone (applied to the default provider) or with an unknown--provider <id>to define an ad-hoc gateway for that run;agentc setup's Custom endpoint choice prompts for a name, base URL and optional key and persists it tosetup.jsonc. SetOLLAMA_HOSTorproviders.ollama.base_urlto point the Ollama preset at a non-default host. - Subcommands:
setup,loginandlogoutare the only subcommands;agentc update,agentc configandagentc mcpare unimplemented and exit 2 (unknown argument). Editconfig.jsonc/mcp.jsoncdirectly instead. - Virtual models (aliases): a top-level
models.<alias>config declares{"provider":"…","model":"…"}; the alias resolves to that concrete target per model step while the transcript keeps the alias, and a broken route fails the step closed. This is how to pin a cheap model for routine work or route around a provider without changing the session. - Extensions add providers too: a linked extension registers its own wire dialect (request
builder + SSE mapping) and model metadata via
add_provider, while core keeps transport, retries, credentials and header sanitization (extensions/fake_provider/is the reference).
- Tools, plus prompts and resources:
- server
promptsbecome/mcp__<server>__<prompt>commands (bounded synchronousprompts/get) - server
resourcesgo through three generic read-only tools:mcp_list_resources,mcp_list_resource_templates,mcp_read_resource(text only;blobentries are rejected)
- server
- Extensions can add skill, prompt and theme roots via
resources_discover, so prompts and named themes ship with the extension without an ABI change. - Limits (over the cap the call fails rather than truncating): 512 prompt registrations, 128 MCP prompts/server and 512 total, 256 resources/server and 1024 total, 64 KiB prompt text, 256 KiB resource text, 64 file templates/themes per scan.
- Resilience: a failed server retries behind capped exponential backoff
(
AGENTC_MCP_RETRY_MS=0disables), status is published throughmcp_servers_change, and an idle stdio server'slist_changedis picked up on the next pump without a request. An idle HTTP server's optional GET event stream (Accept: text/event-stream, withMcp-Session-IdandLast-Event-ID) is pumped the same way;405disables it for good and a dropped stream reconnects without failing the server. - Isolated extensions:
agentc_ext_register_helper()can run an extension in a separate process over the framed JSON hook protocol, so a crashing helper is retired fail-closed instead of taking the agent down (linked/dynamic extensions stay in-process).
/model [id|alias]and/thinking [off|low|medium|high]report or switch; with no argument each opens an interactive picker. A model id is provider-scoped: switching to a model that belongs to another provider is refused with a hint to restart with--provider, so the current endpoint is never silently pointed at a foreign model. A virtual model (alias) is the exception:/model <alias>and the picker — which lists aliases first, labelledalias -> provider/model— switch in place, because step routing resolves the concrete target; no restart is needed. Reasoning defaults to medium unless--thinkingordefault_thinkingsays otherwise. ·/resumeand/continueopen a session picker at any point and switch cleanly (a running turn is cancelled first). ·/compactsummarizes the context. ·/help·/theme [dark|light|<name>](named themes fromthemes/<name>.jsonc) ·/skill:<name>· any registered prompt template, including MCPmcp__<server>__<prompt>, runs as/name·/newstarts a fresh session ·/forkbranches the conversation into a new session ·/treebrowses the session fork forest ·/clear·/quit- TUI: the interactive view is fullscreen (the alternate screen) with a transcript
scrollbar and mouse-wheel scroll. On exit it leaves a compact trace in the shell
scrollback — banner, the first prompt,
..., the last message — instead of the whole transcript.
agentc --mode rpc(RPC v1) is a language-independent control channel: one JSON command per line on stdin, responses and events per line on stdout. It is acceptance-first (promptanswers{"disposition":"started"}before the run, which completes with anagent_settledevent), queues commands while a run is active, and exposesget_state/get_commands/get_available_models/get_messages/set_model/set_thinking_level. The record layer is frozen in.agents/design/60-rpc.md; the version isAGENTC_RPC_VERSION(1).agentc serveis a long-lived framed-CBOR session server. It listens on a Unix socket by default ($XDG_RUNTIME_DIR/abird/agentc/serve.sock,0600in a0700directory) or on TCP with--tcp IP:PORT,--host IP[:PORT]/--remote(a non-loopback bind requires--token),--wsfor an RFC 6455 WebSocket upgrade, and--tls CERT KEYfor TLS (Linux, vendored mbedTLS). It prints a ready JSON line, broadcasts agent events, and keeps a bounded registry of up to 4 independent sessions per process (serialized; each has its own agent and transcript), withnew_session/attach/detachrouting a connection between them. It raisesui_requestdialogs (select/confirm/input/editor) and tool approvals, and cancels a run out of band. See.agents/design/61-protocol.md.agentc-clientis the synchronous framed-protocol client (make client):prompt,events,call,state,cancel,reconnect,resync,new-session,attach, and auisubcommand that answers a dialog.libagentc(SDK) embeds the engine in a host process (one engine / one agent per process) throughinclude/agentc_sdk.h.make libbuilds the PIC archive,make libcheckproves the symbol/object deny-lists and a default-PIE host link,make libstrictproves the archive carries nosrc/app/*.o, andmake install-lib PREFIX=…installs the headers, static/shared library andagentc.pc. See.agents/design/70-sdk.md.
- Language: freestanding C23; no assembly, no runtime.
- TLS: vendored freestanding mbedTLS 3.6.2 on Linux; the OS stacks elsewhere
(SecureTransport, SChannel). The TLS path never
dlopens. - Ollama: through its OpenAI-compatible
/v1endpoint — one wire path for every OpenAI-style provider, no second SSE mapper. - Formats: JSONC config/auth/themes, serde-default JSONL sessions; no cross-agent file-format compatibility.
- Auth: OAuth subscription logins (Claude Pro/Max, ChatGPT) ship in the initial release.
- Extensibility: MCP first, then a versioned C ABI whose tools may be synchronous or
asynchronous. Extensions link into the binary, or on macOS/Windows load at runtime from
<config home>/extensions/(the Linux freestanding build is static-only). - Parallel tool execution on by default; MIT licensed.
agentc is built with heavy inspiration from pi by
Mario Zechner, a minimal, extensible coding agent whose UX, extension model, tool set and
durable/MCP concepts shaped this design. pi is MIT licensed; no pi code or text is copied here.
- opcode — the sibling research project: the same
agent written entirely in hand-written x86-64 assembly, built to push the design to an
extreme.
agentcis built off the learnings fromopcode; the two share a feature surface, andopcodelinks back here as the production sibling. - Model Context Protocol — the standard
agentcspeaks - Mbed TLS — the vendored TLS backend on Linux
- curl CA extract — Mozilla CA trust anchors, replaced by a system bundle when one is present
- Built with clang/LLVM, Nix, Rust/cargo and GNU make
The architecture and design record live in .agents/ — design/ for module contracts, plans/ for forward-looking work. They are agent-facing and are not part of the release artifacts.
We build agentc 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. agentc 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. agentc-authored text and software are MIT-licensed. Vendored third-party components (mbedTLS, the curl CA extract and the adapted base files) and other assets retain their respective upstream licenses; see THIRD_PARTY.md.