@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
package/docs/architecture.md
CHANGED
|
@@ -212,6 +212,17 @@ LCM handles crash recovery through **bootstrap reconciliation**:
|
|
|
212
212
|
|
|
213
213
|
This handles the case where OpenClaw wrote messages to the session file but crashed before LCM could persist them.
|
|
214
214
|
|
|
215
|
+
For forked child sessions, LCM treats a host-copied parent JSONL branch as a
|
|
216
|
+
first-time bootstrap source and imports only the newest messages that fit within
|
|
217
|
+
`bootstrapMaxTokens`. That keeps child LCM state bounded even if the host fork
|
|
218
|
+
payload still contains a long raw parent branch. The remaining fork-continuity
|
|
219
|
+
contract belongs to the host: when lossless-claw advertises the
|
|
220
|
+
`subagent-spawn` requirement for `thread-bootstrap-projection`, OpenClaw should
|
|
221
|
+
bootstrap the child model thread from the context-engine projection rather than
|
|
222
|
+
from the raw copied transcript. If the host cannot provide that capability,
|
|
223
|
+
lossless-claw can preserve bounded durable state, but it cannot stop the host
|
|
224
|
+
from replaying raw JSONL into the model before assembly.
|
|
225
|
+
|
|
215
226
|
## Operation serialization
|
|
216
227
|
|
|
217
228
|
All mutating operations (ingest, compact) are serialized per-session using a promise queue. This prevents races between concurrent afterTurn/compact calls for the same conversation without blocking operations on different conversations.
|
package/docs/configuration.md
CHANGED
|
@@ -30,6 +30,23 @@ Most installations only need to override a handful of keys. If you want a comple
|
|
|
30
30
|
"statelessSessionPatterns": [],
|
|
31
31
|
"skipStatelessSessions": true,
|
|
32
32
|
"contextThreshold": 0.75,
|
|
33
|
+
"contextThresholdOverrides": [
|
|
34
|
+
{
|
|
35
|
+
"name": "large-context-models",
|
|
36
|
+
"match": { "modelContextWindowMin": 900000 },
|
|
37
|
+
"contextThreshold": 0.15
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"name": "small-context-models",
|
|
41
|
+
"match": { "modelContextWindowMax": 250000 },
|
|
42
|
+
"contextThreshold": 0.2
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"name": "telegram-sessions",
|
|
46
|
+
"match": { "sessionPattern": "agent:*:telegram:**" },
|
|
47
|
+
"contextThreshold": 0.3
|
|
48
|
+
}
|
|
49
|
+
],
|
|
33
50
|
"freshTailCount": 64,
|
|
34
51
|
"freshTailMaxTokens": 24000,
|
|
35
52
|
"promptAwareEviction": false,
|
|
@@ -58,14 +75,20 @@ Most installations only need to override a handful of keys. If you want a comple
|
|
|
58
75
|
"expansionModel": "",
|
|
59
76
|
"delegationTimeoutMs": 120000,
|
|
60
77
|
"summaryTimeoutMs": 60000,
|
|
78
|
+
"summaryCallWindowMs": 600000,
|
|
79
|
+
"summaryMaxCallsPerWindow": 24,
|
|
80
|
+
"summarySpendBackoffMs": 1800000,
|
|
61
81
|
"timezone": "America/Los_Angeles",
|
|
62
82
|
"pruneHeartbeatOk": false,
|
|
63
83
|
"transcriptGcEnabled": false,
|
|
84
|
+
"enableSummaryThinking": true,
|
|
64
85
|
"maxAssemblyTokenBudget": 30000,
|
|
65
86
|
"summaryMaxOverageFactor": 3,
|
|
66
87
|
"customInstructions": "",
|
|
67
88
|
"circuitBreakerThreshold": 5,
|
|
68
89
|
"circuitBreakerCooldownMs": 1800000,
|
|
90
|
+
"replayFloodThresholdExternal": 3,
|
|
91
|
+
"replayFloodThresholdInternal": 32,
|
|
69
92
|
"fallbackProviders": [],
|
|
70
93
|
"proactiveThresholdCompactionMode": "deferred",
|
|
71
94
|
"autoRotateSessionFiles": {
|
|
@@ -75,6 +98,11 @@ Most installations only need to override a handful of keys. If you want a comple
|
|
|
75
98
|
"startup": "rotate",
|
|
76
99
|
"runtime": "rotate"
|
|
77
100
|
},
|
|
101
|
+
"independentLogFile": {
|
|
102
|
+
"enabled": true,
|
|
103
|
+
"file": "/tmp/openclaw/lossless-claw-2026-05-19.log",
|
|
104
|
+
"maxFileBytes": 104857600
|
|
105
|
+
},
|
|
78
106
|
"cacheAwareCompaction": {
|
|
79
107
|
"enabled": true,
|
|
80
108
|
"cacheTTLSeconds": 300,
|
|
@@ -87,7 +115,13 @@ Most installations only need to override a handful of keys. If you want a comple
|
|
|
87
115
|
"dynamicLeafChunkTokens": {
|
|
88
116
|
"enabled": true,
|
|
89
117
|
"max": 40000
|
|
90
|
-
}
|
|
118
|
+
},
|
|
119
|
+
"stripInjectedContextTags": [
|
|
120
|
+
"active_memory_plugin",
|
|
121
|
+
"relevant-memories",
|
|
122
|
+
"relevant_memories",
|
|
123
|
+
"hindsight_memories"
|
|
124
|
+
]
|
|
91
125
|
}
|
|
92
126
|
```
|
|
93
127
|
|
|
@@ -139,16 +173,22 @@ openclaw plugins install --link /path/to/lossless-claw
|
|
|
139
173
|
| `timezone` | `string` | `TZ` or system timezone | `TZ` | IANA timezone used for timestamp rendering in summaries. |
|
|
140
174
|
| `pruneHeartbeatOk` | `boolean` | `false` | `LCM_PRUNE_HEARTBEAT_OK` | Retroactively removes `HEARTBEAT_OK` turn cycles from persisted storage. |
|
|
141
175
|
| `transcriptGcEnabled` | `boolean` | `false` | `LCM_TRANSCRIPT_GC_ENABLED` | Enables transcript rewrite GC during `maintain()`; disabled by default so transcript rewrites stay opt-in. |
|
|
176
|
+
| `enableSummaryThinking` | `boolean` | `true` | `LCM_ENABLE_SUMMARY_THINKING` | When true, requests low reasoning budget from the model during summarization calls. Set to false to disable reasoning and keep summarization output concise. |
|
|
142
177
|
| `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. |
|
|
143
178
|
| `autoRotateSessionFiles.enabled` | `boolean` | `true` | `LCM_AUTO_ROTATE_SESSION_FILES_ENABLED` | Enables automatic rotation for oversized LCM-managed session JSONL files. |
|
|
144
179
|
| `autoRotateSessionFiles.createBackups` | `boolean` | `false` | `LCM_AUTO_ROTATE_SESSION_FILES_CREATE_BACKUPS` | Creates or replaces the rolling `rotate-latest` SQLite backup before automatic session-file rotation. Manual `/lcm rotate` backups are always created. |
|
|
145
180
|
| `autoRotateSessionFiles.sizeBytes` | `integer` | `2097152` | `LCM_AUTO_ROTATE_SESSION_FILES_SIZE_BYTES` | Byte threshold that triggers automatic session-file rotation. |
|
|
146
181
|
| `autoRotateSessionFiles.startup` | `"rotate" \| "warn" \| "off"` | `"rotate"` | `LCM_AUTO_ROTATE_SESSION_FILES_STARTUP` | Startup behavior for oversized indexed OpenClaw session transcripts that also have active LCM bootstrap state. |
|
|
147
|
-
| `autoRotateSessionFiles.runtime` | `"rotate" \| "warn" \| "off"` | `"rotate"` | `LCM_AUTO_ROTATE_SESSION_FILES_RUNTIME` | Runtime behavior after `
|
|
182
|
+
| `autoRotateSessionFiles.runtime` | `"rotate" \| "warn" \| "off"` | `"rotate"` | `LCM_AUTO_ROTATE_SESSION_FILES_RUNTIME` | Runtime behavior after post-turn checks. Runtime `rotate` logs deferral for active session JSONL rewrites and leaves direct rotation to startup or manual `/lcm rotate`. |
|
|
183
|
+
| `independentLogFile.enabled` | `boolean` | `true` | `LCM_LOG_FILE_ENABLED` | Writes lossless-claw JSONL logs to an independent plugin-owned file in addition to OpenClaw's runtime logger. |
|
|
184
|
+
| `independentLogFile.file` | `string` | `/tmp/openclaw/lossless-claw-YYYY-MM-DD.log` | `LCM_LOG_FILE` | Optional log path. A dated `lossless-claw-YYYY-MM-DD.log` path rolls over daily. |
|
|
185
|
+
| `independentLogFile.maxFileBytes` | `integer` | `104857600` | `LCM_LOG_MAX_FILE_BYTES` | Size threshold for rotating the active lossless-claw log file to `.1.log` through `.5.log`. |
|
|
148
186
|
|
|
149
187
|
> **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.
|
|
150
188
|
|
|
151
|
-
Automatic session-file rotation rewrites only the live session transcript, keeps the active LCM conversation and durable history intact, and refreshes the bootstrap checkpoint. Startup rotation first scans OpenClaw's current indexed session stores for configured agents, then intersects those candidates with active LCM conversations and matching bootstrap file mappings. Automatic rotation does not create a SQLite backup by default; set `autoRotateSessionFiles.createBackups` to `true` to make
|
|
189
|
+
Automatic session-file rotation rewrites only the live session transcript, keeps the active LCM conversation and durable history intact, and refreshes the bootstrap checkpoint. Before manual or startup rewrites, rotation forces leaf-only compaction for raw context outside the preserved tail so trimmed transcript messages are covered by LCM summaries without running unrelated summary-condensation passes. Startup rotation first scans OpenClaw's current indexed session stores for configured agents, then intersects those candidates with active LCM conversations and matching bootstrap file mappings. Runtime rotation checks from `afterTurn()` and `maintain()` intentionally do not directly rewrite active session JSONL because embedded prompt-lock fences can still be open while tool-call loops and host background maintenance overlap; runtime `rotate` logs a deferral until startup, manual `/lcm rotate`, or a future host-owned full-transcript rewrite primitive is available. Automatic rotation does not create a SQLite backup by default; set `autoRotateSessionFiles.createBackups` to `true` to make startup rotation create one pre-rotation LCM database backup for the batch before any transcript is rewritten. Manual `/lcm rotate` always keeps its backup-backed behavior regardless of this flag. Rotation never runs for ignored sessions, stateless sessions, or sessions without active LCM state. The preserved JSONL tail follows the existing rotate behavior, which is controlled by `freshTailCount`. Transcript GC uses the host-provided `rewriteTranscriptEntries` primitive and defers until host-approved background maintenance when `transcriptGcEnabled` is enabled.
|
|
190
|
+
|
|
191
|
+
Lossless-claw writes routine operational JSONL logs by default at `/tmp/openclaw/lossless-claw-YYYY-MM-DD.log`, beside OpenClaw's `/tmp/openclaw/openclaw-YYYY-MM-DD.log`. Routine info and debug lines go to the independent file instead of the shared OpenClaw log. Startup banners and warning/error lines still go through OpenClaw's runtime logger so gateway-level startup and failure diagnostics remain visible. The independent file follows the same practical rotation model as OpenClaw: a dated filename rolls over when the local date changes, stale dated files are pruned after 3 days, and an oversized active file is rotated through `.1.log` to `.5.log`.
|
|
152
192
|
|
|
153
193
|
Every automatic decision emits grep-able log lines prefixed with `[lcm] auto-rotate:`. Startup emits one compact summary line with `phase=startup`, `action=summary`, `scanned`, `eligible`, `rotated`, `warned`, `skipped`, `durationMs`, `bytesRemoved`, and backup fields when a batch backup was created; quiet skips such as missing files, missing bootstrap mappings, and below-threshold files are counted there instead of producing one line per candidate. Rotation detail lines include `phase`, `action`, `sessionId`, `sessionKey`, `sessionFile`, `sizeBytes`, `thresholdBytes`, `durationMs`, `backupPath`, `bytesRemoved`, `preservedTailMessageCount`, and `checkpointSize`; real warning lines include the same available context plus `reason` or `error`.
|
|
154
194
|
|
|
@@ -157,6 +197,7 @@ Every automatic decision emits grep-able log lines prefixed with `[lcm] auto-rot
|
|
|
157
197
|
| Key | Type | Default | Env override | Purpose |
|
|
158
198
|
| --- | --- | --- | --- | --- |
|
|
159
199
|
| `contextThreshold` | `number` | `0.75` | `LCM_CONTEXT_THRESHOLD` | Fraction of the active model context window that triggers compaction. |
|
|
200
|
+
| `contextThresholdOverrides` | `Array<{ name?: string; match: object; contextThreshold: number }>` | `[]` | none | Optional ordered rules that override `contextThreshold` by model id, model context-window range, or session glob pattern. |
|
|
160
201
|
| `freshTailCount` | `integer` | `64` | `LCM_FRESH_TAIL_COUNT` | Number of newest messages always kept raw. |
|
|
161
202
|
| `freshTailMaxTokens` | `integer` | unset | `LCM_FRESH_TAIL_MAX_TOKENS` | Optional token cap for the protected fresh tail. The newest message is always preserved even if it exceeds the cap. |
|
|
162
203
|
| `promptAwareEviction` | `boolean` | `false` | `LCM_PROMPT_AWARE_EVICTION_ENABLED` | When enabled, budget-constrained assembly keeps older evictable items by prompt relevance instead of pure chronology. This improves retrieval under tight budgets, but it can reduce prompt-cache hit rates because the preserved prefix changes as prompts change. |
|
|
@@ -180,6 +221,13 @@ Every automatic decision emits grep-able log lines prefixed with `[lcm] auto-rot
|
|
|
180
221
|
| `maxAssemblyTokenBudget` | `integer` | unset | `LCM_MAX_ASSEMBLY_TOKEN_BUDGET` | Optional hard cap for assembly and threshold evaluation, useful with smaller-context models. |
|
|
181
222
|
| `maxExpandTokens` | `integer` | `4000` | `LCM_MAX_EXPAND_TOKENS` | Default token cap for `lcm_expand_query` responses. |
|
|
182
223
|
|
|
224
|
+
Forked child transcripts are also bounded by `bootstrapMaxTokens` when a host
|
|
225
|
+
copies a raw parent JSONL branch into the child file. This protects the LCM
|
|
226
|
+
database from importing unbounded parent history, but the host must still honor
|
|
227
|
+
the `thread-bootstrap-projection` context-engine capability for subagent or
|
|
228
|
+
thread forks so the model starts from the LCM-assembled compact view instead of
|
|
229
|
+
the raw copied transcript.
|
|
230
|
+
|
|
183
231
|
### Model selection, execution, and prompts
|
|
184
232
|
|
|
185
233
|
| Key | Type | Default | Env override | Purpose |
|
|
@@ -192,6 +240,9 @@ Every automatic decision emits grep-able log lines prefixed with `[lcm] auto-rot
|
|
|
192
240
|
| `expansionProvider` | `string` | `""` | `LCM_EXPANSION_PROVIDER` | `lcm_expand_query` sub-agent provider hint for bare model names. |
|
|
193
241
|
| `delegationTimeoutMs` | `integer` | `120000` | `LCM_DELEGATION_TIMEOUT_MS` | Maximum time to wait for delegated expansion work. `lcm_expand_query` advertises a dynamic tool `timeoutMs` default with 30 seconds of extra RPC headroom so OpenClaw's tool watchdog does not fire before this wait completes. |
|
|
194
242
|
| `summaryTimeoutMs` | `integer` | `60000` | `LCM_SUMMARY_TIMEOUT_MS` | Maximum time to wait for one model-backed summarizer call. |
|
|
243
|
+
| `summaryCallWindowMs` | `integer` | `600000` | `LCM_SUMMARY_CALL_WINDOW_MS` | Rolling window for the per-session summarization spend guard. |
|
|
244
|
+
| `summaryMaxCallsPerWindow` | `integer` | `24` | `LCM_SUMMARY_MAX_CALLS_PER_WINDOW` | Maximum model-backed summarization calls per session/window before Lossless opens a non-auth spend backoff. |
|
|
245
|
+
| `summarySpendBackoffMs` | `integer` | `1800000` | `LCM_SUMMARY_SPEND_BACKOFF_MS` | Cooldown after the summarization spend guard opens. |
|
|
195
246
|
| `customInstructions` | `string` | `""` | `LCM_CUSTOM_INSTRUCTIONS` | Extra natural-language instructions injected into every summarization prompt. |
|
|
196
247
|
|
|
197
248
|
Summary calls are executed through OpenClaw's `api.runtime.llm.complete` capability. If you configure an explicit Lossless summary model (`summaryModel`, `largeFileSummaryModel`, or `fallbackProviders`), OpenClaw must allow that runtime LLM override under `plugins.entries.lossless-claw.llm.allowModelOverride` and `plugins.entries.lossless-claw.llm.allowedModels`. `openclaw doctor --fix` can add the minimal policy entries for configured Lossless summary models. Delegated expansion calls use OpenClaw's runtime sub-agent layer; explicit `expansionModel` values require `plugins.entries.lossless-claw.subagent.allowModelOverride` and a matching `subagent.allowedModels` entry, or `"*"` if you intentionally trust any expansion target. `openclaw doctor --fix` can add the minimal subagent policy, and `lcm_expand_query` retries once without the override if the host rejects it.
|
|
@@ -203,6 +254,9 @@ Summary calls are executed through OpenClaw's `api.runtime.llm.complete` capabil
|
|
|
203
254
|
| `fallbackProviders` | `Array<{ provider: string; model: string }>` | `[]` | `LCM_FALLBACK_PROVIDERS` | Explicit provider/model fallback chain for compaction summarization. Format for env vars is `provider/model,provider/model`. |
|
|
204
255
|
| `circuitBreakerThreshold` | `integer` | `5` | `LCM_CIRCUIT_BREAKER_THRESHOLD` | Consecutive auth failures before the summarization circuit breaker trips. |
|
|
205
256
|
| `circuitBreakerCooldownMs` | `integer` | `1800000` | `LCM_CIRCUIT_BREAKER_COOLDOWN_MS` | Cooldown before the summarization circuit breaker resets automatically. |
|
|
257
|
+
| `stripInjectedContextTags` | `string[]` | `["active_memory_plugin", "relevant-memories", "relevant_memories", "hindsight_memories"]` | `LCM_STRIP_INJECTED_CONTEXT_TAGS` | XML tag names whose blocks are stripped from message content before compaction summarization. Memory/context plugins inject these via `prependContext`; stripping prevents ephemeral retrieval context from polluting compacted summaries. Env var format is comma-separated tag names. Set to `[]` (or empty env string) to disable. |
|
|
258
|
+
| `replayFloodThresholdExternal` | `integer` | `3` | `LCM_REPLAY_FLOOD_THRESHOLD_EXTERNAL` | Max replay-like messages allowed in a single SQLite-second for `role=user` before `assertNoReplayTimestampFlood` refuses the batch. Defaults to `3` to preserve replay defense for third-partyly-rebroadcastable input. |
|
|
259
|
+
| `replayFloodThresholdInternal` | `integer` | `32` | `LCM_REPLAY_FLOOD_THRESHOLD_INTERNAL` | Max identical messages allowed in a single SQLite-second for `role=tool/assistant/system` before the anti-replay guard refuses the batch. Defaults to `32` to absorb legitimate idempotent sub-agent bursts (same-second tool returns like `{"status":"ok"}`). |
|
|
206
260
|
|
|
207
261
|
### Nested objects
|
|
208
262
|
|
|
@@ -229,13 +283,16 @@ Summary calls are executed through OpenClaw's `api.runtime.llm.complete` capabil
|
|
|
229
283
|
|
|
230
284
|
Automatic compaction is threshold-only:
|
|
231
285
|
|
|
232
|
-
- `afterTurn()` evaluates
|
|
286
|
+
- `afterTurn()` evaluates the resolved context threshold against the active token budget
|
|
233
287
|
- below threshold, no automatic compaction runs and no leaf debt is recorded
|
|
234
288
|
- at or above threshold, inline mode runs a threshold full sweep immediately
|
|
235
|
-
- deferred mode records one coalesced `"threshold"` maintenance row and drains it in the background
|
|
289
|
+
- deferred mode records one coalesced `"threshold"` maintenance row and normally drains it in the background or host-approved `maintain()`
|
|
290
|
+
- pre-assembly drain is reserved as an emergency safeguard when the live prompt is already over the active token budget
|
|
236
291
|
|
|
237
292
|
Lossless still records prompt-cache telemetry for status and diagnostics, but cache hotness no longer delays threshold debt. Legacy `cacheAwareCompaction.*` and `dynamicLeafChunkTokens.*` settings remain accepted so existing OpenClaw config continues to load, but they do not change automatic compaction behavior.
|
|
238
293
|
|
|
294
|
+
`contextThresholdOverrides` are optional and never replace the global fallback. Each rule's `match` object can include `model`, `modelContextWindowMin`, `modelContextWindowMax`, and `sessionPattern`; all fields in a rule must match. If several rules match, Lossless picks the highest-specificity rule, then the earliest rule in the array for ties. Exact `model` matches have higher specificity than `sessionPattern` matches, and session-pattern matches have higher specificity than context-window range matches. Threshold selection logs include the chosen threshold, source, rule index/name, token budget, threshold tokens, model, context-window value, and match reason.
|
|
295
|
+
|
|
239
296
|
Full sweeps first run leaf passes until there are no more eligible raw-message chunks outside the fresh tail. Condensation is then driven by summarized-prefix pressure: the routine condensation phase obeys `sweepMaxDepth`, and if the summarized prefix still exceeds `summaryPrefixTargetTokens`, a pressure phase may use `condensedMinFanoutHard` and condense deeper. Total context pressure starts the sweep, but does not by itself force deeper condensation once the raw prefix has been summarized.
|
|
240
297
|
|
|
241
298
|
A single sweep is bounded by both `maxSweepIterations` (a hard cap on summarizer passes) and `sweepDeadlineMs` (a wall-clock budget). When either limit is reached the sweep stops before starting another pass and returns the consistent partial result built so far, logging a `compactFullSweep stopped at …` warning. This keeps a slow or rate-limited summarizer from hanging the agent turn — remaining context pressure is picked up by the next sweep.
|
|
@@ -287,6 +344,8 @@ LCM_EXPANSION_MODEL=openai/gpt-5.4-mini
|
|
|
287
344
|
- `*` matches any characters except `:`
|
|
288
345
|
- `**` matches anything, including `:`
|
|
289
346
|
|
|
347
|
+
Cron scheduler keys (`agent:<agent>:cron:<job>...`) are isolated automatically when a new runtime `sessionId` reuses the same `sessionKey`. Configure `ignoreSessionPatterns` for cron only when the run should bypass LCM entirely; leave cron sessions included when they need in-run compaction.
|
|
348
|
+
|
|
290
349
|
Example:
|
|
291
350
|
|
|
292
351
|
```json
|
|
@@ -318,7 +377,8 @@ Lossless-claw now defaults `proactiveThresholdCompactionMode` to `deferred`.
|
|
|
318
377
|
- deferred mode records a single coalesced maintenance debt row per conversation
|
|
319
378
|
- new deferred compaction debt is only created for `contextThreshold` pressure and uses reason `"threshold"`
|
|
320
379
|
- `maintain()` consumes threshold debt when the host explicitly opts in to deferred execution
|
|
321
|
-
- `assemble()`
|
|
380
|
+
- `assemble()` leaves pending threshold debt for after-turn background drain or host-approved `maintain()` while the live prompt is still within budget
|
|
381
|
+
- `assemble()` only consumes pending threshold debt synchronously as an emergency safeguard when the live prompt estimate is already over the active token budget
|
|
322
382
|
- old non-threshold debt from earlier builds is revalidated; if the conversation is no longer over threshold, it is cleared as a no-op
|
|
323
383
|
- `/lcm status` / `/lossless status` shows the current maintenance state, including pending/running/last-failure details
|
|
324
384
|
- status output also surfaces the latest API/cache telemetry as diagnostics, not as a deferral gate
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "lossless-claw",
|
|
3
|
+
"name": "Lossless Context Management",
|
|
3
4
|
"kind": "context-engine",
|
|
4
5
|
"activation": {
|
|
5
6
|
"onStartup": true
|
|
@@ -24,6 +25,10 @@
|
|
|
24
25
|
"label": "Context Threshold",
|
|
25
26
|
"help": "Fraction of context window that triggers compaction (0.0–1.0)"
|
|
26
27
|
},
|
|
28
|
+
"contextThresholdOverrides": {
|
|
29
|
+
"label": "Context Threshold Overrides",
|
|
30
|
+
"help": "Optional ordered rules that override contextThreshold by model, model context-window range, or session glob pattern"
|
|
31
|
+
},
|
|
27
32
|
"sweepMaxDepth": {
|
|
28
33
|
"label": "Sweep Max Depth",
|
|
29
34
|
"help": "Preferred maximum condensation source depth during routine full sweeps (0 = leaf only, -1 = unlimited). Pressure sweeps may go deeper when summarized context remains above target."
|
|
@@ -168,6 +173,18 @@
|
|
|
168
173
|
"label": "Summary Timeout (ms)",
|
|
169
174
|
"help": "Maximum time to wait for a single model-backed LCM summarizer call before timing out"
|
|
170
175
|
},
|
|
176
|
+
"summaryCallWindowMs": {
|
|
177
|
+
"label": "Summary Call Window (ms)",
|
|
178
|
+
"help": "Rolling window used by the summarization spend guard"
|
|
179
|
+
},
|
|
180
|
+
"summaryMaxCallsPerWindow": {
|
|
181
|
+
"label": "Summary Max Calls Per Window",
|
|
182
|
+
"help": "Maximum model-backed summarization calls per session/window before Lossless opens a non-auth spend backoff"
|
|
183
|
+
},
|
|
184
|
+
"summarySpendBackoffMs": {
|
|
185
|
+
"label": "Summary Spend Backoff (ms)",
|
|
186
|
+
"help": "Cooldown after the summarization spend guard opens"
|
|
187
|
+
},
|
|
171
188
|
"maxAssemblyTokenBudget": {
|
|
172
189
|
"label": "Max Assembly Token Budget",
|
|
173
190
|
"help": "Hard ceiling for assembly token budget — caps runtime-provided and fallback budgets. Set for smaller context-window models (e.g., 30000 for 32k models)"
|
|
@@ -188,6 +205,14 @@
|
|
|
188
205
|
"label": "Circuit Breaker Cooldown (ms)",
|
|
189
206
|
"help": "Cooldown before the summarization circuit breaker auto-resets"
|
|
190
207
|
},
|
|
208
|
+
"replayFloodThresholdExternal": {
|
|
209
|
+
"label": "Replay Flood Threshold (External)",
|
|
210
|
+
"help": "Max replay-like messages allowed in a single SQLite-second for third-party roles (role=user) before the anti-replay guard refuses the batch. Defaults to 3 to preserve replay defense for third-partyly-rebroadcastable input."
|
|
211
|
+
},
|
|
212
|
+
"replayFloodThresholdInternal": {
|
|
213
|
+
"label": "Replay Flood Threshold (Internal)",
|
|
214
|
+
"help": "Max identical messages allowed in a single SQLite-second for internal roles (role=tool/assistant/system) before the anti-replay guard refuses the batch. Defaults to 32 to absorb legitimate idempotent sub-agent bursts."
|
|
215
|
+
},
|
|
191
216
|
"cacheAwareCompaction.enabled": {
|
|
192
217
|
"label": "Cache-Aware Compaction (Deprecated)",
|
|
193
218
|
"help": "Deprecated compatibility setting. Automatic compaction is now threshold-only and does not use prompt-cache hot/cold state."
|
|
@@ -236,6 +261,10 @@
|
|
|
236
261
|
"label": "Transcript GC",
|
|
237
262
|
"help": "Enable transcript rewrite GC during maintain(); disabled by default"
|
|
238
263
|
},
|
|
264
|
+
"enableSummaryThinking": {
|
|
265
|
+
"label": "Enable Summary Thinking",
|
|
266
|
+
"help": "Request low reasoning budget from the model during summarization calls"
|
|
267
|
+
},
|
|
239
268
|
"proactiveThresholdCompactionMode": {
|
|
240
269
|
"label": "Proactive Threshold Compaction Mode",
|
|
241
270
|
"help": "Choose deferred compaction debt by default or keep legacy inline proactive compaction"
|
|
@@ -260,9 +289,25 @@
|
|
|
260
289
|
"label": "Runtime Auto-Rotate",
|
|
261
290
|
"help": "Runtime behavior for oversized current LCM session files: rotate, warn, or off"
|
|
262
291
|
},
|
|
292
|
+
"independentLogFile.enabled": {
|
|
293
|
+
"label": "Independent Log File",
|
|
294
|
+
"help": "Write lossless-claw JSONL logs to a plugin-owned file in addition to OpenClaw's runtime logger"
|
|
295
|
+
},
|
|
296
|
+
"independentLogFile.file": {
|
|
297
|
+
"label": "Independent Log Path",
|
|
298
|
+
"help": "Optional lossless-claw log path; defaults to /tmp/openclaw/lossless-claw-YYYY-MM-DD.log with daily rollover"
|
|
299
|
+
},
|
|
300
|
+
"independentLogFile.maxFileBytes": {
|
|
301
|
+
"label": "Independent Log Max Bytes",
|
|
302
|
+
"help": "Byte threshold for size rotation of the current lossless-claw log file (default: 104857600)"
|
|
303
|
+
},
|
|
263
304
|
"fallbackProviders": {
|
|
264
305
|
"label": "Fallback Providers",
|
|
265
306
|
"help": "Explicit runtime LLM fallback provider/model pairs for compaction summarization; entries require plugins.entries.lossless-claw.llm policy"
|
|
307
|
+
},
|
|
308
|
+
"stripInjectedContextTags": {
|
|
309
|
+
"label": "Strip Injected Context Tags",
|
|
310
|
+
"help": "XML tag names whose blocks are stripped from messages before summarization. Covers memory/context plugin prepended blocks (active-memory, memory-lancedb, hindsight-openclaw). Set to [] to disable."
|
|
266
311
|
}
|
|
267
312
|
},
|
|
268
313
|
"configSchema": {
|
|
@@ -277,6 +322,50 @@
|
|
|
277
322
|
"minimum": 0,
|
|
278
323
|
"maximum": 1
|
|
279
324
|
},
|
|
325
|
+
"contextThresholdOverrides": {
|
|
326
|
+
"type": "array",
|
|
327
|
+
"items": {
|
|
328
|
+
"type": "object",
|
|
329
|
+
"additionalProperties": false,
|
|
330
|
+
"required": [
|
|
331
|
+
"match",
|
|
332
|
+
"contextThreshold"
|
|
333
|
+
],
|
|
334
|
+
"properties": {
|
|
335
|
+
"name": {
|
|
336
|
+
"type": "string"
|
|
337
|
+
},
|
|
338
|
+
"match": {
|
|
339
|
+
"type": "object",
|
|
340
|
+
"additionalProperties": false,
|
|
341
|
+
"minProperties": 1,
|
|
342
|
+
"properties": {
|
|
343
|
+
"model": {
|
|
344
|
+
"type": "string",
|
|
345
|
+
"minLength": 1
|
|
346
|
+
},
|
|
347
|
+
"modelContextWindowMin": {
|
|
348
|
+
"type": "integer",
|
|
349
|
+
"minimum": 1
|
|
350
|
+
},
|
|
351
|
+
"modelContextWindowMax": {
|
|
352
|
+
"type": "integer",
|
|
353
|
+
"minimum": 1
|
|
354
|
+
},
|
|
355
|
+
"sessionPattern": {
|
|
356
|
+
"type": "string",
|
|
357
|
+
"minLength": 1
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
},
|
|
361
|
+
"contextThreshold": {
|
|
362
|
+
"type": "number",
|
|
363
|
+
"minimum": 0,
|
|
364
|
+
"maximum": 1
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
},
|
|
280
369
|
"sweepMaxDepth": {
|
|
281
370
|
"type": "integer",
|
|
282
371
|
"minimum": -1
|
|
@@ -406,6 +495,18 @@
|
|
|
406
495
|
"type": "integer",
|
|
407
496
|
"minimum": 1
|
|
408
497
|
},
|
|
498
|
+
"summaryCallWindowMs": {
|
|
499
|
+
"type": "integer",
|
|
500
|
+
"minimum": 1
|
|
501
|
+
},
|
|
502
|
+
"summaryMaxCallsPerWindow": {
|
|
503
|
+
"type": "integer",
|
|
504
|
+
"minimum": 1
|
|
505
|
+
},
|
|
506
|
+
"summarySpendBackoffMs": {
|
|
507
|
+
"type": "integer",
|
|
508
|
+
"minimum": 1
|
|
509
|
+
},
|
|
409
510
|
"maxAssemblyTokenBudget": {
|
|
410
511
|
"type": "integer",
|
|
411
512
|
"minimum": 1000
|
|
@@ -425,6 +526,14 @@
|
|
|
425
526
|
"type": "integer",
|
|
426
527
|
"minimum": 1
|
|
427
528
|
},
|
|
529
|
+
"replayFloodThresholdExternal": {
|
|
530
|
+
"type": "integer",
|
|
531
|
+
"minimum": 1
|
|
532
|
+
},
|
|
533
|
+
"replayFloodThresholdInternal": {
|
|
534
|
+
"type": "integer",
|
|
535
|
+
"minimum": 1
|
|
536
|
+
},
|
|
428
537
|
"cacheAwareCompaction": {
|
|
429
538
|
"type": "object",
|
|
430
539
|
"additionalProperties": false,
|
|
@@ -482,6 +591,9 @@
|
|
|
482
591
|
"transcriptGcEnabled": {
|
|
483
592
|
"type": "boolean"
|
|
484
593
|
},
|
|
594
|
+
"enableSummaryThinking": {
|
|
595
|
+
"type": "boolean"
|
|
596
|
+
},
|
|
485
597
|
"proactiveThresholdCompactionMode": {
|
|
486
598
|
"type": "string",
|
|
487
599
|
"enum": [
|
|
@@ -521,6 +633,22 @@
|
|
|
521
633
|
}
|
|
522
634
|
}
|
|
523
635
|
},
|
|
636
|
+
"independentLogFile": {
|
|
637
|
+
"type": "object",
|
|
638
|
+
"additionalProperties": false,
|
|
639
|
+
"properties": {
|
|
640
|
+
"enabled": {
|
|
641
|
+
"type": "boolean"
|
|
642
|
+
},
|
|
643
|
+
"file": {
|
|
644
|
+
"type": "string"
|
|
645
|
+
},
|
|
646
|
+
"maxFileBytes": {
|
|
647
|
+
"type": "integer",
|
|
648
|
+
"minimum": 1
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
},
|
|
524
652
|
"databasePath": {
|
|
525
653
|
"description": "Path to LCM SQLite database (preferred key; alias of dbPath, default: <OPENCLAW_STATE_DIR>/lcm.db)",
|
|
526
654
|
"type": "string"
|
|
@@ -540,6 +668,13 @@
|
|
|
540
668
|
"required": ["provider", "model"],
|
|
541
669
|
"additionalProperties": false
|
|
542
670
|
}
|
|
671
|
+
},
|
|
672
|
+
"stripInjectedContextTags": {
|
|
673
|
+
"description": "XML tag names whose blocks are stripped from messages before summarization. Memory/context plugins prepend tagged blocks via prependContext that should not leak into compacted summaries.",
|
|
674
|
+
"type": "array",
|
|
675
|
+
"items": {
|
|
676
|
+
"type": "string"
|
|
677
|
+
}
|
|
543
678
|
}
|
|
544
679
|
}
|
|
545
680
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@martian-engineering/lossless-claw",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "Lossless Context Management plugin for OpenClaw — DAG-based conversation summarization with threshold compaction",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -18,8 +18,10 @@
|
|
|
18
18
|
"scripts": {
|
|
19
19
|
"build": "esbuild index.ts --bundle --platform=node --target=node22 --format=esm --outfile=dist/index.js --external:openclaw --external:\"@earendil-works/*\" --minify-whitespace",
|
|
20
20
|
"changeset": "changeset",
|
|
21
|
-
"
|
|
21
|
+
"plugin-inspector:ci": "npm exec --yes --package @openclaw/plugin-inspector@0.3.11 -- plugin-inspector ci --plugin-root . --out plugin-inspector-reports",
|
|
22
|
+
"release:verify": "npm run typecheck && npm run build && npm test && npm pack --dry-run",
|
|
22
23
|
"test": "vitest run --dir test",
|
|
24
|
+
"typecheck": "tsc --noEmit --pretty false",
|
|
23
25
|
"version-packages": "changeset version"
|
|
24
26
|
},
|
|
25
27
|
"files": [
|
|
@@ -33,20 +35,20 @@
|
|
|
33
35
|
"LICENSE"
|
|
34
36
|
],
|
|
35
37
|
"dependencies": {
|
|
36
|
-
"@earendil-works/pi-agent-core": ">=0.74 <1",
|
|
37
|
-
"@earendil-works/pi-ai": ">=0.74 <1",
|
|
38
|
-
"@earendil-works/pi-coding-agent": ">=0.74 <1",
|
|
39
38
|
"@sinclair/typebox": "0.34.48"
|
|
40
39
|
},
|
|
41
40
|
"devDependencies": {
|
|
42
41
|
"@changesets/changelog-github": "^0.6.0",
|
|
43
42
|
"@changesets/cli": "^2.30.0",
|
|
43
|
+
"@earendil-works/pi-agent-core": "^0.79.0",
|
|
44
|
+
"@earendil-works/pi-ai": "^0.79.0",
|
|
45
|
+
"@earendil-works/pi-coding-agent": "^0.79.0",
|
|
44
46
|
"esbuild": "^0.28.0",
|
|
45
47
|
"typescript": "^5.7.0",
|
|
46
48
|
"vitest": "^3.0.0"
|
|
47
49
|
},
|
|
48
50
|
"peerDependencies": {
|
|
49
|
-
"openclaw": ">=2026.5.
|
|
51
|
+
"openclaw": ">=2026.5.28"
|
|
50
52
|
},
|
|
51
53
|
"peerDependenciesMeta": {
|
|
52
54
|
"openclaw": {
|
|
@@ -61,14 +63,14 @@
|
|
|
61
63
|
"./dist/index.js"
|
|
62
64
|
],
|
|
63
65
|
"compat": {
|
|
64
|
-
"pluginApi": ">=2026.5.
|
|
65
|
-
"minGatewayVersion": "2026.5.
|
|
66
|
+
"pluginApi": ">=2026.5.28",
|
|
67
|
+
"minGatewayVersion": "2026.5.28",
|
|
66
68
|
"tested": [
|
|
67
|
-
"2026.5.
|
|
69
|
+
"2026.5.28"
|
|
68
70
|
]
|
|
69
71
|
},
|
|
70
72
|
"build": {
|
|
71
|
-
"openclawVersion": "2026.5.
|
|
73
|
+
"openclawVersion": "2026.5.28"
|
|
72
74
|
}
|
|
73
75
|
},
|
|
74
76
|
"repository": {
|
|
@@ -11,10 +11,11 @@ Start here:
|
|
|
11
11
|
|
|
12
12
|
1. Confirm whether the user needs configuration help, diagnostics, recall-tool guidance, or session-lifecycle guidance.
|
|
13
13
|
2. If they need a quick health check, tell them to run `/lossless` (`/lcm` is the shorter alias).
|
|
14
|
-
3. If they
|
|
15
|
-
4. If they
|
|
16
|
-
5. If they
|
|
17
|
-
6.
|
|
14
|
+
3. If they are debugging lossless-claw behavior or failures, check the independent Lossless log before the shared OpenClaw gateway log.
|
|
15
|
+
4. If they suspect summary corruption or truncation, use `/lossless doctor`.
|
|
16
|
+
5. If they want high-confidence junk/session cleanup guidance, use `/lossless doctor clean` before recommending any deletes.
|
|
17
|
+
6. If they ask how `/new`, `/reset`, or `/lossless rotate` interacts with LCM, read the session-lifecycle reference before answering.
|
|
18
|
+
7. Load the relevant reference file instead of improvising details from memory.
|
|
18
19
|
|
|
19
20
|
Reference map:
|
|
20
21
|
|