Releases: Tyan66666/billion-context-dsh
Release list
v0.1.7
v0.1.7 — fix: compress absorbs stale seq refs instead of erroring
The bug
In long sessions, compress threw seq X..Y not in the current surface when the model reused seq refs from an older nudge table or an earlier compress result — an earlier compression had already shadowed those seqs (they leave the surface, but stay in the append-only log). Prompt-only guidance ("verify with acp_status") reduced but never eliminated it, because the old tables stay in the conversation history and re-compressing an already-compressed span looks like a legitimate action.
The fix (engine-side, not prompt-only)
src/region.ts—recoverStaleRange: a requested edge that was shadowed is remapped to the still-live content of the requested span (value-ordered, checkpoint nodes excluded); a fully shadowed span throwsAlreadyCompressedRangeErrorwith the covering block ids; invented/other-session seqs (not in the log) still fail, now naming the exact edge.src/tools.ts— three outcomes instead of a hard error:- fully stale span →
Compressed 0 block(s)+seqs X..Y already compressed (block …) — decompress to recover(no phantom block, no error); - partially stale span → compresses the live remainder, e.g.
seqs 3..10 were already shadowed — compressed the live remainder 6..10; - batch with mixed entries → fresh ranges land, stale ones are skip-warned.
- Block checkpoint nodes are never folded on a stale reference — tier-2/3 distillation stays an explicit act on a LIVE checkpoint seq.
- fully stale span →
- Guidance — system prompt, nudge range-table footer, and the compress tool description now explain staleness and recovery.
- Tests — 6 new regression tests (stale remap, already-compressed no-op, multi-block coverage, checkpoint non-folding, live-middle, mixed batch): 60 total, all green; typecheck + build clean.
Live verification (repro of the reported failure)
first compress: Compressed 1 block(s), ~5162 tokens reclaimed.
second compress (same stale seqs 1..5):
Compressed 0 block(s), ~0 tokens reclaimed.
seqs 1..5 already compressed (block 10d60086) — nothing to reclaim; decompress to recover the originals
partial-stale (seqs 3..10):
Compressed 1 block(s) | block 060e5fca: seqs 6..10, 5 messages shadowed
(seqs 3..10 were already shadowed — compressed the live remainder 6..10)
v0.1.6
What's new
- (feat) Auto-detect the model context window — when
modelContextLimitis omitted, the engine queries the live LLM runtime viaagent.ctx.llm.resolveModelInfo(provider, model)and uses the API-reportedcontextWindow(cached per provider+model). An explicitmodelContextLimitstill wins; failed/unavailable detection falls back to the default (128000).acp_statusnow reports the window source:context window: N (configured | auto-detected from <provider>/<model> | default (auto-detection unavailable)). - (feat) Engine nudge thresholds lowered to 0.70 / 0.85 — the engine defaults for
nudgeMaxContextLimitPct/nudgeEmergencyThresholdPctare now 0.70 / 0.85 (downstream of the kernel/pi defaults 0.75 / 0.95). Rationale: the host'scompaction-basicauto-compaction line sits at 80%, so the old 95% emergency line could never fire and left only a 5% margin before the window fills. The forced nudge now triggers before the host's 80% line. Explicit user values still win. - docs: clarify nudge trigger semantics — the growth path has no percentage floor (fires when some tier's pending tokens ≥ 50k AND usage grew ≥ 22.5k since the last check); 70% is the guaranteed over-limit line, 85% is the emergency line.
nudgeMinContextLimitPctis validation-only.
Live verification
- Auto-detection: with
modelContextLimitomitted,/acp statuson a restarted service reportedcontext window: 1000000 (auto-detected from deepseek-official/deepseek-v4-flash). - Threshold test (window forced to 30000, no explicit nudge thresholds → engine defaults):
/acp statusshowedestimated context: 18795 / 30000 (63%); after injecting ~60k chars of context, surface hit 100% and the pre-step hook injected the emergency nudge (⚠️ Context usage is at 100% of the window…+ compressible-range table) into the agent's next turn — the full chain config → window → threshold → inject works end to end.
Tests
54/54 passing (+3 regression tests: auto-window detection ×9 in tests/window.test.ts, engine threshold defaults/overrides in tests/config.test.ts, status window-source output), typecheck clean, build 47.24 KB.
v0.1.5
(feat) Tier-2/3 block distillation
Compressing a block's summary node now distills it into a higher tier (T1 → T2 → T3), using acp-kernel's native tier machinery:
- Block-boundary refs — compress accepts the checkpoint seq of an existing block; the kernel parses it as a block boundary and produces a tier-2/tier-3 block (consuming the parent block).
- Recursive decompress — decompressing a tier-2/3 block expands through the parent chain back to the original messages ("(tier N, distills k block(s))").
- Log rehydration — kernel blocks (tier, parent, kernel block id, original coverage) are rehydrated from the durable log on session load, so distillation keeps working across restarts.
- Tier-aware nudge/status — the nudge shows "Tier 2: N tier-1 block(s) distillable (X tokens)"; /acp status and compress output tag blocks [T2]/[T3].
- Skip warnings surfaced — overlapping/duplicate batch entries now report "Skipped range …" instead of silently dropping or double-writing.
Live verification (this session, real DSH GUI)
- T1→T2:
compress [219616..219616](checkpoint of a 103-message block) →block f2391cfa: … tier 2;decompress f2391cfa→ "(tier 2, distills 1 block(s))" + full originals. - T2→T3:
compress [251304..251304](f2391cfa's checkpoint) →block aee626a3: … tier 3;decompress aee626a3→ "(tier 3, distills 1 block(s))" + two-level recursive expansion. - Batch multi-segment compress re-verified (
Compressed 2 block(s)).
Tests
42/42 passing (8 new: tier-2 distillation, tier-3 chaining, overlapping-batch skip warning, mixed message↔block boundary, kernel-block rehydration, rehydration-survives-restart, tier-2 nudge recommendation, nudge rendering). typecheck clean; build 44.06 KB.
v0.1.4
v0.1.4 — multi-segment batch compression guidance
(feat) guide the model toward batching multiple disjoint compress ranges in one call.
Changes
- nudge range table header/tail now teach multi-entry
compress: "content is an array: batch multiple unrelated segments in one call, each entry its own block. Keep ranges disjoint." - system prompt compress bullet: batch example + "Keep ranges disjoint — overlapping entries in one batch are skipped."
compresstool description: "(each content entry becomes its own block); keep ranges disjoint."- regression test: M3 — compress accepts multiple disjoint ranges in one call, each its own block (suite now 34 tests, all green; typecheck + build clean)
Live verification (real DSH web session, 128K window, v0.1.4 mounted in ~/.dsh profile)
- one
compresscall with two entries (seqs 11..49838 + 50960..51529) →Compressed 2 block(s), ~54335 tokens reclaimed (ledger reports 50330) - surface 157 → 16 nodes; estimated context ~75% → 6% (8060/128000)
- both summaries visible in
acp_statusledger;decompressrecovers the shadowed originals read-only
v0.1.3
What's new
compress / resolveSurfaceRange errors now guide the model (commit 958a5b8)
When a range edge is not a real surface node — e.g. seq 9..1500 in a fresh session where surface seqs are sparse — the error now explains why and points the model at acp_status:
billion-context-dsh: seq 999999..999999 not in the current surface — surface seqs
are sparse message nodes (only user/message, assistant/message, tool/result
events); consult acp_status for the current surface range
In acceptance testing (session bb0005ca) the model guessed wrong boundaries twice, read source, self-corrected, and compressed the real range in one shot. This release removes the need to dig into source: the error itself is the hint.
Verified live
- acp_status online after restart: 6 blocks, 294691 tokens compressed, surface 47 nodes seqs 79280..345792
- Invalid-range compress probe returned the new guided error verbatim
- 33/33 tests green, typecheck clean, build OK
v0.1.2
What changed
Fix: compress failed with no assigned ref when a range edge landed on a multi-tool-call assistant message.
Multi-tool-call assistant messages project to ${seq}#${callId} CoreMessage ids, so they carry no bare-${seq} ref — compress could never resolve them as range edges. This hit live sessions exactly where the nudge range table placed a multi-call message on a boundary.
Fix (two planes)
region.ts—resolveSurfaceRangenow requires a boundary to be both tool-pairing-balanced and plain-ref (hasPlainRef). Multi-call edges shift inward to the nearest clean cut (shrink-first), or expand outward to the enclosing clean pair when shrinking collapses.tools.ts—handleCompressbalances edges throughresolveSurfaceRangebefore resolving refs.
Live verification (closed loop)
compress 106754..143714failed before the fix (no assigned ref)compress 106756..143714→ block99597a3a, 80 messages, ~24,567 tokens reclaimed;search_context+acp_statusconfirm the durable loop- Session ledger: 4 blocks, ~183K tokens compressed total
Tests
32/32 green (typecheck + build clean): new appendMultiToolCall fixture, region-level boundary tests, end-to-end compress tests (lone multi-call boundary expands to its round; multi-call messages inside a clean range shadow normally; nudge range-table edges compress successfully).
Commits: ea24a7e (fix) · 24959ab (release v0.1.2)
v0.1.1
Bug-fix release from real long-session testing (the Rectangle project acp session) — the full ACP loop now works end-to-end at scale: compress 583 messages / ~139K tokens, context 129% → 20%, ledger reports real reclaimed tokens.
Fixes
- Ledger recorded 0 reclaimed tokens — compress and /acp compress now estimate tokens from the actually shadowed messages (was hardcoded 0).
- Nudge showed absurd usage (230%) — switched from token-meter
totalTokens(request+response pressure) tosurfaceTokens; displayed percentage capped at 100. - Flat 4-char/token estimation — moved to acp-kernel's CJK-aware
defaultCountTokens(1 char/token for CJK, 4 for ASCII), matching billion-context-pi. - Lone tool results could not be compressed — resolveSurfaceRange now expands outward to the smallest balanced tool-call/result pair when the inward pass collapses.
- Large tool results hidden from the nudge range table — kernel ref map drifts after surface replacements in long sessions; the range table is now computed from the surface directly (skips protected tail + checkpoint nodes), so big consumed outputs always surface.
- Legacy blocks backfilled — ledger entries written as 0 are re-estimated from the shadowed originals still in the log.
Verified live
acp_status: blocks: 16 | tokens compressed: 145305 | estimated context: 26165 / 128000 (20%) after a 583-message compress (seqs 64757..265056, ~139200 tokens reclaimed).
28 tests pass; typecheck and build clean. npm: billion-context-dsh@0.1.1
v0.1.0
Model-driven context management (Active Context Pruning / ACP) for the DeepSeek Harness — the model decides when and what to compress.
What it is
A drop-in CompactionEngine backend ported from billion-context-pi: the compress tool durably replaces a surface range with the model-written summary, with decompress / search_context / acp_status tools, a /acp command, and pressure-driven advisory nudges. The compression core (acp-kernel) is reused verbatim.
Install
npm install billion-context-dshThen add one composition row (name: 'billion-context-dsh') — see README.
Highlights
- Model-driven: automatic policy never summarizes, only nudges (advisory, never imperative)
- Durable surface transaction: originals stay in the append-only session log, decompress/search work from the log
- Seq-based refs with auto-balanced range edges and `#callId` tolerance
- Configurable nudge thresholds (defaults match billion-context-pi / acp-kernel)
- 25 tests, strict TS, self-contained 35KB bundle (acp-kernel inlined)
Upstream
MIT — built on billion-context-pi, acp-kernel, and opencode-acp by ranxianglei; host platform DeepSeek Harness by DeepSeek AI.