v0.13.0
0.13.0
Minor Changes
-
#847
264f63aThanks @hubofvalley! - Add scoped context threshold overrides for matching model ids, model context-window
ranges, and session patterns. -
#832
2c3b906Thanks @mpz4life! - AddenableSummaryThinkingconfig option to control whether summarization calls request a low reasoning budget from the model. Defaults totrue(preserves current behavior). Set tofalseto disable reasoning and keep summarization output concise when reasoning is not needed for faithful summaries. -
#854
0837340Thanks @jalehman! - Rebuild transcript reconciliation around the stable JSONL envelope ids that OpenClaw already writes. Transcript imports are now idempotent by construction (messages.transcript_entry_idwith a partial unique index), the afterTurn runtime batch is reconciled by exact alignment against the covered transcript frontier instead of heuristic dedup, rewritten/rotated transcripts are recognized as declared epoch rollovers via the session header id instead of path/size heuristics, and flush-lagged runtime rows are healed in place by adopting the catch-up entry's id rather than duplicated. Entry-id anchoring also survives post-ingest content rewriting (externalized tool results), which previously could freeze conversations. The content-identity machinery remains as the fallback for transcripts without envelope ids. Design: specs/transcript-reconciliation-by-entry-id.md.
Patch Changes
-
#885
9f36aa3Thanks @rafaelreis-r! - Fix a[#822](https://github.com/Martian-Engineering/lossless-claw/issues/822)afterTurndeadlock where a conversation with an all-zero placeholderconversation_bootstrap_staterow and a persisted frontier of only non-anchoring rows (e.g. one or more injectedConversation info (untrusted metadata)preambles) never re-imported its on-disk transcript. The placeholder-checkpoint-recovery reconcile ran withoutallowNoAnchorImport, so it imported 0 messages and never advanced the checkpoint; every turn then emitted thedid not cover the transcript frontierwarning,assemble()fell back to the raw transcript, and compaction never ran for that conversation.The placeholder recovery now opens a no-anchor import, but only when the persisted frontier is proven to hold no real conversation content — generalizing #837's single-injected-metadata-preamble check to a frontier composed entirely of non-anchoring injected-metadata rows (any count, via a bounded scan). A frontier with real anchoring rows conservatively freezes per #649's no-proof-no-advance guard, so an unrelated/rotated transcript can never be stitched onto real history (the failure mode that closed #824). For the proven-safe case the no-anchor import cap is lifted so a transcript larger than the cap recovers fully, bounded by the transcript length rather than left unbounded. The downstream import remains guarded by replay-overlap detection, the delivery-only block (now also applied to this lane), and the cross-conversation raw-id guard.
The same generalized non-anchoring-frontier gate now also drives the afterTurn checkpoint-missing lane (#837), which previously required exactly one persisted frontier row: a conversation that accumulated several injected-metadata preambles before losing its checkpoint recovers there too, and the cap lift applies under the same proof.
-
#838
e9697e4Thanks @holgergruenhagen! - Fix anafterTurndeadlock where a conversation withbootstrapped_atset but noconversation_bootstrap_staterow (reason="checkpoint-missing") and a non-anchoring DB frontier (e.g. a single injectedConversation info (untrusted metadata)preamble) imported 0 messages and never persisted a checkpoint. Such conversations emitted thefound no anchor and imported 0 messages/did not cover the transcript frontierwarning pair on every turn forever, with compaction permanently disabled until the row was archived by hand.The recovery path (
allowNoAnchorImportOnCheckpointMissing) previously ran only on the rotate lane. TheafterTurnlane now also recovers acheckpoint-missingno-anchor frontier, but only for already-bootstrapped conversations (bootstrapped_atset) — a never-bootstrapped conversation with a divergent rewritten transcript still freezes per #649's no-proof-no-advance guard. The downstream no-anchor import remains guarded by replay-overlap detection, the import cap, and the delivery-only block.Fixes #837.
-
#886
e6568d7Thanks @rafaelreis-r! -afterTurnnow fails closed when the transcript reconcile throws. The catch handler previously left the initialized in-sync default (hasOverlap: true) in place, so a thrown reconcile persisted the live batch AND refreshed the checkpoint to EOF — silently advancing past transcript history that was never reconciled into the DB. The catch now reports the turn as not covered, skipping batch persistence and checkpoint refresh for that turn; nothing is lost because the transcript retains the turn and the next successful reconcile imports it. -
#864
68ed199Thanks @jalehman! - Resolve ambiguous session-key runtime rollovers when the new transcript is provably fresh.When a runtime session rolls to a new sessionId while the key's conversation
still tracks an existing transcript file, the ambiguity guard froze the lane
entirely: no adoption, no rotation, no persistence — indefinitely. On a live
instance two agent main lanes ran frozen for a week, silently recording
nothing while their transcripts grew.bootstrap and afterTurn now attempt a tier-2 resolution using full-transcript
evidence: when the rolled transcript is provably fresh — every entry carries
a usable timestamp (message or envelope) postdating the conversation's last
persisted message, and no entry's identity overlaps the conversation's recent
persisted history — the rollover is a legitimate reset, so the old
conversation is archived (fully preserved and queryable) and the new session
binds and bootstraps normally. Freshness is judged on content+time evidence,
never transcript size, so lanes that ran frozen for days self-heal on their
first turn after upgrade. Anything short of proof (overlap, stale or missing
timestamps, no comparable persisted content) stays frozen exactly as before,
and a rotation that lands as a lifecycle no-op is reported honestly instead
of claiming the lane healed. assemble deliberately does NOT rotate: it only
sees the host's live window, which is not transcript evidence.Telemetry: "ambiguous rollover resolved by fresh-transcript rotation",
"ambiguous rollover not provably fresh (freshness=...)", and "rotation had
no effect" warn lines. -
#860
94f3431Thanks @jalehman! - Unfreeze anchored transcript reconcile when the backlog exceeds the import cap.A session whose transcript grew past the reconcile import cap (e.g. heavy
old-harness history) froze permanently: every pass logged "import cap
exceeded ... Aborting to prevent flood", imported nothing, and afterTurn
skipped persistence to avoid advancing the frontier — while the backlog kept
growing faster than the cap ever could. The live incident left a main-topic
conversation with 1,700+ unpersisted messages and silent data loss on every
turn.When the missing tail is anchored (lineage proven by an identity anchor in
this conversation), reconcile now imports a bounded oldest-first chunk per
pass instead of aborting: order is preserved, per-pass flood exposure stays
capped, the growing message count raises the cap, and repeated passes
converge until the backlog drains. The checkpoint/frontier still does not
advance while a pass is capped. No-anchor caps (unproven lineage, e.g.
path-mismatched epochs) still block entirely. Telemetry: the warn line is now
"import cap chunking ... importing N/M anchored backlog messages this pass".Extended after the entry-id reconciliation rework (#854): the same chunked
drain now applies to the entry-id anchored path — which carries 100% of
current-format traffic and had the same permanent-freeze shape — and to
fully id-bearing no-anchor epochs (declared rollovers), where every entry
adopts or imports by verified id, so a kept tail beyond the cap drains in
bounded passes instead of freezing before stale-id adoption can heal it.
After the first chunk persists ids, the entry-id anchor takes over on
subsequent passes. Id-less no-anchor batches still block entirely. -
#755
a5c3a8bThanks @rafaelreis-r! - MakeassertNoReplayTimestampFloodrole-aware so legitimate fast bursts of identicaltool/assistant/systemmessages from sub-agents are not misclassified as replay attacks. External user input keeps the aggregate role/timestamp replay budget, while internal runtime output is budgeted by exact message identity. The threshold is split into two configurable options:replayFloodThresholdExternal(default3, envLCM_REPLAY_FLOOD_THRESHOLD_EXTERNAL) — applies to replay-likerole=userrows, preserving legacy replay defense for third-partyly-rebroadcastable input.replayFloodThresholdInternal(default32, envLCM_REPLAY_FLOOD_THRESHOLD_INTERNAL) — applies torole=tool/assistant/system, absorbing legitimate same-second idempotent runtime output while still bounding pathological loops.
Fixes a class of false-positives that cascaded into
skipping compaction/ reconcile failures on cron and sub-agent workloads. Related to #639. -
#857
36100ceThanks @jalehman! - Bound assemble() output by serialized model-boundary token estimate.Live messages that carry structured tool payloads (e.g. transcripts imported
from a previous harness) were estimated by text blocks only, undercounting the
real prompt by 2-3x. assemble() could return a context the host's LLM-boundary
estimator rejected as far over budget, wedging the session in a
compact/overflow loop while every internal pressure check stayed green.Token estimates for live messages now serialize the full message structure
(with a fixed per-part substitution for embedded binary payloads), live
fallback paths return budget-bounded suffixes instead of the unbounded
transcript, and a final serialized-estimate clamp guarantees assembled output
never exceeds the token budget. Assemble telemetry now logs the serialized
estimate, the internal estimate, and clamp activity (serializedClamped=,
[lcm] assemble: serialized budget clamp,[lcm] assemble: bounded live fallback) for live monitoring. -
#742
13fe4f6Thanks @Yiaos! - Require OpenClaw 2026.5.28 so context-engine assembly can include host memory supplements. -
#809
d73e251Thanks @jwavro! - Sanitize duplicate assistant tool-use blocks during context assembly so replayed history cannot produce provider-invalid tool call payloads. -
#835
a5f7823Thanks @jalehman! - Deduplicate replayed checkpoint tool-result batches without dropping changed payloads or metadata. -
#866
7ed601fThanks @jalehman! - Judge append-only transcript imports by entry id, not content identity.A tool loop that re-issues a byte-identical tool call every iteration made
the append-only import guard declare each appended pair "already persisted"
(content identity matched the previous iteration), forcing a full transcript
re-read per tool call while the covered-frontier alignment refused every
runtime batch — reconcile churned on every iteration of the loop while the
model's context stopped advancing (live incident lossless-claw-3071).The guard now reasons in transcript entry ids: a fresh entry id is a new
entry regardless of content. Full reconciliation is still required for the
three cases that genuinely need it — an already-persisted entry id (replay),
a fresh id whose content matches an unstamped persisted row (flush-lag
catch-up that must adopt, not duplicate), and an entry reparenting onto a
non-tip persisted entry (host suffix rewrite needing stale-id re-stamping).
Parents unknown to the DB (pruned rows, replay-filtered entries) are treated
as genuine continuation since the append-only checkpoint already verified
the file prefix. -
#846
21577eaThanks @jalehman! - Skip synthetic OpenClaw heartbeat transcript rows during bootstrap/reconcile imports so heartbeat-only tails cannot trip replay-flood quarantine. -
#718
5b06bd0Thanks @jalehman! - Write lossless-claw operational logs to an independent daily JSONL log file beside OpenClaw's logs. -
#854
0837340Thanks @jalehman! - Follow the transcript's parentId leaf path during reconciliation and adopt re-issued entry ids onto rows stranded by host copy-on-write rewrites (rewriteTranscriptEntries, host tool-result truncation, gateway chat edits), so rewritten suffixes re-stamp in place instead of importing as content duplicates. -
#862
581ff80Thanks @mpz4life! - Stub oversized live tool results before assemble fallback paths send context to the model, so degraded assembly keeps compact file references instead of raw payloads. -
#823
fb96214Thanks @rafaelreis-r! - Fix the #639 Mode 2 deferred-compaction wedge: treat terminal compaction exhaustion as non-retryable instead of pinning the conversation in a permanent retry loop.When a threshold sweep takes no action and does not fail (no eligible leaf/condensed candidates remain) while the conversation is still over target, compaction can never make progress — it shrinks STORED leaves but cannot reduce the host's OBSERVED live tokens. Previously this returned
ok=false/reason="live context still exceeds target", so the deferred-debt drain kept the maintenance rowpending=1, climbedretry_attempts, opened summary-spend backoff, and thrashed the assemble degraded-fallback every turn.executeCompactionCorenow flags this terminal state asexhausted(while still returningok=falseso overflow recovery and #15 keep the honest still-over-target signal), andconsumeDeferredCompactionDebttreats an exhausted result as a completed no-op: it clears the debt (keepPending=false, no failure summary) instead of retrying forever. Emergency assemble drains still return bounded degraded live context for the current over-budget turn when exhaustion is discovered inline. Adds deterministic regressions that reproduce the wedge (matches the productionconversation_compaction_maintenance.last_failure_summary="live context still exceeds target").Addresses the deferred-compaction-loop half of #639 (the residual that #621/#681 did not cover). Based on @Grynn's exhaustion-handling proposal in the #639 thread.
-
#577
bb59318Thanks @100yenadmin! - Complete the thinking/reasoning half-fix from PR #503 in v0.9.3. #503 sanitized summarizer input atCompactionEngine.leafPass; this PR closes the two remaining gaps that were in scope:- Output side: when the summary provider response would persist a reasoning-shaped payload (text wrapped in
<think>…</think>/<thinking>…</thinking>/<reasoning>…</reasoning>, or opened with a[thinking]/[reasoning]label) as the summary body, log and treat the summary as empty so the existing envelope → retry → deterministic-fallback chain runs instead of silently storing reasoning text. Mitigates the silent-persist failure mode reported by #471 (vLLM+Qwen3) and #542 (Kimi K2.6). - Non-leaf passes:
extractMeaningfulMessageTextis now applied at every summarizer entry point —leafPass(already covered by #503), the condensed/merge pass that re-summarizes leaf summaries, and the prior-summary-context resolver. Summaries built from already-sanitized leaves can no longer reintroduce raw thinking/reasoning blocks at higher levels, including from legacy data persisted before #503.
Doctor remediation for legacy assistant rows that contain only thinking blocks (sub-fix F8 from the issue) is deferred — the existing doctor cleaner architecture operates on conversation-level deletion, not message-row remediation, and adding a backup-table pattern would significantly expand the surface area of this PR. Tracked separately.
- Output side: when the summary provider response would persist a reasoning-shaped payload (text wrapped in
-
#852
173fd21Thanks @jalehman! - Preserve context-engine lifecycle hooks when adding host memory supplements to assembled prompts. -
#881
974c850Thanks @christian-lallo! - Preserve original transcript timestamps during recovery imports instead of stamping import time. -
#840
bf1452fThanks @jalehman! - Skip append-only transcript reconciliation imports for heartbeat afterTurn calls and advance the checkpoint over the heartbeat delta. -
#854
0837340Thanks @jalehman! - Remove the runtime dependency on @earendil-works/pi-coding-agent: rotate and transcript-GC entry-id mapping now use the plugin's own read-only leaf-path parser instead of SessionManager.open, which migrated and rewrote v1/v2 or empty session files as a side effect and no longer tracks OpenClaw's in-tree transcript format. The pi packages remain as devDependencies for test fixtures. -
#865
c594352Thanks @jalehman! - Exclude template noise from the ambiguous-rollover freshness overlap check.The tier-2 rollover resolution false-blocked on the very lane it was built
for: a week-idle conversation whose entire recent history was synthetic
heartbeat traffic. Every session's transcript contains identical
"[OpenClaw heartbeat poll]" / "HEARTBEAT_OK" lines, so the identity-overlap
test matched 46 heartbeat rows and reported plausible lineage where there
was none.The overlap comparison now considers only lineage-discriminating content:
synthetic heartbeat traffic and content that recurs within the window are
excluded from both sides, and the window widens (50 -> 500) when the recent
history yields nothing comparable. When even the widened window is pure
template noise, the overlap test is acknowledged as no-signal and the
strict per-entry time gate decides alone — every new entry must still
postdate the conversation's last persisted message. Real unique-content
overlap still freezes the lane exactly as before. -
#842
ea54c70Thanks @jalehman! - Skip startup maintenance during OpenClaw runtime inspection and read-only plugin discovery. -
#858
b4c97ecThanks @jalehman! - Stop punishing progressing compaction with the summary spend backoff.A threshold sweep that hit its per-sweep wall-clock deadline while still
reducing tokens recorded "compacted but still over target" and opened a
30-minute spend backoff, which then blocked emergency drains and silently
no-opped user-initiated /compact. Recovery that was working was treated the
same as recovery that could never work.Threshold sweeps now chain within the operation-wide deadline
(compactUntilUnderDeadlineMs, capped bymaxSweepIterations) instead of
failing after one bounded sweep; the spend backoff only opens when a round
makes no further progress; and manual compaction clears an open backoff
(an explicit repair request is informed consent to spend). Telemetry: the
compact done line gainschainedSweeps=/spendBackoffOpened=, plus
"spend backoff skipped" and "manual request cleared summary spend backoff"
lines for live monitoring. -
#891
69ef486Thanks @jalehman! - Use the configured leaf or condensed target token budget when retrying empty compaction summaries, giving high-reasoning summary models enough headroom to emit text instead of falling back to deterministic truncation. -
#793
cbf992cThanks @100yenadmin! - Inherit the configured summary provider whenLCM_SUMMARY_MODELoverrides the
summary model without also settingLCM_SUMMARY_PROVIDER. -
#859
1394c10Thanks @jalehman! - Surface terminal compaction exhaustion as an explicit transcript-reset verdict.When a threshold sweep finds no eligible candidates while host-observed
pressure keeps the session over target, the host is rebuilding prompts from
live transcript state the engine cannot shrink — retrying stored compaction
can never converge. This terminal state previously reported the generic
"live context still exceeds target", which hosts answer with misleading
reserve-tuning advice. compact() now returns "stored compaction exhausted but
live context still exceeds target; transcript reset required" (alongside the
existing exhausted flag that clears deferred debt). The verdict requires an
explicit host-observed token count and never fires on budget-stopped sweeps,
so estimator gaps or interrupted sweeps cannot condemn recoverable sessions.
Telemetry: a "[lcm] compact: transcript wedge detected" warn line with
stored/observed/overhead counts. -
#851
c68286dThanks @steipete! - Update Pi runtime dependencies to 0.79.x for the current runtime API surface.
What's Changed
Features
- feat(config): add enableSummaryThinking to control reasoning during summarization [AI] by @mpz4life in #832
- Add independent Lossless log file by @jalehman in #718
Fixes
- fix: deduplicate replayed ingest batches by @100yenadmin in #826
- fix(anti-replay): role-aware threshold to absorb legitimate sub-agent bursts by @rafaelreis-r in #755
- fix(transcript-repair): dedupe duplicate assistant tool_use blocks at assemble time by @jwavro in #809
- fix: populate systemPromptAddition from host memory supplements in context engine assemble() by @Yiaos in #742
- fix(compaction): treat terminal compaction exhaustion as non-retryable (#639) by @rafaelreis-r in #823
- test: cover hot-cache threshold debt drain by @100yenadmin in #799
- fix: inherit summary provider for env model override by @100yenadmin in #793
- [reasoning] complete the half-fix from #503: output side + non-leaf + legacy data (E/8) by @100yenadmin in #577
New Contributors
- @rafaelreis-r made their first contribution in #755
- @mpz4life made their first contribution in #832
- @jwavro made their first contribution in #809
- @Yiaos made their first contribution in #742
- @steipete made their first contribution in #851
- @hubofvalley made their first contribution in #847
- @christian-lallo made their first contribution in #881
Full Changelog: v0.12.0...v0.13.0