dsh-context-policy
Verified@snylonue/dsh-context-policy Β· v0.2.1 Β· MIT
Percentage-only compaction and model-configurable GPT and DeepSeek token counting for DSH
Install
dsh plugin add @snylonue/dsh-context-policy Confirm the layer applied with dsh --profile default --dump-config β see the install guide.
Source
Published to npm without a public repository. Inspect the package contents before installing.
Readme
DSH Context Policy
Percentage-based automatic compaction and model-configurable token accounting for DeepSeek Harness (DSH). The package provides Cordis services, compiled ESM modules, and TypeScript declarations.
Requirements
- Node.js 24 or later.
- DSH 0.2.0-rc.2 - 0.2.1-rc.1.
Installation
Install the npm package into a DSH profile after the release is published:
dsh plugin --profile web add @snylonue/dsh-context-policy # or your profile
The bundle applies cordis.patch.yml, which replaces the global token-meter and compaction-basic entries supplied by DSH's base bundle. Its defaults apply to all model routes; it contains no provider or model bindings. DSH's presets also declare their own isolated compaction service; replace those children as described below.
Edit each affected preset's compaction plugin entry in place:
# Before
- id: compaction-basic
name: '@deepseek-ai/dsh-compaction-basic'
# After
- id: compaction-basic
name: '@snylonue/dsh-context-policy/compaction'
Inspect the composed configuration before starting the profile:
dsh --profile web --dump-config # or your profile
Default behavior
Automatic pressure compaction starts when projected input tokens reach the configured fraction of the model's context window. At the default thresholdRatio of 0.8, a hypothetical 100000-token window triggers at 80000 tokens. Output limits and the summary token budget do not reduce the threshold.
The numeric compaction defaults match DSH's default: thresholdRatio: 0.8, retainRatio: 0.16, and maxTokens: 65536. This plugin calculates pressure and retention against the full context window and ignores upstream headroom deductions. The pressure retention budget defaults to 16% of the full context window. A fixed retainTokens budget can replace the ratio. Range selection preserves the leading system message and complete tool call/result pairs. Compaction rechecks pressure after pruning and summarization, with a configurable retry limit.
The meter uses tokenizer for accurate esitimation. deepseek_v4 tokenizer is used by default. Provider defaults and model policies can select a different tokenizer. A successful request's provider-reported input usage calibrates subsequent custom measurements. Zero-usage failures preserve those measurements. Billing totals follow the upstream provider-usage accounting rules. The existing client pressure and composition views display the custom counts.
Compaction configuration
The global entry has the ID percentage-compaction. The following is a profile patch for that entry:
- id: percentage-compaction
config:
thresholdRatio: 0.8
retainRatio: 0.16
# retainTokens: 12000 # Use instead of retainRatio; remove retainRatio first.
maxTokens: 65536
compactionRetries: 1
maxOverflowRetries: 1
auto: true
# summarizationProvider: example-provider # Optional summarization route override.
# summarizationModel: example-model
providerPolicies: []
modelPolicies: []
headroomTokens: 0 # Accepted for migration; ignored by this plugin.
For an isolated preset, place the configuration on its compaction child. Values below describe the automatic pressure policy unless otherwise stated.
| Option | Default | Description |
|---|---|---|
thresholdRatio |
0.8 | Trigger fraction of the full context window; greater than 0 and at most 1. |
retainRatio |
0.16 | Retention fraction of the full window; nonnegative and below thresholdRatio. |
retainTokens |
Unset | Fixed nonnegative retention budget. Configure this instead of retainRatio. |
maxTokens |
65536 | Positive output budget for the summarization request. |
compactionRetries |
1 | Additional pressure compaction attempts if pressure remains above the threshold. |
maxOverflowRetries |
1 | Retry limit used by the inherited context-overflow handler. |
auto |
true | Enables automatic compaction listeners. |
summarizationProvider |
Unset | Optional provider override for summarization. |
summarizationModel |
Unset | Optional model override for summarization. |
providerPolicies |
[] | Default pressure policy for all models on a provider. |
modelPolicies |
[] | Partial overrides for exact provider/model routes. Both identifiers are required. |
headroomTokens |
Ignored | Accepted for migration from the upstream configuration. |
A route override can adjust pressure behavior independently:
modelPolicies:
- provider: example-provider
model: example-model
thresholdRatio: 0.85
retainTokens: 12000
# retainRatio: 0.16 # Use instead of retainTokens; remove retainTokens first.
maxTokens: 4096
compactionRetries: 1
maxOverflowRetries: 1
# summarizationProvider: example-summary-provider
# summarizationModel: example-summary-model
headroomTokens: 0 # Accepted for migration; ignored by this plugin.
Use the provider identifier and model name registered in the target DSH installation. Pressure policy fields inherit from service defaults β provider policy β exact model policy. Set retainTokens or retainRatio to replace the inherited retention form. Summary and overflow handlers continue to use upstream service/model configuration. auto is a service-wide option and cannot be overridden per route. The LLM adapter must supply contextWindow for pressure compaction.
Token-meter configuration
The global entry has the ID model-token-meter. A profile patch can select a tokenizer and accounting policy per route:
- id: model-token-meter
config:
defaultTokenizer: deepseek_v4
providerPolicies:
- provider: example-provider
tokenizer: o200k_base
modelPolicies:
- provider: example-provider
model: example-model
tokenizer: o200k_base
tokensPerMessage: 3
toolsOverhead: 4
replyPrimingTokens: 3
includeReasoning: false
- model: example-model
tokenizer: o200k_base
The identifiers in this example are placeholders. An exact provider/model policy takes precedence over a model-only policy. Each omitted policy field inherits from the provider policy, then from the global default. The matched model policy's explicit fields override the provider policy. With no request header, the global default applies.
| Option | Default | Description |
|---|---|---|
defaultTokenizer |
deepseek_v4 |
Registered tokenizer used when no model or provider tokenizer applies. |
providerPolicies |
[] | Tokenizer and accounting defaults for all models on a provider. |
modelPolicies |
[] | Route-specific tokenizer and accounting settings. |
tokensPerMessage |
3 | Per-message framing allowance. |
toolsOverhead |
4 | Allowance added when tool definitions are present. |
replyPrimingTokens |
3 | Request-level reply framing allowance. |
includeReasoning |
false | Includes visible reasoning text in the estimated message surface. |
The last four options belong to provider and model policy entries. Token allowances must be nonnegative integers. For duplicate routes at the same specificity, the first matching entry is used. Unknown tokenizer names fail when text is counted.
Builtin tokenizers
| Name | Vocabulary |
|---|---|
o200k_base |
Modern GPT vocabulary. |
deepseek_v4 |
Official shared DeepSeek V4 Pro / Flash vocabulary and pre-tokenization rules; the default. |
DeepSeek V4 is the global default. Select GPT token counting for all models on a provider with a provider default:
- id: model-token-meter
config:
defaultTokenizer: deepseek_v4
providerPolicies:
- provider: openai
tokenizer: o200k_base
Use the provider identifiers configured in your DSH profile; openai is an example.
Custom tokenizers
Register a synchronous counter before loading a meter that selects it:
import { registerTokenizer } from '@snylonue/dsh-context-policy/tokenizers';
export function registerModelTokenizer(encode: (text: string) => readonly number[]) {
return registerTokenizer('custom-tokenizer', text => encode(text).length);
}
Call this function with the model's tokenizer implementation, then select custom-tokenizer in your profile. The returned function unregisters the counter; dispose dependent meters before calling it. Counters must return nonnegative safe integers. Text counts use a bounded cache, and special-token-looking input is treated as ordinary text by both builtin counters.
Accounting and accuracy
Compared with DSH 0.2.0-rc.2's official dsh-token-meter and dsh-compaction-basic, this package changes the following behavior:
| Area | Official DSH | This plugin |
|---|---|---|
| Text and tool counting | Fixed heuristic of roughly four characters per token, with fixed overheads. | Use a configurable tokenizer. |
| Calibration baseline | Uses reported input + cache + output when the request header matches and that total is at least the local estimate for the original request and response. | Uses positive reported input + cache from a successful request when the request header matches, even if it is below the local estimate. |
| Next-request estimate | Starts from the request and response baseline, then adjusts for subsequent content changes. | Starts from the input baseline, then counts retained reply text and later content changes with the configured tokenizer. Reported output usage, which may include hidden reasoning, is not added. |
| UI estimates | Context composition uses the fixed heuristic; displayed pressure is calculated separately from route-aware measurement. | Context composition uses the configured tokenizer and adapter attachment pricing. Displayed projectedTokens equals the measurement used by automatic compaction. |
| Measurement details | Returns pressure and surface counts with per-node prices. | Also reports the selected tokenizer, toolsTokens, and unpricedImages / unpricedFiles flags for attachments lacking adapter pricing. |
| Automatic compaction budgets | Trigger: floor(min(W Γ thresholdRatio, W β O β B)). Ratio retention: floor((W β O) Γ retainRatio). |
Trigger: floor(W Γ thresholdRatio). Ratio retention: floor(W Γ retainRatio). |
| Summary output budget | When maxTokens is omitted, it follows the resolved headroomTokens. |
maxTokens defaults to 65536 independently of headroomTokens. |
W is the model's context window, O is the effective request output reservation, and B is the official compaction headroom.
Module exports
| Import path | API |
|---|---|
@snylonue/dsh-context-policy |
Default Cordis plugin (Config, inject, apply) and named ModelTokenMeter service class. |
@snylonue/dsh-context-policy/token-meter |
Same plugin entry and named ModelTokenMeter service class. |
@snylonue/dsh-context-policy/compaction |
PercentageCompactionEngine and its default service export. |
@snylonue/dsh-context-policy/tokenizers |
Tokenizer registration, text counting, and policy resolution. |
@snylonue/dsh-context-policy/core |
Custom counting replay, measurement, and provider-input helpers. |
Compatibility
The compaction service extends BasicCompactionEngine and replaces automatic pressure range selection. Summarization, durable transactions, manual commands, and overflow listeners use the upstream implementation.
The meter uses the public Cordis service lifecycle and projection registration API. Its billing fold is maintained locally because the upstream definition is not exported at runtime. Replay state does not retain fixed-density heuristic prices. Projection state, wire composition, and durable compaction shadow prices all use model-configured token counts. The upstream heuristicTokens compatibility field carries the same model count as tokens. This does not change independent history estimators owned by other plugins.
Development and release
From a source checkout:
pnpm install --frozen-lockfile
pnpm run check
npm pack
To audit local session logs after building:
node scripts/audit.ts /absolute/path/to/session.v4.jsonl.zstd > audit.json
The audit reports prediction errors and zero-usage occupancy preservation without including message content. It uses the default tokenizer policy and runs without live adapter pricing, so attachment results have the same estimation limits described above.
License
MIT. See LICENSE.