CHANGELOG.md
CHANGELOG.md
All revisions published to the LM Studio Hub as rootmonster/context-compressor.
Published as attitudejacket/context-compressor, a fork of 0.8.3 (Hub revision 27). Changes, each described in NOTICE.md:
(Hub revision assigned at push.) Two findings from the first 0.8.2 session with a web-search tools plugin (qwen3.8-27b at a 14,848-token window).
(Hub revision assigned at push.) Reported by a user running this plugin together with a web-search tools plugin: the search tool's "Searchingβ¦" status disappeared from the chat once compression was on.
act() hands each tool a context whose
status()/warn() only reach the plugin log, so a tools provider's
progress text never reached the chat. Each tool call now gets a per-call
tool status () that mirrors the tool's status and
warnings and ends as succeeded (with timing) or failed β the same widget
LM Studio's native loop shows. A status that cannot be created or
updated (prediction already torn down) never affects the tool call.
Pure bridge in with tests.(Hub revision assigned at push.) Findings from the first live runs of
this plugin together with Agentic Workspace in one LM Studio 0.4.15
chat (qwen3.8-27b, 16k context, plan proposed β /compress β /accept,
then a Manual-mode approval carried through compaction).
(Hub revision assigned at push.)
npm run bench:static (model-free) and
(against the local server); results committed in
bench/results/./compact runs can no longer render an empty reply:
the report block is created before the work starts and upgraded in place./no_think), cutting compaction wall time substantially.(N cached chunks reused), cancel
message notes saved progress); empty assistant messages from canceled
replies are filtered from the model's view.contextOverflowPolicy: stopAtLimit everywhere β the engine's own
truncation could drop the only user message and crash strict templates./usage report the rendered
count.ToolCallContext.callId./compressΒ·/compactΒ·/usage commands, get_context_usage tool,
task-aware summary schema, tool passthrough with result capping, live
progress bar and prompt meter, template compatibility fixes (single
system message, synthetic user message), tool-error feedback, empty-reply
fallbacks, crash-proof top-level guard.All revisions published to the LM Studio Hub as rootmonster/context-compressor.
Published as attitudejacket/context-compressor, a fork of 0.8.3 (Hub revision 27). Changes, each described in NOTICE.md:
(Hub revision assigned at push.) Two findings from the first 0.8.2 session with a web-search tools plugin (qwen3.8-27b at a 14,848-token window).
(Hub revision assigned at push.) Reported by a user running this plugin together with a web-search tools plugin: the search tool's "Searchingβ¦" status disappeared from the chat once compression was on.
act() hands each tool a context whose
status()/warn() only reach the plugin log, so a tools provider's
progress text never reached the chat. Each tool call now gets a per-call
tool status () that mirrors the tool's status and
warnings and ends as succeeded (with timing) or failed β the same widget
LM Studio's native loop shows. A status that cannot be created or
updated (prediction already torn down) never affects the tool call.
Pure bridge in with tests.(Hub revision assigned at push.) Findings from the first live runs of
this plugin together with Agentic Workspace in one LM Studio 0.4.15
chat (qwen3.8-27b, 16k context, plan proposed β /compress β /accept,
then a Manual-mode approval carried through compaction).
(Hub revision assigned at push.)
npm run bench:static (model-free) and
(against the local server); results committed in
bench/results/./compact runs can no longer render an empty reply:
the report block is created before the work starts and upgraded in place./no_think), cutting compaction wall time substantially.(N cached chunks reused), cancel
message notes saved progress); empty assistant messages from canceled
replies are filtered from the model's view.contextOverflowPolicy: stopAtLimit everywhere β the engine's own
truncation could drop the only user message and crash strict templates./usage report the rendered
count.ToolCallContext.callId./compressΒ·/compactΒ·/usage commands, get_context_usage tool,
task-aware summary schema, tool passthrough with result capping, live
progress bar and prompt meter, template compatibility fixes (single
system message, synthetic user message), tool-error feedback, empty-reply
fallbacks, crash-proof top-level guard.<tool_call> XML in the chat. The SDK's act() runs the
final round of a maxPredictionRounds-capped pass with tools DISABLED so
the model is forced to answer; a model still mid-task then writes its tool
call as plain text in its native syntax, which streamed into the chat and
was never executed β the next pass repeated the call for real, so every
capped pass paid a wasted, visible round. Passes are now ended by this
plugin (src/passCap.ts): act() gets no round limit and is aborted at the
end of the last allowed round, after that round's tool calls and results
were emitted, so no tool-less round is ever generated. A capped pass also
no longer runs the empty-reply fallback (it would have promoted the
round's reasoning as visible text).estimateMessageTokens, with a 10% cushion) against the exact
prompt the pass started from, the tool definitions, and the reply reserve
(nextRoundFits); if the next request would not fit, the pass ends there
and mid-task compaction runs before the engine is asked.ctl.createToolStatussrc/toolStatus.tsactivePredictionLoopHandlerWarning banner LM Studio shows in the
model loader is LM Studio's own notice that a plugin owns the prediction
loop; it is expected while this plugin is enabled and not an error.burnedOutInReasoning),
consolidation is now disabled for that model for the session with one
status that says so; summaries stay unmerged and everything else works.<reasoning>__LM_STUDIO_INTERNAL_LSEP_SYNTHETIC_REASONING_END_<hex>__<content>
(one pair per tool round) and hands that whole string back as history. The
compressor forwarded it verbatim, so the model was re-sent its own reasoning
as content and started echoing the marker at the top of its replies. The
adapter now keeps only the text after the last marker (stripSyntheticReasoning,
also folded into stripReasoningMarkers), and the empty-reply fallback's
marker scrub no longer swallows the word that follows a marker. Cached
summaries of affected chats regenerate on their own: the cleaned text hashes
differently./compress announcement
names the raised floor, and /usage (and the get_context_usage tool)
report the configured and effective floor. A β error remains for the one
case that is a problem: a raised floor at or above the trigger, which would
compact on every turn β the message now says what to change.16,557 tokens exceeds the available context size (16,384)). Three causes, three changes: (1) effectiveSizes
now takes the previous turn's measured tool-schema size as a fixed overhead
and clamps every fraction β kept-verbatim, chunk, reserved output, summary
size β against the usable window, as do the auto-compact trigger and the
compact-down-to floor (the floor notice names that window); (2) the safety
pass that runs when the rendered prompt plus tool definitions exceed the
safe budget used to stop as soon as everything older than the verbatim tail
was summarized β it now gives up the tail down to the start of the current
turn (fitCuts, never past currentTurnStart) and may run five rounds
instead of three; (3) the safe budget the fit check enforces now reserves
a whole pass of tool rounds on top of the reply β replies plus capped tool
results, at most a quarter of the window β not just the 2,000 tokens of
one reply, because the retry of that same turn fit by 84 tokens and then
grew by ~3,000 over two reads and an apply before any check could run
(engine 400 again, 17,554). To make that reserve small enough to matter,
one tool result is capped at 15% of the usable window, a pass is at most
one round per 6k of usable window (a check before every round on a 16k
model with a full tools provider), the floor margin and the largest
consolidated summary shrink to 10% / 15% of a small usable window, and the
auto-compact trigger can never sit above what the fit check will accept.
The same fit check now also runs between passes, over the history the pass
just grew β previously the between-pass path only ran the ordinary
trigger-based compaction and went straight into the next pass. The new
caps are listed in the one-time "auto-scaled" notice when they bind. On a
16k model with a full tools provider the honest outcome is still tight
(about 11k usable), and /usage shows the numbers to decide on a larger
context.status: "pending_approval" with the
staged approval record; protocol-aware compaction now keeps that status, the
permission mode, and the whole approval object β the approval_* id,
kind, title, destructive flag, and resume.tool plus every entry of
resume.arguments β so the model can re-issue the exact call after the user
types /accept, even when the chat compacted in between. Previously the
entire approval object was dropped and only the summary/facts survived.plan_* / approval_* identifiers are recognised alongside the existing
tx_/run_/job_/todo_/research_/source_ families: plan status,
revision, supersedes, planPath, the propose instruction, the
approvals/plans recovery arrays of workspace_inspect { action: "changes" }, and the permissionMode / activePlan / pendingPlan /
pendingApprovals fields of capabilities and overview are preserved.
Fields the workspace names in retention.omit_when_summarizing (for example
data.markdown, data.plan.steps) are still dropped as instructed.resume call is now kept byte for byte β no key filter, no
string shortening β so a 30-action workspace_browser batch or 2 KB of
workspace_command stdin re-issues exactly as staged and still matches the
approval's operation hash. Past 12 000 characters of arguments the whole
resume is dropped rather than truncated, with a marker naming the recovery
route; a partial call is never emitted.recent* collections of
overview (every tx_/run_/todo_/research_ id in them used to be
dropped), research.deep's project/agentRun wrappers, a transaction's
created directories, an approved plan's files[].change / risks /
questions, a sub-agent run's commitEdits / allowCommands / allowWeb
and pass budget, the capabilities feature flags and notes.memoryPath, the
counters that say how much was hidden (truncated, linksOmitted,
stdoutBytes, *Chars/*Entries/*Lines), and name / type /
modifiedAt.retention.omit_when_summarizing is honoured on the overflow path too: the
last-resort recovery-field list is built from the omission-stripped copy
(it previously re-read the original payload, putting declared-omittable
content back when the cap was small) and is bounded by the same cap.approval_/plan_ ids and asks for the
exact call to re-issue once the user accepts.jobs index of workspace_inspect { action: "changes" } is preserved entry for entry (job_* id, status, executable,
exit code, start time) β it is the only route back to a job id after a
compaction, and a full twenty-job listing now survives whole rather than
losing its newest entry to the generic array cap. The gate's web kind
(research egress) is covered alongside the transaction and browser kinds.<think>, [THINK], βthinkβ·, <reasoning>,
gpt-oss Harmony channels), including unterminated blocks; the engine's
parsed non-reasoning content is preferred when available, so a summarizer
reply that truncated mid-reasoning can no longer be cached as a chunk
summary (previously it was cached permanently). /no_think now targets
qwen3/QwQ/SmolLM3 (/no_think) and GLM (/nothink) instead of any
identifier containing "qwen" β qwen2.5 no longer receives a stray suffix.
Assistant reasoning in history is stripped before summarization.summarizerPromptMode setting (auto/full/compact) lets the shorter
compact variant be forced by hand; bench evidence (2026-08-24, two
models) showed the full 10-section prompt beating compact on recall at
both 3B and 18B, so auto now always uses full β compact remains
available as a manual override and as the automatic rescue prompt when
a model's reasoning runs away mid-summary./usage numbers and savings
percentages are accurate), and CJK/base64-heavy tool results are
truncated by a character-class-aware estimate instead of a flat
4-chars-per-token rule (previously up to ~4x under-truncation).agentic-workspace/v1 object and JSON-string tool results. Before generic
head/tail truncation it removes payloads named by
retention.omit_when_summarizing while preserving summaries, retention
facts, artifacts, IDs, statuses, hashes, URLs, paths, counts, evidence,
next actions, task text/dependencies, and command verification fields.npm run bench<tool_call> XML in the chat. The SDK's act() runs the
final round of a maxPredictionRounds-capped pass with tools DISABLED so
the model is forced to answer; a model still mid-task then writes its tool
call as plain text in its native syntax, which streamed into the chat and
was never executed β the next pass repeated the call for real, so every
capped pass paid a wasted, visible round. Passes are now ended by this
plugin (src/passCap.ts): act() gets no round limit and is aborted at the
end of the last allowed round, after that round's tool calls and results
were emitted, so no tool-less round is ever generated. A capped pass also
no longer runs the empty-reply fallback (it would have promoted the
round's reasoning as visible text).estimateMessageTokens, with a 10% cushion) against the exact
prompt the pass started from, the tool definitions, and the reply reserve
(nextRoundFits); if the next request would not fit, the pass ends there
and mid-task compaction runs before the engine is asked.ctl.createToolStatussrc/toolStatus.tsactivePredictionLoopHandlerWarning banner LM Studio shows in the
model loader is LM Studio's own notice that a plugin owns the prediction
loop; it is expected while this plugin is enabled and not an error.burnedOutInReasoning),
consolidation is now disabled for that model for the session with one
status that says so; summaries stay unmerged and everything else works.<reasoning>__LM_STUDIO_INTERNAL_LSEP_SYNTHETIC_REASONING_END_<hex>__<content>
(one pair per tool round) and hands that whole string back as history. The
compressor forwarded it verbatim, so the model was re-sent its own reasoning
as content and started echoing the marker at the top of its replies. The
adapter now keeps only the text after the last marker (stripSyntheticReasoning,
also folded into stripReasoningMarkers), and the empty-reply fallback's
marker scrub no longer swallows the word that follows a marker. Cached
summaries of affected chats regenerate on their own: the cleaned text hashes
differently./compress announcement
names the raised floor, and /usage (and the get_context_usage tool)
report the configured and effective floor. A β error remains for the one
case that is a problem: a raised floor at or above the trigger, which would
compact on every turn β the message now says what to change.16,557 tokens exceeds the available context size (16,384)). Three causes, three changes: (1) effectiveSizes
now takes the previous turn's measured tool-schema size as a fixed overhead
and clamps every fraction β kept-verbatim, chunk, reserved output, summary
size β against the usable window, as do the auto-compact trigger and the
compact-down-to floor (the floor notice names that window); (2) the safety
pass that runs when the rendered prompt plus tool definitions exceed the
safe budget used to stop as soon as everything older than the verbatim tail
was summarized β it now gives up the tail down to the start of the current
turn (fitCuts, never past currentTurnStart) and may run five rounds
instead of three; (3) the safe budget the fit check enforces now reserves
a whole pass of tool rounds on top of the reply β replies plus capped tool
results, at most a quarter of the window β not just the 2,000 tokens of
one reply, because the retry of that same turn fit by 84 tokens and then
grew by ~3,000 over two reads and an apply before any check could run
(engine 400 again, 17,554). To make that reserve small enough to matter,
one tool result is capped at 15% of the usable window, a pass is at most
one round per 6k of usable window (a check before every round on a 16k
model with a full tools provider), the floor margin and the largest
consolidated summary shrink to 10% / 15% of a small usable window, and the
auto-compact trigger can never sit above what the fit check will accept.
The same fit check now also runs between passes, over the history the pass
just grew β previously the between-pass path only ran the ordinary
trigger-based compaction and went straight into the next pass. The new
caps are listed in the one-time "auto-scaled" notice when they bind. On a
16k model with a full tools provider the honest outcome is still tight
(about 11k usable), and /usage shows the numbers to decide on a larger
context.status: "pending_approval" with the
staged approval record; protocol-aware compaction now keeps that status, the
permission mode, and the whole approval object β the approval_* id,
kind, title, destructive flag, and resume.tool plus every entry of
resume.arguments β so the model can re-issue the exact call after the user
types /accept, even when the chat compacted in between. Previously the
entire approval object was dropped and only the summary/facts survived.plan_* / approval_* identifiers are recognised alongside the existing
tx_/run_/job_/todo_/research_/source_ families: plan status,
revision, supersedes, planPath, the propose instruction, the
approvals/plans recovery arrays of workspace_inspect { action: "changes" }, and the permissionMode / activePlan / pendingPlan /
pendingApprovals fields of capabilities and overview are preserved.
Fields the workspace names in retention.omit_when_summarizing (for example
data.markdown, data.plan.steps) are still dropped as instructed.resume call is now kept byte for byte β no key filter, no
string shortening β so a 30-action workspace_browser batch or 2 KB of
workspace_command stdin re-issues exactly as staged and still matches the
approval's operation hash. Past 12 000 characters of arguments the whole
resume is dropped rather than truncated, with a marker naming the recovery
route; a partial call is never emitted.recent* collections of
overview (every tx_/run_/todo_/research_ id in them used to be
dropped), research.deep's project/agentRun wrappers, a transaction's
created directories, an approved plan's files[].change / risks /
questions, a sub-agent run's commitEdits / allowCommands / allowWeb
and pass budget, the capabilities feature flags and notes.memoryPath, the
counters that say how much was hidden (truncated, linksOmitted,
stdoutBytes, *Chars/*Entries/*Lines), and name / type /
modifiedAt.retention.omit_when_summarizing is honoured on the overflow path too: the
last-resort recovery-field list is built from the omission-stripped copy
(it previously re-read the original payload, putting declared-omittable
content back when the cap was small) and is bounded by the same cap.approval_/plan_ ids and asks for the
exact call to re-issue once the user accepts.jobs index of workspace_inspect { action: "changes" } is preserved entry for entry (job_* id, status, executable,
exit code, start time) β it is the only route back to a job id after a
compaction, and a full twenty-job listing now survives whole rather than
losing its newest entry to the generic array cap. The gate's web kind
(research egress) is covered alongside the transaction and browser kinds.<think>, [THINK], βthinkβ·, <reasoning>,
gpt-oss Harmony channels), including unterminated blocks; the engine's
parsed non-reasoning content is preferred when available, so a summarizer
reply that truncated mid-reasoning can no longer be cached as a chunk
summary (previously it was cached permanently). /no_think now targets
qwen3/QwQ/SmolLM3 (/no_think) and GLM (/nothink) instead of any
identifier containing "qwen" β qwen2.5 no longer receives a stray suffix.
Assistant reasoning in history is stripped before summarization.summarizerPromptMode setting (auto/full/compact) lets the shorter
compact variant be forced by hand; bench evidence (2026-08-24, two
models) showed the full 10-section prompt beating compact on recall at
both 3B and 18B, so auto now always uses full β compact remains
available as a manual override and as the automatic rescue prompt when
a model's reasoning runs away mid-summary./usage numbers and savings
percentages are accurate), and CJK/base64-heavy tool results are
truncated by a character-class-aware estimate instead of a flat
4-chars-per-token rule (previously up to ~4x under-truncation).agentic-workspace/v1 object and JSON-string tool results. Before generic
head/tail truncation it removes payloads named by
retention.omit_when_summarizing while preserving summaries, retention
facts, artifacts, IDs, statuses, hashes, URLs, paths, counts, evidence,
next actions, task text/dependencies, and command verification fields.npm run bench