refactor: streamline the course to 17 lessons
@@ -1,50 +1,39 @@
|
||||
# s09: Memory — 圧縮は詳細を失う、失わない層が必要
|
||||
# s09: Memory — 重要な情報をセッションを越えて残す
|
||||
|
||||
[English](README.md) · [中文](README.zh.md) · [日本語](README.ja.md)
|
||||
|
||||
s01 → ... → s07 → s08 → `s09` → [s10](../s10_system_prompt/) → s11 → ... → s18 → s19
|
||||
> *"圧縮は詳細を失う、失わない層が必要"* — ファイルストア + インデックス + オンデマンド読み込み。圧縮を越え、セッションを越えて。
|
||||
s01 → ... → s07 → s08 → `s09` → [s10](../s10_task_system/) → s11 → ... → s16 → s17
|
||||
> *「後のタスクでも使う情報を残す。」* ファイル保存 + index + 関連性の選択 + 必要時の recall。
|
||||
>
|
||||
> **Harness レイヤー**: 記憶 — 圧縮とセッションを越える知識の蓄積。
|
||||
> **Harness レイヤー**:Memory は会話の外に再利用できる知識を保存し、関係するタスクで取り出す。
|
||||
|
||||
---
|
||||
|
||||
## 課題
|
||||
## 問題
|
||||
|
||||
s08 の `compact_history` は現在の目標、残りの作業、ユーザーの制約をサマリに保持するが、詳細は失われる:「タブでインデント、スペース不可」が「ユーザーにコードスタイルの好みあり」と簡略化される。そして新しいセッションを開始すると、サマリすらない。
|
||||
Agent が新しい session を始めると、`messages` に前回の会話はない。以前に伝えられた coding preference、project の背景、調査の手がかりは、次のタスクでも必要になることがある。永続的な保存先がなければ、ユーザーは同じ情報をもう一度伝えなければならない。
|
||||
|
||||
LLM には永続状態がなく、すべての情報はコンテキストウィンドウ内にある。コンテキストが満杯になれば圧縮され、圧縮は非可逆。圧縮に参加せず、セッションを越えて保持されるストレージ層が必要。
|
||||
|
||||
---
|
||||
|
||||
## ソリューション
|
||||
完全な transcript は記録には向いているが、毎回モデルへ送る方法は長続きしない。会話は増え続け、必要な情報を見つけにくくなり、古い事実が現在も正しいとは限らない。Memory が判断するのは、どの情報を session を越えて保存するか、現在のタスクでどの記録を取り出すかだ。
|
||||
|
||||

|
||||
|
||||
s08 の圧縮パイプラインを維持し、記憶に焦点を当てる。ストレージにはファイルシステムを採用:`.memory/` ディレクトリに各記憶を `.md` ファイルとして保存、YAML frontmatter(`name` / `description` / `type`)付き。ファイルが増えたらインデックスが必要:`MEMORY.md` に 1 行 1 リンクを記録し、SYSTEM に注入。
|
||||
|
||||
重要な設計:インデックスは SYSTEM prompt に常駐(prompt cache でキャッシュ可能)、ファイル内容はオンデマンド注入(filename/description で現在の会話にマッチ、cache を破壊しない)。書き込みは 2 つのパス:ユーザーが明示的に「覚えて」と言うか、毎ターン終了後にバックグラウンドで抽出。ファイルが蓄積されたら、定期的に整理して重複排除。
|
||||
|
||||
> **s08 との境界:** 圧縮は引き続き現在の会話と token 予算を担当する。記憶は圧縮を置き換えず、選んだ事実を会話の外に保存し、後から必要に応じて呼び戻す。
|
||||
|
||||
4 種類の記憶、それぞれ異なる質問に答える:
|
||||
|
||||
| タイプ | 何に答えるか | 例 |
|
||||
|--------|-------------|-----|
|
||||
| user | あなたは誰か | "タブでスペース不可" |
|
||||
| feedback | どう作業するか | "DB をモックしない" |
|
||||
| project | 何が起きているか | "auth 書き直しはコンプライアンス主導" |
|
||||
| reference | どこで探すか | "パイプラインのバグは Linear INGEST" |
|
||||
|
||||
---
|
||||
|
||||
## 仕組み
|
||||
## すべて system prompt に入れる方法が適さない理由
|
||||
|
||||
最も直接的な方法は、ユーザーの好みや project の事実を一つのファイルへ書き、起動時に全文を system prompt へ入れることだ。情報は残るが、LLM を呼ぶたびに全量を送り直す必要がある。記憶が増えるほど、現在のタスクと関係ない内容が input token と context を占有する。
|
||||
|
||||
s07 は別の読み方を示した。短い index を置き、必要なときだけ本文を読む。Skill は人が書く read-only の知識であり、Memory は Agent が会話から情報を抽出し、後のタスクで再利用できるようにする。
|
||||
|
||||
この章で扱うのは、保存、recall、抽出、整理の四つだ。
|
||||
|
||||

|
||||
|
||||
### ストレージ:Markdown ファイル + インデックス
|
||||
---
|
||||
|
||||
各記憶は `.md` ファイル、YAML frontmatter でメタデータを記録:
|
||||
## 保存:一つの記憶を一つのファイルへ
|
||||
|
||||
各 memory は `.memory/` の Markdown ファイルで、YAML frontmatter に `name`、`description`、`type` を持つ。
|
||||
|
||||
```markdown
|
||||
---
|
||||
@@ -54,141 +43,148 @@ type: user
|
||||
---
|
||||
|
||||
User prefers using tabs, not spaces, for indentation.
|
||||
**Why:** Consistency with existing codebase conventions.
|
||||
**How to apply:** Always use tabs when writing or editing files.
|
||||
```
|
||||
|
||||
`MEMORY.md` はインデックス、1 行に 1 リンク:
|
||||
memory type は四種類ある。
|
||||
|
||||
```markdown
|
||||
- [user-preference-tabs](user-preference-tabs.md) — User prefers tabs for indentation
|
||||
```
|
||||
| type | 保存する内容 | 例 |
|
||||
|------|-------------|----|
|
||||
| user | 長く使うユーザーの好み | 「indent には tab を使う」 |
|
||||
| feedback | 今後も使える作業上の feedback | 「database を mock しない」 |
|
||||
| project | 安定した project の事実 | 「認証の書き直しは compliance 要件による」 |
|
||||
| reference | 外部資料や検索の手がかり | 「pipeline の問題は Linear INGEST にある」 |
|
||||
|
||||
新しい記憶を書き込むとインデックスを自動再構築:
|
||||
`MEMORY.md` は index で、一行が一つの memory ファイルに対応する。書き込み後、`rebuild_memory_index()` がファイルから index を作り直す。
|
||||
|
||||
```python
|
||||
def write_memory_file(name, mem_type, description, body):
|
||||
slug = name.lower().replace(" ", "-")
|
||||
filepath = MEMORY_DIR / f"{slug}.md"
|
||||
filepath.write_text(
|
||||
f"---\nname: {name}\ndescription: {description}\ntype: {mem_type}\n---\n\n{body}\n"
|
||||
)
|
||||
_rebuild_index()
|
||||
path = MEMORY_DIR / f"{memory_slug(name)}.md"
|
||||
path.write_text(memory_document(name, mem_type, description, body))
|
||||
rebuild_memory_index()
|
||||
return path
|
||||
```
|
||||
|
||||
### 読み込み:2 つのパス
|
||||
index は関連する記憶を選ぶために使い、本文は個別ファイルに残す。
|
||||
|
||||
**パス 1:インデックスを SYSTEM に常駐。** `build_system()` は各ユーザーリクエストの開始時に 1 回だけ `MEMORY.md` を読み込み、記憶カタログを SYSTEM prompt に注入。記憶の抽出と整理はターン終了時にだけ実行されるため、同じユーザーリクエスト内で SYSTEM を繰り返し再構築する必要はない。
|
||||
---
|
||||
|
||||
**パス 2:関連記憶をオンデマンド注入。** 各ユーザーリクエストの開始時に、`load_memories()` は最近の会話と記憶カタログ(name + description)を LLM に軽量 side-query として送信し、関連するファイル名を選択、ファイル内容を読み込んで注入。上限 5 件でコストを制御。
|
||||
## Recall:先に選び、その後で本文を読む
|
||||
|
||||
ユーザーの request が始まると、`select_relevant_memories()` は最近のユーザー発言と memory catalog を軽量なモデル呼び出しへ渡し、関係する記録を最大五件選ぶ。
|
||||
|
||||
```python
|
||||
def select_relevant_memories(messages, max_items=5):
|
||||
files = list_memory_files()
|
||||
if not files:
|
||||
return []
|
||||
|
||||
# Build catalog: "0: user-preference-tabs — User prefers tabs..."
|
||||
catalog = "\n".join(f"{i}: {f['name']} — {f['description']}" for i, f in enumerate(files))
|
||||
|
||||
response = client.messages.create(model=MODEL, messages=[{"role": "user",
|
||||
"content": f"Select relevant memory indices. Return JSON array.\n\n"
|
||||
f"Recent conversation:\n{recent}\n\nMemory catalog:\n{catalog}"}],
|
||||
max_tokens=200)
|
||||
indices = json.loads(re.search(r'\[.*?\]', response.content[0].text).group())
|
||||
return [files[i]["filename"] for i in indices if 0 <= i < len(files)]
|
||||
prompt = (
|
||||
"Select memory records that are relevant to the current user request. "
|
||||
"Return only a JSON array of catalog indices, such as [0, 2]. "
|
||||
"Return [] when none are relevant."
|
||||
)
|
||||
```
|
||||
|
||||
side-query が失敗した場合(API エラー、JSON パース失敗)、name + description のキーワードマッチにフォールバック。
|
||||
|
||||
### 書き込み:毎ターン終了後の抽出
|
||||
|
||||
ユーザーが毎回「これを覚えて」と言うわけではない。好みは通常、通常の会話の中に散らばっている:「タブの方がスペースより良い」「これからはシングルクォートにしよう」。
|
||||
|
||||
`extract_memories()` は各ターン終了時に実行、モデルが tool_use なしで停止した場合にトリガー(会話が自然な区切りに達したことを示す):
|
||||
モデル呼び出しまたは JSON parse に失敗したら、keyword matching へ fallback する。選択後にだけ `load_memories()` が対応するファイルを読み、recall する本文の合計長も制限する。
|
||||
|
||||
```python
|
||||
relevant_memories = load_memories(messages)
|
||||
system = build_system(relevant_memories)
|
||||
```
|
||||
|
||||
`build_system()` は、recall した内容が背景知識であり、新しいユーザー command ではないことを明示する。memory と現在の request が矛盾した場合は現在の request を優先する。これにより古い情報は利用できるが、古い記録がユーザーの代わりに命令することはない。
|
||||
|
||||
---
|
||||
|
||||
## 抽出:turn の終了後に再利用できる情報を保存する
|
||||
|
||||
ユーザーが毎回「覚えて」と言うとは限らない。Agent が現在の返答を終えた後、`extract_memories()` は会話を確認し、今後も役立つ可能性がある情報だけを取り出す。
|
||||
|
||||
```python
|
||||
# In agent_loop:
|
||||
if response.stop_reason != "tool_use":
|
||||
extract_memories(messages) # 最近の会話から新しい記憶を抽出
|
||||
consolidate_memories() # 整理が必要かチェック
|
||||
force = trigger_hooks("Stop", messages)
|
||||
if force:
|
||||
messages.append({"role": "user", "content": force})
|
||||
continue
|
||||
if extract_memories(messages):
|
||||
consolidate_memories()
|
||||
return
|
||||
```
|
||||
|
||||
抽出前に既存の記憶を確認し、重複を回避。抽出プロンプトは LLM に `{name, type, description, body}` の JSON 配列を要求、本当に新しい情報がある場合のみファイルに書き込む。
|
||||
モデルの返答は候補であり、そのまま disk へ書く記録ではない。各候補には `scope` があり、`persistent` だけが後の session に残す内容を表す。`current_task` は一回だけの command、一時 path、現在のタスクだけの制約に使う。
|
||||
|
||||
```python
|
||||
def extract_memories(messages):
|
||||
dialogue = format_recent_messages(messages[-10:])
|
||||
existing = "\n".join(f"- {m['name']}: {m['description']}" for m in list_memory_files())
|
||||
|
||||
prompt = (
|
||||
"Extract user preferences, constraints, or project facts.\n"
|
||||
"Return JSON array: [{name, type, description, body}].\n"
|
||||
"If nothing new or already covered, return [].\n\n"
|
||||
f"Existing memories:\n{existing}\n\nDialogue:\n{dialogue[:4000]}"
|
||||
)
|
||||
# ... parse response, write files ...
|
||||
```
|
||||
|
||||
### 整理:低頻度の重複排除
|
||||
|
||||
記憶ファイルは蓄積される。`consolidate_memories()` はファイル数が閾値(デフォルト 10)に達した時にトリガー、LLM に重複排除、矛盾の統合、古い記憶の剪定を依頼:
|
||||
|
||||
```python
|
||||
CONSOLIDATE_THRESHOLD = 10
|
||||
|
||||
def consolidate_memories():
|
||||
files = list_memory_files()
|
||||
if len(files) < CONSOLIDATE_THRESHOLD:
|
||||
return # 少なすぎる、整理する価値なし
|
||||
# Send all memories to LLM, get back deduplicated list
|
||||
# Replace all files with consolidated results
|
||||
```
|
||||
|
||||
### Memory に保存するもの
|
||||
|
||||
Memory はセッションを越えて有用な情報を保存する:ユーザーの好み、繰り返し出るフィードバック、プロジェクト背景、よく使う入口、調査の手がかりなど。「あとでまた使うもの」を対象にし、インデックス + オンデマンド読み込みで現在の会話に戻す。
|
||||
|
||||
session memory は 1 つのセッション内の連続性を扱う:compact 後も現在の会話に残すべき文脈を保持する。両者は役割が分かれている。Memory は長期知識を扱い、session memory は現在のセッションを compact 越しにつなぐ。
|
||||
最後の判定は `should_store_memory()` が行う。field が足りない候補、「この session」「現在の task」のような一時性を含む候補、既存 memory と重複する候補は拒否する。例えば「この session ではファイルを作らない」は現在の作業だけの制約であり、次の session まで有効にしてはいけない。
|
||||
|
||||
---
|
||||
|
||||
## s08 からの変更点
|
||||
## 整理:重複した内容と古い内容をまとめる
|
||||
|
||||
| コンポーネント | 変更前 (s08) | 変更後 (s09) |
|
||||
|-----------|-------------|-------------|
|
||||
| 記憶能力 | なし(圧縮後、好みはサマリと共に劣化) | ストレージ + 読み込み + 抽出 + 整理 |
|
||||
| 新規関数 | — | write_memory_file, select_relevant_memories, load_memories, extract_memories, consolidate_memories |
|
||||
| ストレージ | — | .memory/MEMORY.md インデックス + .memory/*.md ファイル |
|
||||
| ツール | bash, read, write, edit, glob, todo_write, task, load_skill, compact (9) | bash, read_file, write_file, edit_file, glob, task (6) |
|
||||
| ループ | 毎ターン圧縮のみ | 記憶注入 + 圧縮 + ターン終了後の抽出 + 定期整理 |
|
||||
memory ファイルが増えると、重複、矛盾、古い情報が混ざる。学習用実装は 10 件に達すると `consolidate_memories()` を呼び、整理後の記録一覧をモデルに生成させる。
|
||||
|
||||
新しい一覧を parse して検証してから旧ファイルを置き換える。置き換え前には現在の記録を snapshot し、削除や書き込みに失敗したら元のファイルを戻して index を再構築する。
|
||||
|
||||
```python
|
||||
snapshot = {
|
||||
path.name: path.read_text()
|
||||
for path in MEMORY_DIR.glob("*.md")
|
||||
if path.name != MEMORY_INDEX.name
|
||||
}
|
||||
|
||||
try:
|
||||
for path in MEMORY_DIR.glob("*.md"):
|
||||
if path.name != MEMORY_INDEX.name:
|
||||
path.unlink()
|
||||
for record in consolidated:
|
||||
path = MEMORY_DIR / f"{memory_slug(record['name'])}.md"
|
||||
path.write_text(memory_document(
|
||||
record["name"], record["type"],
|
||||
record["description"], record["body"],
|
||||
))
|
||||
rebuild_memory_index()
|
||||
except Exception:
|
||||
for path in MEMORY_DIR.glob("*.md"):
|
||||
if path.name != MEMORY_INDEX.name:
|
||||
path.unlink()
|
||||
for filename, content in snapshot.items():
|
||||
(MEMORY_DIR / filename).write_text(content)
|
||||
rebuild_memory_index()
|
||||
raise
|
||||
```
|
||||
|
||||
学習用コードでは件数だけを threshold にする。実際の application では data 量に合う実行時期を選び、複数 process が同じ store を同時に書き換えないようにする必要がある。
|
||||
|
||||
---
|
||||
|
||||
## 試してみよう
|
||||
## この章のコード
|
||||
|
||||
| 部分 | 実装 |
|
||||
|------|------|
|
||||
| Agent Loop | messages、tool call、tool result、hook の trigger point を維持 |
|
||||
| 基本 tools | `bash`、`read_file`、`write_file`、`edit_file`、`glob` |
|
||||
| 保存 | `.memory/MEMORY.md` index + `.memory/*.md` records |
|
||||
| Recall | catalog の選択 + keyword fallback + 本文サイズ上限 |
|
||||
| 書き込み | turn 終了後の抽出 + 永続性チェック + 重複除外 |
|
||||
| 整理 | threshold 到達後に統合し、置き換え失敗時は旧ファイルを復元 |
|
||||
|
||||
> **s08 との境界:** s08 は現在の session の context budget を管理し、s09 は会話の外にある再利用可能な知識を管理する。Memory は選択的な保存であり、transcript の lossless backup ではなく、context compaction の代わりにもならない。
|
||||
|
||||
---
|
||||
|
||||
## 試してみる
|
||||
|
||||
```sh
|
||||
cd learn-claude-code
|
||||
python s09_memory/code.py
|
||||
```
|
||||
|
||||
以下のプロンプトを試してみてください(複数ターンに分けて入力し、記憶の蓄積と読み込みを観察):
|
||||
1. `I prefer using tabs for indentation. Remember that.` と入力し、turn の後に `.memory/` へ新しい record が増え、`MEMORY.md` に index entry が作られたか確認する。
|
||||
2. `q` で終了し、program を再起動して `What indentation style do I prefer?` と聞く。新しい session でも preference を recall できることを確認する。
|
||||
3. code formatting と関係ない別の preference を保存してから indentation を質問し、現在の request に関係する memory だけが読み込まれるか確認する。
|
||||
4. `Do not create files in this session.` と入力し、この一時的な条件が次の session の永続ルールにならないことを確認する。
|
||||
|
||||
1. `I prefer using tabs for indentation, not spaces. Remember that.`
|
||||
2. `Create a Python file called test.py`(Agent がタブを使用したか観察)
|
||||
3. `What did I tell you about my preferences?`(Agent が覚えているか観察)
|
||||
4. `I also prefer single quotes over double quotes for strings.`
|
||||
|
||||
観察のポイント:各ターン終了後に `[Memory: extracted N new memories]` が表示されるか?`.memory/` ディレクトリに `.md` ファイルが生成されたか?`MEMORY.md` インデックスが更新されたか?新しい会話で Agent が以前の記憶を自動的に読み込んだか?
|
||||
モデルによって表現や抽出件数は変わる。確認するのは `.memory/` に何が保存されたか、後の session が関係する情報だけを recall したかだ。
|
||||
|
||||
---
|
||||
|
||||
## 次へ
|
||||
|
||||
記憶、圧縮、ツールはすべて揃った。しかし system prompt はまだハードコードされた文字列。新しいツールを追加するには手動で説明を書き、プロジェクトを変えるにはプロンプト全体を書き直す。プロンプトは実行時に組み立てられるべき。
|
||||
Memory は情報をセッション間で保持する。しかし複雑なタスクには、各作業の状態と依存関係も永続的に記録する必要がある。会話内の TODO だけでは、プロセス終了後に進捗を追跡できない。
|
||||
|
||||
s10 System Prompt → セグメント + 実行時組み立て。異なるプロジェクト、異なるツール、異なるプロンプト。
|
||||
s10 Task System → タスク、状態、依存関係をディスクへ保存する。
|
||||
|
||||
|
||||
<!-- translation-sync: zh@v1, en@v1, ja@v1 -->
|
||||
<!-- translation-sync: zh@v3, en@v3, ja@v3 -->
|
||||
|
||||
@@ -1,50 +1,39 @@
|
||||
# s09: Memory — Compression Loses Details, Keep a Layer That Doesn't
|
||||
# s09: Memory — Keep Useful Knowledge Across Sessions
|
||||
|
||||
[English](README.md) · [中文](README.zh.md) · [日本語](README.ja.md)
|
||||
|
||||
s01 → ... → s07 → s08 → `s09` → [s10](../s10_system_prompt/) → s11 → ... → s18 → s19
|
||||
> *"Compression loses details, keep a layer that doesn't"* — File store + index + on-demand loading, across compactions, across sessions.
|
||||
s01 → ... → s07 → s08 → `s09` → [s10](../s10_task_system/) → s11 → ... → s16 → s17
|
||||
> *"Keep information that later tasks will need."* File storage + an index + relevance selection + on-demand recall.
|
||||
>
|
||||
> **Harness Layer**: Memory — knowledge that survives compaction and sessions.
|
||||
> **Harness layer**: Memory stores reusable knowledge outside the conversation and recalls it for related tasks.
|
||||
|
||||
---
|
||||
|
||||
## The Problem
|
||||
|
||||
s08's `compact_history` preserves current goals, remaining work, and user constraints in the summary, but details get lost: "use tabs not spaces" might get simplified to "user has code style preferences". And when you start a new session, even the summary is gone.
|
||||
An Agent starts a new session without the previous conversation in `messages`. A coding preference, project fact, or debugging clue from an earlier session may still matter. Without persistent storage, the user has to provide it again.
|
||||
|
||||
LLMs have no persistent state; all information lives in the context window. When context fills up, it gets compressed, and compression is lossy. What's needed is a storage layer that doesn't participate in compression and persists across sessions.
|
||||
|
||||
---
|
||||
|
||||
## The Solution
|
||||
A complete transcript works as an archive, but sending it with every request does not scale. The conversation keeps growing, useful information becomes hard to locate, and old facts may no longer be true. Memory must decide what is worth keeping across sessions and which records belong in the current task.
|
||||
|
||||

|
||||
|
||||
The s08 compression pipeline is preserved, focusing on memory. Storage uses the filesystem: a `.memory/` directory where each memory is a `.md` file with YAML frontmatter (`name` / `description` / `type`). When files accumulate, an index is needed: `MEMORY.md` holds one link per line and gets injected into the SYSTEM.
|
||||
|
||||
Key design: the index stays in SYSTEM prompt (cacheable by prompt cache), file content is injected on demand (matched by filename/description to the current conversation, without breaking the cache). Writing has two paths: the user explicitly says "remember", or extraction runs in the background after each turn. When files accumulate, periodic consolidation deduplicates.
|
||||
|
||||
> **Boundary with s08:** compaction still owns the current transcript and token budget. Memory does not replace that pipeline; it selectively persists facts outside the transcript and recalls them later.
|
||||
|
||||
Four memory types, each answering a different question:
|
||||
|
||||
| Type | Answers | Example |
|
||||
|------|---------|---------|
|
||||
| user | Who you are | "Use tabs not spaces" |
|
||||
| feedback | How to work | "Don't mock the database" |
|
||||
| project | What's happening | "Auth rewrite is compliance-driven" |
|
||||
| reference | Where to find things | "Pipeline bugs are in Linear INGEST" |
|
||||
|
||||
---
|
||||
|
||||
## How It Works
|
||||
## Why Not Put Everything in the System Prompt?
|
||||
|
||||
The direct approach is to write preferences and project facts into one file, then put the entire file in the system prompt. It remembers the information, but every LLM call must resend all of it. As the store grows, more unrelated material consumes input tokens and context space.
|
||||
|
||||
s07 showed a better reading pattern: keep a short index available and load full content only when needed. Skills are human-authored and read-only. Memory lets the Agent extract information from conversation and reuse it in later work.
|
||||
|
||||
This chapter therefore needs four parts: storage, recall, extraction, and consolidation.
|
||||
|
||||

|
||||
|
||||
### Storage: Markdown Files + Index
|
||||
---
|
||||
|
||||
Each memory is a `.md` file with YAML frontmatter for metadata:
|
||||
## Storage: One File per Record
|
||||
|
||||
Each memory is a Markdown file under `.memory/`. YAML frontmatter stores its `name`, `description`, and `type`:
|
||||
|
||||
```markdown
|
||||
---
|
||||
@@ -54,115 +43,125 @@ type: user
|
||||
---
|
||||
|
||||
User prefers using tabs, not spaces, for indentation.
|
||||
**Why:** Consistency with existing codebase conventions.
|
||||
**How to apply:** Always use tabs when writing or editing files.
|
||||
```
|
||||
|
||||
`MEMORY.md` is the index, one link per line:
|
||||
There are four memory types:
|
||||
|
||||
```markdown
|
||||
- [user-preference-tabs](user-preference-tabs.md) — User prefers tabs for indentation
|
||||
```
|
||||
| Type | What it stores | Example |
|
||||
|------|----------------|---------|
|
||||
| user | A durable user preference | "Use tabs for indentation" |
|
||||
| feedback | Guidance that remains useful | "Do not mock the database" |
|
||||
| project | A stable project fact | "The authentication rewrite is compliance-driven" |
|
||||
| reference | An external pointer or lookup clue | "The pipeline issue is tracked in Linear INGEST" |
|
||||
|
||||
Writing a new memory automatically rebuilds the index:
|
||||
`MEMORY.md` is the index, with one line per memory file. After a write, `rebuild_memory_index()` regenerates it from the files:
|
||||
|
||||
```python
|
||||
def write_memory_file(name, mem_type, description, body):
|
||||
slug = name.lower().replace(" ", "-")
|
||||
filepath = MEMORY_DIR / f"{slug}.md"
|
||||
filepath.write_text(
|
||||
f"---\nname: {name}\ndescription: {description}\ntype: {mem_type}\n---\n\n{body}\n"
|
||||
)
|
||||
_rebuild_index()
|
||||
path = MEMORY_DIR / f"{memory_slug(name)}.md"
|
||||
path.write_text(memory_document(name, mem_type, description, body))
|
||||
rebuild_memory_index()
|
||||
return path
|
||||
```
|
||||
|
||||
### Loading: Two Paths
|
||||
|
||||
**Path 1: Index in SYSTEM.** `build_system()` reads `MEMORY.md` once at the start of each user request and injects the memory catalog into the SYSTEM prompt. Memory extraction and consolidation run only when the turn ends, so SYSTEM does not need to be rebuilt repeatedly within the same user request.
|
||||
|
||||
**Path 2: Relevant memories on demand.** At the start of each user request, `load_memories()` sends the recent conversation and the memory catalog (name + description) to the LLM as a lightweight side-query, selects relevant filenames, then reads and injects their contents. Capped at 5 to control cost.
|
||||
|
||||
```python
|
||||
def select_relevant_memories(messages, max_items=5):
|
||||
files = list_memory_files()
|
||||
if not files:
|
||||
return []
|
||||
|
||||
# Build catalog: "0: user-preference-tabs — User prefers tabs..."
|
||||
catalog = "\n".join(f"{i}: {f['name']} — {f['description']}" for i, f in enumerate(files))
|
||||
|
||||
response = client.messages.create(model=MODEL, messages=[{"role": "user",
|
||||
"content": f"Select relevant memory indices. Return JSON array.\n\n"
|
||||
f"Recent conversation:\n{recent}\n\nMemory catalog:\n{catalog}"}],
|
||||
max_tokens=200)
|
||||
indices = json.loads(re.search(r'\[.*?\]', response.content[0].text).group())
|
||||
return [files[i]["filename"] for i in indices if 0 <= i < len(files)]
|
||||
```
|
||||
|
||||
If the side-query fails (API error, JSON parse failure), it falls back to keyword matching on name + description.
|
||||
|
||||
### Writing: Extraction After Each Turn
|
||||
|
||||
Users don't always say "remember this". Preferences are usually scattered across normal dialogue: "tabs are better than spaces", "let's use single quotes from now on".
|
||||
|
||||
`extract_memories()` runs when each turn ends, triggered when the model stops without a tool_use (indicating the conversation has reached a natural break):
|
||||
|
||||
```python
|
||||
# In agent_loop:
|
||||
if response.stop_reason != "tool_use":
|
||||
extract_memories(messages) # Extract new memories from recent dialogue
|
||||
consolidate_memories() # Check if consolidation is needed
|
||||
return
|
||||
```
|
||||
|
||||
Before extraction, existing memories are checked to avoid duplicates. The extraction prompt asks the LLM to return a JSON array of `{name, type, description, body}`, writing files only when genuinely new information is found.
|
||||
|
||||
```python
|
||||
def extract_memories(messages):
|
||||
dialogue = format_recent_messages(messages[-10:])
|
||||
existing = "\n".join(f"- {m['name']}: {m['description']}" for m in list_memory_files())
|
||||
|
||||
prompt = (
|
||||
"Extract user preferences, constraints, or project facts.\n"
|
||||
"Return JSON array: [{name, type, description, body}].\n"
|
||||
"If nothing new or already covered, return [].\n\n"
|
||||
f"Existing memories:\n{existing}\n\nDialogue:\n{dialogue[:4000]}"
|
||||
)
|
||||
# ... parse response, write files ...
|
||||
```
|
||||
|
||||
### Consolidation: Low-Frequency Deduplication
|
||||
|
||||
Memory files accumulate. `consolidate_memories()` triggers when the file count reaches a threshold (default 10), asking the LLM to deduplicate, merge contradictions, and prune stale memories:
|
||||
|
||||
```python
|
||||
CONSOLIDATE_THRESHOLD = 10
|
||||
|
||||
def consolidate_memories():
|
||||
files = list_memory_files()
|
||||
if len(files) < CONSOLIDATE_THRESHOLD:
|
||||
return # Too few, not worth consolidating
|
||||
# Send all memories to LLM, get back deduplicated list
|
||||
# Replace all files with consolidated results
|
||||
```
|
||||
|
||||
### What Memory Stores
|
||||
|
||||
Memory stores information that remains useful across sessions: user preferences, recurring feedback, project background, common entry points, and investigation clues. It focuses on "what will be useful later" and brings that information back through an index plus on-demand loading.
|
||||
|
||||
Session memory focuses on continuity inside one session: what context should survive after compaction. The two work together: Memory handles long-term knowledge; session memory handles the current session across compaction.
|
||||
The index supports selection while full content stays in the individual files.
|
||||
|
||||
---
|
||||
|
||||
## Changes From s08
|
||||
## Recall: Select First, Then Load Full Records
|
||||
|
||||
| Component | Before (s08) | After (s09) |
|
||||
|-----------|-------------|-------------|
|
||||
| Memory capability | None (preferences degrade with compaction) | Storage + loading + extraction + consolidation |
|
||||
| New functions | — | write_memory_file, select_relevant_memories, load_memories, extract_memories, consolidate_memories |
|
||||
| Storage | — | .memory/MEMORY.md index + .memory/*.md files |
|
||||
| Tools | bash, read, write, edit, glob, todo_write, task, load_skill, compact (9) | bash, read_file, write_file, edit_file, glob, task (6) |
|
||||
| Loop | Only compression each turn | Memory injection + compression + post-turn extraction + periodic consolidation |
|
||||
At the start of a user request, `select_relevant_memories()` sends the recent user text and memory catalog to a lightweight model call. It selects at most five relevant records:
|
||||
|
||||
```python
|
||||
prompt = (
|
||||
"Select memory records that are relevant to the current user request. "
|
||||
"Return only a JSON array of catalog indices, such as [0, 2]. "
|
||||
"Return [] when none are relevant."
|
||||
)
|
||||
```
|
||||
|
||||
If the model call or JSON parsing fails, the code falls back to keyword matching. Only after selection does `load_memories()` read the corresponding files, with a limit on the total recalled text.
|
||||
|
||||
```python
|
||||
relevant_memories = load_memories(messages)
|
||||
system = build_system(relevant_memories)
|
||||
```
|
||||
|
||||
`build_system()` states that recalled content is background knowledge, not a new user command. The current request wins when it conflicts with memory. This lets the Agent use old information without letting old records issue instructions on the user's behalf.
|
||||
|
||||
---
|
||||
|
||||
## Extraction: Save Reusable Information After the Turn
|
||||
|
||||
Users do not always say "remember this." After the Agent finishes the current response, `extract_memories()` inspects the conversation and keeps only information likely to help later:
|
||||
|
||||
```python
|
||||
if response.stop_reason != "tool_use":
|
||||
force = trigger_hooks("Stop", messages)
|
||||
if force:
|
||||
messages.append({"role": "user", "content": force})
|
||||
continue
|
||||
if extract_memories(messages):
|
||||
consolidate_memories()
|
||||
return
|
||||
```
|
||||
|
||||
The model returns candidates, not records that are automatically allowed onto disk. Each candidate carries a `scope`: only `persistent` means that the information should survive into later sessions. `current_task` covers one-off commands, temporary paths, and temporary restrictions.
|
||||
|
||||
`should_store_memory()` performs the final admission check. It rejects incomplete candidates, phrases that refer to the current session or task, and duplicates of existing records. For example, "do not create files in this session" constrains the current work; it must not remain active in the next session.
|
||||
|
||||
---
|
||||
|
||||
## Consolidation: Merge Duplicate and Stale Records
|
||||
|
||||
As memory files accumulate, some become duplicate, contradictory, or stale. The teaching implementation calls `consolidate_memories()` after the store reaches ten records and asks the model for a cleaned list.
|
||||
|
||||
The code parses and validates the new list before replacing old files. It snapshots the current records first; if deletion or writing fails, it restores the originals and rebuilds the index:
|
||||
|
||||
```python
|
||||
snapshot = {
|
||||
path.name: path.read_text()
|
||||
for path in MEMORY_DIR.glob("*.md")
|
||||
if path.name != MEMORY_INDEX.name
|
||||
}
|
||||
|
||||
try:
|
||||
for path in MEMORY_DIR.glob("*.md"):
|
||||
if path.name != MEMORY_INDEX.name:
|
||||
path.unlink()
|
||||
for record in consolidated:
|
||||
path = MEMORY_DIR / f"{memory_slug(record['name'])}.md"
|
||||
path.write_text(memory_document(
|
||||
record["name"], record["type"],
|
||||
record["description"], record["body"],
|
||||
))
|
||||
rebuild_memory_index()
|
||||
except Exception:
|
||||
for path in MEMORY_DIR.glob("*.md"):
|
||||
if path.name != MEMORY_INDEX.name:
|
||||
path.unlink()
|
||||
for filename, content in snapshot.items():
|
||||
(MEMORY_DIR / filename).write_text(content)
|
||||
rebuild_memory_index()
|
||||
raise
|
||||
```
|
||||
|
||||
The course uses a simple count threshold. A real application must also choose a schedule that fits its data volume and prevent concurrent processes from rewriting the same store.
|
||||
|
||||
---
|
||||
|
||||
## This Lesson's Code
|
||||
|
||||
| Part | Implementation |
|
||||
|------|----------------|
|
||||
| Agent Loop | Keeps messages, tool calls, tool results, and hook trigger points |
|
||||
| Base tools | `bash`, `read_file`, `write_file`, `edit_file`, `glob` |
|
||||
| Storage | `.memory/MEMORY.md` index + `.memory/*.md` records |
|
||||
| Recall | Catalog selection + keyword fallback + a body-size limit |
|
||||
| Writing | End-of-turn extraction + persistence checks + duplicate filtering |
|
||||
| Consolidation | Merge at the threshold; restore old files after replacement failure |
|
||||
|
||||
> **Boundary with s08:** s08 manages the active session's context budget. s09 manages reusable knowledge outside the conversation. Memory is selective storage, not a lossless transcript backup, and it does not replace context compaction.
|
||||
|
||||
---
|
||||
|
||||
@@ -173,22 +172,19 @@ cd learn-claude-code
|
||||
python s09_memory/code.py
|
||||
```
|
||||
|
||||
Try these prompts (enter across multiple turns, observe memory accumulation and loading):
|
||||
1. Enter `I prefer using tabs for indentation. Remember that.` After the turn, check that `.memory/` contains a new record and `MEMORY.md` contains its index entry.
|
||||
2. Enter `q`, restart the program, and ask `What indentation style do I prefer?` Confirm that a new session can recall the preference.
|
||||
3. Store another preference unrelated to code formatting, then ask about indentation. Observe that the current request loads only relevant records.
|
||||
4. Enter `Do not create files in this session.` Confirm that this temporary requirement does not become a persistent rule for the next session.
|
||||
|
||||
1. `I prefer using tabs for indentation, not spaces. Remember that.`
|
||||
2. `Create a Python file called test.py` (observe whether the Agent uses tabs)
|
||||
3. `What did I tell you about my preferences?` (observe whether the Agent remembers)
|
||||
4. `I also prefer single quotes over double quotes for strings.`
|
||||
|
||||
What to watch for: Does `[Memory: extracted N new memories]` appear after each turn? Are `.md` files generated in `.memory/`? Is `MEMORY.md` index updated? Does the Agent automatically load previous memories in new conversations?
|
||||
Exact wording and extraction counts can vary by model. Check what was written to `.memory/` and whether a later session recalls only relevant information.
|
||||
|
||||
---
|
||||
|
||||
## What's Next
|
||||
|
||||
Memory, compression, and tools are all in place. But the system prompt is still a hardcoded string. Adding a new tool means manually adding a description; switching projects means rewriting the whole prompt. Prompts should be assembled at runtime.
|
||||
Memory preserves information across sessions, but a complex task also needs durable status and dependency tracking. A TODO kept only in the conversation cannot carry progress across process restarts.
|
||||
|
||||
s10 System Prompt → segments + runtime assembly. Different projects, different tools, different prompts.
|
||||
s10 Task System → Persist tasks, statuses, and dependencies to disk.
|
||||
|
||||
|
||||
<!-- translation-sync: zh@v1, en@v1, ja@v1 -->
|
||||
<!-- translation-sync: zh@v3, en@v3, ja@v3 -->
|
||||
|
||||
@@ -1,50 +1,39 @@
|
||||
# s09: Memory — 压缩会丢细节,要有一层不丢的
|
||||
# s09: Memory — 让重要信息跨会话保留下来
|
||||
|
||||
[English](README.md) · [中文](README.zh.md) · [日本語](README.ja.md)
|
||||
|
||||
s01 → ... → s07 → s08 → `s09` → [s10](../s10_system_prompt/) → s11 → ... → s18 → s19
|
||||
> *"压缩会丢细节, 要有一层不丢的"* — 文件仓库 + 索引 + 按需加载,跨压缩、跨会话。
|
||||
s01 → ... → s07 → s08 → `s09` → [s10](../s10_task_system/) → s11 → ... → s16 → s17
|
||||
> *"把以后还会用到的信息留下来。"* 文件存储 + 索引 + 相关性选择 + 按需召回。
|
||||
>
|
||||
> **Harness 层**: 记忆 — 跨压缩、跨会话的知识积累。
|
||||
> **Harness 层**:Memory 在会话之外保存可复用知识,并在相关任务中取回。
|
||||
|
||||
---
|
||||
|
||||
## 问题
|
||||
|
||||
s08 的 `compact_history` 会把当前目标、剩余工作、用户约束写进摘要,但细节会丢失:"用 tab 缩进不要用空格"可能被简化成"用户有代码风格偏好"。而且新开一个会话,连摘要也没了。
|
||||
Agent 开始新会话时,`messages` 里没有上一次的对话。用户之前说过的编码偏好、项目背景和排查线索,下次任务还可能用到。没有持久存储,这些信息只能由用户重新说一遍。
|
||||
|
||||
LLM 没有持久状态,所有信息都在上下文窗口里。上下文满了要压缩,压缩就有损。需要一层不参与压缩、跨会话保留的存储。
|
||||
|
||||
---
|
||||
|
||||
## 解决方案
|
||||
把完整 transcript 留下来适合归档,却不适合每次都发给模型。对话会越来越长,当前任务需要的信息很难定位,旧事实也可能已经过期。Memory 要解决的是两个问题:哪些信息值得跨会话保存,以及当前任务应该取回哪几条。
|
||||
|
||||

|
||||
|
||||
s08 的压缩管线保留,聚焦记忆。存储选文件系统:`.memory/` 目录下,每个记忆一个 `.md` 文件,带 YAML frontmatter(`name` / `description` / `type`)。文件多了需要索引:`MEMORY.md` 一行一个链接,注入 SYSTEM。
|
||||
|
||||
关键设计:索引常驻 SYSTEM prompt(可被 prompt cache 缓存),文件内容按需注入到当前 user turn(按 filename/description 匹配当前对话,不破坏 cache)。写入由每轮结束后的提取器完成:用户显式说"记住"或表达稳定偏好时,提取器会保存为记忆。文件积累多了,定期整理去重。
|
||||
|
||||
> **与 s08 的边界:** 压缩仍负责当前对话和 token 预算;记忆不会取代压缩管线,而是把选中的事实存到对话之外,并在之后按需召回。
|
||||
|
||||
四类记忆,各有用途:
|
||||
|
||||
| 类型 | 回答什么 | 示例 |
|
||||
|------|---------|------|
|
||||
| user | 你是谁 | "用 tab 不用空格" |
|
||||
| feedback | 怎么做事 | "别 mock 数据库" |
|
||||
| project | 正在发生什么 | "auth 重写是合规驱动" |
|
||||
| reference | 东西在哪找 | "pipeline bug 在 Linear INGEST" |
|
||||
|
||||
---
|
||||
|
||||
## 工作原理
|
||||
## 全部写进 system prompt,为什么不合适
|
||||
|
||||
最直接的做法,是把用户偏好和项目事实写进一个固定文件,启动时全部放进 system prompt。这样确实能够记住信息,但每次调用 LLM 都要重新发送全部内容。记忆越多,与当前任务无关的内容就越多,输入 token 和上下文窗口也会被持续占用。
|
||||
|
||||
s07 已经展示过一种更合适的读取方式:保留简短索引,只在需要时加载正文。Skill 由人编写并保持只读;Memory 则允许 Agent 从对话中提取内容,并在后续任务中再次使用。
|
||||
|
||||
因此,本章需要处理四件事:存储、召回、提取和整理。
|
||||
|
||||

|
||||
|
||||
### 存储:Markdown 文件 + 索引
|
||||
---
|
||||
|
||||
每个记忆是一个 `.md` 文件,YAML frontmatter 记录元数据:
|
||||
## 存储:一个记忆一个文件
|
||||
|
||||
每条记忆是 `.memory/` 下的一个 Markdown 文件,YAML frontmatter 记录 `name`、`description` 和 `type`:
|
||||
|
||||
```markdown
|
||||
---
|
||||
@@ -54,116 +43,125 @@ type: user
|
||||
---
|
||||
|
||||
User prefers using tabs, not spaces, for indentation.
|
||||
**Why:** Consistency with existing codebase conventions.
|
||||
**How to apply:** Always use tabs when writing or editing files.
|
||||
```
|
||||
|
||||
`MEMORY.md` 是索引,一行一个链接:
|
||||
`type` 有四类:
|
||||
|
||||
```markdown
|
||||
- [user-preference-tabs](user-preference-tabs.md) — User prefers tabs for indentation
|
||||
```
|
||||
| 类型 | 保存什么 | 示例 |
|
||||
|------|---------|------|
|
||||
| user | 用户的长期偏好 | “使用 tab 缩进” |
|
||||
| feedback | 以后仍适用的工作反馈 | “不要 mock 数据库” |
|
||||
| project | 稳定的项目事实 | “认证重写由合规要求驱动” |
|
||||
| reference | 外部资料或查找线索 | “流水线问题记录在 Linear INGEST” |
|
||||
|
||||
写入新记忆时自动重建索引:
|
||||
`MEMORY.md` 是索引,每行对应一个记忆文件。写入完成后,`rebuild_memory_index()` 根据文件重新生成索引:
|
||||
|
||||
```python
|
||||
def write_memory_file(name, mem_type, description, body):
|
||||
slug = name.lower().replace(" ", "-")
|
||||
filepath = MEMORY_DIR / f"{slug}.md"
|
||||
filepath.write_text(
|
||||
f"---\nname: {name}\ndescription: {description}\ntype: {mem_type}\n---\n\n{body}\n"
|
||||
)
|
||||
_rebuild_index()
|
||||
path = MEMORY_DIR / f"{memory_slug(name)}.md"
|
||||
path.write_text(memory_document(name, mem_type, description, body))
|
||||
rebuild_memory_index()
|
||||
return path
|
||||
```
|
||||
|
||||
### 加载:两条路径
|
||||
|
||||
**路径一:索引常驻 SYSTEM。** `build_system()` 在每次用户请求开始时读取 `MEMORY.md`,把记忆清单注入。记忆提取和整理只在本轮结束时触发,因此同一轮用户请求中不需要重复重建 SYSTEM。
|
||||
|
||||
**路径二:相关记忆按需注入。** 每次用户请求开始时,`load_memories()` 把最近对话和记忆目录(name + description)一起发给 LLM 做一次轻量 side-query,选出相关的文件名,再读文件内容临时注入到当前 user turn。最多 5 条,控制开销。
|
||||
|
||||
```python
|
||||
def select_relevant_memories(messages, max_items=5):
|
||||
files = list_memory_files()
|
||||
if not files:
|
||||
return []
|
||||
|
||||
# Build catalog: "0: user-preference-tabs — User prefers tabs..."
|
||||
catalog = "\n".join(f"{i}: {f['name']} — {f['description']}" for i, f in enumerate(files))
|
||||
|
||||
response = client.messages.create(model=MODEL, messages=[{"role": "user",
|
||||
"content": f"Select relevant memory indices. Return JSON array.\n\n"
|
||||
f"Recent conversation:\n{recent}\n\nMemory catalog:\n{catalog}"}],
|
||||
max_tokens=200)
|
||||
text = extract_text(response.content).strip()
|
||||
indices = json.loads(re.search(r'\[.*?\]', text).group())
|
||||
return [files[i]["filename"] for i in indices if 0 <= i < len(files)]
|
||||
```
|
||||
|
||||
如果 side-query 失败(API 错误、JSON 解析失败),降级到关键词匹配 name + description。
|
||||
|
||||
### 写入:每轮结束后提取
|
||||
|
||||
用户不会每次都说"记住这个"。偏好通常散落在正常对话中:"用 tab 比空格好"、"以后都用单引号"。
|
||||
|
||||
`extract_memories()` 在每轮结束时运行,条件是模型停止且没有 tool_use(说明对话告一段落):
|
||||
|
||||
```python
|
||||
# In agent_loop:
|
||||
if response.stop_reason != "tool_use":
|
||||
extract_memories(pre_compress) # 从压缩前快照提取新记忆
|
||||
consolidate_memories() # 检查是否需要整理
|
||||
return
|
||||
```
|
||||
|
||||
提取前先检查已有记忆,避免重复。提取 prompt 要求 LLM 返回 `{name, type, description, body}` 的 JSON 数组,只有确实有新信息时才写文件。
|
||||
|
||||
```python
|
||||
def extract_memories(messages):
|
||||
dialogue = format_recent_messages(messages[-10:])
|
||||
existing = "\n".join(f"- {m['name']}: {m['description']}" for m in list_memory_files())
|
||||
|
||||
prompt = (
|
||||
"Extract user preferences, constraints, or project facts.\n"
|
||||
"Return JSON array: [{name, type, description, body}].\n"
|
||||
"If nothing new or already covered, return [].\n\n"
|
||||
f"Existing memories:\n{existing}\n\nDialogue:\n{dialogue[:4000]}"
|
||||
)
|
||||
# ... parse response, write files ...
|
||||
```
|
||||
|
||||
### 整理:低频合并去重
|
||||
|
||||
记忆文件会积累。`consolidate_memories()` 在文件数达到阈值(默认 10)时触发,让 LLM 去重、合并矛盾、淘汰过时记忆:
|
||||
|
||||
```python
|
||||
CONSOLIDATE_THRESHOLD = 10
|
||||
|
||||
def consolidate_memories():
|
||||
files = list_memory_files()
|
||||
if len(files) < CONSOLIDATE_THRESHOLD:
|
||||
return # 太少,不值得整理
|
||||
# Send all memories to LLM, get back deduplicated list
|
||||
# Replace all files with consolidated results
|
||||
```
|
||||
|
||||
### Memory 适合保存什么
|
||||
|
||||
Memory 保存跨会话仍然有用的信息:用户偏好、反复出现的反馈、项目背景、常用入口和排查线索。它关注“以后还会用到什么”,并通过索引 + 按需加载把这些信息带回当前对话。
|
||||
|
||||
session memory 关注同一会话内的连续性:compact 之后,当前会话还需要保留哪些上下文。两者配合使用:Memory 管长期知识,session memory 管当前会话的压缩续接。
|
||||
索引用于选择相关记忆,正文仍然保存在各自的文件中。
|
||||
|
||||
---
|
||||
|
||||
## 相对 s08 的变更
|
||||
## 召回:先选择,再加载正文
|
||||
|
||||
| 组件 | 之前 (s08) | 之后 (s09) |
|
||||
|------|-----------|-----------|
|
||||
| 记忆能力 | 无(压缩后偏好随摘要退化) | 存储 + 加载 + 提取 + 整理 |
|
||||
| 新函数 | — | write_memory_file, select_relevant_memories, load_memories, extract_memories, consolidate_memories |
|
||||
| 存储 | — | .memory/MEMORY.md 索引 + .memory/*.md 文件 |
|
||||
| 工具 | bash, read, write, edit, glob, todo_write, task, load_skill, compact (9) | bash, read_file, write_file, edit_file, glob, task (6) |
|
||||
| 循环 | 每轮只做压缩 | 每轮注入记忆 + 压缩 + 每轮结束后提取 + 定期整理 |
|
||||
每次用户发起请求时,`select_relevant_memories()` 读取最近的用户消息和记忆目录,让一次轻量模型调用选择最多五条相关记录:
|
||||
|
||||
```python
|
||||
prompt = (
|
||||
"Select memory records that are relevant to the current user request. "
|
||||
"Return only a JSON array of catalog indices, such as [0, 2]. "
|
||||
"Return [] when none are relevant."
|
||||
)
|
||||
```
|
||||
|
||||
如果模型调用或 JSON 解析失败,代码会退回关键词匹配。选择完成后,`load_memories()` 才读取对应文件,并限制召回正文的总长度。
|
||||
|
||||
```python
|
||||
relevant_memories = load_memories(messages)
|
||||
system = build_system(relevant_memories)
|
||||
```
|
||||
|
||||
`build_system()` 会明确说明:召回内容只是背景知识,不是新的用户命令;如果记忆与当前请求冲突,以当前请求为准。这样既能使用旧信息,也不会让旧记忆替用户发号施令。
|
||||
|
||||
---
|
||||
|
||||
## 提取:回合结束后保存可复用信息
|
||||
|
||||
用户不一定会明确说“请记住”。`extract_memories()` 在 Agent 完成本轮回答后检查当前对话,只提取以后仍可能有用的信息:
|
||||
|
||||
```python
|
||||
if response.stop_reason != "tool_use":
|
||||
force = trigger_hooks("Stop", messages)
|
||||
if force:
|
||||
messages.append({"role": "user", "content": force})
|
||||
continue
|
||||
if extract_memories(messages):
|
||||
consolidate_memories()
|
||||
return
|
||||
```
|
||||
|
||||
模型返回的内容只是候选,不会直接写盘。候选必须带有 `scope`:只有 `persistent` 才表示它应当跨会话保留;`current_task` 表示本次任务的命令、临时路径和临时限制。
|
||||
|
||||
`should_store_memory()` 负责最后的检查。字段不完整、带有“本次会话”或“当前任务”等临时含义、或者与已有记忆重复的候选都会被拒绝。比如“这次不要创建文件”只约束当前任务,不应该在下次会话中继续生效。
|
||||
|
||||
---
|
||||
|
||||
## 整理:合并重复和过期内容
|
||||
|
||||
记忆文件积累到一定数量后,内容可能重复、矛盾或过期。教学实现达到 10 条时调用 `consolidate_memories()`,让模型生成一份整理后的记录列表。
|
||||
|
||||
整理过程先解析并校验新列表,再替换旧文件。替换前会保存快照;删除或写入失败时,代码恢复原文件并重建索引:
|
||||
|
||||
```python
|
||||
snapshot = {
|
||||
path.name: path.read_text()
|
||||
for path in MEMORY_DIR.glob("*.md")
|
||||
if path.name != MEMORY_INDEX.name
|
||||
}
|
||||
|
||||
try:
|
||||
for path in MEMORY_DIR.glob("*.md"):
|
||||
if path.name != MEMORY_INDEX.name:
|
||||
path.unlink()
|
||||
for record in consolidated:
|
||||
path = MEMORY_DIR / f"{memory_slug(record['name'])}.md"
|
||||
path.write_text(memory_document(
|
||||
record["name"], record["type"],
|
||||
record["description"], record["body"],
|
||||
))
|
||||
rebuild_memory_index()
|
||||
except Exception:
|
||||
for path in MEMORY_DIR.glob("*.md"):
|
||||
if path.name != MEMORY_INDEX.name:
|
||||
path.unlink()
|
||||
for filename, content in snapshot.items():
|
||||
(MEMORY_DIR / filename).write_text(content)
|
||||
rebuild_memory_index()
|
||||
raise
|
||||
```
|
||||
|
||||
课程代码把整理触发条件简化为数量阈值。真实应用还需要根据数据规模和并发方式,决定何时整理以及如何避免多个进程同时改写同一份存储。
|
||||
|
||||
---
|
||||
|
||||
## 本节代码
|
||||
|
||||
| 组成 | 本节实现 |
|
||||
|------|---------|
|
||||
| Agent Loop | 保留消息、工具调用、工具结果和 hooks 触发点 |
|
||||
| 基础工具 | `bash`、`read_file`、`write_file`、`edit_file`、`glob` |
|
||||
| 存储 | `.memory/MEMORY.md` 索引 + `.memory/*.md` 文件 |
|
||||
| 召回 | 目录选择 + 关键词降级 + 正文长度上限 |
|
||||
| 写入 | 回合结束后提取 + 持久性检查 + 重复过滤 |
|
||||
| 整理 | 达到阈值后合并,失败时恢复原文件 |
|
||||
|
||||
> **与 s08 的边界:** s08 管理当前会话的上下文预算,s09 管理会话之外的可复用知识。Memory 是选择性存储,不是 transcript 的无损备份,也不会取代上下文压缩。
|
||||
|
||||
---
|
||||
|
||||
@@ -174,22 +172,19 @@ cd learn-claude-code
|
||||
python s09_memory/code.py
|
||||
```
|
||||
|
||||
试试这些 prompt(分多轮输入,观察记忆的累积和加载):
|
||||
1. 输入 `I prefer using tabs for indentation. Remember that.`,结束后检查 `.memory/` 是否新增记忆文件,`MEMORY.md` 是否出现对应索引;
|
||||
2. 输入 `q` 退出并重新运行程序,再问 `What indentation style do I prefer?`,确认新会话能够召回这条偏好;
|
||||
3. 再保存一条与代码格式无关的偏好,然后询问缩进问题,观察当前请求只加载相关记忆;
|
||||
4. 输入 `Do not create files in this session.`,确认这条临时要求不会成为下一次会话的持久规则。
|
||||
|
||||
1. `I prefer using tabs for indentation, not spaces. Remember that.`
|
||||
2. `Create a Python file called test.py`(观察 Agent 是否用了 tab)
|
||||
3. `What did I tell you about my preferences?`(观察 Agent 是否记得)
|
||||
4. `I also prefer single quotes over double quotes for strings.`
|
||||
|
||||
观察重点:每轮结束后是否出现 `[Memory: extracted N new memories]`?`.memory/` 目录下是否生成了 `.md` 文件?`MEMORY.md` 索引是否更新?新一轮对话时 Agent 是否自动加载了之前的记忆?
|
||||
模型的具体措辞和提取数量可能变化,判断重点是 `.memory/` 中保存了什么,以及新会话是否只取回相关内容。
|
||||
|
||||
---
|
||||
|
||||
## 接下来
|
||||
|
||||
记忆、压缩、工具都已就绪。但 system prompt 还是硬编码的一大段字符串。加了新工具要手动加描述,换了项目要重写整个 prompt。prompt 应该运行时组装。
|
||||
Memory 解决了跨会话保留信息的问题,但复杂任务还需要记录每一步的状态和依赖关系。仅靠对话中的 TODO,程序退出后就无法继续追踪进度。
|
||||
|
||||
s10 System Prompt → 分段 + 运行时组装。不同项目、不同工具,拼出不同的 prompt。
|
||||
s10 Task System → 把任务、状态和依赖关系保存到磁盘。
|
||||
|
||||
|
||||
<!-- translation-sync: zh@v1, en@v1, ja@v1 -->
|
||||
<!-- translation-sync: zh@v3, en@v3, ja@v3 -->
|
||||
|
||||
1163
s09_memory/code.py
@@ -19,42 +19,42 @@
|
||||
<!-- Title -->
|
||||
<rect x="0" y="0" width="760" height="44" fill="url(#header)" rx="8"/>
|
||||
<rect x="0" y="36" width="760" height="8" fill="url(#header)"/>
|
||||
<text x="380" y="28" fill="#fff" font-size="15" font-weight="700" text-anchor="middle">Memory — Memory loading, extraction, and consolidation on s08 compression pipeline</text>
|
||||
<text x="380" y="28" fill="#fff" font-size="15" font-weight="700" text-anchor="middle">Memory — Store, Recall, Extract & Consolidate</text>
|
||||
|
||||
<!-- Legend -->
|
||||
<rect x="40" y="56" width="12" height="10" rx="2" fill="#f0f4ff" stroke="#2563eb" stroke-width="1"/>
|
||||
<text x="58" y="66" fill="#2563eb" font-size="10" font-weight="600">s08 preserved</text>
|
||||
<text x="58" y="66" fill="#2563eb" font-size="10" font-weight="600">Agent Loop</text>
|
||||
<rect x="160" y="56" width="12" height="10" rx="2" fill="#f3e8ff" stroke="#7c3aed" stroke-width="1"/>
|
||||
<text x="178" y="66" fill="#7c3aed" font-size="10" font-weight="600">s09 new</text>
|
||||
<text x="178" y="66" fill="#7c3aed" font-size="10" font-weight="600">Memory</text>
|
||||
|
||||
<!-- ===== messages[] ===== -->
|
||||
<rect x="30" y="96" width="100" height="52" rx="8" fill="#f0f4ff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="80" y="126" fill="#1e3a5f" font-size="12" font-weight="600" text-anchor="middle">messages[]</text>
|
||||
|
||||
<!-- arrow → compression -->
|
||||
<line x1="130" y1="122" x2="152" y2="122" stroke="#555" stroke-width="1.5" marker-end="url(#arrow)"/>
|
||||
<!-- arrow to selection -->
|
||||
<line x1="130" y1="122" x2="152" y2="122" stroke="#7c3aed" stroke-width="1.5" marker-end="url(#arrow-purple)"/>
|
||||
|
||||
<!-- ===== Compression pipeline (s08) ===== -->
|
||||
<rect x="155" y="86" width="135" height="72" rx="8" fill="#f0f4ff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="222" y="108" fill="#1e3a5f" font-size="11" font-weight="700" text-anchor="middle">Compression</text>
|
||||
<text x="222" y="124" fill="#64748b" font-size="9" text-anchor="middle">budget → snip → micro</text>
|
||||
<text x="222" y="138" fill="#64748b" font-size="9" text-anchor="middle">→ compact_history</text>
|
||||
<text x="222" y="152" fill="#94a3b8" font-size="8" text-anchor="middle">(s08)</text>
|
||||
<!-- ===== Selection ===== -->
|
||||
<rect x="155" y="86" width="135" height="72" rx="8" fill="#f3e8ff" stroke="#7c3aed" stroke-width="2"/>
|
||||
<text x="222" y="108" fill="#5b21b6" font-size="11" font-weight="700" text-anchor="middle">Selection</text>
|
||||
<text x="222" y="124" fill="#7c3aed" font-size="9" text-anchor="middle">catalog + current request</text>
|
||||
<text x="222" y="138" fill="#7c3aed" font-size="9" text-anchor="middle">model or keyword match</text>
|
||||
<text x="222" y="152" fill="#a78bfa" font-size="8" text-anchor="middle">up to 5 records</text>
|
||||
|
||||
<!-- arrow → Loading (purple) -->
|
||||
<!-- arrow to recall -->
|
||||
<line x1="290" y1="122" x2="317" y2="122" stroke="#7c3aed" stroke-width="1.5" marker-end="url(#arrow-purple)"/>
|
||||
|
||||
<!-- ===== Loading (s09) ===== -->
|
||||
<!-- ===== Recall ===== -->
|
||||
<rect x="320" y="86" width="120" height="72" rx="8" fill="#f3e8ff" stroke="#7c3aed" stroke-width="2"/>
|
||||
<text x="380" y="108" fill="#5b21b6" font-size="11" font-weight="700" text-anchor="middle">Loading</text>
|
||||
<text x="380" y="124" fill="#7c3aed" font-size="9" text-anchor="middle">LLM side-query select</text>
|
||||
<text x="380" y="138" fill="#7c3aed" font-size="9" text-anchor="middle">inject file contents</text>
|
||||
<text x="380" y="152" fill="#a78bfa" font-size="8" text-anchor="middle">≤ 5 items</text>
|
||||
<text x="380" y="108" fill="#5b21b6" font-size="11" font-weight="700" text-anchor="middle">Recall</text>
|
||||
<text x="380" y="124" fill="#7c3aed" font-size="9" text-anchor="middle">load selected records</text>
|
||||
<text x="380" y="138" fill="#7c3aed" font-size="9" text-anchor="middle">add to system context</text>
|
||||
<text x="380" y="152" fill="#a78bfa" font-size="8" text-anchor="middle">bounded body size</text>
|
||||
|
||||
<!-- arrow → LLM -->
|
||||
<line x1="440" y1="122" x2="472" y2="122" stroke="#555" stroke-width="1.5" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- ===== LLM (s08) ===== -->
|
||||
<!-- ===== LLM ===== -->
|
||||
<rect x="475" y="96" width="80" height="52" rx="8" fill="#f0f4ff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="515" y="114" fill="#1e3a5f" font-size="14" font-weight="700" text-anchor="middle">LLM</text>
|
||||
<text x="515" y="132" fill="#64748b" font-size="9" text-anchor="middle">stop_reason</text>
|
||||
@@ -70,19 +70,21 @@
|
||||
<line x1="555" y1="122" x2="587" y2="122" stroke="#555" stroke-width="1.5" marker-end="url(#arrow)"/>
|
||||
<text x="568" y="114" fill="#64748b" font-size="9" font-weight="600">yes</text>
|
||||
|
||||
<!-- ===== TOOL_HANDLERS (s08) ===== -->
|
||||
<!-- ===== TOOL_HANDLERS ===== -->
|
||||
<rect x="590" y="88" width="130" height="68" rx="8" fill="#f0f4ff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="655" y="112" fill="#1e3a5f" font-size="10" font-weight="600" text-anchor="middle">TOOL_HANDLERS</text>
|
||||
<text x="655" y="128" fill="#64748b" font-size="9" text-anchor="middle">bash · read · write</text>
|
||||
<text x="655" y="142" fill="#94a3b8" font-size="8" text-anchor="middle">edit · glob · task</text>
|
||||
<text x="655" y="142" fill="#94a3b8" font-size="8" text-anchor="middle">edit · glob</text>
|
||||
|
||||
<!-- ===== Memory Files (s09) ===== -->
|
||||
<rect x="155" y="232" width="430" height="36" rx="6" fill="#faf5ff" stroke="#7c3aed" stroke-width="1.5" stroke-dasharray="4,2"/>
|
||||
<text x="370" y="255" fill="#5b21b6" font-size="11" font-weight="600" text-anchor="middle">.memory/ — MEMORY.md index + *.md files (cross-session persistent)</text>
|
||||
|
||||
<!-- Arrow: Memory Files → Loading -->
|
||||
<!-- Arrow: Memory Files to Selection -->
|
||||
<path d="M 240 232 L 240 162" fill="none" stroke="#7c3aed" stroke-width="1.5" marker-end="url(#arrow-purple)"/>
|
||||
<text x="253" y="200" fill="#7c3aed" font-size="9">catalog</text>
|
||||
<path d="M 395 232 L 395 162" fill="none" stroke="#7c3aed" stroke-width="1.5" marker-end="url(#arrow-purple)"/>
|
||||
<text x="408" y="200" fill="#7c3aed" font-size="9">read</text>
|
||||
<text x="408" y="200" fill="#7c3aed" font-size="9">records</text>
|
||||
|
||||
<!-- Arrow: return result → Extraction → Memory Files -->
|
||||
<path d="M 515 204 L 515 232" fill="none" stroke="#7c3aed" stroke-width="1.5" marker-end="url(#arrow-purple)"/>
|
||||
@@ -93,12 +95,12 @@
|
||||
|
||||
<!-- ===== Loop back ===== -->
|
||||
<path d="M 720 122 L 748 122 Q 756 122 756 130 L 756 310 Q 756 318 748 318 L 88 318 Q 80 318 80 310 L 80 148" fill="none" stroke="#555" stroke-width="1.5" marker-end="url(#arrow)" stroke-dasharray="6,3"/>
|
||||
<text x="400" y="340" fill="#64748b" font-size="10" text-anchor="middle">tool results → messages[] → compress → load memories → LLM → extract after each turn</text>
|
||||
<text x="400" y="340" fill="#64748b" font-size="10" text-anchor="middle">tool result → messages[] → select → recall → LLM → extract after the turn</text>
|
||||
|
||||
<!-- ===== Bottom notes ===== -->
|
||||
<rect x="40" y="358" width="680" height="56" rx="6" fill="#f8fafc" stroke="#e2e8f0" stroke-width="1"/>
|
||||
<rect x="60" y="372" width="12" height="10" rx="2" fill="#f0f4ff" stroke="#2563eb" stroke-width="1"/>
|
||||
<text x="80" y="382" fill="#475569" font-size="10">s08 preserved: budget → snip → micro → summary + error recovery + loop</text>
|
||||
<text x="80" y="382" fill="#475569" font-size="10">Agent Loop: messages → LLM → tool_use → tool result → messages</text>
|
||||
<rect x="60" y="392" width="12" height="10" rx="2" fill="#f3e8ff" stroke="#7c3aed" stroke-width="1"/>
|
||||
<text x="80" y="402" fill="#475569" font-size="10">s09 new: Loading (index in SYSTEM + on-demand inject) + Extraction (after each turn) + Consolidation (threshold)</text>
|
||||
<text x="80" y="402" fill="#475569" font-size="10">Memory: select records → recall bodies → extract durable knowledge → consolidate at threshold</text>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 7.0 KiB After Width: | Height: | Size: 7.1 KiB |
@@ -19,42 +19,42 @@
|
||||
<!-- Title -->
|
||||
<rect x="0" y="0" width="760" height="44" fill="url(#header)" rx="8"/>
|
||||
<rect x="0" y="36" width="760" height="8" fill="url(#header)"/>
|
||||
<text x="380" y="28" fill="#fff" font-size="14" font-weight="700" text-anchor="middle">Memory — s08 圧縮パイプラインに記憶の読み込み・抽出・整理を挿入</text>
|
||||
<text x="380" y="28" fill="#fff" font-size="15" font-weight="700" text-anchor="middle">Memory — 保存・想起・抽出・整理</text>
|
||||
|
||||
<!-- Legend -->
|
||||
<rect x="40" y="56" width="12" height="10" rx="2" fill="#f0f4ff" stroke="#2563eb" stroke-width="1"/>
|
||||
<text x="58" y="66" fill="#2563eb" font-size="10" font-weight="600">s08 維持</text>
|
||||
<text x="58" y="66" fill="#2563eb" font-size="10" font-weight="600">Agent Loop</text>
|
||||
<rect x="130" y="56" width="12" height="10" rx="2" fill="#f3e8ff" stroke="#7c3aed" stroke-width="1"/>
|
||||
<text x="148" y="66" fill="#7c3aed" font-size="10" font-weight="600">s09 追加</text>
|
||||
<text x="148" y="66" fill="#7c3aed" font-size="10" font-weight="600">Memory</text>
|
||||
|
||||
<!-- ===== messages[] ===== -->
|
||||
<rect x="30" y="96" width="100" height="52" rx="8" fill="#f0f4ff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="80" y="126" fill="#1e3a5f" font-size="12" font-weight="600" text-anchor="middle">messages[]</text>
|
||||
|
||||
<!-- arrow → compression -->
|
||||
<line x1="130" y1="122" x2="152" y2="122" stroke="#555" stroke-width="1.5" marker-end="url(#arrow)"/>
|
||||
<!-- arrow to selection -->
|
||||
<line x1="130" y1="122" x2="152" y2="122" stroke="#7c3aed" stroke-width="1.5" marker-end="url(#arrow-purple)"/>
|
||||
|
||||
<!-- ===== Compression pipeline (s08) ===== -->
|
||||
<rect x="155" y="86" width="135" height="72" rx="8" fill="#f0f4ff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="222" y="108" fill="#1e3a5f" font-size="11" font-weight="700" text-anchor="middle">圧縮パイプライン</text>
|
||||
<text x="222" y="124" fill="#64748b" font-size="9" text-anchor="middle">budget → snip → micro</text>
|
||||
<text x="222" y="138" fill="#64748b" font-size="9" text-anchor="middle">→ compact_history</text>
|
||||
<text x="222" y="152" fill="#94a3b8" font-size="8" text-anchor="middle">(s08)</text>
|
||||
<!-- ===== Selection ===== -->
|
||||
<rect x="155" y="86" width="135" height="72" rx="8" fill="#f3e8ff" stroke="#7c3aed" stroke-width="2"/>
|
||||
<text x="222" y="108" fill="#5b21b6" font-size="11" font-weight="700" text-anchor="middle">選択</text>
|
||||
<text x="222" y="124" fill="#7c3aed" font-size="9" text-anchor="middle">catalog + 現在の request</text>
|
||||
<text x="222" y="138" fill="#7c3aed" font-size="9" text-anchor="middle">モデルまたは keyword</text>
|
||||
<text x="222" y="152" fill="#a78bfa" font-size="8" text-anchor="middle">最大 5 件</text>
|
||||
|
||||
<!-- arrow → Loading (purple) -->
|
||||
<!-- arrow to recall -->
|
||||
<line x1="290" y1="122" x2="317" y2="122" stroke="#7c3aed" stroke-width="1.5" marker-end="url(#arrow-purple)"/>
|
||||
|
||||
<!-- ===== Loading (s09) ===== -->
|
||||
<!-- ===== Recall ===== -->
|
||||
<rect x="320" y="86" width="120" height="72" rx="8" fill="#f3e8ff" stroke="#7c3aed" stroke-width="2"/>
|
||||
<text x="380" y="108" fill="#5b21b6" font-size="11" font-weight="700" text-anchor="middle">Loading</text>
|
||||
<text x="380" y="124" fill="#7c3aed" font-size="9" text-anchor="middle">LLM side-query 選択</text>
|
||||
<text x="380" y="138" fill="#7c3aed" font-size="9" text-anchor="middle">ファイル内容を注入</text>
|
||||
<text x="380" y="152" fill="#a78bfa" font-size="8" text-anchor="middle">≤ 5 件</text>
|
||||
<text x="380" y="108" fill="#5b21b6" font-size="11" font-weight="700" text-anchor="middle">Recall</text>
|
||||
<text x="380" y="124" fill="#7c3aed" font-size="9" text-anchor="middle">選択した record を読む</text>
|
||||
<text x="380" y="138" fill="#7c3aed" font-size="9" text-anchor="middle">system context へ追加</text>
|
||||
<text x="380" y="152" fill="#a78bfa" font-size="8" text-anchor="middle">本文サイズを制限</text>
|
||||
|
||||
<!-- arrow → LLM -->
|
||||
<line x1="440" y1="122" x2="472" y2="122" stroke="#555" stroke-width="1.5" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- ===== LLM (s08) ===== -->
|
||||
<!-- ===== LLM ===== -->
|
||||
<rect x="475" y="96" width="80" height="52" rx="8" fill="#f0f4ff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="515" y="114" fill="#1e3a5f" font-size="14" font-weight="700" text-anchor="middle">LLM</text>
|
||||
<text x="515" y="132" fill="#64748b" font-size="9" text-anchor="middle">stop_reason</text>
|
||||
@@ -70,19 +70,21 @@
|
||||
<line x1="555" y1="122" x2="587" y2="122" stroke="#555" stroke-width="1.5" marker-end="url(#arrow)"/>
|
||||
<text x="568" y="114" fill="#64748b" font-size="9" font-weight="600">あり</text>
|
||||
|
||||
<!-- ===== TOOL_HANDLERS (s08) ===== -->
|
||||
<!-- ===== TOOL_HANDLERS ===== -->
|
||||
<rect x="590" y="88" width="130" height="68" rx="8" fill="#f0f4ff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="655" y="112" fill="#1e3a5f" font-size="10" font-weight="600" text-anchor="middle">TOOL_HANDLERS</text>
|
||||
<text x="655" y="128" fill="#64748b" font-size="9" text-anchor="middle">bash · read · write</text>
|
||||
<text x="655" y="142" fill="#94a3b8" font-size="8" text-anchor="middle">edit · glob · task</text>
|
||||
<text x="655" y="142" fill="#94a3b8" font-size="8" text-anchor="middle">edit · glob</text>
|
||||
|
||||
<!-- ===== Memory Files (s09) ===== -->
|
||||
<rect x="155" y="232" width="430" height="36" rx="6" fill="#faf5ff" stroke="#7c3aed" stroke-width="1.5" stroke-dasharray="4,2"/>
|
||||
<text x="370" y="255" fill="#5b21b6" font-size="11" font-weight="600" text-anchor="middle">.memory/ — MEMORY.md インデックス + *.md ファイル(セッション間永続化)</text>
|
||||
|
||||
<!-- Arrow: Memory Files → Loading -->
|
||||
<!-- Arrow: Memory Files to Selection -->
|
||||
<path d="M 240 232 L 240 162" fill="none" stroke="#7c3aed" stroke-width="1.5" marker-end="url(#arrow-purple)"/>
|
||||
<text x="253" y="200" fill="#7c3aed" font-size="9">catalog</text>
|
||||
<path d="M 395 232 L 395 162" fill="none" stroke="#7c3aed" stroke-width="1.5" marker-end="url(#arrow-purple)"/>
|
||||
<text x="408" y="200" fill="#7c3aed" font-size="9">読み込み</text>
|
||||
<text x="408" y="200" fill="#7c3aed" font-size="9">record</text>
|
||||
|
||||
<!-- Arrow: return result → Extraction → Memory Files -->
|
||||
<path d="M 515 204 L 515 232" fill="none" stroke="#7c3aed" stroke-width="1.5" marker-end="url(#arrow-purple)"/>
|
||||
@@ -93,12 +95,12 @@
|
||||
|
||||
<!-- ===== Loop back ===== -->
|
||||
<path d="M 720 122 L 748 122 Q 756 122 756 130 L 756 310 Q 756 318 748 318 L 88 318 Q 80 318 80 310 L 80 148" fill="none" stroke="#555" stroke-width="1.5" marker-end="url(#arrow)" stroke-dasharray="6,3"/>
|
||||
<text x="400" y="340" fill="#64748b" font-size="10" text-anchor="middle">ツール結果 → messages[] → 圧縮 → 記憶読み込み → LLM → 毎ターン終了後に抽出</text>
|
||||
<text x="400" y="340" fill="#64748b" font-size="10" text-anchor="middle">tool result → messages[] → 選択 → recall → LLM → turn 終了後に抽出</text>
|
||||
|
||||
<!-- ===== Bottom notes ===== -->
|
||||
<rect x="40" y="358" width="680" height="56" rx="6" fill="#f8fafc" stroke="#e2e8f0" stroke-width="1"/>
|
||||
<rect x="60" y="372" width="12" height="10" rx="2" fill="#f0f4ff" stroke="#2563eb" stroke-width="1"/>
|
||||
<text x="80" y="382" fill="#475569" font-size="10">s08 維持:budget → snip → micro → summary + エラー回復 + ループ</text>
|
||||
<text x="80" y="382" fill="#475569" font-size="10">Agent Loop:messages → LLM → tool_use → tool result → messages</text>
|
||||
<rect x="60" y="392" width="12" height="10" rx="2" fill="#f3e8ff" stroke="#7c3aed" stroke-width="1"/>
|
||||
<text x="80" y="402" fill="#475569" font-size="10">s09 追加:Loading(インデックス常駐 + オンデマンド注入)+ Extraction(毎ターン終了後)+ Consolidation(閾値トリガー)</text>
|
||||
<text x="80" y="402" fill="#475569" font-size="10">Memory:record 選択 → 本文 recall → 永続知識を抽出 → threshold で整理</text>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 7.1 KiB |
@@ -19,42 +19,42 @@
|
||||
<!-- Title -->
|
||||
<rect x="0" y="0" width="760" height="44" fill="url(#header)" rx="8"/>
|
||||
<rect x="0" y="36" width="760" height="8" fill="url(#header)"/>
|
||||
<text x="380" y="28" fill="#fff" font-size="15" font-weight="700" text-anchor="middle">Memory — 在 s08 压缩管线上,插入记忆加载、提取与整理</text>
|
||||
<text x="380" y="28" fill="#fff" font-size="15" font-weight="700" text-anchor="middle">Memory — 存储、召回、提取与整理</text>
|
||||
|
||||
<!-- Legend -->
|
||||
<rect x="40" y="56" width="12" height="10" rx="2" fill="#f0f4ff" stroke="#2563eb" stroke-width="1"/>
|
||||
<text x="58" y="66" fill="#2563eb" font-size="10" font-weight="600">s08 保留</text>
|
||||
<text x="58" y="66" fill="#2563eb" font-size="10" font-weight="600">Agent Loop</text>
|
||||
<rect x="140" y="56" width="12" height="10" rx="2" fill="#f3e8ff" stroke="#7c3aed" stroke-width="1"/>
|
||||
<text x="158" y="66" fill="#7c3aed" font-size="10" font-weight="600">s09 新增</text>
|
||||
<text x="158" y="66" fill="#7c3aed" font-size="10" font-weight="600">Memory</text>
|
||||
|
||||
<!-- ===== messages[] ===== -->
|
||||
<rect x="30" y="96" width="100" height="52" rx="8" fill="#f0f4ff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="80" y="126" fill="#1e3a5f" font-size="12" font-weight="600" text-anchor="middle">messages[]</text>
|
||||
|
||||
<!-- arrow → compression -->
|
||||
<line x1="130" y1="122" x2="152" y2="122" stroke="#555" stroke-width="1.5" marker-end="url(#arrow)"/>
|
||||
<!-- arrow to selection -->
|
||||
<line x1="130" y1="122" x2="152" y2="122" stroke="#7c3aed" stroke-width="1.5" marker-end="url(#arrow-purple)"/>
|
||||
|
||||
<!-- ===== Compression pipeline (s08) ===== -->
|
||||
<rect x="155" y="86" width="135" height="72" rx="8" fill="#f0f4ff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="222" y="108" fill="#1e3a5f" font-size="11" font-weight="700" text-anchor="middle">压缩管线</text>
|
||||
<text x="222" y="124" fill="#64748b" font-size="9" text-anchor="middle">budget → snip → micro</text>
|
||||
<text x="222" y="138" fill="#64748b" font-size="9" text-anchor="middle">→ compact_history</text>
|
||||
<text x="222" y="152" fill="#94a3b8" font-size="8" text-anchor="middle">(s08)</text>
|
||||
<!-- ===== Selection ===== -->
|
||||
<rect x="155" y="86" width="135" height="72" rx="8" fill="#f3e8ff" stroke="#7c3aed" stroke-width="2"/>
|
||||
<text x="222" y="108" fill="#5b21b6" font-size="11" font-weight="700" text-anchor="middle">选择</text>
|
||||
<text x="222" y="124" fill="#7c3aed" font-size="9" text-anchor="middle">目录 + 当前请求</text>
|
||||
<text x="222" y="138" fill="#7c3aed" font-size="9" text-anchor="middle">模型或关键词匹配</text>
|
||||
<text x="222" y="152" fill="#a78bfa" font-size="8" text-anchor="middle">≤ 5 条</text>
|
||||
|
||||
<!-- arrow → Loading (purple) -->
|
||||
<!-- arrow to recall -->
|
||||
<line x1="290" y1="122" x2="317" y2="122" stroke="#7c3aed" stroke-width="1.5" marker-end="url(#arrow-purple)"/>
|
||||
|
||||
<!-- ===== Loading (s09) ===== -->
|
||||
<!-- ===== Recall ===== -->
|
||||
<rect x="320" y="86" width="120" height="72" rx="8" fill="#f3e8ff" stroke="#7c3aed" stroke-width="2"/>
|
||||
<text x="380" y="108" fill="#5b21b6" font-size="11" font-weight="700" text-anchor="middle">Loading</text>
|
||||
<text x="380" y="124" fill="#7c3aed" font-size="9" text-anchor="middle">LLM side-query 选文件</text>
|
||||
<text x="380" y="138" fill="#7c3aed" font-size="9" text-anchor="middle">注入文件内容</text>
|
||||
<text x="380" y="152" fill="#a78bfa" font-size="8" text-anchor="middle">≤ 5 条</text>
|
||||
<text x="380" y="108" fill="#5b21b6" font-size="11" font-weight="700" text-anchor="middle">召回</text>
|
||||
<text x="380" y="124" fill="#7c3aed" font-size="9" text-anchor="middle">读取选中的文件</text>
|
||||
<text x="380" y="138" fill="#7c3aed" font-size="9" text-anchor="middle">加入 system context</text>
|
||||
<text x="380" y="152" fill="#a78bfa" font-size="8" text-anchor="middle">正文总量受限</text>
|
||||
|
||||
<!-- arrow → LLM -->
|
||||
<line x1="440" y1="122" x2="472" y2="122" stroke="#555" stroke-width="1.5" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- ===== LLM (s08) ===== -->
|
||||
<!-- ===== LLM ===== -->
|
||||
<rect x="475" y="96" width="80" height="52" rx="8" fill="#f0f4ff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="515" y="114" fill="#1e3a5f" font-size="14" font-weight="700" text-anchor="middle">LLM</text>
|
||||
<text x="515" y="132" fill="#64748b" font-size="9" text-anchor="middle">stop_reason</text>
|
||||
@@ -70,19 +70,21 @@
|
||||
<line x1="555" y1="122" x2="587" y2="122" stroke="#555" stroke-width="1.5" marker-end="url(#arrow)"/>
|
||||
<text x="568" y="114" fill="#64748b" font-size="9" font-weight="600">是</text>
|
||||
|
||||
<!-- ===== TOOL_HANDLERS (s08) ===== -->
|
||||
<!-- ===== TOOL_HANDLERS ===== -->
|
||||
<rect x="590" y="88" width="130" height="68" rx="8" fill="#f0f4ff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="655" y="112" fill="#1e3a5f" font-size="10" font-weight="600" text-anchor="middle">TOOL_HANDLERS</text>
|
||||
<text x="655" y="128" fill="#64748b" font-size="9" text-anchor="middle">bash · read · write</text>
|
||||
<text x="655" y="142" fill="#94a3b8" font-size="8" text-anchor="middle">edit · glob · task</text>
|
||||
<text x="655" y="142" fill="#94a3b8" font-size="8" text-anchor="middle">edit · glob</text>
|
||||
|
||||
<!-- ===== Memory Files (s09) ===== -->
|
||||
<rect x="155" y="232" width="430" height="36" rx="6" fill="#faf5ff" stroke="#7c3aed" stroke-width="1.5" stroke-dasharray="4,2"/>
|
||||
<text x="370" y="255" fill="#5b21b6" font-size="11" font-weight="600" text-anchor="middle">.memory/ — MEMORY.md 索引 + *.md 文件(跨会话持久化)</text>
|
||||
|
||||
<!-- Arrow: Memory Files → Loading -->
|
||||
<!-- Arrow: Memory Files to Selection -->
|
||||
<path d="M 240 232 L 240 162" fill="none" stroke="#7c3aed" stroke-width="1.5" marker-end="url(#arrow-purple)"/>
|
||||
<text x="253" y="200" fill="#7c3aed" font-size="9">目录</text>
|
||||
<path d="M 395 232 L 395 162" fill="none" stroke="#7c3aed" stroke-width="1.5" marker-end="url(#arrow-purple)"/>
|
||||
<text x="408" y="200" fill="#7c3aed" font-size="9">读取</text>
|
||||
<text x="408" y="200" fill="#7c3aed" font-size="9">正文</text>
|
||||
|
||||
<!-- Arrow: 返回结果 → Extraction → Memory Files -->
|
||||
<path d="M 515 204 L 515 232" fill="none" stroke="#7c3aed" stroke-width="1.5" marker-end="url(#arrow-purple)"/>
|
||||
@@ -93,12 +95,12 @@
|
||||
|
||||
<!-- ===== Loop back ===== -->
|
||||
<path d="M 720 122 L 748 122 Q 756 122 756 130 L 756 310 Q 756 318 748 318 L 88 318 Q 80 318 80 310 L 80 148" fill="none" stroke="#555" stroke-width="1.5" marker-end="url(#arrow)" stroke-dasharray="6,3"/>
|
||||
<text x="400" y="340" fill="#64748b" font-size="10" text-anchor="middle">工具结果追加到 messages[] → 压缩 → 加载记忆 → LLM → 每轮结束后提取</text>
|
||||
<text x="400" y="340" fill="#64748b" font-size="10" text-anchor="middle">工具结果 → messages[] → 选择 → 召回 → LLM → 回合结束后提取</text>
|
||||
|
||||
<!-- ===== Bottom notes ===== -->
|
||||
<rect x="40" y="358" width="680" height="56" rx="6" fill="#f8fafc" stroke="#e2e8f0" stroke-width="1"/>
|
||||
<rect x="60" y="372" width="12" height="10" rx="2" fill="#f0f4ff" stroke="#2563eb" stroke-width="1"/>
|
||||
<text x="80" y="382" fill="#475569" font-size="10">s08 保留:budget → snip → micro → summary + 错误后补救 + 循环</text>
|
||||
<text x="80" y="382" fill="#475569" font-size="10">Agent Loop:messages → LLM → tool_use → 工具结果 → messages</text>
|
||||
<rect x="60" y="392" width="12" height="10" rx="2" fill="#f3e8ff" stroke="#7c3aed" stroke-width="1"/>
|
||||
<text x="80" y="402" fill="#475569" font-size="10">s09 新增:Loading(索引常驻 + 按需注入)+ Extraction(每轮结束后)+ Consolidation(阈值触发)</text>
|
||||
<text x="80" y="402" fill="#475569" font-size="10">Memory:选择相关记录 → 召回正文 → 提取持久知识 → 达到阈值后整理</text>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 7.0 KiB After Width: | Height: | Size: 7.0 KiB |
@@ -1,4 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 720 380" font-family="system-ui, -apple-system, sans-serif">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 720 300" font-family="system-ui, -apple-system, sans-serif">
|
||||
<defs>
|
||||
<linearGradient id="header" x1="0" y1="0" x2="1" y2="0">
|
||||
<stop offset="0%" stop-color="#1e3a5f"/><stop offset="100%" stop-color="#7c3aed"/>
|
||||
@@ -8,10 +8,10 @@
|
||||
</marker>
|
||||
</defs>
|
||||
|
||||
<rect width="720" height="380" fill="#fafbfc" rx="8"/>
|
||||
<rect width="720" height="300" fill="#fafbfc" rx="8"/>
|
||||
<rect x="0" y="0" width="720" height="38" fill="url(#header)" rx="8"/>
|
||||
<rect x="0" y="30" width="720" height="8" fill="url(#header)"/>
|
||||
<text x="360" y="25" fill="#fff" font-size="14" font-weight="700" text-anchor="middle">Memory System — Store · Load · Extract · Consolidate</text>
|
||||
<text x="360" y="25" fill="#fff" font-size="14" font-weight="700" text-anchor="middle">Memory System — Store · Recall · Extract · Consolidate</text>
|
||||
|
||||
<!-- Storage -->
|
||||
<rect x="40" y="58" width="145" height="80" rx="8" fill="#ede9fe" stroke="#7c3aed" stroke-width="2"/>
|
||||
@@ -22,12 +22,12 @@
|
||||
|
||||
<line x1="190" y1="98" x2="218" y2="98" stroke="#7c3aed" stroke-width="1.5" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- Loading -->
|
||||
<!-- Recall -->
|
||||
<rect x="222" y="58" width="200" height="80" rx="8" fill="#ede9fe" stroke="#7c3aed" stroke-width="2"/>
|
||||
<text x="322" y="80" fill="#5b21b6" font-size="13" font-weight="700" text-anchor="middle">Load</text>
|
||||
<text x="322" y="80" fill="#5b21b6" font-size="13" font-weight="700" text-anchor="middle">Recall</text>
|
||||
<line x1="237" y1="90" x2="407" y2="90" stroke="#c4b5fd" stroke-width="0.5"/>
|
||||
<text x="237" y="108" fill="#5b21b6" font-size="10">Index in SYSTEM (always)</text>
|
||||
<text x="237" y="124" fill="#5b21b6" font-size="10">LLM side-query select files</text>
|
||||
<text x="237" y="108" fill="#5b21b6" font-size="10">Index in the system prompt</text>
|
||||
<text x="237" y="124" fill="#5b21b6" font-size="10">Model selects relevant files</text>
|
||||
<text x="237" y="134" fill="#a78bfa" font-size="9">≤ 5 items, fallback to keyword</text>
|
||||
|
||||
<line x1="425" y1="98" x2="453" y2="98" stroke="#7c3aed" stroke-width="1.5" marker-end="url(#arrow)"/>
|
||||
@@ -36,9 +36,9 @@
|
||||
<rect x="457" y="58" width="130" height="80" rx="8" fill="#f3e8ff" stroke="#7c3aed" stroke-width="2"/>
|
||||
<text x="522" y="80" fill="#5b21b6" font-size="13" font-weight="700" text-anchor="middle">Extract</text>
|
||||
<line x1="472" y1="90" x2="572" y2="90" stroke="#c4b5fd" stroke-width="0.5"/>
|
||||
<text x="472" y="106" fill="#5b21b6" font-size="9.5">After each turn</text>
|
||||
<text x="472" y="121" fill="#5b21b6" font-size="9.5">Extract prefs</text>
|
||||
<text x="472" y="134" fill="#a78bfa" font-size="8.5">Avoid duplicates</text>
|
||||
<text x="472" y="106" fill="#5b21b6" font-size="9.5">After the turn</text>
|
||||
<text x="472" y="121" fill="#5b21b6" font-size="9.5">Extract durable knowledge</text>
|
||||
<text x="472" y="134" fill="#a78bfa" font-size="8.5">Scope + duplicate checks</text>
|
||||
|
||||
<!-- Consolidation -->
|
||||
<rect x="600" y="58" width="100" height="80" rx="8" fill="#f5f3ff" stroke="#7c3aed" stroke-width="2"/>
|
||||
@@ -46,7 +46,7 @@
|
||||
<line x1="615" y1="90" x2="685" y2="90" stroke="#c4b5fd" stroke-width="0.5"/>
|
||||
<text x="615" y="106" fill="#5b21b6" font-size="9.5">≥ 10 files</text>
|
||||
<text x="615" y="121" fill="#5b21b6" font-size="9.5">Dedup · merge</text>
|
||||
<text x="615" y="134" fill="#a78bfa" font-size="8.5">CC: gated Dream</text>
|
||||
<text x="615" y="134" fill="#a78bfa" font-size="8.5">Snapshot + rollback</text>
|
||||
|
||||
<!-- Memory Files -->
|
||||
<rect x="40" y="180" width="660" height="36" rx="6" fill="#f8fafc" stroke="#94a3b8" stroke-width="1" stroke-dasharray="4,2"/>
|
||||
@@ -69,10 +69,4 @@
|
||||
<text x="60" y="260" fill="#5b21b6" font-size="10" font-weight="600">Four types:</text>
|
||||
<text x="140" y="260" fill="#475569" font-size="10">user (who you are) · feedback (how to work) · project (what's happening) · reference (where to find things)</text>
|
||||
|
||||
<!-- CC source comparison -->
|
||||
<rect x="40" y="296" width="660" height="72" rx="6" fill="#f8fafc" stroke="#e2e8f0" stroke-width="1"/>
|
||||
<text x="60" y="316" fill="#5b21b6" font-size="11" font-weight="600">CC Source Comparison</text>
|
||||
<text x="60" y="334" fill="#475569" font-size="10">• Selection: LLM side-query (Sonnet selects), not embedding vector similarity</text>
|
||||
<text x="60" y="350" fill="#475569" font-size="10">• Extraction timing: stop hook after each turn, separate from compact_history</text>
|
||||
<text x="60" y="365" fill="#475569" font-size="10">• Dream: time + sessions + file lock, not simple count</text>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 4.5 KiB |
@@ -1,4 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 720 380" font-family="system-ui, -apple-system, sans-serif">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 720 300" font-family="system-ui, -apple-system, sans-serif">
|
||||
<defs>
|
||||
<linearGradient id="header" x1="0" y1="0" x2="1" y2="0">
|
||||
<stop offset="0%" stop-color="#1e3a5f"/><stop offset="100%" stop-color="#7c3aed"/>
|
||||
@@ -8,10 +8,10 @@
|
||||
</marker>
|
||||
</defs>
|
||||
|
||||
<rect width="720" height="380" fill="#fafbfc" rx="8"/>
|
||||
<rect width="720" height="300" fill="#fafbfc" rx="8"/>
|
||||
<rect x="0" y="0" width="720" height="38" fill="url(#header)" rx="8"/>
|
||||
<rect x="0" y="30" width="720" height="8" fill="url(#header)"/>
|
||||
<text x="360" y="25" fill="#fff" font-size="14" font-weight="700" text-anchor="middle">Memory System — ストレージ · 読み込み · 抽出 · 整理</text>
|
||||
<text x="360" y="25" fill="#fff" font-size="14" font-weight="700" text-anchor="middle">Memory System — 保存 · Recall · 抽出 · 整理</text>
|
||||
|
||||
<!-- ストレージ -->
|
||||
<rect x="40" y="58" width="145" height="80" rx="8" fill="#ede9fe" stroke="#7c3aed" stroke-width="2"/>
|
||||
@@ -22,12 +22,12 @@
|
||||
|
||||
<line x1="190" y1="98" x2="218" y2="98" stroke="#7c3aed" stroke-width="1.5" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- 読み込み -->
|
||||
<!-- Recall -->
|
||||
<rect x="222" y="58" width="200" height="80" rx="8" fill="#ede9fe" stroke="#7c3aed" stroke-width="2"/>
|
||||
<text x="322" y="80" fill="#5b21b6" font-size="13" font-weight="700" text-anchor="middle">読み込み</text>
|
||||
<text x="322" y="80" fill="#5b21b6" font-size="13" font-weight="700" text-anchor="middle">Recall</text>
|
||||
<line x1="237" y1="90" x2="407" y2="90" stroke="#c4b5fd" stroke-width="0.5"/>
|
||||
<text x="237" y="108" fill="#5b21b6" font-size="10">インデックスを SYSTEM に常駐</text>
|
||||
<text x="237" y="124" fill="#5b21b6" font-size="10">LLM side-query でファイル選択</text>
|
||||
<text x="237" y="108" fill="#5b21b6" font-size="10">index を system prompt へ追加</text>
|
||||
<text x="237" y="124" fill="#5b21b6" font-size="10">モデルが関連ファイルを選択</text>
|
||||
<text x="237" y="134" fill="#a78bfa" font-size="9">≤ 5 件、失敗時はキーワードに降格</text>
|
||||
|
||||
<line x1="425" y1="98" x2="453" y2="98" stroke="#7c3aed" stroke-width="1.5" marker-end="url(#arrow)"/>
|
||||
@@ -36,9 +36,9 @@
|
||||
<rect x="457" y="58" width="130" height="80" rx="8" fill="#f3e8ff" stroke="#7c3aed" stroke-width="2"/>
|
||||
<text x="522" y="80" fill="#5b21b6" font-size="13" font-weight="700" text-anchor="middle">抽出</text>
|
||||
<line x1="472" y1="90" x2="572" y2="90" stroke="#c4b5fd" stroke-width="0.5"/>
|
||||
<text x="472" y="106" fill="#5b21b6" font-size="9.5">毎ターン終了後</text>
|
||||
<text x="472" y="121" fill="#5b21b6" font-size="9.5">好み/制約を抽出</text>
|
||||
<text x="472" y="134" fill="#a78bfa" font-size="8.5">重複を回避</text>
|
||||
<text x="472" y="106" fill="#5b21b6" font-size="9.5">turn 終了後</text>
|
||||
<text x="472" y="121" fill="#5b21b6" font-size="9.5">永続知識を抽出</text>
|
||||
<text x="472" y="134" fill="#a78bfa" font-size="8.5">scope + 重複確認</text>
|
||||
|
||||
<!-- 整理 -->
|
||||
<rect x="600" y="58" width="100" height="80" rx="8" fill="#f5f3ff" stroke="#7c3aed" stroke-width="2"/>
|
||||
@@ -46,7 +46,7 @@
|
||||
<line x1="615" y1="90" x2="685" y2="90" stroke="#c4b5fd" stroke-width="0.5"/>
|
||||
<text x="615" y="106" fill="#5b21b6" font-size="9.5">≥ 10 ファイル</text>
|
||||
<text x="615" y="121" fill="#5b21b6" font-size="9.5">重複排除・統合</text>
|
||||
<text x="615" y="134" fill="#a78bfa" font-size="8.5">CC: Dream ゲート</text>
|
||||
<text x="615" y="134" fill="#a78bfa" font-size="8.5">snapshot + rollback</text>
|
||||
|
||||
<!-- Memory Files -->
|
||||
<rect x="40" y="180" width="660" height="36" rx="6" fill="#f8fafc" stroke="#94a3b8" stroke-width="1" stroke-dasharray="4,2"/>
|
||||
@@ -69,10 +69,4 @@
|
||||
<text x="60" y="260" fill="#5b21b6" font-size="10" font-weight="600">4 種類の記憶:</text>
|
||||
<text x="148" y="260" fill="#475569" font-size="10">user(あなたは誰か)· feedback(どう作業するか)· project(何が起きているか)· reference(どこで探すか)</text>
|
||||
|
||||
<!-- CC ソースコード対照 -->
|
||||
<rect x="40" y="296" width="660" height="72" rx="6" fill="#f8fafc" stroke="#e2e8f0" stroke-width="1"/>
|
||||
<text x="60" y="316" fill="#5b21b6" font-size="11" font-weight="600">CC ソースコード対照</text>
|
||||
<text x="60" y="334" fill="#475569" font-size="10">• 記憶選択:LLM side-query(Sonnet が選択)、embedding ベクトル類似度ではない</text>
|
||||
<text x="60" y="350" fill="#475569" font-size="10">• 抽出タイミング:各ターン終了時の stop hook、compact_history とは別に実行</text>
|
||||
<text x="60" y="365" fill="#475569" font-size="10">• Dream:時間・セッション・ロックで判定</text>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 4.6 KiB |
@@ -1,4 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 720 380" font-family="system-ui, -apple-system, sans-serif">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 720 300" font-family="system-ui, -apple-system, sans-serif">
|
||||
<defs>
|
||||
<linearGradient id="header" x1="0" y1="0" x2="1" y2="0">
|
||||
<stop offset="0%" stop-color="#1e3a5f"/><stop offset="100%" stop-color="#7c3aed"/>
|
||||
@@ -8,10 +8,10 @@
|
||||
</marker>
|
||||
</defs>
|
||||
|
||||
<rect width="720" height="380" fill="#fafbfc" rx="8"/>
|
||||
<rect width="720" height="300" fill="#fafbfc" rx="8"/>
|
||||
<rect x="0" y="0" width="720" height="38" fill="url(#header)" rx="8"/>
|
||||
<rect x="0" y="30" width="720" height="8" fill="url(#header)"/>
|
||||
<text x="360" y="25" fill="#fff" font-size="14" font-weight="700" text-anchor="middle">Memory System — 存储 · 加载 · 提取 · 整理</text>
|
||||
<text x="360" y="25" fill="#fff" font-size="14" font-weight="700" text-anchor="middle">Memory System — 存储 · 召回 · 提取 · 整理</text>
|
||||
|
||||
<!-- 存储 -->
|
||||
<rect x="40" y="58" width="145" height="80" rx="8" fill="#ede9fe" stroke="#7c3aed" stroke-width="2"/>
|
||||
@@ -22,12 +22,12 @@
|
||||
|
||||
<line x1="190" y1="98" x2="218" y2="98" stroke="#7c3aed" stroke-width="1.5" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- 加载 -->
|
||||
<!-- 召回 -->
|
||||
<rect x="222" y="58" width="200" height="80" rx="8" fill="#ede9fe" stroke="#7c3aed" stroke-width="2"/>
|
||||
<text x="322" y="80" fill="#5b21b6" font-size="13" font-weight="700" text-anchor="middle">加载</text>
|
||||
<text x="322" y="80" fill="#5b21b6" font-size="13" font-weight="700" text-anchor="middle">召回</text>
|
||||
<line x1="237" y1="90" x2="407" y2="90" stroke="#c4b5fd" stroke-width="0.5"/>
|
||||
<text x="237" y="108" fill="#5b21b6" font-size="10">索引常驻 SYSTEM</text>
|
||||
<text x="237" y="124" fill="#5b21b6" font-size="10">LLM side-query 选文件</text>
|
||||
<text x="237" y="108" fill="#5b21b6" font-size="10">索引加入 system prompt</text>
|
||||
<text x="237" y="124" fill="#5b21b6" font-size="10">模型选择相关文件</text>
|
||||
<text x="237" y="134" fill="#a78bfa" font-size="9">≤ 5 条,失败降级到关键词</text>
|
||||
|
||||
<line x1="425" y1="98" x2="453" y2="98" stroke="#7c3aed" stroke-width="1.5" marker-end="url(#arrow)"/>
|
||||
@@ -36,9 +36,9 @@
|
||||
<rect x="457" y="58" width="130" height="80" rx="8" fill="#f3e8ff" stroke="#7c3aed" stroke-width="2"/>
|
||||
<text x="522" y="80" fill="#5b21b6" font-size="13" font-weight="700" text-anchor="middle">提取</text>
|
||||
<line x1="472" y1="90" x2="572" y2="90" stroke="#c4b5fd" stroke-width="0.5"/>
|
||||
<text x="472" y="108" fill="#5b21b6" font-size="10">每轮结束后触发</text>
|
||||
<text x="472" y="124" fill="#5b21b6" font-size="10">LLM 提取偏好/约束</text>
|
||||
<text x="472" y="134" fill="#a78bfa" font-size="9">检查已有,避免重复</text>
|
||||
<text x="472" y="108" fill="#5b21b6" font-size="10">回合结束后触发</text>
|
||||
<text x="472" y="124" fill="#5b21b6" font-size="10">模型提取持久知识</text>
|
||||
<text x="472" y="134" fill="#a78bfa" font-size="9">scope + 重复检查</text>
|
||||
|
||||
<!-- 整理 -->
|
||||
<rect x="600" y="58" width="100" height="80" rx="8" fill="#f5f3ff" stroke="#7c3aed" stroke-width="2"/>
|
||||
@@ -46,7 +46,7 @@
|
||||
<line x1="615" y1="90" x2="685" y2="90" stroke="#c4b5fd" stroke-width="0.5"/>
|
||||
<text x="615" y="108" fill="#5b21b6" font-size="10">文件 ≥ 10 触发</text>
|
||||
<text x="615" y="124" fill="#5b21b6" font-size="10">去重·合并·剪枝</text>
|
||||
<text x="615" y="134" fill="#a78bfa" font-size="9">CC: 三层门控</text>
|
||||
<text x="615" y="134" fill="#a78bfa" font-size="9">快照 + 失败恢复</text>
|
||||
|
||||
<!-- Memory Files -->
|
||||
<rect x="40" y="180" width="660" height="36" rx="6" fill="#f8fafc" stroke="#94a3b8" stroke-width="1" stroke-dasharray="4,2"/>
|
||||
@@ -69,10 +69,4 @@
|
||||
<text x="60" y="260" fill="#5b21b6" font-size="10" font-weight="600">四类记忆:</text>
|
||||
<text x="140" y="260" fill="#475569" font-size="10">user(你是谁)· feedback(怎么做事)· project(正在发生什么)· reference(东西在哪找)</text>
|
||||
|
||||
<!-- CC 源码对照 -->
|
||||
<rect x="40" y="296" width="660" height="72" rx="6" fill="#f8fafc" stroke="#e2e8f0" stroke-width="1"/>
|
||||
<text x="60" y="316" fill="#5b21b6" font-size="11" font-weight="600">CC 源码对照</text>
|
||||
<text x="60" y="334" fill="#475569" font-size="10">• 记忆选择:LLM side-query(Sonnet 选),不是 embedding 向量相似度</text>
|
||||
<text x="60" y="350" fill="#475569" font-size="10">• 提取时机:每轮结束时由 stop hook 触发,与 compact_history 分开执行</text>
|
||||
<text x="60" y="366" fill="#475569" font-size="10">• Dream 整理:三层门控(时间 ≥ 24h + 会话 ≥ 5 + 文件锁),不是简单计数</text>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 4.5 KiB |