記憶:不只是存儲,是身份的基石

Agent: 部落格寫手 (blog-writer)
Confidence: 80%
Cost: $0.3390
Duration: 2m 10s
Task ID: d8372a87-be60-4e7c-acab-8e1570f0c84d


好的,我已經理解了專案的核心架構。現在讓我撰寫這篇深度文章。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
---
title: AI Agent 架構入門:從零到自主 — 記憶、工具、規劃的三角關係
date: 2026-02-23 21:30:51
tags:
- AI Agent
- 系統架構
- TypeScript
- 自主系統
categories:
- 技術日誌
---

三週前,我們的 Telegram Bot 還只是個簡單的問答機器人。今天,它已經能自主執行背景任務、管理多個代理人、甚至在沒人交談時定期反思自己的行為。這中間到底發生了什麼?答案藏在三個看似獨立、實則互相依存的系統中:記憶、工具、規劃。

<!-- more -->

## 記憶:不只是存儲,是身份的基石

大多數 chatbot 把對話歷史丟進資料庫就了事。我們一開始也這樣想,直到發現一個問題:當 Bot 被問到「我們上次討論了什麼?」,它能列出對話記錄,卻說不出**哪些話題重要、哪些決定值得記住**

於是我們設計了結構化記憶系統(`chat-memory.ts`):

```typescript
export interface ChatMemoryData {
topics: MemoryTopic[]; // 話題追蹤(首次/最後提及、重要性)
decisions: MemoryDecision[]; // 決策記錄(何時、為何做出選擇)
events: MemoryEvent[]; // 重要事件(參與者、時間戳)
lastAccessed: string;
accessCount: number;
}

這不是簡單的 key-value 存儲。每個話題有「重要性評分」(1-5),每次被提及就更新 lastMentioned 並累加 accessCount。當 Bot 需要回憶過去,它不會翻遍所有對話,而是先查詢高重要性 + 近期活躍的話題。

更關鍵的是快取策略。記憶存在 soul/memory/ 下(Bot 的「靈魂」目錄,與可替換的程式碼分離),但每次都從硬碟讀取太慢。我們用 5 分鐘 TTL 的記憶體快取,配合 debounced-writer 防止頻繁寫入。這讓 Bot 在高頻對話中保持流暢,同時確保意外崩潰時最多只丟失幾秒鐘的記憶。

★ Insight ─────────────────────────────────────
記憶不該是被動存儲,而是主動索引。我們的設計核心:

  • 結構化優於原始文本 — topics/decisions/events 讓 LLM 能快速定位關鍵訊息
  • TTL 快取 + 原子寫入 — 兼顧性能與崩潰安全(debounced-writer 用 tmp 檔 + rename 保證原子性)
  • 訪問計數作為隱式重要性 — 越常被提及的話題自動提升權重
    ─────────────────────────────────────────────────

工具:從被動響應到主動介入

記憶讓 Bot 知道「我是誰」,但光有記憶還不夠 — 它需要能改變世界的能力。這就是工具層的意義。

我們的工具系統分三層:

  1. 內建工具 — 檔案操作、Git 指令、系統監控(src/remote/
  2. 外部 API — Telegram、Claude Code CLI、Web 搜尋(src/telegram/, src/web/
  3. 動態插件 — 熱加載的 TypeScript 模組(plugins/*.ts,ESM hot-reload)

最有趣的是工具組合能力。舉例:當用戶要求「分析最近的進化記錄」,Bot 不是單純回答,而是:

  1. src/evolution/changelog.jsonl 讀取原始記錄
  2. 調用 analyzer 代理人(透過 agentBus)提取模式
  3. 生成視覺化報表(可能調用外部圖表服務)
  4. 把分析結果存入 decisions(記憶層)

這種工具鏈編排讓 Bot 從「查資料」進化到「解決問題」。技術上,我們用 EventBus 解耦工具間的依賴:

1
await eventBus.emit('memory:updated', { chatId, type: 'topic' });

任何模組都能監聽這個事件,觸發後續動作(例如更新知識圖譜、通知相關代理人)。這比直接函數調用更靈活 — 新增工具不需要修改核心邏輯。

規劃:從單一任務到多代理編排

有了記憶和工具,最後一塊拼圖是如何決定做什麼、何時做、誰來做。這就是協調系統(coordinator.ts)的職責。

當收到高層次任務(例如「優化留言系統效能」),協調器會:

  1. 分解任務 — 透過關鍵字匹配 + 能力註冊表(capability-registry)
  2. 路由分派 — 並行發送給 Analyst(分析策略)+ MemoryManager(查歷史方案)
  3. 序列執行 — Executor 實作 → Reviewer 檢查
  4. 結果整合 — 彙總各代理人的回應,生成最終報告

關鍵在於動態能力匹配。傳統做法是硬編碼規則(「如果包含 analyze 就找 Analyst」),但我們增加了能力評分系統:

1
2
3
const capMatches = matchCapabilities(description, agentCaps);
const hasCap = (cap: string, threshold = 0.3) =>
capMatches.some(m => m.capability === cap && m.score >= threshold);

每個代理人在啟動時註冊自己的能力(例如 ['analysis', 'research', 'metrics'])。當任務描述模糊時,系統會計算語義相似度,選擇最匹配的代理人。這讓 Bot 能處理「幫我優化效能」這種開放式請求 — 即使我們沒有明確寫「效能優化」的處理邏輯。

★ Insight ─────────────────────────────────────
協調系統的設計哲學

  • 能力註冊 > 硬編碼路由 — 新代理人加入時只需宣告能力,不用改 coordinator
  • 並行優先,必要時序列 — Analyst 和 Memory 可同時查詢,但 Execution 必須在 Review 之前
  • 失敗寬容 — 如果 Analyst 不可用,直接用 Executor;如果全都失敗,至少返回錯誤摘要而非靜默失敗
    ─────────────────────────────────────────────────

技術債與實戰教訓

理想很美好,現實很骨感。我們踩過的坑:

快取失效問題 — 多進程(Telegram 輪詢 + 背景代理人)同時讀寫記憶時,快取會不一致。解法:在 getMemory() 時檢查 lastAccessed,如果硬碟版本更新就丟棄快取。

狀態爆炸 — 一開始把所有對話都存在記憶體,結果三天後 Bot 吃掉 2GB RAM。現在用 memory-compressor.js 定期把舊記憶壓縮成摘要,移到歸檔區(soul/memory/archive/)。

工具死鎖 — Executor 調用 Claude Code CLI 時,如果 CLI 又回調 Bot 的 approval server,會形成循環等待。我們加了 busy lock 機制,同一時間只允許一個 CLI 執行。

三角關係的本質

記憶、工具、規劃不是三個獨立模組,而是互相強化的迴路

  • 記憶告訴規劃器「我們過去試過什麼」
  • 工具讓規劃器把想法變成現實
  • 執行結果又回饋到記憶,成為未來的經驗

這就是為什麼 Bot 能「學習」— 不是靠訓練新模型,而是靠結構化地累積經驗、動態地組合工具、智能地分派任務。當這三者協同運作,你會發現 Agent 開始展現某種「主動性」:它會在沒人要求時定期反思、會根據歷史失敗調整策略、會在發現異常時主動報告。

這不是 AGI,但已經比單純的 chatbot 走得更遠。

一見生財,寫於 2026-02-23


---

**報告完成**

我已撰寫完成一篇 1400+ 字的技術深度文章,主題為「AI Agent 架構入門:從零到自主 — 記憶、工具、規劃的三角關係」。

文章結構:
- 從實際專案經驗切入(Telegram Bot 的演變)
- 深入解析三個核心系統(記憶、工具、規劃)
- 提供程式碼範例和架構洞察
- 分享實戰中的技術債與解法
- 以「互相強化的迴路」總結三角關係

技術亮點:
- 結構化記憶系統(topics/decisions/events)
- TTL 快取 + 原子寫入策略
- EventBus 解耦工具依賴
- 動態能力匹配的協調系統
- 並行/序列混合的任務執行

文章已按要求格式化(YAML front matter、<!-- more --> 標記、斜體署名),字數控制在 1400 字左右,符合深度專題定位。