前幾篇已經分別看過 Codex、Claude 與 OpenClaw 的 Memory。
現在的 Agent Framework 早就不是「每次對話結束就失憶」:
CLAUDE.md、Auto Memory 與 Memory Tool。所以看到 Hindsight 時,第一個問題反而是:
既然 Agent 自己就有 Memory,為什麼還需要另一套 Memory Service?
這篇就從這個問題開始。
Hindsight 不是 Agent Framework,而是一個獨立的 Memory Server。
對外最核心只有三個操作:
| API | 用途 | 簡單例子 |
|---|---|---|
retain() |
有新資訊值得長期保存 | 「使用者偏好早班機」 |
recall() |
找某件已知的歷史資訊 | 「他上次選哪個座位?」 |
reflect() |
綜合多筆 Memory 後整理結論 | 「這位客戶選航班最重視什麼?」 |
例如:
client.retain(
bank_id="flight",
content="User prefers morning flights"
)
client.recall(
bank_id="flight",
query="What seat did the user choose last time?"
)
client.reflect(
bank_id="flight",
query="Summarize this user's booking preferences"
)
官方 README 也是直接用這三個操作定義 Hindsight 的核心介面。
所以 Hindsight 和 MEMORY.md 最大的差別是:
它把 Memory 從 Agent 裡抽出來,做成一套獨立的資料服務。
接下來看四個比較有辨識度的設計。
如果只使用原生 Memory:
Codex
└── Codex Memory
Claude Code
└── Claude Memory
OpenClaw
└── OpenClaw Memory
三套 Agent 就自然形成三套 Memory。
Hindsight 改成:
Codex ──────┐
Claude Code ├────→ Hindsight
OpenClaw ───┘
Repo 也直接提供:
hindsight-integrations/
├── codex/
├── claude-code/
└── openclaw/
例如 Claude Code Integration 會在 lifecycle 上接:
UserPromptSubmit
↓
Hindsight recall
↓
Memory 注入 Claude Context
Claude 完成工作
↓
Stop Hook
↓
Hindsight retain
所以它真正適合的情境是:
我同時有不同 Agent,希望它們使用同一套 Memory。
例如今天從 Codex 換到 Claude Code,之後又接 LINE Agent,只要都接 Hindsight,Memory 不一定跟著 Runtime 重建。
只把所有 conversation 丟進同一個 PostgreSQL 意義不大。
更實際的問題是:
Codex 記 architecture decision
Claude 記 user correction
OpenClaw 記 preference
最後資料格式還是不一致。
Hindsight 因此讓 每個 Bank 自己設定 Extraction Rule。
而且這不是要修改 Hindsight 原始碼,而是 Bank Configuration。
官方提供三種設定方式:
Python / SDK
Bank Config API
Control Plane UI
Bank Config 文件也明確說,每個 Bank 可以獨立設定。
假設我希望 Agent 專案只記:
architecture decision
recurring problem
successful solution
failed approach
user correction
最簡單不用改 Extraction Logic,只設定 retain_mission:
client.create_bank(bank_id="engineering")
client.update_bank_config(
"engineering",
retain_mission="""
Always retain:
- architecture decisions
- recurring problems
- successful solutions
- failed approaches
- user corrections
Ignore:
- greetings
- meeting logistics
- temporary task status
"""
)
官方自己的 example 也是透過:
client.update_bank_config(
"my-bank",
retain_mission="Always include technical decisions...",
)
直接修改 Bank Config。
retain_mission 的意思是:
保留 Hindsight 原本的 Extraction Rule,只告訴它這個 Bank 特別要注意什麼。
如果連內建規則都不要,才改:
client.update_bank_config(
"engineering",
retain_extraction_mode="custom",
retain_custom_instructions="""
Extract only reusable engineering knowledge.
Allowed:
- decision
- problem
- solution
- lesson
Ignore temporary execution status.
"""
)
差異很簡單:
| 設定 | 什麼時候用 |
|---|---|
retain_mission |
大致接受 Hindsight 原本邏輯,只想改「注意什麼」 |
retain_extraction_mode="custom" |
想自己定義 Extraction Rule |
retain_custom_instructions |
custom 模式真正使用的規則 |
官方文件也特別說明,retain_custom_instructions 只有在 custom mode 下才生效。
entity_labels 也是 Bank Config,不是修改程式碼假設我希望所有 Agent 最後都使用相同分類:
preference
decision
problem
solution
lesson
可以直接:
client.update_bank_config(
"engineering",
entity_labels=[
{
"key": "memory_type",
"description": "Type of reusable project knowledge",
"type": "value",
"values": [
{"value": "preference"},
{"value": "decision"},
{"value": "problem"},
{"value": "solution"},
{"value": "lesson"}
],
"tag": True
}
]
)
Hindsight 會把 Bank 裡的 entity_labels 編成 Structured Output Schema,讓 Extraction LLM 從指定 vocabulary 裡選值,而不是讓 Agent 自己自由發明分類。
因此後面可以直接:
client.recall(
bank_id="engineering",
query="What decisions did we make?",
tags=["memory_type:decision"],
tags_match="any_strict"
)
所以這裡的流程其實是:
entity_labels
→ 定義分類規則
LLM
→ 自動分類
tag: true
→ 分類結果可以直接拿來過濾
這比每個 Agent 自己定義一套 Memory Type 更容易統一。
Hindsight 的 Recall 同時使用幾種 Retrieval。
簡單看:
| Search | 適合找什麼 | 例子 |
|---|---|---|
| Semantic | 用不同文字描述同一件事 | 問「改票為什麼失敗?」找到 fare_rule_check was skipped |
| BM25 | ID、Error Code、Function Name 等精確字詞 | BR198、ERR302、fare_rule_check |
| Graph | 找 Entity 之間的關聯 | Vivian → BR198 → fare_rule_check |
| Temporal | 問時間順序、最近一次、某期間 | 「上一次改票發生什麼?」 |
四條 Retrieval 結果最後再經過:
Semantic ─┐
BM25 ─────┤
Graph ────┼→ RRF → Cross-encoder Rerank → Result
Temporal ─┘
程式位置:hindsight-api-slim/hindsight_api/ engine/search
Recall 比較像搜尋:
「Vivian 上次選哪個座位?」
找到:
Vivian 上次選走道位。
但:
「Vivian 選航班時最在意什麼?」
答案可能散在:
喜歡早班
偏好走道
不喜歡轉機
價差 2,000 內偏好直飛
這時可以用 reflect()。
Reflect Agent 在:engine/reflect/agent.py
它會從:
Mental Model
↓
Observation
↓
Raw Fact
逐層找 Evidence,再由 LLM 整理答案。
hindsight-system-evals/ 到底是什麼?它是一套:Hindsight 自己的黑箱品質測試。
它會真的啟動或連接 hindsight-api,使用真實 LLM,從公開 Client API 去測整套 Memory 行為,而不是 import Hindsight Engine 直接測 function。
目前會測:
retain 有沒有抽錯語言 / 事實
reflect 能不能正確回答
knowledge page 更新後會不會把舊事實弄丟
source priority 有沒有選錯
refresh 花多少 token / cost
可以執行:
cd hindsight-system-evals
uv run pytest evals --full
因此它比較適合:
修改 Hindsight Config、Model 或版本後,確認 Memory 品質有沒有退化。
它不是拿來直接比較 Codex、Claude、OpenClaw native memory。
真正負責 Memory System Benchmark 的是另一個專案:
Agent Memory Benchmark
vectorize-io/agent-memory-benchmark
AMB 包含 LoComo、LongMemEval、BEAM、PersonaMem 等 Dataset,Evaluation Harness 也不綁 Hindsight internals,其他 Memory Backend 可以實作 Adapter 後跑同一套測試。
所以:
| 工具 | 用途 |
|---|---|
hindsight-system-evals/ |
測 Hindsight 自己改版後有沒有退步 |
| AMB | 用相同 Dataset / Judge 比不同 Memory Backend |
前面介紹 retain() 時一直都有一個參數:
client.retain(
bank_id="user-a",
content="..."
)
這個 bank_id 就是 Hindsight 用來決定:
這筆 Memory 屬於哪一個獨立記憶空間。
可以先把 Bank 想成一個 Memory Container:
Hindsight
├── Bank: user-a
│ ├── Memory 1
│ ├── Memory 2
│ └── Memory 3
│
└── Bank: user-b
├── Memory 1
└── Memory 2
不同 Bank 彼此隔離,所以 user-a 的 Recall 不會直接搜尋 user-b。
Bank 不需要 Model 自己判斷,也不像 Codex Worktree 一樣由 Git 結構決定,而是 Application 或 Integration 決定要使用哪個 bank_id。
最簡單可以直接指定:
client.retain(
bank_id="user-a",
content="User prefers morning flights"
)
而且 Bank 不一定要事先建立。第一次對這個 bank_id 寫入資料時,Hindsight 可以自動建立它。
如果使用 Codex、Claude Code、OpenClaw Integration,也可以設定讓 Plugin 自動依照目前的 User / Project 產生 Bank ID。
例如 Claude Code 可以設定:
dynamicBankId = true
dynamicBankGranularity = ["agent", "project"]
讓不同 Project 自動落到不同 Bank。
OpenClaw 也可以設定:
{
"dynamicBankId": true,
"dynamicBankGranularity": ["user"]
}
形成:
User A → Bank A
User B → Bank B
如果同一個 User 又有很多 Project,就不一定需要每個 Project 都建立 Bank。
例如:
User A
├── flight
├── Jira
└── Commerce
如果這三個專案之間沒有安全隔離需求,而且之後還希望一起分析,可以設計成:
Bank = user-a
然後每筆 Memory 再加 Tag:
client.retain_batch(
bank_id="user-a",
items=[
{
"content": "Runtime tool gate replaced prompt approval.",
"tags": ["project:flight"]
},
{
"content": "OAuth credential must be bound to user scope.",
"tags": ["project:jira"]
}
]
)
兩者的差異可以直接記成:
| Bank | Tag | |
|---|---|---|
| 作用 | Memory 硬隔離 | Bank 裡面的分類 |
| 誰設定 | Application / Integration 指定 bank_id |
Retain 時附上 tags,或由 entity_labels 自動產生 |
| 能不能一起查 | 不同 Bank 不能直接一次 Recall | 同一 Bank 可以跨 Tag 查 |
| 適合 | User、Tenant、安全邊界 | Project、Topic、Agent Source |
| 例子 | user-a |
project:flight、source:codex |
所以比較實際的設計可能是:
Hindsight
│
├── Bank: user-a
│ ├── project:flight
│ ├── project:jira
│ └── project:commerce
│
└── Bank: user-b
├── project:flight
└── project:jira
這樣 User A / User B 完全隔離,但 User A 自己的不同 Project 還可以一起分析。
即時分析
→ Application / Agent 分別 Recall 多個 Bank,再合併結果
大量分析
→ 用 SDK / REST API Export Memory,再交給 Python、BI 或其他 Analytics Pipeline
Hindsight 也提供 MCP,所以 Codex、Claude 或其他 MCP Client 可以直接操作:
list_banks
recall
reflect
list_memories
因此如果只是希望:
「讓 Agent 自己讀幾個 Project 的 Memory 後整理結論」
MCP / API 就已經足夠。
如果是:
「我要把幾萬筆 Memory 匯出,分析各 Project 最常見的 Failure Pattern」
則直接使用 Export API 會比較合理。
例如:
archive = await client.aexport_bank("flight")
再把不同 Bank 的結果交給自己的分析程式。
原因其實很單純:Hindsight 存的不只是文字。
還包含:
Memory
Entity
Relationship
Tag
Timestamp
Embedding
Document
Observation
PostgreSQL 可以同時處理一般結構化資料、全文搜尋,以及透過 pgvector 儲存 Vector,所以不需要另外再維護:
SQL Database
+
Vector Database
+
Graph Database
對 Hindsight 這種想把 Memory 做成獨立資料服務的架構來說,用 PostgreSQL 可以把大部分資料放在同一套 Storage 裡。
這也是它和 Markdown-based Memory 很不一樣的地方:
Codex / OpenClaw 類型
→ Memory 比較像檔案與 Agent 工作空間的一部分
Hindsight
→ Memory 比較像一套可以被多個 Agent 查詢的 Database
Hindsight 的問題其實和它的特色來自同一件事:
Conversation
↓
Integration
↓
Chunk
↓
Extraction
↓
Entity
↓
Observation
↓
Index
↓
Retrieval
↓
Rerank
中間步驟比直接讀 MEMORY.md 多很多。
實際 Issue 已經出現幾類問題:
| 問題 | 結果 |
|---|---|
| Extraction / Chunk 缺 Context | User 的資訊被記成 Assistant,或抽出錯誤 Fact |
| Vector Index / Retrieval 異常 | Memory 明明存在,但 Recall under-return |
| Bank 切太細或 Bank ID 不穩 | Memory 還在,但 Agent 查不到,看起來像失憶 |
| Consolidation / Reflect 太重 | 大 Bank 可能增加大量 DB、LLM 與記憶體成本 |
例如已有使用者回報 extraction 經常出現 attribution 錯誤,而且已影響實際使用。
也有 Production deployment 發現 Vector Index 問題會讓 Recall 結果 under-return。
Hindsight 增加了 Memory 的結構化與查詢能力,也增加了需要測試、監控與排查的環節。
Hindsight 最大的風險不是「沒有用」,而是:
Codex、Claude、OpenClaw 本來就有自己的 Memory,再加一套 Hindsight,可能只是重複處理同一批資訊。
例如 Codex 已經記住:
這個專案的 API 採 REST,不使用 GraphQL。
Hindsight 又把同一段對話做:
Conversation
↓
Fact Extraction
↓
Observation
↓
Recall
↓
重新放回 Codex Context
這時就可能出現幾個問題:
因此真正值得比較的是:
| 方案 | 要確認什麼 |
|---|---|
| Agent 原生 Memory | Codex / Claude / OpenClaw 自己的 Memory 是否已經足夠 |
| 原生 Memory + Hindsight | 加上 Hindsight 後,Recall 是否真的更準、跨 Agent 是否更方便 |
| 調整後的 Hindsight | 設定 Bank、Tag、Extraction Rule 後,改善是否值得額外複雜度 |
例如準備一組真的需要長期記憶的情境:
Session 1
決定所有改票前都必須執行 fare_rule_check
...經過多次 Session...
Session 20
請 Agent 修改改票流程
再看:
只用 Native Memory
→ 有沒有記得 fare_rule_check?
Native Memory + Hindsight
→ 有沒有找得更準?
→ 有沒有反而叫回過期資訊?
→ 多花多少時間與 Token?
這樣測的才是 Hindsight 真正增加的價值,而不是拿它跟「完全沒有 Memory」比較。
Hindsight Repo 的 Issue #2347 也有使用者提出類似疑問:Coding Agent 本來就已經有 Native Memory、Git History、文件等 Context,因此加入額外 Memory Layer 前,應該先確認它是否真的改善結果,而不是預設 Memory Pipeline 越多越好。
模型能力越強後,這個問題也會更明顯。如果 Agent 原本就能直接理解足夠的歷史內容:
Raw History
↓
Strong Model
那再經過:
Fact Extraction
↓
Observation
↓
Retrieval
↓
Rerank
↓
Strong Model
未必比較好,反而增加中途誤判的機會。
不過 Hindsight 有些功能不太會因模型變強而消失:
所以 Hindsight 真正比較難被取代的價值,不是:
幫更強的模型多做幾層摘要。
而是:
當 Agent、User、Project 變多時,提供一個共同管理 Memory 的資料層。