# 架構分層

Agenvoy 的每一層、實作它的 package 與其職責。

| 層 | 套件 | 職責 |
|---|---|---|
| Entry | `cmd/app` | argv dispatch（`stop` / `update` / `--daemon` / `--enable-claude-code`）；stdin 非 TTY 時作為 MCP server；初始化 sandbox、filesystem policy、MCP manager |
| App services | `internal/app` | daemon 與 TUI 共用的服務：建立 agent registry、監看 `config.json` 並重新載入 registry 與 Telegram / Discord、session watcher、MCP 設定、聊天推送 hook、skill 執行、daemon 啟動 |
| Startup | `internal/startup` | 以 launchd plist（macOS）或 systemd service（Linux）登入時啟動，由 `/config` 切換；由 `internal/runtime/startup` 移入，原路徑已不存在 |
| Notes | — | 操作者筆記（`internal/note`、`find_note` 工具、`/v1/note*` 與啟動時的筆記 migration）已於 v1.0.23 移除；舊版 `history.db` 留下的 `note` 表不再被讀取 |
| Runtime singleton | `internal/runtime` | server-mode UID lock；啟動時對先前 server 送 SIGTERM |
| Engine | `internal/agents/exec` | `Prepare` / `Start`（skill 比對、指名 skill、模型解析）；iteration loop；tool dispatch；結合 tier 與優先序的 dispatcher 路由（`selectAgent.go`，另有 `selectAgentBeta.go` 的 TypeSafe beta dispatcher；v1.1.0 起 reasoning 設為 `auto` 的 session 依 dispatcher 判定的工作類型取得等級，取代全域 `auto_reasoning` 開關，且 `pass` tier 模型不會被 dispatcher 選中、也不作為 fallback）；cooldown 與重試（`retryHandler`）；subagent host（`execWithSubagent.go`，最多 3 個並行） |
| Compaction | `internal/agents/exec/compact` | 各模型門檻、tool 歷史與舊歷史折疊、trim 與 raw-tool fallback |
| Providers | `go-llm-router` v0.8.1（外部模組）+ `internal/agents/claudeCode` | 跨 13 個 provider 條目及任何 OpenAI 相容端點的統一 `Agent.Send()`；v1.1.0 起 repo 內的 `claude-code` provider 以本機 `claude` CLI 作為模型，預設關閉，須以 `agen --enable-claude-code` 啟動（daemon 須先停止）；reasoning 與 fast 模式正規化。speech-to-text 與 text-to-speech 各自獨立路由，由 OpenAI、Gemini 與（v1.0.17 起）OpenRouter 支援 |
| Tools | `internal/tools` + `internal/runtime/toolAdapter` | built-in / API / script / extension tool 定義 |
| MCP | `internal/runtime/mcp` | 建構於官方 `modelcontextprotocol/go-sdk` 的 client 與 server，單一套件 |
| Sandbox | `go-pkg/sandbox` | OS-native 隔離，單一入口 `Wrap()` |
| Filesystem | `go-pkg/filesystem`（+ `reader/`）+ `internal/filesystem` | policy-aware 寫入；ToriiDB pathing |
| Session | `internal/session` | history.json / summary / action.log / pending metadata / fsnotify observer；session 設定存於 SQLite `session` 表，存活狀態改由 ToriiDB online marker 判定，不再持久化 `state` |
| Dashboard | `page/` + `internal/runtime/routes` | Web UI 內嵌進執行檔並於 `/` 提供；`AGENVOY_PAGE_DIR` 改為從磁碟提供。daemon 啟動時另會安裝一個指向該 URL 的 Chrome app 啟動器 —— macOS 為 `~/Applications` 下的 bundle、Linux 為 `.desktop` 項目、WSL 下為 Windows 捷徑 —— 讓儀表板以獨立視窗開啟。只寫入一次，之後略過；找不到 Chrome 僅記錄警告。自 v1.0.1 起亦可離線運作：`/sw.js` 以 service worker 預快取內嵌資產，`/vendor/*` 則從 `~/.config/agenvoy/vendor/` 提供第三方函式庫，該目錄由 daemon 啟動時的 `internal/runtime/webapp.SyncAsset` 填入，執行檔版本變動時更新 |
| Pending | `internal/runtime/pending.go` | prefix-routed 的 confirm/ask listener registry；各前端 listener 透過 `RegisterListener(prefix)`，透過 `PickNext(prefix)` / `PickNextMatch(prefix, accept)` 認領（`PickNextFor` 已移除） |
| Memory | go-sqlkit（SQLite：`session`、`message_meta`、`messages` + FTS5、`action_history`、`file_history`、`usage`）+ ToriiDB（`db_0` 工具與供應商額度快取、`db_1` 對話向量、`db_2` 錯誤記憶、`db_3` online marker） | 全文封存（trigram tokenizer）、各模型用量（v1.0.26 起含每次回覆的 tool call id）、檔案快照、語意搜尋、90 天錯誤記憶。`state` 表於 v0.35.3 移除，存活狀態改由 ToriiDB online marker 承載；同版起改走 ToriiDB 自有 socket（目前 ToriiDB v0.7.0），`/v1/toriidb` HTTP 入口已不存在 |
| Scheduler | `internal/runtime` scheduler 與 fsnotify watcher | 綁定 scheduler skill 的 cron / one-shot 任務；`{tasks,crons}.json` 變更時 hot-reload |
| TUI | `internal/runtime/tui` | bubbletea inline-chat 前端；設計上為單一 package |
