What this document is: the cross-cutting synthesis of the “AI agent source-analysis series.” It is written from the nine single-project lectures already in this repository—the Claude Code how-it-works (deep dive) plus the eight source analyses under
项目分析/for opencode, hermes-agent, Raven, CodeWhale, goose, nanobot, grok-build, and OpenWorker. Every fact here is those nine documents; key mechanisms re-cite thefile:linethose lectures already marked, so you can walk back to the single-project lecture and then to source. Special notes: nanobot (HKUDS, University of Hong Kong) is Raven’s upstream—Raven’s Python runtime forks nanobotv0.1.5.post3, then stacks EverOS / Curator / Spine and the rest; reading Raven against nanobot is how you see “factory settings” versus “later surgery.” grok-build is xAI’s (Elon Musk) official CLI, a Rust 60+ crate monorepo; its signature is the Goal Mode five-piece explicit state machine plus the ACP protocol. OpenWorker is Andrew Ng’s team’s open-source “AI colleague”—and it is a different species: the first eight are coding agents; OpenWorker is a local-first general-task colleague (Slack, cron, ask before acting, deliver finished artifacts), with the engine built on Ng’s team’s aisuite. Bringing it in is exactly what lights up the unspoken assumptions of the “coding agent” class—so this lecture keeps a “coding-agent-first” comparison frame, but on every dimension it marks OpenWorker as the cross-border sample with a different answer.🆕 The tenth sample, Open Design (
nexu-io/open-design, commitc893b60), is yet another species, and more extreme than OpenWorker: it does not write a single line of agent loop. It treats the 25 local CLIs already on the table—claude/codex/cursor-agent/opencode/hermesand the rest—as its own execution engines. It is not the tenth competitor at this table; it is the table. Chapters 1–6 therefore still use the nine-house comparison frame; Chapter 7 is new, and is reserved for this host-shaped sample—its answers on permissions, context, and quality assurance do not sit on the same axes as the first nine. Seeopen-design.md(Chinese original:open-design-源码分析.md).🆕 The eleventh sample, MiMo Code (
XiaomiMiMo/MiMo-Code, commit076b790), is a different kind of relationship: it is a deep fork of this lecture’s second subject, opencode (the core package is still namedpackages/opencode;LICENSEcarries two copyright lines side by side). Comparability with the other ten is therefore highest—same skeleton, watch what Xiaomi welded on. The answer is concentrated: six “do not let the agent lie to itself” devices (independent judge / four doom-loop gates / Try-Best detection / artifact validation) + 20 one-prompt-per-model files + a 4-tool ABI reserved for the Codex family. Chapter 8 is new, and is reserved for it. Seemimo-code.md(Chinese original:MiMo-Code-源码分析.md).Audience: a beginner who has already read at least one single-project analysis. This lecture does not re-explain “what an agent loop is” (the nine hearts are the same: the model speaks → the host acts for it → results are fed back → until the model stops asking for tools). It answers a more interesting question: same heartbeat, why did nine houses grow nine completely different bodies?
Shape of this lecture (read it like peeling an onion, one layer deeper each time): Chapter 1’s overview table builds a global impression; Chapter 2 lays ten product dimensions side by side—“what they did”; Chapter 3 drills one layer down, comparing “how the code is actually written”—language choice, streaming pipeline, persistence, concurrent cancel, error handling, five technical deep-water zones (technical; includes line numbers from a second source check); Chapter 4 asks “why” in reverse—from team genes, commercial logic, and governance form, it derives the causal chain of technical form; Chapter 5 picks five features for “idea duels”; Chapter 6 gives beginners a selection guide. For a fast read, Chapters 1–2; for engineering detail, Chapters 3–4.
1. Overview table
First, lay nine projects on one table. Read this landscape. Each column is a full “physical exam” of one project (the last column, OpenWorker, is the “general-task colleague” cross-border sample).
| Dimension | Claude Code | opencode | hermes-agent | Raven | CodeWhale | goose | nanobot | grok-build | OpenWorker |
|---|---|---|---|---|---|---|---|---|---|
| Team behind it | Anthropic official (closed-source product) | Anomaly (the SST team), MIT open source | Nous Research (open-weight model company), MIT open source | EverMind-AI (memory-tech ecosystem), Apache-2.0, pre-alpha | Community project (ex deepseek-tui), MIT open source | Started by Block, now Linux Foundation AAIF | HKUDS, University of Hong Kong (Xubin Ren), MIT open source; Raven’s upstream | xAI official (Musk), Apache-2.0, internal monorepo snapshot, does not accept external PRs | Andrew Ng’s team (aisuite authors), MIT open source, Beta |
| Stack | TypeScript monolith, React/Ink terminal UI (forked src/ink/) |
TypeScript on Bun, Effect framework; TUI is SolidJS + OpenTUI; SQLite persistence | Python 3.11+ kernel (the brain) + TypeScript TUI/desktop/Web (the face) | Python 3.12 kernel + Node TUI (UI forked from hermes-agent) | Rust workspace, 18 crates, ~650 000 lines, ratatui terminal UI, dual binary | Rust 12-crate core + Electron desktop + experimental Ink TUI | Python≥3.11+asyncio; talks openai/anthropic SDKs directly (no LiteLLM); React+Vite WebUI; dulwich git memory | Rust 60+ crates; DotSlash locked toolchain; Tokio async; Ratatui TUI; tree-sitter bash AST; JSONL persistence | Python backend (on aisuite) + React GUI + Tauri(Rust) desktop shell + Rust STT; FastAPI+uvicorn service |
| Architectural shape | Monolithic TUI: all five layers in one process | The server is the ontology: even same-process TUI walks the full HTTP/RPC | Python backend + multi-platform gateways (CLI/TUI/desktop/~20 messaging platforms) | Dual process (TCP-loopback JSON-RPC) + Spine scheduling backbone | Rust single binary; UI⇄engine via Op/Event message pipes | The engine is the server: goose serve = ACP over HTTP; GUI is only a client |
Channels/WebUI → MessageBus → AgentLoop (explicit state machine) → AgentRunner → tools/providers | Rust single binary; Goal Mode five-piece kit (GoalTracker/Planner/Worker/Classifier/Strategist); ACP protocol (Zed integration); WorkflowManager | Local-first desktop app: Tauri shell supervises a Python sidecar; UI/TUI/Slack are all consumers of the same Event stream |
| Model strategy | Locked to Anthropic (subscription OAuth / API key / Bedrock / Vertex / Foundry) | Provider-agnostic: models.dev catalog of dozens + home-grown native runtimes + subscription-login plugins | 33 provider plugins + Nous Portal (300+ models) + local models as first-class citizens | LiteLLM unified adapter for many houses + fallback chain | 36 ProviderKind enums (incl. vLLM/SGLang/Ollama, no key) |
15+ handwritten providers + 37 declarative JSON + subscription reuse + local llama.cpp | ~40 providers, home-grown registry + fallback (explicit paths, LiteLLM not outsourced) | Locked to xAI Grok; multimedia tools deep-bound to xAI-only capabilities; AgentDefinition.model can override |
Provider-agnostic (BYO key, on aisuite): OpenAI/Anthropic/Gemini native + a dozen more + Ollama local; curated “verified tool-calling” list |
| Tool scale | ~40 built-in + MCP | ~20 built-in + plugins/MCP | 74 built-in, grouped and gated by toolset | ~25 built-in + MCP/plugin tools | 60+ built-in + MCP | 0 built-in tools—everything is an extension; even built-in capability is provided by extensions | ~20+, discovered by pkgutil |
~40+ built-in + MCP; unique: ImageGenTool / ImageEditTool / ImageToVideoTool / SchedulerCreateTool | Core tools (file/shell/git/search/web) + 40 connectors (GitHub/Slack/Jira/Notion/Gmail…) + MCP |
| Extension mechanism | Plugin marketplace + skills (SKILL.md) + MCP (6 transports) + hooks (20 event points) |
Plugin Hooks (tools/providers/permissions all hookable) + skills + MCP + custom command/tool directories | Everything is a plugin (platforms and providers included) + skills (agentskills.io standard) + bidirectional MCP | Plugins (only two contribution points: memory backend / tools) + Skill Hub remote marketplace + MCP | Skill directories compatible with six houses + MCP (can be started at runtime) + hooks (11 points) + Markdown custom commands | Everything is an MCP extension (six shapes) + Recipe (shareable workflow YAML) + skills | MCP + entry_points tool plugins + channel plugins + SKILL.md/ClawHub |
Plugin Marketplace (Git/GitHub/local; allowlist; InstallRegistry commit hash); Hooks (xai-grok-hooks crate); ACP protocol open |
persona marketplace (directory/git install, consent gate) + skills (progressive disclosure) + MCP(OAuth) + 40 connectors |
| One-sentence position | The best terminal coding agent built for Claude | An open agent platform built for every model and every client | A personal agent that lives in messaging apps and closes a self-learning loop | A self-improving agent substrate built around memory / context / proactivity | A Rust coding agent that makes “safety” a verifiable mechanism | A model-neutral, protocol-open local agent engine “anything can ride” | An ultra-light, fully self-contained personal AI agent; lives in chat apps | xAI official CLI; Goal Mode hours-scale autonomous execution; Rust single binary + ACP protocol as the moat | A local AI colleague that can deliver finished work, run unattended, and ask first (Slack, cron) |
Three things on this table are worth remembering first:
First, the nine architectural shapes still collapse into six or seven large classes. Claude Code and grok-build prove that “one process taken to the extreme” still works (grok-build, with a Rust single binary + Goal Mode five-piece kit, reached a new height of autonomous execution); opencode and goose turn the agent into a “local server,” and even their own UI is only a client—agents are moving from “an App” to “a kind of on-machine infrastructure”; nanobot / hermes / Raven belong to the same “message gateway + brain” family, but scheduling climbs a clear complexity ladder from a naïve MessageBus to the Spine backbone; OpenWorker adds yet another: a local-first desktop app (Tauri shell) that supervises a Python agent service as a sidecar, with UI / terminal / Slack sharing one event stream. There is no canonical answer for architecture, only an answer to “who is your product meant to be used by.”
Second, model strategy is the deepest watershed. Two houses now lock to a single vendor: Claude Code (Anthropic) and grok-build (xAI Grok), each taking their house model’s unique capabilities to the extreme; the other seven are all provider-agnostic—but the width of “wide” differs enormously: from Raven’s LiteLLM adapters, nanobot’s ~40-house home-grown registry, OpenWorker sitting directly on aisuite, to CodeWhale’s 36 enums, goose’s 52+ providers, hermes’s 33 plugins plus local models. Lock-and-go-deep versus agnostic-and-go-wide decides almost every design in the nine chapters that follow.
Third, tool count has nothing to do with product strength. hermes has 74 built-in tools; goose has zero (even read/write file is an extension); nanobot deliberately sits at ~20+ and grows at the edge; grok-build uniquely owns multimedia generation and scheduled-task tools—this is not who is advanced and who is behind, but self-consistent philosophies: hermes/nanobot live by “narrow waist in the core, capability at the edge”; goose lives by “everything is MCP, only one pipeline”; grok-build lives by “platform depth-binding in exchange for unique capability.”
quadrantChart
title 九大 Agent 定位象限(依据九份源码分析归纳)
x-axis 锁定单一厂商 --> provider 无关
y-axis 终端程序 --> 本地基础设施
quadrant-1 开放平台型
quadrant-2 深度绑定的基础设施
quadrant-3 单一厂商的极致工具
quadrant-4 开放的终端工具
"Claude Code": [0.12, 0.18]
"grok-build": [0.10, 0.25]
"CodeWhale": [0.72, 0.22]
"opencode": [0.85, 0.68]
"hermes-agent": [0.80, 0.78]
"Raven": [0.68, 0.82]
"nanobot": [0.75, 0.72]
"goose": [0.90, 0.88]
"OpenWorker": [0.82, 0.58]
Note: OpenWorker lands in the “open platform” quadrant—provider-agnostic (on aisuite), local service sidecarized—but it is desktop-app first, aimed at general tasks rather than coding, same region as opencode/goose’s “server-as-ontology” but a different species.
2. Dimension-by-dimension comparison
This chapter splits ten key dimensions one by one. Each item is a comparison table, followed by a paragraph of “how each house thinks”—the table answers “what,” the analysis answers “why.”
2.1 Architectural shape: from “a terminal program” to “a set of local infrastructure”
| Project | Shape | Relation of UI to core | Key mechanism (cited lines) |
|---|---|---|---|
| Claude Code | Monolithic TUI | React/Ink render tree and main loop share a process; five layers (UI / dispatch / assembly / loop / comms) call functions directly | Five-layer architecture (deep-dive Chapter 2); src/query.ts and src/screens/REPL.tsx same process |
| opencode | The server is the ontology | The server runs in a same-process Bun Worker thread; the TUI wraps RPC as a fake fetch("http://opencode.internal"), identical HTTP protocol to remote mode |
cli/cmd/tui.ts:210-247 (Worker + RPC + in-memory fetch) |
| hermes-agent | Python brain + N faces | TUI (React/Ink) talks JSON-RPC newline-delimited over stdio to the Python process; message gateways hang ~20 platforms | ui-tui/README.md: "TypeScript owns the screen. Python owns sessions, tools, model calls." |
| Raven | Dual process + scheduling backbone | Python kernel listens on a TCP-loopback port; Node TUI connects and speaks JSON-RPC 2.0; the frontend never imports backend internals | raven/tui_rpc/ (protocol model, 911 lines); CONTEXT-MAP.md:15 |
| CodeWhale | Rust single binary (actually dual binary) | codewhale scheduler + codewhale-tui runtime; between UI and engine is an Op/Event enum message pipe, not function calls |
core/ops.rs:87, core/events.rs; crates/cli/src/lib.rs:4215 locates the sibling binary |
| goose | The engine is the server | Desktop GUI spawns a goose serve child (ACP over HTTP/WebSocket, authenticated with GOOSE_SERVER__SECRET_KEY); the frontend talks ACP over WebSocket |
crates/goose-cli/src/cli.rs:844; ui/desktop/src/gooseServe.ts:369,336 |
| nanobot | MessageBus + explicit state machine | Channels/WebUI/CLI → inbound/outbound dual queues → AgentLoop (turn state machine) → AgentRunner (model iteration); single-process asyncio, WebUI over WebSocket | bus/queue.py; agent/loop.py:1351; agent/runner.py:354 |
| grok-build | Rust single binary + Goal Mode five-piece kit | Ratatui TUI + engine all in one process; Goal Mode: GoalOrchestrator coordinates GoalTracker / GoalPlanner / GoalWorker / GoalClassifier / GoalStrategist; ACP protocol exposed outward (Zed integration); WorkflowManager up to 4 concurrent | goal/orchestrator.rs; goal/planner.rs; goal/worker.rs; extensions/acp.rs |
| OpenWorker | Local-first desktop app + event stream | Tauri(Rust) shell supervises the Python service as a sidecar; UI/TUI/Slack are all consumers of the same Event stream, orchestrated by SessionManager | server/run.py (sidecar + orphan guard); engine.py yields Event; events.py |
How each house thinks: architectural shape, at bottom, answers “what sits between the UI and the brain.” Claude Code’s answer is “nothing”—no protocol overhead, state sharing most direct, so iteration is fastest and terminal feel is finest; the cost is weak “being integrated” (later patched with SDK, ACP, bridge). opencode and goose are the most radical: a public protocol sits in between, even in the same process—local single-machine and remote team are the same code path; Zed, GitHub Action, third-party SDKs plug in at zero cost; the cost is lifelong protocol and process-management maintenance. hermes, Raven, nanobot sit behind a message/process boundary: a crashed TUI does not take the kernel with it; nanobot further decouples the storefront from the back shop with two asyncio queues; the cost is one more message model to keep. CodeWhale proves a compromise works: a message pipe can live inside one process, and the same engine serves TUI, exec headless mode, and the web client—“headless is not a castrated edition, it just has no UI.”
flowchart TB
subgraph CC["Claude Code:单体 TUI"]
A1["React/Ink 界面"] --> A2["五层全在同一进程
src/query.ts 主循环"]
end
subgraph OC["opencode:服务器即本体"]
B1["TUI (SolidJS)"] -->|"Worker RPC → 内存 fetch
http://opencode.internal"| B2["HTTP 服务器
session/prompt.ts"]
end
subgraph GS["goose:引擎即服务器"]
C1["Desktop GUI / Zed / 第三方"] -->|"ACP over HTTP/WebSocket"| C2["goose serve
Rust 核心 crates/goose"]
end
subgraph HM["hermes:Python 大脑 + 多平台网关"]
D1["TUI / 桌面 / 约20个IM平台"] -->|"JSON-RPC over stdio 或网关"| D2["Python 内核
conversation_loop.py"]
end
subgraph RV["Raven:Spine 调度脊骨"]
E1["TUI / 12个IM平台"] --> E2["Spine:submit 进 → Lane 串行 → emit 出"]
end
subgraph CW["CodeWhale:单二进制消息管道"]
F1["ratatui 界面"] -->|"Op / Event 枚举"| F2["Rust 引擎
turn_loop.rs"]
end
subgraph NB["nanobot:MessageBus + 状态机"]
G1["Channels / WebUI / CLI"] -->|"inbound / outbound 双队列"| G2["AgentLoop 状态机
→ AgentRunner"]
end
2.2 Agent main-loop implementation: the same heartbeat, six constitutions
| Project | Loop body | Language shape | Turn budget | Interrupt | Most distinctive boundary defense |
|---|---|---|---|---|---|
| Claude Code | queryLoop (src/query.ts:241), while(true) (:307) + cross-turn State struct |
async generator (refactored from recursion) | Caller’s maxTurns |
ESC: signal reaches the HTTP layer and truly cuts the connection; auto-synthesizes fake replies for “orphan tool_use” (query.ts:123-149) |
Output-truncation recovery up to 3 times; injects a nudge when the token budget is almost gone |
| opencode | runLoop (session/prompt.ts:1081), while(true) (:1088) |
Effect generator; re-reads the database every turn | agent.steps default Infinity |
ESC double-tap to confirm; Effect structured interrupt; cleanup marks unfinished tools as interrupted | doom_loop: three identical tool calls in a row pops a permission ask (processor.ts:356-380) |
| hermes-agent | run_conversation() (conversation_loop.py:669), while condition at :822 |
Python while, a single function nearly 5500 lines | Default 90 + IterationBudget (refundable, with a grace call) | Cooperative: each turn starts by checking _interrupt_requested |
Persist first, then execute tools (:5361-5366)—so a destructive tool that kills the process does not cause “amnesia” |
| Raven | _run_agent_loop (agent/loop/main.py:1424), while iteration < 40 (:1476) |
Python asyncio while | Default 40 | Spine Lane cancel (asyncio.Task.cancel); only USER-origin can jump the queue and interrupt |
After emergency shrink, iteration -= 1—“the overflowing call did no work, it does not count against budget” (:1570) |
| CodeWhale | handle_deepseek_turn (turn_loop.rs:286), outer loop (:342) + inner streaming loop (:785) |
Rust tokio double loop | max_steps default 1000 + token budget |
Cancel token through three layers; dropping FuturesUnordered hard-cancels the whole parallel pool |
Sleep-wake detection: Instant and SystemTime dual clocks (issue #2990) |
| goose | reply_internal (agents/agent.rs:1846), loop (:1948) |
Rust tokio loop | Default 1000 (DEFAULT_MAX_TURNS) |
CancellationToken through the main loop / stream / tool concurrency select! |
goal/grind: if it wants to clock out, inject a user-invisible nudge until the goal is met |
| nanobot | AgentLoop state machine (loop.py:1351) + AgentRunner model iteration (runner.py:354) |
Python asyncio; explicit _TRANSITIONS state table |
Default 200 (max_tool_iterations) |
/stop priority command punches through → asyncio.Task.cancel; still checkpoints to disk after cancel |
Persist the user message early + runtime_checkpoint; idle sessions AutoCompact |
| grok-build | Goal Mode three state machines: ordinary chat walks agent_loop.rs; after /goal, GoalOrchestrator takes over, driven on two axes: GoalPhase (Inactive/Planning/Working) × GoalStatus (8 states) |
Rust tokio; GoalStopDetector 9 classes of regex on the last paragraph decide whether done | GoalWorker has no explicit per-turn cap; GoalStrategist fail-open (does not block) | Cancel token throughout; GoalPlanner fail-closed (must write a non-empty plan.md and return the literal "Done") | UpdateGoalTool is the completion signal (must be called); GoalClassifier validates each turn’s output |
| OpenWorker | TurnEngine._loop (engine.py:294): async while + blocking calls wrapped in to_thread |
Python asyncio; provider blocking streams are bridged back to the main loop via thread+queue, racing Stop | Default 150 (config.py:31; engine default 12, explorer 10) |
Interruptible in any state and leaves no orphan tool_call (request_interrupt:120); steering interpolates without breaking |
Durable resume: a turn hanging in the inbox resumes after restart without double-running (idempotent by tool_call_id) |
How each house thinks: at textbook level the eight loops are identical—request the model, if there is tool_use execute and append, request again; “the answer is out when the model stops asking for tools.” The real gap is density of boundary-case handling: opencode re-reads the database every turn, so “queued messages are taken over naturally, multi-client concurrency is safe” comes for free; hermes makes “persist first, then act” iron order—a tool may execute hermes restart and suicide; that order was bought with blood; Raven carries “errors are also context” all the way through, tool errors return a string plus a crib sheet “try another method”; nanobot uses an explicit state machine to split the turn lifecycle cleanly, default budget 200 for long tasks; CodeWhale’s loop is buried in #issue comments for every historical bug, a numbered error-notebook. Another convergent evolution is steer: when the user speaks while the agent is working, the houses give two answers—queue (Claude Code, opencode) versus mid-flight inject (hermes, Raven, CodeWhale, goose)—standard kit in the long-task era.
sequenceDiagram
participant L as 主循环(七家同款骨架)
participant M as 模型 API
participant T as 工具层
loop 直到模型不再要工具
L->>L: 家务:drain插话 / 压缩检查 / 预算检查 / 取消检查
L->>M: 流式请求(全部历史 + 工具清单)
M-->>L: 文本 + tool_use(边收边解析,参数收齐即执行)
L->>T: 权限裁决后执行(只读并行 / 写串行)
T-->>L: tool_result 追加进历史
end
L-->>L: 正常出口:无 tool_use 的那次回复
2.3 Tool-system design: count does not matter; discipline does
| Project | Count and grouping | Safety metadata | What happens when there are too many tools | Unique mechanism |
|---|---|---|---|---|
| Claude Code | ~40 built-in, 11 groups; getAllBaseTools() (tools.ts:193) is the single source of truth |
Three tags: isReadOnly / isConcurrencySafe / isDestructive, all default false (Tool.ts:750-761), and they can be functions judged dynamically from arguments |
ToolSearch lazy load: ordinarily only send the name list; full schema is delivered only when the model looks it up; 26 built-ins + all MCP tools are deferred | deny rules strip tools before the model even sees the list (tools.ts:262-269); built-in tools must form a contiguous prefix (to keep prompt cache) |
| opencode | ~20 built-in (tool/registry.ts:226-244) |
No tag system; permissions managed externally by “name + glob” | Output uniformly truncated at 2000 lines / 50KB, spilled to a temp file | bash parsed with a tree-sitter syntax tree; unfixable tool calls stuffed into an invalid tool as a fallback |
| hermes-agent | 74 built-in, grouped and gated by toolset (toolsets.py:96) |
check_fn service gating: a tool the environment cannot support never appears |
Freeze the toolset at session start (swapping it would smash the prefix cache); tool_search three-bridge tools (10% threshold) |
"Footprint Ladder": new capability prefers skill/plugin; only as a last resort add a core tool (AGENTS.md:24-27) |
| Raven | ~25 built-in (main.py:571-700) |
timeout_seconds hard timeout; blocking_interaction marks tools that wait on a human |
tool_search + tool_call progressive disclosure (opt-in) |
Media / Skill Hub tools registered under config conditions; cast_params lenient type coercion |
| CodeWhale | 60+ built-in, builder-pattern family registration | Capability tags derive approval level: ExecutesCode→must approve, WritesFiles→suggest approve, read-only→auto (spec.rs:1181-1190) |
One canonical name + action param + hidden compatibility aliases (File merges four read/write/edit/list actions) |
Probe registration: if the machine has no pandoc/OCR backend, the tool is not registered at all—hallucinated calls are killed at the source |
| goose | 0 built-in tools, everything is an extension | Relies on MCP tools’ readOnlyHint annotations |
Official suggested cap: 5 extensions / 50 tools (prompt_manager.rs:19-20) |
Tools named extension__tool; the agent can search and enable new extensions itself |
| nanobot | ~20+, pkgutil scans agent/tools/ for auto-discovery |
No per-call approval metadata; relies on blacklist / workspace / SSRF / sandbox up front | Narrow waist: new capability prefers channel/skill/MCP; rarely touch loop.py/runner.py |
entry_points(group="nanobot.tools") for external tools; built-in tools stably ordered to keep cache |
| grok-build | ~40+ built-in + MCP; three unique classes: multimedia (ImageGenTool / ImageEditTool / ImageToVideoTool / ReferenceToVideoTool), scheduled tasks (SchedulerCreateTool / SchedulerDeleteTool / SchedulerListTool), goal mode (UpdateGoalTool) | ToolBridge unifies the tool interface (built-in + MCP same pipeline); tree-sitter bash AST for permissions | ToolBridge.tool_definitions() tool order stable for cache |
UpdateGoalTool is Goal Mode’s completion signal—not calling it means the task is not done (fail-closed design) |
| OpenWorker | Core tools (file/shell/git/search/web) + 40 connectors + MCP; all on aisuite ai.tool / ai.toolkits |
ai.ToolMetadata (category / risk_level / requires_approval)—the main loop’s concurrency ruling and permission verdict both read it |
Packed by persona family (code given to explorer; knowledge given to scheduler/self-wake); skills progressive disclosure | _display privacy bypass: the agent cannot see how many items the privacy filter hid (stops the model from probing) |
How each house thinks: the most important consensus of the tool system is—every tool’s schema is sent with every request, a continuous bill (hermes’s AGENTS.md wrote this as a standing order: "Every model tool we add is sent on every API call, so the bar for a new core tool is high"). Around that bill, the houses give three kinds of throttle: Claude Code’s ToolSearch is “announce the dish names first, then hand the menu” (the shelf first shows only titles; pull the book you need); hermes/Raven’s tool_search is “fetch from the shelf on demand”; CodeWhale is “merge likes” (one File tool eats four actions, the catalog shortens, the model’s chance of picking wrong shrinks); nanobot simply presses core tools to ~20+ and pushes capability to the edge with pkgutil/entry_points/skills. On safety metadata there are two roads: Claude Code and CodeWhale make safety attributes declarations of the tool itself (three tags / capability tags; CodeWhale even derives approval level from the declaration automatically—that is the literal meaning of “safe by construction”); opencode and goose externalize the safety judgment into permission rules or MCP annotations. The former’s benefit is safety living and dying with the tool; the latter’s is a thinner tool implementation. Finally notice a recurring small wisdom: “do not show the model a tool it cannot use”—deny-strip early, probe registration, check_fn gating, all the same thing: if the model cannot see it, it will not call it at random.
2.4 Permissions and safety: four sharply different roads
| Project | Verdict spine | Command parsing and defenses | Sandbox | What the model receives after a deny |
|---|---|---|---|---|
| Claude Code | Five-gate verdict: deny→ask→tool self-check→permission mode→alwaysAllow→fallback popup (permissions.ts:1158); six permission modes |
Bash parsed into an AST; compound commands split and matched one by one (BashTool.tsx:447-465); dangerous-mode list + speculative classifier |
Has a sandbox switch (/sandbox) |
Deny reason fed back as a tool result; the model usually self-corrects and changes plan |
| opencode | Three-state rules: ask/allow/deny + globs, findLast last-write-wins (permission/index.ts:28-38) |
tree-sitter real syntax tree parses the command (tool/shell.ts:91-117) |
None (no sandbox mechanism found in docs) | Teachable refusal: reject can attach a natural-language feedback (CorrectedError), “teaching” the model to correct |
| hermes-agent | Dangerous-pattern detection + human approval (single / session / permanent whitelist, three levels) | DANGEROUS_PATTERNS + Tirith pre-exec scan (lookalike-character phishing, pipe-to-interpreter) |
Six execution backends to choose: local/docker/ssh/singularity/modal/daytona | Deny and tell the model |
| Raven | No per-tool approval: regex blacklist + workspace fence + confirm round-trip | 7 classes of dangerous-command regex (shell.py:32-42); ../ out-of-bounds rejected |
boxlite microVM true isolation (when enabled the blacklist is actually turned off); never pass os.environ into the VM |
Error string + “try another method” crib sheet |
| CodeWhale | Truth table: resolve_tool_permission() outputs Allow/Prompt/Deny (authority.rs:289) |
Four-layer defense: execpolicy prefix policy → truth table → constitution.json write protection (can only add locks, never grant power) → OS sandbox |
Seatbelt (macOS, declared only if probe succeeds) / bubblewrap (Linux, explicitly on); Windows honestly reports “none” | Exact parameter fingerprint records deny, arity-aware fingerprint records allow, so “a deny is not generalized” |
| goose | GooseMode four modes (Auto/Approve/SmartApprove/Chat) + five-level verdict | SmartApprove relies on readOnlyHint annotations + an LLM referee judging read/write; installing an extension always forces an ask |
None (no OS sandbox found in docs) | Deny result fed back; the LLM referee’s verdict is cached |
| nanobot | No per-call approval: blacklist + workspace fence + SSRF + bwrap | Dangerous-command regex intercept; workspace out-of-bounds rejected; exec does not pass the whole os.environ |
bwrap (bubblewrap) sandbox; without bwrap, application-level workspace protection only | Error string fed back; what the front-line defense stops never enters the model loop |
| grok-build | Six PermissionMode grades + LLM classifier (PermissionClassifier): Auto mode uses an LLM to classify risk in real time; 3 consecutive / 20 total auto-denies then force a popup; Ask mode asks every time; YOLO mode |
tree-sitter bash AST parse (permission/manager.rs:8759 lines); 8 classes of dangerous-pattern regex; sandbox probe |
Has a sandbox switch (similar to CC) | Deny reason fed back as a tool result; after Auto deny cap, PermissionMode auto-downgrades to Ask |
| OpenWorker | Five modes (discuss/plan/interactive/auto/custom) + out-of-band approval: needs_user handed to an injected approver (attended: inline card / unattended: hang in inbox) |
shell rejects whitelist if it contains operators (stops git status && rm -rf ~) + argv exact prefix match (permissions.py:216); writes must land in a writable root |
No OS sandbox (local-first desktop): path fence + approval + §25 standard rules (external risk by exact target only; shell always asks) | Deny reason fed back; _display privacy counts never enter the model |
How each house thinks: permissions are where the eight houses diverge most; they split into four roads. The popup-ask school (Claude Code) believes in “human in the loop”: five-gate verdict filters layer by layer, if unsure pop a dialog, “always allow” precipitates into rules—safety is “a chance to veto every time.” The rules-first school (opencode, hermes) believes in “legislate first, then enforce”: write the rules in advance, run with less interruption; opencode’s “teachable refusal” is a neat stroke—attach a sentence on deny (“do not run rm -rf, please use a safer way”) back to the model, and the refuse instant becomes a teaching scene. The structural-safety school (CodeWhale at the extreme) believes “there is simply no tool that can overreach”: Plan mode does not register write tools at all; constitution.json cannot be bypassed even in full-access mode; UserInputProvenance distinguishes “a real person typed this” from “a sub-agent handed this over,” and non-human origin cannot inherit auto-approve—this is a source-level defense against the new threat of “indirect prompt injection privilege escalation.” The sandbox school (nanobot / Raven) simply gives up per-call approval: in always-on IM there is nobody to press y; they replace the human gate with “blacklist + sandbox + workspace fence” front-line defense—nanobot bets bwrap, Raven after the fork upgrades to boxlite microVM. Honesty is also worth remembering: a blacklist is only “best-effort”; the real backstop is the sandbox.
flowchart LR
subgraph P1["① 弹窗询问派(人在回路)"]
A["Claude Code
五关裁决,兜底弹窗
'始终允许'沉淀为规则"]
end
subgraph P2["② 规则前置派(先立法后执法)"]
B1["opencode:三态通配符
拒绝可附教学反馈"]
B2["hermes:审批三级 + YOLO 导入即冻结
防提示注入偷开免审批"]
end
subgraph P3["③ 结构安全派(没有越权的工具)"]
C1["CodeWhale:Plan 不注册写工具
constitution.json 只加锁不放权
provenance 降级"]
C2["goose:Chat 模式禁工具
SmartApprove 靠注解 + LLM 裁判"]
end
subgraph P4["④ 沙箱派(隔离代替审批)"]
D0["nanobot:黑名单 + 工作区 + SSRF + bwrap
Raven 上游出厂设置"]
D1["Raven:黑名单 + microVM
无人值守场景的答案"]
D2["hermes:docker/modal 后端
按任务选隔离级别"]
end
subgraph P5["⑤ LLM 分类器派(智能分类)"]
E1["grok-build:PermissionClassifier 实时判断
Auto 模式 3/20 上限后强制弹窗
把'该不该'也交给模型"]
end
2.5 Context engineering and compaction: the summary-and-discard school vs the lossless-asset-management school
| Project | Trigger | Means, layered | Fidelity philosophy |
|---|---|---|---|
| Claude Code | Trigger line = effective window − 13000 (~167k tokens, start early); fuse: give up after 3 consecutive failures | Five-layer defense: tool results to disk → snip → microcompact → context collapse → autocompact | Summary-and-discard: dispatch a fork clone (maxTurns:1) to write a 9-section structured summary; details before the boundary keep only the gist; on 413 there is still reactiveCompact as a passive rescue |
| opencode | Usage ≥ available cap (cap − 20000 headroom) | Three layers: auto compaction (a hidden no-permission agent writes the summary) + tool-output prune (40k protect line / 20k release line) + single-shot truncate | Summary-and-discard, but the tail is kept very finely: last 2 user turns + 25% of context budget (2k–8k); if not enough, the turn is cut from the middle; overflow compaction replays the last real user message |
| hermes-agent | 50% of the window already triggers (the earliest house); at most 3 times per turn | One tool-output prune pre-scan before compaction | Summary-and-discard: an auxiliary model summarizes the middle, protecting the head (original instructions) and the tail (the work site); compaction is the only scene allowed to rebuild the system prompt |
| Raven | Three scales: this-turn emergency shrink / in-session Curator / cross-session Consolidator | Emergency shrink: old tool results → placeholders, zero LLM calls | Lossless archive: Curator lets a small model look at a Manifest index and produce a ContextPlan; Archive lands the original text verbatim + keeps citations, then distills a Working State (goals / pending / decided) back into the system prompt—“facts left the window, but they did not leave the model’s field of view”; on error there is a deterministic Fail-Safe fallback |
| CodeWhale | Pure token signal (v0.8.11 deleted the message-count branch), default threshold 800k tokens (80% of a 1M window) | Free first, then paid: first mechanically trim duplicate tool outputs (zero cost); only if still over, call an LLM summary | Summary-and-discard but extremely restrained: keep the last 4 originals; pin the working-set top 24 paths’ related messages; summary input truncated to head 14000 + tail 6000 characters; on failure prefer not to compact (“never corrupt state”) |
| goose | Token usage ≥ 80% of available window | compaction + tool-call pair summaries (batches of 10 pairs, in the background) | Summary-and-discard but “with dignity”: old messages marked “visible to the user, invisible to the model” (history is not deleted); the summary is model-visible only, plus a stretch of “bridging lines” teaching the model not to announce it and to keep working |
| nanobot | Token budget triggers Consolidator; idle TTL triggers AutoCompact | Micro-compact (old tool results as placeholders) + lossy LLM summary; cross-session Dream reflection | Summary-and-discard school: second-scale deterministic micro-compact + session-scale lossy archive to history.jsonl; Dream distills into SOUL/USER/MEMORY at night |
| grok-build | 85% context threshold triggers CompactionPolicy; wall_clock_budget_secs=300 against timeout |
Two-pass compaction: Pass1 speculative background pre-compact + Pass2 formal compact (both optional); Goal Mode small updates go fire-and-forget and do not land in JSONL | Summary-and-discard school; Goal Mode GoalOrchestrator’s frequent state updates are decoupled from compaction, so state jitter does not pollute the system prefix |
| OpenWorker | Does not auto-compact context; turns are bounded by max_iterations (150) | Each turn a temporary <system-context> hung on the last user message (not persisted); _outbound_messages strips bypass + adapts PDF/vision to the current model’s capability |
Explicit-memory school (not summary-and-discard): SQLite explicit facts + persisted history; “predictability over saving tokens” |
How each house thinks: this is the eight houses’ most beautiful philosophical battlefield. The summary-and-discard school (including nanobot) thinks “compaction = concentrate”: dispatch a clone or a cheap model to write old history as a summary, then look only at the summary and the last few turns; all the craft is in the details—how much tail to keep, free slimming before compaction (mechanical trim / prune pre-scan / micro-compact), how to fall back on failure (fuse, prefer not to compact); nanobot also uses Dream to write valuable things back to Markdown across sessions. The lossless-asset-management school has only Raven: it holds that “context is not a consumable, it is a managed asset”; compaction is not “throw away old newspapers” but “archive into the library, leave a call number and reading notes”—Archive loses not one character; Working State guarantees the model always remembers goals and pending items. The cost is equally blunt: extra money for a small model plus a Manifest/Plan/validate complexity, and Raven itself still keeps a lossy Consolidator as a retreat (that is exactly the original path forked from nanobot).
flowchart TD
O["上下文要爆了"] --> P{"两派选择"}
P --> Q["摘要丢弃派
Claude Code / opencode / hermes / CodeWhale / goose / nanobot"]
P --> R["无损资产管理派
Raven Curator"]
Q --> Q1["派分身/辅助模型写摘要
旧历史离开窗口,只留要点
功夫在:保留尾部 + 免费预剪 + 失败兜底"]
R --> R1["Archive:原文逐字落盘 + 留引用
Working State:蒸馏笔记注回系统提示
确定性代码执行小模型的方案,Fail-Safe 兜底"]
2.6 Memory systems: from “one constitution file” to “dual-track long-term memory”
| Project | Project-level memory files | Cross-session memory | Auto precipitation | Retrieval |
|---|---|---|---|---|
| Claude Code | CLAUDE.md five sources stacked by level (enterprise control / user global / project every layer upward / --add-dir / auto memory), each file carries an identity tag |
memdir auto memory: MEMORY.md cap 200 lines / 25KB + inject by relevance; Session Memory background-distills session notes |
The model writes on its own; when you correct it, a “consider saving to memory” prompt is appended | Relevance retrieval injection; entries carry a “freshness” prefix |
| opencode | Compatible with AGENTS.md/CLAUDE.md; walking upward stops at the first matching filename (does not stack); the read tool triggers “nearby discovery” | No cross-session long-term memory (the analysis document is explicit: “not found”) | None (AGENTS.md is maintained by hand) | None |
| hermes-agent | AGENTS.md/CLAUDE.md/.cursorrules triple compatibility, plus probing “project fingerprint” (package manager, verify command) | MEMORY.md (its notes) + USER.md (your portrait), frozen snapshot: in-session writes land on disk, do not change the prompt |
Closed-loop learning: skill_manage creates skills itself → curator reviews and archives in the background when idle → session_search FTS5 searches historical sessions |
Snapshot injection + FTS5 full-text search + optional Honcho user modeling |
| Raven | soul.md (persona) / agent.md (code of conduct) / TOOLS.md (tool notes) three-piece kit, split from the CLAUDE.md idea |
EverOS dual-track: user track (portrait/events) + agent track (skills/cases); user.md / episodes.md / Foresight prediction |
Consolidator summarizes into memory notes; after-turn pipeline indexes every turn into EverOS and reports skill usage | SkillForge weighted RRF fusion: local files 1.0 / EverOS recall 0.9 / Skill Hub 0.85 |
| CodeWhale | AGENTS.md as the canonical name, CLAUDE.md compatible, walking upward supports monorepo; merge cap 500KB against explosion | handoff.md (handoff letter on compact/exit) + user memory (legacy, migrating to Moraine MCP) |
# shortcut record; remember tool (opt-in) |
handoff injected as a “continuity” fragment; working-set top 24 paths pinned |
| goose | .goosehints + AGENTS.md (filename configurable), collected layer by layer down from the git root; @file import bounded by git root, .gitignore files not read |
memory extension (category + tag store/retrieve) + chatrecall extension (search historical sessions) | The model calls remember_memory to remember actively |
Retrieve by category; subdirectory hints dynamically loaded (SubdirectoryHintTracker) |
| nanobot | AGENTS.md (project) + SOUL.md / USER.md (agent-workspace persona and portrait) | Pure-file MEMORY.md + history.jsonl; dulwich git-traces memory files |
Dream night-time restricted agent edits SOUL/USER/MEMORY; can create skills | Full-file inject + recent history after Dream; commit messages based on the real diff |
| grok-build | AGENTS.md / CLAUDE.md compatible; AgentDefinition.finalize_prompt at build time renders with MiniJinja templates and locks the system prompt |
JSONL session store (~/.config/grok-build/sessions/); Goal Mode plan.md persists across turns |
No auto-learning; plan.md is Goal Mode’s cross-turn “working memory” (GoalPlanner must write it, GoalWorker reads it) | plan.md fully injected into Goal Mode context; JSONL appends by turn |
| OpenWorker | AGENTS.md (workspace convention, injected into the system prompt) | SQLite remember: global / workspace dual scope (memory/sqlite_store.py) |
No night auto-learning; the model is guided on “when to remember” and actively remember / memory_update / memory_forget |
Injected into the system prompt in full by scope; before store, look up known memories to dedupe |
How each house thinks: the eight houses have three consensuses on memory—memory is Markdown files (humans can read them, can edit them by hand), project constitution enters the system prompt, compatible with the opponent’s format is basic courtesy (opencode/hermes/CodeWhale/goose all read CLAUDE.md). The split is on the “auto-learning” axis: opencode is the most restrained, explicitly does no cross-session memory, and leaves “remembering the user” to config rather than algorithm; Claude Code and CodeWhale sit in the middle, have auto memory but keep the file shape; nanobot’s Dream reflection is the flagship of the file-memory road—night tidy-up, git-auditable; hermes and Raven make memory a closed loop—hermes’s five-piece kit interlocking, Raven’s dual-track storing “your affairs” and “the agent’s own craft” separately, recalled separately. Memory is the dimension of largest “product personality” difference: some treat it as config, some as an asset, some as a moat.
2.7 Sub-agents and multi-agent: three generations of cloning
| Project | How it is spawned | Parent–child relation | How the result comes back | Anti-runaway design |
|---|---|---|---|---|
| Claude Code | Agent tool; the child agent runs the main loop itself—the same query() with three things swapped: its own system prompt, a shrunk toolbox, an identity tag (runAgent.ts:248,748) |
Foreground shares the cancel token (ESC stops parent and child together); background has an independent token (ESC deliberately cannot kill it) | Foreground: block waiting, result as tool_result; background: on complete, stuff a notification into messageQueueManager |
Toolbox shrunk; in coordinator mode the main agent is left only three tools: dispatch work / collect work |
| opencode | task tool creates a child session whose parentID points at the parent |
Permissions derived from the parent session; ask the user before spawning | Foreground waits; last text packed into a <task> tag and returned; background (experimental flag) notifies the main agent on complete |
Depth default 1 layer (child agents by default cannot spawn grandchildren); background concurrency hidden behind an experimental flag |
| hermes-agent | delegate_task tool |
Delegation initiated by the top-level model is always background—"the model does not get to choose"; orchestrator child agents are the exception (sync) | When the child finishes, the result returns to the parent session as a new message | DELEGATE_BLOCKED_TOOLS: no recursive delegate / find-the-user / write shared memory / send messages / schedule tasks; child agents by default auto-deny every dangerous approval (against input deadlock) |
| Raven | spawn tool |
Child agent independent loop (cap 15 iterations), independent sandbox VM | A new TurnRequest with origin=SUBAGENT submitted via Spine—the parent is woken as if it received an ordinary message |
Concurrency gate (semaphore max 4) + frequency gate (max 30 per session per hour); toolbox shrunk: no message, no spawn |
| CodeWhale | agent tool (start/status/peek/wait/cancel) |
Does not barrier the parent loop (issue #3216): the parent turn ends as usual, stays responsive | Sentinel message <codewhale:subagent.done> merges in on a later turn |
Depth “add then clamp to a ceiling” (against re-reporting a value to bypass); total cap 64; child does not inherit the parent’s session-level auto-approve |
| goose | summon extension’s delegate tool; or a recipe’s sub_recipes |
Child agent independent session, independent context, may have independent model config | When done, only the conclusion text is brought back; intermediate process does not pollute parent context | Context isolation is the default shape; orchestrator extension (multi-session director) default off and hidden from the UI |
| nanobot | spawn tool |
Child agent independent loop, shrunk toolbox (no message / no spawn) |
Reinjected via MessageBus: channel=system + injected_event=subagent_result into the same inbound queue |
Default concurrency 1; /stop also cancel_by_session |
| grok-build | GoalWorker inside Goal Mode is itself a “large sub-task executor”; WorkflowManager supports up to 4 concurrent workflows | GoalWorker scheduled uniformly by GoalOrchestrator; GoalStrategist fail-open does not block the main flow | GoalClassifier validates each turn’s output and reports to GoalOrchestrator; GoalStatus 8 states track progress | GoalPlanner fail-closed (must return the literal "Done"); GoalStopDetector 9 regex classes against premature exit |
| OpenWorker | explore tool opens a child TurnEngine (code family, tools/subagent.py) |
Child engine forced PLAN read-only, no approver, brand-new context | Only the final report comes back; intermediate file reads do not enter parent context | Forced plan hard-blocks write/shell + no recursion (child has no explore); also self-wake self-suspend + timer/event wake |
How each house thinks: the hard part of sub-agents was never “sending them out,” but “bringing them back” and “not going mad.” Return methods show three generations: generation 1, block-and-wait—the parent agent sits and waits, simple and reliable but the UI freezes; generation 2, message inject—the result merges into a later turn as a notification/sentinel; CodeWhale’s comment is the most thorough: "Launching a sub-agent is not the same as joining it"; generation 3, scheduling backbone / message bus—nanobot makes the result an InboundMessage with injected_event re-entering through the bus’s front door; Raven on that basis swaps in Spine’s origin=SUBAGENT. Anti-runaway means highly converge: limit depth, limit concurrency, shrink the toolbox, do not inherit permissions—because everyone fears “recursion that spends tokens by itself.” And Claude Code’s “the child agent reuses the same query()” is the most beautiful design among them: main agent, child agent, compaction clone are all reuse of the same engine—which is also why the compaction clone can incidentally hit the prompt cache.
sequenceDiagram
participant P as 父 agent
participant S as 子 agent
Note over P,S: ① 阻塞等待(CC前台 / opencode默认 / hermes编排者)
P->>S: 派活
P-->>P: ⏸️ 原地等待
S-->>P: 结果作为 tool_result
Note over P,S: ② 消息注入(CC后台 / hermes / CodeWhale)
P->>S: 派活(后台,立即返回)
S-->>P: 完成 → 通知消息/哨兵汇入后续回合
Note over P,S: ③ 消息总线 / 调度脊骨(nanobot → Raven)
P->>S: spawn
S-->>P: publish_inbound(injected_event)
或 Spine TurnRequest(origin=SUBAGENT)
2.8 Extension ecosystem: MCP is the floor; above the floor each house shows its hand
| Project | Own extension system | Skills | MCP support | Command system |
|---|---|---|---|---|
| Claude Code | Plugin marketplace: a market = a git repo + marketplace.json, sparse clone pulls only the plugins you install | SKILL.md progressive disclosure; conditional skills (activate only when paths match); skills from MCP sources disable shell pre-exec (a safety red line) | 6 transports; mcp__server__tool naming; annotations mapped into the three tags; untrusted repo start requires approval |
60+ commands, local/local-jsx/prompt three types; user commands support $ARGUMENTS and shell pre-exec |
| opencode | Plugin Hooks, all categories: config/event/tool/auth/provider/chat.params/permission.ask/tool.execute.before…; plus {tool,tools}/*.ts lightweight directories |
Multi-location discovery, including ~/.claude/skills |
3 transports + OAuth; server_tool naming; MCP prompts become commands |
TUI app commands + server-side template commands ($ARGUMENTS, !`cmd` pre-exec, @file) |
| hermes-agent | Everything is a plugin: ~20 messaging platforms, 33 providers, kanban, memory all plugins, and lazy-loaded | agentskills.io open standard + Skills Hub; the agent can create skills itself | Both a client and a server (mcp_serve.py reverse-exposes itself) |
83 commands, one registry: CLI help, gateway dispatch, Telegram menus, Slack subcommands, autocomplete all generated automatically |
| Raven | Plugins deliberately narrowed: only two contribution points, “memory backend” and “tools” (raven-plugin.toml) |
Local SKILL.md + Skill Hub remote marketplace (search pulls only metadata) | mcp_<server>_<tool> wrapped into the same registry |
CLI typer command tree + ~50 TUI slash commands, three-layer nearest-neighbor dispatch |
| CodeWhale | Plugins read-only discovery (“discover ≠ enable ≠ trust ≠ execute”); hooks at 11 lifecycle points | Compatible with six houses’ directories (.codewhale/.agents/.claude/.cursor/.opencode/.codex)—“if we can be compatible, we do not invent another standard” | 3 transports; start_mcp_server tool lets the model itself start a new MCP server mid-conversation |
85+ built-in commands (including Chinese aliases) + .codewhale/commands/*.md custom (can constrain the toolset) |
| goose | Everything is an MCP extension (six shapes including in-process Builtin, pure-Rust Platform); Recipe: instructions+extensions+params+output schema+retry packed into shareable YAML | ~/.agents/skills or project .agents/skills |
rmcp implementation; the agent can search and install extensions itself (forced approve + malice check) | Built-in commands + recipe commands + skill commands, three sources merged and deduped |
| nanobot | Channel plugins (pkgutil discovery) + entry_points tool plugins |
Local SKILL.md progressive disclosure + ClawHub remote skill marketplace | Official mcp SDK; MCP tools enter the same registry | CLI typer + slash commands (/stop and other priority commands can punch through a busy session) |
| grok-build | Plugin Marketplace (Git/GitHub/local; allowlist; InstallRegistry commit hash against tampering); Hooks (xai-grok-hooks crate); ACP protocol exposed outward |
AgentDefinition.skills allowlist + discoverSkills auto-discovery; ~/.claude/skills compatible |
MCP Dispatcher (50ms rolling window merge + last-write-wins + auto-restart); xai-acp-lib independent crate |
/goal /compact /recap /model /effort /memory session commands; Ctrl+P command palette |
| OpenWorker | persona marketplace: directory/git install, snapshot into a managed zone, default disabled pending consent (personas/registry.py:340) |
Anthropic-format skills, progressive disclosure + load_skill (workspace .coworker/skills) |
Home-grown async-layer MCP client: stdio + streamable-http + OAuth (mcp/oauth.py) |
40 connectors (GitHub/Slack/Jira/Notion/Gmail/GCal…) per-tool control; openworker-connectors CLI |
How each house thinks: MCP has become the eight houses’ shared floor—all support it, and treat it as equal to built-in tools (same validation, same permission verdict). Competition happens above the floor: Claude Code uses a marketplace for experience (even sparse-clone details are polished), hermes uses plugins for breadth (even platform adapters and model vendors are plugins), goose uses recipe for circulation (agent workflows can for the first time be versioned and shared like scripts), opencode uses Hooks of every category for openness (even “permission ask” can be taken over by a plugin), CodeWhale uses compatibility to minimize migration cost (skills you wrote for another house just work), nanobot uses ClawHub + entry_points + channel plugins to keep the core small and capability at the edge, Raven uses narrowing to express pre-alpha restraint. Another consensus is SKILL.md + progressive disclosure (ordinarily only the list, full text loaded when used)—almost everyone has it; it is one of the fastest-converging practices across projects.
2.9 Models and auth: subscription reuse is the open-source community’s breakout
| Project | Provider lineup | Auth shape | Local models | Fallback / fault tolerance |
|---|---|---|---|---|
| Claude Code | Anthropic family only (including Bedrock/Vertex/Foundry enterprise channels) | Subscription OAuth (PKCE + loopback/manual dual channel) / API key / enterprise cloud; token in the keychain, silent refresh with a file lock | None | 529 overload retry 3 times then switch --fallback-model; Haiku-class small models for chores (topic detection etc.) |
| opencode | models.dev catalog of dozens; home-grown native runtimes for OpenAI/Anthropic/own, the rest via Vercel AI SDK | api / oauth / wellknown three shapes; built-in auth plugins: ChatGPT Plus (Codex), GitHub Copilot, Claude subscription can all log in | Not a headline in the docs | SessionRetry.policy retry policy; no cross-model fallback chain found in docs |
| hermes-agent | 33 provider plugins + Nous Portal (300+ models) | Portal OAuth device code one-click login / API key / external secret managers (Bitwarden, 1Password) | First-class citizens: custom provider + vLLM/SGLang/llama.cpp; ollama-cloud plugin; own Hermes-series open-weight models as companions | Fallback chain (if the primary hangs, swap spares in order) + MoA (ask several models the same question then synthesize) |
| Raven | LiteLLM unified adapter (OpenAI/Anthropic/Gemini/DeepSeek/Copilot/Codex OAuth/Azure/custom endpoints) | raven onboard wizard config |
Custom OpenAI-compatible endpoints | chat_with_retry + fallback_models; error-class driven: rate-limit/network→retry degrade, auth/arrears→fatal, context overflow→compact rather than degrade |
| CodeWhale | 36 ProviderKind enums (including Moonshot, Zhipu, MiniMax, StepFun, Xiaomi MiMo) |
OS keychain / env var / OAuth device code; base URL forced HTTPS (except loopback, otherwise explicit self-attestation) | No-key first-class citizens: vLLM / SGLang / Ollama | /model auto auto-route: pick model and thinking grade by task content; TUI can show the routing receipt |
| goose | 15+ handwritten + 37 declarative JSON + ACP provider (treat another agent as a model backend) | API key / OAuth device code / system keychain; subscription-reuse five-pack: ClaudeCodeProvider, ChatGptCodexProvider, GeminiCliProvider, KimiCodeProvider, CursorAgentProvider | Ollama; built-in llama.cpp/candle runtime; goose local-models downloads GGUF/MLX from HuggingFace |
Error branches specialized (insufficient balance / refusal / network error each have their own prompt); no explicit fallback chain found in docs |
| nanobot | ~40 providers, home-grown registry (talks openai/anthropic SDKs directly, no LiteLLM) | nanobot onboard / provider subcommand; config hot-reload |
OpenAI-compatible endpoints can connect | fallback_provider: on degradable errors and when no content has streamed out, swap spares down the chain; overflow→compact rather than degrade |
| grok-build | Locked to xAI Grok; multimedia tools deep-bound to xAI-only capabilities; xai-grok-auth dedicated crate |
xAI OAuth / API key / OIDC enterprise SSO / external custom endpoints; AuthRetryMiddleware auto-refreshes token | None (xAI lock) | Error grading; AgentDefinition.model field can override; Goal Mode child agents can specify different models |
| OpenWorker | On aisuite: OpenAI/Anthropic/Gemini native + DeepSeek/GLM/Kimi/Qwen/MiniMax/Grok/Mistral + Ollama | BYO key stored in a local secret store; cloud only brokers OAuth; usable without login (fill credentials by hand) | Ollama fully local, first-class | ProviderRouter routes by provider: prefix; capability matrix records vision/pdf; switching models only changes a field + one degrade notice |
How each house thinks: the most interesting dark war on auth is “subscription reuse”: official products want you to use their subscription; the open-source community turned that wall into a door—opencode logs into your ChatGPT Plus and Copilot with plugins, goose wrote five providers specifically to reuse your existing Claude/ChatGPT/Gemini/Kimi/Cursor subscriptions, hermes on detecting a sibling project’s config directory even offers one-click migration. Fallback design exposes maturity: nanobot / Raven’s error classification is the most careful—distinguish “should retry” (rate limit), “no hope” (arrears), and “wrong medicine” (on context overflow, degrading is meaningless; what you should do is compact); Raven outsources adapters to LiteLLM, nanobot insists on a home-grown registry so “every path is traceable.” CodeWhale’s auto-route represents another direction: since the model is a part, let the system pick the part for you.
3. Deeper comparison of technical implementation: six engineering-decision dilemmas
Chapter 2 compared “what the product did.” This chapter drills one layer down, comparing “how the code is actually written.” The same function—say “display the words the model spat on the screen”—differs enough across seven houses that you would doubt they are the same class of software. These differences are not random; each traces to a concrete engineering dilemma: something you can only pick one of three, and picking A means paying for the absence of B and C. This chapter picks the six technical axes that most expose “preference logic” and splits them one by one.
Reading note: this chapter’s
file:linemainly come from a second source check of each repo (supplementary mining on top of Chapter 2’s single-project analyses); nanobot line numbers are re-cited from nanobot source analysis; places marked “not found / uncertain” really were not found, not omitted.
3.1 Language and runtime: four bets, on four different pain points
Before an AI agent’s first line of code is written, you have to answer: what language, on what runtime? That decision permeates concurrency model, distribution method, contributor barrier, almost irreversible. Seven houses gave four answers.
| Project | Language / runtime | Key selection evidence | Core pain it wants to solve | Cost paid |
|---|---|---|---|---|
| Claude Code | TypeScript monolith + forked Ink | src/ink/ ships its own reconciler/DOM/Yoga/termio, not import 'ink'; src/native-ts/yoga-layout/ is a pure-TS rewrite of a Yoga subset; at build, bun:bundle’s feature() does dead-code elimination (src/entrypoints/cli.tsx:1) |
Treat the terminal UI as a “small React DOM,” so streaming typewriter, mouse selection, alt-screen become first-class | Maintain a whole renderer + Yoga subset; almost impossible to merge back to upstream Ink |
| opencode | TypeScript on Bun + Effect | packageManager: bun@1.3.14 (package.json:7); the service by default runs in a Bun Worker thread (cli/cmd/tui.ts:210); SQLite walks bun:sqlite (database/sqlite.bun.ts:1); service injection uses Layer.effect, cancel uses Fiber.interrupt (effect/runner.ts:178) |
Functional + structured concurrency in exchange for clean “interrupt / DI / testability” | yield* all over the screen, steep contributor barrier; Node compatibility needs a #db dual track (db.bun.ts/db.node.ts) |
| hermes-agent / Raven / nanobot | Python kernel (hermes/Raven also add a TypeScript UI) | hermes main loop "entirely synchronous" (AGENTS.md:352); Raven full-chain asyncio; nanobot: Python≥3.11 full asyncio, talks openai/anthropic SDKs directly (no LiteLLM), React+Vite WebUI same process over WebSocket |
Reuse Python’s AI/memory/research ecosystem; nanobot additionally bets “readable core, fully self-contained” | Dual runtime (hermes/Raven) or a browser-frontend boundary (nanobot); a home-grown provider registry must be fed by yourself |
| CodeWhale / goose | Rust + tokio | CodeWhale: 635 .rs files, ~650 000 lines, release opens LTO + codegen-units=1 but deliberately does not set panic=abort (Cargo.toml:67-73), globally swaps in mimalloc; goose: 12 crates, CLI 8MB stack (main.rs:38-46) |
Single-binary distribution, memory safety, getting “stream while running tools in parallel” concurrency written correctly | Slow compiles, async-Rust learning curve screens contributors; the rigor bought with type gymnastics is also a burden |
| OpenWorker | Python backend (on aisuite) + three languages | pyproject.toml depends on aisuite (pinned commit) / textual / fastapi; three command entries (TUI/service/connectors); engine fully asyncio; the shell is Tauri(Rust), UI React, speech Rust STT |
Reuse Python AI ecosystem + aisuite’s unified provider abstraction; desktop shell for a “local-first” experience + self-update | Three-language build/pack complexity (Python+Node+Rust, a whole packaging/ set); provider coverage depends on aisuite |
How each house thinks: the most interesting thing about this table is—no house picked a language “to run fast”; all picked it so “a particular hard thing is harder to get wrong.”
- Claude Code’s pain is precision of terminal experience. It has to do “ESC emergency-brakes all the way to the wire,” character-level streaming, mouse selection; an ordinary Ink wrapper cannot satisfy that, so it simply vendors the whole of Ink and rewrites even a Yoga layout-engine subset in TS (
src/native-ts/yoga-layout/index.ts:1). This is “for experience, we would rather maintain a renderer ourselves.” - opencode’s pain is long-term maintainability and concurrency correctness. It bets Effect—
Layerfor DI,Fiberfor structured concurrency—in exchange for extremely clean interrupt-cleanup paths, almost no mocks needed to test. The cost is written in its own contributing guide: the barrier is high. A typical “long-termism high-stakes bet.” - hermes, Raven, nanobot’s pain is ecosystem reuse + a readable core. Memory, multi-provider, full-text search: Python’s ecosystem is most mature; nanobot as academic open source even writes “small core / readable internals” as iron law, and refuses to outsource LiteLLM—explicit over magic. Interestingly they split on sync/async: hermes’s main loop insists on fully synchronous; Raven / nanobot are full asyncio (nanobot’s MessageBus and state machine are concurrent by nature).
- CodeWhale and goose’s pain is concurrency correctness + clean distribution. The agent main loop must simultaneously handle an SSE stream, parallel tools, sub-agent completion, user steer—Rust’s ownership model makes these harder to get wrong, and
Dropsemantics even gift a “hard cancel” (see 3.5). Single-binary distribution is a huge plus for a tool “to be installed on someone else’s machine.” The cost is compile time of 650 000 lines of Rust and the contributor barrier; CodeWhale’sAGENTS.mdeven specially teaches newcomers how to run targeted tests to dodge a full compile.
A counter-intuitive observation: language choice correlates strongly with “who the team can hire.” Nous / HKUDS lean Python research ecosystem, so the hermes/Raven/nanobot family is a Python kernel; Block and the CodeWhale community lean systems engineering, so they pick Rust; Anthropic wants extreme product iteration speed, a TypeScript monolith is fastest. Tech selection was never a purely technical decision.
3.2 Streaming pipeline: from byte stream to screen, six forks
The model’s answer is spat as a stream (one SSE event after another); the agent must receive, parse, display, and when arguments are complete execute tools, all at once. This pipeline “from the provider’s byte stream to the user’s screen” is the most real-time, most bug-prone place in an agent. Seven houses share a skeleton; they fork at three points.
Fork 1: SDK or raw stream? Claude Code’s choice is hardcore—it deliberately does not use the Anthropic SDK’s BetaMessageStream, and instead beta.messages.create({stream:true}) takes the raw SSE and accumulates it itself (src/services/api/claude.ts:1818). Source comments state the reason: the SDK does a partial JSON parse on every input_json_delta, which is O(n²) and stalls under large tool arguments. This is a miniature of “first party knows its own API best.” opencode is the opposite: the main path walks Vercel AI SDK’s streamText().fullStream, then normalizes into its own LLMEvent (session/llm.ts:373)—one layer of abstraction in exchange for multi-provider generality. nanobot sits between: talks openai/anthropic official SDKs directly, but maintains ~40-house registry and OpenAI-compatible adapter layer itself, refusing LiteLLM magic.
Fork 2: how to accumulate partial tool arguments? This is the most consistent design in the whole field: all seven use “string accumulate + parse once when complete.” When the model spits tool arguments they are JSON fragments ({"pa ... th":"/foo"}); nobody parses half a JSON:
| Project | Accumulate implementation | Evidence |
|---|---|---|
| Claude Code | contentBlock.input += delta.partial_json, normalizeContentFromAPI parses once at content_block_stop |
claude.ts:2087, messages.ts:2670 |
| opencode | ToolStream.appendOrStart concatenates strings, on complete parseToolInput (empty string as "{}") |
protocols/utils/tool-stream.ts:117 |
| hermes | tool_calls_acc[idx], name assigned, arguments += (against a provider re-sending the whole name) |
chat_completion_helpers.py:2845 |
| Raven | _merge_tool_call_fragments opens a slot by index, arguments append then json.loads |
agent/loop/main.py:2474 |
| CodeWhale | Delta::InputJsonDelta{partial_json} → input_buffer.push_str, final parse at block end |
turn_loop.rs:1080 |
| goose | First tool_calls delta builds a HashMap, args.push_str(...), after complete parse_tool_arguments |
formats/openai.rs:1090 |
| nanobot | AgentRunner streaming path accumulates arguments by index, parses when complete (same-origin design as Raven) | agent/runner.py (re-cited from nanobot analysis) |
This high convergence says one thing: this is the optimum educated by reality. Whoever tries to parse half a JSON will explode when a provider wraps lines, fields arrive out of order, or several tools run in parallel. hermes’s comments even specially mark MiniMax/Ollama special cases; Raven / nanobot comments record the evolution from “assume a single tool in order” to “respect index, support parallel multi-tool”—scars left by stepping in holes.
Fork 3: what about thinking / reasoning blocks? Here Claude Code again exposes first-party depth: Anthropic’s signed thinking blocks must be echoed back byte-for-byte to continue the conversation trajectory, so it specially accumulates signature_delta and explicitly does not count them in output-length stats (messages.ts:3078, so the thinking animation does not inflate token metering); when swapping API keys it also stripSignatureBlocks peels signatures (signatures are bound to the key, messages.ts:5061). goose meets the multi-provider version of the same problem: it hangs thinking blocks on the tool-call message rather than making them independent messages; the comment’s motive is “avoid independent thinking messages merging with tool-call and getting a 400 from Anthropic for duplicate signed blocks” (agents/agent.rs:2346). Same pit: first party solves it “inside job,” provider-agnostic solves it “accommodate the rule”—foreshadowing Chapter 4’s “first-party dividend.”
flowchart LR
P["Provider SSE 字节流"] --> A{"分岔一
SDK 还是裸流"}
A -->|裸流·第一方最懂| CC["Claude Code
自管状态机
避开 O(n²) parse"]
A -->|SDK·多provider通用| OC["opencode/goose/nanobot
归一化或直连官方 SDK"]
CC & OC --> B["分岔二:工具参数
字符串累积 + 收齐解析一次
(七家完全一致)"]
B --> C{"分岔三
thinking 块"}
C -->|签名块里应外合| CC2["CC:不计输出长度
换key剥签名"]
C -->|迁就provider规则| GS["goose:挂到tool-call
防重复signed block 400"]
3.3 “When to fire”: execute as soon as arguments are complete, or wait until this turn is fully received?
The previous section said “parse when arguments are complete,” but “execute the tool the instant parse finishes” versus “wait until the model has finished this whole turn, then execute uniformly” is where seven houses disagree, and it directly decides startup latency of the first tool.
Claude Code is the most aggressive: query.ts the instant it receives an assistant message with tool_use immediately streamingToolExecutor.addTool (query.ts:837), at which point the stream may still be spitting later content—the tool is already running. This is pressing “first token to tool start” latency to the minimum. The cost is complexity: if the stream later fails or needs fallback, the whole batch of executors must be discard()ed, then synthesized error results patched back to the model (StreamingToolExecutor.ts:174). This set is also hidden behind a gate config.gates.streamingToolExecution; turn it off and it falls back to the old “collect then runTools” path (query.ts:1380).
opencode walks the AI SDK’s scheduling: tools after arguments are complete are executed by the SDK (session/llm.ts:276 comment "AI SDK owns tool dispatch")—that is “execute when arguments are complete,” but not “execute on a half fragment.” goose, CodeWhale similar—once that tool’s arguments are complete it can enter the parallel pool.
How each house thinks: this choice is essentially complexity in exchange for latency. Claude Code, as a first-party product that must polish extreme experience, is willing to bear the discard/synthesized-error complexity for “after Enter, the tool starts a few tenths of a second earlier”; provider-agnostic projects prefer to let the SDK or a unified pipeline own scheduling, sacrificing a bit of latency for simpler implementation and cross-provider consistency. Latency is a product metric, complexity is an engineering cost; which way the scale tips depends on whether you treat “experience” as a moat.
3.4 Session persistence and crash recovery: three storage mentalities
An agent must be able to --resume last time’s chat; if the process crashes it must not amnesia, so the session must land on disk. What format it is stored in exposes each house’s understanding of “what a session is.”
| Project | Storage format | Location | Crash-recovery mechanism |
|---|---|---|---|
| Claude Code | JSONL (multi-type Entry, with parentUuid chain, mode/worktree/file-history and other metadata) | ~/.claude/projects/<cwd>/<sessionId>.jsonl |
Queue + 100ms flush (sessionStorage.ts:565); worktree three states distinguish “clean exit vs crash” (:541); no WAL, ≤100ms unflushed in the crash window may be lost |
| opencode | SQLite + Drizzle (session/message/part tables, event projection lands in the DB) | Global.Path.data/opencode.db, WAL mode |
Business only events.publish, projector lands SQLite (session/projector.ts:262); after abort, cleanup marks tools interrupted |
| hermes | SQLite + FTS5 state.db (replaced old JSONL) |
~/.hermes/state.db, WAL |
Persist the tool-call first, then execute the tool (conversation_loop.py:5361, with a contract test); gateway resume_pending continues the chat on restart |
| Raven | JSONL append-only (for LLM cache); Curator Archive separately stores lossless originals | workspace/sessions/{channel}/{chat_id}.jsonl |
On load, skip half-line JSON left by a crash (session/manager.py:303); Spine Lane is not persisted (pure memory) |
| CodeWhale | One atomic JSON per session (not Codex-style rollout) + checkpoint | {id}.json atomic write; checkpoints/<id>.json |
Mid-turn checkpoint atomic write, cleared on complete; utils.rs:371 directory fsync against power-loss losing a rename |
| goose | SQLite + WAL sessions.db (sessions/messages/usage_ledger three tables) |
Paths::data_dir() (still uses Block-branded directories for old users) |
BEGIN IMMEDIATE + WAL; can import Claude Code/Codex/Pi sessions |
| nanobot | JSONL sessions + atomic land; memory separately uses dulwich git | session/manager.py; agent-workspace Markdown |
Persist the user message early + runtime_checkpoint; after /stop cancel, still keep results of tools already run |
There is a clear split: the JSONL school (Claude Code, Raven, nanobot) wants simple, human-readable, append naturally good for prompt cache (history not changed by one character); the SQLite school (opencode, hermes, goose) wants queryable, transactional, able to build full-text indexes and a usage ledger. CodeWhale took a third road—atomic JSON files + checkpoint, wanting JSON’s simplicity, then patching crash safety with “atomic write + fsync”; the cost is that randomly rewriting history is expensive (fork/undo must rewrite the whole).
Most worth lifting out on its own is hermes’s “persist first, then act” (conversation_loop.py:5361, plus a dedicated contract test test_tool_call_incremental_persistence.py). Its logic: a tool may execute hermes restart and kill its own process; if you execute first then persist, after restart it “forgets it just initiated this tool call,” and the conversation trajectory breaks. So the iron law is first write the assistant’s tool-call message into SQLite, then execute the tool. This is the order bought with blood by software that must “run unattended for months on someone else’s machine”—a tool used only locally, interactively, would never think to defend against this.
How each house thinks: notice a shared choice of opencode and goose—event / projection split. Business code does not write the DB directly, it emits events (events.publish), a projector lands events as SQLite state (opencode projector.ts). goose goes further, tagging messages with two booleans user_visible / agent_visible (conversation/message.rs:661); after compaction, originals marked “user-visible, model-invisible,” summaries marked “model-visible, UI-hideable.” This “treat state change as an event stream” idea is the natural extension of their “server-as-ontology” architecture—because multiple clients must subscribe to the same state, an event bus is a necessity. Claude Code’s monolith does not need this layer; writing JSONL directly is fastest. Architectural shape decides the mental model of persistence.
3.5 Concurrency and cancel: six implementations of read-parallel write-serial; hard cancel vs cooperative cancel
In one turn the model may want several tools at once. Fully parallel collides (two tools writing one file); fully serial is slow. The seven-house consensus is “read-only tools may parallel, write tools serial,” but implementation and “how to shout stop at something already running” differ enormously.
Parallel scheduling: who can parallel, decided by what?
- Claude Code has two schedulers (a detail easy to mix up): on the streaming path
StreamingToolExecutor, concurrency-safe tools parallel, unsafe exclusive, no hardcoded cap; the non-streamingrunToolsConcurrentlyis what has the default cap 10 (toolOrchestration.ts:152). “Is concurrency-safe” defaults fail-closed to false (Tool.ts:750), and can be a function judged dynamically from arguments. Another neat idea: only Bash error triggers sibling-tool cancel (siblingAbortController,StreamingToolExecutor.ts:356); Read failure does not implicate siblings—because Bash often has “command-chain” semantics. - CodeWhale uses
FuturesUnorderedas a parallel pool (turn_loop.rs:2214); the parallel condition is the tool declaringsupports_parallel(default false,spec.rs:1217); shell additionally has a semaphoreMAX_PARALLEL_SHELL_EXEC=4. What can parallel are read-only File/Grep/WebSearch/read-only shell and the like. - goose uses
stream::select_allto merge each tool future and advance in parallel (agent.rs:2266), outerselect!with a 100ms sleep (biased) so it can poll cancel. - hermes is the finest, with three states: parallel / sequential / segmented (the safe subset of a batch of tools parallel, a write tool sets a barrier serial,
run_agent.py:6443); file tools whose paths overlap can parallel but not the same subtree. - Raven / nanobot instead execute tools serially within the same turn (
await tools.execute), even though the model can emit several tool_calls in parallel. They give concurrency to the “cross-session” and “sub-agent” dimensions.
Cancel: hard cancel vs cooperative cancel is this section’s largest philosophical split.
flowchart TB
subgraph HARD["硬取消派(能强杀正在跑的工具)"]
R1["Rust 系:CancellationToken 三层贯穿
CodeWhale drop(FuturesUnordered) 即杀掉所有在跑 future
turn_loop.rs:2333 注释:不是等协作取消,是直接 drop"]
R2["opencode:Fiber.interrupt ↔ AbortController
Effect 结构化中断 + acquireRelease 清理"]
R3["Claude Code:AbortSignal 直达 HTTP 层
ESC 真掐断连接;后台 agent 独立 controller 不被连累"]
R4["nanobot:/stop → asyncio.Task.cancel
硬取消 + checkpoint 优雅落盘"]
end
subgraph SOFT["协作取消派(只能等它自己检查标志)"]
S1["hermes:每轮开头查 _interrupt_requested
检查点分布在 API 前/重试sleep中/退避循环
卡死的工具不能强杀,只能靠超时"]
S2["Raven:asyncio.Task.cancel 向上冒泡
Lane 即取消单元;AgentLoop 内几乎不 catch CancelledError"]
end
CodeWhale’s hard cancel is the most typical: on cancel it directly drop(tool_tasks), the comment is blunt—"Dropping FuturesUnordered drops every still-active tool future ... instead of merely waiting for cooperative cancellation" (turn_loop.rs:2333). This is ability gifted by Rust Drop semantics: dropping the future equals cancel, plus an RAII guard so after cancel the terminal is not stuck in cooked mode.
hermes is the other pole: cooperative interrupt. The main loop each turn starts by checking _interrupt_requested (conversation_loop.py:837 and several other checkpoints); a tool halfway with no checkpoint can only wait for it to finish or time out. hermes itself admits this cost in its trade-off list: “a stuck tool cannot be force-killed, only timed out.” But it buys one benefit: it will not kill a half-written damaged file—force-killing a tool mid-write may leave a corrupted half file; cooperative cancel guarantees the tool either has not started or has finished completely.
How each house thinks: hard cancel vs cooperative cancel is essentially the trade-off of “response speed” versus “state integrity.” Claude Code wants the satisfaction of “press ESC, stop immediately,” so AbortSignal punches through to the HTTP layer and truly cuts the connection; hermes wants “unattended, leave no mess,” so it would rather be a bit slower and stay cooperative. And this again correlates strongly with language: Rust’s Drop makes hard cancel almost free; Python cooperative cancel requires laying checkpoints yourself. A language’s capability boundary quietly shapes a product’s interrupt philosophy.
3.6 Error handling: classifiers, feed-back, error-notebooks
Models will talk nonsense, providers will rate-limit, networks will drop, context will overflow. How errors are handled is the hardest metric distinguishing “toy” from “production.” Here there are three consensus layers, each one deeper.
Consensus 1: errors must be classified; you cannot retry uniformly. The most mature are Raven’s and goose’s error classifiers:
- Raven’s
classify_errorsplits errors into three actions:retryable(rate-limit→retry),should_fallback(swap model),should_compress(providers/base.py:16). The most brilliant is lifting context overflow out on its own—on overflow, degrading the model is meaningless; what you should do is compact. Many projects get this distinction wrong. - goose’s
ProviderErrorbranches are finer:ContextLengthExceededcompact at most 2 times then give up (agent.rs:2532),CreditsExhaustedpops a top-up link,Refusalterminates immediately and forbids retrying the same conversation (:2617). - Claude Code forks by product identity, a first-party trait: 529 foreground retries, background sources immediately drop so they do not amplify cascade; 429 by default retryable, but Claude.ai subscription users by default do not retry (because it is mostly a quota window, blind retry is useless,
withRetry.ts:765); 413 even splits two meanings—status 413 is “request body too many bytes,” while the copyprompt is too longis “too many tokens,” the latter walking compaction rescue (errors.ts:560).
Consensus 2: feed tool errors back to the model, let it change itself. Seven houses agree—tool failure does not throw and explode the session; the error is fed back as an ordinary tool_result. Raven is the most humane: on tool error uniformly append [Analyze the error above and try a different approach.] (agent/tools/registry.py:48), literally “teaching” the model to change method. Claude Code uses structured XML tags <tool_use_error>...</tool_use_error> + is_error:true (toolExecution.ts:669). opencode’s “teachable refusal” goes further—on a permission deny it can attach a natural-language feedback CorrectedError, turning refuse into teaching.
Consensus 3: write the pits you have stepped in into the code, so they are not repeated. CodeWhale made this a culture—every historical bug leaves an #issue numbered comment, a numbered error-notebook:
| Issue | The pit | The fix |
|---|---|---|
#4030 |
codewhale doctor \| head triggers SIGPIPE panic |
Unix boot resets SIGPIPE to SIG_DFL (cli/src/main.rs:11) |
#2990 |
After a laptop lid-close sleep, SSE fake-dead | Monotonic clock vs wall clock dual, discard half stream and resend (turn_loop.rs:337) |
#3014 |
Anthropic signed thinking blocks must be replayed as-is | Accumulate SignatureDelta and echo as-is (turn_loop.rs:1072) |
#1542 |
Non-DeepSeek OpenAI-compatible endpoints reject reasoning_content |
Generic compatible ends drop that field (client/chat.rs) |
#2264 |
Prompt-prefix drift busts the KV cache | Three-zone prefix contract PrefixStabilityManager (prompt_zones.rs:1) |
This “error-notebook culture” together with its “never corrupt state” philosophy—session atomic write + fsync (session_manager.rs:372), malformed trust files parse as an empty list rather than crash (workspace_trust.rs:49), a poisoned mutex also into_inner and keeps running (engine.rs:903), release keeps unwind so a single-tool panic does not kill the session—together constitute a sample of “what a production-grade Rust agent should look like.” Claude Code also has a pile of defensive pairing repairs (ensureToolResultPairing patches missing results, peels orphans, dedupes tool_use ids across messages, messages.ts:5120); it just hid them in the code rather than writing them as issue comments.
How each house thinks: maturity of error handling correlates strongly with “how long it runs.” The longer it runs, the more unattended, the heavier error handling. hermes, nanobot and Raven must live in IM long-term online, so they have the most complete fallback chains and cooldown; CodeWhale must run on all kinds of strange machines, so it accumulated an error-notebook; Claude Code must serve a sea of users’ subscription and API two billings, so error classification forks by product identity. An agent’s volume of error-handling code is roughly equal to the number of times the real world has beaten it.
4. How product logic decides technical form: from team genes to architectural cause
The first three chapters all talk “how.” This chapter answers a more fundamental question: why did seven houses make such different choices? The answer is not in the technology; it is in product logic—who pays, what they want to sell, whom they serve, what they live on. Technical form is downstream of product logic. This chapter pushes in reverse: starting from team genes, watch how every “technical preference” is forced by “commercial logic.”
4.1 A “gene table”: who pays, what they sell, therefore how they write code
| Project | Team genes | What they live on (commercial logic) | Therefore technical preference |
|---|---|---|---|
| Claude Code | Anthropic official (model vendor) | Sell Claude subscriptions / API tokens—make you use Claude more | Lock Anthropic and squeeze every feature dry (cache billing headers, signed thinking, effort grades); closed-source monolith for extreme iteration |
| opencode | Anomaly (the SST infrastructure team) | Open-source agent as funnel, cloud monetization: OpenCode Zen (curated model gateway, claims zero markup) + OpenCode Go (cheap subscription) (console/app/src/i18n/en.ts:196; sst.config.ts) |
Server-as-ontology (infrastructure thinking); provider-agnostic but they build their own model gateway as a monetization entry |
| hermes-agent | Nous Research (open-weight model company) | Sell Nous Portal subscriptions (300+ models) + use agent trajectories to produce training data (batch_runner.py, trajectory_compressor.py) |
Model-agnostic but with entitlement/routing special cases for their own Portal (conversation_loop.py:269); everything-is-a-plugin so their own models plug in easily |
| Raven | EverMind-AI (memory-tech company) | Sell a memory substrate—EverOS (external service + PyPI package everos==1.1.3, HTTP API /api/v1/memory) |
Context/memory made the architectural protagonist; EverOS default backend, swappable via plugins; fork nanobot then stack Curator/Spine |
| CodeWhale | Community project (ex deepseek-tui, DataWhale collaboration) | No direct monetization, lives on community contribution + AI-assisted development (.mailmap maps Copilot/Cursor/Qwen commits onto maintainers) |
“The model is a part, not the product”; compatible with six houses’ directories to lower migration cost; facing Chinese + multi-provider community |
| goose | Started by Block, donated to Linux Foundation AAIF | Does not monetize directly (governance-neutral, values Open/Flexible/Choice, GOVERNANCE.md:7); Block internal tool open-sourced and spilled out |
Engine-as-server + everything-is-MCP (highest ecosystem ceiling); ACP protocol-neutral |
| nanobot | HKUDS academic open source (Xubin Ren), MIT | No direct monetization; uses a readable core and full self-containment for research/community influence (same house as LightRAG) | MessageBus + explicit state machine; talks SDKs directly, home-grown ~40 providers; ultra-light personal agent that lives in chat apps |
This table is the key to understanding every technical difference. Three of the sharpest causalities:
- Only a model vendor will lock a model. Claude Code locking Anthropic is not technical conservatism; it is commercial necessity—Anthropic building this agent’s purpose is to make you use Claude more. Locking instead lets it squeeze every knob of the Anthropic API dry (see 4.3). Conversely, none of the other six houses build the main model themselves (Nous has the Hermes series but does not force it), so “model-agnostic” is a hard need for them—you cannot say “I am neutral” and bind one house at the same time.
- Whatever you sell, you make that the architectural protagonist. EverMind is a memory company, so Raven makes context engineering a 6-stage explicit pipeline, makes compaction a lossless archive, hangs EverOS by default—memory is its commodity, of course it must be the protagonist. Nous lives on Portal subscriptions and training data, so hermes ships trajectory-compression tools and has special cases for Portal. HKUDS sells “readable, self-contained,” so nanobot presses the core to loop/runner and pushes all capability to the edge. A product’s commercial kernel stubbornly shows up in the technical module it works hardest on.
- Projects that do not monetize dare take architecture to the most radical place. goose was donated to a foundation, has no monetization pressure, so it dares bet “everything is MCP, engine-as-server,” the route with the highest ecosystem ceiling but short-term performance cost (it even had to invent Builtin/Platform two “cheat” shapes to claw performance back). CodeWhale is a pure community project, so it dares use 650 000 lines of Rust to grind safety and cache. nanobot as academic open source dares write “keep the core small” as iron law and refuse LiteLLM. Only a project without a KPI has the qualification to make a long-termism technical high-stakes bet.
4.2 Who the client’s protagonist is decides where the architecture’s center of gravity sits
The product position “what is the agent’s UI” directly decides where the architecture grows. Seven houses’ “protagonist clients” are completely different, so the architectural center of gravity leans with them.
flowchart TD
Q["产品问自己:用户主要从哪里用我?"] --> A["终端/编辑器
(写代码的人)"]
Q --> B["消息软件
(Telegram/微信/飞书)"]
Q --> C["桌面 GUI + 第三方宿主
(非程序员 + 生态)"]
A --> A1["Claude Code:单体 TUI,五层同进程
CodeWhale:双二进制消息管道
→ 重心在终端体验与主循环精度"]
B --> B1["hermes:Python大脑 + ~20平台适配器
nanobot:MessageBus + 17平台 + WebUI
Raven:Spine调度脊骨 + 12平台
→ 重心在'会话键映射'与'无人值守安全'"]
C --> C1["goose:引擎即服务器 ACP
opencode:服务器即本体 HTTP
→ 重心在'协议'与'多客户端一致性'"]
- Protagonist is the terminal (Claude Code, CodeWhale): all the craft goes into main-loop precision, streaming render, ESC emergency-brake, those “terminal feels.” Claude Code even forked the whole of Ink for this.
- Protagonist is messaging software (hermes, nanobot, Raven): the architectural center of gravity is two things others do not much worry about: one, session-key mapping—the same brain must simultaneously take a dozen platforms’ DMs / groups / topics; two, unattended safety—in IM nobody is sitting at the computer pressing “allow,” so nanobot / Raven simply give up per-call approval and bet the sandbox (this is the product root of Chapter 2’s permission duel “sandbox school”). Their position is “the agent is a member of your address book,” not “a tool you open.”
- Protagonist is GUI + third parties (goose, opencode): you must first have a protocol. goose’s
goose serveis ACP over HTTP/WebSocket; desktop, Zed, any third party are clients; opencode is more extreme, even the same-process TUI walks the full HTTP protocol (cli/cmd/tui.ts:210Worker + in-memory fetch), the reason being “local single-machine and remote team should be the same code path.” The cost is equally real—goose must give its own GUI aGOOSE_SERVER__SECRET_KEYagainst impersonated access (gooseServe.ts:336), the protocol must be maintained for life, cross-language types still need a codegen pipeline to stay in sync (goose usesschemarsto generate JSON Schema → OpenAPI → TS,Justfile:165).
How each house thinks: this choice “is best thought through on day one,” because turning a monolith into a server after the fact is almost a rewrite. opencode’s sentence “the server is the ontology, the protocol is the product” holds because it wrote to a protocol from the first commit. Claude Code’s monolith later had to patch “being integrated” (SDK, ACP bridge are all later adds), but its single-machine experience is still the benchmark—architectural advancedness and product completeness are two different things.
4.3 “First-party dividend” and “provider-agnostic tax”: a technical bill you can actually add up
The most technical section of Chapter 4. What did Claude Code locking Anthropic actually buy, and what tax did the other six houses pay for “model freedom”? This is not a slogan; it is a bill you can point to, line by line, in source.
Claude Code’s first-party dividend (these optimizations cannot even be written in a multi-provider architecture):
| Dividend | Mechanism | Evidence |
|---|---|---|
| Billing/QoS inside job | x-anthropic-billing-header carries cc_version/cc_entrypoint/cc_workload |
constants/system.ts:60 |
| 1-hour prompt cache | Subscription and not overage + remote gate on, session latch against mid-flight bust | claude.ts:358 |
| Requests traceable | CLIENT_REQUEST_ID sent only to first-party URLs; on timeout you can match server logs |
claude.ts:1810 |
| Internal effort override | anthropic_internal.effort_override is an ant-only field |
claude.ts:458 |
| ant-only server tools | advisor / connector_text / research fields | claude.ts:2003 |
| Signed thinking echoed byte-for-byte | Accumulate signature_delta, not counted in output length, peel signatures when swapping keys | messages.ts:3078 |
| Employee debug whole-tree | USER_TYPE=ant can mock rate limit, dump system prompt |
withRetry.ts:201 |
Together these are a concrete of “depth”: when you serve only one API, you can inside-job with it down to billing headers, cache scope, internal fields. ToolSearch’s defer_loading depending on Anthropic’s unique tool_reference capability is the same logic.
The other six houses’ “provider-agnostic tax” (equally pointable in source):
- Adapter tax: opencode’s
provider/transform.tstwo-thousand-plus lines, all absorbing each house’s model quirks (who supports which effort grade, Copilot’s billing fields, Azure’s URL modes). nanobot instead feeds ~40-house registry itself—this is a tax that can never be finished writing—every new provider is one more conditional branch. - Least-common-denominator compromise: when some API has a unique capability (say Anthropic’s signed thinking), a multi-provider architecture either gives it up or writes a pile of special cases. goose, to accommodate Anthropic’s “duplicate signed block gets 400” rule, has to hang thinking on the tool-call (
agent.rs:2346)—forced accommodation. - Crutch cost: goose, meeting a small model that does not support native tool calling, has to write
toolshimusing an interpreter to hand it a “crutch” (providers/toolshim.rs:1), accumulate the full payload then parse, and also turn off true streaming. This is the implementation tax paid for “any model can be used.”
How each house thinks: the conclusion of this bill is—lock-and-go-deep and agnostic-and-go-wide are two depths that cannot both be had. Claude Code’s cache engineering, ToolSearch, silky fallback, are all grindable only because “we serve only one house”; the cost is fate-binding (vendor raises prices, rate-limits, replaces a generation, the user has no exit) and hard exclusion of non-Anthropic users. The six that go wide buy user freedom and bargaining power (local models can even keep data on-machine), at the cost of those two never-finished taxes. Interestingly every house is quietly leaning toward the other side: opencode built its own Zen model gateway (wanting a bit of “depth” back), Claude Code also added SDK/bridge (wanting a bit of “width”). In the long run, whoever first makes “near-first-party depth optimization” inside a “provider-agnostic” architecture can eat both sides’ dividends—this is the next real technical highland of the model-agnostic camp.
4.4 Open-source governance form will reverse-shape the code
The last easily ignored product logic: who the project belongs to, how people collaborate, seeps into code style.
- Foundation governance → type contracts and documentation discipline. After goose was donated to AAIF, governance is written in
GOVERNANCE.md(Contributors → Maintainers → Core Maintainers, deadlocks broken by founders); in code this shows as strong protocol boundaries (ACP + codegen type sync) and the value “most hackable.” Neutral governance forces it to make interfaces normative, because they must be used by contributors worldwide. - Pure community + AI assistance → error-notebook and compatibility-first. CodeWhale’s
.mailmapmaps Claude/Copilot/Cursor/Qwen bot commits onto maintainer names;CONTRIBUTORS.mdemphasizes harvest PRs (even if they cannot be merged as-is, keep the attribution). This “AI participates in development” reality, together with its#issue-comment error-notebook culture—because contributors flow, AI will re-commit old mistakes, writing the pit into a comment is the only defense. It is compatible with six houses’ skill directories, recognizes everyone’sAGENTS.md/CLAUDE.md, also the product logic of “a community project uses compatibility to buy users.” - Company-led pre-alpha → honest stubs. Raven is led by EverMind, forked from nanobot, still pre-alpha, so the code is full of honest labels:
/yolostubs honestly markedsupported: falserather than deleted (_stubs.py:40),NOTICES.mdeven records the evaluation conditions for “when we would abandon this hermes fork,”CONTEXT.mdannotates every term with “what not to call it.” This “honest to the teeth” engineering culture is the product posture of “a company must be responsible for an open-source project that is not yet mature.” - Academic open source → readable core and design iron laws. nanobot writes "Less structure, more intelligence" and “keep the core small” into
.agent/design.md, preferring channels/providers to duplicate rather than abstract too early—this is the natural choice of a lab project facing student contributors: the code must be readable, changeable, fully self-contained. - Closed-source first-party → gates as product boundary. Claude Code’s
feature()/ GrowthBook gating,USER_TYPE=antwhole-tree, telemetry migrating from Statsig to GrowthBook dual-cache (growthbook.ts:792), is essentially the inevitable form of a closed-source product that must “serve a sea of users, still leave employees a debug back door, still remotely tune experiments.” Many of its “implicit product boundaries” hide at thesefeature()call sites.
How each house thinks: governance form and code form are isomorphic. A neutral foundation births protocolization, a pure community births compatibility and an error-notebook, company leadership births honest stubs or gates, academic open source births readable iron laws. Read an open-source agent’s code style and you can almost reverse-derive what kind of organization sits behind it.
flowchart LR
T["团队基因
谁出钱"] --> B["商业逻辑
靠什么活"]
B --> M["模型策略
锁定 vs 无关"]
B --> A["架构重心
终端/IM/服务器"]
G["治理形态
公司/社区/基金会"] --> S["代码风格
门控/错题本/协议/残桩"]
M & A & S --> R["最终技术形态
= 产品逻辑的下游"]
style R fill:#e8f5e9
5. Review: idea duels and trade-offs on important features
The first two chapters are “side-by-side comparison” (Chapter 2 on product dimensions, Chapter 3 on technical implementation; Chapter 4 made the causality of product logic and technical form clear). This chapter changes viewpoint: pick the five most important features, and watch seven houses’ idea duels on the same question. Each point unfolds in the same rhythm: how each house thinks → comparison of solutions → pros and cons → our evaluation (who it suits).
Duel 1: the permission-model route war—the trade-off of safety versus interruption
How each house thinks: every agent faces the same horror story—the model may execute rm -rf, may send secrets onto the net, may be injected instructions by a malicious page. The question is: where is the gate set, and who stands it. Claude Code trusts no prior judgment; dangerous operations always give a human a chance to veto; opencode and hermes trust “the law the user wrote in advance”; CodeWhale simply structurally eliminates the possibility (if there is no tool that can overreach, what is there to approve); nanobot / Raven admit that in always-on IM there is simply no “person” to press a button, and hand trust to isolation.
Comparison of solutions: popup-ask (Claude Code five-gate verdict + fallback popup) → rules-first (opencode three-state globs, hermes three-level whitelist + YOLO frozen on import) → structural safety (CodeWhale capability tags derive approval, Plan does not register write tools, constitution.json only adds locks never grants power, provenance downgrade; goose’s Chat forbidding tools and readOnlyHint also count as a lightweight edition of this school) → sandbox isolation (nanobot blacklist + workspace + SSRF + bwrap; Raven upgrades boxlite microVM; hermes six backends pick isolation by task) → LLM classifier (grok-build: PermissionClassifier judges risk in real time, Auto mode 3/20 cumulative cap then fallback popup) → out-of-band approval / inbox hang (OpenWorker: needs_user does not sit waiting in the loop, it turns the request into an idempotent inbox item and hangs the turn—attended inline card, unattended into a cross-session inbox, first answer from any UI takes effect, after restart resume does not double-run; plus §25 standard rules, connectors skip ask by exact target, shell always asks).
Pros and cons: the popup school’s sense of control is strongest, but interruption cost grows linearly with usage time, and it depends on “a person in front of the machine”—Claude Code when a remote channel is active simply disables ask tools (a popup would hang the session dead). The rules-first school amortizes cost onto config; a skilled user is almost zero-interrupted, but a new user has nowhere to start, and rules always lag new threats. The structural-safety school is once-and-for-all, auditable, highest safety ceiling, but lowest flexibility—it answers “can it,” not “should it this time.” The sandbox school is the only correct answer for unattended, but a blacklist is destined best-effort; a misconfigured isolation boundary is running naked. The LLM-classifier school intelligentizes approval itself, but introduces a new attack surface—the classifier may be fooled by adversarial samples, and still needs a cap as backstop.
Our evaluation (who it suits): this is not a single-choice question; it is a spectrum. High-frequency interactive coding scenes, Claude Code’s popup school has the best experience—its “always allow” precipitates each of your approvals into rules, the more you use it the closer to the rules school; automation/CI scenes, rules-first + structural safety is the correct answer (CodeWhale’s execpolicy + constitution combo is designed for pipelines); always-on IM assistant / unattended, the sandbox school is the only choice that does not self-deceive (for a light Python factory setting read nanobot, for a microVM upgrade read Raven); when you run an agent on a strange codebase, the structural-safety school (Plan mode does not register write tools) gives you protection the prompt school can never give; grok-build’s LLM-classifier school is the most exploratory route, worth taking as a research sample of “next-generation permission systems.” And OpenWorker’s “inbox-hang school” is the answer to the new scene “unattended + a person can take over at any time”—it does not chase making approval intelligent; it makes approval a durable, answerable from any UI, restart-does-not-lose queue, so the same run seamlessly switches between “a person is here” and “a person is not”; plus “external risk skip-ask by exact target, shell always asks” §25 grading, it dares let an agent unattended send Slack and change calendars for you—a question no other house’s permission model has faced head-on.
quadrantChart
title 权限路线象限:打扰成本 × 安全上限
x-axis 低打扰 --> 高打扰
y-axis 防呆(防误操作) --> 防恶意(防注入/越权)
quadrant-1 重兵把守
quadrant-2 严防死守但最烦
quadrant-3 佛系
quadrant-4 省心但防不住高手
"结构安全派 CodeWhale": [0.35, 0.85]
"沙箱派 nanobot/Raven": [0.15, 0.7]
"弹窗派 Claude Code": [0.8, 0.75]
"规则派 opencode/hermes": [0.5, 0.5]
"goose SmartApprove": [0.45, 0.6]
"收件箱挂起派 OpenWorker": [0.3, 0.68]
Duel 2: two philosophies of context engineering—token cost versus information fidelity
How each house thinks: the context window is the agent’s only “workbench”; tool results pile up like parcels; something always has to be thrown. The summary-and-discard school (six houses including nanobot) holds: the value of old detail decays exponentially, spending tokens on fidelity is not worth it—concentrating into gist is enough, the workbench is freed. The lossless school (Raven) holds: the facts squeezed out are precisely what long tasks need—if “goals, pending topics, decisions already made” are lost, the agent will redo work or even contradict itself, so compaction should be “archive + cite + distill and re-inject,” not “throw away.”
Comparison of solutions: inside the summary school there are again three grades of fineness—the plainest is hermes/nanobot’s “auxiliary model writes minutes + micro-compact”; the middle is Claude Code’s “fork clone + 9-section template + 413 passive rescue” and goose’s “invisible marks + bridging lines”; the finest is opencode (tail keep constrained by token budget, can cut from the middle of a turn, overflow-type replays the last user message) and CodeWhale (zero-cost mechanical trim first, pin the working set, on failure prefer not to compact). The lossless school has only Raven’s Curator: the small model only looks at a Manifest index and produces a plan; landing is executed and validated by deterministic code; on error there is still a Fail-Safe that does not depend on an LLM at all—it even defends against “the small model is unreliable” (and Curator is exactly an upgrade of nanobot’s lossy Consolidator path).
Pros and cons: the summary school wins on simple, cheap, battle-tested—each house’s implementation has already been polished in large-scale real use; the defect is “a summary is lossy”: the file paths, error stacks, intermediate conclusions that were compacted may be exactly what you need three hours later; and compaction itself busts cache (CodeWhale is therefore especially stingy with compaction). The lossless school wins on fidelity and auditability (Archive loses not one character, Working State is readable and checkable), at the cost of extra money for a small model, significantly higher system complexity, and the benefit only cashes in on “long enough tasks”—in a short session its Fast Path is zero LLM calls, equivalent to carrying this complexity for nothing.
Our evaluation (who it suits): the summary school represents reality; the lossless school represents direction. On today’s main battlefield (coding tasks within dozens of turns), fine-grade summary (CodeWhale/opencode’s rank) is already good enough, unbeatable cost-performance; and when agents walk toward “unattended tasks that run for hours,” the problem Raven posed will become everyone’s problem—the houses are in fact quietly leaning toward “lossless”: Claude Code’s tool results landing on disk is essentially a prototype of “archive + cite.” Advice for beginners: first read and understand the summary school (the standard answer of the stock world; nanobot’s micro-compact + Dream is a light readable sample), then take Curator as the best sample for understanding next-generation context engineering.
Duel 3: openness of the extension ecosystem—the trade-off of standardization versus experience
How each house thinks: no team can write every tool the world needs, so the question becomes: in what shape does third-party capability come in. goose’s answer is the purest: everything is MCP, even built-in capability walks the protocol, the whole ecosystem has only one socket standard. Claude Code’s answer is the most “product manager”: MCP handles tool ingest, but above that they still build a plugin marketplace (git repo + sparse clone + official market), holding “discover, install, manage” experience in their own hands. hermes’s answer is the most “platform”: everything is a plugin—messaging platforms are plugins, model vendors are plugins, kanban is a plugin, the core only does the loop. opencode’s answer is the most “programmer”: open Hooks to the bottom (even permission ask, request params, shell environment are hookable), then compatible with Claude Code’s skills and memory files to lower migration cost. CodeWhale makes “compatibility” the strategy itself (skill directories scan six houses). nanobot uses entry_points + channel plugins + ClawHub to keep the core small. Raven deliberately narrows: at pre-alpha only two contribution points are open.
Pros and cons: everything-is-MCP (goose) architecture most unified, ecosystem ceiling highest (MCP servers already in the world plug and play), the cost is one process per extension overhead and failure surface—goose itself had to invent Builtin/Platform two “cheat” shapes to claw performance back. Own marketplace (Claude Code) experience best, distribution strongest, but the ecosystem is bound under a single vendor’s governance. Plugins/Hooks fully open (hermes/opencode) flexibility highest, but plugin-API stability is a long-term burden—every category of hook opened is a contract to maintain for ten years. Skill files (seven-house consensus) are the smartest compromise: no code written, only tokens occupied, progressive disclosure, eating the “user customization” dividend at lowest cost.
Our evaluation (who it suits): standardization decides the ecosystem’s floor; own systems decide experience’s ceiling. If you are an ordinary user, Claude Code’s marketplace + skills is the lowest onboarding cost; if you are an enterprise/team, goose’s MCP + recipe combo is the best deal—recipe lets “best practice” be reviewed, shared, cron’d like a script, the only design among the seven that made “workflow” a circulating asset; if you are a developer who wants to build ecosystem, first write an MCP server (one investment, seven houses general), then write a plugin for a specific host (eat the experience dividend). One sentence: writing an MCP server is “a deposit”; writing a host plugin is “wealth management.”
Duel 4: architectural shape—terminal program vs local infrastructure
How each house thinks: this question is essentially “is your agent a product, or a kind of infrastructure.” Claude Code and CodeWhale answer “product”: one process (or a pair of binaries) takes experience to the extreme; between UI and core even a protocol is not needed. opencode and goose answer “infrastructure”: the agent is a local server; TUI/GUI/editor/CI are all only clients—opencode even lets the same-process TUI walk the full HTTP protocol, the reason being “local single-machine and remote team should be the same code path.” hermes, nanobot and Raven’s answer sits between: the core is a “brain service,” but the client’s protagonist is not an editor, it is messaging platforms (Telegram / WeChat / Feishu); the agent is a member of your address book, not a tool you open—nanobot uses MessageBus to decouple storefront from back shop, Raven upgrades that to Spine.
Pros and cons: a terminal program (monolith) iterates fastest, debugs most intuitively, performance loss smallest; Claude Code can take “ESC emergency-brake all the way to the wire” to this precision because the whole chain is same-process; the cost is “being integrated” needs after-the-fact makeup (SDK, ACP, bridge are all later adds). The dividend of server/gateway shape is build once, plug in everywhere: goose’s desktop and Zed ride the same goose; nanobot / Raven’s dozen IM platforms share one bus or one Spine. The cost is equally real: the protocol must be maintained for life (Raven’s message model alone is 911 lines), process management adds a failure surface (goose must give its own GUI a SECRET_KEY against impersonated access), single-machine experience may instead lose to a monolith.
Our evaluation (who it suits): this choice is almost impossible to have both, and is best thought through on day one—the sentence in opencode’s analysis “the server is the ontology, the protocol is the product” holds because from the first commit it wrote code to a protocol; after-the-fact transforming a monolith into a server equals a rewrite. An extreme tool for individual developers, learn Claude Code; a platform for teams/ecosystems, learn opencode and goose; an always-on assistant / message gateway, learn hermes, nanobot and Raven’s gateway idea (to read the lightest Python core, prefer nanobot). An observation for beginners: serverization/gatewayization is the large trend, but the monolith Claude Code is still the experience benchmark—architectural advancedness and product completeness are two different things.
Duel 5: model strategy—lock a single vendor and go deep vs provider-agnostic and go wide
How each house thinks: Claude Code and grok-build both bet “a model’s distinctiveness is worth using in depth”: CC’s ToolSearch depends on Anthropic’s unique tool_reference, cache scope and billing headers inside-job; grok-build locks xAI Grok, multimedia tools (image gen/edit/video) deep-bound to xAI-only capabilities, Goal Mode five-piece kit designed around Grok’s reasoning depth—these optimizations cannot even be written in a multi-provider architecture. Seven open-source projects bet “the model is a part, not the product” (CodeWhale README original): the user should have the freedom to vote with their feet; the agent’s value is in the loop, tools, and engineering, not in binding which brain.
Comparison of solutions: the deep representatives are Claude Code (making an essay of every Anthropic API feature) and grok-build (Goal Mode + multimedia tools designed around Grok in depth); among the wide representatives, opencode uses provider/transform.ts two-thousand-plus lines to absorb each house’s model quirks, hermes uses “customize the prompt by model family” to dissolve differences (GPT family given patch format, Claude/Gemini family given replace format), goose uses toolshim to hand a “crutch” to small models that do not support native tool calling, CodeWhale lets 36 providers run the same runtime and toolset, nanobot uses a home-grown ~40-house registry talking official SDKs directly (Raven outsources to LiteLLM), OpenWorker sits directly on Andrew Ng’s team’s aisuite—outsourcing the whole of “unified provider + each call adapts PDF/vision by current model capability, mid-session model switch only changes a field” to this abstraction layer, writing not one line of quirk code itself.
Pros and cons: lock-and-go-deep’s optimization depth is irreplaceable—prompt-cache engineering, ToolSearch, silky model-fallback switching, all grindable only because “we serve only one house”; the cost is fate-binding: vendor raises prices, rate-limits, model generation changes, the user has no exit, and non-house-model users are hard-excluded. Note that the “lock-and-go-deep” camp is now two houses: Anthropic-direction CC and xAI-direction grok-build, each taking their house model’s unique capabilities to the extreme. Go-wide’s user freedom and bargaining power are largest (local models can even keep data on-machine), but two taxes must be paid: one, adapter tax (thousands of lines of quirk code like transform.ts / home-grown registry, and never finished writing), two, least-common-denominator compromise—when some API has a unique capability, a multi-provider architecture either gives it up or writes a pile of conditionals; optimization depth is naturally limited.
Our evaluation (who it suits): if you are a heavy Claude user, there is nothing to agonize over—Claude Code has ground every detail around your model down to the bone. If you are a heavy Grok user and need multimedia generation or hours-scale long autonomous tasks, grok-build’s Goal Mode is currently the most mature explicit-state-machine scheme. If you are cost-sensitive, want free combination (cheap models for chores, strong models for hard attacks), or must privatize deploy, provider-agnostic is a hard need: for engineering rigor pick opencode, for local models pick hermes/CodeWhale/goose; to study “how to write an abstraction layer that is not cramped in a multi-model era,” transform.ts, hermes’s model-family custom prompts, and nanobot’s home-grown registry vs Raven’s LiteLLM split, are all the best textbooks. In the long run, whoever in the “go wide” camp first solves the contradiction of “depth optimization” versus “provider-agnostic” can eat both sides’ dividends.
6. Selection advice for beginners
Having read nine analyses and the comparison above, the last practical question: which project should I take, to do what? Answered by five classes of need (the fifth class is the “non-coding” new scene OpenWorker represents).
Want to understand agent principles, whose source to read? Recommended path: first the Claude Code deep dive (the main line is clearest; loop/permissions/compaction the three big pieces are told thoroughly) → then CodeWhale (an error-notebook with issue numbers; see how many dirty problems a production agent has to handle) → split by interest: for rigor read opencode (boundary-handling density nobody matches), for modern design read Raven (Curator/Spine/dual-track memory), for protocol read goose (engine-as-server + everything-is-MCP), want a light Python core / Raven’s upstream factory settings → nanobot (MessageBus + explicit state machine, core readable). Want to see Goal Mode explicit state machine and heavy Rust engineering → grok-build (GoalTracker/GoalPlanner/GoalWorker three sets of source are the best textbook for an autonomous agent). hermes’s nearly 5500-line single-function loop is the hard-man route; leave it last.
Want to write code daily, use whom? Have a Claude subscription: use Claude Code directly, the experience benchmark, no substitute. Have a Grok subscription / need image gen, video gen, or hours of autonomous execution: grok-build (Goal Mode + multimedia tools are unique capabilities). Want free model switching or cost control: opencode (TUI experience finest, model switching smoothest) or CodeWhale (Rust single binary, cache telemetry visible, auto-route saves worry). Want a GUI and shareable automated workflows: goose (desktop + recipe + cron). Want an always-on assistant that lives in Telegram/WeChat: hermes, nanobot or Raven (nanobot lighter, more self-contained; Raven heavier on memory/proactivity).
Want to secondary-develop (build your own agent or plugin), pick whom? Only want to extend capability: first write an MCP server, eight houses general, one investment usable everywhere. Want to deep-customize a host: opencode (Hooks categories fullest, MIT license, Effect architecture suits long-term evolution) or hermes (Python onboarding fast, everything is a plugin); want to first eat through a light Python kernel then fork, pick nanobot (design iron laws clear, MessageBus readable); want to do multi-agent / scheduling / memory-direction research, Raven’s substrate design (Spine + engine pluginized) is the clearest starting point, but you must accept its pre-alpha reality. Want to do heavy engineering in Rust: CodeWhale and goose are two ready roads; want to study Goal Mode state machines and the ACP protocol: grok-build (Apache-2.0, does not accept PRs but the code is readable).
Want local models / data stays on-machine, pick whom? Three houses are first-class citizens: hermes (custom provider connects vLLM/SGLang/llama.cpp, plus own Hermes-series open-weight models as companions), CodeWhale (vLLM/SGLang/Ollama no key, AGENTS.md directly gives intranet-deploy commands), goose (Ollama + built-in llama.cpp/candle runtime, goose local-models one-stop download). nanobot can connect local services via OpenAI-compatible endpoints, but does not make a local-model runtime its headline. OpenWorker also writes “local-first + Ollama fully local” into product position (data only leaves via the model/integration you chose), for people who “want a digital colleague and want data on-machine.” Claude Code, opencode and grok-build do not make local models their headline; this scene is not recommended.
Want an unattended, cross-app “digital colleague” (not a coding tool), use whom? This is a scene none of the first eight faced head-on—the answer is OpenWorker. It is not optimized for writing code, but for “finishing daily work like a colleague”: Slack (@ it and it works in a desktop session, replies in the thread), cron (morning brief / weekly / watch a channel), 40 connectors (GitHub/Jira/Notion/Gmail/calendar…), ask before acting, deliver finished files when done, and can be fully local. The three things most worth learning: inbox (when unattended, safely stop an ask and wait for you, restart does not lose), durable resume (after a turn hangs, restart can still continue), §25 standard rules (dare let it unattended send messages for you but never overreach opening a shell). To read a complete engineering sample of “a general-task agent rather than a coding agent,” it is currently the one in open source that has thought this through most fully.
flowchart TD
Q1{"你的首要目标?"} --> A["读懂 agent 原理"]
Q1 --> B["日常写代码"]
Q1 --> C["二次开发"]
Q1 --> D["本地 / 私有模型"]
A --> A1["① Claude Code 深入版(主线最清晰)
② CodeWhale(带 issue 编号的错题本)
③ 按兴趣:opencode 严谨 / Raven 现代 / goose 协议 / nanobot 轻量 Python / grok-build Goal Mode"]
B --> B1{"订阅类型?"}
B1 -->|Claude 订阅| B2["Claude Code(体验标杆)"]
B1 -->|Grok 订阅 / 多媒体 / 长时自主| B2b["grok-build(Goal Mode + 多媒体)"]
B1 -->|没有或想多模型| B3["opencode / CodeWhale / goose(GUI)"]
C --> C1{"做什么层面的开发?"}
C1 --> C2["扩展能力:先写 MCP 服务器(七家通用)
定制宿主:opencode(Hooks 全)/ hermes(Python 友好)
轻量内核:nanobot;研究多 agent 与记忆:Raven"]
D --> D1["hermes / CodeWhale / goose
(vLLM / Ollama / llama.cpp 均为一等公民)"]
If you remember only one thing: each of the nine projects has one sentence worth taking away—
mindmap
root((九家各记住一件事))
Claude Code
围绕单一模型,把循环/权限/缓存/生态每个细节磨到极致的教科书
opencode
服务器即本体,协议即产品;用 Effect 的严谨赌长期主义
hermes-agent
前缀缓存神圣;闭环学习不是口号而是代码路径
Raven
上下文不是消耗品,是被管理的资产
CodeWhale
安全是机制,不是承诺:模型根本没有越权的工具
goose
一切皆 MCP 扩展,引擎即服务器:agent 是本机基础设施
nanobot
核心保持小、能力在边缘;住在聊天软件里、可完全自持
grok-build
Goal Mode 五件套显式状态机让 agent 在数小时级长任务里自主坚持,Rust 单二进制 + ACP 协议是壁垒
OpenWorker
收件箱 + 持久恢复:能无人值守跑、要人时挂起等你、重启不丢——把审批做成一条可从任意界面回答的队列
And behind these nine sentences is the same larger sentence: an agent’s core technology has long not been a secret—model + tools + loop, everyone can write it; what opens the gap is the ring of systems around that heart: the permission gate, context as asset management, cache accounting, memory’s time scale, the ecosystem’s open posture. The first eight houses gave eight answers on the “coding” track; OpenWorker moved the same heartbeat onto the new track of “general-task colleague”—proving this engineering map does not belong only to coding agents. Once you have seen the nine houses’ different answers on these questions, you have seen the whole 2026 AI-agent engineering map.
7. The tenth sample: Open Design—when the “host” sits at the table
The facts in this chapter rest on open-design source analysis (18 chapters, against commit
c893b60). It gets its own chapter rather than being folded into the first six chapters’ tables because it and the first nine are not like-for-like comparable objects: the first nine compete on “whose agent is better”; Open Design asks “these agents are already good; how do we make them together do a job they were not originally good at.”
7.1 What it actually is: one sentence and three numbers
Open Design (hereafter OD) is the open-source stand-in for Claude Design. Anthropic’s April 2026 Claude Design was the first time a large model directly delivered finished design artifacts, but it is closed-source, pure-cloud, locked to Anthropic’s models and skills. OD turns the same agent-native loop—discover the brief → lock a direction → stream output → review → deliver—into a filesystem any CLI agent can read.
| Number | Value | Note |
|---|---|---|
| stars / time | 82.0k / 3 months | Created 2026-04-28; at writing 82k stars, 9.5k forks |
| repo scale | 11 745 files / 321.9 MB | TS 2 341 · HTML 2 493 · MD 2 246 |
| CLIs supported | multiple runtime defs (exact count moves with version; at analysis commit c893b60, runtimes/defs/ contains 8 .ts definition files) |
byok-opencode reuses the OpenCode executable |
| Main loop implemented by itself | 0 | All delegated to the hosted CLI |
| Prompt assembler | 2 075 lines | 25× larger than the runtime registry (81 lines) |
| Aesthetic linter | 1 000 lines / 16 rules | Unique in this series |
7.2 One table to see how far its coordinates sit from the first nine
| Dimension | First nine (coding / task agents) | Open Design (host) |
|---|---|---|
| Main loop | Each implements its own (8 000–20 000 line scale) | Does not implement; delegates to 25 CLIs |
| Tool system | Defines 20–74 tools itself | Zero; uses the hosted CLI’s native tools |
| Permission model | From CodeWhale’s safe-by-construction to OpenWorker’s five modes + inbox | No execution-layer gate; all delegated + boundary defense |
| Context engineering | Compact, prune, dual-track memory, Curator lossless | Layered assembly + cache bands by change frequency (20+ layers) |
| Quality assurance | Tests pass / compile passes / tool calls correct | Aesthetic linter + five-juror review theater |
| Extension mechanism | Plugins / MCP / skills | Four planes: skills + design-templates + design-systems + craft |
| Deliverable | Code changes | HTML / PDF / PPTX / MP4 real files |
| Relation to the first nine | Competitors of each other | Treats at least 5 of them (claude / opencode / hermes / grok-build / deepseek) as its own execution engines |
🧠 The most interesting point: CodeWhale (i.e. DeepSeek TUI, before the upstream rename), hermes-agent, opencode, grok-build, all analyzed in this series, all appear in OD’s
runtimes/defs/directory, each an object literal of a few hundred bytes to a few KB. Those tens-of-thousands-of-lines agents you have read, in OD’s eyes, are one argv-build rule plus one stream-format tag.
7.3 Three designs none of the first nine have
① Adapter-as-data: press the cost of “supporting N CLIs” from O(N) to O(1)
Among the first nine, whoever has to connect multiple backends (goose’s 52+ providers, nanobot’s ~40-house home-grown registry, Raven’s LiteLLM) is handling model providers—the protocol is highly homogeneous (all chat-completions). OD has to connect complete agent CLIs: 25 different argv conventions, 7 stdout formats, 3 session-resume mechanisms, permission flags all different.
Its solution is to write adapters as pure data objects (runtimes/types.ts:101-253, ~45 fields):
An adapter is not a class that implements the agent loop. It is a plain data object… There is no per-agent subclass and no
run()/cancel()method to implement.
Of the 45 fields only buildArgs is a function, and it is pure (input prompt and options, output argv array). Probe, start, invoke, cancel, parse all live in the shared engine. Result:
- Adding a CLI of a known wire format = one object literal + one line in the registry; the smallest def is only hundreds of bytes
- Nine ACP adapters share the same transport
- Zero per-agent branches in the engine
Contrast goose’s “everything is an MCP extension”: goose unifies all capability into one protocol, at the cost that the outside world must first adapt to MCP; OD is the reverse—accept the outside world’s heterogeneity, absorb the difference with one layer of data spec. The former asks the ecosystem to accommodate itself; the latter accommodates the ecosystem.
② Turn “aesthetic quality” into a programmatic gate
This is a capability unique in this series. The first nine’s quality gates are all functional: tests pass, compile passes, tool-call format correct, P0 checklist all green. What OD does is “does this thing look like AI slop”:
Layer 1 lint-artifact.ts (1 000 lines / 16 rules)—drop subjective judgment into executable code:
| Category | How specific |
|---|---|
| Default Tailwind indigo as accent | Precise to 7 hexes |
| Two-stop “trust gradient” | Blue-family 13 × cyan-family 8 hex pairings |
| emoji as functional icons | 17 emoji × 4 contexts |
| ALL CAPS tracking insufficient | With CSS variable recursive eval (depth 4) + theme-scope resolution + font-size conversion |
The last is where the craft shows: letter-spacing: 1px is enough on 12px type (0.083em), far from enough on 48px type (0.021em), so font-size in the same rule must be converted; letter-spacing: var(--caps-tracking) must pierce token evaluation; light theme 0.08em, dark theme overridden to 0.02em must be judged separately. There is also a comment that is a textbook case of regex engineering—changing the character class from [^}]* to [^{}]* (match only the innermost rule), otherwise ALL-CAPS headings inside @media would miss the whole block.
Layer 2 Design Jury—five jurors (Designer / Critic / Brand / Accessibility / Copy) weighted composite, ships only at ≥8.0/10, at most 3 rounds. Two design decisions worth remembering:
- Designer weight is 0: “their dimensions are aesthetic preferences rather than ship gates”—keep the seat so qualitative opinion enters the record, but do not let subjective aesthetics block ship;
- Five jurors = five turns of the same CLI session, not five processes—“prevents the ‘panelist disagrees with itself across processes’ failure mode,” and auth/env/logs do not need five copies.
And neither layer hard-blocks: a P0 hit does not stop landing on disk; a review that misses the bar walks ship_best. This is the mature trade-off of “there is a gate, but it does not lock the user dead.”
③ Prompt cache-band ordering: take hermes’s “prefix is sacred” one step further
hermes-agent’s signature is prefix cache is sacred—never insert content in the middle of an already-cached prefix. OD on top of that does three more things:
- Tag every segment by change frequency, split into four bands: globally static (designer charter) → session-stable (mode/locale) → project-stable (design system/skills/metadata) → turn-volatile (deck/media signal-triggered blocks);
- The stability of the trigger signal decides the block’s position, not the importance of the content. The same “platform-contract block”: triggered by metadata fixed at project creation → project-stable band; triggered by conversation text (the user mid-flight says “change it to the iOS edition”) → must be pushed to the turn-volatile suffix, because “an early insert would break the cached prefix for every section after this line”;
- Cache-hit attribution:
describeStablePromptCache()on a miss diffs segment by segment which one drifted, and deliberately does not report full-set change in a “no baseline” situation—that would drown the signal you actually care about.
7.4 Its cost: three trade-offs heavier than the first nine
Trade-off 1: actively giving up the permission gate (unique in this series)
| Project | Execution-layer permission model |
|---|---|
| CodeWhale | safe by construction—the model simply has no tool that can overreach |
| nanobot | bwrap sandbox |
| OpenWorker | five modes + inbox hang + shell injection defense |
| Claude Code | permission gate + hooks |
| Open Design | None—feeds every CLI the most dangerous non-interactive flag |
What OD feeds child processes: --permission-mode bypassPermissions (Claude) · --force (Cursor) · --permission-mode dangerous (Devin) · --yolo (Qoder/Trae) · --allow-all-tools (Copilot) · --auto (DeepSeek) · --dangerously-allow-all (Amp).
The reason is self-consistent: the daemon runs them in an environment with no TTY; an interactive approval prompt would hang the run dead. The docs are also frank: “The effective project cwd is an execution root, not a uniform Open Design sandbox… users must treat these runs as trusted agent execution.”
Its security budget is all spent on the boundary (loopback default + SSRF exact-host allowlist + short-lived single-use HMAC for desktop import + sandboxed iframe + .od-skills real copy rather than symlink), not on execution. Reasonable for personal local use; for team shared deploy, needs a very serious evaluation.
Trade-off 2: two prompt assemblers must stay byte-sync
Daemon-side prompts/system.ts (2 075 lines) and contracts-side (1 064 lines) are two independently implemented copies of the same semantics—the former serves local CLI execution, the latter serves BYOK direct connect. The repo has at least six comments reminding “keep both in sync” “keep this whole block BYTE-IDENTICAL.” This is known and explicitly managed technical debt, but it is real.
Trade-off 3: the content library’s volume itself is a maintenance burden
151 design-system packages × at least 3 files each, 164 skills, 115 templates, 277 official plugins, 183 examples—2 246 Markdown, 2 493 HTML, 322 MB. Quality guards are written in quantity (pnpm guard, pnpm lint:craft, package quality guards, derived-file consistency, token contracts, component fixtures, source evidence, preview coverage), but the speed of content rot and the speed of content growth are the same order of magnitude.
7.5 Updated position quadrant
quadrantChart
title 十大 Agent 定位象限(Open Design 独占「宿主」区)
x-axis 锁定单一厂商 --> provider 无关
y-axis 终端程序 --> 本地基础设施
quadrant-1 开放平台型
quadrant-2 深度绑定的基础设施
quadrant-3 单一厂商的极致工具
quadrant-4 开放的终端工具
"Claude Code": [0.12, 0.18]
"grok-build": [0.10, 0.25]
"CodeWhale": [0.72, 0.22]
"opencode": [0.85, 0.68]
"hermes-agent": [0.80, 0.78]
"Raven": [0.68, 0.82]
"nanobot": [0.75, 0.72]
"goose": [0.90, 0.88]
"OpenWorker": [0.82, 0.58]
"Open Design": [0.96, 0.94]
Open Design lands in the far upper-right, and its “provider-agnostic” is agnostic of another order of magnitude: the other nine are agnostic of models; OD is agnostic of the entire agent.
7.6 Duel 6: “build the engine” or “build the socket”
Chapter 5 told five duels (permission route / context philosophy / ecosystem openness / architectural shape / model strategy). After Open Design joins, a sixth appears, and it is more fundamental than the first five.
| Engine-building school (first nine) | Socket-building school (Open Design) | |
|---|---|---|
| Claim | The agent loop is the product’s core; you must own it | The loop is already a mature commodity; the moat is outside it |
| Representatives | Claude Code · goose · hermes · CodeWhale · OpenWorker | Open Design |
| Advantage | Full control of reasoning quality, permissions, cost, experience consistency | Marginal cost extremely low; the user’s already-strongest agent is your engine; energy all spent on domain knowledge |
| Disadvantage | Your loop is probably not as good as the one a full-time team spent a year on; every provider/protocol change you have to follow | You cannot control how it thinks; the permission model is gone; you have to handle N kinds of stdout; if the user has not installed the CLI it cannot run |
| Where the moat sits | The loop itself + ecosystem | Content library + prompt engineering + quality gates |
| Who it suits | Core competitiveness is reasoning orchestration; facing ordinary users who do not know tech | Product value is in content and experience (design/writing/reports); the user already has a CLI agent |
To judge which of these two roads is right, one number is enough: OD’s adapter layer is only ~3 000 lines; the prompt assembler 2 000 lines + aesthetic linter 1 000 lines + 151 brand packages + 11 craft rules + 330 lines of behavior script. 80% of its engineering volume is spent on “what is good design,” only 20% on “how to call a CLI.” If your domain also has a body of “hard to put into words but can be encoded” expertise, building the socket is right; if not, what you build is only a shell.
7.7 Supplementary advice for beginners
Chapter 6 gave nine-house selection advice by audience. After adding OD, three more:
- What you want to build is a “content/experience” AI product (design, writing, data reports, PPT) → read Open Design first. It will tell you: do not start by writing the main loop.
- You are already building a coding agent and want to know how to let others integrate it → read OD’s
runtimes/defs/directory, see how others describe you. If your CLI does not support stdin-delivering the prompt, does not emit a structured stream, has no stable session-resume identifier, in this system you are “the hardest one to wait on” (see DeepSeek adapter’s triple Windows command-line guards). - You are evaluating whether to deploy a design agent for a team → first read §7.4 trade-off 1. OD is the only project in this series with no execution-layer permission gate.
8. The eleventh sample: MiMo Code—same skeleton, what Xiaomi welded on
Facts rest on MiMo Code source analysis (19 chapters, commit
076b790). It is a deep fork of this lecture’s second subject, opencode, so this chapter’s writing differs from Chapter 7: Chapter 7 was “another species”; this chapter is the delta on the same skeleton—comparability highest, and most able to show “what a team thinks a coding agent is missing.”
8.1 Origin: to read a fork, read the diff
| Evidence | Note |
|---|---|
| Core package name | packages/opencode (still not renamed) |
Root package.json name |
"opencode" |
LICENSE |
Copyright 2026 MiMo Code, Xiaomi and Copyright 2025 opencode two lines side by side |
| Inherited parts | Effect service layer · SolidJS TUI · SQLite+Drizzle · provider abstraction · ACP protocol · server mode |
This series has a precedent: Raven’s Python runtime forks nanobot, and the reading then was “look at later surgery against upstream.” MiMo × opencode is the same pattern, only the surgery amplitude is much larger—large enough to deserve a 19-chapter standalone analysis.
Scale: 5 222 files / 83.8 MB, 12.5k stars / 1.27k forks / 838 open issues, repo created 2026-06-10 (a month and a half). License MIT + a separate USE_RESTRICTIONS.md.
8.2 Its theme: do not trust the model’s self-report
Every house in this series has a sentence that can summarize its theme. MiMo’s is especially clear, because it was made into four layers, six devices:
flowchart TB
subgraph S["步级(每一步都查)"]
A1["空参数工具调用检测
软→硬 2 级"]
A2["重复动作签名
键序归一化 + 排除叙述文本"]
A3["文本复读 + n-gram
剥开场白后比对"]
end
subgraph T["回合级"]
B1["步数上限:禁工具 + 强制交代"]
end
subgraph C["会话级"]
C1["Goal 独立裁判
temperature=0 · 只读 transcript"]
C2["Try-Best 检测器"]
end
subgraph D["产物级"]
D1["checkpoint-validator + retry"]
end
S --> T --> C --> D
Depth comparison with the other ten:
| Project | Layers of doom-loop / fake-completion defense |
|---|---|
| Most projects | 1–2 layers (usually only a step cap, or plus one repeat detector) |
| Claude Code | Step cap + permission gate |
| openworker | Interrupt leaves no orphans + inbox hang |
| Open Design | Aesthetic linter + five-juror review (but that is a quality gate, not anti-self-deception) |
| MiMo Code | Step-level 3 + turn-level 1 + session-level 2 + artifact-level 1 = 7 |
8.3 Three designs none of the first ten have
① Goal independent judge: split “acceptance” and “construction” completely with two model calls
After /goal sets a stop condition, the main loop refuses to stop until an independent judge model has read the transcript and ruled the condition met (or genuinely impossible).
The judge is a separate model call that only reads the transcript — it does not do the work, so its verdict stays cold relative to the working agent's optimism. (
session/goal.ts:17-21)
The most critical sentence in the judge prompt:
the assistant claiming the goal is impossible is evidence, not proof; independently confirm the condition is genuinely unachievable rather than deferring to the assistant's self-assessment.
Without this sentence, the model only has to say “this cannot be done” and the judge lets it through; the whole mechanism is dead.
Four matching escape hatches: fail-open (“a flaky judge can never trap the user”) · impossible · MAX_GOAL_REACT = 12 · only takes effect on the main agent.
🔍 Exactly the opposite of Open Design’s Design Jury: OD’s five jurors are five turns of the same session (share context to keep consistency, save auth and logs); MiMo’s judge is an independent call, deliberately not shared—because what it wants is “cold.” Same question, two correct answers in opposite directions.
② Take “reshape by model” from a prototype to the extreme (inherited + greatly expanded)
⚠️ Attribution must be said first: family-split prompt routing and the GPT tool-gating predicate expression both come from upstream OpenCode—treating them as Xiaomi originals is the most common misread. Item-by-item evidence is in MiMo Code source analysis Chapter 20 (a real file-level diff of both sides’ source).
| Dimension | Upstream OpenCode 62e4641 |
MiMo Code 076b790 |
|---|---|---|
| Prompt families | 10 files (including same-structure if-chain routing) |
20 files (including 3 .ts code files) |
| Of which byte-level unchanged | — | 5 files (gemini 15372=15372 · kimi 8695=8695 · codex 7390=7390 · trinity differ 1B · copilot-gpt-5 differ 2B) |
| Three main prompts | gpt 9.3 KB · default 8.5 KB · anthropic 8.2 KB | 25.4 KB (2.7x) · 20.8 KB (2.4x) · 14.3 KB (1.7x) |
| Routing input | Only model.api.id |
Dual-ID fallback |
| GPT gating predicate | registry.ts:292-296 inline variable usePatch |
Not one character different, but extracted as a named function in tool/gpt.ts |
| GPT gating scope | apply_patch ↔ edit/write pick one of two |
Whole ABI replacement (further gates exec/view_image, hides read/grep/glob/multiedit/notebook_edit) |
Xiaomi’s real contribution is “walk this road to a place upstream did not walk”: add 5 families, rewrite the 3 highest-hit files, add dual-ID fallback (because its model ingest paths are far more than upstream), expand gating from “swap one screwdriver” to “swap a whole toolbox.”
The reason is aligning with the training distribution: Codex-family models in OpenAI’s own harness were trained on this ABI; giving them Claude-style tools instead makes them more likely to call wrong.
This is a frontal negation of the “universal harness” assumption:
| Claim | Representatives |
|---|---|
| Write one good prompt that every model can use | The other ten |
| Each model’s training distribution differs; the harness should reshape for it | MiMo Code (the slogan "Models and Agents Co-Evolve" means exactly this) |
The cost the docs themselves admit: prompt routing and tool routing are two independent string-rule sets, “not yet unified into a model-capability negotiation layer.” In live test gpt-oss-120b will show the inconsistency of prompt walking GPT, tool surface not GPT ABI.
③ Sandbox engine swap: acorn handwritten interpreter → QuickJS WASM (rewrite)
⚠️ The idea “run a script in a restricted sandbox to orchestrate tools” also comes from upstream (
tool/code-mode.ts, tool nameexecute). But the implementation was wholly swapped, and this replacement itself is the engineering decision worth remembering:
| Dimension | Upstream @opencode-ai/codemode |
MiMo |
|---|---|---|
| Isolation implementation | acorn + 3 465-line handwritten AST interpreter | QuickJS-emscripten (a real WASM engine) |
| What is exposed | MCP tools only | Host tools (late-bound registry) |
| Known cost | Must implement JS semantics yourself; coverage has holes | Handles not released hard-crash the process |
Why it was worth swapping: a handwritten JS interpreter is a bottomless pit—every place not implemented is a behavior difference, every place implemented wrong may be an escape, and 3 465 lines have to be maintained long-term. After swapping a real engine, risk moves from “semantic correctness” to “resource management”; the latter has a determinate checklist, the former does not.
And expanding the exposure from MCP tools to host tools raises permission risk one grade—which is why it forced the matching design below:
| Constraint | Practice |
|---|---|
| Can compose | The model submits an async function body; tools.<name>() calls host tools |
| Does not amplify permission | Late-bound registry—gets the exact same already-filtered tool table as the outer layer; control-flow tools (task/actor/skill/workflow/cron/session) are excluded, because they “change conversation or scheduling state, not suitable to hide inside one script call” |
| Does not mis-kill a normal script | Active-compute timing—time waiting for a host tool to return does not count against the 60-second budget; wall clock 30 minutes is a separate backstop |
| Does not crash the process | Every QuickJSHandle (including unsettled deferreds) must be released before context dispose, otherwise hard abort |
And the boundary is stated clearly: “QuickJS only isolates exec code. bash is still a real shell, not a container sandbox.”
8.4 Several engineering details worth remembering on their own
This repo’s comment density is high; several details’ general value exceeds the project itself:
| Detail | Insight |
|---|---|
| Empty-step detection deliberately does not catch “empty terminal” | “An empty terminal is a natural turn end, not a spin”—early versions caught it, false-reported to unusability. A detector drawing a boundary is more important than catching one more class |
stableStringify serializes by sorted keys |
The model “routinely re-emit the same arguments with keys in a different order”; without normalize you miss a real loop |
| Signature deliberately excludes text and reasoning | “the model narrates each step in slightly different words while taking the exact same action”—counting it in masks the repeated action you want to catch |
| Text normalize peels “Let me/I'll/I will” | On reread the opening will randomly switch; without peel you cannot see that what follows is the same sentence |
| Environment block’s date anchored to session creation time | “including ones that cross midnight”—using request time would suddenly cache-invalidate a session that crosses midnight |
| BM25 uses a relative floor rather than an absolute threshold | “BM25 magnitudes are corpus-size-dependent… any fixed absolute floor would wrongly wipe real hits”; first place is always kept |
| Checkpoint truncate repairs surrogate pairs | slice() cutting by UTF-16 code units will split a surrogate pair, emoji become mojibake |
| Debounce window aligned to checkpoint boundary | “The boundary only advances when a checkpoint/rebuild actually discards context”—not a made-up message count |
| never-worse gate | Any “optimize” pipeline should have a “if it did not get better, roll back as-is” backstop; the worst case equals not optimizing, not equals wrecking |
Object.create(null) as a registry |
When keys may come from model output, defending prototype-chain pollution is required |
| FORCED_ASK | “the intent to perform an irreversible action must be recorded in-band, not inherited from a broad blanket rule”—a glob allow cannot suppress delete |
8.5 Updated position quadrant
quadrantChart
title 十一大 Agent 定位象限
x-axis 锁定单一厂商 --> provider 无关
y-axis 终端程序 --> 本地基础设施
quadrant-1 开放平台型
quadrant-2 深度绑定的基础设施
quadrant-3 单一厂商的极致工具
quadrant-4 开放的终端工具
"Claude Code": [0.12, 0.18]
"grok-build": [0.10, 0.25]
"CodeWhale": [0.72, 0.22]
"MiMo Code": [0.78, 0.42]
"opencode": [0.85, 0.68]
"hermes-agent": [0.80, 0.78]
"Raven": [0.68, 0.82]
"nanobot": [0.75, 0.72]
"goose": [0.90, 0.88]
"OpenWorker": [0.82, 0.58]
"Open Design": [0.96, 0.94]
MiMo Code lands down-left of opencode: equally provider-agnostic, but product center of gravity clearly closer to “terminal program.”
This displacement has hard evidence (based on a file-level diff of commit
076b790vs upstream62e4641; concrete numbers may have small deviations from different version-detection methods): MiMo substantially cutserver/andacp/code volume, while significantly expandingcli/(including i18n). The server ambition cut was doubled into terminal experience.
8.6 Duel 7: “universal harness” or “one shape per model”
| Universal-harness school | Reshape-by-model school (MiMo Code) | |
|---|---|---|
| Claim | One prompt + one toolset, cover all models | One prompt per model family; strong families also swap the tool surface |
| Representatives | The other ten | MiMo Code |
| Advantage | Maintenance surface small; new model ingest zero cost; behavior consistent, easy to debug | Fits the training distribution, tool-call reliability clearly higher; can medicate a model’s weakness separately |
| Disadvantage | Weak models dragged by strong models’ prompts, and vice versa | 20 files each to maintain; routing is string if, prompt and tool two rule sets may be inconsistent |
| Who it suits | Small model pool, or mainly one or two strong models | Large model pool, and people will actually use weak models (MiMo supports MiMo/GPT/Claude/Gemini/DeepSeek/Kimi/GLM/MiniMax/Trinity……) |
Judgment basis: if your users will only use 1–2 frontier models, a universal harness is enough; if you seriously support a dozen families of uneven capability, “one prompt to rule them all” is burying with the weakest. The number of model families MiMo supports decided it had to walk the second road.
📌 Note that the two sides of this duel are actually “upstream vs downstream”: OpenCode already opened the family-split head (10 files), but its center of gravity is server and protocol; MiMo pushed the same road to 20 files and expanded to the tool surface. Same repo bloodline, two teams’ investment amplitude on the same question differs by an order of magnitude—this more than comparing it with unrelated projects shows where the marginal return of “reshape by model” sits.
8.7 Three trade-offs
| Trade-off | Note |
|---|---|
session/prompt.ts single file 4 595 lines / 208 KB |
Main loop + prompt assembly + goal gate + various gates + structured output all in one file. Contrast Open Design’s system.ts (2 075 lines, assembly only). Comment density is high, but too many concerns |
| Experimental features default off | Orchestrator, Token Efficient both flag-gated and default off—finely designed capability most users will never turn on, and flag branches explode code-path combinations |
| Fork’s dual cost | Upstream evolution must be continuously merged (package name still not changed); Effect’s reading barrier is brought by upstream; leftover files (default.old.txt) left in the tree |
| One “anti-refactor” | Upstream split LLM request construction / native runtime / tool assembly across four or five files in session/llm/ (33 KB total) + session/tools.ts (23 KB); MiMo deleted them all, gathered them into prompt.ts. The benefit of gathering is the control flow can be seen in one glance; the cost is a 4 595-line single file. On someone else’s skeleton stuffing new mechanism, concentrating the change in one place is much faster than first refactoring upstream layering—but this debt will roll |
8.8 An interesting ecosystem relationship
- Open Design treats
mimoas an engine (it is one of OD’s 26 adapters,streamFormat: json-event-stream, MCP inject walksMIMOCODE_CONFIG_CONTENT) - MiMo Code’s 26 built-in skills include
claude-code,codex,grok-build—it can in turn call these external CLIs as tools (and only exposes them when the corresponding executable is already installed) - MiMo’s memory index can be configured to include
~/.claude/projects—read Claude Code’s project memory; workflow and skills also scan.claude/directories
🧠 In this ecosystem there is no absolute host and object. Every house is treating the others as tools, and being treated as a tool.
9. The twelfth sample MiroFish: when the agent no longer does work, but evolves
Facts rest on: MiroFish source analysis (
666ghj/MiroFish, commit60757b3, 2026-07-23), AGPL-3.0, incubated by Shanda Group.
The first eleven chapters all rest on a shared premise: an agent is for doing work. This chapter has to take that premise apart.
9.1 It belongs to none of the first eleven’s classes
| Dimension | First eleven | MiroFish |
|---|---|---|
| Core question | How to let AI do work for me | How to let a crowd of AIs rehearse the thing for me |
| Agent count | 1 main + a few sub-agents | Hundreds to thousands, interacting as equals |
| Agent’s purpose | Finish a task | Behave like that person |
| Success criterion | The task was done right | The macro phenomenon that evolved looks real |
| Loop shape | ReAct (think → act → observe) | Social evolution (post → get recommended → get interacted with → influence others) |
| Memory | The agent reads and writes itself | A temporal graph the whole world writes together |
| Deliverable | Code / files / design drafts | A report + a world you can walk into and talk |
| Moat | Engineering complexity | Domain-common-sense parameters + product idea |
| License | Mostly MIT / Apache | AGPL-3.0 (the only one of the twelve) |
A set of numbers that make the point: 8 months, 69 573 stars, real business code only 24 428 lines of Python. About 1 550 stars per thousand lines of code—Open Design about 20, MiMo Code about 3.
🧠 Its value is not in code volume. This is the series’ first sample of “the idea’s value far exceeds the engineering value.”
9.2 Three things none of the first eleven have done
① Knowledge graph ↔ simulation bidirectional closed loop
The first eleven’s memory systems (OpenWorker’s SQLite facts, MiMo’s FTS5, Raven’s EverOS dual-track, hermes’s closed-loop learning) are all unidirectional: the agent writes memory, the agent reads memory.
MiroFish’s graph is written together by a whole simulated world:
flowchart LR
A["种子材料"] -->|本体+摄取| G[("Zep 时序图谱")]
G -->|实体→人设| S["OASIS 模拟"]
S -->|活动带帖子原文回写| G
G -->|GraphRAG 检索| R["报告"]
S -->|采访活着的 Agent| R
The two back-edges are the key. zep_graph_memory_updater.py batch-writes simulation activity carrying post original text back to the graph (BATCH_SIZE=5, counted per platform, MAX_EPISODE_CHARS=9_500). Without this back-edge, the report agent can only read “the world before simulation.”
② Interview agents that are still alive
The interview_agents docstring at zep_tools.py:1270 writes:
需要获取模拟 Agent 的真实回答(非 LLM 模拟)
This capability is jointly supported by three things: do not close the door when the run finishes (run_parallel_simulation.py explicitly writes “after completing the simulation do not immediately close the environment, enter wait-for-command mode”) + filesystem IPC interview channel (simulation_ipc.py) + LLM picking people and drafting questions (:1549 / :1632).
📌 Other multi-agent projects when done throw the log to an LLM to summarize; MiroFish lets the report agent go back and ask the people involved.
This is turning a one-shot process into a sustainably queryable asset—it turns the product from a “prediction-report generator” into an “explorable parallel world.”
③ Treat social-science parameters as core product config
The first eleven’s “config” is all engineering parameters: timeouts, concurrency, retry, token budget, permission grades.
MiroFish’s core config is sociological parameters:
| Parameter | Value | Corresponding phenomenon |
|---|---|---|
| China daily-schedule activity curve | Peak ×1.5 / night ×0.5 / work ×0.7 / morning ×0.4 / pre-dawn ×0.05 | Whether public opinion has a “sense of rhythm” |
| Viral-spread threshold | Twitter 10 vs Reddit 15 | The critical point at which information breaks a circle |
| Echo-chamber intensity | Twitter 0.5 vs Reddit 0.6 | Opinion polarization, information cocoons |
| Recommendation-weight triple | Twitter freshness 0.4 / Reddit heat 0.4 | The platform algorithm decides who sees what |
Note the last three rows are two sets of values by platform. These five numbers turn “Twitter is fast and shallow, Reddit is slow and deep and more cliquey”—a judgment everyone has as intuition but is hard to quantify—into parameters that can drive a simulation, and the directions are all right.
🔑 Copying its 24 000 lines of code is easy; what you cannot copy is experience like “pre-dawn activity should be set 0.05, 22:00 to 23:00 should have a first-level intermediate step.”
9.3 It took “outsourcing” to the extreme
| Hardest part | Who does it |
|---|---|
| Social simulation engine (platform environment / action space / recommendation algorithm) | OASIS (camel-ai) |
| Temporal knowledge-graph memory | Zep Cloud |
| Language model | Any OpenAI-compatible API |
| The remaining glue + productization | Written by themselves, 24 428 lines |
requirements.txt has only 12 packages in full. No LangChain (though the docstring writes that), no vector database, no message queue (uses filesystem IPC), no ORM.
Contrast Open Design’s practice is interesting—both are “do not build the engine yourself,” but in opposite directions:
| Open Design | MiroFish | |
|---|---|---|
| What is outsourced | The main loop (treat 25 coding CLIs as engines) | Simulation and memory (OASIS + Zep) |
| What they write themselves | Prompt engineering, quality enforcement, review theater | Ontology, persona, parameters, orchestration, closed loop |
| Result | Did not write a single line of main loop | 24 000 lines holding up 69k stars |
🧠 Shared lesson: first validate the idea, then consider replacing dependencies. If the MiroFish team first spent half a year home-growing a simulation engine and a graph database, they might not have shipped even today.
9.4 Duel 8: “one very capable agent” or “a thousand agents that look very like people”
| First eleven: capability-ceiling school | MiroFish: emergent-behavior school | |
|---|---|---|
| Optimization target | Make a single agent stronger, less likely to go off | Let the crowd evolve a credible macro phenomenon |
| Typical means | Independent judge, doom-loop gates, context compaction, permission gates | Persona fidelity, platform-mechanism parameters, time rhythm |
| What failure looks like | The agent did the work wrong, stuck, burned money | The evolved result does not look real (curve too flat, polarization too fast) |
| Verification | Run a task, see if the output is right | No standard answer—can only see whether it matches domain intuition |
| Cost | Complexity piled into one loop | Cannot be falsified; cost explodes with agent count × rounds |
This school of MiroFish’s soft underbelly is very clear: it cannot be verified. Code a coding agent writes can run tests; a design agent’s output can pass a linter; “how Wuhan University public opinion will go” has no ground truth. Its persuasiveness can only come from explainability of process—you can see who said what in which round, how information spread, which node was the turning point, rather than a black-box conclusion.
This also explains why “do not close the door when the run finishes + can interview” is not icing; it is a necessity of this route: you cannot verify the result, so you must let the process be questionable.
9.5 Updated position quadrant (swap a pair of axes)
The previous chapters’ quadrant axes (provider lock-in / terminal vs infrastructure) do not apply to MiroFish at all. Swap two axes before it can sit:
quadrantChart
title 十二大 Agent:Agent 数量 × 成功标准
x-axis "少数 Agent,各司其职" --> "大量 Agent,平等互动"
y-axis "追求任务正确" --> "追求现象逼真"
quadrant-1 "群体智能 / 仿真"
quadrant-2 "创作与生成"
quadrant-3 "编码助手 / 工具型"
quadrant-4 "编排与工作流"
"Claude Code": [0.16, 0.14]
"MiMo Code": [0.22, 0.11]
"opencode": [0.13, 0.09]
"grok-build": [0.11, 0.13]
"goose": [0.19, 0.17]
"CodeWhale": [0.14, 0.08]
"hermes-agent": [0.24, 0.19]
"nanobot": [0.21, 0.16]
"Raven": [0.26, 0.28]
"OpenWorker": [0.30, 0.22]
"Open Design": [0.38, 0.64]
"MiroFish": [0.90, 0.92]
The upper-right has only MiroFish one. This is not saying it is better; it is saying it is answering another question.
Eleven projects crowd the lower-left comparing whose agent is more capable—this itself is a signal: “emergent behavior of many mediocre agents” may be an empty lot nobody has seriously worked.
9.6 Its cost: several places looser than the first eleven
| Cost | Note |
|---|---|
| Can only use Zep Cloud, and it is forced | config.py:71-72 explicitly rejects ZEP_API_URL. Data must leave the country, if the service hangs the product is unusable, almost fatal for government/finance customers |
| Cost has no guardrail at all | Hundreds of agents × dozens of rounds × an LLM call each round. The only protection is a comment in the README .env example: # High consumption, try simulations with fewer than 40 rounds first. Contrast MiMo Code encoding billing grades into /context-limit |
| No persistent database | models/ is only dataclass + JSON land. No concurrency control, no transactions; multiple instances will fight |
| Single files too large | Step4Report.vue 5 162 lines · api/simulation.py 2 878 lines · report_agent.py 2 619 lines (stuffed with 8 classes) |
| One silent data-corruption | _to_pascal_case / _to_upper_snake_case’s [^a-zA-Z0-9] only recognizes ASCII: 武汉大学 → Unknown. Several Chinese types overwrite each other in the entity_types dict, no throw, no log. The only defense is that sentence in the prompt MUST be in English—and it is concatenated right after “please answer in Chinese” |
9.7 It has one place cleaner than all of the first eleven: full-stack i18n
This is an implementation unrelated to the theme, but worth copying away on its own.
locales/languages.json is a language registry (7 kinds, each with label and llmInstruction). Frontend i18n/index.js (27 lines) uses Vite glob to take the intersection of registry ∩ actual translation files, so the switcher only shows languages that actually have translations, will not show “selected but useless.” And backend utils/locale.py:8 walks '..','..','..' out of backend, reads the same directory.
llmInstruction is injected at 7 prompt-construction points—and every injection point is immediately followed by an exception “but these fields must be English”:
IMPORTANT: The 'stance' field value MUST be one of the English strings:
'supportive', 'opposing', 'neutral', 'observer'.
Only natural language text fields should use the specified language.
🔑 The easiest-to-step, hardest-to-debug pit of multilingual LLM apps: tell the model “answer in French,” it will write
stance: "supportive"as"favorable", downstreamif stance == 'supportive'mismatches directly—and it only appears when you switch off the default language.Discipline: natural-language fields follow the user language; enum values, type names, field names are forever English. MiroFish wrote this exception at every injection point, missed none.
One JSON registry simultaneously drives frontend menu + backend logs + LLM output language + missing fallback, in total under 80 lines of code.
9.8 One sentence for whoever has read this far
The first eleven projects tell you how to make one agent well. The question MiroFish poses is:
If you do not use one very capable agent, but a thousand agents that look very like people, what can you do?
The answer is: you can, before the thing happens, run it through once.
Whether this answer is right, 8 months 69k stars gave a preliminary market judgment. But its real inspiration is—when everyone is optimizing a single agent’s capability ceiling, beside the track there may still be a whole empty lot nobody is standing on.
Writing notes: Chapters 1, 2, 5, 6’s facts rest on this repository’s nine single-project analysis documents (Claude Code deep dive, eight under
项目分析/, including the newly added grok-build source analysis and openworker source analysis); line-number citations are re-cited from those documents’ marks. Chapters 3 and 4’s technical details are a second deep-dig check of each source repo on that basis (covering language/runtime, streaming pipeline, persistence, concurrent cancel, error handling, business model six axes); newly added line numbers can be walked back to the corresponding commit: Claude Code (claude-code/src/), opencode (62e4641), hermes-agent (d9165d7), Raven (85c7a5b), CodeWhale (88a158e), goose (d17d65f), nanobot (6a9157f), grok-build (xai-org/grok-build), OpenWorker (andrewyng/openworker,db93d75, package namecoworker). Wherever the second check “not found / uncertain,” this lecture never writes it as a determinate conclusion, or labels it as-is; no invention. Special note: OpenWorker is a “general-task colleague” rather than a coding agent; this lecture folds it in as a cross-border sample; some dimensions in a coding context (e.g. tool “scale,” prompt-cache “engineering”) have weaker comparability for it than the other eight, and its different emphasis is marked at the corresponding places. Chapter 7’s facts rest on open-design source analysis (nexu-io/open-design, commitc893b60, 2026-07-28); Chapter 8’s facts rest on MiMo Code source analysis (XiaomiMiMo/MiMo-Code, commit076b790, 2026-07-27); Chapter 9’s facts rest on MiroFish source analysis (666ghj/MiroFish, commit60757b3, 2026-07-23). All three documents have been checked file by file and markedfile:line. Special note: MiroFish is not a coding agent, nor a “doing-work” agent; most dimensions of the first six chapters are not comparable for it, so Chapter 9 started another set of comparison axes and did not forcibly fold it into Chapter 1’s overview table. Projects iterate very fast; line numbers will drift in new versions, but module boundaries and design principles stay stable.