@sagmans/dsh-auto-compact 0.1.0
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/LICENSE +21 -0
- package/README.md +175 -0
- package/cordis.patch.yml +18 -0
- package/dist/config.js +91 -0
- package/dist/index.js +90 -0
- package/dist/policy.js +78 -0
- package/dist/view.js +82 -0
- package/package.json +83 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sercan Sagman - ahmetsercansagman@gmail.com
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# dsh-auto-compact
|
|
2
|
+
|
|
3
|
+
A DeepSeek Harness compaction backend that adds an absolute token budget to the
|
|
4
|
+
shipped window-ratio trigger. It condenses at a fixed price on large-window
|
|
5
|
+
models, while every model whose window is already smaller than that budget keeps
|
|
6
|
+
its proportional trigger.
|
|
7
|
+
|
|
8
|
+
```
|
|
9
|
+
trigger = min(thresholdTokens, floor(contextWindow x thresholdRatio))
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Releases publish to npm as [`@sagmans/dsh-auto-compact`](https://www.npmjs.com/package/@sagmans/dsh-auto-compact); every release carries a provenance attestation built by the tag workflow, and no npm token is stored. The code is [MIT licensed](LICENSE).
|
|
13
|
+
|
|
14
|
+
## Requirements
|
|
15
|
+
|
|
16
|
+
- Node.js 24 LTS (verified with 24.20.0).
|
|
17
|
+
- pnpm 11.21.0 for this repository.
|
|
18
|
+
- A DeepSeek Harness install on the supported line: `>=0.1.5-rc.1 <0.1.6` (verified against `0.1.5-rc.2`). The plugin declares that range as a peer dependency, so a profile resolves the harness copy it already has rather than a second framework instance.
|
|
19
|
+
|
|
20
|
+
## Configuration
|
|
21
|
+
|
|
22
|
+
Every shipped setting keeps its meaning; this backend adds two fields and one
|
|
23
|
+
per-route override.
|
|
24
|
+
|
|
25
|
+
| Field | Default | Meaning |
|
|
26
|
+
|---|---|---|
|
|
27
|
+
| `thresholdTokens` | — | Absolute trigger budget in tokens. Omitted means the ratio trigger alone. |
|
|
28
|
+
| `retainShare` | `0.2` | Retained tail as a fraction of the derived trigger, applied only when the budget is what triggers condensation. |
|
|
29
|
+
| `modelPolicies[].thresholdTokens` | — | Budget for one exact `provider`/`model` route. |
|
|
30
|
+
| `thresholdRatio`, `retainRatio`, `retainTokens`, `summarizationProvider`, `summarizationModel`, `maxTokens`, `compactionRetries`, `maxOverflowRetries`, `modelPolicies`, `auto` | shipped | Unchanged; see the shipped backend documentation. |
|
|
31
|
+
|
|
32
|
+
```yaml
|
|
33
|
+
- id: auto-compact
|
|
34
|
+
name: '@sagmans/dsh-auto-compact'
|
|
35
|
+
config:
|
|
36
|
+
thresholdTokens: 200000
|
|
37
|
+
modelPolicies:
|
|
38
|
+
- provider: opencode-go-session
|
|
39
|
+
model: deepseek-flash
|
|
40
|
+
thresholdTokens: 120000
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Trigger examples
|
|
44
|
+
|
|
45
|
+
| Context window | `thresholdRatio` | `thresholdTokens` | Trigger |
|
|
46
|
+
|---|---|---|---|
|
|
47
|
+
| 1,000,000 | 0.8 | 200,000 | **200,000** (budget) |
|
|
48
|
+
| 262,144 | 0.8 | 200,000 | **200,000** (budget) |
|
|
49
|
+
| 131,072 | 0.8 | 200,000 | 104,857 (ratio) |
|
|
50
|
+
| 1,000,000 | 0.8 | — | 800,000 (ratio only) |
|
|
51
|
+
|
|
52
|
+
## Install
|
|
53
|
+
|
|
54
|
+
The bundle patch disables the shipped `compaction-basic` row and inserts this
|
|
55
|
+
backend in its place, so the profile keeps exactly one compaction service.
|
|
56
|
+
|
|
57
|
+
```sh
|
|
58
|
+
dsh plugin --profile tui add link:/path/to/dsh-auto-compact
|
|
59
|
+
dsh --profile tui --dump-config | grep -A3 'id: auto-compact'
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Profile-specific tuning belongs in `$DSH_HOME/profiles/<name>/cordis.patch.yml`,
|
|
63
|
+
which the profile watches:
|
|
64
|
+
|
|
65
|
+
```yaml
|
|
66
|
+
- id: auto-compact
|
|
67
|
+
config:
|
|
68
|
+
thresholdTokens: 200000
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Restart the profile after a bundle change. Tune the patch file afterwards
|
|
72
|
+
without a restart.
|
|
73
|
+
|
|
74
|
+
## Where the engine runs
|
|
75
|
+
|
|
76
|
+
The TUI and Web bundles disable the host-plane compaction rows so that each
|
|
77
|
+
agent preset owns an engine in its own realm. This bundle provides the service
|
|
78
|
+
on the host plane, which serves every agent because an unscoped listener
|
|
79
|
+
receives all agent events. A preset that still mounts the shipped backend keeps
|
|
80
|
+
it as a later listener: that engine can only condense after this one already
|
|
81
|
+
did, or at its own wider ratio trigger, so it stays inert.
|
|
82
|
+
|
|
83
|
+
Two consequences are worth knowing:
|
|
84
|
+
|
|
85
|
+
- The host plane mounts no tool-result pruner in those profiles, so
|
|
86
|
+
condensation here summarizes without the model-free prune pass. Insert a
|
|
87
|
+
`tool-result-pruner` row at the host plane if you want that pass.
|
|
88
|
+
- A deployment that wants exactly one engine can instead copy the agent preset
|
|
89
|
+
it uses, point that copy's `compaction-basic` row at this package, and select
|
|
90
|
+
the copy as the default preset. The copy is a snapshot and drifts from the
|
|
91
|
+
shipped preset.
|
|
92
|
+
|
|
93
|
+
## How it works
|
|
94
|
+
|
|
95
|
+
The absolute budget cannot be expressed as a shipped setting, so the engine
|
|
96
|
+
subclasses the shipped backend and overrides `compactIfNeeded` for the pressure
|
|
97
|
+
trigger only:
|
|
98
|
+
|
|
99
|
+
1. Read the durable route and the budget for it. Below the budget, delegate
|
|
100
|
+
unchanged and make no capacity lookup.
|
|
101
|
+
2. Read the routed model's context window from the adapter.
|
|
102
|
+
3. Derive the trigger and the retained tail, then run the shipped transaction
|
|
103
|
+
under that policy.
|
|
104
|
+
|
|
105
|
+
The shipped engine resolves its policy from a frozen instance field, so the
|
|
106
|
+
derived policy is placed on a per-call receiver that shares the engine's
|
|
107
|
+
context and state. A field swap would leak between agents condensing
|
|
108
|
+
concurrently on different routes.
|
|
109
|
+
|
|
110
|
+
Provider-confirmed context overflow, `/compact`, and `auto: false` keep their
|
|
111
|
+
shipped behaviour. Overflow recovery never consults a threshold.
|
|
112
|
+
|
|
113
|
+
## Verification
|
|
114
|
+
|
|
115
|
+
Unit tests cover the decision surface, including the boundary where the budget
|
|
116
|
+
stops binding and the point where the derived trigger must carry the decision
|
|
117
|
+
past the shipped ratio check.
|
|
118
|
+
|
|
119
|
+
A controlled pair of real sessions proves the trigger end to end. Both runs used
|
|
120
|
+
the same task, the same route, and a reported window of 1,000,000 tokens, where
|
|
121
|
+
the shipped ratio trigger is 800,000:
|
|
122
|
+
|
|
123
|
+
| Run | `thresholdTokens` | Compaction events |
|
|
124
|
+
|---|---|---|
|
|
125
|
+
| A | 8,000 | 4 starts, 3 summaries, 1 prune |
|
|
126
|
+
| B | — | none |
|
|
127
|
+
|
|
128
|
+
Reproduce it with a scratch profile:
|
|
129
|
+
|
|
130
|
+
```sh
|
|
131
|
+
dsh --profile autocompact-dogfood --from-default-profile headless --dump-config
|
|
132
|
+
dsh plugin --profile autocompact-dogfood add link:/path/to/dsh-auto-compact
|
|
133
|
+
# patch the profile with the route rows and a small thresholdTokens, then:
|
|
134
|
+
cd /tmp/dsh-auto-compact-dogfood
|
|
135
|
+
dsh --profile autocompact-dogfood "cat big-a.md big-b.md, then report the character count"
|
|
136
|
+
zstd -dc ~/.dsh/sessions/--private-tmp-dsh-auto-compact-dogfood--/*/session.v3.jsonl.zstd \
|
|
137
|
+
| grep -oE '"type":"compaction/[a-z]+"' | sort | uniq -c
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Remove `thresholdTokens` and the same run must report no compaction events.
|
|
141
|
+
|
|
142
|
+
## Limits
|
|
143
|
+
|
|
144
|
+
- The adapter must report a context window. A route without one keeps the
|
|
145
|
+
shipped warn-once failure and no capacity is guessed.
|
|
146
|
+
- Token pressure comes from the shipped meter (four characters per token, with
|
|
147
|
+
provider usage when available), so a budget is as accurate as that meter.
|
|
148
|
+
- Per-route overrides match exactly; there is no wildcard matcher.
|
|
149
|
+
- A budget below the fixed request envelope (system prompt plus tool schemas)
|
|
150
|
+
cannot be satisfied. The shipped retry policy then warns and continues the
|
|
151
|
+
turn.
|
|
152
|
+
|
|
153
|
+
## Development
|
|
154
|
+
|
|
155
|
+
```sh
|
|
156
|
+
pnpm install
|
|
157
|
+
pnpm run check # typecheck, unit tests, build, built-artifact tests, release guards, pack smoke
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
The profile loads `dist/index.js`, so rebuild and restart the profile after a
|
|
161
|
+
source change.
|
|
162
|
+
|
|
163
|
+
## Releasing
|
|
164
|
+
|
|
165
|
+
Published artefacts carry a provenance attestation, which only a CI provider can issue, so releases ship from the tag workflow rather than a laptop.
|
|
166
|
+
|
|
167
|
+
1. Bump `version` in `package.json`, land it on `main` through a reviewed PR, and wait for CI to pass on the merged SHA.
|
|
168
|
+
2. Tag that SHA with a signed tag and push it. The tag ruleset admits repository admins only.
|
|
169
|
+
3. [`.github/workflows/release.yml`](.github/workflows/release.yml) re-runs the checks and the package smoke; the publish job then waits for a maintainer's approval on the `npm-release` environment before it publishes with OIDC trusted publishing and automatic provenance.
|
|
170
|
+
|
|
171
|
+
The workflow stores no npm token: the registry trusts `release.yml` on the `npm-release` environment, and [`scripts/npm/release.py`](scripts/npm/release.py) creates both the environment and that trust. The full runbook is [RELEASE.md](RELEASE.md).
|
|
172
|
+
|
|
173
|
+
## License
|
|
174
|
+
|
|
175
|
+
MIT
|
package/cordis.patch.yml
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# dsh-auto-compact — the shipped compaction engine, plus an absolute token cap.
|
|
2
|
+
#
|
|
3
|
+
# The base row is disabled rather than reconfigured: a patch replaces a row's
|
|
4
|
+
# whole config, and the engine swap is what adds the absolute trigger. This
|
|
5
|
+
# profile plane owns no `compaction` provider of its own (the TUI and Web
|
|
6
|
+
# bundles disable the base row so each agent's preset can own one), so this row
|
|
7
|
+
# provides the service without a realm conflict and serves preset-composed
|
|
8
|
+
# agents through the unscoped host listener.
|
|
9
|
+
- id: compaction-basic
|
|
10
|
+
disabled: true
|
|
11
|
+
|
|
12
|
+
- insert:
|
|
13
|
+
- id: auto-compact
|
|
14
|
+
name: '@sagmans/dsh-auto-compact'
|
|
15
|
+
config:
|
|
16
|
+
# Condense at 200K tokens even on a much larger window; the 80% ratio
|
|
17
|
+
# keeps governing every model whose window falls below 250K.
|
|
18
|
+
thresholdTokens: 200000
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration vocabulary and load-time resolution for the absolute compaction
|
|
3
|
+
* budget.
|
|
4
|
+
* @module @sagmans/dsh-auto-compact/config
|
|
5
|
+
*/
|
|
6
|
+
import Schema from '@deepseek-ai/schemastery';
|
|
7
|
+
import { BasicCompactionEngine } from '@deepseek-ai/dsh-compaction-basic';
|
|
8
|
+
/** Tail share of the derived threshold used when no explicit share is configured. */
|
|
9
|
+
export const DEFAULT_RETAIN_SHARE = 0.2;
|
|
10
|
+
const thresholdTokensSchema = Schema.number().step(1).min(1);
|
|
11
|
+
// Bounds only; the open lower bound stays with resolvers, which reject a zero
|
|
12
|
+
// or negative share with a message naming the field.
|
|
13
|
+
const retainShareSchema = Schema.number().min(0).max(1);
|
|
14
|
+
/** Shipped schema dicts; composing from them keeps a new shipped field usable here. */
|
|
15
|
+
const stockConfigDict = BasicCompactionEngine.Config.dict ?? {};
|
|
16
|
+
const stockRouteDict = stockConfigDict.modelPolicies?.inner?.dict ?? {};
|
|
17
|
+
/** One exact route override: every shipped field plus the absolute budget. */
|
|
18
|
+
const routePolicySchema = Schema.object({
|
|
19
|
+
...stockRouteDict,
|
|
20
|
+
thresholdTokens: thresholdTokensSchema,
|
|
21
|
+
});
|
|
22
|
+
/** Loader-validated plugin schema. */
|
|
23
|
+
export const Config = Schema.object({
|
|
24
|
+
...stockConfigDict,
|
|
25
|
+
thresholdTokens: thresholdTokensSchema,
|
|
26
|
+
retainShare: retainShareSchema,
|
|
27
|
+
modelPolicies: Schema.array(routePolicySchema),
|
|
28
|
+
});
|
|
29
|
+
/** Strip the plugin-only fields from one route override. */
|
|
30
|
+
function stockRoutePolicy(policy) {
|
|
31
|
+
const { thresholdTokens: _thresholdTokens, ...stock } = policy;
|
|
32
|
+
return stock;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Reduce this plugin's configuration to the shipped engine's own vocabulary.
|
|
36
|
+
*
|
|
37
|
+
* The shipped resolver rejects unknown keys at the top level and inside every
|
|
38
|
+
* route override, so an absolute budget left in place would fail the plugin at
|
|
39
|
+
* load instead of being ignored.
|
|
40
|
+
*
|
|
41
|
+
* @param config - validated plugin configuration.
|
|
42
|
+
* @returns the configuration the shipped engine can resolve.
|
|
43
|
+
*/
|
|
44
|
+
export function toEngineConfig(config = {}) {
|
|
45
|
+
const { thresholdTokens: _thresholdTokens, retainShare: _retainShare, modelPolicies, ...stock } = config;
|
|
46
|
+
if (modelPolicies === undefined)
|
|
47
|
+
return stock;
|
|
48
|
+
return { ...stock, modelPolicies: modelPolicies.map(stockRoutePolicy) };
|
|
49
|
+
}
|
|
50
|
+
/** Key matching the shipped engine's exact provider/model route identity. */
|
|
51
|
+
export function routeKey(provider, model) {
|
|
52
|
+
return `${provider}\u0000${model}`;
|
|
53
|
+
}
|
|
54
|
+
function assertCap(name, value) {
|
|
55
|
+
if (value === undefined)
|
|
56
|
+
return undefined;
|
|
57
|
+
if (typeof value !== 'number' || !Number.isInteger(value) || value < 1) {
|
|
58
|
+
throw new Error(`${name} (${String(value)}) must be a positive integer`);
|
|
59
|
+
}
|
|
60
|
+
return value;
|
|
61
|
+
}
|
|
62
|
+
function assertRetainShare(value) {
|
|
63
|
+
if (value === undefined)
|
|
64
|
+
return DEFAULT_RETAIN_SHARE;
|
|
65
|
+
if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0 || value > 1) {
|
|
66
|
+
throw new Error(`retainShare (${String(value)}) must be a number in (0, 1]`);
|
|
67
|
+
}
|
|
68
|
+
return value;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Validate the deployment's absolute budgets.
|
|
72
|
+
*
|
|
73
|
+
* Duplicate route overrides are rejected by the shipped resolver, which sees
|
|
74
|
+
* every override through {@link toEngineConfig}.
|
|
75
|
+
*
|
|
76
|
+
* @param config - plugin configuration, before or after schema normalization.
|
|
77
|
+
* @returns detached budget lookups.
|
|
78
|
+
*/
|
|
79
|
+
export function resolveCapPolicy(config = {}) {
|
|
80
|
+
const routeCaps = new Map();
|
|
81
|
+
for (const [index, policy] of (config.modelPolicies ?? []).entries()) {
|
|
82
|
+
const cap = assertCap(`modelPolicies[${index}].thresholdTokens`, policy.thresholdTokens);
|
|
83
|
+
if (cap !== undefined)
|
|
84
|
+
routeCaps.set(routeKey(policy.provider, policy.model), cap);
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
defaultCap: assertCap('thresholdTokens', config.thresholdTokens),
|
|
88
|
+
routeCaps,
|
|
89
|
+
retainShare: assertRetainShare(config.retainShare),
|
|
90
|
+
};
|
|
91
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Absolute-budget compaction engine for the DeepSeek Harness.
|
|
3
|
+
*
|
|
4
|
+
* The shipped engine triggers on a fraction of the routed model's window. This
|
|
5
|
+
* backend keeps that policy and adds an absolute token budget, so a deployment
|
|
6
|
+
* can condense at a fixed price on large-window models while every smaller
|
|
7
|
+
* window keeps its ratio trigger.
|
|
8
|
+
* @module @sagmans/dsh-auto-compact
|
|
9
|
+
*/
|
|
10
|
+
import { BasicCompactionEngine } from '@deepseek-ai/dsh-compaction-basic';
|
|
11
|
+
import { Config, resolveCapPolicy, routeKey, toEngineConfig } from "./config.js";
|
|
12
|
+
import { decidePressurePolicy, routePolicyTerms } from "./policy.js";
|
|
13
|
+
import { derivedConfig, findOverride, resolveContextWindow, routedTarget, withDerivedPolicy, } from "./view.js";
|
|
14
|
+
export { Config, DEFAULT_RETAIN_SHARE, resolveCapPolicy, routeKey, toEngineConfig } from "./config.js";
|
|
15
|
+
export { decidePressurePolicy, routePolicyTerms } from "./policy.js";
|
|
16
|
+
/**
|
|
17
|
+
* Shipped compaction backend plus an absolute token budget.
|
|
18
|
+
*
|
|
19
|
+
* Every shipped setting keeps its meaning; `thresholdTokens` adds a cap on the
|
|
20
|
+
* trigger, and `retainShare` bounds the retained tail whenever that cap is what
|
|
21
|
+
* triggers compaction.
|
|
22
|
+
*/
|
|
23
|
+
export class AutoCompactEngine extends BasicCompactionEngine {
|
|
24
|
+
static Config = Config;
|
|
25
|
+
static inject = [...BasicCompactionEngine.inject];
|
|
26
|
+
/** Absolute-budget policy resolved at load. */
|
|
27
|
+
capPolicy;
|
|
28
|
+
/**
|
|
29
|
+
* @param ctx - context owning the LLM, token meter, and session services.
|
|
30
|
+
* @param config - plugin configuration; the shipped subset is forwarded as-is.
|
|
31
|
+
*/
|
|
32
|
+
constructor(ctx, config = {}) {
|
|
33
|
+
super(ctx, toEngineConfig(config));
|
|
34
|
+
this.capPolicy = resolveCapPolicy(config);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Compact for step-boundary pressure or provider-confirmed overflow.
|
|
38
|
+
*
|
|
39
|
+
* Pressure runs under the derived absolute budget when one applies; every
|
|
40
|
+
* other trigger, and every route the budget does not bind, delegates to the
|
|
41
|
+
* shipped implementation unchanged.
|
|
42
|
+
*
|
|
43
|
+
* @param agent - agent whose latest durable routed request is measured.
|
|
44
|
+
* @param trigger - pressure or context-overflow.
|
|
45
|
+
* @param signal - live turn cancellation signal.
|
|
46
|
+
* @returns the compaction result, or `null` when nothing was needed.
|
|
47
|
+
*/
|
|
48
|
+
async compactIfNeeded(agent, trigger, signal) {
|
|
49
|
+
const target = trigger === 'pressure' ? routedTarget(agent.session) : undefined;
|
|
50
|
+
const cap = target === undefined
|
|
51
|
+
? undefined
|
|
52
|
+
: this.capPolicy.routeCaps.get(routeKey(target.provider, target.model))
|
|
53
|
+
?? this.capPolicy.defaultCap;
|
|
54
|
+
const measured = cap === undefined ? 0 : this.ctx.tokenMeter.measure(agent.session).totalTokens;
|
|
55
|
+
// The derived trigger never exceeds the budget, so below it there is nothing
|
|
56
|
+
// to decide and no capacity lookup is worth making on every step.
|
|
57
|
+
if (target === undefined || cap === undefined || measured < cap) {
|
|
58
|
+
return super.compactIfNeeded(agent, trigger, signal);
|
|
59
|
+
}
|
|
60
|
+
// The shipped transaction resolves the same capacity again when it prices
|
|
61
|
+
// the span. That second lookup is accepted: it runs only on steps that
|
|
62
|
+
// already exceeded the budget, and the shipped signature exposes no seam to
|
|
63
|
+
// hand a resolved window through.
|
|
64
|
+
const contextWindow = await resolveContextWindow(this.ctx, target, signal);
|
|
65
|
+
if (contextWindow === undefined)
|
|
66
|
+
return super.compactIfNeeded(agent, trigger, signal);
|
|
67
|
+
const override = findOverride(this.config.modelPolicies, target);
|
|
68
|
+
const terms = routePolicyTerms(this.config, override, contextWindow);
|
|
69
|
+
const derived = decidePressurePolicy({
|
|
70
|
+
totalTokens: measured,
|
|
71
|
+
contextWindow,
|
|
72
|
+
thresholdTokens: cap,
|
|
73
|
+
thresholdRatio: terms.thresholdRatio,
|
|
74
|
+
retainTokens: terms.retainTokens,
|
|
75
|
+
retainShare: this.capPolicy.retainShare,
|
|
76
|
+
});
|
|
77
|
+
if (derived === undefined)
|
|
78
|
+
return super.compactIfNeeded(agent, trigger, signal);
|
|
79
|
+
this.ctx.logger.debug(`auto-compact: ${target.provider}/${target.model} pressure ${measured}, `
|
|
80
|
+
+ `ratio trigger ${Math.floor(contextWindow * terms.thresholdRatio)} on window ${contextWindow}, `
|
|
81
|
+
+ `absolute budget ${cap} -> threshold ${derived.thresholdTokens}, tail ${derived.retainTokens}`);
|
|
82
|
+
const result = await withDerivedPolicy(this, derivedConfig(this.config, override, derived), view => BasicCompactionEngine.prototype.compactIfNeeded.call(view, agent, trigger, signal));
|
|
83
|
+
if (result !== null) {
|
|
84
|
+
this.ctx.logger.info(`auto-compact: condensed at the absolute budget of ${derived.thresholdTokens} tokens `
|
|
85
|
+
+ `(window ${contextWindow}, tail ${derived.retainTokens})`);
|
|
86
|
+
}
|
|
87
|
+
return result;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
export default AutoCompactEngine;
|
package/dist/policy.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure decision logic for the absolute compaction budget: no cordis, no I/O.
|
|
3
|
+
* @module @sagmans/dsh-auto-compact/policy
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Half-token bias added to the derived budget before it is expressed as a
|
|
7
|
+
* fraction. The shipped engine re-derives its trigger with
|
|
8
|
+
* `Math.floor(window * ratio)`, and a budget that binary floating point cannot
|
|
9
|
+
* represent exactly scales back to one token less, which then rejects the
|
|
10
|
+
* retained tail as reaching the trigger. Half a token clears that rounding
|
|
11
|
+
* error while staying below the next whole token.
|
|
12
|
+
*/
|
|
13
|
+
const SCALE_EPSILON = 0.5;
|
|
14
|
+
/**
|
|
15
|
+
* Resolve the shipped per-route ratio and retained tail in tokens.
|
|
16
|
+
*
|
|
17
|
+
* This transcribes the shipped merge — an exact override wins field by field,
|
|
18
|
+
* retention resolves to exactly one form, and a ratio retention scales with the
|
|
19
|
+
* routed model's capacity — so pricing stays identical when no absolute budget
|
|
20
|
+
* applies.
|
|
21
|
+
*
|
|
22
|
+
* @param base - the shipped engine's resolved deployment policy.
|
|
23
|
+
* @param override - the exact route override, when one matches.
|
|
24
|
+
* @param contextWindow - adapter-owned capacity for the routed model.
|
|
25
|
+
* @returns the shipped terms for this route.
|
|
26
|
+
*/
|
|
27
|
+
export function routePolicyTerms(base, override, contextWindow) {
|
|
28
|
+
const thresholdRatio = override?.thresholdRatio ?? base.thresholdRatio;
|
|
29
|
+
let retainTokens;
|
|
30
|
+
if (override?.retainTokens !== undefined) {
|
|
31
|
+
retainTokens = override.retainTokens;
|
|
32
|
+
}
|
|
33
|
+
else if (override?.retainRatio !== undefined) {
|
|
34
|
+
retainTokens = Math.floor(contextWindow * override.retainRatio);
|
|
35
|
+
}
|
|
36
|
+
else if (base.retainTokens !== undefined) {
|
|
37
|
+
retainTokens = base.retainTokens;
|
|
38
|
+
}
|
|
39
|
+
else if (base.retainRatio !== undefined) {
|
|
40
|
+
retainTokens = Math.floor(contextWindow * base.retainRatio);
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
throw new Error('auto-compact: resolved policy carries no retention form');
|
|
44
|
+
}
|
|
45
|
+
return { thresholdRatio, retainTokens };
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Decide whether the absolute budget, rather than the shipped ratio, is what
|
|
49
|
+
* should trigger compaction for this request.
|
|
50
|
+
*
|
|
51
|
+
* @param input - measured pressure plus the routed model's resolved budgets.
|
|
52
|
+
* @returns the derived policy, or `undefined` when the shipped path must decide
|
|
53
|
+
* alone (no budget configured, no reported capacity, or a ratio trigger that is
|
|
54
|
+
* already at least as tight).
|
|
55
|
+
*/
|
|
56
|
+
export function decidePressurePolicy(input) {
|
|
57
|
+
const cap = input.thresholdTokens;
|
|
58
|
+
// The derived trigger can never exceed the absolute budget, so below the
|
|
59
|
+
// budget there is nothing to decide and no capacity is needed.
|
|
60
|
+
if (cap === undefined || input.totalTokens < cap)
|
|
61
|
+
return undefined;
|
|
62
|
+
const window = input.contextWindow;
|
|
63
|
+
if (window === undefined)
|
|
64
|
+
return undefined;
|
|
65
|
+
const ratioThreshold = Math.floor(window * input.thresholdRatio);
|
|
66
|
+
if (cap >= ratioThreshold)
|
|
67
|
+
return undefined;
|
|
68
|
+
// A model-free prune can land before summarization; the tail is bounded by
|
|
69
|
+
// the derived trigger so a later step always has history left to condense.
|
|
70
|
+
const boundedTail = Math.min(input.retainTokens, Math.floor(cap * input.retainShare),
|
|
71
|
+
// The shipped capacity scaling rejects a tail that reaches the trigger.
|
|
72
|
+
cap - 1);
|
|
73
|
+
return {
|
|
74
|
+
thresholdTokens: cap,
|
|
75
|
+
thresholdRatio: (cap + SCALE_EPSILON) / window,
|
|
76
|
+
retainTokens: Math.max(0, boundedTail),
|
|
77
|
+
};
|
|
78
|
+
}
|
package/dist/view.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Route resolution and the derived-policy receiver used to re-enter the shipped
|
|
3
|
+
* compaction transaction.
|
|
4
|
+
* @module @sagmans/dsh-auto-compact/view
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Read the route the shipped engine itself would price this session against.
|
|
8
|
+
* @param session - session whose latest durable request header is inspected.
|
|
9
|
+
* @returns the routed target, or `undefined` when no route is durable yet.
|
|
10
|
+
*/
|
|
11
|
+
export function routedTarget(session) {
|
|
12
|
+
const config = session.requestHeader()?.config;
|
|
13
|
+
if (config === undefined || config.provider.length === 0 || config.model.length === 0)
|
|
14
|
+
return undefined;
|
|
15
|
+
return { provider: config.provider, model: config.model };
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Find the exact provider/model override the shipped merge would apply.
|
|
19
|
+
* @param policies - the shipped engine's resolved override table.
|
|
20
|
+
* @param target - routed target to match exactly.
|
|
21
|
+
* @returns the matching override, or `undefined`.
|
|
22
|
+
*/
|
|
23
|
+
export function findOverride(policies, target) {
|
|
24
|
+
return policies.find(policy => policy.provider === target.provider && policy.model === target.model);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Read the routed model's adapter-owned capacity.
|
|
28
|
+
* @param ctx - context owning the LLM runtime.
|
|
29
|
+
* @param target - routed provider/model pair.
|
|
30
|
+
* @param signal - cancellation signal forwarded to the adapter.
|
|
31
|
+
* @returns the reported window, or `undefined` when the adapter declares none.
|
|
32
|
+
*/
|
|
33
|
+
export async function resolveContextWindow(ctx, target, signal) {
|
|
34
|
+
return (await ctx.llm.resolveModelInfo(target.provider, target.model, signal)).context?.contextWindow;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Build the complete policy the shipped transaction must run under.
|
|
38
|
+
*
|
|
39
|
+
* The route override is merged here and then cleared from the table: the
|
|
40
|
+
* shipped merge would otherwise re-apply the ratio this derivation replaced.
|
|
41
|
+
*
|
|
42
|
+
* @param base - the shipped engine's resolved deployment policy.
|
|
43
|
+
* @param override - the matching exact route override, when any.
|
|
44
|
+
* @param derived - the derived absolute-budget policy.
|
|
45
|
+
* @returns a complete, detached policy for one transaction.
|
|
46
|
+
*/
|
|
47
|
+
export function derivedConfig(base, override, derived) {
|
|
48
|
+
const { provider: _provider, model: _model, ...overrideFields } = override ?? {};
|
|
49
|
+
return {
|
|
50
|
+
...base,
|
|
51
|
+
...overrideFields,
|
|
52
|
+
thresholdRatio: derived.thresholdRatio,
|
|
53
|
+
retainRatio: undefined,
|
|
54
|
+
retainTokens: derived.retainTokens,
|
|
55
|
+
modelPolicies: [],
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Run one shipped compaction transaction under a derived policy.
|
|
60
|
+
*
|
|
61
|
+
* WHY a receiver instead of a field: the shipped engine resolves its policy
|
|
62
|
+
* from the instance field `config`, which is deep-frozen at load and therefore
|
|
63
|
+
* cannot be replaced. Swapping the field for the duration of the call would let
|
|
64
|
+
* two agents compacting concurrently on different routes observe each other's
|
|
65
|
+
* policy, so the derived value lives on a per-call receiver that shares the
|
|
66
|
+
* engine's context, overrides, and private state through the prototype chain.
|
|
67
|
+
*
|
|
68
|
+
* @param engine - the live engine instance.
|
|
69
|
+
* @param policy - the derived policy for this one transaction.
|
|
70
|
+
* @param run - the shipped method to invoke on the receiver.
|
|
71
|
+
* @returns the shipped method's result.
|
|
72
|
+
*/
|
|
73
|
+
export function withDerivedPolicy(engine, policy, run) {
|
|
74
|
+
const view = Object.create(engine);
|
|
75
|
+
Object.defineProperty(view, 'config', {
|
|
76
|
+
value: policy,
|
|
77
|
+
enumerable: false,
|
|
78
|
+
writable: false,
|
|
79
|
+
configurable: false,
|
|
80
|
+
});
|
|
81
|
+
return run(view);
|
|
82
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@sagmans/dsh-auto-compact",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Absolute token trigger for DeepSeek Harness automatic compaction: condense at a fixed token budget or at the model window ratio, whichever comes first.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"deepseek-harness",
|
|
7
|
+
"dsh",
|
|
8
|
+
"dsh-plugin",
|
|
9
|
+
"plugin",
|
|
10
|
+
"compaction",
|
|
11
|
+
"cordis"
|
|
12
|
+
],
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"type": "module",
|
|
15
|
+
"main": "./dist/index.js",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": "./dist/index.js",
|
|
18
|
+
"./cordis.patch.yml": "./cordis.patch.yml",
|
|
19
|
+
"./package.json": "./package.json"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist",
|
|
23
|
+
"cordis.patch.yml",
|
|
24
|
+
"README.md"
|
|
25
|
+
],
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": "^24.0.0"
|
|
28
|
+
},
|
|
29
|
+
"packageManager": "pnpm@11.21.0",
|
|
30
|
+
"publishConfig": {
|
|
31
|
+
"access": "public",
|
|
32
|
+
"registry": "https://registry.npmjs.org/"
|
|
33
|
+
},
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "git+https://github.com/sagmans/dsh-auto-compact.git"
|
|
37
|
+
},
|
|
38
|
+
"homepage": "https://github.com/sagmans/dsh-auto-compact#readme",
|
|
39
|
+
"bugs": "https://github.com/sagmans/dsh-auto-compact/issues",
|
|
40
|
+
"dsh": {
|
|
41
|
+
"bundle": {
|
|
42
|
+
"patch": "./cordis.patch.yml"
|
|
43
|
+
},
|
|
44
|
+
"compatibility": {
|
|
45
|
+
"dsh": ">=0.1.5-rc.1 <0.1.6"
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
"scripts": {
|
|
49
|
+
"typecheck": "tsc --noEmit",
|
|
50
|
+
"test": "node --import tsx/esm --test \"tests/*.spec.ts\"",
|
|
51
|
+
"build": "tsc -p tsconfig.build.json",
|
|
52
|
+
"test:build": "node --test \"tests/*.test.mjs\"",
|
|
53
|
+
"test:release": "python3 -m unittest discover -s tests/release -p 'test_*.py'",
|
|
54
|
+
"pack-smoke": "node tools/pack-smoke.mjs",
|
|
55
|
+
"check": "pnpm run typecheck && pnpm test && pnpm run build && pnpm run test:build && pnpm test:release && pnpm run pack-smoke",
|
|
56
|
+
"prepack": "pnpm run build",
|
|
57
|
+
"prepublishOnly": "pnpm run typecheck && pnpm test && pnpm test:release"
|
|
58
|
+
},
|
|
59
|
+
"peerDependencies": {
|
|
60
|
+
"@deepseek-ai/cordis": "^4.0.2",
|
|
61
|
+
"@deepseek-ai/dsh-agent": ">=0.1.5-rc.1 <0.1.6",
|
|
62
|
+
"@deepseek-ai/dsh-compaction": ">=0.1.5-rc.1 <0.1.6",
|
|
63
|
+
"@deepseek-ai/dsh-compaction-basic": ">=0.1.5-rc.1 <0.1.6",
|
|
64
|
+
"@deepseek-ai/dsh-llm": ">=0.1.5-rc.1 <0.1.6",
|
|
65
|
+
"@deepseek-ai/dsh-session": ">=0.1.5-rc.1 <0.1.6",
|
|
66
|
+
"@deepseek-ai/dsh-token-meter": ">=0.1.5-rc.1 <0.1.6",
|
|
67
|
+
"@deepseek-ai/schemastery": "^3.18.2"
|
|
68
|
+
},
|
|
69
|
+
"devDependencies": {
|
|
70
|
+
"@deepseek-ai/cordis": "4.0.2",
|
|
71
|
+
"@deepseek-ai/dsh": "0.1.5-rc.2",
|
|
72
|
+
"@deepseek-ai/dsh-agent": "0.1.5-rc.2",
|
|
73
|
+
"@deepseek-ai/dsh-compaction": "0.1.5-rc.2",
|
|
74
|
+
"@deepseek-ai/dsh-compaction-basic": "0.1.5-rc.2",
|
|
75
|
+
"@deepseek-ai/dsh-llm": "0.1.5-rc.2",
|
|
76
|
+
"@deepseek-ai/dsh-session": "0.1.5-rc.2",
|
|
77
|
+
"@deepseek-ai/dsh-token-meter": "0.1.5-rc.2",
|
|
78
|
+
"@deepseek-ai/schemastery": "3.18.2",
|
|
79
|
+
"@types/node": "22.20.0",
|
|
80
|
+
"tsx": "4.22.4",
|
|
81
|
+
"typescript": "6.0.3"
|
|
82
|
+
}
|
|
83
|
+
}
|