底層架構好奇者向
平常用 Pi Agent 你不需要看這篇——這裡講的是「掀開引擎蓋看裡面在跑什麼」。當 第 22 章的 Logs 出現看不懂的錯、當你想自己 debug 而不是等下一版、當你想知道為什麼一個對話介面要跑那麼多背景服務——這篇一次講清楚。技術詞第一次會標「中文(English)」,但不會要你寫程式。看完你會知道:s6-overlay 是什麼、nginx 為什麼要擋在前面、/data/pi-agent/ 底下每個資料夾裝什麼、Watchdog 探針怎麼探、Skill 是怎麼被「發現」的。像看家裡的水電配置圖——不用你動手,但知道總開關在哪就是安心。
config.yaml、Dockerfile、rootfs/etc/nginx/nginx.conf、CHANGELOG.md、DOCS.md),pi-web 上游 agegr/pi-web(HA add-on 把版本鎖在 0.8.4,可看該 tag 的 package.json),agent SDK earendil-works/pi。若下文哪一句你覺得可疑,直接去那三個 repo 對原始檔——比這裡的敘述準。為什麼要看這篇(不看也行)
先講清楚:99% 時候你不需要這篇。你按著第 2 章裝好、第 3 章從側邊欄打開、用得順順的——不用知道背後跑了什麼。就像你開冷氣不用知道壓縮機的原理。
但有那 1% 的時候會派上用場:
- Logs 出現看不懂的錯——你在 add-on 頁面切到 Log 分頁看到「nginx: [emerg] host not found in upstream」、「s6-rc: fatal: unable to start service pi-web」、「video-tools-init exited with code 1」,這些訊息你查 Google 也查不到 Pi Agent 專屬解法。看完這附錄你會知道每個服務是幹嘛的、失敗代表什麼。
- 想自己 debug 不想等下一版——某個功能怪怪的,你想 SSH 進 add-on 容器看看到底哪出問題。你得先知道有哪些目錄、哪些 process、log 存哪。
- 想改進或客製——你想 fork Pi Agent、想幫上游貢獻、想寫一個 companion add-on。這時你必須理解整條 pipeline。
- 純粹好奇——你是那種買了新設備會拆開看的人,那繼續往下讀就對了。
/etc/services.d/ 底下實際的資料夾為準,附錄只是給你一個「大約長這樣」的地圖。整體架構:大樓管理員比喻
用一個具體比喻先把層次講清楚,之後每一節再展開。把整套系統想像成一棟大樓:
| 比喻 | 對應的東西 | 做什麼 |
|---|---|---|
| 大樓本身 | Home Assistant(HA)作業系統 | 提供地基、水電、網路——住戶(add-on)才有得住 |
| 門房(Supervisor) | HA Supervisor | 負責裝新住戶、啟動住戶、每分鐘去看住戶還好嗎、住戶掛了幫他重開 |
| 接待櫃檯(Ingress) | HA Ingress 系統 | 外面的訪客(你的瀏覽器)想找住戶都得經過櫃檯登記,櫃檯負責認證與轉介 |
| 某戶租戶(我們家) | Woow HA Pi Agent add-on 這個容器 | 就是這篇要拆解的主角 |
| 戶內的家人 | 容器內的多個 process | nginx、pi-web、video-tools-init 各自負責家裡的一部分工作 |
| 戶內的家管系統 | s6-overlay(process supervisor) | 負責喚醒家人、按順序上班、家人偷懶就叫醒他 |
| 戶內的儲藏室 | /data/pi-agent/ 目錄 |
存所有值錢的東西——對話、金鑰、Skill、影片專案 |
兩個關鍵設計你要抓住:
-
Ingress 是唯一入口
你的瀏覽器不能直接連 Pi Agent 的內部服務。所有請求都得經過 HA 的接待櫃檯(Ingress),櫃檯先確認你是登入過的 HA admin,再放行到 Pi Agent 內部。這是為什麼第 3 章強調「側邊欄按鈕自動出現」——那個按鈕背後就是走 Ingress。
-
戶內是多層次的
Pi Agent 這個 add-on 容器內部不是只跑一個 process,而是一整組——nginx 站在門口收轉發、pi-web 是主應用、video-tools-init 是首次啟動才會跑的一次性 worker,加上 s6-overlay 這個「家管系統」在背後協調。
s6-overlay 開機順序:家人幾點上班
當 HA Supervisor 按下 Pi Agent 的「啟動」,容器內部發生的事其實有明確順序。s6-overlay(有時寫 s6)是這棟家管系統的核心——它是一套「行程監督工具(process supervisor)」,負責把家人一個個依序叫起來。
| 階段(見下方細節) | 服務名稱 | 類型 | 做的事 | 典型耗時 |
|---|---|---|---|---|
| 1 | s6-rc-init |
系統啟動 | s6 自己的「家管系統」先開機,把之後要用的內部訊號通道準備好 | < 1 秒 |
| 2 | video-tools-init |
一次性任務(oneshot) | 檢查儲藏室有沒有 .video-tools-installed sentinel 檔且 venv 裡的 python3 真的存在——兩個都通過就跳過;否則下載 Playwright、edge-tts、pyyaml、mutagen 加上 Chromium(~720 MB 合計),裝到 /data/pi-agent/venv/ 與 /data/pi-agent/playwright-cache/;裝完放 sentinel 說「裝好了下次別再裝」。失敗是 non-fatal——退出時對話介面還能用 |
首次 3-8 分鐘,之後 < 1 秒 |
| 3 | nginx |
長駐服務(long-running) | 啟動 nginx(反向代理,reverse proxy)監聽容器內部 30142 埠——所有 HA Ingress 轉進來的請求都會先撞到它 | < 1 秒 |
| 4 | pi-web |
長駐服務 | 啟動 Node.js Next.js 主應用(就是你在瀏覽器看到的介面);監聽容器內部 30141 埠;nginx 會把請求轉給它 | 3-15 秒 |
這裡有一件看原始碼才發現的細節:實際上 video-tools-init oneshot 沒有被列進 nginx/pi-web 的 dependencies.d——換句話說,s6-overlay 開機時把三個服務平行啟動:video-tools-init 一邊下 720MB Chromium,nginx 跟 pi-web 一邊已經在收請求。所以裝機那 3-8 分鐘的等待時,你其實已經可以打開對話介面聊天,只是影片管線暫時不能用。這是作者刻意的設計取捨(看 rootfs/etc/s6-overlay/scripts/video-tools-init 開頭的註解「Parallel with pi-web」),為了「chat UI 立刻能用、影片工具慢慢裝」,非「等一切齊全才給你進門」。
另外,Supervisor 從外面也會定期打 Watchdog 探針(下一節詳說)——如果 pi-web 卡住、hang 住、或應答變得很慢,Supervisor 會下令重啟整個容器。s6-overlay 這時會按同樣順序再走一次,只不過第 2 步會秒退(sentinel 存在+${VENV}/bin/python3 可執行都通過才會 skip,這是雙重檢查,避免第一次裝到一半掉線的殘骸誤判成裝好)。
nginx 的角色:HA Ingress 的翻譯官
你可能會覺得「Pi Agent 是個網頁應用,為什麼中間要多一層 nginx?多此一舉」。實際上 nginx 是整個容器裡最微妙、最關鍵的一個服務——它是翻譯官,把 HA Ingress 講的話翻譯給 pi-web 聽。
先看問題:HA 給每個 add-on 分配的 URL 長這樣——
https://homeassistant.local:8123/hassio/ingress/woow_ha_pi_agent/
而 pi-web 這個應用內部只認得「根路徑」(/),它不知道自己被藏在 /hassio/ingress/woow_ha_pi_agent/ 底下。如果不處理,pi-web 產生的 HTML 裡的連結會寫成 /settings 之類的相對路徑,瀏覽器一點就跳到 https://homeassistant.local:8123/settings——404,因為那不在 HA 的路由裡。
nginx 就是負責幫忙「路徑翻譯」的中間人:
-
收 HA Ingress 進來的請求
HA 把請求塞給 Pi Agent 容器的 30142 埠(
ingress_port: 30142),nginx 監聽那個埠,所以第一手一定收到。 -
從請求頭部把 Ingress path 取出來
HA 會在請求裡塞一個叫
X-Ingress-Path的 HTTP header,內容像/api/hassio_ingress/A1b2C3d4e5F6g7H8(16-128 字元的隨機字串,每次 HA 重啟會換)。 -
轉給 pi-web、拿到回覆
nginx 把請求轉發到
http://127.0.0.1:30141(pi-web 內部埠),pi-web 產出 HTML 回覆。 -
白名單防禦:先驗證 header 合法
為了避免有人偽造
X-Ingress-Path亂射,nginx 用map $http_x_ingress_path $safe_ingress_path區塊配合 regex(正則表達式)驗證,只讓格式為^/api/hassio_ingress/[A-Za-z0-9_-]{16,128}$的過(直接從 nginx.conf 抄的,不是推測)。不符合就強制設成空字串——之後所有 body-rewrite 都變成 no-op,關閉「透過偽造 header 打 XSS」的可能。這是 v0.7.0 為了修 F-01 安全審計加的。 -
用 sub_filter 改寫 HTML 裡的絕對路徑
nginx 用
sub_filter逐字節改寫傳出的 HTML/CSS/JS:把href="/_next/、src="/_next/、href="/manifest、href="/icons/、CSS 裡的url(/_next/、還有 RSC flight payload 裡 JSON-escape 的\"/_next/,通通加上 Ingress 前綴。 -
但 sub_filter 蓋不到執行期的 JS 呼叫——所以又塞了一個 client-side shim
這才是這個檔案裡最麻煩的一段。pi-web 的前端 bundle 有 40+ 個絕對路徑的
/api/*呼叫(fetch、EventSource、XMLHttpRequest),加上 Next.js App Router 的 RSC prefetch(?_rsc=<token>)、history.pushState/replaceState、以及ReactDOM.preinit()/next/font執行期塞進 DOM 的<link>——這些 sub_filter 都攔不到。所以 nginx 額外用sub_filter在</head>前塞一段 JS,猴子補丁(monkey-patch)window.fetch、EventSource、XMLHttpRequest.prototype.open、history.pushState/replaceState、Element.prototype.setAttribute、HTMLLinkElement/HTMLScriptElement/HTMLImageElement的href/srcsetter,全部繞去補上window.__INGRESS_PATH__。順便把navigator.serviceWorker.registerstub 成回傳空的假 registration(HA Ingress 底下 PWA service worker 沒意義還會噴 console 錯誤)。這段補丁的迭代史看CHANGELOG.mdv0.5.0~v0.10.4 就知道有多痛。
這一層看起來很囉唆,實際上是把「pi-web 完全不需要知道 HA Ingress」這件事變成真的。pi-web 只管好好當一個 Next.js 應用;nginx 加 sub_filter 加 client-side shim 三層合力把它塞進 HA 的路由架構。這種設計叫 Ingress shim——就像插頭的轉接器,兩邊規格不同時中間放好幾片。
Host 改成 localhost 加 Origin 清空(v0.6.0 修 pi-web 的 isApiRequestAllowed() 403 用的,跟 podman 版同一招),再 疊上 Ingress path 改寫加 client-side shim。差異不是「做的事不同」,是「HA 版比 podman 版多做兩層 Ingress 相關的東西」。/data/pi-agent/ 目錄地圖
第 20 章已經給過一個簡表,這裡給的是完整版——你 SSH 進 add-on container 進到儲藏室之後,眼前會看到這些:
| 路徑 | 裝什麼 | 備份會不會抓 |
|---|---|---|
sessions/ 下的 .jsonl 檔 |
每個對話 session 一個 JSONL 檔(每一行一個訊息)。DOCS.md 只寫「one per conversation」;子目錄結構(依 cwd 分資料夾或不分)是上游 pi coding agent SDK 決定的,若你要準確路徑SSH 進容器 ls /data/pi-agent/sessions/ 看實況為準,別信本文猜的樹狀圖 |
會(是對話存檔,per-addon 掛載、HA snapshot 涵蓋) |
models.json |
你在 Models 面板加的 provider、model 名稱、API key——金鑰是明文存的沒有加密,靠檔案權限 chmod 600 只讓 owner 讀寫;第 6 章提過 |
會(換 HA 主機這個一定要走) |
auth.json |
各家 OAuth provider 的 refresh token(例如 Google Drive 的授權);同樣 chmod 600 |
會 |
home/pi-cwd-*/ |
pi coding agent 的工作目錄。v0.10.0 把容器內 HOME 環境變數 pin 到 /data/pi-agent/home,所以 pi CLI 產出的 pi-cwd-* worktree 不會像 v0.9.x 以前那樣每次升級都被吃掉。命名慣例是上游決定的(DOCS.md 只寫 pi-cwd-*/,實際是 pi-cwd-<date> 或別的 suffix 以容器內為準) |
會(node_modules/ 跟 .cache/ 被 backup_exclude 排除以壓 snapshot 大小) |
skills/<skill-name>/ |
安裝的 Skill——本質上就是從 GitHub git clone 下來的資料夾;第 14 章講過概念 |
會(Skill 內容通常不大) |
rclone/rclone.conf |
rclone 的雲端授權設定(包含 Google Drive access token);第 19 章設過 | 會 |
projects/<project>/ |
影片管線的工作區。子目錄有 script/、clips/、segments/、voice/、output/——第 17 章那張中間檔表就是這裡 |
部分——final.mp4、script.md、subtitles.srt 會;clips/、voice/ 這些大檔通常被 exclude 避免塞爆 snapshot |
venv/ |
Python 虛擬環境(virtual environment)——edge-tts 跟其他 pip 套件裝在這裡 | 不會(HA snapshot exclude,因為可以重下載) |
playwright-cache/ |
Playwright 下載的 Chromium 二進位檔(大約 200 MB) | 不會(同上,可以重下載) |
.video-tools-installed |
一個空的 sentinel 檔——存在就代表「video-tools-init 跑過了、下次不用再跑」;第 18 章講的重跑機制就是把這個檔刪掉 | 會(很小,但抓不抓其實影響不大) |
settings.json |
UI 偏好設定(主題、預設模型等) | 會 |
兩個要記住的原則:
- 值錢的(不能重生的)都在
/data/pi-agent/——sessions、models.json、skills、rclone conf。這也是 HA snapshot 抓的核心。 - 大而可重生的(venv、playwright-cache)刻意被 exclude——如果 snapshot 抓 venv,一個 backup 檔就 500MB 起跳,實務上很痛苦。所以政策是「刪掉這些,還原後首次啟動 video-tools-init 會自動重下載」。
/mnt/data/supervisor/addon_configs/<addon-slug>/、Supervised 大概是 /usr/share/hassio/addon_configs/<addon-slug>/——但這兩條路徑本文沒有從 add-on 原始碼裡驗證過(source 只寫「per-addon mount」,實際 host 路徑是 HA Supervisor 決定的,跨版本會變)。要動就 SSH 進 HA host 然後 ls /mnt/data/supervisor/addon_configs/ 跟 ls /usr/share/hassio/addon_configs/ 兩處都試——哪個看得到就是哪個。比較穩的做法是 docker exec -it addon_<hash>_woow_ha_pi_agent bash 進到 container 內直接看 /data/pi-agent/(其中 <hash> 依 add-on 註冊 repo 而定,DOCS.md 舉的例子是 b9cf5676_woow_ha_pi_agent;local_woow_ha_pi_agent 只有你把 repo 用 local override 加進來才會出現)。一般用戶不用碰。Watchdog 探針:Supervisor 怎麼確認你還活著
Pi Agent 是長時間跑的服務,可能因為 memory leak、無窮迴圈、外部 API 死等而卡住。Watchdog(守望)是 HA Supervisor 用來偵測這種「還在跑但實際上死掉」情況的機制。
| 項目 | 內容 |
|---|---|
| 探針端點(endpoint) | http://[HOST]:[PORT:30142]/api/home——直接抄自 config.yaml 的 watchdog: 欄位。Supervisor 打 30142,經 nginx 轉給 pi-web 的 /api/home。作者選這個端點的原因:全新裝的 pi-web 在沒有 session context 下唯一會回 200 的路由就是 /api/home(CHANGELOG v0.10.0 有記錄) |
| 啟用方式 | config.yaml 直接寫成完整 URL(watchdog: "http://[HOST]:[PORT:30142]/api/home")——不是 watchdog: true 的形式(本文初版寫錯,這是 HA add-on 特有的 URL 型 watchdog 語法) |
| 頻率 | 由 HA Supervisor 決定,實際數字(30 秒?60 秒?)Supervisor 沒公開,跟 HA 版本會有微調——想確定看你的 add-on Log 分頁裡「Watchdog」出現的間隔就是實測值 |
| 成功條件 | HTTP 200 回應(不管 body 內容);逾時或非 200 就算失敗 |
| 失敗動作 | Supervisor 呼叫 docker restart addon_<hash>_woow_ha_pi_agent——整個容器重啟;s6-overlay 從頭再走一次 |
| 對 session 的影響 | 不會遺失。對話檔案是 append-only 的 JSONL——每收到一句話就寫一行 flush 到磁碟,就算 restart 也只會少「還沒寫完那句」。下次開啟 session 從最後一行接續 |
| 對影片管線的副作用 | 如果 Watchdog 在 pitch_video 跑到一半觸發 restart,那條 pipeline 產生的 clips/ 可能有半寫入的 webm 檔——那支影片八成毀了,重跑一次。這是第 22 章「影片突然壞掉」的常見成因 |
Watchdog 是雙面刃:能自動撿回卡住的服務很棒,但如果你的 add-on 本身沒問題只是回應比較慢(例如你叫它處理大量對話歷史),有機會被誤判成「hang」被強制 restart。這時候要嘛加大 timeout(改 config.yaml)、要嘛暫時把 Watchdog 關掉(HA 頁面上有開關)。
Skill 發現流程:AI 怎麼知道有哪些工具
第 14 章講過 Skill 的心智模型(給 AI 讀的工作手冊),這裡講「AI 怎麼發現」——這是很多人納悶的地方:「我 pi install 裝了新 skill,為什麼要重開 session 才生效?」
@earendil-works/pi-coding-agent 的原始碼。可能的流程是這樣,發生在每次你按「新對話」的瞬間:
-
Session 建立
你按新對話,pi-web 建一個新的 session 檔案(
sessions/.../*.jsonl)。 -
掃描 skills 資料夾
pi-web 開始掃
/data/pi-agent/skills/*/SKILL.md——把每個子資料夾底下的SKILL.md檔案讀進來。 -
解析 YAML frontmatter
每個
SKILL.md檔案開頭有一段 YAML frontmatter,長這樣:--- name: fridge_inventory description: 幫使用者盤點冰箱食材、找出快過期的東西 --- # 冰箱盤點手藝 ...細節內容...
pi-web 只讀
name跟description——整份 SKILL.md 的內文不會直接塞給 AI,只給摘要。 -
組成
<available_skills>塊所有 skill 的 name + description 合成一個列表,包在 XML 標籤裡。看起來像:
<available_skills> <skill name="fridge_inventory"> 幫使用者盤點冰箱食材、找出快過期的東西 </skill> <skill name="pitch_video"> 從腳本到 YouTube 可上傳的影片管線 </skill> </available_skills>
-
貼到 system prompt 開頭
這個
<available_skills>塊被貼在 AI 的 system prompt 開頭——AI 開啟對話的第一件事就是看到「這台機器可以呼叫的手藝有這些」。 -
AI 判斷什麼時候用哪個
之後你講什麼 AI 都會依這份清單判斷「這個問題有沒有適合的 skill 可以叫」。要叫的時候 AI 才會去讀完整的 SKILL.md 內容(透過工具呼叫),拿到細節做事。
這就是為什麼裝新 skill 必須重開 session——已經開的 session 的 system prompt 是舊的、沒把新 skill 塞進去。也是為什麼SKILL.md 的 description 那一行超重要——寫得不好 AI 就不會挑到你這個 skill;寫得好會經常派上用場。
pi-web 的技術棧:主應用到底用什麼寫的
你在瀏覽器看到的整個介面,包括打字區、對話列表、模型下拉、Skills 面板——通通都是 pi-web 這個 Node.js 應用做出來的。它的組成:
| 元件 | 用途 | 備註 |
|---|---|---|
| Node.js 22 | JavaScript 執行環境(runtime) | Dockerfile 從 nodesource 裝 node_22.x;pi-web 的 package.json 明寫 "node": ">=22.19.0" |
| Next.js 16.2.12 | 網頁框架——同時處理前端 React 與後端 API routes | 抄自 pi-web 0.8.4 tag 的 package.json。App Router 架構(app/ 底下);本文沒驗證有沒有 legacy pages/ 混合,看 pi-web repo 為準 |
| React ^19.2.4 | 前端 UI 元件框架 | 抄自同一份 package.json。Server Components / Suspense 都是預期有用,但實際哪個路徑用什麼本文沒進 pi-web 原始碼確認 |
| pino | 結構化 JSON logger | Log 分頁看到的 JSON 行是它產出的。DOCS.md 的 log_level 欄位描述明寫「exported as LOG_LEVEL for pi-web's Next.js pino logger」,但 pino 不在 pi-web 0.8.4 package.json 直接列的 deps 裡——是透過 Next.js / 上游 SDK 引入的間接依賴 |
| @earendil-works/pi-coding-agent | coding agent 的 SDK——AI 邏輯、tool call、skill 管理都由它處理 | pi-web 0.8.4 直接把 @earendil-works/[email protected] 列為 dep,in-process 呼叫(同個 Node 進程),不是另開 daemon——DOCS.md 也明寫「there is no separate agent daemon」 |
| 內部埠 30141 | pi-web 監聽的 port | 只在容器內開,外部經 nginx(30142)進來 |
| 對外埠 30142(Ingress) | nginx 監聽的 port,HA Ingress 打進來的地方 | 對應 config.yaml 裡的 ingress_port: 30142 |
幾個技術點值得注意:
- pi-coding-agent SDK 是 in-process 的——這代表 pi-web 崩了 agent 也跟著崩、agent 執行的 tool call(比如寫檔案)是用 pi-web 的權限做的。沒有 daemon 隔離就是為了少一層 IPC。
- 沒有資料庫——對話存 JSONL 檔案、設定存 JSON 檔案,一切都是檔案系統。這是刻意的簡化設計(也讓備份很好處理)。
- chat 走 SSE(Server-Sent Events)——AI 回覆邊產生邊 stream 給你看那種效果,是用 SSE 傳的。這也是為什麼 nginx 那節的
proxy_buffering一定要關掉——不然回覆會被 nginx 緩到 AI 生完才一次吐出來。
0.8.4 不是 @latest(Dockerfile 的 ARG PI_WEB_VERSION=0.8.4),因為 nginx 的 sub_filter 跟 shim 綁到當時的 _next chunk 命名/RSC prefetch 行為,上游隨便一次 refactor 都可能悄悄回歸。改 pi-web 之前先看該版 nginx.conf再動。v0.13.0 為什麼把 API key 從 add-on config 搬到 pi-web UI
這是 v0.13.0 版本最大的架構改動,你如果從舊版升上來會發現 add-on Configuration 分頁上原本的 API key 欄位不見了(第 21 章講過這個升級踩雷)。這節從架構角度講「為什麼要搬」。
舊架構(v0.12 之前):
-
你在 add-on Configuration 分頁填欄位
畫面上有
anthropic_api_key、openai_api_key、glm_api_key一堆欄位。 -
Supervisor 寫到
/data/options.json你按存檔,Supervisor 把整個表單序列化成 JSON 寫進容器內的
/data/options.json。 -
啟動時當環境變數塞給 pi-web
s6-overlay 開機腳本讀
/data/options.json,把每個欄位變成ANTHROPIC_API_KEY=...之類的 env var,然後才 exec pi-web。 -
pi-web 從環境變數讀
pi-web 啟動時
process.env.ANTHROPIC_API_KEY拿金鑰。
舊架構的缺點:
- 加新家 provider 就要改
config.yaml的 schema、發新版才行——動態不了 - 同一家想接兩把 key(工作一把、家用一把)?做不到,一個欄位一個值
- 沒有 Test 按鈕——你貼錯的 key,要等實際發第一封請求才知道 401 錯了
- 動態切換慢——改欄位要 restart add-on 才吃到新值
新架構(v0.13 之後):
-
你打開 pi-web 的 Models 面板
在瀏覽器介面裡,不是 add-on 那邊。
-
按 Add Provider、填表、按 Test
第 6 章教過的流程。Test 按下去 pi-web 會實際發一封 API 請求驗證,綠燈才存。
-
pi-web 存到
/data/pi-agent/models.json結構化的 JSON,可以裝很多 provider、每 provider 很多 key、每 key 很多 model。
-
不需要 restart 就生效
下個 session 就吃到新設定,因為 pi-web 每次開 session 都會 re-read
models.json。
新架構的好處:
- 多家、多 key、多 model 都能動態管理
- Test 按鈕可以事先驗證
- 不用重啟 add-on
- UI 上可以做「按這家的 provider 只想用其中某幾個 model」這種細節控制
遷移路徑(更正!):作者原本以為 v0.13.0 會自動幫你 seed 舊 key 進去models.json——去讀 CHANGELOG.md 跟 DOCS.md 才發現是 hard cut,沒有 auto-migration(原文兩處講兩次「No auto-import」)。升上 v0.13.0 之後每一個 provider 都要你在 pi-web Models 面板重新貼一次 key 加按 Test——舊 options.json 裡的欄位 Supervisor 依新 schema 直接丟掉。models.json 裡舊的 $GLM_API_KEY 之類的環境變數 placeholder 會 resolve 成空字串,每個 chat 都會 401 直到你重貼——這是預期的過渡狀態。
debug 進階技巧:進到容器裡面看
普通的 debug 靠 add-on Log 分頁看訊息就行。進階 debug 得進到容器內部——這裡整理幾招最常用的:
-
開 debug log level
在 add-on Configuration 分頁把
log_level從info改成debug(附錄 A 有詳細等級對照)。改完存檔會自動重啟,重啟後 Log 分頁會有非常詳細的訊息(有時多到眼花,看完記得改回info)。 -
SSH 進 HA host 再 exec 進容器
裝 SSH & Web Terminal add-on,或用 HA 的 Advanced SSH。連進去之後跑:
docker ps | grep pi_agent # 先看容器實際叫什麼 docker exec -it addon_<hash>_woow_ha_pi_agent bash
就進到 Pi Agent 容器內部了。可以
ls /data/pi-agent、cat /etc/nginx/nginx.conf、ps aux看有哪些 process 在跑。注意這是 read-only 心態——別亂改容器內的檔案(重啟會消失,除非改在/data底下)。 -
看 nginx access log 抓 Ingress path
容器內:
tail -f /var/log/nginx/access.log
可以看每個請求進來的 URL、狀態碼、response time。想確認「HA 有沒有真的把
X-Ingress-Pathheader 送進來」,看這裡最快。 -
手動測 edge-tts 是不是能連微軟
容器內:
/data/pi-agent/venv/bin/python -m edge_tts --list-voices | head
能列出聲音就代表網路通、venv 沒壞。列不出來就是「video-tools 半殘」——沿著 第 18 章的重跑機制修復。
-
手動測 rclone 到 Google Drive
容器內:
rclone --config /data/pi-agent/rclone/rclone.conf ls gdrive: | head
能看到 Google Drive 內容代表授權還沒過期。看到
token expired之類就是要重新跑rclone config reconnect(第 19 章結尾提過)。
docker exec 進到那個容器裡動作,別在 HA host 的檔案系統亂寫。看完就 exit。架構層 troubleshoot 清單
這一節不重複第 22 章的表面症狀,只列「懂架構才能修」的那些:
-
想在 HA host 上直接動
/data/pi-agent/的檔症狀:你想在 HA host 用 File Editor 或 SSH 直接
vi一份models.json,但找不到那個路徑。
原因:HA add-on 的/data在 host 端不是/data——實際路徑本文沒從 add-on 原始碼確認(source 只寫「per-addon mount」),大概候選是/mnt/data/supervisor/addon_configs/<slug>/(HA OS)或/usr/share/hassio/addon_configs/<slug>/(Supervised)——但這是 Supervisor 決定的、跨版本會變。
解法:SSH 進 HA host → 兩個路徑都ls一下,哪個存在就進去。更穩的做法:直接進 containerdocker exec -it addon_<hash>_woow_ha_pi_agent bash然後vi /data/pi-agent/models.json——容器內的/data才是 add-on API 直接綁定的位置,不用猜 host 側 mount point。改完存檔不用重啟 add-on,pi-web 下次 read 就吃到(但影響大的檔改完最好重啟保險)。 -
Ingress 掛:nginx 一直回 403
症狀:側邊欄按 Pi Agent 按鈕,畫面出現一片空白或 nginx 的 403 錯誤頁。
原因:nginx 的白名單 regex 沒讓X-Ingress-Pathheader 過。可能是 HA 版本更新後 header 格式微改(多了字元、少了_),或 HA 那邊沒送這個 header 進來。
解法:容器內tail -f /var/log/nginx/access.log看實際請求的 header;如果格式明顯不同(例如 slug 突然有大寫或全形),去 GitHub Woow_ha_pi_agent_add_on 開 issue 貼給作者。臨時解法:重啟 HA 本體讓 Ingress 重發新的 slug。 -
pi-web hang:Watchdog 一直 restart
症狀:Log 分頁每幾分鐘就跳
s6-rc-init started,代表容器不斷被 Supervisor 重啟。
原因:pi-web 的/api/home端點沒在 60 秒內回 200——可能是 CPU 被吃光、記憶體不夠、Node event loop 卡在某個同步操作。
解法:(a)先看 HA 主機 CPU/RAM 是不是滿載——Pi 3、太舊的 mini PC 很容易掛;(b)暫時關掉 Watchdog(HA add-on 頁面上有開關)看 pi-web 到底能不能自己撐;(c)開log_level: debug抓死前那幾秒在幹嘛;(d)真的搞不定就到 GitHub 開 issue 附上 log。 -
video-tools 半殘:sentinel 有了但 venv 是空的
症狀:影片管線一叫就爆錯,說找不到 edge-tts 或 Playwright。
原因:video-tools-init 第一次跑到一半掉線但已經寫了 sentinel 就退出,之後每次都以為裝好了直接跳過。
解法:add-on Configuration 分頁把reset_video_tools: true打勾存檔——這個開關會刪掉 sentinel 讓下次啟動重新跑一次 init。跑完記得回來把開關關掉(不然每次啟動都重下 720MB)。第 18 章那邊有更多這個開關的細節。 -
Skill 裝了但 AI 看不見
症狀:
pi install說裝成功了,但你在對話裡 AI 就是不會叫。
原因:Skill 發現流程只在新 session 開始時跑一次——你的舊 session system prompt 沒有這個 skill。
解法:按「新對話」開新 session。還是看不到就ls /data/pi-agent/skills/確認資料夾真的存在、cat SKILL.md確認 frontmatter 的name跟description都在。frontmatter 格式錯誤(YAML 縮排錯之類)會讓整個 skill 被跳過。
常見問題
我可以自己 fork Pi Agent add-on 改嗎?授權允許嗎?
Dockerfile 的 org.opencontainers.image.licenses="MIT" label 有明文,以 repo 的 LICENSE 為準),你可以 fork、改、發自己的 repo 讓別人裝。但要注意兩件事:(1)上游的 pi-web(agegr/pi-web,package.json 寫 MIT)跟 pi-coding-agent(earendil-works/pi)是不同 repo 的授權,改它們也要看那邊的 LICENSE;(2)你 fork 後改的部分建議另外命名(例如 Woow_ha_pi_agent_myfork)不要沿用原始 slug woow_ha_pi_agent,不然升級時 HA 會混淆。Home Assistant Container 版(不是 Supervised/HA OS)能不能跑 Pi Agent?
homeassistant/home-assistant)只有 core,不能裝 add-on。如果你用 HA Container 想要 Pi Agent,建議直接跑 Woow_podman_pi_agent_package——那是給 podman/docker 用的等效部署,不需要 HA add-on 系統。有 K3s 或 Podman 版本嗎?我想跑在 Kubernetes 上
- Woow_ha_pi_agent_add_on——HA add-on 版(就是這本教學的主角)
- Woow_k3s_pi_agent_package——K3s(輕量 Kubernetes)部署
- Woow_podman_pi_agent_package——podman/docker 部署
我想幫 pi-web 或 agent 上游貢獻,要去哪個 repo?
- UI 元件、對話介面、Models 面板、Skills 面板:改 github.com/agegr/pi-web(Next.js 應用本體)。
- AI 邏輯、tool call、skill 呼叫、streaming 行為:改 github.com/earendil-works/pi(pi coding agent SDK)。
- HA add-on 打包(Dockerfile、nginx.conf、s6-overlay 服務、config.yaml):改 github.com/WOOWTECH/Woow_ha_pi_agent_add_on。
為什麼要 panel_admin: true?我想給家人一般 HA 帳號也能用
config.yaml 裡設 panel_admin: true——這代表側邊欄按鈕只有 admin 權限的 HA 帳號才看得到。這是刻意的:Pi Agent 內部有 API 金鑰、有存對話紀錄、有能執行任意工具的 AI,一般家人帳號拿到會有安全與帳單風險。如果你真的要開給非 admin 帳號:改 config.yaml 把 panel_admin 設成 false——但你要承擔多人共用金鑰、對話混雜、家人不小心叫 AI 亂花錢的後果。折衷做法是為家人開單獨的 admin 帳號(在 HA 使用者管理裡),你比較能追蹤誰做了什麼。Pi Agent 會不會把我家的對話送到雲端?
sessions/*.jsonl 都在你 HA 硬碟上,沒有任何 telemetry 把它上傳到 WOOWTECH。但 AI 回覆需要打 provider API——你選了 OpenAI/Anthropic/GLM/DeepSeek 之類,你送出的訊息就是走那家的 API(那家的隱私政策決定他們怎麼用)。真正 100% 本地:接一家本地的 LLM(例如自己跑 Ollama 或 llama.cpp)當 provider,這樣完全不出家。這是 BYOK 架構的好處——你選誰去打交道自己決定。能不能同時跑兩個 Pi Agent add-on(一個工作用一個家用)?
版本升級(例如從 v0.12 到 v0.14)會不會刪掉我的資料?
/data 底下的東西——你的 sessions、models.json、skills 通通還在。第 21 章講過的 v0.13 API key 搬家是結構變了但資料沒丟,pi-web 首次啟動會自動 seed 到新位置。真正會丟的只有你自己主動刪(例如 uninstall add-on 再重裝)、或 reset_video_tools 那類明確標「reset」的開關。