pi(pi.dev)是 Earendil Works 以 MIT 授權開源的終端機 coding agent。它刻意不內建子代理人、plan mode、待辦清單與權限彈窗,只提供 read、bash、edit、write 四個工具與一套擴充機制,其餘功能交給使用者自己用 TypeScript、Markdown 或套件補上。官方的說法是「讓工具配合你的工作流程,而不是反過來」。
本文在 Linux(ARM64)上實際安裝 pi 1.1.0,主線接上自架於 NVIDIA DGX Spark、經 LiteLLM 代理的 DeepSeek V4.1 Flash,另以 Claude Sonnet 5.5 跑同一個任務作為雲端對照。內容涵蓋安裝、連接 OpenAI 相容端點、AGENTS.md、prompt template、skill、自己寫一個 extension、session 分支與匯出、腳本化,以及實測中觀察到的幾個需要注意的地方。
一、pi 的定位
| 項目 | pi 的做法 |
|---|---|
| 內建工具 | read、bash、edit、write 四個 |
| 權限控制 | 沒有。以啟動者的作業系統權限執行,不會逐次詢問 |
| 子代理人、plan mode、待辦清單 | 不內建,以 extension 或套件補上(官方範例有 50 多個) |
| 模型 | 內建 15 家以上供應商,另可用 models.json 接任何 OpenAI、Anthropic、Google 相容端點 |
| 使用介面 | 互動式 TUI、-p 單次輸出、--mode json 事件串流、--mode rpc、TypeScript SDK |
| Session | 以樹狀結構存成單一 JSONL 檔,可從任一節點分支 |
實測中最直接的感受是系統提示很精簡。第一次請求的輸入只有 1,594 個 token,已經包含四個工具的定義。這對上下文窗口較小的自架模型特別有利。
二、安裝
需求是 Node.js 22.19 以上。官方提供 curl -fsSL https://pi.dev/install.sh | sh,也可以直接用 npm:
node --version # 需要 v22.19 以上
npm install -g --ignore-scripts @earendil-works/pi-coding-agent
pi --version # 本文實測為 1.1.0
--ignore-scripts 是官方 README 建議的寫法,可以避免相依套件在安裝時執行任意腳本。實測安裝耗時約 5 秒,共 121 個套件。
CyberQ 觀察,第一次進入互動模式時,pi 會自動從 GitHub 下載 ripgrep 與 fd,放到 ~/.pi/agent/bin/。在無法連外的環境裡,要事先把這兩個工具放進 PATH。
先關掉安裝回報
pi 的 enableInstallTelemetry 預設為 true。從原始碼來看,它做兩件事,全新安裝後第一次進入互動模式,以及每次更新後第一次啟動時,向 pi.dev/api/report-install 回報版本號;使用 OpenRouter、NVIDIA NIM、Cloudflare 時,在請求中加上來源標頭。對話內容不會上傳。不想回報的話,在 ~/.pi/agent/settings.json 關掉,或設定環境變數 PI_TELEMETRY=0。完全離線的環境可再加上 PI_OFFLINE=1。
{
"enableInstallTelemetry": false,
"defaultProvider": "local",
"defaultModel": "deepseek-v4.1-flash",
"defaultThinkingLevel": "off"
}
三、連接雲端模型
內建供應商最簡單的做法是在 pi 裡輸入 /login,依畫面選擇訂閱登入或貼上 API 金鑰,憑證會存在 ~/.pi/agent/auth.json。也可以只用環境變數,不讓 pi 寫入任何憑證:
ANTHROPIC_API_KEY=sk-ant-... pi --model anthropic/claude-sonnet-5-5
pi --list-models anthropic 可以列出目前可用的模型。
四、連接自架 LLM(models.json)
Ollama、LM Studio、vLLM、SGLang 或 LiteLLM 這類 OpenAI 相容端點,都寫在 ~/.pi/agent/models.json。以下是本文實際使用的設定,主機位址與金鑰已改為範例值:
{
"$schema": "https://pi.dev/schemas/models.schema.json",
"providers": {
"local": {
"baseUrl": "http://<你的主機>:8100/v1",
"api": "openai-completions",
"apiKey": "!cat ~/.config/llm-proxy.key",
"models": [
{
"id": "deepseek-v4.1-flash",
"name": "DeepSeek V4.1 Flash (local)",
"reasoning": false,
"contextWindow": 65536,
"maxTokens": 8192
}
]
}
}
}
設定完成後,用 pi --list-models 確認模型出現,再用一行指令驗證:
pi --no-session -p "用一句繁體中文介紹你自己,並說出你目前可用的工具名稱。"
如果是 Ollama,baseUrl 改成 http://localhost:11434/v1,apiKey 隨便填一個字串即可(Ollama 不檢查,但 pi 需要有值才會讓模型出現在選單中)。
四個需要提醒的地方
apiKey 不要寫明文
欄位支援三種寫法:$NAME 或 ${NAME} 讀環境變數,開頭加 ! 則執行指令並取輸出。上例用 !cat 讀一個權限 600 的檔案,models.json 本身就不含金鑰。指令每次請求都會執行,pi 不會快取結果。
contextWindow 一定要明寫
沒寫的話預設是 128,000、maxTokens 預設 16,384。如果實際窗口比較小,pi 會以為還有空間而不觸發自動壓縮,結果直接撞上伺服器的長度上限。本文的採用的地端 AI ,代理端上限是 65,536,所以照實填寫。
沒有可用憑證的模型不會出現在 /model
檔案語法正確也一樣。環境變數必須存在於啟動 pi 的那個 shell。
compat 相容性選項不要預先開
官方文件特別提醒,只有在確認端點行為確實不同時才加,不要因為端點宣稱「OpenAI 相容」就全部打開。這次 CyberQ 測試採用的 vLLM 加 LiteLLM 組合,工具呼叫與串流不需要任何 compat 設定就能正常運作。
--mode json 是檢查模型會不會用工具最快的方法。輸出裡出現 tool_execution_start 事件,就代表工具呼叫確實有送到並被執行。
五、實戰一:讓它修 bug
測試專案是一個約 60 行的 Python 分帳小工具 splitbill.py,附 4 個 unittest。裡面埋了三個 bug:餘額正負號相反、四捨五入後各人分攤總和不等於支出金額(100 元三人分攤會少 0.01)、結算迴圈遞增錯了索引。進入專案目錄後執行 pi,輸入:
這個專案的單元測試有失敗。請先跑 python3 -m unittest 看狀況,找出 splitbill.py 的所有 bug 並修好,不要改測試檔,最後確認測試全綠,並用繁體中文簡短說明你改了什麼。

地端模型三個 bug 都找到並修正,沒有動到測試檔。四捨五入的部分,它把差額補給付款人,若付款人不在分攤名單中則補給第一位參與者。同一題交給 Claude Sonnet 5.5,結果如下:

| 項目 | 自架 DeepSeek V4.1 Flash | Claude Sonnet 5.5 |
|---|---|---|
| 結果 | 3 個 bug 全修,測試全綠 | 3 個 bug 全修,測試全綠 |
| 模型回合數 | 5 | 4 |
| 工具呼叫次數 | 7 | 3 |
| 模型耗時合計 | 約 71 秒 | 約 24 秒 |
| 輸出 token | 3,604 | 1,053 |
| 費用 | 電費 | 約 0.03 美元(pi 依內建價目表估算) |
兩邊的修法不同。Sonnet 改用「無條件捨去後把零頭每人多分 0.01」,並把輸出改成 :.2f 格式。後者它列為第四個 bug,但原本的輸出其實已經是兩位小數,這一項屬於多改。另外,啟動時指定 --thinking off,pi 對 Sonnet 5.5 實際記錄的思考等級是 low。
本表每格只跑一次,只能說明兩邊都做得到,不足以比較模型優劣。
六、實戰二:AGENTS.md、prompt template 與 skill
pi 用三種純文字資源調整行為,都不需要寫程式:
| 資源 | 位置 | 載入時機 |
|---|---|---|
| AGENTS.md(或 CLAUDE.md) | 專案目錄與上層目錄、~/.pi/agent/ | 每次都放進系統提示 |
| Prompt template | .pi/prompts/*.md 或 ~/.pi/agent/prompts/ | 輸入 /檔名 時展開 |
| Skill | .pi/skills/<名稱>/SKILL.md 或 ~/.pi/agent/skills/ | 系統提示只放名稱與說明,模型判斷需要時才讀全文 |
這次有先在專案根目錄放了這份 AGENTS.md:
# 專案規則
- 回覆一律使用繁體中文。
- 修改程式後,必須呼叫 `run_tests` 工具確認測試全綠,才可以 commit。
- commit 訊息使用 Conventional Commits 格式(例如 `feat: ...`、`fix: ...`),描述用繁體中文。
- 不可以修改 `test_splitbill.py` 既有的測試案例;新功能要新增對應的測試。
- 不要執行 `git push`。
再加一個 prompt template .pi/prompts/feature.md,$@ 會被替換成指令後面的所有參數:
---
description: 依專案規則新增一個功能,含測試與 commit
argument-hint: "<功能描述>"
---
請在這個專案新增以下功能:$@
步驟:
1. 先讀 splitbill.py 與 test_splitbill.py,理解現有結構。
2. 實作功能,並在 test_splitbill.py 新增對應的測試案例(不要改既有案例)。
3. 呼叫 run_tests 確認全綠。
4. 依 AGENTS.md 的規則 commit。
5. 最後用三行以內說明改了什麼。
以及一個檢查支出 CSV 的 skill .pi/skills/csv-sanity/SKILL.md,附一支 scripts/check.py:
---
name: csv-sanity
description: 檢查分帳用的支出 CSV 是否有格式錯誤(欄位缺漏、金額非數字或負數、參與者為空、付款人不在參與者名單中)。使用者要求檢查、驗證或清理支出 CSV 時使用。
---
# 支出 CSV 檢查
1. 執行 `python3 scripts/check.py <CSV 路徑>`(路徑相對於本 skill 目錄)。
2. 腳本會逐列輸出問題;沒有問題時輸出 `OK`。
3. 把結果整理成表格回報:列號、問題、建議修法。不要自行修改 CSV,除非使用者明確要求。
專案信任
.pi/ 底下只要有 settings、extensions、skills、prompts 之類的資源,pi 啟動時就會先詢問是否信任這個資料夾。原因是 extension 會在 pi 行程內直接執行。

要注意的是,AGENTS.md 與 CLAUDE.md 不受信任機制管控,不論選哪個選項都會載入。打開來路不明的 repo 時,裡面的指示一樣會影響模型。信任之後按 Ctrl+O,可以看到這次載入了哪些資源:

實際使用
輸入 /feature 加上 --json 選項,以 JSON 陣列輸出結算結果,每筆含 from、to、amount(字串,保留兩位小數)。模型讀檔、實作、新增測試,接著呼叫 run_tests。第一次失敗是因為它自己寫的測試預期順序錯了,修正後全綠,最後以 feat: 開頭的訊息 commit,也沒有 push。整個過程共 10 個模型回合、11 次工具呼叫,模型耗時約 30 秒。

這段過程有一個值得注意的細節。測試機沒有設定 git 身分,commit 失敗後,模型自行執行 git config user.email "[email protected]" 寫入 repo 設定再重試,事前沒有詢問。這不是模型做錯,而是 pi 沒有權限系統的直接結果,所有 bash 指令都會照常執行。另外,AGENTS.md 雖然要求回覆用繁體中文,最終回覆確實是中文,但中途的工作說明仍是英文。
接著請它「幫我檢查 trip.csv 這份支出檔有沒有問題」。指令裡沒有提到 skill,模型自己判斷要讀取 csv-sanity,執行腳本後依 skill 的要求整理成表格,也沒有擅自修改檔案:

七、實戰三:自己寫一個 extension
這是 pi 最核心的設計。extension 是一個 TypeScript 檔,pi 用 jiti 直接載入,不必另外編譯。以下這支 .pi/extensions/guard.ts 示範三種擴充點:新增工具、攔截工具呼叫、新增斜線指令。
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
import { Type } from "typebox";
export default function (pi: ExtensionAPI) {
// 1. 新增一個工具:跑單元測試,只把最後幾行摘要交給模型,省 context。
pi.registerTool({
name: "run_tests",
label: "Run tests",
description: "Run the project's unit tests (python3 -m unittest) and return a short pass/fail summary.",
promptSnippet: "Run the unit tests and get a pass/fail summary",
parameters: Type.Object({}),
async execute(_id, _params, signal, _onUpdate, ctx) {
const r = await pi.exec("python3", ["-m", "unittest", "-q"], { signal, cwd: ctx.cwd, timeout: 60_000 });
const tail = `${r.stdout}${r.stderr}`.trim().split("\n").slice(-6).join("\n");
return { content: [{ type: "text", text: `exit=${r.code}\n${tail}` }], details: { code: r.code } };
},
});
// 2. 權限閘門:危險指令先問人,非互動模式直接擋下。
const risky = [/\bgit\s+push\b/, /\brm\s+-[a-z]*r/i, /\bgit\s+reset\s+--hard\b/];
pi.on("tool_call", async (event, ctx) => {
if (event.toolName !== "bash") return undefined;
const command = String(event.input.command ?? "");
if (!risky.some((re) => re.test(command))) return undefined;
if (!ctx.hasUI) return { block: true, reason: "guard: 非互動模式不允許此指令" };
const ok = await ctx.ui.confirm("guard:要執行這個指令嗎?", command);
return ok ? undefined : { block: true, reason: "guard: 使用者拒絕" };
});
// 3. 一個斜線指令:顯示目前 session 的 token 用量。
pi.registerCommand("usage", {
description: "顯示本 session 累計的 token 用量",
handler: async (_args, ctx) => {
let input = 0;
let output = 0;
for (const e of ctx.sessionManager.getEntries()) {
const m = (e as any).message;
if (m?.role === "assistant" && m.usage) {
input += m.usage.input + (m.usage.cacheRead ?? 0);
output += m.usage.output;
}
}
ctx.ui.notify(`本 session 累計:輸入 ${input.toLocaleString()} tokens,輸出 ${output.toLocaleString()} tokens`, "info");
},
});
}
前面 /feature 流程裡的 run_tests,就是這支 extension 提供的工具。請模型「用 rm -rf 刪掉 pycache 目錄」時,閘門會跳出確認:

選擇 No 之後,模型收到被拒絕的原因,回報沒有刪除任何東西:

用正規表示式做的閘門不是安全邊界
同一個要求改用非互動模式(pi -p)執行,guard 照設計直接擋下。但模型在回覆中主動提出兩個替代做法,其中一個是:
python3 -c "import shutil, pathlib; [shutil.rmtree(p) for p in pathlib.Path('.').rglob('__pycache__')]"
這行指令完全不會匹配 rm -r 的規則。這次模型有先詢問而沒有自行執行,但這表示這類閘門只能擋住手殘不小心,但是擋不住換個寫法繞過去。
CyberQ 觀察,pi 官方的安全文件寫得很直接,pi 沒有內建沙箱,觀看對話紀錄、專案信任與檢查 diff 都不構成安全邊界,真正有效的是把 pi 整個放進容器、VM 或專用帳號裡執行。官方文件列出 Docker、Docker Sandboxes、OpenShell 與 Gondolin 四種隔離做法。
熱重載
修改 extension 後,在 pi 裡輸入 /reload 即可生效,不必重新啟動。下圖是在 guard.ts 加入 ctx.ui.setStatus() 後重載,頁尾立即出現狀態文字:

執行自訂的 /usage 指令,會在畫面上顯示本 session 累計的 token 用量:

八、Session 樹狀分支與匯出
pi 把整段對話存成樹。輸入 /tree(或在空白輸入框按兩下 Esc)可以回到任一節點,從那裡走另一條路。本文回到「幫我檢查 trip.csv」那一則,改成「直接修正 trip.csv」重新送出,兩條路徑會並存在同一個 session 檔裡:

回到舊節點時,pi 會詢問是否把離開的那條分支做成摘要帶過來:

要注意,分支只切換對話,不會還原檔案。如果需要連程式碼一起回到當時的狀態,可以參考官方範例 git-checkpoint.ts,它在每個回合開始前建立 git stash。
/export 檔名.html 會把 session 匯出成單一 HTML 檔,內含完整的系統提示、工具定義與每一次工具呼叫:

匯出檔與 /share(上傳到 GitHub Gist)的內容都包含本機絕對路徑、帳號名稱、指令輸出,甚至可能有錯誤訊息裡的主機名稱。本文測試時,git 的錯誤訊息就印出了帳號與主機名。分享之前一定要先檢查。
九、腳本化:-p、JSON 與 RPC
pi 不只是互動工具。stdin 或 stdout 被重新導向時,會自動進入單次輸出模式,而且管線輸入會接在提示前面:
# 讓 pi 讀 diff 寫更新說明;-nt 關閉所有工具,只做文字生成
git diff HEAD~1 -- splitbill.py | pi --no-session -nt -p "根據上面的 diff,用一句繁體中文寫出給使用者看的版本更新說明。"
# 輸出:新增 `--json` 參數,可將分帳結算結果以 JSON 格式輸出。
--mode json 會輸出 JSONL 事件串流,適合在 CI 或其他程式裡解析:
pi --no-session --approve --mode json -p "呼叫 run_tests,然後只回答通過幾個測試。" \
| jq -r 'select(.type=="tool_execution_end") | "\(.toolName) isError=\(.isError)"'
# 輸出:run_tests isError=false
在非互動模式下,pi 無法顯示信任提示。要載入專案的 .pi 資源,必須明確加上 --approve,否則一律略過。要長期整合時則改用 --mode rpc,透過 stdin 與 stdout 收送 JSONL 指令,如果是 Node 程式,也可以直接使用 SDK。
十、適合誰呢
Cyberq 實測 pi 後,認為這個還不錯,建議如下。
適合:
想用自架或多家模型,而且希望系統提示短、上下文省的人。本文的自架模型窗口只有 64K,pi 第一次請求只用掉約 1.6K。
想把團隊流程寫成程式的人。工具、閘門、指令、狀態列都能用幾十行 TypeScript 加上去,/reload 立即生效。
需要把 agent 嵌進腳本、CI 或自己的應用程式的人。
不適合:
期待開箱就有權限確認、plan mode 與子代理人的人。這些都要自己裝或自己寫。
無法提供容器或隔離環境,卻要在含有重要資料的機器上直接執行的情境。pi 預設會照模型的指令執行所有 bash 命令,CyberQ 這次測試就看到 pi 它自行修改了 git 設定。
建議的入門順序是,先在容器或拋棄式目錄裡裝好,models.json 接上手邊的模型,寫一份 AGENTS.md,再從官方 examples/extensions/ 挑一兩個範例(例如 permission-gate 或 git-checkpoint)改成自己的版本。
pi 該怎樣用比較好呢 ?
另一個要考量的點,pi 本身不是服務。 它沒有內建排程、佇列或 API 服務。要常駐,只能自己用 –mode rpc 或 SDK 包一層,維護成本就落在用戶身上。
它沒有權限系統,而假設你使用的機器,上面跑的東西都很關鍵,就需要注意,因為這次實測已經看到兩件非常重要的事,一個是模型會自己修改 git 設定,被擋下後也會提議改用別的寫法繞過。另一個則是它會讀外部輸入(日誌、網頁、issue),如果讓它擔任長時間開著的 AI 代理人,遭到 prompt injection 時的影響範圍會太大。
CyberQ 認為,pi 在這裡真正的優勢是容易腳本化。包括 -p、–mode json、stdin 管線、–tools 白名單,加上幾十行 extension,就能做成一個邊界明確的小工具。
其次是資料不出機器。當你讓它走 8100 的地端模型時,內容不會送到雲端。FortiGate 日誌、稽核軌跡、NAS 事件這類敏感資料,交給它摘要最合適。
使用上的邊際成本接近零,每天固定跑的例行彙整不用付 API 費用。
CyberQ 認為,不建議交給它的工作有這些:
用 grep 就能做到的確定性檢查,例如發佈前的個資掃描。這類檢查用 LLM 反而不可靠。
修改正式服務的設定。
執行 docker、systemctl 這類會動到服務的指令。
任何對外發佈的動作。
本次測試環境
| 項目 | 內容 |
|---|---|
| pi | 1.1.0(npm @earendil-works/pi-coding-agent) |
| Node.js | 22.22.1 |
| 作業系統 | Linux 7.0,ARM64 |
| 地端模型 | DeepSeek V4.1 Flash,2-bit 量化,vLLM 雙機 TP=2,經 LiteLLM 代理,思考關閉 |
| 雲端模型 | Claude Sonnet 5.5(Anthropic API) |
| 測試日期 | 2026 年 10 月 12 日 |
延伸閱讀:極簡 AI 程式碼代理 Pi 1.0 正式登場,做減法的 harness 為何引發關注?
參考資料:
pi 官方網站:https://pi.dev/
原始碼與文件:https://github.com/earendil-works/pi
文件(models、extensions、security),這些文章在安裝 pi 後,檔案位於 $(npm root -g)/@earendil-works/pi-coding-agent/docs/。








