A native C++23 coding-agent harness with a Luau extension layer.
One native binary. No runtime. A scripting ecosystem that costs nothing until you use it.
The short version
Every serious coding harness converged on the same skeleton: one LLM loop over a small set of typed file and shell tools, OS-level sandboxing, markdown instruction files, MCP for reach. So "another harness" is a losing frame.
mcode competes on four things the field leaves alone.
1. Native speed as a product. Every comparable harness ships a runtime: Node,
Python, Go, or Rust. A statically linked C++23 binary cold-starts in single-digit
milliseconds with a small resident set, which is what makes an agent
composable. Scriptable, pipeable, spawnable in parallel, embeddable in CI. A
native Rust harness has been measured at 14.0 ms to first frame and 27.8 MB
resident; a TypeScript one at 590.7 ms and 144.4 MB
([THIRD-PARTY], docs/02). Nobody in this space
currently optimizes for it.
2. A scripting ecosystem in a native core. No mainstream harness embeds a scripting language; their extension language is their runtime. mcode embeds Luau, so the extension ecosystem costs under 1 MB, costs nothing when unused, and extensions are written without a compiler.
3. Context as a budgeted resource. Models degrade before their context window fills, and prefix instability destroys prompt-cache economics. mcode treats context as a budget with explicit accounting, not a buffer you append to.
4. Trust over raw capability. Top-tier resolve rates are saturated and statistically hard to separate. The remaining headroom is whether the agent can be trusted not to game the grader, silently shrink scope, or claim success it never verified.
Everything else (tool names, loop shape, permission axes, TUI idioms) is deliberately copied from systems that already validated it.
Our philosophy:
native core, scripting ecosystem
context is a budget, not a buffer
enforce invariants in the harness, not the prompt
fail closed on authority, fail open on capability
boring technology, spent novelty budget
measure or it did not happen
Note
C++ provides the runtime primitives. Lua provides the ecosystem. That single
rule is why the core knows four abstractions (ModelClient, Context,
ToolRegistry, EventBus) and nothing about Lua, MCP, skills, or the
terminal. New capability attaches to a registry or the event bus, never to the
loop.
| mcode | The field | |
|---|---|---|
| Runtime | None, just a static binary | Node, Python, Go, or Rust |
| Extension layer | Luau VM in-process, ~110 µs marginal per extension | The runtime is the extension language |
| Idle footprint | ≤ 30 MB budget | 100 MB+ is normal |
| Cold start | ≤ 15 ms budget, built for per-invocation spawning | Seconds; designed as a long-lived session |
| TUI | Custom ANSI cell-diff renderer, no TUI library | Blessed framework, or plain stdout |
| Dependencies | 9 runtime, size is the binding constraint | Whatever the ecosystem drags in |
| Extension ABI | A scripting language: versionable, writable without a compiler | A C ABI, dlopen, or nothing |
The interesting number is how much the harness can do at that size. A tool is a Lua table, and a provider is a descriptor.
$ mcode
❯ refactor the config loader to return std::expected
✻ The loader returns bool and swallows the error code, so callers cannot tell
✻ which failure they got. std::expected carries it without changing the call sites.
✓ read src/mcode/support/config.cxx 0.3s
✓ grep "return false" 0.2s
Three call sites need the new return type, then I'll build.
✓ edit src/mcode/support/config.cxx 0.4s
✓ edit src/mcode/cli/session.cxx 0.3s
mcode wants to run:
cmake --build build/Release
rule: default: exec
[y] allow once [a] always allow (saved to this project's store)
[n] deny once [d] never allow (this session) [?] details
✓ bash cmake --build build/Release 8.1s
> /context
▸ /context Show context-window usage: used, capacity and percent
/model Show the model this session runs
/export Write the session transcript to a Markdown file
/mention Pick a workspace file to mention as @path
combo/deepseek ▸ 12.4k tok 8% ▸ $0.031 ▸ 4.2s
What the TUI does
An inline renderer. The committed transcript lives in your terminal's own
scrollback, and a bounded live region at the bottom is repainted by a cell-level
diff. SIGKILL loses at most the live region, and copy-paste and terminal search
work the way you expect.
- Streaming markdown. Headings, lists, fences and inline emphasis render as they arrive, then commit as the rows you saw.
- Tool rows. One live row per call, collapsing to one committed line with the target and duration.
- Palette.
/filters commands,@opens a fuzzy file picker,Ctrl+Rsearches your session history. Ghost text completes at the caret. - Scrollback. PageUp/PageDown and the wheel page history inside the region,
with
Endto return to live. The terminal's own scrollback is never written to or cleared. - Status line. Model, tokens, context percentage (warn at 80%, error at 95%),
spend, elapsed, and the current phase with an
esc to interrupthint. - Approval in the UI. The permission prompt renders in the live region and reads the same console, with option labels that state their scope.
- Resize-safe. A shrink or grow re-lays the region and clears ghost rows.
What an extension looks like
Luau, written without a compiler, loaded in ~0.4 ms:
-- ext.toml: permissions = ["fs_read"]
mcode.tool.register({
name = "todo_scan",
description = "Find TODO markers in the workspace",
schema = { type = "object", properties = { path = { type = "string" } } },
permission = "read",
run = function(args)
local hits = mcode.fs.grep({ pattern = "TODO", path = args.path or "." })
return { count = #hits, hits = hits }
end,
})A tool is a table. It lands in the same registry as the core tools, with the same schemas, permission classes and result types, so the agent's dispatch path cannot tell where a tool came from. Extensions can also subscribe to hooks, register model providers as data, add slash commands, and run timers, all behind a capability check that fails closed.
vs. Node and Python harnesses. They are the reason a coding agent needs a runtime install, a warm start, and hundreds of megabytes resident. mcode is one binary you can spawn per invocation and pipe.
vs. Rust harnesses. Closest in spirit, and they proved the native-speed thesis. They still have no extension language, so adding a tool means shipping a build.
vs. shell-only agents. Minimal and honest, but a typed tool layer is what makes edits anchored, reads bounded, and every action permissioned by class instead of by string matching.
vs. nothing. An agent with no budget discipline burns your context window and your money at the same rate. mcode accounts for both, per turn.
One command downloads the binary for your platform, verifies its SHA-256, puts it on your PATH, and starts a setup wizard. No compiler, no Python, no Conan.
Windows (PowerShell)
irm https://raw.githubusercontent.com/immutex/mcode/master/install.ps1 | iexLinux / macOS
curl -fsSL https://raw.githubusercontent.com/immutex/mcode/master/install.sh | shThe wizard picks a provider, takes an API key, and verifies the endpoint with a
real turn before it writes anything. It writes exactly one file:
mcode config's path, which it prints.
Then, from your project:
mcode # interactive session
mcode "fix the failing test" # one task, then exit
mcode exec --json "…" # machine-readable event streamTip
Run mcode --help for the flag surface, and /help inside a session for the
command palette. --yolo skips approval prompts. It does not disable the
hard-deny floor.
Build from source instead
mcode is a C++23 project, so this route needs a compiler and Conan 2. The bootstrap script does the three things that are easy to get wrong by hand: it activates the MSVC developer environment, builds the private Luau package, and selects the right Conan profile.
Windows
python -m pip install "conan==2.32.0"
pwsh -File scripts/bootstrap.ps1
cmake --build build/Release
ctest --preset windows-msvc
./build/Release/bin/mcode.exeLinux / macOS
python3 -m pip install "conan==2.32.0"
./scripts/bootstrap.sh
cmake --build build/Release
ctest --preset linux-gcc # or macos-clang
./build/Release/bin/mcodeThe design is the authoritative specification. Where code and docs disagree, the docs win. Start at the index.
- Design index: reading order for all 43 docs
- North Star: thesis, hard budgets, principles, non-goals
- Prior art: 16 harnesses compared, plus the Lua-extension precedent
- Architecture: process model, layers, core types, extension seams
- Agent loop: state machine, termination, test-time compute
- Context engineering: budgeting and compaction
- Core tools, MCP, Skills
- Security: trust boundary, permission model, sandboxing
- CLI & TUI: renderer, theme, commit protocol
- Lua runtime, Lua API, Extensions
- Stack, Providers, Cross-platform
- Measurements: extension load, RSS, hook dispatch, measured
- Non-obvious constraints: the traps, numbered
- Roadmap: milestones and risks
CHANGELOG.md records what changed in each release.
| Area | State |
|---|---|
| Agent loop | plan → act → observe → verify, with reflect, replan and thrash guards; three-axis budget (steps, tokens, USD) |
| Core tools | read, edit, write, glob, grep, bash, ask_user, tool_search |
| Permissions | Hard-deny floor ahead of every rule and ahead of --yolo; project and session rule stores; approval prompt inside the TUI; a taint gate that holds back out-of-workspace actions once untrusted content is in the session |
| Sandboxing | OS primitives on all three platforms: Linux and macOS sandbox profiles, Windows restricted tokens and job objects; egress denied by default, with an allowlist proxy for when it is enabled |
| Extension trust | A repository's .mcode/extensions/ is inert until a grant matches its tree digest; the bytes hashed are the bytes executed, so there is no TOCTOU window |
| Luau extensions | 28-name frozen API surface, hooks with veto, timers, net with a host allowlist, provider descriptors, per-VM memory and time budgets |
| MCP | stdio and Streamable HTTP transports, supervisor with restart and backoff, server tools flattened into the one registry |
| Skills | AGENTS.md chain with closest-wins precedence, skill discovery and a budgeted routing index |
| Subagents | Read-only fan-out: the child's registry is built from the read class, so a write call fails schema validation rather than reaching an approval prompt |
| Memory | A user-authored notes ledger, /remember to append, memory_search to grep it; the ledger lives outside the workspace and no model writes to it |
| Model layer | Streaming SSE, delta assembly, tool-call fragments, retry policy, prompt-cache alignment, provider descriptors as data |
| Context | Tool-result clearing before compaction, a pinned state block at the front of the prompt, and a meter that reads window fill rather than cumulative spend |
| TUI | Inline cell-diff renderer, streaming markdown, palette, mention picker, history search, bracketed paste, in-region scrollback |
| Sessions | Append-only JSONL event log per workspace; --continue, --resume, --sessions, --worktree |
| Verification | --verify <command> arms a gate the harness runs itself; the run reports verified, failed or no-tests, and mcode eval prints false_success_rate |
| Observability | Typed event log, exec --json for editors and CI, eval suite |
Not built yet: OSC 52 clipboard, image input, background jobs, LSP, ACP, and
the /skill, /mcp, /session, /reload commands.
Shipped in 0.1.0: the batch that connects what 0.0.x built. Eleven interfaces
existed with a producer or a caller missing, each with a passing test — the defect
was unconnected code, not absent code. They are wired, and the seven first-party
extensions docs/23 mandates all ship: task, fs-extra (todo), skills,
memory, web (web_search, fetch), git, github. Two new core API entries
carry them: mcode.bash, which routes to the same core bash handler so the
sandbox and the permission gate cannot be bypassed from Lua, and mcode.fetch
for a URL the model chose, gated per domain and refusing private addresses. The
loop now recites the goal and the plan every ten steps and after each compaction,
routes by model tier (model.tier.plan|act|side), and delegates to a read-only
subagent whose budget is carved from the parent's. MCP gains resources/* and
prompts/*.
Shipped in 0.0.3: the repo-local extension trust gate, mcode ext install,
the verification gate and its false_success_rate, the context controller,
memory, read-only subagents, and MCP over Streamable HTTP. The egress proxy ships
with its limits stated: it raises the cost of exfiltration and does not stop it
through an allowlisted destination, and Windows network enforcement reports
best-effort because WFP requires admin.
Shipped in 0.0.4: a code-quality pass over the whole tree, not a feature
batch. Dead code and history-narrating comments are gone, every file is under 600
lines, and the comment rule is a CI gate (tools/comment_lint.py) rather than a
convention. Three defects were fixed on the way, all found by running the code:
the egress proxy sent a SOCKS5 reply with an invalid address type, mcode ext deny wrote a trust record its own parser rejected (losing every grant beside
it), and five file-scope symbols in mcode::platform had external linkage
despite no header declaring them.
src/mcode/
├── core/ error model, tool registry, version
├── support/ logging, JSON, TOML, Unicode, config
├── ext/ the Luau extension host
├── fs/ workspace boundary and file primitives
├── net/ SSE parser, HTTP client
├── proc/ subprocess, PTY
├── perm/ the permission engine and stores
├── mcp/ MCP client and supervisor
├── skills/ discovery, frontmatter, routing index
├── instruct/ the AGENTS.md instruction chain
├── model/ provider descriptors, request rendering, capabilities
├── tools/ the core tool set
├── tui/ terminal layer, cell buffer, renderer
├── events/ the event bus
├── agent/ loop, session budget, event log
└── cli/ command surface
Dependencies point downward only, from agent/ toward core/. The core knows
four abstractions (ModelClient, Context, ToolRegistry, EventBus) and
nothing about Lua, MCP, skills, or the terminal.
Everything comes from Conan 2 except Luau, which mcode builds from its own
recipe. See docs/14 for why each was chosen. Size is
the binding constraint: a new dependency must justify itself against measured
size and compile cost, not against an arbitrary count.
| Component | Version | Role |
|---|---|---|
| Luau | 0.0.0-mcode.c0e346ed (commit c0e346ed) |
Extension layer |
| yyjson | 0.12.0 | JSON |
| Boost | 1.91.0 | Beast (HTTP/SSE), Process v2 (subprocess) |
| fmt / spdlog | 12.1.0 / 1.17.0 | Formatting, logging |
| mimalloc | 3.5.1 | Allocator |
| simdutf | 9.0.0 | UTF-8/UTF-16 |
| ankerl::unordered_dense | 5.0.1 | Tool registry |
| OpenSSL | 3.5.7 | TLS for the model transport (static) |
| Catch2 | 3.16.0 | Tests |
Every trap that cost real time to find is recorded in
docs/40, numbered, with the reason it
exists. Docs cite them by number. Read the one you were sent to.
cmake --preset windows-msvc # or linux-gcc / macos-clang
cmake --build build/Release
ctest --preset windows-msvcPresets are defined in CMakePresets.json and consume the Conan toolchain, so
conan install must run first. -DMCODE_WARNINGS_AS_ERRORS=ON for a strict
build, and the dev preset turns LTO off for faster iteration.
Note
MSVC is only on PATH inside a developer prompt. Either run
scripts/bootstrap.ps1 (which activates it) or start a "Developer PowerShell
for VS". A MinGW link.exe on the same machine will otherwise shadow the linker
and produce confusing errors.
The smoke test proves the toolchain works end to end: every dependency, the Lua boundary, the workspace boundary, and the agent loop. Its exit code is the number of failed checks, so CI gates on it directly.
./build/Release/bin/mcode --smokeCI runs the full suite on Windows and Linux, and build + smoke on macOS. The
strict warning set is on everywhere; _clgate.py also syntax-checks every
translation unit against a second toolchain, because the Windows build defines
_WIN32 and never parses the POSIX branches.
Windows, Linux, and macOS are all tier-1. Every abstraction that touches the OS (process spawn, PTY, sandbox, paths, file watching) is designed behind an interface, because retrofitting a platform later is how a project ends up with a Windows-shaped core.
| Platform | Toolchain | Linking |
|---|---|---|
| Windows x64 | MSVC 19.4x | /MT, fully static |
| Linux x86_64 | GCC 14+ or Clang 18+ (libstdc++) | musl static for release |
| macOS arm64 | Apple Clang 16+ | Cannot be fully static: Apple requires a dynamic libSystem |
AGENTS.md is the authoritative rule set, and it applies to humans as much as to
agents. Read CONTRIBUTING.md for setup, the style rules that
are load-bearing in review, and the two things CI catches that no local build
will — the POSIX branches, which a Windows workstation never compiles, and the
warnings-as-errors set, which is off locally and on in CI.
Two checks are worth running before every push:
python _clgate.py # clang++ with the CI Clang warning set, in seconds
./build/Release/bin/mcode --smoke # exit code is the number of failed checksReport vulnerabilities privately through
GitHub Security Advisories,
never in a public issue. SECURITY.md has the threat model, what
is in scope, and — importantly — a table of which controls actually ship, since
the design docs describe the target and the tree is at 0.0.1.
Apache-2.0, see LICENSE.
mcode embeds Luau, which is distributed under the MIT License. Luau is
Copyright (c) 2019-2025 Roblox Corporation and Copyright (c) 2005-2019 Lua.org,
PUC-Rio. The full text ships in the Conan package under licenses/ as
LICENSE.txt and lua_LICENSE.txt, and is reproduced in
THIRD-PARTY-NOTICES.md.
Upstream asks that products embedding Luau carry attribution for the language and a link to https://luau.org/ in their documentation. This section and the notices file satisfy that request.