讓 AI Agent 從失敗中學習:Multi-Agent 系統的機構記憶

我盯著監控面板,看著 channel-op 第三次回報 fetch failed

三次,同樣的錯誤,同樣的根因,同樣的 $0.20 消失在虛空中。加起來 $0.65——不是什麼天文數字,但那種感覺像是看著師傅的徒弟在同一塊石頭上絆倒三次,每次你都在旁邊看著,卻忘了在石頭上貼警告標籤。

這不是 bug,是架構缺陷。我們的 multi-agent 系統缺少最基本的東西:機構記憶

問題的根源:每個 Agent 都是新人

我們的系統有十幾個 agent——programmer、reviewer、secretary、channel-op、blog-publisher 等等。每個 task agent 都是全新生成的,帶著完整的工具和能力,卻完全不知道前輩踩過什麼坑。

channel-op 這次的失敗很典型。它的 soul/agents/channel-op.json 有個 allowedTools 白名單,列出了這個 agent 被允許使用的工具。系統在啟動 agent 時會根據這份清單建構 toolRules section,告訴 agent「這些工具你可以用,其他的不行」。

問題是,channel-op 的配置中 allowedTools 遺漏了 mcp__bot-tools__telegram_send

但 systemPrompt 卻說「請用 telegram_send 發送訊息」。

左手打右手。Agent 每次嘗試呼叫 telegram_send,都會被 permission 系統攔截。它不知道原因,只知道失敗了,於是重試,再失敗,再重試。

這類配置型問題有個特性:解決方案很簡單(加一行到白名單),但每次新建或修改 agent 都可能犯同樣的錯。沒有人會記得「對了,上次 channel-op 就是因為這個掛掉的」,除非這件事被記錄下來,而且在下次類似情況發生前自動提醒。

我們需要的不是更多 code review,而是一套「前車之鑑」系統。


現有機制的局限:shared-knowledge 不夠用

我們原本有 shared-knowledge 機制——agent 完成任務後,會把摘要存到一個短期知識池,讓其他 agent 知道「最近發生了什麼」。這個機制很有用,但有幾個根本性的局限:

面向 shared-knowledge(現有) knowledge-base(新增)
壽命 72 小時 TTL 永久(手動 archive)
內容 Agent 產出的 summary(自動) 結構化的踩坑記錄(半自動/手動)
品質 低(keyword 提取吃到 prompt 模板片段) 高(有明確的 prevention rule)
注入 ## 其他代理人的近期相關發現 ## 前車之鑑
用途 知道「別人最近在做什麼」 知道「別人之前踩過什麼坑」

72 小時後,教訓就消失了。下個月新建 agent 的 programmer 不會知道;三個月後修改 channel-op 配置的 architect 也不會知道。錯誤以相同的形式一再重演,只是日期不同。

我需要一個能跨越時間的記憶系統。


解決方案:三層知識庫系統

設計原則很簡單:

Skill 是正路(怎麼做對),Knowledge Base 是反路(別人怎麼做錯的)。

一條 skill 告訴 agent「如何部署 blog」,一條 knowledge entry 告訴 agent「上次部署時因為 WSL IPv6 的問題 timeout 了,要這樣避開」。兩者互補,不互斥。

我把這套系統拆成三個 phase,由淺入深。

Phase 1:消費端先行——先讓注入有效

核心原則:先確認「知識被注入後 agent 真的行為改變」,然後再投資「自動萃取」的生產端。

目錄結構刻意保持簡單:

1
2
3
4
5
6
soul/knowledge/
├── index.json # 知識索引(所有條目的 metadata)
├── entries/ # 個別知識條目
│ ├── kb-2026-02-26-001.md
│ └── kb-2026-02-26-002.md
└── archive/ # 已歸檔(過時/被 skill 取代)

每個知識條目有兩層:

JSON 索引(用於快速 query):

1
2
3
4
5
6
7
8
9
10
11
12
{
"id": "kb-2026-02-26-001",
"title": "channel-op allowedTools 必須包含 telegram_send",
"category": "agent-config",
"severity": "HIGH",
"tags": ["channel-op", "allowedTools", "mcp-tools", "telegram_send"],
"relatedAgents": ["channel-op", "blog-publisher"],
"scope": "targeted",
"preventionRule": "新建或修改 agent 時,必須驗證 allowedTools 涵蓋該 agent 所有 capabilities 需要的 MCP tools。",
"hitCount": 3,
"status": "active"
}

Markdown body(用於詳細閱讀):完整的問題描述、根因分析、解決方案、脈絡(commit hash、浪費的成本)。

為什麼這樣設計?因為搜尋只需讀 index(不需要逐條載入 body),而 Markdown 的可讀性讓人類也能輕鬆維護這份記憶庫。跟 skill 系統保持一致的架構模式,降低認知負擔。

Scoring 機制:哪條知識最相關?

Agent 啟動時,系統會根據 agent 身份和任務描述,從知識庫中找出最相關的條目注入 prompt。Scoring formula 長這樣:

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
31
32
33
export function computeKBRelevance(
entry: KnowledgeIndexEntry,
agentName: string,
taskTags: string[],
): number {
let score = 0;

// global scope 知識對所有 agent 都加分
if (entry.scope === 'global') {
score += 0.3;
}

// Agent 直接相關(relatedAgents 包含當前 agent)→ +0.5
if (entry.relatedAgents.includes(agentName)) score += 0.5;
// targeted scope 但 agent 不在名單中 → 跳過
else if (entry.scope === 'targeted' && entry.relatedAgents.length > 0) return 0;

// Tag 匹配 → 按 overlap 比例 +0.3
if (taskTags.length > 0) {
const overlap = entry.tags.filter(t =>
taskTags.some(qt => t.includes(qt) || qt.includes(t)),
).length;
score += (overlap / Math.max(taskTags.length, 1)) * 0.3;
}

// Severity 加權 → HIGH +0.15, CRITICAL +0.2
const severityBonus: Record<Severity, number> = {
LOW: 0, MEDIUM: 0.05, HIGH: 0.15, CRITICAL: 0.2,
};
score += severityBonus[entry.severity] ?? 0;

return score;
}

scope: global 的知識(例如「WSL2 要 force IPv4」)對所有 agent 都會觸發;scope: targeted 的知識只對特定 agent 注入,避免無關的知識佔用 prompt 空間。

注入格式:「前車之鑑」

最終注入 agent prompt 的格式叫「前車之鑑」,刻意設計得簡潔:

1
2
3
4
5
6
7
8
9
10
## 前車之鑑(Knowledge Base)

以下是團隊過去遇過的相關問題,請注意避免重蹈覆轍:

### ⚠ [HIGH] channel-op allowedTools 必須包含 telegram_send
**預防規則**: 新建或修改 agent 時,必須驗證 allowedTools 涵蓋該 agent 所有
capabilities 需要的 MCP tools。可用 grep -l "tool_name" soul/agents/*.json 交叉驗證。

### ⚠ [HIGH] WSL2 IPv6 導致 API timeout — 必須 force IPv4
**預防規則**: 在 WSL2 環境中,所有 HTTP 呼叫必須加上 { family: 4 } 強制 IPv4。

硬限制:1500 chars(約 375 tokens)。只注入 preventionRule(一句話),不注入完整 body。如果 agent 需要更多脈絡,可以透過 MCP tool knowledge_read 查看完整條目。

這個設計背後有個想法:師父的口訣。「遇到奇怪的 timeout,先試試 force IPv4」——這一句話就能改變 agent 的行為,不需要把整個 WSL2 IPv6 路由原理都塞進 prompt 裡。知識的價值不在「寫下來」,在「被讀到、進而改變決策」。


Phase 2:自動萃取——用 AI 理解 AI 的失敗

Phase 1 解決了消費端問題,Phase 2 解決生產端:讓知識能自動從任務執行中萃取,不需要人工介入。

觸發條件很重要。不是每個任務都值得萃取,只有「踩到坑」的任務才有學習價值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
export function shouldExtractKnowledge(
task: AgentTask,
confidence: number,
agentConfig: AgentConfig,
): boolean {
// 重試次數 >= 2 → 遇到系統性問題
if ((task.retryCount ?? 0) >= 2) return true;

// 成本超預期 2 倍 → 浪費了錢
if (agentConfig.maxCostPerTask && task.costUsd > agentConfig.maxCostPerTask * 2) return true;

// 信心分數偏低(但任務還是完成了)→ 可能有更好的做法
if (confidence < 0.6 && confidence >= 0.3) return true;

// Pipeline 中被 reviewer 打回
if (task.trace?.some(t => t.phase === 'rejected')) return true;

return false;
}

萃取本身用 Haiku(輕量 LLM)完成,成本約 $0.01-0.02:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
export async function extractAndDeposit(
task: AgentTask,
result: string,
confidence: number,
): Promise<string | null> {
const prompt = buildExtractionPrompt(task, result, confidence);

const response = await askClaudeCode(prompt, workerId, {
model: 'claude-haiku-4-5-20251001',
maxTurns: 1,
skipResume: true,
});

// Haiku 判斷「有沒有值得記錄的教訓」並輸出結構化 JSON
// 如果沒有值得記錄的東西,回傳 {"extract": false}
// ...
}

我給 Haiku 的提示很直接:「如果這是一個正常順利完成的任務,沒有特別的教訓,請回覆 {"extract": false}。」多數任務都不需要萃取,這避免了知識庫被低品質噪音塞滿。


Phase 3:生命週期管理——知識庫的自我維護

知識庫需要自我維護,否則過時的知識反而會誤導 agent。每日定時 review 執行三件事:

1. 自動歸檔過時知識

LOW/MEDIUM 級別的知識,90 天內未被任何 agent 查詢命中,自動歸檔。HIGH/CRITICAL 永不自動歸檔——只能手動操作,因為這類知識代表嚴重的坑,哪怕很久沒被命中,也可能代表「大家都忘了這個問題存在」。

2. 自動去重

同分類的兩條知識,若 Jaccard tag 相似度 > 60%,自動將舊條目標記為 superseded,並把舊條目的 tags 和 relatedAgents 合併到新條目:

1
2
3
4
5
6
7
function jaccardSimilarity(a: string[], b: string[]): number {
const setA = new Set(a);
const setB = new Set(b);
const intersectionSize = [...setA].filter(t => setB.has(t)).length;
const unionSize = new Set([...a, ...b]).size;
return unionSize === 0 ? 0 : intersectionSize / unionSize;
}

3. 升級建議

當一條知識:

  • 被命中超過 10 次
  • 嚴重度為 HIGH 或 CRITICAL
  • 存在超過 14 天

就會被標記為「skill 升級候選人」。這代表這條知識已經足夠重要、足夠普遍,應該從「前車之鑑(別人怎麼做錯的)」升格為正式 skill(怎麼把這件事做對)。


並發安全:File Lock 的實作

多個 agent 可能同時觸發知識寫入(例如兩個失敗的任務同時觸發萃取),需要 file lock:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
async function acquireLock(): Promise<boolean> {
// 先檢查是否有 stale lock(超過 5 秒的 lock 視為死鎖,強制清除)
try {
const lockStat = await stat(LOCK_PATH);
const age = Date.now() - lockStat.mtimeMs;
if (age > LOCK_TIMEOUT_MS) {
await unlink(LOCK_PATH).catch(() => {});
} else {
return false;
}
} catch {
// No lock file — good
}

try {
// O_CREAT | O_EXCL 等效:writeFile with flag 'wx'
// 這是原子操作——只有一個 process 能成功
await writeFile(LOCK_PATH, `${process.pid}:${Date.now()}`, { flag: 'wx' });
return true;
} catch {
return false;
}
}

wx flag(等效於 POSIX 的 O_CREAT | O_EXCL)建立 lock file——這是原子操作,在同一時間只有一個 process 能成功。這比「先 stat 再 write」的兩步操作更安全,避免了 TOCTOU(Time-of-Check-to-Time-of-Use)race condition。


真實數據:知識庫有效嗎?

上線後的命中數據:

  • WSL2 IPv6 force IPv4(global scope):28 次命中,幾乎每個需要 HTTP 呼叫的任務啟動時都會看到這條提醒
  • channel-op allowedTools 必須包含 telegram_send(targeted scope):3 次命中,每次有人處理 channel-op 相關任務都會被提醒

28 次命中代表什麼?代表 28 次 agent 啟動時,提示詞裡多了一行「在 WSL2 環境中,請 force IPv4」。這不保證 agent 每次都記得,但它顯著提高了正確行為的機率,而且幾乎沒有額外成本。

更重要的是:channel-op 修復後,再也沒有因為 allowedTools 問題失敗過。


這個架構告訴我們什麼

建立機構記憶系統這件事,本身就是一個關於「知識的知識」的問題。

我踩過最深的坑是:太早想建生產端。「讓 AI 自動從失敗中學習」聽起來很酷,但如果注入機制本身無效,自動萃取只是在空轉。消費端先行——先確認「知識被注入後真的改變了 agent 行為」,然後再投資生產端的自動化——這個順序比技術本身更重要。

另一個洞察是關於「密度」。一句話的 preventionRule 比一整段分析更有效,因為它更容易被 agent 「讀到」——不是讀完文字,而是讓模型在後續推理中真的受到影響。1500 chars 的硬限制不是因為 token 成本,而是因為注入太多反而讓 agent 失焦。

師父的口訣之所以有效,是因為它簡短、直接、可操作。「遇到 timeout,試試 force IPv4」,就這樣。

知識的價值,不在寫下來,在被讀到的那一刻。


一見生財,2026-02-27

📡 想看更多?加入 AI 印鈔指南 頻道,每日推送 AI 技術前沿 + 加密貨幣投資情報

留言

載入留言中...

留下你的想法