@smyslenny/agent-memory 4.1.0-alpha.1 → 4.2.0

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,43 @@
1
1
  # Changelog
2
2
 
3
+ ## 4.2.0 (2026-03-19)
4
+
5
+ ### 🛡️ Anti-Noise Hardening
6
+
7
+ This release addresses the "heartbeat flood" problem where memory-sync cron
8
+ ingested hundreds of low-value status observations (e.g. "HEARTBEAT_OK",
9
+ "安静模式", "PR 无变化") into the curated memory store.
10
+
11
+ #### Guard improvements
12
+ - **Raised specificity threshold** for P2/P3 memories from 8 to 20 effective
13
+ characters
14
+ - **CJK-aware length calculation**: CJK characters count as 3 effective chars
15
+ (reflecting their higher information density vs ASCII), preventing false
16
+ rejections of legitimate Chinese content
17
+
18
+ #### Ingest noise filter
19
+ - Added `isIngestNoise()` pre-filter in `extractIngestItems()` that skips lines
20
+ matching known noise patterns before they reach the Write Guard:
21
+ - Heartbeat status: `HEARTBEAT_OK`, `安静模式`, `不打扰`, `继续安静待命`
22
+ - Empty deltas: `无新 delta`, `无变化`, `无紧急`, `无新进展`
23
+ - System dumps: `openclaw status`, `openclaw gateway status`, `session_status`
24
+ - Stale PR observations: `PR #NNN 无变化`, `基线未变`, `轻量复查`
25
+ - Cron noise: `cron 会话`, `距上次心跳`, `危险区协议`
26
+
27
+ #### Tidy expansion
28
+ - `getDecayedMemories()` now includes **P2 (knowledge)** in cleanup candidates
29
+ (previously only P3 event). This means decayed low-vitality knowledge entries
30
+ will be cleaned up during sleep tidy, not just events.
31
+
32
+ #### Govern env config
33
+ - `maxMemories` can now be set via `AGENT_MEMORY_MAX_MEMORIES` environment
34
+ variable (default: 200)
35
+
36
+ ### ✅ Tests
37
+ - 66 tests passing (19 files)
38
+ - Added `tests/ingest/noise-filter.test.ts` covering heartbeat noise rejection,
39
+ meaningful content preservation, and mixed signal/noise handling
40
+
3
41
  ## 4.0.0-alpha.1 (2026-03-09)
4
42
 
5
43
  ### 🚀 Repositioning
@@ -2187,9 +2187,11 @@ function fourCriterionGate(input) {
2187
2187
  const content = input.content.trim();
2188
2188
  const failed = [];
2189
2189
  const priority = input.priority ?? (input.type === "identity" ? 0 : input.type === "emotion" ? 1 : input.type === "knowledge" ? 2 : 3);
2190
- const minLength = priority <= 1 ? 4 : 8;
2191
- const specificity = content.length >= minLength ? Math.min(1, content.length / 50) : 0;
2192
- if (specificity === 0) failed.push(`specificity (too short: ${content.length} < ${minLength} chars)`);
2190
+ const cjkCount = (content.match(/[\u4e00-\u9fff\u3400-\u4dbf]/g) ?? []).length;
2191
+ const effectiveLength = content.length + cjkCount * 2;
2192
+ const minLength = priority <= 1 ? 4 : 20;
2193
+ const specificity = effectiveLength >= minLength ? Math.min(1, effectiveLength / 50) : 0;
2194
+ if (specificity === 0) failed.push(`specificity (too short: effective ${effectiveLength} < ${minLength} chars)`);
2193
2195
  const tokens = tokenize(content);
2194
2196
  const novelty = tokens.length >= 1 ? Math.min(1, tokens.length / 5) : 0;
2195
2197
  if (novelty === 0) failed.push("novelty (no meaningful tokens after filtering)");
@@ -2420,9 +2422,9 @@ function getDecayedMemories(db, threshold = 0.05, opts) {
2420
2422
  const agentId = opts?.agent_id;
2421
2423
  return db.prepare(
2422
2424
  agentId ? `SELECT id, content, vitality, priority FROM memories
2423
- WHERE vitality < ? AND priority >= 3 AND agent_id = ?
2425
+ WHERE vitality < ? AND priority >= 2 AND agent_id = ?
2424
2426
  ORDER BY vitality ASC` : `SELECT id, content, vitality, priority FROM memories
2425
- WHERE vitality < ? AND priority >= 3
2427
+ WHERE vitality < ? AND priority >= 2
2426
2428
  ORDER BY vitality ASC`
2427
2429
  ).all(...agentId ? [threshold, agentId] : [threshold]);
2428
2430
  }
@@ -2520,7 +2522,8 @@ function rankEvictionCandidates(db, opts) {
2520
2522
  }
2521
2523
  function runGovern(db, opts) {
2522
2524
  const agentId = opts?.agent_id;
2523
- const maxMemories = opts?.maxMemories ?? 200;
2525
+ const envMax = Number.parseInt(process.env.AGENT_MEMORY_MAX_MEMORIES ?? "", 10);
2526
+ const maxMemories = opts?.maxMemories ?? (Number.isFinite(envMax) && envMax > 0 ? envMax : 200);
2524
2527
  let orphanPaths = 0;
2525
2528
  let emptyMemories = 0;
2526
2529
  let evicted = 0;