am-memory 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. am_memory-0.1.0/PKG-INFO +573 -0
  2. am_memory-0.1.0/README.md +551 -0
  3. am_memory-0.1.0/agent_memory/__init__.py +4 -0
  4. am_memory-0.1.0/agent_memory/cli.py +425 -0
  5. am_memory-0.1.0/agent_memory/dashboard.py +488 -0
  6. am_memory-0.1.0/agent_memory/db.py +264 -0
  7. am_memory-0.1.0/agent_memory/dream.py +1057 -0
  8. am_memory-0.1.0/agent_memory/embedding.py +278 -0
  9. am_memory-0.1.0/agent_memory/extract.py +120 -0
  10. am_memory-0.1.0/agent_memory/init_cmd.py +181 -0
  11. am_memory-0.1.0/agent_memory/llm.py +43 -0
  12. am_memory-0.1.0/agent_memory/llm_extract.py +186 -0
  13. am_memory-0.1.0/agent_memory/mcp_server.py +278 -0
  14. am_memory-0.1.0/agent_memory/models.py +21 -0
  15. am_memory-0.1.0/agent_memory/search.py +380 -0
  16. am_memory-0.1.0/agent_memory/session.py +567 -0
  17. am_memory-0.1.0/agent_memory/state.py +51 -0
  18. am_memory-0.1.0/agent_memory/store.py +717 -0
  19. am_memory-0.1.0/agent_memory/vector.py +115 -0
  20. am_memory-0.1.0/agent_memory/watch.py +81 -0
  21. am_memory-0.1.0/agent_memory/write_queue.py +186 -0
  22. am_memory-0.1.0/am_memory.egg-info/PKG-INFO +573 -0
  23. am_memory-0.1.0/am_memory.egg-info/SOURCES.txt +41 -0
  24. am_memory-0.1.0/am_memory.egg-info/dependency_links.txt +1 -0
  25. am_memory-0.1.0/am_memory.egg-info/entry_points.txt +2 -0
  26. am_memory-0.1.0/am_memory.egg-info/requires.txt +8 -0
  27. am_memory-0.1.0/am_memory.egg-info/top_level.txt +1 -0
  28. am_memory-0.1.0/pyproject.toml +38 -0
  29. am_memory-0.1.0/setup.cfg +4 -0
  30. am_memory-0.1.0/tests/test_cli.py +117 -0
  31. am_memory-0.1.0/tests/test_dashboard.py +102 -0
  32. am_memory-0.1.0/tests/test_db.py +58 -0
  33. am_memory-0.1.0/tests/test_dream.py +560 -0
  34. am_memory-0.1.0/tests/test_dream_e2e.py +290 -0
  35. am_memory-0.1.0/tests/test_embedding.py +81 -0
  36. am_memory-0.1.0/tests/test_extract.py +80 -0
  37. am_memory-0.1.0/tests/test_integration.py +71 -0
  38. am_memory-0.1.0/tests/test_search.py +148 -0
  39. am_memory-0.1.0/tests/test_session.py +252 -0
  40. am_memory-0.1.0/tests/test_state.py +25 -0
  41. am_memory-0.1.0/tests/test_store.py +433 -0
  42. am_memory-0.1.0/tests/test_watch.py +43 -0
  43. am_memory-0.1.0/tests/test_write_queue.py +203 -0
@@ -0,0 +1,573 @@
1
+ Metadata-Version: 2.4
2
+ Name: am-memory
3
+ Version: 0.1.0
4
+ Summary: Persistent memory for AI coding agents — SQLite-backed knowledge store with BM25+Vector search and MCP integration
5
+ License: MIT
6
+ Keywords: claude,mcp,memory,rag,claude-code
7
+ Classifier: Development Status :: 4 - Beta
8
+ Classifier: Intended Audience :: Developers
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.10
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Requires-Python: >=3.10
15
+ Description-Content-Type: text/markdown
16
+ Requires-Dist: mcp>=1.0
17
+ Provides-Extra: vector
18
+ Requires-Dist: sqlite-vec; extra == "vector"
19
+ Provides-Extra: dev
20
+ Requires-Dist: pytest; extra == "dev"
21
+ Requires-Dist: pytest-cov; extra == "dev"
22
+
23
+ # am-memory
24
+
25
+ Persistent memory for [Claude Code](https://claude.ai/code) — a self-evolving knowledge layer that survives across sessions, grows from every conversation, and surfaces relevant context automatically.
26
+
27
+ ```
28
+ pip install am-memory
29
+ am init
30
+ # Restart Claude Code → done
31
+ ```
32
+
33
+ No external database. No cloud service. A single SQLite file at `~/.am-memory/memory.db`.
34
+
35
+ ---
36
+
37
+ ## What problem does this solve?
38
+
39
+ Every Claude Code session starts from zero. Context window fills up. You re-explain the same architecture, re-debug the same gotchas, re-type the same config values — session after session.
40
+
41
+ `am-memory` gives Claude a memory that persists across sessions. Knowledge is captured automatically from your conversations and file edits. A debugging insight from six months ago surfaces when it's relevant today.
42
+
43
+ ---
44
+
45
+ ## Quick Start
46
+
47
+ **Requirements:** Python 3.10+, Claude Code
48
+
49
+ ```bash
50
+ pip install am-memory
51
+ am init
52
+ ```
53
+
54
+ `am init` does everything in one shot:
55
+
56
+ ```
57
+ ✓ Created ~/.am-memory/memory.db
58
+ ✓ Registered MCP server in ~/.claude/mcp.json
59
+ ✓ Installed hook: ~/.claude/hooks/SessionStart/am-memory.sh
60
+ ✓ Installed hook: ~/.claude/hooks/Stop/am-memory.sh
61
+ ✓ Appended memory instructions to ~/.claude/CLAUDE.md
62
+ ```
63
+
64
+ Restart Claude Code. Done. Claude can now search and save memory across sessions.
65
+
66
+ ---
67
+
68
+ ## How it works
69
+
70
+ ### The big picture
71
+
72
+ ![System Overview](docs/img/overview.png)
73
+
74
+ ### Session lifecycle
75
+
76
+ ```
77
+ Claude Code opens
78
+
79
+
80
+ SessionStart hook
81
+ → am session start --project myproject
82
+ → writes session_id to state table
83
+
84
+
85
+ Claude works (multiple turns)
86
+ → calls am_search before answering
87
+ → calls am_save when it learns something
88
+
89
+
90
+ Claude Code closes (Stop hook)
91
+ → am session end
92
+ → quality-gate promote:
93
+ decisions non-empty → P0 (never expires)
94
+ key_facts ≥ 3 → P1 (90 days)
95
+ else → P2 (30 days)
96
+ → session promoted to documents table
97
+ → raw session + messages deleted
98
+ ```
99
+
100
+ ---
101
+
102
+ ## Architecture: memory.db
103
+
104
+ One SQLite file. Four semantic layers.
105
+
106
+ ```
107
+ ~/.am-memory/memory.db
108
+
109
+ │ ── SEARCH LAYER (virtual) ──────────────────────────────────────
110
+
111
+ ├── documents_fts FTS5 virtual table
112
+ │ trigram tokenizer (Chinese + English)
113
+ │ indexes: title, summary, key_facts, decisions
114
+ │ auto-synced via INSERT/UPDATE/DELETE triggers
115
+ │ BM25 ranking
116
+
117
+ ├── vec_documents sqlite-vec virtual table (optional)
118
+ │ float32[4096] HNSW index
119
+ │ cosine similarity
120
+ │ joined to documents via document_id FK
121
+
122
+ │ ── KNOWLEDGE LAYER ─────────────────────────────────────────────
123
+
124
+ ├── documents long-term knowledge store
125
+ │ ├── doc_id INTEGER PK
126
+ │ ├── title TEXT — primary search signal, BM25 weight ×3
127
+ │ ├── summary TEXT — 2-3 sentence distillation
128
+ │ ├── key_facts TEXT — JSON array of extracted facts
129
+ │ ├── decisions TEXT — JSON array of architectural decisions
130
+ │ ├── code_sigs TEXT — function/class names mentioned
131
+ │ ├── embedding BLOB — float32[4096], populated async
132
+ │ ├── priority P0 | P1 | P2
133
+ │ ├── source explicit | session_extract | hook | file
134
+ │ ├── file_path TEXT UNIQUE — upsert key for file-sourced docs
135
+ │ ├── expires_at REAL — NULL = never expires (P0)
136
+ │ └── last_accessed_at REAL — updated on every search hit (LRU)
137
+
138
+ │ ── CONVERSATION LAYER ──────────────────────────────────────────
139
+
140
+ ├── sessions session lifecycle records
141
+ │ ├── session_id TEXT PK
142
+ │ ├── project, topic metadata
143
+ │ ├── source "cli:myproject"
144
+ │ ├── summary extracted at session end
145
+ │ ├── key_facts JSON array
146
+ │ └── decisions JSON array
147
+
148
+ ├── messages raw message storage (30-day TTL, auto-pruned)
149
+
150
+ │ ── WORKING MEMORY ──────────────────────────────────────────────
151
+
152
+ └── state K/V working memory
153
+ ├── current_session_id
154
+ ├── active_work JSON: { ticket, topic, ... }
155
+ └── scratchpad freeform per-session notes
156
+ ```
157
+
158
+ ---
159
+
160
+ ## Architecture: search
161
+
162
+ Every `am_search` call flows through a 3.5-layer cascade. It stops at the first layer that returns results. The cheapest path is always tried first.
163
+
164
+ ![Search Cascade](docs/img/search.png)
165
+
166
+ > **Score ranges are not comparable across layers.**
167
+ > Sub-L1: 0.008–0.066 · Sub-L2: 1e-6–0.2 · Sub-L3: 4–500+
168
+ > Detect origin by magnitude.
169
+
170
+ ---
171
+
172
+ ## Architecture: memory lifecycle
173
+
174
+ Memory is not static. Three automated paths write to it continuously.
175
+
176
+ ![Memory Lifecycle](docs/img/lifecycle.png)
177
+
178
+ ### TTL + LRU
179
+
180
+ TTL is derived automatically from `source`. Accessed documents have their TTL reset on every search hit — frequently used knowledge never expires.
181
+
182
+ ![TTL and LRU](docs/img/ttl.png)
183
+
184
+ > **TTL only affects the search index.** Physical files in your knowledge base are never deleted. An expired document is unsearchable, but the file remains. Re-editing it via PostToolUse rebuilds the index entry automatically.
185
+
186
+ ---
187
+
188
+ ## Architecture: dream consolidation
189
+
190
+ `am dream` runs background memory consolidation in 5 phases:
191
+
192
+ ```
193
+ Phase 1: Orient — read existing documents (top 100 recent)
194
+ Phase 2: Gather — collect ended sessions since last dream
195
+ Phase 3: Consolidate — cross-session pattern detection + contradiction resolution
196
+ Phase 3.5: Health Check — proactive knowledge base quality maintenance
197
+ Phase 4: Prune — remove expired documents
198
+ ```
199
+
200
+ ### Phase 3.5: Health Check
201
+
202
+ The health check operates on the **entire knowledge base**, not just new sessions. It runs three sub-checks:
203
+
204
+ **1. Staleness Detection**
205
+
206
+ Scans non-P0 documents for `code_sigs` (function/class references) that no longer exist in the project directory. Documents with >50% dead references get downgraded:
207
+
208
+ - P1 → `expires_at` set to 7 days (grace period)
209
+ - P2 → `expires_at` set to now (pruned immediately in Phase 4)
210
+
211
+ **2. Cross-Document Contradiction Scan**
212
+
213
+ Extends contradiction detection across ALL documents (not just within a single consolidation batch). When two documents in the same project have conflicting facts (e.g., `timeout: 30s` vs `timeout: 600s`), the older fact is removed.
214
+
215
+ **3. Redundancy Merge**
216
+
217
+ Finds document pairs with >70% key_facts overlap (fuzzy Jaccard similarity) and merges the smaller into the larger. Unique facts and decisions from the source document transfer to the target before deletion.
218
+
219
+ When 3+ documents cluster around the same topic, a **concept index** document is generated — a lightweight map linking related documents with shared themes.
220
+
221
+ ### Gating
222
+
223
+ Dream only runs when:
224
+ - `min_hours` (default 24) have passed since last dream
225
+ - `min_sessions` (default 5) ended sessions exist since last dream
226
+ - Use `--force` to bypass gating
227
+
228
+ ```bash
229
+ am dream --force # run now
230
+ am dream --force --dry-run # preview planned actions without writing
231
+ am dream --status # check gate state
232
+ ```
233
+
234
+ ---
235
+
236
+ ## MCP tools reference
237
+
238
+ Claude calls these tools autonomously based on instructions injected by `am init` into `~/.claude/CLAUDE.md`.
239
+
240
+ ### `am_search`
241
+
242
+ Search persistent memory for relevant documents.
243
+
244
+ ```
245
+ Parameters:
246
+ query string required Keywords, technical terms, or concepts
247
+ limit integer default 5 Max results to return
248
+
249
+ Returns: structured context injection (~200 tokens)
250
+ Latency: ~50ms (BM25 only) · ~500ms (with Ollama vector)
251
+ ```
252
+
253
+ **When Claude calls it:** Before answering questions about past work, architecture decisions, debugging solutions, technical constraints.
254
+
255
+ ### `am_save`
256
+
257
+ Save a piece of knowledge to persistent memory.
258
+
259
+ ```
260
+ Parameters:
261
+ title string required One-line title — primary search signal
262
+ content string required Full context: facts, decisions, rationale
263
+ source enum required architectural_decision | debug_solution | technical_insight
264
+ | session_note | routine
265
+ Determines TTL automatically — no priority field needed.
266
+
267
+ Returns: doc_id of saved document
268
+ ```
269
+
270
+ **When Claude calls it:** After discovering a non-obvious config constraint, an architectural decision, a bug gotcha worth remembering.
271
+
272
+ ### `am_state_get` / `am_state_set`
273
+
274
+ Read/write K/V working memory (active task, scratchpad notes).
275
+
276
+ ```
277
+ am_state_get key: string
278
+ am_state_set key: string, value: any JSON-serializable
279
+ ```
280
+
281
+ ---
282
+
283
+ ## CLI reference
284
+
285
+ ```bash
286
+ am init # one-time setup (run once after install)
287
+ am update # upgrade to latest version
288
+
289
+ am search --query "kafka rebalance" # search documents (returns injection text)
290
+ am search --query "..." --format json # return raw JSON
291
+
292
+ am doc save \ # save a document
293
+ --title "bug title" \
294
+ --content "description" \
295
+ --source architectural_decision
296
+
297
+ am state get --key active_work # read working memory
298
+ am state set --key scratchpad \
299
+ --value "current task" # write working memory
300
+
301
+ am session start --project myproj # create session (prints session_id)
302
+ am session end --session-id S # end session + promote to documents
303
+ am session list # list recent sessions (JSON)
304
+
305
+ am mcp # start MCP server (stdio) — called by Claude Code
306
+ ```
307
+
308
+ ---
309
+
310
+ ## Optional: vector search with Ollama
311
+
312
+ By default, am-memory uses BM25 full-text search (Sub-Layer 2). This handles ~80% of queries well — technical terms, config keys, function names, error codes.
313
+
314
+ For fuzzy / semantic queries ("that kafka thing we fixed"), install Ollama and pull an embedding model:
315
+
316
+ ```bash
317
+ # Install Ollama: https://ollama.com
318
+ ollama pull qwen3-embedding:8b # 4096-dim, ~5GB
319
+
320
+ pip install 'am-memory[vector]'
321
+ ```
322
+
323
+ When Ollama is running, searches automatically upgrade to hybrid BM25 + vector (Sub-Layer 1). When Ollama is offline, they fall back to BM25 silently. No configuration required.
324
+
325
+ ---
326
+
327
+ ## Real-world scenario: 8-hour session expiry
328
+
329
+ Claude Code sessions expire after ~8 hours. Here is what that means in practice, using a real data pipeline debugging workflow.
330
+
331
+ **Setup:** You are a engineer working on a Python ETL pipeline built on Airflow + Spark/YARN. The pipeline uses a CPL(YAML-driven config system) that generates DAGs at runtime. You are debugging a flaky Spark job submission.
332
+
333
+ ---
334
+
335
+ ### Day 1 — 09:00, session starts
336
+
337
+ **Without am-memory:**
338
+ ```
339
+ You: The dan-sync Spark job keeps failing on YARN with a timeout.
340
+ Help me debug it.
341
+ Claude: Sure — what does the error look like? What's your submission
342
+ config? What version of the orchestration layer are you on?
343
+ (you spend 15 minutes re-explaining the stack)
344
+ ```
345
+
346
+ **With am-memory:**
347
+ ```
348
+ [SessionStart hook creates session silently]
349
+
350
+ You: The dan-sync Spark job keeps failing on YARN with a timeout.
351
+
352
+ [Claude calls am_search("dan-sync Spark YARN timeout")]
353
+ → hits:
354
+ [P0] "Spark/YARN restart API — polling and timeout config"
355
+ key_facts: ["15s polling interval", "800s timeout default",
356
+ "HTTP API at /restart endpoint"]
357
+ [P1] "cpl-modules pipeline config — dan-sync"
358
+ key_facts: ["YAML → dtechclipy → cplapi → cplorchestration → Airflow",
359
+ "Spark executor config in pipeline YAML"]
360
+
361
+ Claude: The orchestration layer uses an HTTP restart API with 800s timeout
362
+ and 15s polling. Is the job hitting the 800s wall, or failing
363
+ before that? Check /restart endpoint response — it will tell you
364
+ whether YARN accepted the submission or rejected it upstream.
365
+ ```
366
+
367
+ Zero re-explanation. Claude already knows your stack.
368
+
369
+ ---
370
+
371
+ ### Day 1 — 14:00, root cause found
372
+
373
+ After 3 hours of debugging you find the issue: the Spark executor memory config in the pipeline YAML was using a soft limit that YARN silently ignores, causing the container to get killed without a clear error.
374
+
375
+ **Without am-memory:**
376
+ ```
377
+ Claude explains the fix. You apply it. The insight lives only in this session.
378
+ ```
379
+
380
+ **With am-memory:**
381
+ ```
382
+ Claude explains the fix, then calls am_save automatically:
383
+
384
+ title: "YARN silently ignores Spark soft memory limit in pipeline YAML"
385
+ content: "executor_memory soft limit is not enforced by YARN —
386
+ container gets OOM-killed with no error message.
387
+ Fix: use hard limit field executor_memory_hard.
388
+ Affected: all cpl-modules Spark pipelines on YARN."
389
+ priority: P0 ← architectural constraint, never expires
390
+
391
+ → written to documents table permanently
392
+ ```
393
+
394
+ ---
395
+
396
+ ### Day 1 — 17:00, session expires (8-hour limit)
397
+
398
+ **Without am-memory:**
399
+ ```
400
+ [Session context window compresses / expires]
401
+
402
+ Day 2, 09:00:
403
+ You: Continue where we left off on dan-sync.
404
+ Claude: I don't have context from a previous session.
405
+ Could you describe the issue again?
406
+
407
+ (15 minutes re-explaining, then another 30 minutes to re-derive
408
+ what you already found yesterday)
409
+ ```
410
+
411
+ **With am-memory:**
412
+ ```
413
+ [Stop hook fires silently]
414
+ → reads session messages
415
+ → extracts summary + key_facts + decisions
416
+ → quality gate: decisions non-empty → P0
417
+ → promotes to documents:
418
+ title: "dan-sync YARN OOM debugging — Day 1"
419
+ summary: "Spark executor soft memory limit silently ignored by YARN.
420
+ Container OOM-killed. Fix: use executor_memory_hard in YAML."
421
+ decisions: ["executor_memory_hard replaces executor_memory_soft",
422
+ "applies to all cpl-modules YARN pipelines"]
423
+ → raw session + messages deleted
424
+
425
+ Day 2, 09:00 — new session starts:
426
+ You: Continue where we left off on dan-sync.
427
+
428
+ [Claude calls am_search("dan-sync YARN memory")]
429
+ → hits yesterday's promoted P0 doc + the fix from 14:00
430
+
431
+ Claude: Yesterday's finding: YARN silently ignores the soft memory limit.
432
+ The fix is executor_memory_hard in the pipeline YAML.
433
+ Did you get to deploy it, or do you need to pick up from there?
434
+ ```
435
+
436
+ Exact continuation. No re-explanation. No re-derivation.
437
+
438
+ ---
439
+
440
+ ### 3 weeks later — different pipeline, same platform
441
+
442
+ **Without am-memory:**
443
+ ```
444
+ A different Spark job starts failing with the same silent OOM pattern.
445
+ → 45 minutes debugging
446
+ → re-discover the same YAML config issue
447
+ → apply the same fix
448
+ (every engineer re-learns the same lesson)
449
+ ```
450
+
451
+ **With am-memory:**
452
+ ```
453
+ You: This Airflow-triggered Spark job on YARN keeps dying silently.
454
+ No error in the logs.
455
+
456
+ [Claude calls am_search("YARN silent failure Spark")]
457
+ → hits the P0 doc from 3 weeks ago
458
+
459
+ Claude: Seen this before — YARN silently kills the container when
460
+ executor_memory soft limit is exceeded. No error message.
461
+ Check executor_memory_hard in your pipeline YAML.
462
+
463
+ 2 minutes to resolution instead of 45.
464
+ ```
465
+
466
+ ---
467
+
468
+ ### What actually accumulates over time
469
+
470
+ ```
471
+ Week 1: am init · 0 documents
472
+ First session — Claude saves 2 pipeline config decisions
473
+ → 2 documents
474
+
475
+ Week 2: You debug YARN, edit 8 pipeline YAML files
476
+ PostToolUse captures each edit → upsert by file_path
477
+ → 10 documents
478
+
479
+ Week 3: You delete 4 old sessions from dashboard
480
+ Each promoted with quality gate (P0/P1/P2)
481
+ → 14 documents
482
+
483
+ Month 2: Routine work — edits, sessions, decisions
484
+ → ~60 documents · growing
485
+
486
+ Month 6: ~200 documents · all searchable in <50ms
487
+ Claude answers "why did we move away from soft memory limits?"
488
+ with the exact rationale from a session that no longer exists
489
+ as a raw conversation — only the distilled decision survives.
490
+ ```
491
+
492
+
493
+ ## How knowledge grows over time
494
+
495
+ ```
496
+ Day 1: am init · 0 documents
497
+
498
+ Day 1: First session · Claude saves 2 decisions via <memory>
499
+ → 2 documents
500
+
501
+ Day 3: You edit 5 .py files · PostToolUse captures each
502
+ → 7 documents
503
+
504
+ Day 7: You delete a 30-message session from the dashboard
505
+ → session distilled → 1 session_extract P1 doc promoted
506
+ → 8 documents
507
+
508
+ Day 30: Routine file edits, session work
509
+ → ~50 documents · ~40% vectorized (if Ollama installed)
510
+
511
+ Day 90: 301 documents · 98% vectorized · <50ms search
512
+ Claude answers "why did we choose X?" with full rationale
513
+ from sessions that no longer exist as raw conversations.
514
+ ```
515
+
516
+ ---
517
+
518
+ ## How `am init` configures Claude Code
519
+
520
+ After `am init`, three things change in your `~/.claude/` directory:
521
+
522
+ **`mcp.json`** — registers the MCP server:
523
+ ```json
524
+ {
525
+ "mcpServers": {
526
+ "am-memory": { "command": "am", "args": ["mcp"] }
527
+ }
528
+ }
529
+ ```
530
+
531
+ **`hooks/SessionStart/am-memory.sh`** — creates a session on startup:
532
+ ```bash
533
+ SESSION_ID=$(am session start --project "$PROJECT" --source "cli:$PROJECT")
534
+ am state set --key current_session_id --value "\"$SESSION_ID\""
535
+ ```
536
+
537
+ **`hooks/Stop/am-memory.sh`** — ends and promotes session on exit:
538
+ ```bash
539
+ SESSION_ID=$(am state get --key current_session_id | tr -d '"')
540
+ am session end --session-id "$SESSION_ID"
541
+ am state set --key current_session_id --value "null"
542
+ ```
543
+
544
+ **`CLAUDE.md`** — tells Claude when to use the MCP tools:
545
+ ```markdown
546
+ ## Persistent Memory (am-memory)
547
+ You have am_search and am_save MCP tools.
548
+ • Before answering questions about past work → call am_search
549
+ • After learning non-obvious facts → call am_save with P0/P1/P2
550
+ ```
551
+
552
+ ---
553
+
554
+ ## Updates
555
+
556
+ ```bash
557
+ # Check current version
558
+ am --version
559
+
560
+ # Upgrade (pipx recommended)
561
+ pipx upgrade am-memory
562
+
563
+ # Or via am
564
+ am update
565
+ ```
566
+
567
+ am-memory checks PyPI once every 24 hours and prints a notice when a newer version is available.
568
+
569
+ ---
570
+
571
+ ## License
572
+
573
+ MIT