@martian-engineering/lossless-claw 0.11.3 → 0.13.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.
@@ -30,6 +30,44 @@ Good default:
30
30
 
31
31
  - `0.75`
32
32
 
33
+ ### `contextThresholdOverrides`
34
+
35
+ Optional ordered rules that choose a different compaction threshold for matching runtime contexts.
36
+
37
+ Supported match fields:
38
+
39
+ - `model`: exact runtime model id, such as `openai/gpt-5.5`
40
+ - `modelContextWindowMin`: match models/windows at or above this token count
41
+ - `modelContextWindowMax`: match models/windows at or below this token count
42
+ - `sessionPattern`: session-key glob, using the same `*` and `**` semantics as ignored/stateless sessions
43
+
44
+ Rules are AND-matched: if a rule includes both `model` and `sessionPattern`, both must match. If multiple rules match, Lossless picks the highest-specificity rule, then the earliest rule in the array for ties. If no rule matches, it falls back to global `contextThreshold`.
45
+
46
+ Example:
47
+
48
+ ```json
49
+ {
50
+ "contextThreshold": 0.75,
51
+ "contextThresholdOverrides": [
52
+ {
53
+ "name": "large-context-models",
54
+ "match": { "modelContextWindowMin": 900000 },
55
+ "contextThreshold": 0.15
56
+ },
57
+ {
58
+ "name": "telegram-sessions",
59
+ "match": { "sessionPattern": "agent:*:telegram:**" },
60
+ "contextThreshold": 0.3
61
+ }
62
+ ]
63
+ }
64
+ ```
65
+
66
+ Debugging:
67
+
68
+ - threshold-selection logs include the selected threshold, source, rule index/name, token budget, threshold tokens, model, context-window value, and match reason
69
+ - there is no env-var override for `contextThresholdOverrides`; use plugin config for structured rules
70
+
33
71
  ### `freshTailCount`
34
72
 
35
73
  Keeps the newest messages raw instead of compacting them.
@@ -286,6 +324,20 @@ Why it matters:
286
324
  - keep this off unless you want transcript GC to mutate the live session file during maintenance
287
325
  - the default is `false`
288
326
 
327
+ ### `enableSummaryThinking`
328
+
329
+ Controls whether the summarization model receives a low reasoning budget.
330
+
331
+ Why it matters:
332
+
333
+ - when `true` (default), summarization calls request `reasoningIfSupported: "low"`, allowing the model to think before producing summaries — this is the current default behavior
334
+ - when `false`, no explicit reasoning budget is requested, which can reduce cost and keep summarization output more concise when reasoning is not needed for faithful summaries
335
+ - set to `false` when you want to minimize token spend on reasoning during compaction, especially with reasoning-capable models
336
+
337
+ Env override:
338
+
339
+ - `LCM_ENABLE_SUMMARY_THINKING`
340
+
289
341
  ### `proactiveThresholdCompactionMode`
290
342
 
291
343
  Controls whether proactive threshold compaction is deferred into maintenance debt or kept inline for legacy behavior.
@@ -296,7 +348,7 @@ Why it matters:
296
348
  - `deferred` also stores provider/model/cache telemetry so Anthropic-family sessions can avoid rewriting a still-hot prompt cache
297
349
  - `inline` preserves the legacy foreground compaction path for hosts that do not yet support deferred execution
298
350
  - `/lossless status` and `/lcm status` surface pending/running/last-failure maintenance state so operators can see when compaction is queued
299
- - background `maintain()` can still do non-prompt-mutating work, but prompt-mutating debt is consumed pre-assembly once cache is cold or the next turn is already approaching overflow
351
+ - after-turn background drain and host-approved `maintain()` consume routine threshold debt; `assemble()` only drains pending threshold debt synchronously as an emergency safeguard when the live prompt estimate is already over budget
300
352
 
301
353
  ### `autoRotateSessionFiles`
302
354
 
@@ -314,6 +366,7 @@ Why it matters:
314
366
 
315
367
  - prevents very large OpenClaw session JSONL files from choking fallback/gateway startup while LCM owns the durable context
316
368
  - runtime rotation only creates or replaces the rolling `rotate-latest` DB backup when `createBackups` is `true`; manual `/lossless rotate` / `/lcm rotate` always keeps its backup-backed behavior
369
+ - runtime JSONL rewrites run from `afterTurn()` after the host turn completes; `maintain()` skips rotation and leaves it to `afterTurn()` or startup because background maintenance can overlap an embedded model call
317
370
  - startup scans OpenClaw's current indexed session stores for configured agents, intersects those candidates with active LCM bootstrap state, and creates one pre-rotation DB backup for the startup batch only when `createBackups` is `true`
318
371
  - only runs for active, writable LCM conversations; ignored sessions, stateless sessions, sessions outside the indexed startup candidate set, and sessions without active LCM state are skipped
319
372
  - the preserved transcript tail follows the normal rotate behavior controlled by `freshTailCount`
@@ -325,6 +378,28 @@ Operational logging:
325
378
  - rotate logs include `phase`, `action`, `sessionId`, `sessionKey`, `sessionFile`, `sizeBytes`, `thresholdBytes`, `durationMs`, `backupPath`, `bytesRemoved`, `preservedTailMessageCount`, and `checkpointSize`
326
379
  - real warning logs include the same available context plus `reason` or `error`; quiet startup skips such as missing files, missing bootstrap mappings, and below-threshold files are counted in the summary instead of logged per candidate
327
380
 
381
+ ### `independentLogFile`
382
+
383
+ Writes lossless-claw JSONL logs to an independent plugin-owned file in addition to OpenClaw's runtime logger.
384
+
385
+ Defaults:
386
+
387
+ - `enabled: true`
388
+ - `file: /tmp/openclaw/lossless-claw-YYYY-MM-DD.log`
389
+ - `maxFileBytes: 104857600`
390
+
391
+ Why it matters:
392
+
393
+ - keeps high-volume `[lcm]` operational traces separate from the shared OpenClaw gateway log
394
+ - still sends startup banners and warning/error lines through OpenClaw's runtime logger, so gateway-level startup and failure diagnostics remain visible
395
+ - a dated `lossless-claw-YYYY-MM-DD.log` path rolls over daily, stale dated files are pruned after 3 days, and oversized files rotate through `.1.log` to `.5.log`
396
+
397
+ Env overrides:
398
+
399
+ - `LCM_LOG_FILE_ENABLED`
400
+ - `LCM_LOG_FILE`
401
+ - `LCM_LOG_MAX_FILE_BYTES`
402
+
328
403
  ## Compaction timing and shape
329
404
 
330
405
  ### `contextThreshold`
@@ -468,6 +543,7 @@ Why it matters:
468
543
 
469
544
  - keeps low-value automation or noisy sessions out of the DB
470
545
  - useful for excluding certain agent lanes or ephemeral traffic entirely
546
+ - cron scheduler keys are already isolated per runtime run, so ignore them only when they should bypass LCM compaction
471
547
 
472
548
  ### `statelessSessionPatterns`
473
549
 
@@ -515,6 +591,29 @@ Why it matters:
515
591
  - useful when the runtime model window is smaller than the surrounding system assumes
516
592
  - can prevent oversized assembly on smaller-context models
517
593
 
594
+ ## Anti-replay flood guard
595
+
596
+ The ingest path runs `assertNoReplayTimestampFlood` to refuse batches that look like webhook-style replay attacks (many replay-like user messages or many identical internal messages at the same `created_at`). Because SQLite `datetime('now')` is second-granularity, legitimate idempotent bursts from sub-agents can also trip the guard if it is single-threshold. The role-aware thresholds below split the budget by message origin.
597
+
598
+ ### `replayFloodThresholdExternal`
599
+
600
+ Max replay-like messages allowed in a single SQLite-second for `role=user` before the guard refuses the batch. Defaults to `3`.
601
+
602
+ Why it matters:
603
+
604
+ - preserves replay defense for third-partyly-rebroadcastable input
605
+ - lower values are stricter but risk rejecting legitimate dedup retries from upstream channels
606
+
607
+ ### `replayFloodThresholdInternal`
608
+
609
+ Max identical messages allowed in a single SQLite-second for `role=tool/assistant/system` before the guard refuses the batch. Defaults to `32`.
610
+
611
+ Why it matters:
612
+
613
+ - absorbs legitimate same-second idempotent tool returns (for example, sub-agents emitting many `{"status":"ok"}` results)
614
+ - still bounded so a pathological loop cannot ingest unboundedly under the same timestamp
615
+ - raise it if you operate cron sub-agents that emit very tight bursts; lower it if you want stricter sanity protection
616
+
518
617
  ## Nested objects
519
618
 
520
619
  ### `cacheAwareCompaction`
@@ -622,6 +721,28 @@ Why it matters:
622
721
  - guards against runaway summaries that are much larger than their target budget
623
722
  - useful when summary models are verbose or unstable
624
723
 
724
+ ### `summaryMaxCallsPerWindow`, `summaryCallWindowMs`, and `summarySpendBackoffMs`
725
+
726
+ Bounds model-backed compaction and large-file summarization calls per session.
727
+
728
+ Defaults:
729
+
730
+ - `summaryMaxCallsPerWindow`: `24`
731
+ - `summaryCallWindowMs`: `600000`
732
+ - `summarySpendBackoffMs`: `1800000`
733
+
734
+ Env overrides:
735
+
736
+ - `LCM_SUMMARY_MAX_CALLS_PER_WINDOW`
737
+ - `LCM_SUMMARY_CALL_WINDOW_MS`
738
+ - `LCM_SUMMARY_SPEND_BACKOFF_MS`
739
+
740
+ Why they matter:
741
+
742
+ - prevents non-auth provider failures, ineffective compaction, or repeated deferred debt from spending unbounded summarization calls
743
+ - keeps provider-auth failures on the separate auth circuit breaker path
744
+ - direct deterministic fallbacks remain available when model-backed large-file summaries are throttled
745
+
625
746
  ### `customInstructions`
626
747
 
627
748
  Natural-language instructions injected into summarization prompts.
@@ -631,6 +752,25 @@ Why it matters:
631
752
  - lets operators steer formatting or emphasis without patching code
632
753
  - should be used sparingly; low-quality instructions can degrade summary quality system-wide
633
754
 
755
+ ### `stripInjectedContextTags`
756
+
757
+ | | |
758
+ | --- | --- |
759
+ | Type | `string[]` |
760
+ | Default | `["active_memory_plugin", "relevant-memories", "relevant_memories", "hindsight_memories"]` |
761
+ | Env | `LCM_STRIP_INJECTED_CONTEXT_TAGS` (comma-separated) |
762
+
763
+ XML tag names whose blocks are stripped from message content before compaction summarization.
764
+
765
+ Why it matters:
766
+
767
+ - Memory and context plugins (active-memory, memory-lancedb, hindsight-openclaw) prepend XML-tagged blocks to user messages via the `prependContext` hook. These blocks are ephemeral retrieval context — they helped the model on that specific turn but are not part of the actual conversation.
768
+ - Without stripping, the summarizer treats injected memories as real conversation content, permanently corrupting compacted summaries with auto-retrieved context that the user never said.
769
+ - The default list covers well-known OpenClaw memory plugin tags. Add custom tag names if you use plugins that inject context via other tags.
770
+ - Set to `[]` (or empty env string) to disable stripping.
771
+
772
+ Design note: stripping happens at compaction time, not at message ingestion. The raw message stored in the LCM database still contains the original injected blocks, so `lcm_expand` and `lcm_grep` can still surface the full context the model saw on any given turn. Only the summarizer input is cleaned.
773
+
634
774
  ## Practical operator workflow
635
775
 
636
776
  1. Install and enable the plugin.
@@ -1,9 +1,34 @@
1
1
  # Diagnostics
2
2
 
3
- For the MVP, use the native command surface first.
3
+ For the MVP, use the native command surface first. For debugging lossless-claw behavior or failures, inspect the independent Lossless log before the shared OpenClaw gateway log.
4
4
 
5
5
  ## Fast path
6
6
 
7
+ ### Independent Lossless log
8
+
9
+ Check this first when lossless-claw needs to debug itself, because routine `[lcm]` info and debug lines are written here instead of the shared OpenClaw gateway log.
10
+
11
+ Default path:
12
+
13
+ ```bash
14
+ /tmp/openclaw/lossless-claw-YYYY-MM-DD.log
15
+ ```
16
+
17
+ For today's local log, use:
18
+
19
+ ```bash
20
+ tail -n 200 "/tmp/openclaw/lossless-claw-$(date +%F).log"
21
+ ```
22
+
23
+ Useful patterns:
24
+
25
+ ```bash
26
+ rg -n "\\[lcm\\] (auto-rotate|rotate|runtime\\.llm\\.complete|summary|compact|assembly)" /tmp/openclaw/lossless-claw-*.log
27
+ rg -n "warn|error|failed|truncated|deterministic|fallback" /tmp/openclaw/lossless-claw-*.log
28
+ ```
29
+
30
+ The dated default log rolls over daily. Dated files are pruned after 3 days, and oversized active logs rotate through `.1.log` to `.5.log`. Startup banners and warning/error lines are also sent to OpenClaw's runtime logger, so check `/tmp/openclaw/openclaw-YYYY-MM-DD.log` after the Lossless log when you need gateway-level startup or failure context.
31
+
7
32
  ### `/lossless` (`/lcm` alias)
8
33
 
9
34
  Use this when you need a quick health snapshot.
@@ -8,25 +8,28 @@ For stock `lossless-claw` on current main:
8
8
 
9
9
  - OpenClaw handles `/new` and `/reset` as session-reset operations.
10
10
  - `lossless-claw` handles `/lossless rotate` (`/lcm rotate`) as transcript maintenance on the current conversation.
11
- - `lossless-claw` does **not** currently register its own `before_reset` hook or a custom reset policy.
12
11
  - `lossless-claw` prefers **`sessionKey`** as the stable identity for an LCM conversation.
13
- - When the same `sessionKey` reappears with a new `sessionId`, `lossless-claw` updates the stored `sessionId` on the existing LCM conversation row instead of creating a brand-new LCM conversation.
12
+ - `/reset` archives the active conversation and creates a fresh active row for the same stable `sessionKey`.
13
+ - Cron scheduler keys (`agent:<agent>:cron:<job>...`) are isolated per runtime run when a new `sessionId` reuses the same `sessionKey`.
14
+ - For ordinary non-cron session keys, continuity still follows the stable `sessionKey`.
14
15
 
15
16
  ## What that means in practice
16
17
 
17
- If a user asks whether `/new` or `/reset` gives them a fresh LCM conversation, the answer is usually **no** under the current implementation.
18
+ If a user asks whether `/new` or `/reset` gives them a fresh LCM conversation, distinguish the commands.
18
19
 
19
- They get a fresh OpenClaw session runtime, but LCM continuity still follows the stable `sessionKey` when one is available.
20
+ They get a fresh OpenClaw session runtime, but LCM continuity usually still follows the stable `sessionKey` when one is available.
20
21
 
21
22
  So today:
22
23
 
23
- - `/new` and `/reset` can reset the runtime session
24
- - but LCM history may continue in the same conversation row if the chat/thread keeps the same `sessionKey`
24
+ - `/new` prunes active context but keeps the same LCM conversation row
25
+ - `/reset` archives the active LCM conversation row and creates a fresh active row
26
+ - ordinary chat/thread LCM history may continue in the same row across runtime `sessionId` changes when the stable `sessionKey` continues
27
+ - cron scheduler keys create fresh LCM rows per runtime run so prior runs do not enter the new run's assembled context
25
28
  - `/lossless rotate` keeps that same conversation row, summaries, and context items in place while compacting only the live transcript backing
26
29
 
27
30
  ## Why
28
31
 
29
- Current lossless-claw conversation resolution does this:
32
+ Current lossless-claw conversation resolution generally does this:
30
33
 
31
34
  1. look up by `sessionKey` first
32
35
  2. fall back to `sessionId` only when no `sessionKey` match exists
@@ -34,6 +37,8 @@ Current lossless-claw conversation resolution does this:
34
37
 
35
38
  That behavior preserves continuity across session resets for the same chat identity.
36
39
 
40
+ Cron keys are the exception: when an active cron conversation exists for the same `sessionKey` but a different runtime `sessionId`, lossless-claw archives the prior active row and starts a fresh one for the new run. Prior messages remain persisted on the archived conversation.
41
+
37
42
  ## `/lossless rotate`
38
43
 
39
44
  `/lossless rotate` is distinct from `/new` and `/reset`.
@@ -49,22 +54,23 @@ This makes rotate the lightweight option when the problem is transcript bloat ra
49
54
 
50
55
  ## Important limitation
51
56
 
52
- There is still **no plugin-specific `/new` vs `/reset` split** in stock lossless-claw docs or runtime behavior.
57
+ There is a plugin-specific `/new` vs `/reset` split in current lossless-claw behavior.
53
58
 
54
59
  If someone is asking for semantics like:
55
60
 
56
61
  - `/new` gives them a fresh LCM conversation row
57
- - `/reset` archives old LCM conversation and starts a new one for the same stable `sessionKey`
58
62
 
59
- that is a **design/spec topic**, not current stock behavior.
63
+ that remains a **design/spec topic**, not current stock behavior.
60
64
 
61
65
  ## Safe operator guidance
62
66
 
63
67
  When answering users:
64
68
 
65
- - do not promise that `/new` or `/reset` clears LCM history
69
+ - do not promise that `/new` clears LCM history
70
+ - explain that `/reset` archives the active LCM row and starts a fresh one for the same stable `sessionKey`
66
71
  - explain that `/lossless rotate` compacts the current transcript without splitting the LCM conversation
67
- - explain that current stock behavior follows `sessionKey` continuity
72
+ - explain that ordinary current stock behavior follows `sessionKey` continuity
73
+ - explain that cron scheduler session keys are isolated per runtime run while preserving archived prior runs
68
74
  - if they need a truly separate LCM history, use a different session key context (for example a different chat/thread/binding) or explicit non-MVP migration/surgery tools
69
75
 
70
76
  ## Relation to `/status`