smart-compaction 0.1.0 → 0.2.1

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.md CHANGED
@@ -1,9 +1,10 @@
1
1
  # smart-compaction
2
2
 
3
3
  A [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (dsh) plugin that gives the
4
- model a `compact_now` tool, so it can trigger compaction itself at a point *it* knows is safe —
5
- right after finishing a step, never mid-edit — instead of only ever being interrupted by dsh's
6
- automatic token-threshold trigger.
4
+ model two tools: `compact_now`, so it can trigger compaction itself at a point *it* knows is safe
5
+ — right after finishing a step, never mid-edit — instead of only ever being interrupted by dsh's
6
+ automatic token-threshold trigger; and `context_status`, so it can check real, current token
7
+ usage against its actual context window instead of guessing whether a conversation "feels long."
7
8
 
8
9
  ## Why
9
10
 
@@ -19,7 +20,9 @@ blind token counter.
19
20
 
20
21
  ## What it does
21
22
 
22
- One tool: **`compact_now`**. No arguments.
23
+ Two tools, both no-argument.
24
+
25
+ **`compact_now`**
23
26
 
24
27
  - Selects the largest currently-compactable span of conversation history — the same
25
28
  tool-call-pairing-safe boundary logic dsh's own compaction already guarantees, reimplemented
@@ -31,6 +34,21 @@ One tool: **`compact_now`**. No arguments.
31
34
  - No-ops harmlessly (`"Not enough compactable history yet."`) if there isn't enough history yet —
32
35
  safe for the model to call speculatively.
33
36
 
37
+ **`context_status`**
38
+
39
+ - Read-only — never modifies the session, safe to call anytime.
40
+ - Reports current token usage and the model's actual context-window size for *this* session,
41
+ e.g. `"~42,300 / 77,824 tokens used (54.3%)."`
42
+ - Reads dsh's own `contextPressure` session projection (registered by
43
+ `@deepseek-ai/dsh-token-meter`, mounted wherever `compaction-basic` is) via the public
44
+ `ctx.sessionProjections.stateOf()` API — the same numbers the web UI's own context meter
45
+ reads. Nothing is hardcoded: the context-window figure comes from whatever model this
46
+ session is actually routed to, so it's correct unchanged across different models, profiles,
47
+ and context-window sizes.
48
+ - Exists because, without it, the model has zero visibility into its own context usage — the
49
+ only prior signal was a vague "if the conversation feels long" in `compact_now`'s own
50
+ description.
51
+
34
52
  ## How it works
35
53
 
36
54
  Two entry points exist on dsh's compaction service: `compactNow()` (what the human `/compact`
@@ -56,24 +74,41 @@ array yourself.)
56
74
  No build step, no config. Restart your dsh service after adding it — new bundles are only picked
57
75
  up on boot.
58
76
 
59
- **Requires a `compaction` service on your profile.** Most profile templates ship one, but not all
60
- do (e.g. `@deepseek-ai/dsh-web-app`-based profiles don't by default). If yours doesn't, `compact_now`
61
- still installs cleanly (it won't break your profile's boot) but returns an error every time it's
62
- called: `"no compaction service is configured on this profile"`. Add a `compaction-basic` bundle to
63
- get one.
77
+ **`compact_now` requires a `compaction` service on your profile.** Most profile templates ship
78
+ one, but not all do (e.g. `@deepseek-ai/dsh-web-app`-based profiles don't by default). If yours
79
+ doesn't, `compact_now` still installs cleanly (it won't break your profile's boot) but returns an
80
+ error every time it's called: `"no compaction service is configured on this profile"`. Add a
81
+ `compaction-basic` bundle to get one.
64
82
 
65
- ### Tell the model when to use it
83
+ **`context_status` requires `@deepseek-ai/dsh-token-meter` mounted** (it registers the
84
+ `contextPressure` projection this tool reads). It's normally pulled in wherever `compaction-basic`
85
+ is, so if `compact_now` works, `context_status` should too. If it isn't mounted, the tool still
86
+ installs cleanly and just reports `available: false` instead of erroring.
66
87
 
67
- `compact_now` only *offers* the capability — nothing calls it unless instructed to. Add something
68
- like this to your `AGENTS.md` (or whatever your profile injects as standing instructions):
69
-
70
- > After marking a todo item `completed` (never while one is `in_progress`), if the conversation
71
- > has gotten long, call `compact_now`. It's safe to call speculatively — it no-ops if there isn't
72
- > enough history to compact yet.
88
+ ### Tell the model when to use it
73
89
 
74
- Tying it to todo-completion matters: it's a real, already-tracked signal for "I just finished a
75
- self-contained unit of work," instead of asking the model to estimate its own remaining work,
76
- which it's generally bad at.
90
+ This is baked into both tools' own descriptions, so it works out of the box with no setup: each
91
+ tool's description tells the model to check `context_status` right after finishing a
92
+ self-contained step (e.g. right after `todo_write` marks an item `completed`) and to follow up
93
+ with `compact_now` once usage climbs past roughly 70-90%. Since both descriptions are sent to the
94
+ model on every request automatically, no `AGENTS.md` edit is required for this behavior — unlike
95
+ an early version of this plugin, which relied entirely on a hand-written `AGENTS.md` rule and
96
+ (measured directly against real session logs) got essentially no organic use as a result: the
97
+ description alone wasn't a strong enough signal.
98
+
99
+ That said, standing instructions carry more weight than a tool description competing against
100
+ everything else in a long tool catalog. If you want to reinforce it further, add something like
101
+ this to your `AGENTS.md` (or whatever your profile injects as standing instructions):
102
+
103
+ > After marking a todo item `completed` (never while one is `in_progress`), call `context_status`.
104
+ > If usage is climbing past roughly 70-80% of the context window, call `compact_now` too. Both are
105
+ > safe to call speculatively — `context_status` is read-only, and `compact_now` no-ops if there
106
+ > isn't enough history to compact yet.
107
+
108
+ Tying this to todo-completion matters: it's a real, already-tracked signal for "I just finished a
109
+ self-contained unit of work," instead of asking the model to estimate its own remaining work or
110
+ guess whether a conversation "feels long," both of which it's generally bad at. `context_status`
111
+ replaces that guess with the real number.
77
112
 
78
113
  ## Building from source
79
114
 
@@ -86,8 +121,8 @@ pnpm install
86
121
  node test.js
87
122
  ```
88
123
 
89
- `test.js` is a pure unit-test check of the range-selection logic (`select-range.js`) — no live
90
- dsh/Ollama session required.
124
+ `test.js` is a pure unit-test check of the range-selection logic (`select-range.js`) and the
125
+ context-usage summary logic (`context-status.js`) — no live dsh/Ollama session required.
91
126
 
92
127
  ## Configuration
93
128
 
@@ -95,7 +130,7 @@ None. The tool takes no arguments and needs no setup beyond installing it.
95
130
 
96
131
  ## Status
97
132
 
98
- Verified end-to-end against a real dsh session, including:
133
+ `compact_now` verified end-to-end against a real dsh session, including:
99
134
 
100
135
  - Basic call: the tool loads, the model calls it, it selects a valid boundary-safe range, and it
101
136
  drives `compactRegion()` mid-turn without ever hitting the `busy` failure this design exists to
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Pure summary of dsh's own `contextPressure` session-projection state.
3
+ *
4
+ * That projection is registered by `@deepseek-ai/dsh-token-meter` (mounted
5
+ * on any profile that runs `compaction-basic`) and read here through the
6
+ * public `ctx.sessionProjections.stateOf(session, 'contextPressure')` API —
7
+ * the exact same numbers the web UI's own context meter reads. Nothing about
8
+ * a model or its context-window size is hardcoded: `contextWindow` is
9
+ * whatever the last `request/context` event actually logged for whichever
10
+ * model this session is really routed to, so this works unchanged across
11
+ * profiles, models, and context-window sizes.
12
+ *
13
+ * The `usedTokens` formula mirrors token-meter's own private
14
+ * `contextPressureProjectionDefinition.wire.view` (not part of that
15
+ * package's public exports — see the doc comment above it in
16
+ * `usage-projection.ts` for the reasoning): prompt-side pressure from the
17
+ * last real provider usage sample, adjusted by how much the tracked surface
18
+ * has grown or shrunk (e.g. via compaction) since that sample was taken.
19
+ * Before any request has completed this session, no usage sample exists yet
20
+ * — `surfaceTokens` alone is the best available estimate, flagged
21
+ * `estimated: true`.
22
+ *
23
+ * @see HANDOFF.md for the full research trail behind this file.
24
+ */
25
+
26
+ /**
27
+ * @param {import('@deepseek-ai/dsh-token-meter').ContextPressureState | undefined} state
28
+ * @returns {{
29
+ * available: boolean,
30
+ * contextWindow?: number,
31
+ * usedTokens?: number,
32
+ * percentUsed?: number,
33
+ * estimated?: boolean,
34
+ * }}
35
+ */
36
+ export function summarizeContextUsage(state) {
37
+ if (state === undefined) return { available: false }
38
+ if (state.contextWindow === undefined) {
39
+ return { available: true, usedTokens: state.surfaceTokens, estimated: true }
40
+ }
41
+
42
+ const hasSample = state.pressureTokens !== undefined && state.sampledSurfaceTokens !== undefined
43
+ const usedTokens = hasSample
44
+ ? Math.max(0, state.pressureTokens + state.surfaceTokens - state.sampledSurfaceTokens)
45
+ : state.surfaceTokens
46
+
47
+ return {
48
+ available: true,
49
+ contextWindow: state.contextWindow,
50
+ usedTokens,
51
+ percentUsed: Math.round((usedTokens / state.contextWindow) * 1000) / 10,
52
+ estimated: !hasSample,
53
+ }
54
+ }
package/cordis.patch.yml CHANGED
@@ -1,5 +1,6 @@
1
1
  # This bundle's own default layer: mounts the plugin under id
2
- # `tool-compact-now`. Nothing to configure — the tool takes no config.
2
+ # `tool-compact-now`. One module registers both the `compact_now` and
3
+ # `context_status` tools — nothing to configure, the tool takes no config.
3
4
  - insert:
4
5
  - id: tool-compact-now
5
6
  name: 'smart-compaction'
package/index.js CHANGED
@@ -1,7 +1,10 @@
1
1
  /**
2
2
  * dsh plugin: registers a `compact_now` tool on `ctx.tools` so the model can
3
3
  * voluntarily trigger compaction at a point it judges safe, instead of only
4
- * ever being interrupted by dsh's automatic token-threshold trigger.
4
+ * ever being interrupted by dsh's automatic token-threshold trigger. Also
5
+ * registers `context_status`, so the model can check real, current token
6
+ * usage against its actual context window on demand instead of guessing
7
+ * whether "the conversation feels long" — see context-status.js.
5
8
  *
6
9
  * Calls `ctx.compaction.compactRegion()` directly — NOT `compactNow()` (what
7
10
  * the human `/compact` command uses). `compactRegion` doesn't require an
@@ -14,6 +17,7 @@
14
17
 
15
18
  import { toolPairingBalancedBefore } from '@deepseek-ai/dsh-compaction'
16
19
  import { defineTool } from '@deepseek-ai/dsh-tools'
20
+ import { summarizeContextUsage } from './context-status.js'
17
21
  import { selectCompactableRange } from './select-range.js'
18
22
 
19
23
  export const name = 'tool-compact-now'
@@ -29,9 +33,12 @@ const DESCRIPTION =
29
33
  'Voluntarily compact older conversation history now, at a point you know is safe: '
30
34
  + 'right after finishing a concrete step (e.g. just marked a todo item completed), '
31
35
  + 'never mid-edit or with unfinished work pending. Use this instead of waiting to be '
32
- + 'interrupted by automatic compaction, if the conversation feels like it has gotten '
33
- + 'long. Harmless to call speculatively — it no-ops if there is not enough history to '
34
- + 'compact yet.'
36
+ + 'interrupted by automatic compaction. Call context_status first if unsure whether now '
37
+ + 'is a good time — usage past roughly 70-90% of the context window is a concrete signal '
38
+ + 'to call this proactively, not just a vague feeling that the conversation has gotten '
39
+ + 'long. As a habit: check context_status after every completed step, and call this '
40
+ + 'whenever that check comes back high. Harmless to call speculatively either way — it '
41
+ + 'no-ops if there is not enough history to compact yet.'
35
42
 
36
43
  /**
37
44
  * @param {import('@deepseek-ai/cordis').Context} ctx
@@ -87,4 +94,57 @@ export function apply(ctx) {
87
94
  }
88
95
  },
89
96
  }))
97
+
98
+ ctx.tools.register(defineTool({
99
+ name: 'context_status',
100
+ description: CONTEXT_STATUS_DESCRIPTION,
101
+ parameters: {},
102
+ output: {
103
+ schema: {
104
+ type: 'object',
105
+ additionalProperties: false,
106
+ properties: {
107
+ available: { type: 'boolean', required: true },
108
+ contextWindow: { type: 'integer' },
109
+ usedTokens: { type: 'integer' },
110
+ percentUsed: { type: 'number' },
111
+ estimated: { type: 'boolean' },
112
+ },
113
+ },
114
+ render: (_args, value) => [{ type: 'text', text: renderContextStatus(value) }],
115
+ },
116
+ async execute(_args, exec) {
117
+ if (!exec.agent) {
118
+ throw new Error('context_status requires an owning agent session')
119
+ }
120
+ const projections = ctx.get('sessionProjections')
121
+ if (!projections) return { available: false }
122
+ const state = projections.stateOf(exec.agent.session, 'contextPressure')
123
+ return summarizeContextUsage(state)
124
+ },
125
+ }))
126
+ }
127
+
128
+ const CONTEXT_STATUS_DESCRIPTION =
129
+ 'Check real, current context-window usage for this session: tokens used so far and the '
130
+ + 'model\'s actual context-window size, whatever model this session happens to be running. '
131
+ + 'Call this right after finishing any self-contained step — e.g. right after todo_write '
132
+ + 'marks an item completed, never mid-edit or with unfinished work pending — instead of '
133
+ + 'guessing whether the conversation "feels long." Automatic compaction generally triggers '
134
+ + 'well before the window fills, typically somewhere around 70-90% depending on profile '
135
+ + 'config, so if percentUsed comes back past that range, follow up by calling compact_now '
136
+ + 'proactively rather than wait to be interrupted. Harmless to call anytime — read-only, '
137
+ + 'never modifies the session.'
138
+
139
+ function renderContextStatus(value) {
140
+ if (!value.available) {
141
+ return 'Context usage isn\'t available on this profile (no token-meter service mounted).'
142
+ }
143
+ if (value.contextWindow === undefined) {
144
+ return `~${value.usedTokens} tokens used so far this session; context-window size isn't `
145
+ + 'known yet (no model request has completed yet).'
146
+ }
147
+ const note = value.estimated ? ' (estimated — no confirmed usage sample yet)' : ''
148
+ return `~${value.usedTokens.toLocaleString()} / ${value.contextWindow.toLocaleString()} tokens `
149
+ + `used (${value.percentUsed}%)${note}.`
90
150
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "smart-compaction",
3
- "version": "0.1.0",
4
- "description": "dsh plugin: a compact_now tool letting the model trigger compaction itself at a safe point, instead of only the automatic token-threshold trigger.",
3
+ "version": "0.2.1",
4
+ "description": "dsh plugin: a compact_now tool letting the model trigger compaction itself at a safe point, plus a context_status tool so it can check real token usage against its context window, instead of only the automatic token-threshold trigger.",
5
5
  "type": "module",
6
6
  "main": "./index.js",
7
7
  "engines": {
@@ -30,7 +30,7 @@
30
30
  "dshhub": {
31
31
  "schemaVersion": 1,
32
32
  "displayName": "Smart Compaction",
33
- "summary": "Gives the model a compact_now tool so it can trigger compaction itself at a safe point, instead of only being interrupted by the automatic token-threshold trigger.",
33
+ "summary": "Gives the model a compact_now tool so it can trigger compaction itself at a safe point, plus a context_status tool to check real token usage against its context window, instead of only being interrupted by the automatic token-threshold trigger.",
34
34
  "categories": [
35
35
  "Sessions & Messages",
36
36
  "Tools & Capabilities"
@@ -41,7 +41,8 @@
41
41
  ],
42
42
  "capabilities": {
43
43
  "provides": [
44
- "tool:compact_now"
44
+ "tool:compact_now",
45
+ "tool:context_status"
45
46
  ]
46
47
  },
47
48
  "compatibility": {
@@ -53,6 +54,7 @@
53
54
  "files": [
54
55
  "index.js",
55
56
  "select-range.js",
57
+ "context-status.js",
56
58
  "cordis.patch.yml"
57
59
  ],
58
60
  "peerDependencies": {