Skip to content
系列
38 篇文章

跟成熟 coding agent 學設計

以 Rivumi 為實作載體,逐題對照 pi、OMP、OpenCode、Codex CLI 與 Claude Code:從 loop、workspace、approval 與 verification,一路追到已落地 baseline 的 memory、compaction、MCP、sandbox、subagents、replay、LSP、cost tracking 與 Agent as a Service,並保留 production validation 與跨 runtime parity 的真實缺口。

跟成熟 coding agent 學設計:系列總覽——拆五個專案的原始碼,蓋自己的 agent

我在寫自己的 Python coding agent「rivumi」,這個系列把 pi、oh-my-pi、opencode、codex、claude-code 五個成熟專案的原始碼逐題對照,也對照 Rivumi 現在已經落地的 TUI、外部 CLI runtime、local gateway、usage/OTel/session 工具與 Cloudflare 切片。每篇固定走「設計問題→五家做法→rivumi 選擇→學術依據→改善路線」五段,證據一律給到 file#symbol 層級。

跟成熟 coding agent 學設計(2):Agent loop 的形狀——事件流、checkpoint、resume

pi 的 loop 是雙層 while 加 EventStream;claude-code 明講 stop_reason 不可靠、改以串流中收到的 tool_use block 當唯一續跑訊號;codex 把 turn 做成可取消的 SessionTask 再靠 rollout crate 錄 JSONL;rivumi 選了「manifest 先落盤、JSONL 跟上」的寫入順序,讓 Ctrl-C 之後能做驗證式續跑而非重跑。這篇全部附 file#symbol 級證據。

跟成熟 coding agent 學設計(3):Workspace 隔離與 path policy

Rivumi 的 disposable clone 與 SafePathPolicy 保護來源 repo;現在 `--sandbox-checks` 也能用 macOS sandbox-exec、Linux bubblewrap 或 Landlock 包住 verification command,Cloudflare 另有受限 Sandbox slice。但不同 backend 的 network policy、外部 runtime coverage 與 production hardening 仍未一致驗證。

跟成熟 coding agent 學設計(4):Approval 分級與 audit trail

Rivumi 的 effect 已有 read/modify/modify_execute/execute 四級,未分類工具 fail closed;native MCP tool 預設 execute,只有可信 read-only annotation 才降級。審批仍先進 events.jsonl,grant 可精確到同一組變更或 backend;一般化 command 規則與全 runtime sandbox coupling 仍未完成。

跟成熟 coding agent 學設計(5):Verification gate——改了檔案不算成功,驗過才算

五家參考專案裡沒有任何一家在 harness 層強制「宣告的驗證指令全過才算成功」:pi 靠模型自覺、OpenCode 和 Codex 把驗證寫進 system prompt、Claude Code 用獨立的對抗式驗證 subagent 但仍是軟性合約、只有 OMP 的 cleanse 真的由 harness 跑檢查。rivumi 選最硬的一條路:改過檔案就必須重跑所有宣告的驗證指令,全過才給 terminal_reason=verified;沒動任何檔案就不重跑(no_changes),把「跑不跑」變成程式碼決定,不是模型決定。

跟成熟 coding agent 學設計(6):ModelProvider 抽象——為什麼不能直接包 SDK 就好

直接包 SDK 三個月就會後悔:每家的 usage 欄位、tool call 格式、錯誤語意都不一樣,換模型等於重寫迴圈。五家參考專案都把「wire protocol」從「provider 身分」裡拆出來當獨立維度;rivumi 更進一步,用 pydantic canonical contract(Message/ToolCall/Usage/ModelTurn)加六種 protocol adapter,把 OpenAI SDK 的內建 retry 關到 0,所有錯誤先分類成 ProviderErrorKind 再交給統一的重試政策。provider 表本身是跟 pi 的 packages/ai 對著抄的——這是血統,不是巧合。

跟成熟 coding agent 學設計(7):Provider retry policy——從單次 5xx 到有界 retry 與 fallback

NVIDIA NIM 間歇 500 暴露了 rivumi 早期只有錯誤分類、沒有重試消費者的缺口。現在 SDK retry 關閉,harness 對每個候選最多嘗試 5 次,使用帶 jitter 的指數退避並尊重有上限的 Retry-After;耗盡後可依明確設定切到 fallback model,model.retry 與 model.fallback 都進 event log。

跟成熟 coding agent 學設計(8):訂閱的正道與邪路——OAuth 與 credential 邊界

五家在「訂閱認證」上分成三派:Codex 和 Claude Code 只為自己官方 client 做 OAuth 並把 token 收進 OS keyring;pi 和 OMP 直接重用 Claude Code 的 client ID 實作 Pro/Max OAuth(技術可行但 Anthropic 文件明文禁止第三方未經核准提供 claude.ai 登入);OpenCode 則把內建 Pro/Max plugin 整組移除,是生態系最乾淨的政策先例。rivumi 的原則:自己的 grant 自己做、絕不刮別家 CLI 的 credential 檔、第三方 client 要 provider 明確支援才接 OAuth、credential 不複製不轉發。

跟成熟 coding agent 學設計(9):外部 CLI 當 backend——包別人的 loop,安全邊界畫在哪

每家成熟 coding agent 都有 headless 機器介面:codex 有 `exec --json` 和更完整的 app-server JSON-RPC,claude-code 有 `-p` 加 stream-json,pi/opencode/omp 各有一種 JSON 事件流。直接把這些 CLI 當 backend 是最快的路,但代價是:它們自帶 agent loop、自己的權限模型、自己的登入。rivumi 的答案是讓外來 CLI 完整擁有它的 loop,自己只守住三件事——隔離副本、patch audit、最終驗證——並且一條 runtime 永遠不偽裝成另一條。

跟成熟 coding agent 學設計(10):編輯工具的取捨——unified diff、exact edit、hashline 與 whole-file

LLM 寫 unified diff 壞在簿記:hunk 行數算錯、上下文行幻覺。五家的答案分成兩派——把 diff 文法做簡單(Codex 拿掉行號)、或乾脆不用 diff(Claude Code/Pi/OpenCode 的 exact replace);OMP 更進一步用 hash 錨點綁住讀取狀態。rivumi 走最小干預:保留 guarded apply_patch,加一個零模糊匹配的 replace_text,qwen3:4b eval 就從穩定失敗變 5/5。

跟成熟 coding agent 學設計(11):沙箱與遠端執行——Cloudflare Sandbox 部署實戰

本地沙箱管的是『agent 在你的機器上爆炸半徑多大』,雲端沙箱管的是『怎麼把程式碼安全地搬到別人的機器上跑』——五家成熟專案幾乎都在做前者,只有 rivumi 真的部署了後者。實戰教訓:mock 測不出 SSE framing、CI 綠燈擋不住 stale wheel,而清理路徑要跟成功路徑一樣有 timeout。

跟成熟 coding agent 學設計(12):小模型能寫程式嗎——能力邊界與 eval 紀律

小模型卡的不是『不會想』而是『格式不穩』:tool call JSON、diff hunk 計數、context 預算都會爆。五家參考專案的共識是把評測建立在真實模型行為上(pi 的 model-backed eval、OMP 從真實 session log 校準編輯基準、Codex 甚至為弱模型放寬 parser),rivumi 則選最窄但最硬的路:一個 fixture、五次真實 Ollama 執行、manifest 宣告改哪些檔案和哪些 patch 片段才算過,並且把 M2 的失敗原封不動留成證據——不把 mock 當 E2E,不把部分成功講成全過。

跟成熟 coding agent 學設計(13):CLI 人體工學——讓新工具長得像使用者已經會用的工具

成熟的 coding agent CLI 都收斂到同一套慣例:positional prompt、-p 是 print、exec 是 headless、resume 是一級指令、-C 換目錄;rivumi 直接繼承這套詞彙,把學習成本壓到接近零。

跟成熟 coding agent 學設計(14):Onboarding 設計——provider-aware 初始化與即時驗證

空白設定檔勸退人,憑證錯太晚發現更勸退人。五家成熟 agent 都把 setup 做成 first-class state,rivumi 再補上存完 key 立即驗證這一步。

跟成熟 coding agent 學設計(15):從全螢幕 TUI 到 semantic transcript

成熟的 coding agent TUI 都不是把事件流印出來,而是先做一層 typed projection 再渲染;rivumi 走了全螢幕組合、runtime-first 雙模式、移除 Ask/Agent 分離三步,才把 non-streaming 和 resume 不能 replay 兩個舊限制真正解除。

跟成熟 coding agent 學設計(16):Runtime 抽象與 capability handshake

五個外部 CLI 有五種機器介面:JSONL 事件流、JSON-RPC、HTTP API、ACP、stream-json。支援它們的正確姿勢不是抽一個「都一樣」的介面,而是一條窄的 runtime 邊界加一份誠實的 capability matrix——availability 只代表裝了,不代表登入;協定飄移就 fail closed。

跟成熟 coding agent 學設計(17):啟動效能與工程紀律——慢的從來不是語言

CLI 工具每次叫用都要付一次啟動成本,而沒有 baseline 的效能優化等於沒有回歸保護。codex 用 daemon 重用與 skill snapshot 快取、claude-code 把入口切成七十個動態 import 加上內建啟動 profiler、opencode/omp 各有 lazy 載入紀律;pi 則什麼都沒做,靠 Bun 的速度快撐著。rivumi 是 Python,天生慢,所以把紀律做滿:lazy import、單飛磁碟快取、背景預熱 controller、hyperfine paired benchmark 加上 CI 大於 10% 退步就擋 merge。

跟成熟 coding agent 學設計(18):工具集設計哲學——tool surface 的邊界劃分

Rivumi 的核心 surface 已從七個長到九個:新增 read-only `tool_program` 與可 rollback 的 `tool_transaction`,搜尋優先走 ripgrep,仍不開任意 shell;native MCP 只從 allowlist 動態加入,缺少可信 read-only metadata 就按 execute 審批。

跟成熟 coding agent 學設計(19):Session 持久化與 crash recovery——agent 死掉之後,狀態怎麼救

五家 agent 的 session 儲存幾乎都是 append-only JSONL 加上某種單寫者保護,但 crash recovery 的差別在細節:pi 會修 torn tail、codex 寫失敗會重開檔重試、rivumi 選了「manifest 先落盤」讓唯一的 crash window 變成可修復的一格。這篇拆解每家的寫入順序與 fail-closed 條件,全部附 file#symbol 證據。

跟成熟 coding agent 學設計(20):Run artifacts 契約——跑完之後憑什麼審計?

agent 跑完之後,「模型說它做完了」不是證據。codex 把 trace 拆成 manifest + JSONL + payloads 的 bundle、omp 用 SQLite 鏡像磁碟上的固定檔案、pi 用 runs.jsonl 索引原生 session 檔。rivumi 選了最硬的一條:每個 run 固定六個檔案,缺一個就不算完成,patch 審計看 changes.patch 不看口頭宣稱。

跟成熟 coding agent 學設計(21):Headless 模式與 CI 使用——沒有人可以按 approve 的時候

agent 進 CI 後最大的問題是審批:沒有終端機、沒有人可以按 approve。五家的解法收斂成兩條路——把權限決策外包給呼叫端(claude-code 的 control protocol),或直接換掉審批語意(codex 預設 Never 配沙箱、opencode 預設自動拒絕)。rivumi 用同一個 AgentRunner loop 注入不同的 ApprovalPolicy:headless 下用 HeadlessApprovalPolicy,不讀 stdin 所以不可能卡住 pipeline,EXECUTE 預設 fail closed。

跟成熟 coding agent 學設計(22):Gateway 模式——把任何 provider 變成 OpenAI 相容端點

生態系都把 /v1/chat/completions 當共通語,但你手上的 provider 不一定講這個方言。五家的答案分三派:pi 和 OpenCode 讓 client 本身講多種方言所以不做 gateway;OMP 做了真正的 protocol translator(foreign wire → 中立 context → provider adapter,禁止 raw passthrough);Codex 和 Claude Code 的 proxy 不翻譯,只負責強制流量管控。rivumi 抄 OMP 的邊界但收斂成一進一出:只收 OpenAI Chat,嚴格解析成 canonical contract,後面接任何 ModelProvider——順便踩掉一個 cross-event-loop client close bug,教訓是 provider 的生命週期必須交給 ASGI lifespan。

為什麼 Python:寫 coding agent 的語言選擇代價與補償

五個成熟 coding agent 沒有一家用 Python——pi/opencode/claude-code 用 TypeScript,codex 從 TS 重寫成 Rust,omp 把熱路徑補上 8 萬行 Rust native crate。rivumi 仍選 Python,代價是啟動效能與打包,補償手段是 lazy import、uv 和 Cloudflare Sandbox。

跟成熟 coding agent 學設計(24):測試一個會動的 agent——fake-CLI 合約、錄製串流、TUI pilot

agent 的兩個依賴——LLM 和外部 CLI——都不是決定性的,但成熟專案的招式是把「會動的部分」與「邊界的形狀」切開:codex 用 wiremock 假 Responses API 加腳本化 SSE server,TUI 用 insta 快照;opencode 乾脆做了 VCR 式的 http-recorder 錄放套件;pi 把 eval 與 unit test 分成兩份 vitest config;omp 把 edit benchmark 本身用 unit test 圍起來。rivumi 對外部 CLI 做了四層:單元測試、fake-CLI 合約、錄製串流整合、Textual pilot TUI 測試。核心方法論一句話:錄下真實的非決定性輸出,對它做決定性的斷言。

Prompt 版本控制:改一個字可能讓 eval 從 5/5 掉到 0/5

Rivumi 的 prompt 已到 `m3-exact-edit-v4`:版本寫進 artifact,core/tool/interaction/runtime/instructions/skills/workspace/memory 以 stable/dynamic section 組裝,並加入 replace_text、unified diff、direct reply 的正反例。unit tests 已釘住結構,live eval 覆蓋仍需擴大。

跟成熟 coding agent 學設計(26):Context 壓縮與 compaction——從缺口到可審計 baseline

五家成熟 agent 的 compaction 都要處理觸發、完整 turn 切點與失敗恢復。rivumi 已補上 85% high-watermark、自動 compaction、原生 loop 的 deterministic fallback summary、checkpoint 落盤與 workspace context 重新注入;目前仍缺跨 runtime 等價 fallback、模型品質摘要,以及真實 provider 的長 session 驗證。

跟成熟 coding agent 學設計(27):跨 session 記憶——從 explicit remember 到 semantic recall

omp 與 claude-code 都有跨 session 記憶,其他參考專案主要靠 instruction files。rivumi 已落地明確的 remember/list/inject baseline:型別化 JSONL 記憶可跨 session 注入 prompt,但目前只按 scope 與近期排序,還沒有語意檢索、去重、遺忘指令或自動萃取。

跟成熟 coding agent 學設計(28):危險指令攔截與 shell escalation——在白名單和每次都問之間

五家對自由 shell 的風險處理都包含放行/詢問/拒絕、複合指令檢查與 fail-closed。rivumi 已補上 deny-first command classifier、critical floor、複合 shell 分段、timeout-deny、設定式 allow/deny rule 與可見的 policy reason;仍需擴大語法涵蓋與真實互動流程驗證。

跟成熟 coding agent 學設計(29):OS 級沙箱

OS 沙箱是 path policy 之外的核心防線。rivumi 已落地 fail-closed `CommandSandbox`:macOS 包 sandbox-exec,Linux 用 Landlock 加 seccomp,verification 預設在能證明 containment 時進沙箱;不支援時回 exit 126。剩餘限制是目前主要只包 verification、外部 CI 結果仍待確認。

跟成熟 coding agent 學設計(30):MCP 整合——工具生態的標準插座

MCP client 要同時處理 transport、工具刷新、approval 與 credential 邊界。rivumi 已支援 allowlisted stdio、Streamable HTTP/SSE、tools/resources/prompts、tools/list_changed、OAuth metadata/PKCE 與 0600 credential store;尚缺真實 authorization server E2E 與 MCP 專用確認 UX。

Hooks/Skills/Plugins:成熟 coding agent 的三層擴展機制

Hooks 管控制流、skills 注入知識、plugins 負責打包。rivumi 已有 opt-in deny-only project hooks、bounded SKILL.md loader、exact enabled_skills 選擇、plugin manifest/install/list 與 external runtime projection;仍缺 hook input rewrite、完整 lifecycle、遠端 registry 與成熟 marketplace。

跟成熟 coding agent 學設計(32):Subagent 與 worktree 隔離——讓主 loop 學會分工

成熟 subagent 需要角色、fan-out 上限、權限收窄與成果回傳契約。rivumi 已有 native named-role schedule、平行 fan-out、子 task allowed_paths 不得超出 parent、預設禁用 unsafe exec,以及 parent-approved transaction proposal baseline;常駐 background lifecycle、遞迴深度管理與自動 worktree merge 仍未完成。

跟成熟 coding agent 學設計(33):Session 錄製與 replay——從事件檔走到可重播、可分叉

rivumi 已把 events.jsonl 接成 deterministic reducer、CLI timeline、canonical JSON、SDK replay 與安全分叉;分叉不會重跑舊工具或模型呼叫。剩下的是 provider/live runtime 驗證、redaction 與更完整的 replay hook。

跟成熟 coding agent 學設計(34):Telemetry 與成本追蹤——token 記了,然後呢

rivumi 已加入 CostBreakdown、明確標示 estimated 的 GPT-5 家族靜態價目表、per-lane usage/cost 與 OTel cost 欄位;未知模型仍只顯示 token,不硬算美元。價目覆蓋、外部 CLI 權威帳單與 live billing 對帳仍待補。

跟成熟 coding agent 學設計(35):Model catalog 與 per-role 多 provider 路由——rivumi 的 role alias 與 reviewer lane

rivumi 已有 ModelRole/ModelRoute 靜態候選表、--model @cheap 等 opt-in alias、跨 provider fallback,以及驗證完成後才啟動的 no-tool reviewer lane;下一步是補 role inheritance/override 規則,並決定 summarizer、parser、scout 是否自動路由。

跟成熟 coding agent 學設計(36):LSP 整合——把編譯器診斷推進 agent context

rivumi 已能把 repo 內 diagnostics 與 open-file 狀態注入下一個 model turn,也有 typed WebSocket IDE context push、可封裝的 VS Code bridge,以及管理長駐 LSP subprocess 的 ManagedLspServer。剩下的是各語言 initialize/didOpen/didChange adapter 的打磨與 live editor 驗證。

跟成熟 coding agent 學設計(37):Code mode——把工具呼叫編譯成程式碼批次執行

rivumi 已先做 bounded tool-program DSL:唯讀程式支援 list/read/search/diff、repeat 與 if_contains;modify/check transaction 會經整體 approval,失敗時回滾 touched paths。它還不是任意 JavaScript/Python code mode,也沒有平行 transaction execution。

跟成熟 coding agent 學設計(38):Agent as a Service——把 loop 包成別的程式可以呼叫的服務

rivumi 已有 Cloudflare Durable Object run resource:非同步建立、狀態/取消/artifact、live NDJSON 與支援 Last-Event-ID 的 SSE;遠端 approval 走獨立短效 capability。Python 另有 attach client 與 stateful conversation WebSocket。production deploy、跨 runtime parity 與完整多租戶 hardening 尚未驗證。