@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.
- package/README.md +32 -5
- package/dist/index.js +248 -59
- package/docs/architecture.md +11 -0
- package/docs/configuration.md +66 -6
- package/openclaw.plugin.json +135 -0
- package/package.json +12 -10
- package/skills/lossless-claw/SKILL.md +5 -4
- package/skills/lossless-claw/references/config.md +141 -1
- package/skills/lossless-claw/references/diagnostics.md +26 -1
- package/skills/lossless-claw/references/session-lifecycle.md +18 -12
|
@@ -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()`
|
|
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
|
-
-
|
|
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,
|
|
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`
|
|
24
|
-
-
|
|
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
|
|
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
|
|
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`
|
|
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`
|