@crazx/dsh-compaction-basic 0.1.1-rc.2.zw.1 → 0.1.2-alpha.3.zw.2

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.i18n.yaml CHANGED
@@ -2,5 +2,5 @@
2
2
  # side as of the last confirmed-consistent state. Both languages carry equal authority;
3
3
  # after editing either side, bring the other along and re-record with:
4
4
  # pnpm run verify-translation-pairing --write packages/compaction/compaction-basic/README.md
5
- README.md: 9dc6ba5e77856d762b002fcc4606a45d82fbd324
6
- README.zh.md: 16f95f140f8d08896bacbe9d498a5167c9143a80
5
+ README.md: 2086394781f7e4db0791a7f7b519be51ab53e6e2
6
+ README.zh.md: d818f4a3f05e59ed39e32ccb76c89779eb7f22e4
package/README.md CHANGED
@@ -1,78 +1,49 @@
1
+ ---
2
+ description: "Automatic conversation condensation for deployments choosing, tuning, or debugging how older history is summarized as token pressure builds."
3
+ kind: "package-reference"
4
+ ---
5
+
1
6
  # @deepseek-ai/dsh-compaction-basic
2
7
 
3
8
  English | [中文](README.zh.md)
4
9
 
5
- The **basic compaction backend**: a `BasicCompactionEngine` implementing the `@deepseek-ai/dsh-compaction` Service Definition with reusable `ctx.tokenMeter` pressure, token-budget retention, and bounded summarization. Inputs that fit use the direct one-shot `ctx.llm.stream()` path and replay the conversation prefix for provider KV-cache reuse; oversized or Provider-rejected inputs automatically use chronological map-reduce calls (all interceptable at `llm/stream`).
10
+ ## Summary
6
11
 
7
- This package owns the Service Provider role of the compaction capability see the [Service Definition package](../compaction/README.md) for its contract and the [capability-seam Agent Note](../../../.agents/notes/implemented/feature/2026-06-18-compaction-capability-seam.md) for the design.
12
+ `dsh-compaction-basic` keeps long agent conversations working near the model's context limit. As token pressure builds, it automatically condenses the oldest part of the conversation into a summary and keeps the recent part intact; after a context-overflow error it condenses and retries. You can also condense on demand with `/compact` from `dsh-command-compact`, and mount `dsh-compaction-tool-result-pruner` to trim oversized tool outputs first. Condensation costs one extra model request that reads the selected history and writes the summary; only the summary text is kept. It condenses derived history only — it cannot shrink the system prompt, tools, or session prefix, and one indivisible unit such as a single huge tool call cannot be split.
8
13
 
9
- ## What it owns
14
+ ## Table of Contents
10
15
 
11
- This backend owns the compaction policy:
16
+ - [Use this package](#use-this-package)
17
+ - [Understand the implementation](#understand-the-implementation)
18
+ - [Further Exploration](#further-exploration)
19
+ - [Model Experience](#model-experience)
20
+ - [Known Limitations and Deferred Work](#known-limitations-and-deferred-work)
21
+ - [Dev Note](#dev-note)
12
22
 
13
- - **Measurement** — the singleton `ctx.tokenMeter` prices the latest canonical logged envelope and current surface at one consumed-log revision. Step-boundary pressure therefore includes the actual system prompt, tools, routing, assistant completion, tool results, buffered context, and steering.
14
- - **Routed policy** — proactive pressure resolves capacity from the adapter that owns the latest durable provider/model route, then scales the default policy plus an optional exact-target override into concrete token budgets. Model discovery remains advisory and is not consulted.
15
- - **Model-free pruning** — after pressure or canonical overflow qualifies, the optional [`ctx.toolResultPruner`](../compaction-tool-result-pruner/README.md) service rewrites oversized tool results before range selection. Compact-basic remeasures through `ctx.tokenMeter`, skips summarization when pressure becomes safe, and otherwise summarizes the pruned surface. Below-pressure step checks never prune.
16
- - **Retention** — compact the oldest whole surface units while preserving a recent tail and balanced tool-call/result cuts through the [`dsh-compaction` boundary helpers](../compaction/README.md#tool-pairing-boundaries). Turn boundaries do not protect old steps inside a runaway turn. An open indivisible tail declines until it closes. The optional pruner can repair an oversized closed tool unit when its text-bearing result is the removable bulk; indivisible non-tool units and non-prunable tool remainders remain out of scope.
17
- - **Convergence** — retry head-checkpoint compaction up to `compactionRetries`; reject a summary that does not shrink its source, and throw if retries cannot return below threshold.
18
- - **Summarization** — the configured provider/model pair, falling back to the latest logged request target and then the agent target, handles every auxiliary `llm/stream` call without running the loop-only `agent/request` extension point. When estimated input plus `maxTokens` fits the declared summary-model window, the unchanged one-shot path replays the conversation's system prompt, tools, and shadowed messages verbatim and appends the compaction instruction, preserving warm-prefix KV-cache reuse. If that envelope cannot fit, or the Provider returns canonical `CONTEXT_WINDOW_EXCEEDED`, compaction maps bounded chronological, tool-balanced spans and recursively reduces structured partial checkpoints. Provider-confirmed overflow bisects only the failed span and retains successful siblings. Hierarchy stages set `purpose: 'compaction'`, omit tool schemas by default, reject truncated, visual, or structurally incomplete output, and make no durable replacement until the complete hierarchy succeeds.
19
- - **Summary provenance** — a successful one-shot keeps `llmStreamCall: true` and its reported usage. A single successful hierarchy stage may also carry that marker; multi-call or failed-attempt recovery leaves it unset. Multi-stage usage is summed only when every successful call reports usage and no failed model attempt occurred, preventing partial accounting from appearing complete.
20
- - **Framing** — the replacement user message marks established checkpoint context with `<compacted-summary>` tags. The raw summary remains on the `compaction/summary` event, and later automatic cycles merge the prior checkpoint.
21
- - **Lifecycle** — all entry points share one bracket-first region transaction. It validates the range and live lock, appends `compaction/start` synchronously, prepares and awaits the summary, revalidates, appends `compaction/summary` plus the replacement, and makes exactly one closing attempt. Automatic and explicit-region calls require a numeric open-turn owner and whole-surface stability; the serial `agent/pre-step` listener checks pressure before request derivation, while canonical provider overflow enters through `agent/request-error` and authorizes retry only after durable surface progress. `compactNow()` reserves idle admission, uses `turn: null`, accepts append-only context outside its selected span, flushes every closed attempt, and releases admission in `finally`.
22
- - **Overflow recovery** — provider-confirmed overflow needs no capacity metadata: it bypasses normal pressure and retention, prunes, then attempts one maximal balanced head reduction while leaving the newest indivisible unit. Retry is authorized whenever `surface.replaceGeneration` advances, including when pruning lands before later summary work throws. No replacement, an exhausted target-specific cap, cancellation, or an unknown/noncanonical error preserves the original provider failure.
23
- - **Failure handling** — a live unmatched `compaction/start` is the durable lock. An unmatched marker before a newer `session/end-seed` is stale evidence from a prior lifecycle and does not block; one after that boundary reports `busy`. Summary and changed-span failures close with an error and leave the conversation surface untouched, though the attempt remains in the log. A failed close deliberately leaves a blocking orphan. Operational pressure failures warn and continue, while overflow-recovery failure preserves the original provider error only when no earlier replacement advanced the surface. Cancellation remains authoritative after cleanup and durability.
23
+ -----
24
24
 
25
- The protected `summarize()` method is the sole subclass hook. A template- or remote-summarizer subclass can override it while pressure, retention, cited source events, shrink validation, and shadowed-token accounting stay on `ctx.tokenMeter`. The hook returns the safe summary plus the complete provider output, call envelope, and usage when available (`{ summary, rawOutput?, llmStreamCall?, provider, model, maxTokens?, usage? }`); `llmStreamCall: true` means producing that result consumed exactly one call through this context's `ctx.llm.stream()` and requires complete `rawOutput`, while unmarked `rawOutput` does not identify the call path. The transaction preserves those fields on `compaction/summary`.
25
+ <a id="use-this-package"></a>
26
+ ## Use this package
26
27
 
27
- ## Config (`BasicCompactionConfig`)
28
+ Mount this package to get automatic conversation condensation in a composition that already provides an LLM, session storage, and token measurement. The shipped `dsh` base enables it by default; mount it explicitly to control when condensation starts.
28
29
 
29
- Every setting is optional. Top-level policy fields are defaults for every routed model; `modelPolicies` applies partial overrides to exact provider/model pairs. At pressure time, compaction-basic asks the owning LLM adapter for that route's context capacity and resolves absolute budgets. Unrecognized keys, duplicate targets, mutually exclusive retention forms, and a merged `retainRatio` that is not below `thresholdRatio` fail plugin load. An absolute `retainTokens` budget that is not below its scaled threshold fails on the first resolvable target because that comparison requires model capacity.
30
+ ### What you get
30
31
 
31
- | Key | Required | Meaning |
32
- |---|---|---|
33
- | `thresholdRatio` | no (default `0.8`) | Compact at `floor(routedContextWindow × ratio)`. |
34
- | `retainRatio` | no (default `0.16`) | Recent surface budget kept verbatim as a fraction of the routed context window; mutually exclusive with `retainTokens`. |
35
- | `retainTokens` | no | Absolute recent surface budget kept verbatim; mutually exclusive with `retainRatio` and must be below the resolved threshold. |
36
- | `summarizationProvider` | no (default `''`) | Set together with `summarizationModel`; an empty pair resolves the latest logged request target, then the `AgentOptions` pair. |
37
- | `summarizationModel` | no (default `''`) | Set together with `summarizationProvider`; an empty pair resolves the latest logged request target, then the `AgentOptions` pair. |
38
- | `maxTokens` | no (default `8192`) | Provider generation cap for the summarization call; may include reasoning tokens. |
39
- | `compactionRetries` | no (default `1`) | Extra attempts after the first when pressure remains above threshold. |
40
- | `maxOverflowRetries` | no (default `1`) | Maximum retries after canonical context-window overflow; `0` disables recovery only. |
41
- | `chunkInputRatio` | no (default `0.6`) | Fraction of the summary model window available to each hierarchy stage input; valid range `[0.1, 0.9]`. |
42
- | `mapMaxTokens` | no (default `4096`) | Provider generation cap for one hierarchy map call. |
43
- | `reduceMaxTokens` | no (default `8192`) | Provider generation cap for one hierarchy reduce call. |
44
- | `maxDepth` | no (default `4`) | Maximum recursive reduce rounds; valid range `1..8`. |
45
- | `replayTools` | no (default `false`) | Replay tool schemas in hierarchy stages. Enabling this may be required by strict Providers but consumes chunk input and reduces prefix reuse. |
46
- | `modelPolicies` | no (default `[]`) | Exact `{ provider, model, ...partialPolicy }` overrides; matching uses both fields and does not depend on `listModels()`. |
47
- | `auto` | no (default `true`) | Register step-boundary pressure and overflow-recovery listeners. Set `false` for manual-only. |
48
-
49
- Every `modelPolicies` entry accepts the policy fields above except `auto` and `modelPolicies` itself. If an entry supplies either retention field, it replaces the default policy's retention choice; otherwise retention is inherited. Summarization provider/model remain a pair inside each entry.
50
-
51
- An adapter may return no capacity for a valid dynamic route, and resolved capacity may expose an invalid absolute retention budget. Manual pressure checks then throw a target-specific configuration error; the automatic listener warns once for that exact target and continues with full history. Unrelated operational failures remain independently visible. Canonical provider overflow still attempts recovery because the provider has already established that compaction is necessary.
52
-
53
- ## Usage
54
-
55
- `BasicCompactionEngine` requires `ctx.llm`, `ctx.tokenMeter`, and `ctx.sessions`. The composition below receives `ctx.llm` from its host and installs the other two services:
56
-
57
- ```ts
58
- import type { Context } from '@deepseek-ai/cordis'
59
- import { BasicCompactionEngine } from '@deepseek-ai/dsh-compaction-basic'
60
- import SessionStore from '@deepseek-ai/dsh-session'
61
- import TokenMeter from '@deepseek-ai/dsh-token-meter'
62
-
63
- export const name = 'compaction-basic'
64
- export const inject = ['llm']
65
-
66
- export function apply(ctx: Context): void {
67
- ctx.plugin(SessionStore)
68
- ctx.plugin(TokenMeter)
69
- ctx.plugin(BasicCompactionEngine)
70
- }
71
- ```
32
+ With the default settings you get four behaviors: automatic condensation as the conversation grows toward the model's context limit; recovery after a confirmed context-overflow error, where the conversation condenses and the request retries; on-demand condensation through the `/compact` command; and — when the pruner is mounted — trimming of oversized tool outputs before condensation. Inputs that fit the summary-model window stay on the cache-reusing one-shot path; oversized or Provider-rejected inputs fall back to bounded chronological map-reduce.
72
33
 
73
- Loading the plugin registers `ctx.compaction`. Add [`dsh-compaction-tool-result-pruner`](../compaction-tool-result-pruner/README.md) as a sibling before this plugin to enable the optional model-free pass. With `auto: true` (the default) it compacts automatically under token pressure. The sibling [`dsh-command-compact`](../command-compact/README.md) calls `ctx.compaction.compactNow(...)`; programmatic callers may also use any seam operation directly.
34
+ ### Smallest working composition
74
35
 
75
- For example, the same compact plugin can safely serve models with different capacities and one target-specific policy:
36
+ Mount session storage, token measurement, the optional pruner, this backend, and optionally the on-demand command:
37
+
38
+ ```yaml
39
+ - name: '@deepseek-ai/dsh-session'
40
+ - name: '@deepseek-ai/dsh-token-meter'
41
+ - name: '@deepseek-ai/dsh-compaction-tool-result-pruner'
42
+ - name: '@deepseek-ai/dsh-compaction-basic'
43
+ - name: '@deepseek-ai/dsh-command-compact'
44
+ ```
45
+
46
+ You can verify success by watching the conversation continue past the point where it would otherwise overflow, and by running `/compact` for an immediate condensation. If the composition lacks an LLM, session storage, or token measurement, the plugin fails to load. One backend can serve models with different context sizes; give each route its own threshold and retention with a per-model override:
76
47
 
77
48
  ```yaml
78
49
  - name: '@deepseek-ai/dsh-compaction-basic'
@@ -86,6 +57,109 @@ For example, the same compact plugin can safely serve models with different capa
86
57
  retainTokens: 2048
87
58
  ```
88
59
 
60
+ ### Tuning when condensation starts
61
+
62
+ All settings are optional. The defaults start condensing at 80% of the routed model's context window and keep the newest 16% verbatim; the table below is the complete policy surface, and the generated [configuration catalog](../../../docs/config-catalog.md#deepseek-aidsh-compaction-basic) is the exhaustive source.
63
+
64
+ | Field | Default | Meaning |
65
+ |---|---|---|
66
+ | `thresholdRatio` | `0.8` | Start condensing at `floor(routedContextWindow × ratio)`. |
67
+ | `retainRatio` | `0.16` | Recent conversation kept verbatim as a fraction of the routed context window; mutually exclusive with `retainTokens`. |
68
+ | `retainTokens` | — | Absolute recent-conversation budget kept verbatim; mutually exclusive with `retainRatio` and must be below the resolved threshold. |
69
+ | `summarizationProvider` | `''` | Set together with `summarizationModel`; an empty pair uses the latest routed request target, then the `AgentOptions` pair. |
70
+ | `summarizationModel` | `''` | Set together with `summarizationProvider`; an empty pair uses the latest routed request target, then the `AgentOptions` pair. |
71
+ | `maxTokens` | `8192` | Output cap for the summarization request; may include reasoning tokens. |
72
+ | `compactionRetries` | `1` | Extra condensation attempts after the first when pressure remains above threshold. |
73
+ | `maxOverflowRetries` | `1` | Maximum retries after a confirmed context-window overflow; `0` disables recovery only. |
74
+ | `chunkInputRatio` | `0.6` | Fraction of the summary model window available to each hierarchy stage input; valid range `[0.1, 0.9]`. |
75
+ | `mapMaxTokens` | `4096` | Provider generation cap for one hierarchy map call. |
76
+ | `reduceMaxTokens` | `8192` | Provider generation cap for one hierarchy reduce call. |
77
+ | `maxDepth` | `4` | Maximum recursive reduce rounds; valid range `1..8`. |
78
+ | `replayTools` | `false` | Replay tool schemas in hierarchy stages. Enabling this may be required by strict Providers but consumes chunk input and reduces prefix reuse. |
79
+ | `modelPolicies` | `[]` | Exact `{ provider, model, ...partialPolicy }` overrides for individual model routes. |
80
+ | `auto` | `true` | Enable automatic condensation and overflow recovery; set `false` for manual-only operation. |
81
+
82
+ Misconfiguration fails fast: an unknown setting, a duplicate per-model override, both retention forms together, or a ratio retention that is not below the threshold all reject the plugin at load. An absolute `retainTokens` budget — top-level or per-model — that is not below its threshold fails when that model is first used, because the comparison needs the model's context size.
83
+
84
+ ### What happens when condensation runs
85
+
86
+ The oldest balanced span is replaced by one summary message and the recent tail stays verbatim; the conversation continues from the summary. The operation reports how many history items were condensed and the estimated tokens freed. If nothing can be condensed safely — for example the whole conversation is one indivisible unit — nothing changes and nothing is written to the session log. If no model is available to write the summary (no configured target and no routed request yet), condensation fails with a clear error telling you to configure the summarization provider and model or route one request.
87
+
88
+ ### On-demand condensation with /compact
89
+
90
+ With `dsh-command-compact` mounted, type `/compact` in a chat UI to condense immediately, even below the pressure threshold. The command reports how many history items were condensed and the estimated tokens saved. While the agent is mid-turn or condensation is already running, `/compact` reports that condensation is unavailable; prompts you send while it runs are accepted and start after it finishes.
91
+
92
+ ### Trimming oversized tool outputs
93
+
94
+ Mount `dsh-compaction-tool-result-pruner` before this package to trim oversized tool results as part of condensation. Trimming makes no model call and can remove the need to summarize at all: when the trimmed conversation fits within the threshold, condensation skips the summary. Trimming only runs after a condensation trigger qualifies — a below-pressure conversation is never touched.
95
+
96
+ -----
97
+
98
+ <a id="understand-the-implementation"></a>
99
+ ## Understand the implementation
100
+
101
+ <details>
102
+ <summary>Implementation internals — click to expand</summary>
103
+
104
+ This section explains the design decisions behind the backend; the observable behavior is fully covered in [Use this package](#use-this-package).
105
+
106
+ ### Design philosophy
107
+
108
+ The backend is built on four commitments:
109
+
110
+ - **One measurement service prices every decision.** The singleton `ctx.tokenMeter` measures the latest canonical logged envelope and current surface at one consumed-log revision. When the routed adapter declares request-image pricing, the meter applies it to image history. Pressure, recent-tail retention, range selection, and shrink validation use the same route-priced node figures; logged replacement shadow prices stay on the route-independent heuristic so pure projection folds remain consistent.
111
+ - **The log-recorded bracket is the transaction.** All entry points share one bracket-first region transaction: validate the range and live lock, append `compaction/start` synchronously, prepare and await the summary, revalidate, append `compaction/summary` plus the replacement, and make exactly one closing attempt. Automatic and explicit-region calls require a numeric open-turn owner and whole-surface stability; `compactNow()` reserves idle admission, uses `turn: null`, accepts append-only context outside its selected span, flushes every closed attempt, and releases admission in `finally`.
112
+ - **Summarization reuses the provider's warm prefix.** Replaying the last routed request's system prompt, tools, and shadowed-region messages byte-for-byte makes the auxiliary call a genuine prefix of the conversation, so only the trailing instruction and the summary output are uncached.
113
+ - **`summarize()` is the sole subclass hook.** A template- or remote-summarizer subclass can override it while pressure, retention, cited source events, shrink validation, and shadowed-token accounting stay on the token meter.
114
+
115
+ ### Automatic triggers and overflow recovery
116
+
117
+ With `auto: true`, a serial `agent/pre-step` listener checks pressure before request derivation: it prices the latest durable routed request envelope through `ctx.tokenMeter`, and when pressure crosses the routed model's threshold it prunes, then summarizes the oldest balanced span while keeping a priced recent tail. The `agent/request-error` listener reacts to a provider-confirmed `CONTEXT_WINDOW_EXCEEDED`: it bypasses the normal threshold and retention policy, attempts one maximal balanced head reduction, and authorizes a retry only after the surface replacement generation advances. Cancellation stays authoritative throughout.
118
+
119
+ Pressure policy resolves capacity from the adapter that owns the durable route. An adapter that returns no capacity for a valid dynamic route makes the manual pressure path throw a target-specific configuration error; the automatic listener warns once for that exact target and continues with full history.
120
+
121
+ ### Summarization mechanics
122
+
123
+ A direct `ctx.llm.stream()` call uses the configured provider/model pair and cap, falling back to the latest logged request target and then the `AgentOptions` pair, without running the loop-only `agent/request` extension point. The call replays the conversation's own system prompt, tools, and shadowed-region messages verbatim — including image references, which the selected adapter must resolve or explicitly reject — and appends the compaction instruction as the final user message, so it reuses the provider's warm prefix cache instead of invalidating it. The call sets `GenerateOptions.purpose` to `compaction`; only returned text enters the checkpoint, excluding reasoning and tool calls. Image output fails with `UNSUPPORTED_CONTENT` rather than disappearing. The replacement user message frames the summary with `<compacted-summary>` tags; the raw summary remains on the `compaction/summary` event.
124
+
125
+ ### The region transaction
126
+
127
+ The transaction validates the surface span and the durable lock, appends `compaction/start`, summarizes through the hook, revalidates stability (whole-surface for automatic calls, selected-span for manual calls), rejects a summary that does not shrink its source, appends `compaction/summary` plus the replacement `user/message`, and makes exactly one `compaction/end` attempt. A live unmatched start is the durable lock: an unmatched marker before a newer `session/end-seed` is stale evidence from a prior lifecycle and does not block; one after that boundary reports `busy`. A failed close deliberately leaves a blocking orphan. Cancellation remains authoritative after cleanup and durability.
128
+
129
+ ### Config resolution
130
+
131
+ `resolveConfig` validates and detaches the defaults, `resolveTargetPolicy` merges an exact provider/model override over them, and `resolveCompactSpec` scales the merged policy into concrete token budgets using the adapter-owned context capacity. Model discovery (`listModels()`) is never consulted for policy; only the durable route's capacity matters.
132
+
133
+ ### Source map
134
+
135
+ | File | Role |
136
+ |---|---|
137
+ | [`src/index.ts`](src/index.ts) | Plugin entry: `BasicCompactionEngine`, automatic listeners, entry-point dispatch |
138
+ | [`src/region.ts`](src/region.ts) | Retention selection and the shared bracket-first compaction transaction |
139
+ | [`src/summarizer.ts`](src/summarizer.ts) | Default `ctx.llm.stream()` summarization, checkpoint framing, safe-summary projection |
140
+ | [`src/config.ts`](src/config.ts) | Load-time validation and routed-model policy resolution |
141
+ | [`src/types.ts`](src/types.ts) | `BasicCompactionConfig` and resolved policy vocabulary |
142
+ | [`src/invariant.ts`](src/invariant.ts) | Invariant companion (no runtime invariant; the durable bracket is observable in the session log) |
143
+
144
+ </details>
145
+
146
+ -----
147
+
148
+ <a id="further-exploration"></a>
149
+ ## Further Exploration
150
+
151
+ Read these pages when the package-level contract is not enough; they move from the shared seam to the optional companions and the decision evidence.
152
+
153
+ - [Compaction seam](../compaction/README.md) — the condensation contract this backend implements.
154
+ - [Compaction subsystem reference](../../../docs/subsystems/compaction.md) — the condensation vocabulary, results, and service behavior.
155
+ - [Tool-result pruner](../compaction-tool-result-pruner/README.md) — the optional companion that trims oversized tool outputs first.
156
+ - [Human /compact command](../command-compact/README.md) — on-demand condensation without waiting for pressure.
157
+ - [Token meter](../../llm/token-meter/README.md) — the measurement service that decides when to condense.
158
+ - [Generated configuration catalog](../../../docs/config-catalog.md#deepseek-aidsh-compaction-basic) — every accepted config field and its source declaration.
159
+
160
+ -----
161
+
162
+ <a id="model-experience"></a>
89
163
  ## Model Experience
90
164
 
91
165
  ### Conversation history
@@ -163,10 +237,29 @@ The fitting one-shot request matches the conversation's replayed system prompt,
163
237
 
164
238
  ## Known Limitations and Deferred Work
165
239
 
166
- - **Meter accuracy follows the fixed heuristic** — missing reusable provider usage falls back to character count plus structural overhead rather than exact tokenization.
167
- - **Overflow classification is adapter-maintained** — provider wording can change; both DeepSeek adapters normalize currently recognized context-limit failures to `CONTEXT_WINDOW_EXCEEDED`.
240
+ <a id="known-limitations-and-deferred-work"></a>
241
+
242
+
243
+ These limits define when automatic condensation is a poor fit or needs special care; they are the current package constraints.
244
+
245
+ - **Meter accuracy follows the fixed heuristic** — missing reusable provider usage falls back to character count plus structural overhead rather than exact tokenization; image occurrences carry provider-exact visual tokens only on routes whose adapter declares request-image pricing.
246
+ - **Overflow classification is adapter-maintained** — provider wording can change; both DeepSeek adapters normalize recognized context-limit failures to `CONTEXT_WINDOW_EXCEEDED`.
168
247
  - **Bounded recovery requires summary-model capacity metadata** — an adapter that omits `contextWindow` keeps the legacy one-shot path. If that request succeeds, behavior is unchanged; if it overflows, hierarchy cannot derive safe chunk budgets and fails with an actionable capacity error.
169
248
  - **Hierarchy output is a strict checkpoint protocol** — every map and reduce stage must return all required headings. Truncation, visual output, malformed structure, exhausted `maxDepth`, or an indivisible source/partial that still overflows fails the complete compaction transaction without installing a partial checkpoint.
170
249
  - **Some indivisible-unit and envelope-only overflow remains outside surface compaction** — recovery cannot shrink system/tools/prefix, split an indivisible non-tool node, or repair a tool unit whose non-prunable remainder still exceeds the window. The optional pruner can shrink text-bearing tool-result bulk inside an otherwise indivisible pair.
171
250
  - **`compactRegion` requires an open turn** — a manual call on a fully-closed session throws ("no open turn") rather than compacting.
172
251
  - **Summarization failure preserves the latest durable surface** — before any replacement, the auto path logs a warning and proceeds with full over-budget history. If pruning already landed, a later summarization failure proceeds from that durable pruned surface. Summarization truncation at `maxTokens`, which hidden reasoning tokens can consume, follows the same rule.
252
+
253
+ <a id="dev-note"></a>
254
+ ### Dev Note
255
+
256
+ <details>
257
+ <summary>Working context for maintainers — click to expand</summary>
258
+
259
+ This Dev Note is working context for maintainers and is explicitly non-authoritative; shipped behavior lives in the sections above, the package code, and the linked Agent Notes.
260
+
261
+ - **Default ratios, undecided** — `thresholdRatio: 0.8` and `retainRatio: 0.16` are fixed defaults; per-model tuning via `modelPolicies` exists, but no corpus-backed guidance on ideal values is recorded.
262
+ - **Tokenizer-accurate measurement, deferred** — the token meter's four-characters-per-token heuristic underprices CJK text and JSON schemas; exact tokenization remains an open direction for the measurement service.
263
+ - **Overflow recovery beyond canonical errors, undecided** — recovery triggers on `CONTEXT_WINDOW_EXCEEDED` only; other provider-side context failures are not classified.
264
+
265
+ </details>
package/README.zh.md CHANGED
@@ -1,78 +1,49 @@
1
+ ---
2
+ description: "面向部署方的自动会话压缩:选择、调优或排查随 token 压力上升如何把较早历史摘要为总结。"
3
+ kind: "package-reference"
4
+ ---
5
+
1
6
  # @deepseek-ai/dsh-compaction-basic
2
7
 
3
8
  [English](README.md) | 中文
4
9
 
5
- **基础压缩(compaction)后端**:`BasicCompactionEngine` 实现 `@deepseek-ai/dsh-compaction` Service Definition,使用可复用的 `ctx.tokenMeter` 压力、token 预算保留与有界摘要。能够装入窗口的输入继续走直接的一次性 `ctx.llm.stream()` 路径,并回放会话前缀以复用提供方 KV Cache;超大输入或被 Provider 拒绝的输入会自动改用按时间顺序执行的 map-reduce 调用(均可在 `llm/stream` 处拦截)。
10
+ ## 概述
6
11
 
7
- 本包承担压缩能力的 Service Provider 角色;其约定见 [Service Definition 包](../compaction/README.zh.md),设计见 [能力 seam Agent Note](../../../.agents/notes/implemented/feature/2026-06-18-compaction-capability-seam.zh.md)。
12
+ `dsh-compaction-basic` 让长时 agent 会话在接近模型上下文上限时仍能正常工作。token 压力上升时,它会自动把对话最旧的部分压缩为摘要,并保持近期部分完整;上下文溢出错误发生后,它会压缩并重试。你也可以通过 `dsh-command-compact` `/compact` 按需压缩,并挂载 `dsh-compaction-tool-result-pruner` 先修剪超大工具输出。压缩的代价是一次额外的模型请求,它读取所选历史并写出摘要;只有摘要文本会被保留。它只压缩派生历史——无法缩减系统提示词、工具或会话前缀,也无法拆分单个不可分单元(例如一次超大工具调用)。
8
13
 
9
- ## 拥有的职责
14
+ ## 目录
10
15
 
11
- 该后端拥有压缩策略:
16
+ - [使用本包](#use-this-package)
17
+ - [理解实现](#understand-the-implementation)
18
+ - [进一步探索](#further-exploration)
19
+ - [模型体验](#model-experience)
20
+ - [已知限制与延期工作](#known-limitations-and-deferred-work)
21
+ - [开发备注](#dev-note)
12
22
 
13
- - **测量**:单例 `ctx.tokenMeter` 会在同一个已消费日志 revision 上,计量最新一份规范化已记录 envelope 与当前表层的 token 用量。因此,步骤边界的压力计量会包含实际系统提示词、工具、路由、assistant 完成、工具结果、缓冲上下文与 steering(中途引导)。
14
- - **路由策略**:主动压力从拥有最新持久提供方/模型路由的适配器解析容量,再将默认策略与可选的精确目标覆盖缩放为具体 token 预算。模型发现仍仅供参考,不参与此处的策略解析。
15
- - **不依赖模型的剪枝**:在压力或规范溢出符合条件后,可选的 [`ctx.toolResultPruner`](../compaction-tool-result-pruner/README.zh.md) 服务会在选择范围之前改写超大工具结果。Compact-basic 通过 `ctx.tokenMeter` 重新测量;如果压力已回到安全范围,就跳过摘要,否则对已剪枝的表层进行摘要。低于压力的步骤检查绝不剪枝。
16
- - **保留**:压缩最旧的完整表层单元,同时保留近期尾部,并通过 [`dsh-compaction` 边界 helper](../compaction/README.zh.md#tool-pairing-boundaries) 将切分点调整到工具调用/结果配对平衡的位置。轮次边界不会保护失控轮次内的旧步骤。尚未闭合且不可分的尾部会在闭合前拒绝压缩。当闭合的超大工具单元以文本型结果为可移除主体时,可选 pruner 可以修复它;不可分的非工具单元与不可剪枝的工具剩余部分不在范围内。
17
- - **收敛**:最多按 `compactionRetries` 重试头部检查点压缩;拒绝不能缩小源内容的摘要,如果重试仍无法回到阈值以下,则抛出异常。
18
- - **摘要**:已配置的提供方/模型对会处理所有辅助 `llm/stream` 调用;未配置时先回退到最新已记录请求目标,再回退到 agent(智能体)目标,并且不运行仅用于 agent loop 的 `agent/request` 扩展点。当估算输入与 `maxTokens` 能装入摘要模型声明的窗口时,原有 one-shot 路径保持不变:逐字回放会话系统提示词、工具与已遮蔽消息,再追加压缩指令,从而复用热前缀 KV Cache。如果该 envelope 无法装入,或 Provider 返回规范的 `CONTEXT_WINDOW_EXCEEDED`,压缩会映射有界、按时间排序且工具配对平衡的 span,再递归归并结构化部分检查点。Provider 已确认的溢出只会二分失败 span,并保留成功 sibling。Hierarchy stage 均设置 `purpose: 'compaction'`,默认省略工具 schema,拒绝截断、视觉或结构不完整的输出,并且只有整个层级成功后才执行持久替换。
19
- - **摘要来源与 usage**:成功 one-shot 保留 `llmStreamCall: true` 及 Provider 上报的 usage。只有一个成功 stage 的 hierarchy 也可以携带该标记;多调用或曾有失败尝试的恢复不会设置它。只有所有成功调用都报告 usage 且不存在失败模型尝试时,才汇总多 stage usage,避免把不完整计量伪装成完整值。
20
- - **框定**:替换 user 消息使用 `<compacted-summary>` 标签标记已建立的检查点上下文。原始摘要保留在 `compaction/summary` 事件上,后续自动周期会合并之前的检查点。
21
- - **生命周期**:所有入口点共享一个先记录标记的区域事务。它会验证范围与活动锁,同步追加 `compaction/start`,准备并等待摘要,重新验证,再追加 `compaction/summary` 和替换,最后恰好进行一次闭合尝试。自动调用和显式范围调用要求数字标识的开放轮次归属,并要求整个表层保持稳定;串行 `agent/pre-step` listener 会在派生请求之前检查压力,而规范提供方溢出则经由 `agent/request-error` 进入,并且只在表层取得持久进展后才允许重试。`compactNow()` 会预留空闲接纳,使用 `turn: null`,允许所选 span 之外追加仅追加上下文,flush 每次已闭合尝试,并在 `finally` 中释放接纳预留。
22
- - **溢出恢复**:提供方已确认的溢出不需容量元数据。它会绕过常规压力与保留,执行剪枝,再尝试一次最大平衡头部缩减,并留下最新不可分单元。只要 `surface.replaceGeneration` 前进,就允许重试,包括剪枝在后续摘要工作抛出异常前已落地的情况。如果没有替换、目标特定上限已耗尽、已取消,或遇到未知/非规范错误,则保留原始提供方失败。
23
- - **失败处理**:活动的未匹配 `compaction/start` 是持久锁。位于较新 `session/end-seed` 之前的未匹配标记,是先前生命周期留下的陈旧证据,不会阻塞;位于该边界之后的标记报告 `busy`。摘要和 span 变更失败会以错误闭合,并保持会话表层不变,但日志中仍保留该尝试。闭合失败会有意留下阻塞性的未匹配标记。压力检查中的运行故障会发出警告并继续;只有此前没有替换推进表层时,溢出恢复失败才保留原始提供方错误。完成清理与持久化后,取消仍具有最终决定权。
23
+ -----
24
24
 
25
- 受保护的 `summarize()` 方法是唯一的子类钩子。基于模板或远程摘要器的子类可以覆盖该方法,同时压力、保留、被引用的源事件、缩减验证与已遮蔽 token 计量仍由 `ctx.tokenMeter` 负责。钩子返回安全摘要,以及完整提供方输出、调用 envelope 和可用时的 usage(`{ summary, rawOutput?, llmStreamCall?, provider, model, maxTokens?, usage? }`);`llmStreamCall: true` 表示生成该结果时恰好通过此上下文的 `ctx.llm.stream()` 发起了一次调用,且必须提供完整的 `rawOutput`;未带标记的 `rawOutput` 并不能判定调用路径。事务会在 `compaction/summary` 上保留这些字段。
25
+ <a id="use-this-package"></a>
26
+ ## 使用本包
26
27
 
27
- ## 配置(`BasicCompactionConfig`)
28
+ 在已提供 LLM(大语言模型)、会话存储与 token 测量的组合中挂载本包,即可获得自动会话压缩。随附 `dsh` 基础配置默认启用它;需要控制压缩发生的时机时请显式挂载。
28
29
 
29
- 所有设置都可选。顶层策略字段是每个已路由模型的默认值;`modelPolicies` 对精确提供方/模型对应用部分覆盖。出现压力时,compaction-basic 会请求所属 LLM(大语言模型)适配器提供该路由的上下文容量,并解析绝对预算。无法识别的配置键、重复目标、互斥保留形式,以及合并后的 `retainRatio` 不低于 `thresholdRatio`,都会使插件加载失败。不低于缩放后阈值的绝对 `retainTokens` 预算会在首次解析出目标时导致失败,因为该比较需要模型容量。
30
+ ### 你会得到什么
30
31
 
31
- | Key | 必填 | 含义 |
32
- |---|---|---|
33
- | `thresholdRatio` | 否(默认 `0.8`) | 在 `floor(routedContextWindow × ratio)` 处压缩。 |
34
- | `retainRatio` | 否(默认 `0.16`) | 以已路由上下文窗口的一部分表示逐字保留的近期表层预算;与 `retainTokens` 互斥。 |
35
- | `retainTokens` | 否 | 逐字保留的近期表层绝对预算;与 `retainRatio` 互斥,并且必须低于已解析阈值。 |
36
- | `summarizationProvider` | 否(默认 `''`) | 与 `summarizationModel` 一起设置;空对会解析为最新已记录请求目标,再回退到 `AgentOptions` 对。 |
37
- | `summarizationModel` | 否(默认 `''`) | 与 `summarizationProvider` 一起设置;空对会解析为最新已记录请求目标,再回退到 `AgentOptions` 对。 |
38
- | `maxTokens` | 否(默认 `8192`) | 摘要调用的提供方生成上限;可包含推理 token。 |
39
- | `compactionRetries` | 否(默认 `1`) | 压力仍高于阈值时,在首次尝试后进行的额外尝试次数。 |
40
- | `maxOverflowRetries` | 否(默认 `1`) | 规范上下文窗口溢出后的最大重试次数;`0` 只禁用恢复。 |
41
- | `chunkInputRatio` | 否(默认 `0.6`) | 每个 hierarchy stage 输入可使用的摘要模型窗口比例;有效范围为 `[0.1, 0.9]`。 |
42
- | `mapMaxTokens` | 否(默认 `4096`) | 单次 hierarchy map 调用的 Provider 生成上限。 |
43
- | `reduceMaxTokens` | 否(默认 `8192`) | 单次 hierarchy reduce 调用的 Provider 生成上限。 |
44
- | `maxDepth` | 否(默认 `4`) | 最大递归 reduce 轮数;有效范围为 `1..8`。 |
45
- | `replayTools` | 否(默认 `false`) | 在 hierarchy stage 中回放工具 schema。严格 Provider 可能要求开启,但会占用 chunk 输入并降低前缀复用。 |
46
- | `modelPolicies` | 否(默认 `[]`) | 精确的 `{ provider, model, ...partialPolicy }` 覆盖;匹配使用两个字段,不依赖 `listModels()`。 |
47
- | `auto` | 否(默认 `true`) | 注册步骤边界压力与溢出恢复 listener。设为 `false` 则仅手动执行。 |
48
-
49
- 每个 `modelPolicies` 配置项都接受上述策略字段,但不接受 `auto` 和 `modelPolicies` 自身。如果配置项提供任意一个保留字段,就替换默认策略的保留选择;否则继承保留设置。摘要提供方/模型在每个配置项内仍然成对。
50
-
51
- 适配器可能无法为有效动态路由返回容量,已解析容量也可能暴露无效的绝对保留预算。此时手动压力检查会抛出目标特定配置错误;自动 listener 会对该精确目标警告一次,并携带完整历史继续。不相关的操作性失败仍会独立可见。规范提供方溢出仍会尝试恢复,因为提供方已确立压缩的必要性。
52
-
53
- ## 用法
54
-
55
- `BasicCompactionEngine` 需要 `ctx.llm`、`ctx.tokenMeter` 和 `ctx.sessions`。以下组合从其宿主接收 `ctx.llm`,并安装另外两项服务:
56
-
57
- ```ts
58
- import type { Context } from '@deepseek-ai/cordis'
59
- import { BasicCompactionEngine } from '@deepseek-ai/dsh-compaction-basic'
60
- import SessionStore from '@deepseek-ai/dsh-session'
61
- import TokenMeter from '@deepseek-ai/dsh-token-meter'
62
-
63
- export const name = 'compaction-basic'
64
- export const inject = ['llm']
65
-
66
- export function apply(ctx: Context): void {
67
- ctx.plugin(SessionStore)
68
- ctx.plugin(TokenMeter)
69
- ctx.plugin(BasicCompactionEngine)
70
- }
71
- ```
32
+ 默认设置下你会获得四种行为:会话向模型上下文上限增长时自动压缩;提供方确认上下文溢出错误后的恢复(先压缩再重试该请求);通过 `/compact` 命令按需压缩;以及——挂载修剪器时——压缩前对超大工具输出的修剪。
72
33
 
73
- 加载插件会注册 `ctx.compaction`。在该插件之前添加同级 [`dsh-compaction-tool-result-pruner`](../compaction-tool-result-pruner/README.zh.md) 以启用可选的不依赖模型的处理阶段。当 `auto: true`(默认)时,它会在 token 压力下自动压缩。同级 [`dsh-command-compact`](../command-compact/README.zh.md) 调用 `ctx.compaction.compactNow(...)`;编程调用方也可以直接使用任一 seam 操作。
34
+ ### 最小可用组合
74
35
 
75
- 例如,同一个压缩插件可以安全服务于容量不同的模型,并应用一项目标特定策略:
36
+ 挂载会话存储、token 测量、可选修剪器、本后端,以及可选的按需命令:
37
+
38
+ ```yaml
39
+ - name: '@deepseek-ai/dsh-session'
40
+ - name: '@deepseek-ai/dsh-token-meter'
41
+ - name: '@deepseek-ai/dsh-compaction-tool-result-pruner'
42
+ - name: '@deepseek-ai/dsh-compaction-basic'
43
+ - name: '@deepseek-ai/dsh-command-compact'
44
+ ```
45
+
46
+ 你可以通过观察会话越过本来会溢出的位置继续工作、以及运行 `/compact` 立即压缩一次来确认成功。如果组合缺少 LLM、会话存储或 token 测量,插件会加载失败。同一个后端可以服务上下文大小不同的模型;用按模型覆盖为每条路由设置各自的阈值与保留:
76
47
 
77
48
  ```yaml
78
49
  - name: '@deepseek-ai/dsh-compaction-basic'
@@ -86,13 +57,116 @@ export function apply(ctx: Context): void {
86
57
  retainTokens: 2048
87
58
  ```
88
59
 
60
+ ### 调整压缩开始的时机
61
+
62
+ 所有设置都可选。默认在已路由模型上下文窗口的 80% 处开始压缩,并逐字保留最新的 16%;下表是完整的策略面,生成的[配置目录](../../../docs/config-catalog.zh.md#deepseek-aidsh-compaction-basic)是穷尽式真源。
63
+
64
+ | 字段 | 默认值 | 含义 |
65
+ |---|---|---|
66
+ | `thresholdRatio` | `0.8` | 在 `floor(routedContextWindow × ratio)` 处开始压缩。 |
67
+ | `retainRatio` | `0.16` | 以已路由上下文窗口的一部分表示逐字保留的近期对话;与 `retainTokens` 互斥。 |
68
+ | `retainTokens` | — | 逐字保留的近期对话绝对预算;与 `retainRatio` 互斥,并且必须低于已解析阈值。 |
69
+ | `summarizationProvider` | `''` | 与 `summarizationModel` 一起设置;空对使用最新已路由请求目标,再回退到 `AgentOptions` 对。 |
70
+ | `summarizationModel` | `''` | 与 `summarizationProvider` 一起设置;空对使用最新已路由请求目标,再回退到 `AgentOptions` 对。 |
71
+ | `maxTokens` | `8192` | 摘要请求的输出上限;可包含推理 token。 |
72
+ | `compactionRetries` | `1` | 压力仍高于阈值时,在首次压缩后进行的额外尝试次数。 |
73
+ | `maxOverflowRetries` | `1` | 已确认上下文窗口溢出后的最大重试次数;`0` 只禁用恢复。 |
74
+ | `chunkInputRatio` | `0.6` | 每个层次阶段输入可用的摘要模型窗口比例;有效范围 `[0.1, 0.9]`。 |
75
+ | `mapMaxTokens` | `4096` | 单次层次 map 调用的提供方生成上限。 |
76
+ | `reduceMaxTokens` | `8192` | 单次层次 reduce 调用的提供方生成上限。 |
77
+ | `maxDepth` | `4` | 递归 reduce 轮次上限;有效范围 `1..8`。 |
78
+ | `replayTools` | `false` | 在层次阶段回放工具 schema。严格提供方可能需要打开此项,但会占用 chunk 输入并降低前缀复用。 |
79
+ | `modelPolicies` | `[]` | 针对个别模型路由的精确 `{ provider, model, ...partialPolicy }` 覆盖。 |
80
+ | `auto` | `true` | 启用自动压缩与溢出恢复;设为 `false` 则仅手动执行。 |
81
+
82
+ 配置错误会快速失败:未知设置、重复的按模型覆盖、两种保留形式同时出现,或比例保留量不低于阈值,都会在加载时拒绝插件。任何绝对 `retainTokens` 预算——顶层或按模型——不低于其阈值时,都会在该模型首次使用时失败,因为该比较需要模型的上下文大小。
83
+
84
+ ### 压缩运行时会发生什么
85
+
86
+ 最旧的平衡范围会被替换为一条摘要消息,近期尾部保持逐字不变;对话从摘要继续。操作会报告压缩了多少历史项以及估算释放的 token 数。如果没有任何内容可以安全压缩——例如整个对话就是一个不可分单元——则不会有任何改变,也不会向会话日志写入任何内容。如果没有模型可以撰写摘要(既未配置目标,也还没有已路由请求),压缩会失败并给出清晰错误,提示你配置摘要提供方与模型,或先路由一次请求。
87
+
88
+ ### 通过 /compact 按需压缩
89
+
90
+ 挂载 `dsh-command-compact` 后,在聊天 UI 中输入 `/compact` 即可立即压缩,即使未达到压力阈值。命令会报告压缩了多少历史项以及估算节省的 token 数。当 agent 正在轮次中或压缩已在运行时,`/compact` 会报告压缩暂不可用;运行期间你发送的提示词会被接受,并在压缩结束后才开始。
91
+
92
+ ### 修剪超大工具输出
93
+
94
+ 在本包之前挂载 `dsh-compaction-tool-result-pruner`,即可在压缩过程中修剪超大工具结果。修剪不发起模型调用,并可能完全省去摘要:当修剪后的对话在阈值之内时,压缩会跳过摘要。修剪只在压缩触发条件满足后运行——低于压力的对话绝不会被触碰。
95
+
96
+ -----
97
+
98
+ <a id="understand-the-implementation"></a>
99
+ ## 理解实现
100
+
101
+ <details>
102
+ <summary>实现细节——点击展开</summary>
103
+
104
+ 本节解释后端背后的设计决策;可观察行为已在[使用本包](#use-this-package)中完整说明。
105
+
106
+ ### 设计理念
107
+
108
+ 该后端建立在四项承诺之上:
109
+
110
+ - **一个测量服务为每个决策定价。** 单例 `ctx.tokenMeter` 会在同一个已消费日志 revision 上测量最新规范已记录 envelope 与当前表层。路由适配器声明请求图片定价时,meter 会将其应用于图片历史。压力、近期尾部保留、范围选择与缩减验证使用同一套路由定价的节点数值;已记录的替换影子价仍使用与路由无关的启发式规则,使纯投影 fold 保持一致。
111
+ - **日志记录的标记对就是事务。** 所有入口点共享一个先记录标记的区域事务:验证范围与活动锁,同步追加 `compaction/start`,准备并等待摘要,重新验证,再追加 `compaction/summary` 与替换,最后恰好进行一次闭合尝试。自动调用与显式范围调用要求数字标识的开放轮次归属与整个表层稳定;`compactNow()` 会预留空闲接纳,使用 `turn: null`,允许所选 span 之外追加仅追加上下文,flush 每次已闭合尝试,并在 `finally` 中释放接纳预留。
112
+ - **摘要复用提供方的热前缀。** 逐字回放上次已路由请求的系统提示词、工具与已遮蔽区域消息,使辅助调用成为会话的真正前缀,因此只有尾随指令与摘要输出未缓存。
113
+ - **`summarize()` 是唯一的子类钩子。** 基于模板或远程摘要器的子类可以覆盖它,同时压力、保留、被引用的源事件、缩减验证与已遮蔽 token 计量仍由 token meter 负责。
114
+
115
+ ### 自动触发与溢出恢复
116
+
117
+ 当 `auto: true` 时,串行 `agent/pre-step` listener 会在请求派生前检查压力:它通过 `ctx.tokenMeter` 为最新持久路由请求 envelope 定价,当压力越过路由模型的阈值时,先剪枝,再在保留已定价近期尾部的同时摘要最旧的平衡范围。`agent/request-error` listener 响应提供方确认的 `CONTEXT_WINDOW_EXCEEDED`:它绕过常规阈值与保留策略,尝试一次最大平衡头部缩减,并且只在表层替换 generation 前进后才授权重试。取消全程保持最终决定权。
118
+
119
+ 压力策略从拥有持久路由的适配器解析容量。适配器无法为有效动态路由返回容量时,手动压力路径会抛出目标特定配置错误;自动 listener 会对该精确目标警告一次,并携带完整历史继续。
120
+
121
+ ### 摘要机制
122
+
123
+ 直接 `ctx.llm.stream()` 调用使用已配置的提供方/模型对与上限,回退到最新已记录请求目标,然后再回退到 `AgentOptions` 对,而不运行仅用于 agent loop 的 `agent/request` 扩展点。该调用逐字回放会话自身的系统提示词、工具与已遮蔽区域消息——包括所选适配器必须解析或明确拒绝的图片引用——并将压缩指令作为最后一条 user 消息追加,从而复用提供方的热前缀 cache,而非使它失效。调用将 `GenerateOptions.purpose` 设为 `compaction`;只有返回文本进入检查点,推理与工具调用都会被排除。图片输出会以 `UNSUPPORTED_CONTENT` 失败,而不是消失。替换 user 消息用 `<compacted-summary>` 标签框定摘要;原始摘要保留在 `compaction/summary` 事件上。
124
+
125
+ ### 区域事务
126
+
127
+ 事务验证表层范围与持久锁,追加 `compaction/start`,通过钩子生成摘要,重新验证稳定性(自动调用要求整个表层、手动调用只要求所选范围),拒绝不缩小源内容的摘要,追加 `compaction/summary` 与替换 `user/message`,并恰好进行一次 `compaction/end` 尝试。活动的未匹配 start 是持久锁:位于较新 `session/end-seed` 之前的未匹配标记是先前生命周期留下的陈旧证据,不会阻塞;位于该边界之后的标记报告 `busy`。闭合失败会有意留下阻塞性的未匹配标记。完成清理与持久化后,取消仍具有最终决定权。
128
+
129
+ ### 配置解析
130
+
131
+ `resolveConfig` 验证并分离默认值,`resolveTargetPolicy` 将精确的提供方/模型覆盖合并到默认值之上,`resolveCompactSpec` 使用适配器拥有的上下文容量将合并后的策略缩放为具体 token 预算。策略解析绝不咨询模型发现(`listModels()`);只有持久路由的容量才重要。
132
+
133
+ ### 源码地图
134
+
135
+ | 文件 | 职责 |
136
+ |---|---|
137
+ | [`src/index.ts`](src/index.ts) | 插件入口:`BasicCompactionEngine`、自动 listener、入口点分发 |
138
+ | [`src/region.ts`](src/region.ts) | 保留选择与共享的先记录标记压缩事务 |
139
+ | [`src/summarizer.ts`](src/summarizer.ts) | 默认 `ctx.llm.stream()` 摘要、检查点框定、安全摘要投影 |
140
+ | [`src/config.ts`](src/config.ts) | 加载时验证与路由模型策略解析 |
141
+ | [`src/types.ts`](src/types.ts) | `BasicCompactionConfig` 与已解析策略词汇 |
142
+ | [`src/invariant.ts`](src/invariant.ts) | 不变式伴生插件(无运行时不变式;持久标记对可在会话日志中观察) |
143
+
144
+ </details>
145
+
146
+ -----
147
+
148
+ <a id="further-exploration"></a>
149
+ ## 进一步探索
150
+
151
+ 当包级约定不够用时阅读以下页面;它们从共享 seam 逐步进入可选配套工具与决策证据。
152
+
153
+ - [压缩 seam](../compaction/README.zh.md)——本后端实现的压缩约定。
154
+ - [压缩子系统参考](../../../docs/subsystems/compaction.zh.md)——压缩词汇、结果与服务行为。
155
+ - [工具结果修剪器](../compaction-tool-result-pruner/README.zh.md)——先修剪超大工具输出的可选配套工具。
156
+ - [人类 /compact 命令](../command-compact/README.zh.md)——无需等待压力的按需压缩。
157
+ - [Token meter](../../llm/token-meter/README.zh.md)——决定何时压缩的测量服务。
158
+ - [生成配置目录](../../../docs/config-catalog.zh.md#deepseek-aidsh-compaction-basic)——每个受支持配置字段及其源声明。
159
+
160
+ -----
161
+
162
+ <a id="model-experience"></a>
89
163
  ## 模型体验
90
164
 
91
165
  ### 会话历史
92
166
 
93
167
  #### 模型看到的内容
94
168
 
95
- 成功步骤越过阈值后,如果已加载可选 pruner,超大工具结果会先被改写。如果仍需摘要,下一个请求会收到下方检查点前导、一个空行、`<compacted-summary>`、根据数据生成的摘要以及 `</compacted-summary>`。溢出恢复会根据使表层前进的任何替换重建立即重试。检查点会替换已选较早范围,后面跟随已保留的近期单元。
169
+ 成功步骤越过阈值后,如果已加载可选修剪器,超大工具结果会先被改写。如果仍需摘要,下一个请求会收到下方检查点前导、一个空行、`<compacted-summary>`、根据数据生成的摘要以及 `</compacted-summary>`。溢出恢复会根据使表层前进的任何替换重建立即重试。检查点会替换已选较早范围,后面跟随已保留的近期单元。
96
170
 
97
171
  ##### 会话检查点前导
98
172
 
@@ -112,7 +186,7 @@ This is an automatically generated checkpoint condensing an earlier span of the
112
186
 
113
187
  #### 模型看到的内容
114
188
 
115
- 完整请求能够装入时,摘要模型会接收逐字回放的会话:与上次已路由请求为已遮蔽区域发送的相同系统提示词、工具 schema 与消息,后面跟随一条最终 user 消息,即下方压缩指令。Hierarchy 中,每个 map 请求接收相同系统提示词、一个按顺序且工具配对平衡的源 span 与结构化 map 指令;reduce 请求接收按顺序排列的 `<partial-summary>` frame 和结构化 reduce 指令。只有 `replayTools: true` 时,工具 schema 才会随 hierarchy 调用发送。会话模型绝不会看到这些私有请求或其推理;只有最终文本会被存储。
189
+ 摘要模型会接收逐字回放的会话:与上次已路由请求为已遮蔽区域发送的相同系统提示词、工具 schema 与消息,后面跟随一条最终 user 消息,即下方压缩指令。会话模型绝不会看到该私有请求或其推理;只有返回文本会被存储。
116
190
 
117
191
  ##### 压缩指令(最终 user 消息)
118
192
 
@@ -155,18 +229,37 @@ Rules:
155
229
 
156
230
  #### Token 影响
157
231
 
158
- 能够装入的输入会产生一次独立模型调用:输入是已回放会话前缀加固定指令,输出受 `maxTokens` 限制。Hierarchy 每个 map span 产生一次调用,并再产生一次或多次 reduction 调用,分别受 `mapMaxTokens` 与 `reduceMaxTokens` 限制;Provider 已确认的溢出在局部二分前还可能增加失败尝试。收敛重试可能多次支付任一种成本。
232
+ 这是一次独立模型调用:输入是已回放会话前缀加固定指令,输出受 `maxTokens` 限制。收敛重试可能多次支付这项成本。
159
233
 
160
234
  #### KV Cache 影响
161
235
 
162
- 能够装入的 one-shot 请求与会话已回放系统提示词、工具和已遮蔽区域消息逐字匹配,因此提供方的热前缀 cache 可复用至尾随指令之前。路由到另一个模型或压缩非头部范围会放弃该复用。Hierarchy 为保证每次调用有界,无法保留一个完整热前缀:Provider 允许时,map 调用仍可复用其前导系统/消息前缀,而 reduce 调用处理新生成的 partial。`replayTools: false` 还会省略工具 schema 前缀,为源消息留出更多空间。
236
+ 已回放系统提示词、工具与已遮蔽区域消息与会话最后一个已路由请求逐字匹配,因此提供方的热前缀 cache 可复用至尾随指令之前;只有该指令与摘要输出未缓存。将摘要器路由到不同提供方/模型,或压缩非头部范围,都会放弃该复用。
237
+
238
+ ## 已知限制与延期工作
239
+
240
+ <a id="known-limitations-and-deferred-work"></a>
241
+
242
+
243
+ 这些限制说明自动压缩何时不合适,或何时需要特别注意;它们是当前包约束。
244
+
245
+ - **计量准确度取决于固定启发式规则**——可复用提供方用量缺失时,会回退到字符数加结构开销,而非精确的 token 化;只有在适配器声明了请求图片定价的路由上,图片出现处才携带提供方精确的视觉 token。
246
+ - **溢出分类由适配器维护**——提供方措辞可能改变;两个 DeepSeek 适配器将可识别的上下文限制失败规范化为 `CONTEXT_WINDOW_EXCEEDED`。
247
+ - **有界恢复需要摘要模型的容量元数据**——省略 `contextWindow` 的适配器继续走旧的一次性路径。该请求成功则行为不变;若溢出,层次无法推导安全 chunk 预算,并以可操作的容量错误失败。
248
+ - **层次输出是严格的检查点协议**——每个 map 与 reduce 阶段必须返回全部必需标题。截断、视觉输出、结构畸形、耗尽 `maxDepth`,或仍溢出的不可分源/部分摘要,都会让整次压缩事务失败,不安装部分检查点。
249
+ - **部分不可分单元与仅 envelope 溢出仍不在表层压缩范围内**——恢复无法缩减系统/工具/前缀、拆分不可分的非工具节点,或修复不可剪枝剩余部分仍超出窗口的工具单元。可选 pruner 可以缩减原本不可分工具对内的文本型工具结果主体。
250
+ - **`compactRegion` 要求存在未结束的轮次**——在完全关闭的会话上手动调用会抛出异常(「no open turn」),而不是执行压缩。
251
+ - **摘要失败会保留最新持久表层**——任何替换前,自动路径会记录警告,并携带完整超预算历史继续。如果剪枝已落地,后续摘要失败会从该持久剪枝表层继续。因达到 `maxTokens` 而发生的摘要截断(隐藏推理 token 可能会耗尽该额度)遵循同一规则。
252
+
253
+ <a id="dev-note"></a>
254
+ ### 开发备注
255
+
256
+ <details>
257
+ <summary>维护者的工作上下文——点击展开</summary>
258
+
259
+ 本开发备注是维护者的工作上下文,明确不具权威性;已交付行为以上文、包代码与所链接的 Agent Note 为准。
163
260
 
164
- ## 已知限制与暂缓事项
261
+ - **默认比例,尚未决定**——`thresholdRatio: 0.8` 与 `retainRatio: 0.16` 是固定默认值;存在通过 `modelPolicies` 进行的按模型调优,但没有基于语料的理想值指引记录。
262
+ - **tokenizer 精确测量,暂缓**——token meter 每 token 四字符的启发式对 CJK 文本与 JSON schema 定价偏低;精确 token 化仍是测量服务的开放方向。
263
+ - **规范错误之外的溢出恢复,尚未决定**——恢复仅针对 `CONTEXT_WINDOW_EXCEEDED` 触发;其他提供方侧上下文失败不参与分类。
165
264
 
166
- - **计量准确度取决于固定启发式规则**:可复用提供方用量缺失时,会回退到字符数加结构开销,而非精确的 token 化。
167
- - **溢出分类由适配器维护**:提供方措辞可能改变;两个 DeepSeek 适配器将当前可识别的上下文限制失败规范化为 `CONTEXT_WINDOW_EXCEEDED`。
168
- - **有界恢复需要摘要模型容量元数据**:省略 `contextWindow` 的适配器会保留旧 one-shot 路径。如果该请求成功,行为不变;如果它溢出,hierarchy 无法推导安全 chunk 预算,并会以可操作的容量错误失败。
169
- - **Hierarchy 输出是严格检查点协议**:每个 map 和 reduce stage 都必须返回所有必需标题。截断、视觉输出、结构错误、耗尽 `maxDepth`,或仍然溢出的不可分源/partial,都会使完整压缩事务失败,不会安装部分检查点。
170
- - **部分不可分单元与仅 envelope 溢出仍不在表层压缩范围内**:恢复无法缩减系统/工具/前缀、拆分不可分的非工具节点,或修复不可剪枝剩余部分仍超出窗口的工具单元。可选 pruner 可以缩减原本不可分工具对内的文本型工具结果主体。
171
- - **`compactRegion` 要求存在未结束的轮次**:在完全关闭的会话上手动调用会抛出异常(「no open turn」),而不是执行压缩。
172
- - **摘要失败会保留最新持久表层**:任何替换前,自动路径会记录警告,并携带完整超预算历史继续。如果剪枝已落地,后续摘要失败会从该持久剪枝表层继续。因达到 `maxTokens` 而发生的摘要截断(隐藏推理 token 可能会耗尽该额度)遵循同一规则。
265
+ </details>
package/lib/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import z from "@deepseek-ai/schemastery";
2
2
  import { CompactionEngine, CompactionId, ManualCompactionError, compactCheckpointSource, toolPairingBalancedAfter, toolPairingBalancedBefore } from "@deepseek-ai/dsh-compaction";
3
- import { BlockAssembler, CONTEXT_WINDOW_EXCEEDED_CODE, LlmError, assertNever, contentHasImage, createUserMessage, deepFreeze, errorChain } from "@deepseek-ai/dsh-llm";
3
+ import { BlockAssembler, CONTEXT_WINDOW_EXCEEDED_CODE, LlmError, contentHasImage, createUserMessage, errorChain } from "@deepseek-ai/dsh-llm";
4
+ import { assertNever, deepFreeze } from "@deepseek-ai/dsh-util-values";
4
5
  import { randomUUID } from "node:crypto";
5
6
  import { isDeepStrictEqual } from "node:util";
6
7
  //#region lib/types/config.js
@@ -585,7 +586,8 @@ function prepareCompaction(dependencies, session, selection) {
585
586
  ...selection,
586
587
  measurement,
587
588
  selectedNodes,
588
- shadowedTokenCount: selectedNodes.reduce((total, node) => total + node.tokens, 0),
589
+ shadowedTokenCount: selectedNodes.reduce((total, node) => total + node.heuristicTokens, 0),
590
+ shadowedRouteTokenCount: selectedNodes.reduce((total, node) => total + node.tokens, 0),
589
591
  input: buildSummarizationInput(session, selection.shadowedSeqs)
590
592
  };
591
593
  }
@@ -597,7 +599,7 @@ async function summarizeCompaction(dependencies, prepared, agent, compactionId,
597
599
  source: compactCheckpointSource(compactionId, sourceCommandId)
598
600
  });
599
601
  const framedSummaryTokenCount = dependencies.meter.estimateMessage(checkpointMessage);
600
- if (framedSummaryTokenCount >= prepared.shadowedTokenCount) throw new Error(`summary is not smaller than the shadowed content (${framedSummaryTokenCount} estimated framed tokens >= ${prepared.shadowedTokenCount})`);
602
+ if (framedSummaryTokenCount >= prepared.shadowedRouteTokenCount) throw new Error(`summary is not smaller than the shadowed content (${framedSummaryTokenCount} estimated framed tokens >= ${prepared.shadowedRouteTokenCount})`);
601
603
  return {
602
604
  ...prepared,
603
605
  ...summaryResult,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@crazx/dsh-compaction-basic",
3
3
  "description": "Token-meter-driven compaction policy and LLM summarization backend for the DeepSeek Harness",
4
- "version": "0.1.1-rc.2.zw.1",
4
+ "version": "0.1.2-alpha.3.zw.2",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -32,15 +32,15 @@
32
32
  ],
33
33
  "license": "MIT",
34
34
  "peerDependencies": {
35
- "@deepseek-ai/dsh-agent": "^0.1.1-rc.2",
36
- "@deepseek-ai/dsh-compaction": "^0.1.1-rc.2",
37
- "@deepseek-ai/dsh-commands": "^0.1.1-rc.2",
38
- "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2",
39
- "@deepseek-ai/dsh-llm": "^0.1.1-rc.2",
40
- "@deepseek-ai/dsh-session": "^0.1.1-rc.2",
41
- "@deepseek-ai/dsh-token-meter": "^0.1.1-rc.2",
42
- "@deepseek-ai/dsh-compaction-tool-result-pruner": "^0.1.1-rc.2",
43
- "@deepseek-ai/cordis": "^4.0.1"
35
+ "@deepseek-ai/cordis": "^4.0.2",
36
+ "@deepseek-ai/dsh-agent": "^0.1.2-alpha.3",
37
+ "@deepseek-ai/dsh-commands": "^0.1.2-alpha.3",
38
+ "@deepseek-ai/dsh-compaction": "^0.1.2-alpha.3",
39
+ "@deepseek-ai/dsh-compaction-tool-result-pruner": "^0.1.2-alpha.3",
40
+ "@deepseek-ai/dsh-invariants": "^0.1.2-alpha.3",
41
+ "@deepseek-ai/dsh-llm": "^0.1.2-alpha.3",
42
+ "@deepseek-ai/dsh-session": "^0.1.2-alpha.3",
43
+ "@deepseek-ai/dsh-token-meter": "^0.1.2-alpha.3"
44
44
  },
45
45
  "peerDependenciesMeta": {
46
46
  "@deepseek-ai/dsh-compaction-tool-result-pruner": {
@@ -48,23 +48,25 @@
48
48
  }
49
49
  },
50
50
  "dependencies": {
51
- "@deepseek-ai/schemastery": "^3.18.1"
51
+ "@deepseek-ai/dsh-util-values": "^0.1.2-alpha.3",
52
+ "@deepseek-ai/schemastery": "^3.18.2"
52
53
  },
53
54
  "devDependencies": {
54
- "@deepseek-ai/cordis-plugin-include": "^1.0.6",
55
- "@deepseek-ai/cordis-plugin-loader": "^1.0.2",
56
- "@deepseek-ai/dsh-agent": "^0.1.1-rc.2",
57
- "@deepseek-ai/dsh-agent-loop": "^0.1.1-rc.2",
58
- "@deepseek-ai/dsh-agent-loop-testkit": "^0.1.1-rc.2",
59
- "@deepseek-ai/dsh-compaction": "^0.1.1-rc.2",
60
- "@deepseek-ai/dsh-commands": "^0.1.1-rc.2",
61
- "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2",
62
- "@deepseek-ai/dsh-llm": "^0.1.1-rc.2",
63
- "@deepseek-ai/dsh-llm-retry": "^0.1.1-rc.2",
64
- "@deepseek-ai/dsh-session": "^0.1.1-rc.2",
65
- "@deepseek-ai/dsh-token-meter": "^0.1.1-rc.2",
66
- "@deepseek-ai/dsh-compaction-tool-result-pruner": "^0.1.1-rc.2",
67
- "@deepseek-ai/dsh-tools": "^0.1.1-rc.2",
68
- "@deepseek-ai/cordis": "^4.0.1"
55
+ "@deepseek-ai/cordis": "^4.0.2",
56
+ "@deepseek-ai/cordis-plugin-include": "^1.0.7",
57
+ "@deepseek-ai/cordis-plugin-loader": "^1.0.3",
58
+ "@deepseek-ai/dsh-agent": "^0.1.2-alpha.3",
59
+ "@deepseek-ai/dsh-agent-loop": "^0.1.2-alpha.3",
60
+ "@deepseek-ai/dsh-agent-loop-testkit": "^0.1.2-alpha.3",
61
+ "@deepseek-ai/dsh-commands": "^0.1.2-alpha.3",
62
+ "@deepseek-ai/dsh-compaction": "^0.1.2-alpha.3",
63
+ "@deepseek-ai/dsh-compaction-tool-result-pruner": "^0.1.2-alpha.3",
64
+ "@deepseek-ai/dsh-invariants": "^0.1.2-alpha.3",
65
+ "@deepseek-ai/dsh-llm": "^0.1.2-alpha.3",
66
+ "@deepseek-ai/dsh-llm-retry": "^0.1.2-alpha.3",
67
+ "@deepseek-ai/dsh-session": "^0.1.2-alpha.3",
68
+ "@deepseek-ai/dsh-session-projection": "^0.1.2-alpha.3",
69
+ "@deepseek-ai/dsh-token-meter": "^0.1.2-alpha.3",
70
+ "@deepseek-ai/dsh-tools": "^0.1.2-alpha.3"
69
71
  }
70
72
  }