@lh8ppl/claude-memory-kit 0.3.4 → 0.3.5
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/package.json +1 -1
- package/src/compress-retry.mjs +25 -0
- package/src/compress-session.mjs +14 -2
- package/src/daily-distill.mjs +7 -3
- package/src/lazy-compress.mjs +6 -0
- package/src/weekly-curate.mjs +7 -4
package/package.json
CHANGED
package/src/compress-retry.mjs
CHANGED
|
@@ -37,6 +37,31 @@
|
|
|
37
37
|
// a time, gated by the cooldown), so there is no herd to avoid — a plain exponential
|
|
38
38
|
// backoff is sufficient and keeps the timing deterministic for tests.
|
|
39
39
|
|
|
40
|
+
// The timeout for compress callers that have NO outer hook ceiling — the cron /
|
|
41
|
+
// detached-lazy children (daily-distill, weekly-curate, the lazy compressSession).
|
|
42
|
+
// The D-92/F-2 composition rule: a ceiling-free caller must NOT inherit the
|
|
43
|
+
// hook-sized 50s bound (which is sized under the 60s SessionEnd ceiling, §8.5).
|
|
44
|
+
// 120s is chosen against MEASURED `claude --print` latency: it runs ~18-27s when
|
|
45
|
+
// fast but was observed at 78s (Task 163 live, on a 4.7KB input) and 89s (the v0.3.4
|
|
46
|
+
// cut-gate, 10KB) in slow-Haiku windows — environmental, not size-driven (D-174).
|
|
47
|
+
// 120s clears those with headroom; the 50s budget killed them needlessly, leaving
|
|
48
|
+
// `recent.md` stale (D-179). One constant so the family can't drift back to 50s.
|
|
49
|
+
// (auto-extract uses its own 90s under the Stop hook — a separate detached path.)
|
|
50
|
+
export const CEILING_FREE_TIMEOUT_MS = 120_000;
|
|
51
|
+
|
|
52
|
+
// The backoff BETWEEN retries on the ceiling-free paths. The default baseBackoffMs
|
|
53
|
+
// (600ms) is far too short for the kit's failure mode: `claude --print` slowness is
|
|
54
|
+
// a transient WINDOW (slow for a stretch, then fine — D-174), and the whole point of
|
|
55
|
+
// backoff is to let that window PASS before retrying. A 600ms wait retries while
|
|
56
|
+
// still INSIDE the same slow window, so attempt 2 hits the same slowness and also
|
|
57
|
+
// times out. The field waits SECONDS for exactly this reason (graphiti 5-120s,
|
|
58
|
+
// Letta cap 10s, mempalace 2-8s — all checked across 19 systems; NONE use sub-second
|
|
59
|
+
// backoff, and NONE escalate the timeout itself). 5s is the field's low end — one
|
|
60
|
+
// 5s wait between the 2 ceiling-free attempts gives the slow window room to clear.
|
|
61
|
+
// Safe on every path: the HOOK path is maxAttempts:1 (no retry → backoff never
|
|
62
|
+
// fires); the ceiling-free paths run detached/cron, so a multi-second wait is free.
|
|
63
|
+
export const CEILING_FREE_BACKOFF_MS = 5_000;
|
|
64
|
+
|
|
40
65
|
/**
|
|
41
66
|
* Classify a compress() rejection as transient (worth a retry) or deterministic
|
|
42
67
|
* (a re-call re-fails identically — don't waste the attempt or the budget).
|
package/src/compress-session.mjs
CHANGED
|
@@ -232,6 +232,16 @@ export async function compressSession({
|
|
|
232
232
|
// caller (runLazyCompress) passes maxAttempts:2 to opt into one retry; the hook
|
|
233
233
|
// keeps its restore-on-failure (D-79) and delegates the retry to that lazy path.
|
|
234
234
|
maxAttempts = 1,
|
|
235
|
+
// DEFAULT 50s = the SessionEnd-hook budget (sized under the 60s ceiling, §8.5).
|
|
236
|
+
// The ceiling-free LAZY caller (runLazyCompress, a detached SessionStart child
|
|
237
|
+
// with NO outer ceiling) passes 120s so a slow-but-not-broken `claude --print`
|
|
238
|
+
// window doesn't time out needlessly — the D-92/F-2 composition rule: a
|
|
239
|
+
// ceiling-free caller must not inherit a ceiling-sized timeout.
|
|
240
|
+
timeoutMs = 50_000,
|
|
241
|
+
// Backoff between retries (only the lazy maxAttempts:2 path retries). DEFAULT
|
|
242
|
+
// undefined → compressWithRetry's 600ms; the ceiling-free LAZY caller passes the
|
|
243
|
+
// 5s ceiling-free backoff so a retry lands AFTER the slow-Haiku window (D-179).
|
|
244
|
+
baseBackoffMs,
|
|
235
245
|
} = {}) {
|
|
236
246
|
const ts = now ?? nowIso();
|
|
237
247
|
const date = dateFromIso(ts);
|
|
@@ -343,9 +353,11 @@ export async function compressSession({
|
|
|
343
353
|
instructions,
|
|
344
354
|
preserveCitationIds: true,
|
|
345
355
|
maxOutputBytes,
|
|
346
|
-
timeoutMs
|
|
356
|
+
timeoutMs,
|
|
347
357
|
},
|
|
348
|
-
|
|
358
|
+
// baseBackoffMs only forwarded when the caller set it (the lazy ceiling-free
|
|
359
|
+
// path passes the 5s backoff); undefined → compressWithRetry's 600ms default.
|
|
360
|
+
{ maxAttempts, ...(baseBackoffMs != null ? { baseBackoffMs } : {}), onRetry: () => { retries += 1; } },
|
|
349
361
|
);
|
|
350
362
|
} catch (err) {
|
|
351
363
|
// Distinguish HAIKU_TIMEOUT (slow Anthropic) from COMPRESS_FAILED
|
package/src/daily-distill.mjs
CHANGED
|
@@ -28,7 +28,7 @@ import { join } from 'node:path';
|
|
|
28
28
|
import { nowIso } from './audit-log.mjs';
|
|
29
29
|
import { ERROR_CATEGORIES } from './result-shapes.mjs';
|
|
30
30
|
import { HaikuTimeoutError } from './compressor.mjs';
|
|
31
|
-
import { compressWithRetry } from './compress-retry.mjs';
|
|
31
|
+
import { compressWithRetry, CEILING_FREE_TIMEOUT_MS, CEILING_FREE_BACKOFF_MS } from './compress-retry.mjs';
|
|
32
32
|
import {
|
|
33
33
|
DEFAULT_COOLDOWN_MS,
|
|
34
34
|
isCooldownActive,
|
|
@@ -209,9 +209,13 @@ export async function dailyDistill({
|
|
|
209
209
|
instructions,
|
|
210
210
|
preserveCitationIds: true,
|
|
211
211
|
maxOutputBytes,
|
|
212
|
-
|
|
212
|
+
// Ceiling-free (cron / detached lazy child, NO 60s hook ceiling) → the
|
|
213
|
+
// generous ceiling-free timeout, NOT the hook-sized 50s (D-92/F-2 + D-179).
|
|
214
|
+
timeoutMs: CEILING_FREE_TIMEOUT_MS,
|
|
213
215
|
},
|
|
214
|
-
|
|
216
|
+
// 5s backoff between the 2 attempts (NOT the 600ms default) so a retry lands
|
|
217
|
+
// AFTER the transient slow-Haiku window, not inside it (D-179).
|
|
218
|
+
{ maxAttempts: 2, baseBackoffMs: CEILING_FREE_BACKOFF_MS, onRetry: () => { retries += 1; } },
|
|
215
219
|
);
|
|
216
220
|
touchCooldownMarker({ projectRoot, now: ts });
|
|
217
221
|
} catch (err) {
|
package/src/lazy-compress.mjs
CHANGED
|
@@ -44,6 +44,7 @@ import {
|
|
|
44
44
|
import { dailyDistill } from './daily-distill.mjs';
|
|
45
45
|
import { weeklyCurate } from './weekly-curate.mjs';
|
|
46
46
|
import { compressSession } from './compress-session.mjs';
|
|
47
|
+
import { CEILING_FREE_TIMEOUT_MS, CEILING_FREE_BACKOFF_MS } from './compress-retry.mjs';
|
|
47
48
|
import { syncDecisionsJournal } from './decisions-journal.mjs';
|
|
48
49
|
|
|
49
50
|
const DEFAULT_DAILY_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
@@ -416,6 +417,11 @@ export async function runLazyCompress({
|
|
|
416
417
|
// is where the SessionEnd-hook's failed roll (which restored now.md, D-79) gets
|
|
417
418
|
// its real bounded retry.
|
|
418
419
|
maxAttempts: 2,
|
|
420
|
+
// Ceiling-free (detached child, no 60s ceiling) → the generous timeout + the 5s
|
|
421
|
+
// backoff so a retry lands AFTER the slow-Haiku window (D-92/F-2 + D-179; matches
|
|
422
|
+
// daily-distill / weekly-curate). compressSession forwards both to compressWithRetry.
|
|
423
|
+
timeoutMs: CEILING_FREE_TIMEOUT_MS,
|
|
424
|
+
baseBackoffMs: CEILING_FREE_BACKOFF_MS,
|
|
419
425
|
});
|
|
420
426
|
} else if (verdict.action === 'stale-weekly') {
|
|
421
427
|
delegatedTo = 'weekly-curate';
|
package/src/weekly-curate.mjs
CHANGED
|
@@ -39,7 +39,7 @@ import { canonicalize } from '@lh8ppl/cmk-canonicalize';
|
|
|
39
39
|
import { nowIso } from './audit-log.mjs';
|
|
40
40
|
import { ERROR_CATEGORIES, errorResult } from './result-shapes.mjs';
|
|
41
41
|
import { HaikuTimeoutError } from './compressor.mjs';
|
|
42
|
-
import { compressWithRetry } from './compress-retry.mjs';
|
|
42
|
+
import { compressWithRetry, CEILING_FREE_TIMEOUT_MS, CEILING_FREE_BACKOFF_MS } from './compress-retry.mjs';
|
|
43
43
|
import {
|
|
44
44
|
DEFAULT_COOLDOWN_MS,
|
|
45
45
|
isCooldownActive,
|
|
@@ -344,7 +344,8 @@ export async function weeklyCurate({
|
|
|
344
344
|
// corpus (heavier than a session summary) and runs as a cron/lazy child
|
|
345
345
|
// with no 60s hook ceiling — give the classifier headroom so a large
|
|
346
346
|
// corpus doesn't time out. (Corpus is byte-capped at PERSONA_CORPUS_BYTES.)
|
|
347
|
-
|
|
347
|
+
// The shared ceiling-free timeout (D-92/F-2; was the original 120s literal).
|
|
348
|
+
timeoutMs: CEILING_FREE_TIMEOUT_MS,
|
|
348
349
|
});
|
|
349
350
|
}
|
|
350
351
|
|
|
@@ -400,9 +401,11 @@ export async function weeklyCurate({
|
|
|
400
401
|
instructions,
|
|
401
402
|
preserveCitationIds: true,
|
|
402
403
|
maxOutputBytes: archiveMaxBytes,
|
|
403
|
-
|
|
404
|
+
// Ceiling-free → the generous timeout, NOT the hook-sized 50s (D-92/F-2 + D-179).
|
|
405
|
+
timeoutMs: CEILING_FREE_TIMEOUT_MS,
|
|
404
406
|
},
|
|
405
|
-
|
|
407
|
+
// 5s backoff so a retry lands after the slow-Haiku window, not inside it (D-179).
|
|
408
|
+
{ maxAttempts: 2, baseBackoffMs: CEILING_FREE_BACKOFF_MS, onRetry: () => { retries += 1; } },
|
|
406
409
|
);
|
|
407
410
|
touchCooldownMarker({ projectRoot, now: ts });
|
|
408
411
|
} catch (err) {
|