8 agent harnesses,
read from the source

Every coding agent ships the same shape of turn and almost nothing else in common. This is what their insides actually look like — subsystem by subsystem, measured at a pinned commit rather than summarised from a README.

Architecture atlas

These eight harnesses agree on almost nothing except the shape of a turn — and they are not even strangers to each other: four of the eight descend from one 126k-line core, and one ships working bridges to three of the others. Underneath that, they split along five axes: where the boundary is (OS sandbox, opt-in container, permission engine, or nothing), where the extension seam is (in-process hooks, a plugin container, a protocol, a marketplace, or the interpreter itself), where the state lives (JSONL log, event log, or SQL), how many surfaces the core has to serve, and — the axis that explains the most — who is allowed to talk to the agent.

The harnesses

Each plate decomposes one harness into its real subsystems, with the files and line counts every claim came from.

earendil-works/pi

pi · the minimal core

Self-extensible agent, no sandbox

LANGUAGE
TypeScript · Node/Bun
SOURCE LOC
120,756 (+115k tests)
PACKAGES
10
Open the plate →

PrimeIntellect-ai/prime-agent

prime-agent · the RLM fork

One tool: a live Python interpreter

LANGUAGE
TypeScript + Python
SOURCE LOC
151,531 TS (+2.6k py)
PACKAGES
4 + 1 python runtime
Open the plate →

can1357/oh-my-pi

omp · the maximal fork

Native-powered agent with hash-anchored edits

LANGUAGE
TypeScript + Rust · Bun
SOURCE LOC
1.41M TS · 171k Rust
PACKAGES
17 packages · 8 crates
Open the plate →

deepseek-ai/deepseek-harness

dsh · everything is a plugin

Everything is a hot-swappable plugin

LANGUAGE
TypeScript · Node · Cordis
SOURCE LOC
236,248 (+291k tests)
PACKAGES
234 workspace packages
Open the plate →

anomalyco/opencode

opencode · server-first

Effect-native agent server, many clients

LANGUAGE
TypeScript · Bun · Effect
SOURCE LOC
~495k (+173k tests)
PACKAGES
33
Open the plate →

NousResearch/hermes-agent

hermes · the personal agent

Personal agent that writes its own skills

LANGUAGE
Python 3.11+ · TS surfaces
SOURCE LOC
919,291 py (+779k tests)
MODULES
13 packages · ~600 modules
Open the plate →

openclaw/openclaw

openclaw · the gateway

Assistant in your chats, on your devices

LANGUAGE
TypeScript (+Swift/Kotlin)
SOURCE LOC
~1.83M core (+0.95M ext)
MODULES
78 src dirs · 22 pkgs · 151 ext
Open the plate →

openai/codex

codex · sandboxed by default

Rust agent, sandboxed by default

LANGUAGE
Rust · Tokio
SOURCE LOC
~1.08M (1.39M with tests)
CRATES
134 Cargo members
Open the plate →

Side by side

Every row was answered from source. Where a claim could only be evidenced from a project's own documentation, it is marked partial rather than asserted.

+ present or strong · ~ partial, off by default, or documentation-only · verified absent

Feature comparison across 8 agent harnesses, 89 rows.
FeaturePiPrime AgentOMP (Oh My Pi)DeepSeek HarnessopencodeHermes AgentOpenClawCodex CLI
FOUNDATION
Primary languageTypeScriptTypeScript + PythonTypeScript + RustTypeScriptTypeScriptPythonTypeScript (+Swift/Kotlin)Rust
RuntimeNode 22 / BunNode 22.8+ / BunBun (enforced)Node 22/24Bun (+Node path)CPython 3.11–3.13Node 22.22+ (pnpm)Tokio async
Build systemtsgo + npm workspacestsgo + npm workspacesBun + Cargo + Bazeltsc + tsdown + pnpmBun + Turboreposetuptools + uvtsdown + build-all.mtsCargo + Bazel
Source LOC125,552 TS151,531 TS (+2.6k py)782k TS + 170k Rust245,957 TS482,356 TS918k py + 367k TSpartial: 1.83M core (+0.95M ext)1,079,904 Rust
Test LOC / count115,736 · 476 files161,495 · 463 files612,043 · 2,265 files303,442 · 1,014 files188,075 · 848 files785,624 py · 3,114 filespartial: 4.6M · 11,971 files312,784 · 467 files
Modules10 packages4 pkgs + 1 py runtime17 pkgs + 8 crates234 packages33 packages13 pkgs / ~600 modules78 src · 22 pkgs · 151 ext134 members · 141 crates
LicenseMITMITMITMITMITMITMITApache-2.0
Binarypiprime-agent (bin: pi)ompdshopencodehermesopenclawcodex
Distributionnpm + 6 Bun binariescurl installer + binariesnpm, brew, nix, docker, curlnpm (npx dsh web)curl, npm, nix, dockerpartial: shell/docker/nix only — no PyPInpm, docker, nix, fly, brewnpm + brew binaries
THE TURN
Agent loopagent-loop.ts · 796 Lagent-loop.ts · 963 Lagent-loop.ts · 2,935 LReactLoopAgent (plugin)prompt.ts runLoop:1081conversation_loop.py · 6.5k Lrun-loop.ts · 725 Lsession/turn.rs run_turn
OrchestratorAgentSession · 3,344 LAgentSession · 10,946 LAgentSession · 9,428 LAgentLoop service + inboxSessionProcessor + runnerAIAgent · ~180 methodsGateway + run-orchestratorSession + submission_loop
Turn protocolin-process eventslocal daemon socketin-process eventsappend-only event logEffect stream → SQLite rowsNormalizedResponsegateway RPC + eventspresent: Op / EventMsg queue (~95)
Parallel tool callspresent: Promise.all unless sequentialpartial: inherited; one toolpresent: shared/exclusive schedulerpresent: rolling pool, cap 10present: AI SDK concurrencypresent: 3 executors, 8 workerspresent: per-turn schedulerpresent: RwLock read/write gate
Interrupt modelone AbortControllerpresent: abort + kernel interruptpresent: four distinct signalsfused AbortControllersfiber interruptioninterrupt / steer / redirectabort across gatewayCancellationToken tree
Mid-run steeringpresent: steering + follow-up queuespresent: steering + side questionspresent: non-consuming detectionpresent: per-agent inboxpartial: queued inputpresent: steer() / redirect()present: queued follow-upspresent: input_queue drain
Retrybackoff + regex classifierbackoff + classifier (pi)present: model fallback chainsprovider-owned policy5 attempts, honors retry-afterclassifier + credential rotationpresent: failover chainsstream + request level
Context-overflow recoverypresent: compact then continuepresent: compact then continuepresent: compact then continuepresent: compact then retrypresent: compact then continuepresent: compress + failoverpresent: queued compactionpresent: auto-compact then continue
TOOLS
Built-in tools7 (4 default)1 — ipython325217104partial: 45 core, per-contextpartial: 32
Edit formatexact string replaceexact replace (host op)hashline (content-anchored)string replace + str_replace_editor9-strategy fuzzy replace9-strategy fuzzy + V4A patchstring replace + repairapply_patch (Lark grammar)
Read-before-write guardabsent: noneabsent: nonepresent: seen-line provenancepresent: fs-observation policyabsent: nonepresent: file_state trackingpartial: via tool policypartial: context-match anchoring
Shellone-shot spawnpartial: host op, not a toolpresent: persistent embedded shell (Rust)one-shot + persistent PTY pkgone-shot, 120spresent: persistent per task_idexec host or containerpresent: persistent PTY sessions
Shell built-ins in-processabsent: noabsent: nopresent: ~58 Rust utilitiesabsent: noabsent: noabsent: noabsent: noabsent: no
Code searchauto-downloaded rg + fdpartial: Python in the kernelnative Rust grep enginepackaged @vscode/ripgrepauto-downloaded ripgrepsubprocess grep/globpartial: ripgrep via execpartial: none — model uses shell
LSPabsent: noneabsent: nonepresent: full client + mux daemonpresent: lsp tool, stdio providerpresent: 38 servers, auto-installpresent: client + managerabsent: noneabsent: none
AST / structural editabsent: noneabsent: nonepresent: ast_grep + ast_editabsent: nonepartial: tree-sitter for shell parsingabsent: noneabsent: noneabsent: none
Debugger (DAP)absent: noneabsent: nonepresent: 14 adaptersabsent: noneabsent: noneabsent: noneabsent: noneabsent: none
Browser controlabsent: noneabsent: nonepresent: Chromium + relay to your browserpartial: web_fetch / web_searchpartial: webfetchpresent: 3 backends (CDP, camoufox, browser-use)partial: sandboxed CDP sidecarpartial: hosted web_search
Desktop / computer useabsent: noneabsent: nonepresent: windows, input, a11y treesabsent: noneabsent: nonepresent: computer_use (macOS)present: via native app nodesabsent: none
Code mode / interpreterabsent: nonepresent: the only toolpresent: 4 REPL kernels w/ tool bridgepresent: run_code reserved transportpresent: confined JS interpreterpresent: execute_codeabsent: nopresent: V8 isolate (exec/wait)
Plan / todo toolpartial: example extensionabsent: nonepresent: todo + plan modepresent: todo_write + exit_plan_modepresent: todowrite + plan agentpresent: todo + kanbanpresent: boards + taskspresent: update_plan + Plan mode
Tool-output truncation2,000 lines / 50 KB2,000 lines / 50 KB3,000 lines / 50 KB + artifacts50 KB spill + pruner2,000 lines / 50 KB + spillbounded response + storagetruncation + context guardcatalog-driven policy
CONTEXT
System prompt sourceinline TS, tool snippetsimmutable base + RLM prompt163 markdown files, templatedplugin-contributed sections9 per-model .txt files3 cache tiers, SOUL.mdcomposed + plugin sectionspresent: remote catalog per model
Per-model prompt variantsabsent: noabsent: nopresent: conditional templatespartial: persona presetspresent: 9 familiespartial: personalitiespartial: per-provider cachingpresent: server-delivered
Memory filesAGENTS.md / CLAUDE.md + worktree logicAGENTS.md + harness stateAGENTS/CLAUDE/GEMINI + RULES.mdAGENTS/CLAUDE + .local overlaysAGENTS/CLAUDE/CONTEXT.mdHERMES/AGENTS/CLAUDE/.cursorrulesAGENTS.md + skillsAGENTS.md + override
Agent-written memoryabsent: nonepresent: /refine, with rollbackpresent: 4 backends (local/SQLite/remote)partial: skills onlyabsent: nonepresent: MEMORY.md + USER.md + 9 providerspresent: 4 memory extensionspresent: memories subsystem
Compaction triggerwindow − 16k reservewindow − 16k reservethreshold % or absolute0.8 ratio + overflow eventusable = limit − reservepluggable context enginequeued, engine-ownedcatalog auto-compact limit
Compaction extrasturn-split + file carryoverbranch summarizationpresent: prune, shake, snapcompact PNGshadowing, no deletiontool-result pruninglineage split + recall evalsprompt-cache retention4 impls, remote v1/v2
Prompt-cache engineeringpresent: cache-waste analyzerpartial: inherited from pipresent: 5m/1h retention, fork inheritpresent: prefix-stability by constructionpresent: declarative breakpoint policypresent: write-once prompt, tier scopespresent: per-provider + observabilitypresent: cache key + prefix-excluded budget
Context as diffabsent: noabsent: no — live variablesabsent: nopartial: dynamic context sectionspartial: context epochs (v2)absent: noabsent: nopresent: ~15 versioned world states
MODEL LAYER
Providers4032 (1,227 models)64 (4,367 models)2 adapters (+pi-ai bridge)24 bundled + 23 loaders35 plugins77 (234 models)5
Catalog sourcegenerated + remote refreshgenerated in-repoin-repo models.jsonadapter defaults + discoverymodels.dev (embedded + live)per-profile fetch_models()extension manifestsremote, ETag-cached
Wire protocols10914 + custom registry3 (via bridge)2 stacks, 5 protocols6 api modespartial: via plugin providers1 — Responses only
WebSocket transportpartial: optionalabsent: noabsent: noabsent: noabsent: noabsent: nopresent: gateway websocketpresent: Responses-over-WS
OAuth flows7 + device code + PKCEpartial: inherited + MCP OAuth6 + CLI credential reusepartial: credential refs onlyAnthropic, Copilot, GitLab, Poe7 auth typespresent: per-provider pluginsPKCE + device code
Reasoning support6 levels, budgetseffort levels + budgetsauto-thinking + ultrathinkoff/high/max + passbackinterleaved, signature-safesummaries + scrubberper-model capabilitieseffort + summaries
Non-native tool callingabsent: noabsent: nopresent: 11 textual dialectsabsent: noabsent: nopartial: schema sanitizerspresent: tool-call repairabsent: no
Serves model APIs itselfpartial: pi-messages protocolabsent: nopresent: Anthropic/OpenAI compatibleabsent: noabsent: nopresent: OpenAI-compatible serverpresent: OpenAI + Responses HTTPpartial: responses-api-proxy
SAFETY & PERMISSIONS
OS sandboxabsent: none (documented refusal)absent: none — workers ≠ sandboxabsent: nonepresent: bwrap+Landlock/Seatbelt/ACLabsent: nonepartial: 8 pluggable backends, local defaultpartial: opt-in Docker, host defaultpresent: Seatbelt/bwrap+seccomp/Windows
Fails closed if unavailableabsent: n/aabsent: n/aabsent: n/apresent: yesabsent: n/aabsent: noabsent: nopartial: guardian fails closed
Approval modesabsent: noneabsent: none3 tiers × 3 modes (yolo default)ask/never × 3 sandbox modesallow/ask/deny rulesetsmanual / smart / offpresent: operator approval storeUnlessTrusted/OnRequest/Granular/Never
Command policypartial: extension hookabsent: noneregex patterns + interceptorpartial: no allowlist — fs-effect basedpresent: tree-sitter parsed, arity-awaredangerous-pattern detectionpresent: sandbox tool policypresent: Starlark execpolicy rules
LLM-based approverabsent: noabsent: noabsent: noabsent: noabsent: nopresent: smart approval (aux model)absent: no — human in the looppresent: Guardian sub-session
Network policyabsent: nonepartial: kernel boot gate onlyabsent: nonepartial: sandbox-levelabsent: noneURL safety + egress cmdpresent: net-policy + proxy capturepresent: MITM proxy, per-host ask
Trust / load-time guardpresent: project trustabsent: dropped from pipartial: plugin install warningspresent: trusted layers for envpartial: --pure disables pluginspresent: capability grantspresent: install security scanpresent: layered managed config
Path guardsabsent: noneabsent: nonegenerated-file guard, plan modefs fence shares sandbox rootsexternal_directory askpath_security + self-repo guardpresent: sandbox path rootsread-only subpaths in writable roots
Secret redactionabsent: noneabsent: nonepresent: share/export obfuscatorpartial: credential refs never inlinedpartial: export --redactpresent: egress-boundary redactionpresent: secrets subsystempartial: keyring storage
EXTENSIBILITY
Extension modelin-process TS extensionsin-process TS + py skillsextensions + plugins + marketplaceCordis DI container + YAML patchesnpm/local plugins + TUI pluginsdirectory plugins (4 sources)plugins + marketplace13-trait ext API + plugins
Lifecycle hooks34partial: extension event surfaceextension events + legacy hookswaterfalls (+CC/Codex bridges)partial: 2037present: hook bus + native relay11
Hot reloadpartial: themes onlyabsent: nopartial: /reload-pluginspresent: module + config HMRabsent: nopartial: /reload-mcppresent: plugin reloadabsent: no
MCP clientabsent: nonepresent: stdio + OAuthpresent: stdio/HTTP/SSE + OAuthpresent: stdio + streamable-httppresent: stdio/HTTP + OAuthpresent: stdio/HTTP/SSE + OAuthpresent: stdio + HTTPpresent: rmcp 3.0, 4 transports
MCP serverabsent: noabsent: noabsent: no (separate memory server)absent: noabsent: nopresent: yes (10 tools)present: serves its own toolspresent: yes (codex mcp-server)
Skillspresent: SKILL.md, 3 rootspresent: python packages, 13 bundledpresent: multi-provider + agent-authoredpresent: registry + watcherpresent: local + remote indexpresent: 82 bundled +115 optional + Hubpresent: 52 bundled + plugin skillspresent: 5 scopes + skill-creator
Slash commands22 + templates4270 built-in6built-in + markdown commands96 + one per skillpresent: plugin-contributed56 (no user .md dir)
Package/plugin registrypresent: pi install npm/git/pathpresent: npm / git / path installspresent: marketplace + auto-updatepresent: dsh plugin (pnpm)present: opencode plugpresent: Skills Hub + entry pointspresent: ClawHub + scan on installpresent: marketplace + signatures
Agent writes its own toolsabsent: nopresent: writes Python at willpartial: custom tools + learnpresent: tool_cordis (live plugins)absent: nopresent: skill_manage + /learnpartial: via plugin SDKpartial: skill-creator skill
SDKTS in-process + RPCTS in-process + RPC + ACPTS + Python RPC bindingsTS + Python JSON-RPCgenerated JS/TS + EffectACP + OpenAI API + TUI SDKpackages/sdk + plugin SDKTypeScript + Python
MULTI-AGENT
Subagentspartial: example extension onlypresent: rlm() inside the REPLpresent: task tool, subprocessespresent: 6 interchangeable providerspresent: child sessionspresent: delegate_task + kanban swarmpresent: embedded + system agentpresent: two generations (v1/v2)
Isolationprocessprocess (+ own kernel)present: copy-on-write worktreesscope + presetnarrowed permissionsgit worktree + task_idprocess (+ optional container)real thread + rollout
Depth limitpartial: n/apresent: configurable per chatspawn policy by depthbounded1 (configurable)max_spawn_depthpartial: per-agent policyagent_max_depth
Peer messagingabsent: nopresent: agent-message skillpresent: IRC bus + hubpresent: send_message / interrupt_agentabsent: nopartial: async delegationpresent: gateway routingpresent: InterAgentCommunication
Background jobsabsent: nopresent: daemon-backed sessionspresent: job manager + hubpresent: ctx.jobs + workflow enginepartial: experimental flagpresent: process registry + asyncpresent: cron + tasks + fleetpresent: background terminals
Scheduler / cronabsent: nopresent: heartbeat + cronabsent: nopresent: schedule packageabsent: nopresent: full cron subsystempresent: 33k L cron subsystempartial: cloud tasks
Second-opinion modelabsent: nopartial: side questionspresent: advisor on separate contextabsent: noabsent: nopresent: MoA + background reviewpartial: failover, not reviewpresent: guardian + review mode
STATE
StorageJSONL filesJSONL + artifact dirsJSONL (+SQLite/Redis backends)JSONL+zstd (+SQLite backend)SQLite (Drizzle)SQLite (schema v26)2 SQLite DBs (raw SQL)JSONL+zstd + 2 SQLite DBs
Modelappend-only tree, parent idsappend-only, resumableappend-only, title slotappend-only event logrows = streaming buffertables + lineage chain169 tables, migrated in placerollout envelopes
Config changes persistedpresent: model/thinking/tools as entriespresent: settings + migrationsheader + usage entriespresent: all policy as eventspartial: session settingspartial: session columnspresent: config + plugin statepartial: thread settings
Fork / branchpresent: in-file tree + branch summarypresent: resume + forkpresent: exact-route fork keeps cachepresent: log-prefix forkpresent: fork endpointpresent: parent_session_id lineagepartial: session spawn + threadspresent: fork / side threads
Filesystem revertabsent: noneabsent: nonepartial: context-level checkpoint/rewindabsent: nonepresent: shadow git snapshotspresent: checkpoint managerpartial: snapshotspartial: rollback drops turns only
Session searchpartial: fuzzy pickerpartial: agents-view browserlisting + history:// URLspresent: FTS5 (opt-in)partial: fuzzy pickerpresent: FTS5 + trigram + CJKpresent: session cards + boardspresent: search_threads index
Share / exportHTML + secret gistHTML exportsealed share + HTML/export + downloadshare service + export --redactHTML/MD + HF trace uploadpartial: transcripts/export + codex apply
Telemetrypartial: attribution headers onlypartial: traces, opt-inpartial: OTEL opt-inpartial: OTLP, disabled by defaultpartial: OTEL spans, opt-inpartial: OTLP local-first, opt-inpartial: OTEL extensionOTEL + analytics crate
SURFACES
TUIpresent: in-house renderer (16.7k)present: forked pi renderer (14.6k)present: in-house (70k) + agent hubabsent: nonepresent: SolidJS on OpenTUIpresent: prompt_toolkit + Ink TUIpresent: pi-tui based (14.1k)present: ratatui (258k LOC)
Web UIabsent: noneabsent: nonepartial: collab guest + bench dashboardpresent: lead surface, Cordis in browserpresent: full SolidJS apppresent: React dashboardpresent: Control UI (276k)absent: none in repo
Desktop appabsent: noneabsent: noneabsent: noneabsent: nonepresent: Electronpresent: Electronpresent: 5 native targetspartial: launcher for separate app
IDE / ACPabsent: nonepresent: ACP modepresent: ACP (Zed)present: ACP serverpresent: VS Code ext + ACPpresent: ACP adapterpresent: ACP + acp-corepartial: /ide via app-server
Server APICBOR protocol (experimental)partial: local daemon protocolauth-gateway + relaysHTTP+WS + Typert gatewaypresent: ~118 REST endpoints + SSEpresent: FastAPI + OpenAI-compatiblepresent: gateway + 2 HTTP APIspresent: 226-method JSON-RPC
Headless / CIpresent: print + json + rpc modespresent: print + json + rpcpresent: --print + rpc modespresent: headless profilepresent: run + GitHub Actionpresent: oneshot + batch runnerpresent: CLI + docker deploypresent: codex exec --json
Chat platformsabsent: none (separate repo)absent: noneabsent: noneabsent: nonepartial: Slack packagepresent: 31 platforms in one gatewaypresent: 26 channel idsabsent: none
Voiceabsent: noneabsent: nonepresent: STT/TTS + WebRTC liveabsent: noneabsent: nonepresent: TTS + wake word + transcriptionpresent: talk + TTS + transcriptionpartial: realtime ops present

What the differences mean

The axes these harnesses actually split along, and why each split is a design decision rather than an accident.

Size is a design statement, not an accident

Pi is 126k lines of source with 7 tools. Prime Agent, a fork of Pi, is 152k lines with one tool. OMP, another fork, is 782k lines of TypeScript plus 170k of Rust with 32 tools. Codex is 1.08M lines of Rust across 134 workspace members and 141 crates. Hermes is 918k lines of Python. OpenClaw is 1.83M lines of core TypeScript before its 151 bundled extensions, its 276k-line web UI, or its 481k lines of Swift and Kotlin. These are not the same kind of program. Pi's minimalism is enforced by pushing subsystems into examples/extensions/; Prime Agent's comes from deleting the tool surface and keeping the interpreter; OMP's maximalism from replacing the shell-out layer entirely; Codex's mass is mostly sandboxing, the app-server protocol and a 258k-line ratatui TUI; Hermes' and OpenClaw's is the gateway and the channel catalogue. Test-to-source ratios diverge just as sharply, and DeepSeek is no longer alone in inverting them: it ships 303k lines of tests against 246k of source, Prime Agent 161k against 152k, and OpenClaw ~4.6M against 1.83M — two and a half times its own source.

One core, four descendants, four different answers

The most striking thing in this set is not how differently the harnesses are built but how closely related four of them are — and it is visible in package manifests rather than prose. Pi is the core: 126k lines, 7 tools, examples/extensions/ as the product surface. Prime Agent is an in-tree hard fork that never renamed anything: its four packages still publish as @earendil-works/pi-agent-core, pi-ai, pi-coding-agent and pi-tui pinned at Pi's version 0.7.3, pi-coding-agent still declares bin: pi, and 72 of Pi's worked examples survive. OMP is a hard fork that rewrote the internals in Rust. OpenClaw is a library consumer: THIRD_PARTY_NOTICES.md records that portions were adapted from Pi, and it depends on current upstream @earendil-works/pi-tui at 0.82.1 — where Prime Agent froze a copy, OpenClaw tracks the original. What they disagree about most is the tool surface: the same ancestor yields 7 tools, 1, 32, and roughly 45-resolved-per-conversation.

The edit format is the most contested design decision

Four families. String replace — Pi (exact, uniqueness-enforced, one fuzzy fallback), opencode (a cascade of nine fuzzy replacers), DeepSeek (single-match, with a read-before-write freshness waterfall), Hermes (nine fuzzy strategies plus V4A multi-file patches), OpenClaw (string replace with a dedicated tool-call repair package behind it). Structured patch — Codex's apply_patch, a Lark grammar shipped verbatim to the model, parsed leniently, with fuzzy context anchoring and a safety assessment before the write; opencode swaps to the same format for GPT models. Content-anchored — OMP's hashline, the only format that binds an edit to the read that produced it: a 16-bit whole-file hash, per-line "was this actually displayed to you" provenance, and a proven line-remap recovery path that fails closed. And no edit format at all — Prime Agent, where the model mutates files by writing Python in a live kernel; edit.ts survives as a host operation and a bundled skill, not as something the model calls. The trade is output tokens against implementation complexity: hashline never retypes context, string replace never needs a snapshot store, and an interpreter needs neither but buys a kernel-lifecycle problem instead.

Sandboxing stopped splitting the field cleanly

Two enforce at the OS. Codex: Seatbelt SBPL on macOS (via a hardcoded absolute sandbox-exec path to defend against PATH injection), bubblewrap plus in-thread seccomp on Linux, a restricted-token sandbox on Windows, a MITM proxy for network policy, Starlark execpolicy rules, and an LLM guardian that fails closed. DeepSeek: bubblewrap then Landlock via a 300-line static-musl C launcher that restricts itself before exec, Seatbelt, a Windows ACL runner — and it refuses to run at all if confinement is unavailable. Two offer OS isolation but default to the host. Hermes ships eight pluggable execution backends and defaults to local. OpenClaw has a real container sandbox — validate-sandbox-security.ts, Docker setup scripts, a sandbox tool policy — but types.sandbox.ts declares enabled?: boolean with no default and agent-tools.ts:379 gates every tool on it, so tools run on your host until you switch it on. Four refuse. Pi documents the refusal. OMP defaults to yolo with copy-on-write worktrees. opencode replaces the sandbox with the field's most sophisticated permission engine — tree-sitter-parsed shell commands, arity-aware patterns, last-match-wins wildcards. And Prime Agent is the sharpest case: it inherits Pi's refusal, makes arbitrary model-written Python the primary tool, and appears to have dropped Pi's project-trust boundary — Pi carries 418 trust references across non-test source, Prime Agent 13, none of them a load-time guard. Its own docs/architecture.md pre-empts the misreading: the worker and kernel processes are "not security sandboxes".

Extensibility: hooks, container, protocol, marketplace, interpreter

Pi bets on in-process hooks — 34 lifecycle events including raw provider-payload mutation, plus virtual-module imports so an extension can rebuild a built-in tool. No MCP at all. Prime Agent inherits that API and adds a seam Pi does not have: the interpreter. Its skills are importable Python packages, so third-party extension happens inside the model's runtime rather than around it. DeepSeek bets on a dependency-injection container: Cordis services resolved by name, YAML patch layers composed at boot from an empty root, real hot reload, per-session sub-compositions. opencode and Codex bet on protocols — both MCP clients with OAuth; Codex is also an MCP server and adds a 13-trait extension API. Hermes and OpenClaw are both full MCP clients and servers. OpenClaw's own bet is a marketplace with a security gate: 113k lines across 526 files for discovery, a hook bus, a manifest registry and ClawHub — and it is the only harness here that security-scans a plugin at install time. Two seams are unique to it: lifecycle hooks relayed across a process boundary into Swift clients, and extensions/ containing working bridges to three other harnesses in this atlas plus migrate-hermes, which imports Hermes's own database, memories, skills and secrets.

Context management is where the real engineering hides

Everyone summarises; the differences are in what surrounds it. Pi splits mid-turn cut points and issues the summary call with caching disabled so it cannot pollute the prefix. opencode runs compaction as a hidden agent with all tools denied, and separately prunes old tool-result bodies past a protection window. OMP runs a five-stage pipeline and, uniquely, rasterises dropped history into bitmap-font PNGs for vision models — no LLM call, no latency. DeepSeek shadows rather than deletes, so replay stays exact, and replays the conversation's own prefix verbatim during summarisation to keep the KV cache warm. Codex treats context as a diff of ~15 versioned world states. Hermes caches the whole system prompt for the agent's lifetime. OpenClaw makes context management a swappable component — a registry of engines with a delegate and a quarantine-health check — and pairs it with per-provider cache retention and observability. Most of these are ultimately prompt-cache preservation strategies. Prime Agent is the exception that breaks the generalisation: its answer is don't put it in the context at all. Context lives as live Python variables in a persistent kernel, and its refinement.ts lets the agent persist durable lessons about how to work — with recorded history and rollback — without ever touching the immutable base prompt. Every other harness's system prompt is static or template-assembled; this one has one the agent revises.

Multi-agent is table stakes now — but the isolation differs

Pi has none in core (an example extension spawns subprocesses) — and its two forks went opposite ways. OMP runs subagents as subprocesses in copy-on-write worktrees with schema-validated structured returns, plus a peer-to-peer message bus and an "advisor" second model watching every turn. Prime Agent instead makes a subagent a function call: rlm(...) inside the interpreter spawns a child runtime with its own optional kernel, depth-capped and configurable per chat, supervised by a daemon, retained across sessions, and able to message its peers directly. Isolation is process, not filesystem. opencode's subagents are full child sessions with a permission set that can only narrow the parent's, depth-capped at 1. DeepSeek exposes six interchangeable subagent providers behind one seam. Codex ships two generations of multi-agent tooling side by side. Hermes adds a kanban board where a dispatcher claims tasks and spawns workers. OpenClaw is the only one where "another agent" can mean another machine: paired phones and laptops register through a node host and a gateway registry as capability providers, alongside a fleet registry and a system agent that maintains the installation itself.

Surfaces determine the core's shape

opencode's client/server split is the most consequential decision on this axis: because even the local TUI is an HTTP client, the desktop app, browser client, VS Code extension and remote attach all come nearly free — at the cost of running a server for a terminal session. Codex reaches a similar place with a 226-method JSON-RPC app-server beside the TUI. DeepSeek leads with a web UI that is itself a Cordis tree in the browser. Prime Agent is new evidence for opencode's thesis from the opposite direction: a terminal-first agent that adopted the split over a local daemon socket rather than HTTP, spending ~24,000 lines — one line in six — to buy one property, that a session survives its terminal. Hermes went widest until OpenClaw: gateway, Control UI, CLI, TUI, ACP, two OpenAI-compatible HTTP surfaces, 26 chat channels, native apps on macOS, iOS, Android and Linux, voice, canvas and paired device nodes. The cost is quantifiable — OpenClaw's gateway is 252k lines against 379k for its agent code, a control plane two-thirds the size of the agent. Pi and OMP stay terminal-first, exposing machine surfaces rather than GUIs.

The real dividing line is who is allowed to talk to the agent

Six of these eight assume one trusted operator, in a terminal, in a repository they own. Two — Hermes and OpenClaw — assume untrusted third parties can message the agent, and that single assumption forces almost everything else about their architecture. It is why an unknown sender must be paired and approved before OpenClaw will answer a DM; why inbound message content is quarantined and treated as data rather than instruction; why the tool set is resolved per conversation from who is asking, on which channel, under which policy — and therefore why OpenClaw cannot answer "how many tools do you have" with a number while Pi answers "seven"; why there is a 2,029-line operator-approval store that can route a risky command to a human over the chat itself; why state is a migrated SQL database rather than a session file; and why the control plane rivals the agent in size. A coding agent's threat model is a mistake. A personal assistant's threat model is a stranger. The two produce different programs, and the fact that OpenClaw ships an importer for Hermes's saved state suggests the category knows it.

Sources and method

Every claim in this atlas was read out of the source, not the README. Each repository was cloned at a pinned commit and analysed there, so the figures stay checkable as the projects move.

How the lines were counted

One recipe was applied identically to every pinned checkout: all non-generated source files of a language, excluding dependency, build, vendored and example trees, split into source and test by path. Test code hides under names like *.harness.ts and *.fixture.ts as well as the obvious ones, so the classifier accounts for those — without it, one harness’s source inflates by hundreds of thousands of lines.

A marker of ~ means partial, indirect, off by default, shipped as a bundled extension rather than in core, or a claim we could evidence only from a repository’s own documentation. A marker of means verified absent by exhaustive search over non-test source. Where a capability could not be settled either way, it is marked partial rather than absent: absence of evidence is not evidence of absence.

What we know less well

OpenClaw is roughly fifteen times the source mass of the median harness here. Its plate traces the gateway, agent, tool and state spine, whose entry modules were read; its 151 bundled extensions, its Control UI and its five native app targets are measured and named but not decomposed. Its source figure counts src/ and packages/ — the harness itself — with the extension catalogue, the web UI, the native apps and the test suite reported separately, because no single number is honest for a program shaped like that.

Two harnesses are named differently here than you may expect. Pi’s repository moved to earendil-works/pi; the older badlogic/pi-mono now only resolves by redirect. OpenClaw’s licence is verbatim MIT plus a pointer to its third-party notices, which is why GitHub’s licence detector reports no assertion for it.

Finally, the honest limit of this edition: the line counts, module counts and licences for all eight were measured here, and the load-bearing claims about the two most recent additions were verified against source several independent ways. The architectural detail for the six older harnesses is carried over from the survey this atlas grew out of and spot-checked rather than re-derived cell by cell. Where a spot-check disagreed with the original, the original was corrected and the correction is recorded in the commit history.

BIOS is a harness
for science