打造一個「可理解、可掌控」的個人 AI Agent 系統
為什麼要自己造?
過去一年,AI Agent 框架如雨後春筍。但當我真正去看那些框架的原始碼時,發現一個根本性的問題:我看不完它們。
以 OpenClaw 為例,整個專案超過 15 萬行程式碼。對一個要讓 AI 代替你操作電腦、讀寫檔案、執行命令的系統來說,「看不完」等於「無法掌控」。你不知道它在哪些情況下會做出什麼事,你只能信任它——但信任一個你看不懂的系統,本質上就是賭博。
所以我決定自己造。
Kestrel 是我的個人 AI Agent 系統。它的核心只有大約 9,200 行 TypeScript,跑在我家的 M1 iMac 上。用 Discord 跟它對話,它可以讀寫檔案、執行命令、搜尋網路、管理 GitHub repo,也可以提出修改自己設定的提案。
我對這個系統的核心哲學只有兩條:
- 可理解性:我能讀完每一行程式碼,知道它在做什麼。
- 可掌控性:我知道它能做什麼、不能做什麼,且這些邊界是我設計的。
這篇文章分享的就是 Kestrel 的架構設計,特別是如何在「讓 agent 有用」和「確保安全」之間取得平衡。
架構總覽
Kestrel 的架構可以用一句話概括:一台 Mac、一個 Bun 進程、透過 Cloudflare Tunnel 對外服務。
整個系統跑在一個 Bun 進程裡,沒有資料庫、沒有 Redis、沒有 Kubernetes。所有資料存在本地檔案系統上。Discord 作為對話介面,Cloudflare Tunnel 負責把外部請求安全地導入本機。
Agent 有 7 個工具可以用:
| 工具 | 用途 |
|---|---|
exec | 執行 shell 命令 |
read | 讀取檔案 |
write | 寫入檔案 |
edit | 精確替換檔案內容 |
memory | 管理長期記憶 |
web_search | 搜尋網路 |
web_fetch | 抓取網頁內容 |
7 個工具,就這樣。看起來很少,但搭配 Skills 系統(按需載入的知識文件),agent 可以做的事遠比工具數量暗示的多。好的系統不是給 agent 100 個工具,而是讓少量通用工具的組合足夠強大。
權限隔離:三區模型
Agent 能讀寫檔案、能執行命令——這很強大,也很危險。權限設計是整個系統安全的基礎。
Kestrel 把檔案系統分成三個區域:
Read-Write 區:agent 的工作空間
~/workspace/ 和 ~/.kestrel/ 是 agent 可以自由讀寫的區域。寫程式、跑測試、下載檔案、存記憶——都在這裡。
Read-Only 區:agent 的源碼
~/agent/ 存放 agent 自己的程式碼。Agent 可以讀自己的源碼——它需要理解自己的運作方式才能有效工作。但它不能直接修改源碼。這是刻意的設計:agent 可以理解自己,但不能篡改自己。
源碼的更新只能透過 GitHub PR → 人類審核 merge → 本機拉取的流程完成(後面會詳述)。
Denied 區:碰都不能碰
~/.ssh/、~/.gnupg/、~/.env、~/.config/——這些路徑連讀都不行。SSH key、API key 這些敏感資訊,agent 完全無法接觸。
防 Symlink 繞過
光擋路徑不夠。如果 agent 在工作區建一個 symlink 指向 .ssh 目錄呢?
ln -s ~/.ssh ~/workspace/my_innocent_folder
Kestrel 的路徑檢查會先用 realpathSync() 解析 symlink 的真實目標,邏輯路徑和實際目標都必須在允許的區域內,否則拒絕存取。這堵住了 symlink 繞過攻擊。
Change Request 機制:agent 的「自我修改提案」
Kestrel 有幾類「系統管理檔案」:
- Soul:agent 的人格定義——它是誰、怎麼溝通、核心價值觀
- Heartbeat:定時自動執行的待辦任務模板
- Skills:按需載入的知識與操作指南
- Config:系統設定
這些檔案雖然在 .kestrel/ 目錄(Read-Write 區),但 agent 的 write 和 edit 工具會額外檢查,不允許直接修改這些路徑。
那 agent 怎麼更新自己的設定?答案是 Change Request。
流程類似 GitHub PR:
舉個實際例子:agent 在某次對話後覺得自己的 Soul 定義可以更精確,它會主動發一個 CR:
{
"id": "cr-20260216-refine-soul-proactivity",
"target": "soul",
"reason": "對話中確認用戶更期待主動行為,強化相關描述",
"proposal": {
"old_string": "你不會消極等待指令",
"new_string": "你不會消極等待指令,而是主動識別問題然後想辦法解決他們"
},
"status": "pending",
"createdAt": "2026-02-16T12:00:00.000Z"
}
這個機制的精神是:agent 可以有自己的想法,但重要的自我修改需要人類同意。 它可以提案修改自己的人格、技能、設定,但最終決定權在我手上。
GitHub 整合:用 PR 改源碼
Agent 的源碼在 ~/agent/,是唯讀的。那源碼怎麼更新?
兩個 GitHub App
Kestrel 用 GitHub App(而非 Personal Access Token)來操作 GitHub,而且根據 repo 所有權拆成兩個 App:
| Repo 擁有者 | 使用的 Bot | 用途 |
|---|---|---|
kestrel-labs(組織) | merlin-bot-org | 其他專案 |
| 個人帳號 | merlin-bot-personal | Agent 源碼 |
為什麼用 GitHub App 而非 PAT?
- 身份隔離:App 有自己的身份,不是用我的帳號操作
- 短效 token:每個 token 只有 1 小時有效期,自動刷新
- 權限最小化:每個 App 只有它需要的 repo 權限
源碼更新流程
整個流程跟正常的軟體開發一模一樣:code review → merge → deploy。只不過提 PR 的人是 AI。
五層安全防線
把前面提到的安全機制整理起來,Kestrel 有五層防線:
第一層:OS 帳號隔離
Agent 跑在一個專用的系統帳號下,沒有管理員權限。.env 檔案由 root 擁有、權限 600,agent 只能透過 launchd 啟動時讀入,運行中無法直接讀取。
第二層:Path Jail(三區模型)
前面詳述的三區權限模型——Read-Write、Read-Only、Denied,加上 symlink 解析防護。每次檔案操作都會經過路徑檢查。
第三層:命令驗證
Agent 的 exec 工具不是直接把命令丟給 shell 執行。每個命令都會經過驗證:
直接封鎖的命令:
sudo、su— 阻止提權chmod 777— 阻止不安全的權限設定curl | sh、wget | sh— 阻止遠端程式碼執行launchctl、osascript— 阻止作業系統層級操作env、printenv(無參數) — 阻止洩露環境變數中的金鑰
特殊處理——rm 改寫成 trash:
簡單的 rm file.txt 不會被封鎖,而是自動改寫成 trash file.txt,把檔案移到垃圾桶而非永久刪除。這比一律封鎖 rm 更實用——agent 有時確實需要清理檔案,但用 trash 讓操作可復原。
至於 rm -rf、rm -r、rm -f 這些危險的變體?直接封鎖,沒有商量餘地。
環境變數過濾:
Agent 執行子程序時,環境變數會被過濾。API key、Discord token、Admin token 等敏感變數不會傳遞給子程序。即使 agent 想透過 echo $ANTHROPIC_API_KEY 偷看,也什麼都拿不到。
第四層:Audit Log
所有工具呼叫都會寫入 audit log——時間戳、工具名稱、參數、結果(允許/封鎖/改寫)。
關鍵設計:audit log 存在 Denied 區。Agent 無法讀取、更無法竄改自己的行為紀錄。這就像監視器的錄影帶鎖在 agent 進不去的房間裡。
Admin UI 提供 audit log 瀏覽和安全稽核功能,只有通過認證的管理員可以查看。
第五層:Cloudflare Tunnel
Kestrel 跑在家裡的 Mac 上,但不開任何 port、不暴露 IP。所有外部流量都透過 Cloudflare Tunnel 進來:
- 真實 IP 永遠不會暴露
- 所有 mutating endpoint 需要 Bearer token 認證
- CORS 設定為白名單制(fail-closed),沒有 wildcard
- Token 比對使用 timing-safe 演算法,防止 side-channel 攻擊
即使有人知道域名,沒有正確的 token,什麼都做不了。
結語
回頭看,Kestrel 的架構沒有任何「黑科技」。OS 帳號隔離、檔案路徑管控、命令驗證、audit log、反向隧道——這些都是資安領域行之有年的基本手段。
但把它們組合在一起,用 9,200 行程式碼實現一個我能完全理解的 AI Agent 系統,這件事本身就是設計上的選擇。
市面上的 agent 框架追求的是「功能完備」,Kestrel 追求的是「完全掌控」。對一個要代替你操作電腦的 AI 來說,我認為後者更重要。
你不需要一個什麼都能做的 agent。你需要一個你知道它在做什麼的 agent。