fx — the harness as one binary
A 7.8 MiB coding agent in Zig with an empty dependency list. Not few dependencies — none: .dependencies = .{} in the build manifest, so every primitive on this register was written by hand inside one artifact. The three harnesses before it answer the slots by composing parts. This one answers them by refusing to load any, and then argues that a boundary you cannot extend is a boundary you can reason about.
Identification
- Repository
- vercel-labs/fx
- Steward
- Vercel Labs
- Opened
- 11 August 2026
- Licence
- Apache-2.0
- Runtime
- Zig 0.16+ · single static binary, 7.8 MiB
- Dependencies
- none — .dependencies = .{}
- Artifacts
- CLI · N-API addon · fx-core.wasm 2.2 MiB · fx-term.wasm 5.0 MiB
- Surfaces
- shell · fx ask · ACP · libfx 0.0.4, experimental
LineageThe name on the door is Vercel Labs, but the shape is borrowed from further back: the README calls it Unix-like and means it — a program that reads, writes, and composes with pipes rather than a terminal IDE that owns the screen. The more telling inheritance is sideways. fx reads skills out of .opencode/skills/ and .codex/skills/, and its own login flow accepts an OpenAI Codex OAuth token, so it runs on a competitor's subscription and a competitor's know-how while sharing none of their code. It is the youngest sheet on this register by some margin, opened in August 2026, and its SDK still ships at 0.0.4 marked experimental.
Read it as
A sealed instrument
No expansion slots, no field-serviceable parts, nothing to plug in — you change what it does by turning its dials, and anything the dials do not reach is a factory job.
The drawing, in one paragraph
One line in build.zig.zon generates the rest of this sheet: the dependency table is empty. Nothing is vendored, nothing is fetched, and — the part that matters architecturally — nothing can be loaded at runtime either. There is no plugin API, no extension language, no user-supplied hook script; the four lifecycle hooks (PreToolUse, Stop, PostTurnEnd, AttentionRequired) are first-party Zig handlers registered at init, and MCP servers load only from ~/.fx/mcp.json because “repository-local MCP files are never loaded or executed, so cloning a repository cannot add a server.” Extension is therefore data or nothing: AGENTS.md, SKILL.md, mcp.json, settings.json. Code composition happens once, at the build, where the same source emits four artifacts — the CLI, a Node N-API addon, a headless WebAssembly core, and a WebAssembly terminal. Where the previous sheet had two hundred packages behind swappable seams, this one has one file list and a compiler flag.
The examination — eleven slots, one harness
- Built in
- Ships in the core; works with no configuration.
- Via config or extension
- The slot is real but you fill it — config, extension, or convention.
- Deliberately omitted
- Left out on purpose, with a stated argument for the gap.
Three scopes — a global ~/.fx/AGENTS.md, the workspace chain, and target-scoped files — but the resolution is the interesting part: instructions are not assembled once into a system prompt, they are resolved per tool call. “When a tool targets a path, fx resolves the instruction chain for that target,” narrowest scope winning, so an edit under apps/web/src/ carries that package's rules and a sibling edit does not. The layer answers its own limitation: passive text becomes delivered text, chosen by what the tool is about to touch.
Built in
AGENTS.md · resolved per tool target · 128 KiB total
The usual channels, hand-rolled: list, glob, grep, ranged read, file_info, plus web_search and web_fetch as first-party tools rather than an MCP afterthought. semantic_search is honest about being lexical repository search despite the name. Additional workspace directories can be granted for tool access, and they deliberately do not contribute AGENTS.md — reach is widened without letting a second repository start giving orders.
Built in
grep / glob / ranged read · web_search built in
Two mechanisms, both stated as numbers rather than heuristics. Compaction fires after eight completed turns, keeps the latest four verbatim, and condenses everything earlier into a record of requests, outcomes, tool and file evidence, background work, and interruptions — changing only what the model receives, never the saved transcript. Beneath that, every context source carries a declared byte budget in settings: 128 KiB for all project instructions, 16 KiB for the skill catalogue, 1 KiB per MCP tool description, 64 KiB for a selected schema. You can raise one for a single run, or set it to off and still meet a 64 MiB emergency ceiling.
Built in
8 turns → keep 4 · a byte budget per source
Roughly twenty-five built-ins across files, shell, web, skills, subagents, memory and MCP, all compiled in. The MCP client is where the architecture shows: servers come only from ~/.fx/mcp.json, never from the repository, so cloning a project cannot hand the agent a new tool. Discovery is lazy in two steps — mcp_search_tools then mcp_select_tool — with selected tools namespaced and sanitised against collisions, which keeps a large server off the prompt until the model asks for it.
Built in
~25 built-ins · mcp.json is user-global only · lazy select
Permissions and sandboxing are deliberately separate axes, and only one of them is portable. Rules live in settings with wildcard matching, last match wins, workspace over user-global; modes are ask, auto and yolo. The default, auto, puts a second model in the loop — an unresolved call is sent for automatic review on a separate Gateway request using openai/gpt-5.4, independent of the agent's own model, falling back to human approval when that review is unavailable. The sandbox axis is thinner: os exists on macOS only, so on Linux auto resolves to none and the boundary is policy plus reviewer, with no kernel behind it.
Via config or extension
ask / auto / yolo · gpt-5.4 reviewer · OS sandbox: macOS only
Sessions are files under ~/.fx/sessions/, resumable by picker, by id, or as last — which stays workspace-scoped while an explicit id can be rebound to wherever you are now. Compaction is explicitly non-destructive to that record. Two smaller distinctions carry weight: /undo reverses the most recent tracked file operation, and background processes are treated as session state with their own lifetime rule — /clear starts a new session and keeps them running, /reset starts a new session and forgets them.
Built in
~/.fx/sessions · /undo · /clear keeps, /reset kills
The loop exposes four typed lifecycle points and they are not decoration. PreToolUse runs before validation, permissions and execution, and may pass a call through, rewrite its arguments, or block it outright. Stop fires on a terminal assistant candidate and can refuse the ending — requesting a synthetic continuation instead, which is the harness deciding the model is not finished. PostTurnEnd and AttentionRequired observe without altering. Long work escapes the turn entirely: run_command launches OS processes that outlive it, managed afterwards through /background.
Built in
PreToolUse · Stop → synthetic continuation · /background
One subagent tool with six branches — create, inspect, message, relationship, configure, lifecycle — spawning child fx sessions in the parent's workspace with their own model, effort, permission mode, transcript and lifecycle. Children run one-off or persist idle between messages, and the queues between parent and child are durable, so a child works without copying its transcript into the parent's window. The constraint is the good part: “children cannot elevate model-created authority” — a child the model spawns inherits the caller's effective permission mode and cannot ask for more.
Built in
one tool, six branches · durable queues · no privilege escalation
SKILL.md directories with YAML frontmatter, discovered at startup and loaded only on invocation — “discovering a skill does not add its instructions to every prompt.” Budgets keep the catalogue honest at 1 KiB per description and 16 KiB overall, with bodies streamed in 20 KiB chunks. The neighbourly detail is the search path: fx reads .opencode/skills/ and .codex/skills/ as well as its own, but managed installs always land in ~/.fx/skills/ because “fx never writes into another agent's directory.” It borrows know-how and declines to leave fingerprints.
Built in
SKILL.md · progressive disclosure · reads rivals' dirs, writes only its own
Observability is strong and verification is absent, and the sheet should not blur them. /trace produces a private diagnostic trace — carrying prompts, code, paths, commands, model output and possibly secrets, which the docs say to review before sharing — and fx doctor audits workspace, configuration, authentication, resolved startup settings, session state and Git integration without spending a turn. What there is no equivalent of is the language-server fleet on the first sheet of this register: nothing hands the model diagnostics after an edit. Receipts exist only if the model thinks to run the tests itself.
Via config or extension
/trace · fx doctor · FX_TRACE_LOG · no LSP fleet
The thinnest slot on the sheet, and the thesis explains why. Two things persist across sessions: permission decisions, promoted to durable rules by /permissions remember and /allowlist; and the memory tool, which is a flat JSON array of strings at ~/.fx/memories.json supporting exactly save, list and clear — user-global, unscoped, unranked, with no decay and no retrieval beyond reading all of it. Everything past those two dials is a recompile, because the hooks that could have carried learned behaviour are compiled-in Zig with no configuration surface.
Via config or extension
memories.json — save / list / clear · rules · otherwise recompile
Where this design runs out of road
A sealed instrument has no seam to put a provider behind, and the WebAssembly build is the invoice. Compile the same source for the browser and the register goes dark in patches: no subagents, no skills, no MCP, no web search, no OS sandbox, no native processes, no arbitrary filesystem — the host app must supply even run_command itself. Nothing degrades gracefully because nothing was written against an interface that a browser could implement; the primitives were written against the machine, inside the binary, which is exactly what made them small and dependency-free. The trade is legible and it is real: the previous sheet could swap a plank and keep sailing, and this one has to leave the feature out. And where fx does need judgement it cannot compile — deciding whether a command is safe — it does not reach for a seam either. It calls another model.
06Side by sideThe comparison table — one row per primitive, one column per harnessRead from — every finding traces to one of these
- vercel-labs/fxRepository — src/core, src/tools, src/builtins, build.zig.zon
- PermissionsDocs — modes, rules, automatic review, sandbox axis
- Project instructionsDocs — scopes and per-target instruction chains
- Context limitsDocs — byte budgets per context source and defaults
- SessionsDocs — storage, resume, eight-turn compaction
- SubagentsDocs — six branches, durable queues, authority rule
- Skills · MCPDocs — progressive disclosure, search path, mcp.json isolation
- WebAssembly buildDocs — the omission list, and the workspace exec contract