dsh-better-compact 0.0.0-stage → 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 +674 -0
- package/README.md +546 -2
- package/cordis.patch.yml +33 -0
- package/docs/DEVELOPMENT.md +363 -0
- package/package.json +90 -4
- package/presets/better-compact.patch.yml +141 -0
- package/src/budget.js +250 -0
- package/src/client.js +2417 -0
- package/src/config.js +493 -0
- package/src/engine.js +491 -0
- package/src/handoff.js +122 -0
- package/src/identity.js +30 -0
- package/src/index.js +253 -0
- package/src/inspect.js +562 -0
- package/src/live.js +130 -0
- package/src/models.js +97 -0
- package/src/prompts.js +391 -0
- package/src/state.js +171 -0
- package/third-party/LICENSE-MIT-dsh +21 -0
- package/third-party/NOTICE +47 -0
package/README.md
CHANGED
|
@@ -1,3 +1,547 @@
|
|
|
1
|
-
#
|
|
1
|
+
# dsh-better-compact
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Independent DSH compaction plugin. It keeps the native checkpoint transaction and
|
|
4
|
+
adds two continuation-oriented summarization modes: **goal** (default) and
|
|
5
|
+
**file handoff**, plus an embedded visual settings page and a read-only
|
|
6
|
+
inspect/models API. It does not need Apollo, a second history database, or a
|
|
7
|
+
custom write API.
|
|
8
|
+
|
|
9
|
+
- Package: `dsh-better-compact` `0.1.0`
|
|
10
|
+
- License: GPL-3.0-or-later (adapted DSH material remains MIT; see
|
|
11
|
+
[third-party/NOTICE](third-party/NOTICE))
|
|
12
|
+
- Host baseline: DSH `>=0.2.1-rc.2 <0.3.0` (`0.2.1-rc.2` is the verified build)
|
|
13
|
+
- Node.js: `^22.19.0 || >=24.0.0`
|
|
14
|
+
- Pure ESM, no build step.
|
|
15
|
+
|
|
16
|
+
## What it changes
|
|
17
|
+
|
|
18
|
+
`BasicCompactionEngine` remains the owner of token measurement, safe-cut
|
|
19
|
+
selection, tool-pairing boundaries, retention, locking, retries, overflow
|
|
20
|
+
recovery, the durable `compaction/start → compaction/summary → checkpoint
|
|
21
|
+
replacement → compaction/end` sequence, and manual `/compact`. This package
|
|
22
|
+
only customizes its public `summarize()` hook and the post-success manual
|
|
23
|
+
continuation:
|
|
24
|
+
|
|
25
|
+
| Mode | Summary input | Output |
|
|
26
|
+
| --- | --- | --- |
|
|
27
|
+
| `native` | The installed native summarizer, unchanged. | Native checkpoint; no goal read, no temp file. |
|
|
28
|
+
| `goal` (default) | Replayed conversation + current authoritative goal/todo/questions/running jobs/children. | Structured continuation checkpoint. |
|
|
29
|
+
| `handoff` | Same authoritative state. | A per-attempt temporary Markdown handoff file plus the same Markdown body embedded in the native checkpoint; after a successful live **manual** compaction one ordinary same-session follow-up may be scheduled. |
|
|
30
|
+
|
|
31
|
+
All modes share native selection, retention, pricing, and durability. No mode
|
|
32
|
+
creates a new session, answers a pending question, or changes goal state.
|
|
33
|
+
|
|
34
|
+
## Install
|
|
35
|
+
|
|
36
|
+
Standalone (no Apollo):
|
|
37
|
+
|
|
38
|
+
```sh
|
|
39
|
+
dsh plugin --profile <profile> add dsh-better-compact@0.1.0
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
From GitHub main (secondary entry point):
|
|
43
|
+
|
|
44
|
+
```sh
|
|
45
|
+
dsh plugin --profile <profile> add github:Menghuan1918/dsh-better-compact
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
After installing or changing a load-time setting, **restart the DSH profile**
|
|
49
|
+
(restart the DSH process). Refreshing the Web page only reloads the UI; it does
|
|
50
|
+
not replace a running engine. See [apply-on-reload](#apply-on-reload).
|
|
51
|
+
|
|
52
|
+
### Configuring in the Web UI
|
|
53
|
+
|
|
54
|
+
In the Web Plugins page, open the `dsh-better-compact` row. Its page is the
|
|
55
|
+
visual settings editor described in [Visual settings page](#visual-settings-page):
|
|
56
|
+
three strategy cards, summary model/effort, the budget and retention
|
|
57
|
+
visualization, cache priority, collapsible advanced options, and the recent
|
|
58
|
+
native compaction status. It is embedded in the Host's existing config slot and
|
|
59
|
+
saves through the Host configForms revision/CAS path.
|
|
60
|
+
|
|
61
|
+
The same values can still be edited manually in the profile's
|
|
62
|
+
`cordis.patch.yml` (the same file the Host settings service writes); restart
|
|
63
|
+
the profile after either kind of change:
|
|
64
|
+
|
|
65
|
+
```yaml
|
|
66
|
+
- id: dsh-better-compact
|
|
67
|
+
config:
|
|
68
|
+
mode: handoff # native | goal | handoff
|
|
69
|
+
auto: true
|
|
70
|
+
thresholdRatio: 0.8
|
|
71
|
+
headroomTokens: 65536
|
|
72
|
+
retainRatio: 0.16
|
|
73
|
+
maxTokens: 65536
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
`retainTokens` replaces `retainRatio` (never set both). Omitted fields take the
|
|
77
|
+
documented defaults and the page shows those effective values rather than
|
|
78
|
+
hard-coded sketch numbers.
|
|
79
|
+
|
|
80
|
+
### Web: select the `better-compact` preset
|
|
81
|
+
|
|
82
|
+
The shipped Web composition disables the host-plane compaction rows and mounts
|
|
83
|
+
compaction inside each **agent preset**. A bundle patch cannot edit preset
|
|
84
|
+
rows, so this package ships its own selectable preset:
|
|
85
|
+
|
|
86
|
+
- choose **Better Compact** when creating a session, or set it as the default
|
|
87
|
+
preset in **Settings → General → Agent preset**;
|
|
88
|
+
- other presets keep their own native compaction engine; the host-plane engine
|
|
89
|
+
detects a preset-owned engine and stays out of the way rather than
|
|
90
|
+
double-compacting.
|
|
91
|
+
|
|
92
|
+
The preset is a verbatim copy of the installed DSH `0.2.1-rc.2` `standard`
|
|
93
|
+
preset with exactly one row changed inside the compaction group
|
|
94
|
+
(`compaction-basic` → `dsh-better-compact/engine`). If upstream changes its
|
|
95
|
+
standard preset, this copy must be refreshed for that host version; it is not a
|
|
96
|
+
dynamic reference. The copy is a known rc.2-era limitation, not a claim that
|
|
97
|
+
preset composition is stable across DSH releases.
|
|
98
|
+
|
|
99
|
+
### Headless / TUI / non-preset compositions
|
|
100
|
+
|
|
101
|
+
There are no agent presets to mount, so the host-plane engine row is the active
|
|
102
|
+
compaction provider. `/compact`, automatic pressure, and overflow recovery work
|
|
103
|
+
there without selecting a preset. The inspect HTTP route is absent in headless
|
|
104
|
+
compositions (no webserver); that is expected and does not affect compaction.
|
|
105
|
+
|
|
106
|
+
### Apollo
|
|
107
|
+
|
|
108
|
+
Apollo will declare the same stable rows (`dsh-better-compact` companion row and
|
|
109
|
+
`dsh-better-compact/engine` engine row) inside its `eng` / `eng-ptc` presets.
|
|
110
|
+
The browser half registers the same config page under all three row keys, so
|
|
111
|
+
standalone and Apollo assemblies share one implementation:
|
|
112
|
+
|
|
113
|
+
- `dsh-better-compact#dsh-better-compact`
|
|
114
|
+
- `@menghuan1918/dsh-apollo#dsh-better-compact`
|
|
115
|
+
- `dsh-apollo#dsh-better-compact`
|
|
116
|
+
|
|
117
|
+
Do not install both bundle sources merely to enable compaction; only one explicit
|
|
118
|
+
entry is recommended.
|
|
119
|
+
|
|
120
|
+
## Visual settings page
|
|
121
|
+
|
|
122
|
+
The browser half is a zero-build React closure loaded by the Host module loader
|
|
123
|
+
(`id: dsh-better-compact`, only `require('react')`). It registers one component
|
|
124
|
+
under the canonical row key and both Apollo aliases, so standalone and Apollo
|
|
125
|
+
assemblies share the same page and the same Host form. The page never imports
|
|
126
|
+
Apollo, never adds a chart/build dependency, and only performs read-only GETs to
|
|
127
|
+
`/better-compact/api/inspect` and `/better-compact/api/models`.
|
|
128
|
+
|
|
129
|
+
### Layout and modes
|
|
130
|
+
|
|
131
|
+
The order is overview first, advanced collapsed: three mode cards, summary
|
|
132
|
+
model/effort, context budget and retention, cache priority, advanced settings,
|
|
133
|
+
then recent native compaction status. Each card has its own inline SVG strategy
|
|
134
|
+
diagram, not a shared picture or an icon:
|
|
135
|
+
|
|
136
|
+
| Card | What the diagram shows |
|
|
137
|
+
| --- | --- |
|
|
138
|
+
| DSH native | Earlier history → native summary; recent verbatim stays retained; result is the native checkpoint. |
|
|
139
|
+
| Goal-enhanced (default) | Earlier history **plus** the external authoritative goal/todo → goal/constraints/progress/next summary; recent verbatim retained; goal stays read-only. |
|
|
140
|
+
| File handoff | Conversation/state → this attempt's temporary handoff file → native events store the same body → same-session continuation; recent verbatim retained. |
|
|
141
|
+
|
|
142
|
+
The cards are one `role="radiogroup"`; each card is a `role="radio"` with
|
|
143
|
+
`aria-checked`, mouse and keyboard selection (Arrow keys, Home/End, Space/Enter),
|
|
144
|
+
focus-visible styling, and a disabled state. Selection is not conveyed by color
|
|
145
|
+
alone: the selected card also shows a "✓ Selected" tag and a filled radio dot.
|
|
146
|
+
The section is labelled "strategy schematic, not a live token flow or cache hit
|
|
147
|
+
rate". If the selected/saved mode differs from the active engine snapshot, the
|
|
148
|
+
card and the active-config banner say a profile reload is required.
|
|
149
|
+
|
|
150
|
+
### Session selection and read-only status
|
|
151
|
+
|
|
152
|
+
The row page has no session prop, so the page uses the client `sessions` service
|
|
153
|
+
snapshot (`ids`/`byId`) to offer a picker. It auto-selects the first available
|
|
154
|
+
session, can switch back to **Plugin state only (no session)**, and never invents
|
|
155
|
+
a session id. The selected id becomes `?sessionId=` on the inspect GET; changing
|
|
156
|
+
the selection aborts the stale request. The page renders the native
|
|
157
|
+
`lastCompaction` projection (trigger, status, duration, before/after tokens,
|
|
158
|
+
mode, real cache usage, error), `handoffContinuation` (scheduled/skipped plus
|
|
159
|
+
blockers and "waiting for the user"), and deduplicated `diagnostics`. It also
|
|
160
|
+
names the serving engine: for a foreign-preset session it says a different
|
|
161
|
+
compaction engine runs and does not draw a fake budget/preview, while still
|
|
162
|
+
showing that session's real compaction record. Unknown values stay unknown; only
|
|
163
|
+
provider-reported cache usage is shown, never an estimated savings rate.
|
|
164
|
+
|
|
165
|
+
### Model, effort, budget, cache, and advanced fields
|
|
166
|
+
|
|
167
|
+
- **Model/effort.** An empty provider/model pair follows the session; an
|
|
168
|
+
explicit pair is saved atomically and never changes the main session model. A
|
|
169
|
+
model that is not in the host's allowed candidate list is kept and marked
|
|
170
|
+
(the list is host metadata, not a silent replacement rule), and a known
|
|
171
|
+
unsupported effort is marked as an error. An empty effort means the selected
|
|
172
|
+
model's default. Candidate metadata comes from
|
|
173
|
+
[GET /better-compact/api/models](#read-only-inspect-and-models-api); when it
|
|
174
|
+
is unavailable the page says capability is unknown and keeps free-text
|
|
175
|
+
provider/model/effort available.
|
|
176
|
+
- **Native mode.** Enhanced reasoning effort and cache priority are visible but
|
|
177
|
+
disabled with "not applicable: the native implementation decides"; the saved
|
|
178
|
+
enhanced values are kept for switching back and are not claimed to have
|
|
179
|
+
applied.
|
|
180
|
+
- **Budget/retention.** The diagram uses `inspect.budget` only: context window,
|
|
181
|
+
current tokens, fixed system/tools prefix, earlier-history region, retained
|
|
182
|
+
tail, the configured `thresholdRatio` plus the effective `thresholdTokens`,
|
|
183
|
+
and the output-reserve/headroom zone after the trigger. It states that the
|
|
184
|
+
exact output-reserve/headroom split is not exposed by the payload. Retention
|
|
185
|
+
is either a ratio or absolute tokens (saving one writes the other as an
|
|
186
|
+
unset op); the resolved safe cut comes from `inspect.preview` or the page says
|
|
187
|
+
"no computable safe cut". Automatic pressure retention is contrasted with the
|
|
188
|
+
more aggressive manual/overflow span, and the retained tail is called a
|
|
189
|
+
budget, not a permanent pin. The before/after composition and
|
|
190
|
+
`afterUpperBoundTokens` are labelled an upper-bound estimate, not a prediction.
|
|
191
|
+
Capacity, fixed-prefix, retention-vs-trigger, and no-safe-region diagnostics
|
|
192
|
+
name the control to adjust.
|
|
193
|
+
- **Cache priority.** The switch copy is exactly
|
|
194
|
+
"优先复用缓存(尽可能保留 KV Cache 前缀)". The explanation covers prefix reuse,
|
|
195
|
+
model-switch cost, and prefix invalidation after a replacement; it never claims
|
|
196
|
+
lossless KV reuse or zero cache loss.
|
|
197
|
+
- **Advanced.** `auto`, `headroomTokens`, `maxTokens`, `compactionRetries`
|
|
198
|
+
(post-compaction pressure retries) and `maxOverflowRetries` (context-overflow
|
|
199
|
+
recoveries) are editable with the two retry meanings stated separately.
|
|
200
|
+
Effective `modelPolicies` are shown read-only; the global form never writes or
|
|
201
|
+
drops them. There are no handoff-directory, compaction-history, memory, or
|
|
202
|
+
sidecar controls.
|
|
203
|
+
|
|
204
|
+
### Saving and apply-on-reload
|
|
205
|
+
|
|
206
|
+
The page edits one local draft and saves only through
|
|
207
|
+
`form.mutate(ops, expectedRevision)` — one atomic mutation per save, one
|
|
208
|
+
expected revision, no plugin-owned write route. It respects the Host form's
|
|
209
|
+
loading/unavailable/read-only state. If the Host revision changes while the
|
|
210
|
+
draft has staged edits, the page raises an explicit conflict and disables save
|
|
211
|
+
with a "reload host values" action; it does not silently overwrite the newer
|
|
212
|
+
configuration. A refused save or a transport error keeps the draft and never
|
|
213
|
+
shows success. On success the page says **saved — reload profile** and explains
|
|
214
|
+
that refreshing the web page does not reload the engine: restart the DSH process
|
|
215
|
+
to apply the new snapshot.
|
|
216
|
+
|
|
217
|
+
### Accessibility and known limits
|
|
218
|
+
|
|
219
|
+
Neutral host surfaces and thin borders are used, with a single accent plus state
|
|
220
|
+
colors only for errors/warnings, tabular numerals, and visible units. The layout
|
|
221
|
+
stacks vertically on narrow screens; the SVGs remain readable; light/dark
|
|
222
|
+
surfaces come from host theme tokens; `prefers-reduced-motion` disables
|
|
223
|
+
transitions. Every control has an accessible label and the diagrams have
|
|
224
|
+
equivalent text. The page is covered by React component tests
|
|
225
|
+
(`react-test-renderer`, fake form/fetch), not by a current-GUI visual check;
|
|
226
|
+
that acceptance belongs to S4. The strategy drawings are illustrations of
|
|
227
|
+
semantics and are not evidence of a live token flow, cache hit rate, or a
|
|
228
|
+
successful compaction.
|
|
229
|
+
|
|
230
|
+
## Configuration
|
|
231
|
+
|
|
232
|
+
The host companion row `dsh-better-compact` owns the schema. Native key names,
|
|
233
|
+
units, and meanings are preserved from `@deepseek-ai/dsh-compaction-basic`
|
|
234
|
+
`0.2.1-rc.2`; enhanced-only keys are stripped before the native base sees them.
|
|
235
|
+
|
|
236
|
+
| Key | Default | Constraint / meaning |
|
|
237
|
+
| --- | --- | --- |
|
|
238
|
+
| `mode` | `goal` | `native` / `goal` / `handoff`. |
|
|
239
|
+
| `auto` | `true` | Turns automatic pressure/overflow recovery on or off; manual `/compact` still works. |
|
|
240
|
+
| `thresholdRatio` | `0.8` | Request-pressure fraction in `(0, 1)`; the trigger is `floor(min(W × ratio, W − O − headroom))`. |
|
|
241
|
+
| `headroomTokens` | `65536` | Non-negative extra pressure headroom beyond the routed output reservation `O`. |
|
|
242
|
+
| `retainRatio` / `retainTokens` | ratio `0.16` | Mutually exclusive recent-tail budgets; absolute tokens must be below the resolved trigger. Exactly one form is stored. |
|
|
243
|
+
| `summarizationProvider` / `summarizationModel` | both `""` | Empty pair = inherited route (latest routed request, then AgentOptions). Non-empty pair must be set together. |
|
|
244
|
+
| `reasoningEffort` | `""` | Empty = the selected summary model's own default. Non-empty must be an effort id that model actually advertises. |
|
|
245
|
+
| `cachePriority` | `true` | Prefer keeping the conversation's system/tools/history prefix so the auxiliary call is a genuine prefix; `false` sends a leaner request without tool declarations. |
|
|
246
|
+
| `maxTokens` | follows resolved `headroomTokens` | Positive summary output cap, counted with input against the model context window. |
|
|
247
|
+
| `compactionRetries` | `1` | Extra attempts when pressure remains after a successful compaction (not a network retry count). |
|
|
248
|
+
| `maxOverflowRetries` | `1` | Bounded provider-confirmed context-overflow recoveries; `0` disables recovery. |
|
|
249
|
+
| `modelPolicies` | `[]` | Exact `{provider, model, ...native partial policy}` overrides with native semantics; no visual rule editor in this version. |
|
|
250
|
+
|
|
251
|
+
The unresolved checks are enforced at load/reload and by the visual settings form:
|
|
252
|
+
mutually exclusive retention forms, an empty/non-empty summarization pair,
|
|
253
|
+
`retainRatio < thresholdRatio`, positive output caps, duplicate/unknown model
|
|
254
|
+
policies, zero headroom without an explicit `maxTokens`, and positive integer
|
|
255
|
+
counts. Unknown **top-level** keys and unknown keys inside a `modelPolicies`
|
|
256
|
+
entry are rejected at load with the offending key and the supported key list, so
|
|
257
|
+
a typo in the manual `cordis.patch.yml` path cannot be silently ignored.
|
|
258
|
+
Invalid saved values fail the row reload with an actionable message instead of
|
|
259
|
+
silently coercing.
|
|
260
|
+
|
|
261
|
+
### Apply-on-reload
|
|
262
|
+
|
|
263
|
+
All fields are **volatile**: the native settings service can save them without
|
|
264
|
+
remounting the row, and revision/CAS prevents silent overwrites. The engine
|
|
265
|
+
therefore takes one frozen snapshot at load:
|
|
266
|
+
|
|
267
|
+
- `activeConfig` in the inspect response is what the running engine holds;
|
|
268
|
+
- `requiresReload` becomes `true` when the saved value differs from that
|
|
269
|
+
snapshot;
|
|
270
|
+
- a save **never** reconfigures a running engine;
|
|
271
|
+
- restart the DSH profile to take the new snapshot.
|
|
272
|
+
|
|
273
|
+
This is the closest honest mapping to DSH 0.2.1-rc.2, which has no host "saved
|
|
274
|
+
but requires reload" state of its own.
|
|
275
|
+
|
|
276
|
+
### Reasoning effort and cache priority
|
|
277
|
+
|
|
278
|
+
- `native` mode reports these options as **not applicable**: the inherited
|
|
279
|
+
native summarizer decides its own request shape. Saved enhanced values are
|
|
280
|
+
kept for switching back.
|
|
281
|
+
- In `goal`/`handoff`, a non-empty effort is validated against
|
|
282
|
+
`ctx.llm.resolveModelInfo()` for the exact summary model. An unsupported id is
|
|
283
|
+
rejected before provider I/O; it is never replaced by `off`/`high`/another
|
|
284
|
+
model. Unknown capability is treated as unsupported for an explicit value.
|
|
285
|
+
- `cachePriority: true` reuses the native prefix shape (system prompt, tool
|
|
286
|
+
schema/tool history, shadowed messages, then the instruction at the end).
|
|
287
|
+
`cachePriority: false` omits tool declarations and tool history; it is not a
|
|
288
|
+
promise that the provider disables its cache.
|
|
289
|
+
- Changing the summary model/provider can invalidate the warm prefix; that is a
|
|
290
|
+
real trade-off, not a cost this plugin can hide.
|
|
291
|
+
|
|
292
|
+
### Capacity and budget diagnostics
|
|
293
|
+
|
|
294
|
+
The summary request is bounded by the actual selected model route:
|
|
295
|
+
|
|
296
|
+
- trigger/retention follow the native formulas exactly; no second token unit is
|
|
297
|
+
introduced;
|
|
298
|
+
- the auxiliary request is priced with the same fixed estimator
|
|
299
|
+
(`estimateMessage` / `estimateToolsTokens`);
|
|
300
|
+
- if the full authoritative state does not fit, the state is reduced in a fixed
|
|
301
|
+
order — drop the usually-large todo list first while keeping pending
|
|
302
|
+
questions/jobs/running children, then goal-only, then no state — and the
|
|
303
|
+
instruction carries an explicit `## Budget note` naming exactly what was
|
|
304
|
+
omitted. The omission is also exposed in inspect `diagnostics`;
|
|
305
|
+
- if even the state-free request cannot fit, the attempt fails with
|
|
306
|
+
`BETTER_COMPACT_BUDGET` and an actionable message (compact a smaller span,
|
|
307
|
+
lower `maxTokens`, or choose a larger model). The plugin never silently
|
|
308
|
+
truncates or shrinks history.
|
|
309
|
+
|
|
310
|
+
### Retention reality
|
|
311
|
+
|
|
312
|
+
`retainRatio`/`retainTokens` apply to the **automatic pressure** path only.
|
|
313
|
+
Native manual `compactNow` and context-overflow recovery deliberately use
|
|
314
|
+
`retainTokens = 0` (maximal balanced reduction), unchanged from the installed
|
|
315
|
+
engine. The retained tail is a budget, not a permanent pin: overflow recovery may
|
|
316
|
+
choose a more aggressive safe span. The preview in the inspect API mirrors the
|
|
317
|
+
automatic pressure rule; it does not promise the manual/overflow range.
|
|
318
|
+
|
|
319
|
+
## Modes in detail
|
|
320
|
+
|
|
321
|
+
### `goal` (default)
|
|
322
|
+
|
|
323
|
+
The summarizer reads the live authoritative goal (`ctx.goals.get(agent)` when
|
|
324
|
+
present), the current todo projection, pending user questions, jobs owned by the
|
|
325
|
+
session, and running child agents; all are read-only and optional. The
|
|
326
|
+
instruction asks for:
|
|
327
|
+
|
|
328
|
+
- current goal and unresolved user requests;
|
|
329
|
+
- constraints, preferences, and approved boundaries;
|
|
330
|
+
- decisions and reasons;
|
|
331
|
+
- completed / in progress / blocked work;
|
|
332
|
+
- verified vs unverified results;
|
|
333
|
+
- exact artifacts, files, commands, errors, identifiers;
|
|
334
|
+
- next actions.
|
|
335
|
+
|
|
336
|
+
Rules enforced by the prompt and output check: newer history outranks older
|
|
337
|
+
checkpoints; prior checkpoint goal state is not authoritative; incomplete work
|
|
338
|
+
must not be marked complete; pending questions/jobs/children are facts to wait
|
|
339
|
+
for, not things to answer/stop; secrets should be redacted; exact identifiers
|
|
340
|
+
should be preserved; output must contain every required section. The check is a
|
|
341
|
+
degenerate/truncation guard, **not** proof that no fact was lost.
|
|
342
|
+
|
|
343
|
+
If no goal service or no goal exists, compaction still runs normally; the
|
|
344
|
+
prompt explicitly says not to infer a goal from old checkpoint text. The native
|
|
345
|
+
`todo` projection is turn-scoped (`turn/start` resets it), so an automatic
|
|
346
|
+
pressure compaction at the start of a turn usually has no todo list to inject;
|
|
347
|
+
manual idle compaction normally sees the previous turn's list. No workaround
|
|
348
|
+
copies the list into another store.
|
|
349
|
+
|
|
350
|
+
### `handoff`
|
|
351
|
+
|
|
352
|
+
The model writes a handoff document for another agent continuing *this same
|
|
353
|
+
session*, with the same authority rules plus a `Suggested Skills` section that
|
|
354
|
+
is advisory only. Then:
|
|
355
|
+
|
|
356
|
+
1. a fresh per-attempt directory is created under the OS temp directory
|
|
357
|
+
(`os.tmpdir()`, so Unix `TMPDIR` or `/tmp`);
|
|
358
|
+
2. the final document — including the **actual** file path, session id, and
|
|
359
|
+
creation time in a deterministic header — is written with owner-only
|
|
360
|
+
permissions, read back byte-for-byte, and verified by platform:
|
|
361
|
+
on POSIX the file must report mode `0600` with a `0700` attempt directory;
|
|
362
|
+
Windows has no POSIX mode bits (`stat.mode` only reflects the read-only
|
|
363
|
+
attribute), so the POSIX assertion is skipped there and the proof is the
|
|
364
|
+
per-user OS temp directory plus the exact read-back. An ACL check is not
|
|
365
|
+
practical with Node's standard library and is not claimed;
|
|
366
|
+
3. the same document is returned as the summary body, so the native checkpoint
|
|
367
|
+
replacement is self-contained; deleting the file later does not break replay;
|
|
368
|
+
4. if and only if this was a live, successful **manual** `compactNow`, the agent
|
|
369
|
+
was idle with no queued input, the goal is not paused/blocked, and there is no
|
|
370
|
+
pending user question, running job, or running child agent, exactly one
|
|
371
|
+
ordinary follow-up is enqueued through the native same-agent input mechanism
|
|
372
|
+
(`agent.followup(createUserMessage(...))`).
|
|
373
|
+
|
|
374
|
+
The continuation never resumes/rearms a goal, answers a question, starts a
|
|
375
|
+
required turn during an in-flight turn, creates/switches a session, or uses a
|
|
376
|
+
sidecar. A replay, page refresh, or plugin reload cannot re-trigger it because
|
|
377
|
+
the trigger is the live manual call, not a session event. If the process dies
|
|
378
|
+
between a successful commit and enqueue, the checkpoint is already durable but
|
|
379
|
+
the continuation is not; that gap is real and is not claimed atomic. The user
|
|
380
|
+
can continue manually in the same session.
|
|
381
|
+
|
|
382
|
+
Handoff files are not scheduled for cleanup; they live with the OS temp
|
|
383
|
+
directory. They are a convenience copy, never the only recovery source. Secret
|
|
384
|
+
redaction is conservative pattern matching (`sk-*`, GitHub/Slack/AWS/Google
|
|
385
|
+
tokens, bearer tokens, private-key blocks, assignment-style passwords/secrets);
|
|
386
|
+
it cannot recognize every credential format.
|
|
387
|
+
|
|
388
|
+
## Failure, cancellation, and recovery
|
|
389
|
+
|
|
390
|
+
| Situation | Behavior |
|
|
391
|
+
| --- | --- |
|
|
392
|
+
| Automatic pressure summary fails before replacement | Warning; the latest durable surface is kept (pruning or an earlier replacement may already have landed) and the turn continues. A later turn may compact successfully. |
|
|
393
|
+
| Manual summary fails | The native command reports a `ManualCompactionError`-based error (`could not produce a useful summary`); the attempt is recorded; no checkpoint is installed. |
|
|
394
|
+
| Provider-confirmed context overflow | Native bounded recovery (`maxOverflowRetries`); on failure the real request error is preserved. |
|
|
395
|
+
| Replacement appended, durability flush fails | Native `ManualCompactionError('persistence')`: history may have changed but saving failed; no rollback is claimed. `lastCompaction.status` is `persistence-error` when this process observed it. |
|
|
396
|
+
| `compaction/end` cannot be appended | Native commit-stage failure: the summary/replacement may already be durable while the bracket stays open; the command reports “did not finish cleanly”. Inspect reports `failed` with the recorded error; no rollback or fake `completed` is claimed. |
|
|
397
|
+
| Cancellation / pending user / paused goal | Summarization or continuation stops; no goal is revived; no question is answered for the user. |
|
|
398
|
+
| No safe useful range | Native no-op/`null` with a reason; no fake checkpoint. |
|
|
399
|
+
| Invalid model/effort/capacity | Actionable error; never a silent native or model fallback. |
|
|
400
|
+
| Empty/truncated/degenerate enhanced output | Rejected before installation (`BETTER_COMPACT_STRUCTURE`, `MAX_TOKENS`); the native error path applies. |
|
|
401
|
+
|
|
402
|
+
Enhanced modes never fall back to native mode automatically. Native mode itself
|
|
403
|
+
is the inherited implementation, byte-compatible with the same native config.
|
|
404
|
+
As the design requires: an external native recovery listener
|
|
405
|
+
(`compaction/summary-error`) may already have written durable changes before a
|
|
406
|
+
later failure; "automatic compaction failed" therefore never means "the session
|
|
407
|
+
has no side effects". Inspect preserves the recorded attempt and its error
|
|
408
|
+
instead of claiming a clean rollback.
|
|
409
|
+
|
|
410
|
+
## Read-only inspect and models API
|
|
411
|
+
|
|
412
|
+
`GET /better-compact/api/inspect`, optional `?sessionId=<id>`.
|
|
413
|
+
|
|
414
|
+
`GET /better-compact/api/models` (no session) returns the host's allowed
|
|
415
|
+
candidate routes with their advertised reasoning efforts:
|
|
416
|
+
|
|
417
|
+
```json
|
|
418
|
+
{
|
|
419
|
+
"enabled": true,
|
|
420
|
+
"routes": [{ "provider": "p", "model": "m", "efforts": ["low", "high"], "defaultEffort": "low" }]
|
|
421
|
+
}
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
The models projection reads the optional host `subagentModelSelection` service's
|
|
425
|
+
`current().allowedModels` and enriches each entry with
|
|
426
|
+
`ctx.llm.resolveModelInfo(provider, model).reasoning.efforts`. It is metadata
|
|
427
|
+
only: no provider I/O, no CLI call, and no model enumeration happens on page
|
|
428
|
+
load. If the service is absent or has no catalog the response is
|
|
429
|
+
`{ "enabled": false, "routes": [] }`; a failed per-route capability lookup only
|
|
430
|
+
empties that route's `efforts`, and the visual page falls back to free-text
|
|
431
|
+
provider/model/effort with an "unknown" capability note. The list is the host's
|
|
432
|
+
allowed-candidate metadata, not proof that a route is usable in the current
|
|
433
|
+
session.
|
|
434
|
+
|
|
435
|
+
Both routes are registered only when **both** a Host webserver and the Host
|
|
436
|
+
`connection` trust fence exist. A webserver without the fence fails closed: the
|
|
437
|
+
routes are not registered and an already-registered handler returns 503 if the
|
|
438
|
+
fence disappears. Every request passes `connection.requestRejection(req)`
|
|
439
|
+
(401/403); only `GET` is accepted (405), the inspect query is validated (400 for
|
|
440
|
+
unknown/duplicate/empty `sessionId`), an unknown/invisible session is 404, and
|
|
441
|
+
every JSON response carries `cache-control: no-store`. The models route is
|
|
442
|
+
session-free and ignores query parameters. There is no write API, no cross-origin
|
|
443
|
+
allowance, and no plugin-owned trust switch. The visual settings page uses these
|
|
444
|
+
payloads as its only source of truth for token/session/status values.
|
|
445
|
+
|
|
446
|
+
Response fields (unknown values are `null`, never `0`):
|
|
447
|
+
|
|
448
|
+
| Field | Meaning |
|
|
449
|
+
| --- | --- |
|
|
450
|
+
| `activeConfig` | The frozen configuration this engine instance holds. No credentials. |
|
|
451
|
+
| `requiresReload` | Saved value differs from `activeConfig` (including a saved value that no longer normalizes). |
|
|
452
|
+
| `sessionId` | Echo of the requested session, or `null` without one. |
|
|
453
|
+
| `diagnostics` | Always an array of read-only strings for the visual settings page. It carries token-meter/model-info failures, budget-capacity reasons, last-summary budget omissions, and the engine-applicability reason (empty when everything is available). |
|
|
454
|
+
| `engine` | `null` without a session, else `{owner, inspectApplies, reason}`. `owner` is `host`, `better-compact-preset`, `foreign-preset`, or `unknown`. A **foreign preset** (the shipped Web `standard` preset, for example) runs its own native engine, so `inspectApplies: false`, `budget`/`preview` are `null`, and `lastCompaction` is still projected from that session's real events. A cold/non-live session cannot prove ownership and is reported `unknown` with the active configuration. |
|
|
455
|
+
| `budget` | `null` without a session, else `{contextWindow, currentTokens, measurement, fixedTokens, thresholdTokens, retainTokens, summaryMaxTokens}`. `measurement` is `measured` (provider-usage anchor), `estimated` (fixed heuristic), or `unavailable`. |
|
|
456
|
+
| `preview` | `null` without a usable session, else `{beforeTokens, afterUpperBoundTokens, rangeStart, rangeEnd, reason}`. `rangeStart`/`rangeEnd` are native surface seqs from the automatic-pressure safe-cut rule; `afterUpperBoundTokens` includes the summary output budget and is an upper-bound estimate, not a prediction. `reason` explains below-trigger/no-range/unavailable. |
|
|
457
|
+
| `lastCompaction` | `null` when no compaction exists, else `{id, trigger, status, mode, beforeTokens, afterTokens, durationMs, cacheReadTokens, cacheWriteTokens, error}` projected only from real events/usage. |
|
|
458
|
+
| `handoffContinuation` | `null` until this process makes a manual handoff continuation decision, else `{compactionId, status: 'scheduled'\|'skipped', blockers, at}`. `blockers` names queued input, paused/blocked goal, pending question, running job, running child, cancellation, or a queue failure so the visual settings page can show why nothing continued. |
|
|
459
|
+
|
|
460
|
+
`lastCompaction` rules:
|
|
461
|
+
|
|
462
|
+
- `trigger`: `manual` for a standalone (`turn: null`) compaction, `automatic`
|
|
463
|
+
for a turn-owned one; the log cannot distinguish pressure from overflow, so
|
|
464
|
+
both are `automatic`.
|
|
465
|
+
- `status`: `running | completed | failed | cancelled | persistence-error`.
|
|
466
|
+
`running` is claimed only for a live in-memory summary attempt; an orphaned
|
|
467
|
+
unmatched start is reported as `failed`, not as a phantom active process.
|
|
468
|
+
`cancelled` is recognized from the recorded abort/cancel error.
|
|
469
|
+
`persistence-error` is only knowable from this process's own observation after
|
|
470
|
+
a successful append failed its flush; the persisted log alone looks
|
|
471
|
+
completed.
|
|
472
|
+
- `mode` is `native`/`goal`/`handoff` only when the checkpoint's structure
|
|
473
|
+
proves it; older/foreign records are `null`.
|
|
474
|
+
- A recorded commit/append failure on an open bracket is surfaced in `error`
|
|
475
|
+
rather than reported as a silent `failed` with `error: null`.
|
|
476
|
+
- `cacheReadTokens`/`cacheWriteTokens` are reported only when the summary event
|
|
477
|
+
carried real provider usage; absence is not zero. No “savings percentage” is
|
|
478
|
+
fabricated.
|
|
479
|
+
|
|
480
|
+
Read-only guarantee: inspection performs no LLM generation, no compaction, no
|
|
481
|
+
CLI call, no temp-file write, and no goal/session mutation. Cold sessions are
|
|
482
|
+
read through `ctx.sessionQuery.observeSession` and replayed into a detached
|
|
483
|
+
`Session` for the same singleton token meter; if that reconstruction or the
|
|
484
|
+
meter fails, the affected numbers are unavailable rather than computed by a
|
|
485
|
+
second algorithm.
|
|
486
|
+
|
|
487
|
+
## Host drift and limitations (DSH 0.2.1-rc.2)
|
|
488
|
+
|
|
489
|
+
- Web compaction is **preset-owned**; the bundle cannot replace it globally.
|
|
490
|
+
The host-plane engine exists for headless/TUI and for presets without a
|
|
491
|
+
compaction group, and detects preset-owned engines to avoid double work.
|
|
492
|
+
- There is no host reload state. Saving is volatile and immediate; load-time
|
|
493
|
+
snapshot changes need a profile restart.
|
|
494
|
+
- The shipped `preset-better-compact` is a pinned copy of the rc.2 standard
|
|
495
|
+
preset. Re-verify/refresh it when changing DSH baseline.
|
|
496
|
+
- The inspect API has no separate authentication beyond the Host trust fence.
|
|
497
|
+
- One compaction provider per context/realm only. Do not mount a second
|
|
498
|
+
compaction engine row beside this package; two explicit bundles are not
|
|
499
|
+
recommended.
|
|
500
|
+
|
|
501
|
+
## Licensing
|
|
502
|
+
|
|
503
|
+
The package is GPL-3.0-or-later. Small substantive parts adapted from the MIT
|
|
504
|
+
DeepSeek Harness packages (budget formulas, summarization request shape, verbose
|
|
505
|
+
preset copy) remain under MIT; copyright and license text are preserved in
|
|
506
|
+
[third-party/LICENSE-MIT-dsh](third-party/LICENSE-MIT-dsh) and described in
|
|
507
|
+
[third-party/NOTICE](third-party/NOTICE). The goal/handoff prompt text is new;
|
|
508
|
+
pi-atlas, xbot, OpenCode, and Codex are cited design references, not copied
|
|
509
|
+
source.
|
|
510
|
+
|
|
511
|
+
## Verification status
|
|
512
|
+
|
|
513
|
+
Verified in this repository (see [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md)):
|
|
514
|
+
|
|
515
|
+
- unit tests (`node --test`) for config validation/defaults, native budget
|
|
516
|
+
formulas and safe-cut selection, prompt/state/redaction, handoff file
|
|
517
|
+
permissions/read-back, engine routing/effort/failure/continuation gating,
|
|
518
|
+
inspect projection/null semantics/fence, models-route projection/fence,
|
|
519
|
+
preset copy, and the visual client's mode cards, model/effort, budget,
|
|
520
|
+
retention, save/revision/conflict/transport, status and accessibility states
|
|
521
|
+
with fake form/fetch;
|
|
522
|
+
- a real-profile smoke with a temp `DSH_HOME`, scripted LLM adapter, real
|
|
523
|
+
AgentLoop/session log/token meter/commands/settings/config-editor/webserver,
|
|
524
|
+
real Connection authentication, real job registry, and real child agents:
|
|
525
|
+
automatic pressure, manual `/compact`, native event sequence, authenticated
|
|
526
|
+
inspect over HTTP (401/400/404/405/no-store), the models route over the same
|
|
527
|
+
fence and its real allowed-model catalog (401/405/no-store), failure/recovery,
|
|
528
|
+
handoff file + redacted raw output + one continuation + replay after file
|
|
529
|
+
deletion, a real standard-preset foreign-engine session, injected
|
|
530
|
+
durability-flush failure (`session/flush` listener; the real JSONL writer still
|
|
531
|
+
runs), in-flight cancellation, commit-stage append failure, pending-
|
|
532
|
+
question/job/child continuation gates, preset isolation/no double-compaction,
|
|
533
|
+
and save → reload → `activeConfig` update. Manual handoff continuation is
|
|
534
|
+
exercised on the host-plane engine; the preset engine's automatic path is
|
|
535
|
+
covered, and the preset manual path shares the same engine code. Run the exact
|
|
536
|
+
commands for the current test counts.
|
|
537
|
+
|
|
538
|
+
The current build was verified with **115 unit/component tests** and **45
|
|
539
|
+
real-profile smoke checks** at commit time; always prefer the fresh command
|
|
540
|
+
output over these numbers.
|
|
541
|
+
|
|
542
|
+
**Not verified here:** real-model summary fidelity, reasoning-effort quality,
|
|
543
|
+
provider cache-read/write hit rates against a real provider, and a visual/
|
|
544
|
+
keyboard acceptance pass in the current GUI. The visual settings behavior above
|
|
545
|
+
is covered by React component tests and the backend routes by the real-profile
|
|
546
|
+
smoke; the current GUI/URL check belongs to S4. Do not infer GUI acceptance from
|
|
547
|
+
the controlled-provider or component tests.
|
package/cordis.patch.yml
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Standalone dsh-better-compact bundle patch.
|
|
2
|
+
#
|
|
3
|
+
# One package, two rows, one engine realm each:
|
|
4
|
+
#
|
|
5
|
+
# * `dsh-better-compact` is the HOST COMPANION row: it owns the volatile
|
|
6
|
+
# configuration schema, the load-time frozen snapshot service
|
|
7
|
+
# (`betterCompactConfig`), the read-only GET inspect route, and the browser
|
|
8
|
+
# module entry. It never registers a compaction service.
|
|
9
|
+
# * `dsh-better-compact-engine` is the ENGINE row used by profiles with no
|
|
10
|
+
# agent presets (headless/TUI, or any composition that keeps the host-plane
|
|
11
|
+
# agent plane). Web sessions get their own engine from the
|
|
12
|
+
# `better-compact` agent preset below.
|
|
13
|
+
#
|
|
14
|
+
# The host Basic engine is disabled here. In the shipped Web composition it is
|
|
15
|
+
# already disabled by `@deepseek-ai/dsh-web-app/cordis.patch.yml` and mounted
|
|
16
|
+
# inside each preset; that patch cannot address preset-internal rows, which is
|
|
17
|
+
# why the `better-compact` preset below carries its own engine copy instead of
|
|
18
|
+
# trying to reconfigure the shipped presets.
|
|
19
|
+
#
|
|
20
|
+
# `command-compact` and `tool-result-pruner` are deliberately not touched:
|
|
21
|
+
# headless keeps the base rows, and the Web composition keeps them inside each
|
|
22
|
+
# preset's isolated group. A session that selects another preset keeps that
|
|
23
|
+
# preset's compaction engine; the host-plane engine below detects a
|
|
24
|
+
# preset-owned `compaction` service and stays out of the way.
|
|
25
|
+
- id: compaction-basic
|
|
26
|
+
disabled: true
|
|
27
|
+
|
|
28
|
+
- insert:
|
|
29
|
+
- id: dsh-better-compact
|
|
30
|
+
name: dsh-better-compact
|
|
31
|
+
|
|
32
|
+
- id: dsh-better-compact-engine
|
|
33
|
+
name: dsh-better-compact/engine
|