@martian-engineering/lossless-claw 0.8.1 → 0.9.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.
@@ -190,7 +190,7 @@ Files embedded in user messages (typically via `<file>` blocks from tool output)
190
190
  1. Parse file blocks from message content.
191
191
  2. For each block exceeding `largeFileTokenThreshold` (default 25k tokens):
192
192
  - Generate a unique file ID (`file_` prefix)
193
- - Store the content to `~/.openclaw/lcm-files/<conversation_id>/<file_id>.<ext>`
193
+ - Store the content to `largeFilesDir/<conversation_id>/<file_id>.<ext>` (default `~/.openclaw/lcm-files/...`)
194
194
  - Generate a ~200 token exploration summary (structural analysis, key sections, etc.)
195
195
  - Insert a `large_files` record with metadata
196
196
  - Replace the file block in the message with a compact reference
@@ -51,6 +51,7 @@ Most installations only need to override a handful of keys. If you want a comple
51
51
  "circuitBreakerThreshold": 5,
52
52
  "circuitBreakerCooldownMs": 1800000,
53
53
  "fallbackProviders": [],
54
+ "proactiveThresholdCompactionMode": "deferred",
54
55
  "cacheAwareCompaction": {
55
56
  "enabled": true,
56
57
  "maxColdCacheCatchupPasses": 2,
@@ -103,7 +104,7 @@ openclaw plugins install --link /path/to/lossless-claw
103
104
  | `enabled` | `boolean` | `true` | `LCM_ENABLED` | Enables or disables lossless-claw without uninstalling it. |
104
105
  | `databasePath` | `string` | `${OPENCLAW_STATE_DIR}/lcm.db` | `LCM_DATABASE_PATH` | Preferred path for the SQLite database. |
105
106
  | `dbPath` | `string` | alias of `databasePath` | `LCM_DATABASE_PATH` | Legacy alias for `databasePath`. Prefer `databasePath` in new config. |
106
- | `largeFilesDir` | `string` | `${OPENCLAW_STATE_DIR}/lcm-files` | `LCM_LARGE_FILES_DIR` | Directory where large-file text payloads are persisted. Automatically follows the active state directory. |
107
+ | `largeFilesDir` | `string` | `${OPENCLAW_STATE_DIR}/lcm-files` | `LCM_LARGE_FILES_DIR` | Directory where externalized large files and inline images are persisted. Automatically follows the active state directory. |
107
108
  | `ignoreSessionPatterns` | `string[]` | `[]` | `LCM_IGNORE_SESSION_PATTERNS` | Session-key glob patterns that skip LCM entirely. |
108
109
  | `statelessSessionPatterns` | `string[]` | `[]` | `LCM_STATELESS_SESSION_PATTERNS` | Session-key glob patterns that may read from LCM but never write to it. |
109
110
  | `skipStatelessSessions` | `boolean` | `true` | `LCM_SKIP_STATELESS_SESSIONS` | Enforces `statelessSessionPatterns` when enabled. |
@@ -111,6 +112,7 @@ openclaw plugins install --link /path/to/lossless-claw
111
112
  | `timezone` | `string` | `TZ` or system timezone | `TZ` | IANA timezone used for timestamp rendering in summaries. |
112
113
  | `pruneHeartbeatOk` | `boolean` | `false` | `LCM_PRUNE_HEARTBEAT_OK` | Retroactively removes `HEARTBEAT_OK` turn cycles from persisted storage. |
113
114
  | `transcriptGcEnabled` | `boolean` | `false` | `LCM_TRANSCRIPT_GC_ENABLED` | Enables transcript rewrite GC during `maintain()`; disabled by default so transcript rewrites stay opt-in. |
115
+ | `proactiveThresholdCompactionMode` | `"deferred" \| "inline"` | `"deferred"` | `LCM_PROACTIVE_THRESHOLD_COMPACTION_MODE` | Controls whether proactive threshold compaction is deferred into maintenance debt by default or run inline for legacy behavior. |
114
116
 
115
117
  > **Multi-profile note:** `OPENCLAW_STATE_DIR` (set by the host OpenClaw gateway) controls where state is stored. When two gateways run on the same host (e.g. separate bot personas), each gateway sets its own `OPENCLAW_STATE_DIR` and lossless-claw automatically uses that directory for the database, large-file payloads, auth-profile lookups, and legacy secrets — no per-profile plugin config is needed.
116
118
 
@@ -164,9 +166,11 @@ openclaw plugins install --link /path/to/lossless-claw
164
166
  | Key | Type | Default | Env override | Purpose |
165
167
  | --- | --- | --- | --- | --- |
166
168
  | `cacheAwareCompaction.enabled` | `boolean` | `true` | `LCM_CACHE_AWARE_COMPACTION_ENABLED` | Defers incremental leaf compaction more aggressively when prompt-cache telemetry indicates a hot cache. |
169
+ | `cacheAwareCompaction.cacheTTLSeconds` | `integer` | `300` | `LCM_CACHE_TTL_SECONDS` | Fallback cache TTL used when deferred Anthropic compaction has provider/model telemetry but no explicit runtime cache-retention window. |
167
170
  | `cacheAwareCompaction.maxColdCacheCatchupPasses` | `integer` | `2` | `LCM_MAX_COLD_CACHE_CATCHUP_PASSES` | Maximum bounded catch-up passes allowed in one maintenance cycle when cache telemetry is cold. |
168
171
  | `cacheAwareCompaction.hotCachePressureFactor` | `number` | `4` | `LCM_HOT_CACHE_PRESSURE_FACTOR` | Multiplier applied to the hot-cache leaf trigger before raw-history pressure overrides cache preservation. |
169
172
  | `cacheAwareCompaction.hotCacheBudgetHeadroomRatio` | `number` | `0.2` | `LCM_HOT_CACHE_BUDGET_HEADROOM_RATIO` | Minimum fraction of the real token budget that must remain free before hot-cache incremental compaction is skipped entirely. |
173
+ | `cacheAwareCompaction.coldCacheObservationThreshold` | `integer` | `3` | `LCM_COLD_CACHE_OBSERVATION_THRESHOLD` | Consecutive cold observations required before non-explicit cache misses are treated as truly cold. This dampens one-off routing noise and provider failover blips. |
170
174
 
171
175
  #### `dynamicLeafChunkTokens`
172
176
 
@@ -240,6 +244,28 @@ Lossless-claw treats OpenClaw reset commands differently:
240
244
 
241
245
  This keeps long-term history available while still giving users a real clean-slate reset.
242
246
 
247
+ ### Deferred proactive compaction
248
+
249
+ Lossless-claw now defaults `proactiveThresholdCompactionMode` to `deferred`.
250
+
251
+ - deferred mode records a single coalesced maintenance debt row per conversation
252
+ - deferred mode persists provider/model/cache telemetry so Anthropic-family sessions can avoid rewriting a still-hot prompt cache
253
+ - `maintain()` can still process non-prompt-mutating work when the host explicitly opts in to deferred execution, but it leaves prompt-mutating debt pending while Anthropic cache is still hot
254
+ - `assemble()` consumes deferred prompt-mutating debt pre-assembly once the cache is cold or the next turn is already approaching overflow
255
+ - `/lcm status` / `/lossless status` shows the current maintenance state, including pending/running/last-failure details
256
+ - status output also surfaces the latest API/cache telemetry so operators can see whether a deferred debt item is being preserved for cache-safety reasons
257
+ - set `proactiveThresholdCompactionMode` to `inline` only if you need the legacy inline proactive compaction behavior for compatibility
258
+
259
+ ### `/lcm rotate`
260
+
261
+ `/lcm rotate` exists for a different use case than `/new` or `/reset`:
262
+
263
+ - `/new` keeps the same active LCM conversation row and only prunes context.
264
+ - `/reset` changes OpenClaw session flow, which is sometimes more disruptive than users want.
265
+ - `/lcm rotate` keeps the live OpenClaw session identity and the same active LCM conversation row, but rewrites the backing transcript into a compact preserved-tail form.
266
+
267
+ Before rotating, Lossless-claw replaces one rolling `rotate-latest` SQLite backup. It then rewrites the current session transcript and checkpoints the same conversation at the new transcript frontier so bootstrap does not replay the dropped transcript history. Existing summaries, context items, and conversation identity stay in place. If you want additional timestamped snapshots, run `/lcm backup` explicitly before `/lcm rotate`.
268
+
243
269
  ## Environment-only knobs outside plugin config
244
270
 
245
271
  These settings are not part of `plugins.entries.lossless-claw.config`, but they still affect the system:
@@ -271,6 +297,12 @@ cp "${OPENCLAW_STATE_DIR:-$HOME/.openclaw}/lcm.db" "${OPENCLAW_STATE_DIR:-$HOME/
271
297
  sqlite3 "${OPENCLAW_STATE_DIR:-$HOME/.openclaw}/lcm.db" ".backup ${OPENCLAW_STATE_DIR:-$HOME/.openclaw}/lcm.db.backup"
272
298
  ```
273
299
 
300
+ Or from a supported OpenClaw chat/native command surface:
301
+
302
+ ```text
303
+ /lcm backup
304
+ ```
305
+
274
306
  ## Disabling lossless-claw
275
307
 
276
308
  To disable the plugin but keep it installed:
@@ -73,6 +73,10 @@
73
73
  "label": "Large Files Directory",
74
74
  "help": "Directory for persisting large-file text payloads (default: <stateDir>/lcm-files). Uses OPENCLAW_STATE_DIR when set."
75
75
  },
76
+ "largeFilesDir": {
77
+ "label": "Large Files Directory",
78
+ "help": "Directory for externalized large files and inline-image payloads (default: ~/.openclaw/lcm-files)"
79
+ },
76
80
  "ignoreSessionPatterns": {
77
81
  "label": "Ignored Sessions",
78
82
  "help": "Glob patterns for session keys to exclude from LCM storage"
@@ -149,6 +153,10 @@
149
153
  "label": "Cache-Aware Compaction",
150
154
  "help": "When enabled, hot prompt cache defers incremental compaction while cold cache allows bounded catch-up passes"
151
155
  },
156
+ "cacheAwareCompaction.cacheTTLSeconds": {
157
+ "label": "Short Cache TTL (seconds)",
158
+ "help": "Treat short-lived prompt-cache entries as hot for this many seconds before deferred compaction may rewrite the prompt"
159
+ },
152
160
  "cacheAwareCompaction.maxColdCacheCatchupPasses": {
153
161
  "label": "Cold Cache Catch-Up Passes",
154
162
  "help": "Maximum incremental leaf passes allowed in one maintenance cycle when prompt cache is cold"
@@ -161,6 +169,10 @@
161
169
  "label": "Hot Cache Budget Headroom",
162
170
  "help": "Fraction of the real token budget that must remain free before hot-cache incremental compaction is skipped entirely"
163
171
  },
172
+ "cacheAwareCompaction.coldCacheObservationThreshold": {
173
+ "label": "Cold Cache Observation Threshold",
174
+ "help": "Consecutive cold observations required before non-explicit cache misses are treated as truly cold"
175
+ },
164
176
  "dynamicLeafChunkTokens.enabled": {
165
177
  "label": "Dynamic Leaf Chunk Tokens",
166
178
  "help": "When enabled, incremental compaction uses a larger working leaf chunk in busy sessions and keeps the static floor in quieter sessions"
@@ -181,6 +193,10 @@
181
193
  "label": "Transcript GC",
182
194
  "help": "Enable transcript rewrite GC during maintain(); disabled by default"
183
195
  },
196
+ "proactiveThresholdCompactionMode": {
197
+ "label": "Proactive Threshold Compaction Mode",
198
+ "help": "Choose deferred compaction debt by default or keep legacy inline proactive compaction"
199
+ },
184
200
  "fallbackProviders": {
185
201
  "label": "Fallback Providers",
186
202
  "help": "Explicit fallback provider/model pairs for compaction summarization (e.g., [{\"provider\": \"anthropic\", \"model\": \"claude-haiku-4-5\"}])"
@@ -249,6 +265,9 @@
249
265
  "dbPath": {
250
266
  "type": "string"
251
267
  },
268
+ "largeFilesDir": {
269
+ "type": "string"
270
+ },
252
271
  "ignoreSessionPatterns": {
253
272
  "type": "array",
254
273
  "items": {
@@ -324,6 +343,10 @@
324
343
  "enabled": {
325
344
  "type": "boolean"
326
345
  },
346
+ "cacheTTLSeconds": {
347
+ "type": "integer",
348
+ "minimum": 1
349
+ },
327
350
  "maxColdCacheCatchupPasses": {
328
351
  "type": "integer",
329
352
  "minimum": 1
@@ -336,6 +359,10 @@
336
359
  "type": "number",
337
360
  "minimum": 0,
338
361
  "maximum": 0.95
362
+ },
363
+ "coldCacheObservationThreshold": {
364
+ "type": "integer",
365
+ "minimum": 1
339
366
  }
340
367
  }
341
368
  },
@@ -361,6 +388,13 @@
361
388
  "transcriptGcEnabled": {
362
389
  "type": "boolean"
363
390
  },
391
+ "proactiveThresholdCompactionMode": {
392
+ "type": "string",
393
+ "enum": [
394
+ "deferred",
395
+ "inline"
396
+ ]
397
+ },
364
398
  "databasePath": {
365
399
  "description": "Path to LCM SQLite database (preferred key; alias of dbPath, default: <OPENCLAW_STATE_DIR>/lcm.db)",
366
400
  "type": "string"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@martian-engineering/lossless-claw",
3
- "version": "0.8.1",
3
+ "version": "0.9.0",
4
4
  "description": "Lossless Context Management plugin for OpenClaw — DAG-based conversation summarization with incremental compaction",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -16,7 +16,7 @@
16
16
  "dag"
17
17
  ],
18
18
  "scripts": {
19
- "build": "esbuild index.ts --bundle --platform=node --target=node22 --format=esm --outfile=dist/index.js --external:openclaw --external:\"@mariozechner/*\"",
19
+ "build": "esbuild index.ts --bundle --platform=node --target=node22 --format=esm --outfile=dist/index.js --external:openclaw --external:\"@mariozechner/*\" --minify-whitespace",
20
20
  "changeset": "changeset",
21
21
  "release:verify": "npm run build && npm test && npm pack --dry-run",
22
22
  "test": "vitest run --dir test",
@@ -13,7 +13,7 @@ Start here:
13
13
  2. If they need a quick health check, tell them to run `/lossless` (`/lcm` is the shorter alias).
14
14
  3. If they suspect summary corruption or truncation, use `/lossless doctor`.
15
15
  4. If they want high-confidence junk/session cleanup guidance, use `/lossless doctor clean` before recommending any deletes.
16
- 5. If they ask how `/new` or `/reset` interacts with LCM, read the session-lifecycle reference before answering.
16
+ 5. If they ask how `/new`, `/reset`, or `/lossless rotate` interacts with LCM, read the session-lifecycle reference before answering.
17
17
  6. Load the relevant reference file instead of improvising details from memory.
18
18
 
19
19
  Reference map:
@@ -22,7 +22,7 @@ Reference map:
22
22
  - Internal model and data flow: `references/architecture.md`
23
23
  - Diagnostics and summary-health workflow: `references/diagnostics.md`
24
24
  - Recall tools and when to use them: `references/recall-tools.md`
25
- - `/new` and `/reset` behavior with current lossless-claw session mapping: `references/session-lifecycle.md`
25
+ - `/new`, `/reset`, and `/lossless rotate` behavior with current lossless-claw session mapping: `references/session-lifecycle.md`
26
26
 
27
27
  Working rules:
28
28
 
@@ -87,6 +87,7 @@ Good defaults:
87
87
  - `maxColdCacheCatchupPasses: 2`
88
88
  - `hotCachePressureFactor: 4`
89
89
  - `hotCacheBudgetHeadroomRatio: 0.2`
90
+ - `coldCacheObservationThreshold: 3`
90
91
 
91
92
  Operationally:
92
93
 
@@ -187,7 +188,6 @@ Why it matters:
187
188
 
188
189
  - defaults to `${OPENCLAW_STATE_DIR}/lcm-files`; on multi-profile hosts each profile stores files in its own state directory automatically
189
190
  - override with `LCM_LARGE_FILES_DIR` or set `largeFilesDir` in plugin config when you want an explicit path
190
-
191
191
  ### `largeFileThresholdTokens`
192
192
 
193
193
  Threshold for externalizing oversized tool/file payloads out of the main transcript into large-file storage.
@@ -206,6 +206,18 @@ Why it matters:
206
206
  - keep this off unless you want transcript GC to mutate the live session file during maintenance
207
207
  - the default is `false`
208
208
 
209
+ ### `proactiveThresholdCompactionMode`
210
+
211
+ Controls whether proactive threshold compaction is deferred into maintenance debt or kept inline for legacy behavior.
212
+
213
+ Why it matters:
214
+
215
+ - `deferred` is the default and avoids foreground turn stalls by recording one coalesced maintenance row per conversation
216
+ - `deferred` also stores provider/model/cache telemetry so Anthropic-family sessions can avoid rewriting a still-hot prompt cache
217
+ - `inline` preserves the legacy foreground compaction path for hosts that do not yet support deferred execution
218
+ - `/lossless status` and `/lcm status` surface pending/running/last-failure maintenance state so operators can see when compaction is queued
219
+ - 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
220
+
209
221
  ## Compaction timing and shape
210
222
 
211
223
  ### `contextThreshold`
@@ -330,6 +342,19 @@ Why it matters:
330
342
 
331
343
  Defers incremental leaf compaction more aggressively when prompt-cache telemetry indicates a hot cache.
332
344
 
345
+ #### `cacheAwareCompaction.cacheTTLSeconds`
346
+
347
+ Fallback cache TTL used when deferred Anthropic compaction has provider/model telemetry but no explicit runtime cache-retention window.
348
+
349
+ Why it matters:
350
+
351
+ - lets cache-safe deferred compaction stay conservative even when the host only knows that the turn was Anthropic-family, not the exact retention tier
352
+ - keeps prompt-mutating debt pending until the cached prefix is likely cold
353
+
354
+ Default:
355
+
356
+ - `300`
357
+
333
358
  #### `cacheAwareCompaction.maxColdCacheCatchupPasses`
334
359
 
335
360
  Maximum bounded catch-up passes allowed in one maintenance cycle when cache telemetry is cold.
@@ -360,6 +385,19 @@ Default:
360
385
 
361
386
  - `0.2`
362
387
 
388
+ #### `cacheAwareCompaction.coldCacheObservationThreshold`
389
+
390
+ Consecutive cold observations required before non-explicit cache misses are treated as truly cold.
391
+
392
+ Why it matters:
393
+
394
+ - prevents a single OpenRouter routing miss or provider failover blip from immediately triggering cold-cache catch-up
395
+ - explicit cache breaks still count as cold immediately
396
+
397
+ Default:
398
+
399
+ - `3`
400
+
363
401
  ### `dynamicLeafChunkTokens`
364
402
 
365
403
  #### `dynamicLeafChunkTokens.enabled`
@@ -1,4 +1,4 @@
1
- # Session lifecycle (`/new` and `/reset`)
1
+ # Session lifecycle (`/new`, `/reset`, and `/lossless rotate`)
2
2
 
3
3
  This reference describes the current behavior on `main`.
4
4
 
@@ -7,6 +7,7 @@ This reference describes the current behavior on `main`.
7
7
  For stock `lossless-claw` on current main:
8
8
 
9
9
  - OpenClaw handles `/new` and `/reset` as session-reset operations.
10
+ - `lossless-claw` handles `/lossless rotate` (`/lcm rotate`) as transcript maintenance on the current conversation.
10
11
  - `lossless-claw` does **not** currently register its own `before_reset` hook or a custom reset policy.
11
12
  - `lossless-claw` prefers **`sessionKey`** as the stable identity for an LCM conversation.
12
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.
@@ -21,6 +22,7 @@ So today:
21
22
 
22
23
  - `/new` and `/reset` can reset the runtime session
23
24
  - but LCM history may continue in the same conversation row if the chat/thread keeps the same `sessionKey`
25
+ - `/lossless rotate` keeps that same conversation row, summaries, and context items in place while compacting only the live transcript backing
24
26
 
25
27
  ## Why
26
28
 
@@ -32,14 +34,27 @@ Current lossless-claw conversation resolution does this:
32
34
 
33
35
  That behavior preserves continuity across session resets for the same chat identity.
34
36
 
37
+ ## `/lossless rotate`
38
+
39
+ `/lossless rotate` is distinct from `/new` and `/reset`.
40
+
41
+ - it does **not** create a fresh LCM conversation row
42
+ - it does **not** archive the current conversation
43
+ - it **does** create or replace the rolling `rotate-latest` SQLite backup first
44
+ - it **does** rewrite the current transcript into a compact suffix-preserving form
45
+ - it **does** refresh bootstrap state on the same conversation so dropped transcript history is not replayed
46
+ - it **does** preserve the current conversation id, summary DAG, and active context items
47
+
48
+ This makes rotate the lightweight option when the problem is transcript bloat rather than LCM conversation structure.
49
+
35
50
  ## Important limitation
36
51
 
37
- There is currently **no plugin-specific `/new` vs `/reset` split** in stock lossless-claw docs or runtime behavior.
52
+ There is still **no plugin-specific `/new` vs `/reset` split** in stock lossless-claw docs or runtime behavior.
38
53
 
39
54
  If someone is asking for semantics like:
40
55
 
41
- - `/new` keeps LCM history but rotates transcript
42
- - `/reset` archives old LCM conversation and starts a new one
56
+ - `/new` gives them a fresh LCM conversation row
57
+ - `/reset` archives old LCM conversation and starts a new one for the same stable `sessionKey`
43
58
 
44
59
  that is a **design/spec topic**, not current stock behavior.
45
60
 
@@ -48,6 +63,7 @@ that is a **design/spec topic**, not current stock behavior.
48
63
  When answering users:
49
64
 
50
65
  - do not promise that `/new` or `/reset` clears LCM history
66
+ - explain that `/lossless rotate` compacts the current transcript without splitting the LCM conversation
51
67
  - explain that current stock behavior follows `sessionKey` continuity
52
68
  - 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
53
69