一台機器上要同時養幾個 agent,最直接的做法是開多個 profile。Hermes 把組態、記憶、排程與 session 各自分開,切換只需要一個旗標。這種隔離的涵蓋範圍決定了其中一個 profile 能掛上多高權限的工具。以下逐項確認邊界落在哪裡。
hermes profile 自己的說明是「多個互相隔離的 Hermes 實例」。每個 profile 是根目錄底下 profiles/<名稱> 的一整棵目錄,結構與根目錄同構:
| 路徑 | 內容 |
|---|---|
config.yaml |
模型、工具與 MCP 掛載清單 |
.env |
該 profile 專用的金鑰與平台 token |
SOUL.md |
身分宣告 |
memories/ |
跨 session 保存的狀態 |
sessions/、state.db |
對話歷史與全文檢索索引 |
cron/ |
排程定義 |
skills/、hooks/ |
技能與 hook |
gateway.pid、gateway_state.json |
該 profile 的 gateway 執行狀態 |
切換用 -p 或 --profile,兩個旗標等效。在同一台機器上對不同 profile 讀同一個鍵,拿到的是各自的值:
hermes config get model.default # 目前作用中的 profile
hermes -p work config get model.default # 指定 profile
建立 profile 時會順手在 ~/.local/bin 放一支同名的包裝腳本,內容是帶好旗標再呼叫 hermes,於是 work <指令> 等同 hermes -p work <指令>。加 --no-alias 可略過這支腳本。
| 方式 | 複製什麼 |
|---|---|
| 無旗標 | 空 profile,只帶內建技能 |
--clone |
組態、身分與記憶索引 |
--clone-all |
來源 profile 的完整副本,扣掉歷史與基礎設施 |
--clone 的清單寫死在原始碼裡,共五個檔案:
config.yaml、.env、SOUL.md
memories/MEMORY.md、memories/USER.md
註解說明記憶檔為何列在這一組:它們與 SOUL.md 同屬 agent 的身分,複製 profile 時一併帶走才有延續性。
--clone-all 排除兩組東西。
第一組是歷史類,任何來源都排除:
state.db 與它的 wal、shm 檔sessions、backups、state-snapshots、checkpoints
原始碼給的理由有兩個,一是在新 profile 裡還原來源 profile 的快照會把來源的狀態整個救回來,二是這些目錄可以膨脹到數十 GB。
第二組是基礎設施類,只在來源是根目錄時排除:
hermes-agent、node_modules、bin
profiles、.worktrees
複製完再刪掉 gateway.pid、gateway_state.json、processes.json 三個執行期檔案,避免新 profile 一開機就以為自己有 gateway 在跑。
另外 --no-skills 會在 profile 根目錄寫一個 .no-bundled-skills 標記檔,hermes update 的全 profile 技能同步看到這個檔案就跳過。
複製的範圍是身分與組態,歷史留在原 profile
profile 的實作方式是把 HERMES_HOME 指到 profiles/<名稱>。這條邊界管的是 Hermes 自己讀寫的狀態,工具子行程的權限由另外三個組態值決定:
| 鍵 | 預設值 | 效果 |
|---|---|---|
terminal.backend |
local |
工具指令在本機直接執行 |
terminal.cwd |
. |
工作目錄是啟動 hermes 時所在的目錄 |
terminal.home_mode |
auto |
本機安裝維持作業系統使用者的真實 HOME |
三個值合起來的結果是:工具子行程以目前這個作業系統使用者的身分執行,看到的是完整的家目錄與完整的檔案系統。一個 profile 裡的 agent 讀得到另一個 profile 的 .env,也讀得到這台機器上該使用者讀得到的任何檔案。
SOUL.md 裡寫「只在某個目錄底下工作」是 prompt 層的指示,遵守與否取決於模型,越界的讀寫在檔案系統層照常執行。
terminal.backend 換掉才會出現檔案系統邊界。除了 local 之外,Hermes 認得的遠端 backend 有 docker、singularity、modal、daytona、ssh、managed_modal、vercel_sandbox。
遠端 backend 建立的沙箱從空的檔案系統開始。 要讓 agent 用得到憑證檔、技能目錄與上傳快取,得在建立沙箱時明確掛載或同步進去。Hermes 為此維護一份 session 範圍的檔案通行登記,來源有兩處:
required_credential_files
terminal.credential_files
兩個 Docker 相關的預設值看得出取向:
| 鍵 | 預設值 | 說明 |
|---|---|---|
terminal.docker_mount_cwd_to_workspace |
false |
原始碼註解寫明預設關閉的理由是把 host 目錄傳進沙箱會削弱隔離 |
terminal.docker_network |
true |
設為 false 時 docker run 帶 --network=none,容器的對外連線全部斷開 |
隔離程度由 terminal backend 決定
Gateway 連上 Discord 或 Telegram 這類平台時會先取一把 scoped lock。鎖檔放在整台機器共用的 ~/.local/state/hermes/gateway-locks(可用 HERMES_GATEWAY_LOCK_DIR 覆寫),檔名是 <範圍>-<憑證的 SHA-256 前 16 碼>.lock。
實際的鎖檔內容:
{
"pid": 11304,
"kind": "hermes-gateway",
"start_time": 178865880330,
"hermes_home": "...\hermes",
"scope": "discord-bot-token",
"identity_hash": "<16 碼雜湊>",
"metadata": {"platform": "discord"},
"profile": "<profile 名稱>"
}
檔名只留雜湊的前 16 碼,token 本身留在 .env。 兩個 profile 填同一個 token 才會算出同一個檔名而相撞,填不同 token 的兩個 profile 可以同時跑 gateway。
鎖被占用時的錯誤訊息會指名持有者是哪一個 profile,並附上停掉它的指令。原始碼註解說明為何要多印這個名稱:scoped lock 是整台機器共用的,持有者可能是另一個 profile 的 gateway,只給 PID 時,維運者要另外查出它對應哪一個 profile。
這把鎖放在 HERMES_HOME 外面,因為被保護的資源是平台那一側的連線。同一個 bot token 在平台那一側只對應一條連線,兩個 gateway 同時連上去,同一則訊息會被處理兩次。
殘留鎖檔在下一次取鎖時會被查核。 取鎖時做兩道查核:
兩者都相符才認定是有效持有者。PID 被系統回收再指派給別的行程時,指紋隨之改變。

這台機器的 gateway-locks 目錄裡躺著兩個鎖檔,Discord 與 Telegram 各一把,記錄的 PID 是 11304、時間戳是九天前,登記的 profile 也不是現在作用中的那一個。那個行程早就結束了,鎖檔還在。
鎖檔留著,後續啟動仍會經過 PID 與啟動時間的驗證。直接看這個目錄會讀成現在有 gateway 正在跑,而且跑的是鎖檔上登記的那個 profile,實際作用中的是另一個。
要知道哪個 profile 的 gateway 正在跑,看 hermes profile list 的 Gateway 欄位,它逐一顯示 running 或 stopped。
鎖檔記錄的是憑證占用,行程存活由 PID 與啟動時間決定,正常關閉時兩者一致,強制終止時鎖檔會留下
Gemini provider 的組態,以及原生 API 與 OpenAI 相容層的差別。